---
title: "Node.js cross compile für ARM Systeme"
date: 2019-03-25T17:45:00+02:00
draft: false
author: robert-kaussow
description: "Cross Compiling für ARM ist nicht schwer braucht aber ein wenig Hintergrundwissen. Zwar gibt es auch für ARM Systeme bereits fertige Pakete für Node.js, unter CentOS 7 auf dem Raspberry Pi haben diese Aufgrund einer zu alten libgcc Version aber nicht funktioniert. Der einzige Ausweg war selbst kompilieren."
categories:
  - Open Source
  - Entwicklung
resources:
  - name: release
    src: 'images/release.png'
    title: Automatisertes Release für Node.js ARM Builds
---

Es soll ja Menschen geben, die freiwillig jedes Stück Software, welches sie einsetzen wollen vorher aus den Quellen selber kompilieren. Ich persönlich gehöre eher zu der Sorte, die sich lieber nach einer alternativen Lösung umschauen, wenn es kein passendes Paket für die eigene Distribution gibt, anstatt sich mit sowas rum zu schlagen. Vielleicht liegt es auch an meinem Skillset aber jedes Mal, wenn ich dann doch gezwungen war eine Anwendung selber zu bauen, ist es in wüste Beschimpfungen meines (unschuldigen) Arbeitsgerätes ausgeartet. Daher mache ich um den Compiler meist einen großen Bogen.

Manchmal gibt es aber Situationen, wo man einfach nicht drum rum kommt. In diesem Fall war das bei Node.js der Fall. Zum Hintergrund: Ich spiele seit einer Weile schon mit einer kleinen Sammlung SmartHome Devices herum. Dazu habe ich mir OpenHab als Zentrale auf einem Raspberry aufgesetzt und ein paar günstige aber duchaus brauchbare IKEA Tradfri Leuchtmittel zugelegt. IKEA setzt für diese Geräte auf Zigbee als Kommunikationsprotokoll, da das entsprechende Binding für OpenHab aber nicht so der Brüller ist, habe ich mich entschieden [zigbee2mqtt](https://github.com/Koenkk/zigbee2mqtt) als Vermittler dazwischenzuschalten. Das hat bis vor ein paar Wochen auch wunderbar funktioniert und dann kam ein Update. Viele neue Funktionen, auf die ich schon eine Weile gewartet habe und ehrlich gesagt auch etwas gespannt war. Mit dem Update kam aber auch ein Breaking Change: Der Node.js Support für Version 8 und 9 wird eingestellt, Mindestvoraussetzung ist jetzt die derzeitige LTS Version 10.x. Soweit ja okay immerhin gibt es mittlerweile für ARMv7 auch pre-compiled Binaries. Also fix die entsprechende Variable in Ansible angepasst, Playbook ausgeführt und siehe da: ERROR.

Toll, das NPM Modul von Ansible kann die Dependencys nicht installieren und quittiert das mit der Fehlermeldung `node: /lib/libstdc++.so.6: version GLIBCXX_3.4.20  not found (required by node)`. Na immerhin ein Ansatzpunkt. Problem an der Sache ist, dass Node die ARM Pakete per Cross Compiler unter Ubuntu 16 mit GCC4.9 baut und die entsprechende Bibliothek von libgcc dynamisch gelinkt ist, sprich nicht Teil der Binary ist, sondern vom Betriebssystem bereitgestellt und geladen wird. Für CentOS 7 ARMv7 gibt es aber keine neuere GCC Version und auch nicht die benötigte Library. Nach ein wenig Internetrecherchen und Gesprächen mit ein paar schlaueren Menschen war die potentielle Lösung allerdings schnell gefunden. Cross Compile auf einem System mit GCC4.9 und der Option `-static-libstdc++` sollte am Ende zu einer funktionierenden Binary führen. Klingt in der Theorie simpel, hat mich bei der konkreten Umsetzung dann doch ein paar Nerven gekostet.

Meine Ansprüche an die Problemlösung:

- Funktionierendes Node.js 10 unter CentOS 7 ARM
- Cross Compiler Setup (macht auf dem Pi einfach kein Spaß und dauert ewig)
- Automatisiertes Setup damit ich das auch für beliebiege andere Node.js Versionen machen kann

Wer direkt Ergebnisse sehen will, anstatt sich den Quatsch hier durchzulesen, kann sich das Setup auf [GitHub](https://github.com/xoxys/nodejs-arm) anschauen. Für den Rest kommt jetzt noch eine kurze Erklärung des Setups. Die Kernkomponenten für die Umsetzung waren Drone CI, ein [Docker Image](https://cloud.docker.com/u/xoxys/repository/docker/xoxys/cc-arm) mit den benötigen Build Tools und der Cross Compiler Toolchain sowie ein Buildscript. Bauen lassen sich neue Versionen bequem über ein Release im Git Repo. Die Node Version wird dabei über den Releasetag gesteuert. Im Hintergrund passiert dabei durch die CI folgendes:

- Drone startet das Cross Compiler Image
- Buildscript des Repositories wird aufgerufen
  - Sources der gewünschten Node Version herunterladen und entpacken
  - Konfiguration für den Compiler erzeugen (Zielarchitektur, ARM spezifische Einstellungen, usw.)
  - make und make install (wichtig ist hier `PORTABLE=1` zu setzen)
  - tar.gz Archiv erzeugen
  - RELEASENODE.md mit den Compileroptionen für diesen Build erzeugen
- Drone erstellt Checksum
- Signieren des Archivs per GPG
- GitHub Release erzeugen, Dateien Anhängen und RELEASENODE.md schreiben

{{< imgproc release resize "950x" />}}

Auch wenn ich in manchen Momenten kurz davor war es aufzugeben und hinzuschmeißen bin ich mit dem Ergebnis zufrieden. Bleibt zu hoffen, dass auch die offiziellen Releases ihre dynamischen Abhängigkeiten ein wenig redizieren und kritische Bibliotheken vielleicht doch mit in die Binary integrieren. Wer will kann die Releases in meine Repo gerne nutzen, Issues, PR oder Anregungen sind selbstverständlich gern gesehen.
