Working Node.js on Raspberry Pi

  • Download Gist
1 Instructions for compiling node0.8.8 with hardfloat.md
Markdown

These instructions work for the Raspberry Pi running Raspbian (hard float) and create a hardware optimized version of NodeJS for the Raspberry PI, (and include a working install and NPM!!!):

1) Install Raspbian - http://www.raspberrypi.org/downloads

2) Install the necessary dependecies:

sudo apt-get install git-core build-essential

(If you just installed git then you need to administer your git identity first, else adding the patches below will fail!!!)

3) Check out Node.js source (0.8.8 or higher):

git clone https://github.com/joyent/node.git
cd node
git checkout v0.8.8 -b v0.8.8

4) Push four patches for V8 and OpenSSL ARM support:

The following two patches are only needed when compiling Node version <0.8.10 (already committed for 0.8.10pre NOT yet for master)

Remark: The first patch is already commited upstream to V8 cutting-edge branch (node is on a lower version of V8). The 2nd patch is pushed to the V8 bug list.

curl https://github.com/joyent/node/commit/25c2940a08453ec206268f5e86cc520b06194d88.patch | git am
curl https://github.com/joyent/node/commit/1d52968d1dbd04053356d62dc0804bcc68deed8a.patch | git am

The following two patches are only needed when compiling Node version <0.8.9 (already committed for master and 0.8.9pre + higher)

curl https://github.com/joyent/node/commit/f8fd9aca8bd01fa7226e1abe75a5bcf903f287ab.patch | git am
curl https://github.com/joyent/node/commit/7142b260c6c299fc9697e6554620be47a6f622a9.patch | git am

5) Configure correctly and 'make':

./configure
make

6) (Optional!) If you want to do the tests then execute (make test can't be used because the standard timeout is too low, i.e. the Rasp PI is too slow... ;-)):

python tools/test.py -t 120 --mode=release simple message

7) Install Node and NPM in your OS:

sudo make install

NOTE on 0.8.8 tests: some tests still have issues (8 tests fail, some are timeouts (as the PI isn't that fast ;-) and one is a known bug)!

Tests.md
Markdown

After compiling node0.8.8 the following 7 (or 8 if IPV6 is not enabled) tests will fail. After checking 3 failing tests are related to the slow processing/slow filesystems, 1 is a known bug and 3 (repl problems) are under investigation.

The following is a known bug. Will be solved in one of the next releases of Node (see https://github.com/joyent/libuv/issues/526).

=== release test-os ===
Path: simple/test-os
hostname = raspberrypi
uptime = 19738.871849515
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-os.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If time is changed from 1000 to 10000 it will complete without error...

=== release test-child-process-fork-net2 ===
Path: simple/test-child-process-fork-net2
assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: timeElasped was not between 190 and 1000 ms
    at process.<anonymous> (/home/pi/node0.8.6/test/simple/test-child-process-fork-net2.js:128:12)
    at process.EventEmitter.emit (events.js:115:20)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-child-process-fork-net2.js

Raspbian doesn't have IPv6 enabled by default and thats where this following test is counting on. if IPv6 is enabled (sudo modprobe ipv6) this test will complete without errors.

=== release test-net-pingpong ===
Path: simple/test-net-pingpong
server listening on /home/pi/node0.8.6/test/tmp/test.sock undefined
server listening on 20988 undefined
connection: 127.0.0.1
connection: undefined
events.js:66
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: listen EAFNOSUPPORT
    at errnoException (net.js:768:11)
    at Server._listen2 (net.js:891:19)
    at listen (net.js:935:10)
    at Server.listen (net.js:992:9)
    at dns.js:71:18
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-net-pingpong.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If timeouts are changed from 10 to 100 and 20 to 200 it will complete without error...

=== release test-http-client-timeout-with-data ===
Path: simple/test-http-client-timeout-with-data
assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: 0 == 1
    at process.<anonymous> (/home/pi/node0.8.6/test/simple/test-http-client-timeout-with-data.js:30:10)
    at process.EventEmitter.emit (events.js:115:20)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-http-client-timeout-with-data.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If run seperately this test will complete without error!!!

=== release test-eio-race ===
Path: simple/test-eio-race
trying to kill event loop ...
could not kill event loop, retrying...
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-eio-race.js
--- TIMEOUT ---

All the following REPL test fail are under investigation (possibly due to timing issues generated by the slow processing of the Rasp PI).

=== release test-debugger-repl ===
Path: simple/test-debugger-repl
./node debug --port=13683 /home/pi/node0.8.6/test/fixtures/breakpoints.js
line> debug>< debugger listening on port 13683
line> debug>connecting... ok
line> debug>break in test/fixtures/breakpoints.js:1
line>  1 debugger;
line>  2 function a(x) {
line>  3   var i = 10;
line> debug>break in test/fixtures/breakpoints.js:11
Error: Timeout. Expected: /9/
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-debugger-repl.js
=== release test-force-repl ===
Path: simple/test-force-repl
timers.js:103
            if (!process.listeners('uncaughtException').length) throw e;
                                                                      ^
Error: timeout!
    at Object._onTimeout (/home/pi/node0.8.6/test/simple/test-force-repl.js:30:9)
    at Timer.list.ontimeout (timers.js:101:19)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-force-repl.js
=== release test-debugger-repl-utf8 ===
Path: simple/test-debugger-repl-utf8
./node debug --port=13683 /home/pi/node0.8.6/test/fixtures/breakpoints_utf8.js
line> debug>< debugger listening on port 13683
line> debug>connecting... ok
dying badly
Error: Timeout. Expected: /break in .*:1/
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-debugger-repl-utf8.js

I've been trying a bunch of different build instructions to get v0.8.6 to compile - none work. Following your guidelines I got to compilation but running node or npm throws "Illegal Instruction." Do you have final build instructions that work? Binary perhaps? :)

If you followed the instructions in the 'Instructions node0.8.6' file then everything should work. I compiled v0.8.6 numerous times the last 4 days and it always worked.. ;-)

Because of the "Illegal Instruction" error, it looks like you didn't revert commit https://github.com/joyent/node/commit/90efdb3a5b7eecb23d78cffee24c3181280efa56 or didn't apply the v0.8.2-release-raspberrypi patch. If you don't know how to revert the commit you can also replace the content of the 'configure' file with the following content:

#!/usr/bin/env python
import optparse
import os
import pprint
import re
import subprocess
import sys

CC = os.environ.get('CC', 'cc')

root_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(root_dir, 'deps', 'v8', 'tools'))

# parse our options
parser = optparse.OptionParser()

parser.add_option("--debug",
    action="store_true",
    dest="debug",
    help="Also build debug build")

parser.add_option("--prefix",
    action="store",
    dest="prefix",
    help="Select the install prefix (defaults to /usr/local)")

parser.add_option("--without-npm",
    action="store_true",
    dest="without_npm",
    help="Don\'t install the bundled npm package manager")

parser.add_option("--without-waf",
    action="store_true",
    dest="without_waf",
    help="Don\'t install node-waf")

parser.add_option("--without-ssl",
    action="store_true",
    dest="without_ssl",
    help="Build without SSL")

parser.add_option("--without-snapshot",
    action="store_true",
    dest="without_snapshot",
    help="Build without snapshotting V8 libraries. You might want to set"
         " this for cross-compiling. [Default: False]")

parser.add_option("--shared-v8",
    action="store_true",
    dest="shared_v8",
    help="Link to a shared V8 DLL instead of static linking")

parser.add_option("--shared-v8-includes",
    action="store",
    dest="shared_v8_includes",
    help="Directory containing V8 header files")

parser.add_option("--shared-v8-libpath",
    action="store",
    dest="shared_v8_libpath",
    help="A directory to search for the shared V8 DLL")

parser.add_option("--shared-v8-libname",
    action="store",
    dest="shared_v8_libname",
    help="Alternative lib name to link to (default: 'v8')")

parser.add_option("--shared-openssl",
    action="store_true",
    dest="shared_openssl",
    help="Link to a shared OpenSSl DLL instead of static linking")

parser.add_option("--shared-openssl-includes",
    action="store",
    dest="shared_openssl_includes",
    help="Directory containing OpenSSL header files")

parser.add_option("--shared-openssl-libpath",
    action="store",
    dest="shared_openssl_libpath",
    help="A directory to search for the shared OpenSSL DLLs")

parser.add_option("--shared-openssl-libname",
    action="store",
    dest="shared_openssl_libname",
    help="Alternative lib name to link to (default: 'crypto,ssl')")

# deprecated
parser.add_option("--openssl-use-sys",
    action="store_true",
    dest="shared_openssl",
    help=optparse.SUPPRESS_HELP)

# deprecated
parser.add_option("--openssl-includes",
    action="store",
    dest="shared_openssl_includes",
    help=optparse.SUPPRESS_HELP)

# deprecated
parser.add_option("--openssl-libpath",
    action="store",
    dest="shared_openssl_libpath",
    help=optparse.SUPPRESS_HELP)

parser.add_option("--no-ssl2",
    action="store_true",
    dest="no_ssl2",
    help="Disable OpenSSL v2")

parser.add_option("--shared-zlib",
    action="store_true",
    dest="shared_zlib",
    help="Link to a shared zlib DLL instead of static linking")

parser.add_option("--shared-zlib-includes",
    action="store",
    dest="shared_zlib_includes",
    help="Directory containing zlib header files")

parser.add_option("--shared-zlib-libpath",
    action="store",
    dest="shared_zlib_libpath",
    help="A directory to search for the shared zlib DLL")

parser.add_option("--shared-zlib-libname",
    action="store",
    dest="shared_zlib_libname",
    help="Alternative lib name to link to (default: 'z')")

parser.add_option("--with-dtrace",
    action="store_true",
    dest="with_dtrace",
    help="Build with DTrace (default is true on supported systems)")

parser.add_option("--without-dtrace",
    action="store_true",
    dest="without_dtrace",
    help="Build without DTrace")

parser.add_option("--with-etw",
    action="store_true",
    dest="with_etw",
    help="Build with ETW (default is true on Windows)")

parser.add_option("--without-etw",
    action="store_true",
    dest="without_etw",
    help="Build without ETW")

# CHECKME does this still work with recent releases of V8?
parser.add_option("--gdb",
    action="store_true",
    dest="gdb",
    help="add gdb support")

parser.add_option("--dest-cpu",
    action="store",
    dest="dest_cpu",
    help="CPU architecture to build for. Valid values are: arm, ia32, x64")

parser.add_option("--no-ifaddrs",
    action="store_true",
    dest="no_ifaddrs",
    help="Use on deprecated SunOS systems that do not support ifaddrs.h")

(options, args) = parser.parse_args()


def b(value):
  """Returns the string 'true' if value is truthy, 'false' otherwise."""
  if value:
    return 'true'
  else:
    return 'false'


def pkg_config(pkg):
  cmd = os.popen('pkg-config --libs %s' % pkg, 'r')
  libs = cmd.readline().strip()
  ret = cmd.close()
  if (ret): return None

  cmd = os.popen('pkg-config --cflags %s' % pkg, 'r')
  cflags = cmd.readline().strip()
  ret = cmd.close()
  if (ret): return None

  return (libs, cflags)


def host_arch_cc():
  """Host architecture check using the CC command."""

  try:
    p = subprocess.Popen(CC.split() + ['-dM', '-E', '-'],
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
  except OSError:
    print '''Node.js configure error: No acceptable C compiler found!

        Please make sure you have a C compiler installed on your system and/or
        consider adjusting the CC environment variable if you installed
        it in a non-standard prefix.
        '''
    sys.exit()

  p.stdin.write('\n')
  out = p.communicate()[0]

  out = str(out).split('\n')

  k = {}
  for line in out:
    import shlex
    lst = shlex.split(line)
    if len(lst) > 2:
      key = lst[1]
      val = lst[2]
      k[key] = val

  matchup = {
    '__x86_64__'  : 'x64',
    '__i386__'    : 'ia32',
    '__arm__'     : 'arm',
  }

  rtn = 'ia32' # default

  for i in matchup:
    if i in k and k[i] != '0':
      rtn = matchup[i]
      break

  return rtn


def host_arch_win():
  """Host architecture check using environ vars (better way to do this?)"""

  arch = os.environ.get('PROCESSOR_ARCHITECTURE', 'x86')

  matchup = {
    'AMD64'  : 'x64',
    'x86'    : 'ia32',
    'arm'    : 'arm',
  }

  return matchup.get(arch, 'ia32')


def compiler_version():
  try:
    proc = subprocess.Popen(CC.split() + ['--version'], stdout=subprocess.PIPE)
  except WindowsError:
    return (0, False)

  is_clang = 'clang' in proc.communicate()[0].split('\n')[0]

  proc = subprocess.Popen(CC.split() + ['-dumpversion'], stdout=subprocess.PIPE)
  version = tuple(map(int, proc.communicate()[0].split('.')))

  return (version, is_clang)


def configure_node(o):
  # TODO add gdb
  o['variables']['v8_no_strict_aliasing'] = 1 # work around compiler bugs
  o['variables']['node_prefix'] = os.path.expanduser(options.prefix or '')
  o['variables']['node_install_npm'] = b(not options.without_npm)
  o['variables']['node_install_waf'] = b(not options.without_waf)
  o['default_configuration'] = 'Debug' if options.debug else 'Release'

  host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()
  target_arch = options.dest_cpu or host_arch
  o['variables']['host_arch'] = host_arch
  o['variables']['target_arch'] = target_arch

  # V8 on ARM requires that armv7 is set. CPU Model detected by
  # the presence of __ARM_ARCH_7__ and the like defines in compiler

  if target_arch == 'arm':
    o['variables']['armv7'] = 0 # FIXME

  # clang has always supported -fvisibility=hidden, right?
  cc_version, is_clang = compiler_version()
  if not is_clang and cc_version < (4,0,0):
    o['variables']['visibility'] = ''

  # By default, enable DTrace on SunOS systems. Don't allow it on other
  # systems, since it won't work.  (The MacOS build process is different than
  # SunOS, and we haven't implemented it.)
  if sys.platform.startswith('sunos'):
    o['variables']['node_use_dtrace'] = b(not options.without_dtrace)
  elif b(options.with_dtrace) == 'true':
    raise Exception('DTrace is currently only supported on SunOS systems.')
  else:
    o['variables']['node_use_dtrace'] = 'false'

  if options.no_ifaddrs:
    o['defines'] += ['SUNOS_NO_IFADDRS']

  # By default, enable ETW on Windows.
  if sys.platform.startswith('win32'):
    o['variables']['node_use_etw'] = b(not options.without_etw);
  elif b(options.with_etw) == 'true':
    raise Exception('ETW is only supported on Windows.')
  else:
    o['variables']['node_use_etw'] = 'false'


def configure_libz(o):
  o['variables']['node_shared_zlib'] = b(options.shared_zlib)

  # assume shared_zlib if one of these is set?
  if options.shared_zlib_libpath:
    o['libraries'] += ['-L%s' % options.shared_zlib_libpath]
  if options.shared_zlib_libname:
    o['libraries'] += ['-l%s' % options.shared_zlib_libname]
  elif options.shared_zlib:
    o['libraries'] += ['-lz']
  if options.shared_zlib_includes:
    o['include_dirs'] += [options.shared_zlib_includes]


def configure_v8(o):
  o['variables']['v8_use_snapshot'] = b(not options.without_snapshot)
  o['variables']['node_shared_v8'] = b(options.shared_v8)

  # assume shared_v8 if one of these is set?
  if options.shared_v8_libpath:
    o['libraries'] += ['-L%s' % options.shared_v8_libpath]
  if options.shared_v8_libname:
    o['libraries'] += ['-l%s' % options.shared_v8_libname]
  elif options.shared_v8:
    o['libraries'] += ['-lv8']
  if options.shared_v8_includes:
    o['include_dirs'] += [options.shared_v8_includes]


def configure_openssl(o):
  o['variables']['node_use_openssl'] = b(not options.without_ssl)
  o['variables']['node_shared_openssl'] = b(options.shared_openssl)

  if options.without_ssl:
    return

  if options.no_ssl2:
    o['defines'] += ['OPENSSL_NO_SSL2=1']

  if options.shared_openssl:
    (libs, cflags) = pkg_config('openssl') or ('-lssl -lcrypto', '')

    if options.shared_openssl_libpath:
      o['libraries'] += ['-L%s' % options.shared_openssl_libpath]

    if options.shared_openssl_libname:
      libnames = options.shared_openssl_libname.split(',')
      o['libraries'] += ['-l%s' % s for s in libnames]
    else:
      o['libraries'] += libs.split()

    if options.shared_openssl_includes:
      o['include_dirs'] += [options.shared_openssl_includes]
    else:
      o['cflags'] += cflags.split()


output = {
  'variables': {},
  'include_dirs': [],
  'libraries': [],
  'defines': [],
  'cflags': [],
}

configure_node(output)
configure_libz(output)
configure_v8(output)
configure_openssl(output)

# variables should be a root level element,
# move everything else to target_defaults
variables = output['variables']
del output['variables']
output = {
  'variables': variables,
  'target_defaults': output
}
pprint.pprint(output, indent=2)

def write(filename, data):
  filename = os.path.join(root_dir, filename)
  print "creating ", filename
  f = open(filename, 'w+')
  f.write(data)

write('config.gypi', "# Do not edit. Generated by the configure script.\n" +
  pprint.pformat(output, indent=2) + "\n")

write('config.mk', "# Do not edit. Generated by the configure script.\n" +
  ("BUILDTYPE=%s\n" % ('Debug' if options.debug else 'Release')))

if os.name == 'nt':
  gyp_args = ['-f', 'msvs', '-G', 'msvs_version=2010']
else:
  gyp_args = ['-f', 'make']

subprocess.call([sys.executable, 'tools/gyp_node'] + gyp_args)

Good luck!!

@beriberikix If you follow the new gist it is possible to compile and install Nodejs0.8.7 on your Raspberry Pi. Just forget my previous answer, thats not correct anymore as it is now possible to compile NodeJS0.8.7... ;-)

Hi there,
I followed your latest instructions for v0.8.8 apart from apply TooTallNate's patches, as they lead to a 404.
configure, make, make install complete however running 'node' or 'node index.js' outputs the "Illegal instruction" error.

node -v outputs: v0.8.8

I've tried a couple of times to make sure but no luck. I've seen in other instructions that people recommend exporting a few environment variables (CXXFLAGS, etc..) Would there be any other steps to be aware of?

Thanks for your work on this!

Still not sure how to get 0.8.8 working but I got 0.8.7 by adapting this guy's script: https://gist.github.com/3245130#gistcomment-399244

@originalmind Oeps, TooTallNate removed his branch so I made my own and referenced it. The receipt above should work again now!!

With the other instructions (exporting variables etc) you get a not working SSL module or a partly working older Raspbian system shared SSL module that is not optimized for use with Node....

Note that if you're setting this up on a rather fresh SD card (as installing git-core implies) you will need to first configure your git identity — otherwise the patches will not apply! I'd quickly pasted in the full "shell script" above without watching and so the build still failed.

I can confirm this mostly works. Thanks @stolsma for this post!

It doesn't seem to fix the HTTPS protocol issue for me though:

var f = require('fermata');
f.raw({base:'https://xs4all.nl'}).get(function (e,d) { console.log(e,d); });
// Error: 1074672352:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:683:

Based on your comment I was under the impression this should work?

Amazing, Worked perfectly!

Worked perfectly here too, thanks !

@natevw That HTTPS problem you are seeing is probably related to the use of openssl 1.0.0f in node i.r.t. XS4ALL using 512 bit and higher stuff... They are busy implementing openssl 1.0.1c now in node master so maybe that helps ???

To all: Today the node team landed the two remaining patches on the 0.8.10pre branch! So it looks like that these instructions are not needed anymore when node 0.8.10 is released!!! Yippieajee!!!!!! :-)

I have also updated the patch locations to the node repository for everyone still trying to compile node <0.8.10

Thanks Sander, that worked a treat on my pi

Thanks mate, worked first time. Nice post

Install it in 5 min, no need to compile no more :)

wget http://nodejs.org/dist/v0.10.2/node-v0.10.2-linux-arm-pi.tar.gz
tar xvzf node-v0.10.2-linux-arm-pi.tar.gz

you only need to add it to your path variables here:

nano /etc/profile

Check https://github.com/DonaldDerek/rPi-cheat-sheet for more info..

@DonaldDerek, thanks!

I didn't have to add any path. I only had to symbolic link node and npm to be under /usr/bin

sudo ln -s ./node-v0.10.2-linux-arm-pi/bin/node /usr/bin/node
sudo ln -s ./node-v0.10.2-linux-arm-pi/bin/npm /usr/bin/npm

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.