Paketbaurichtlinien für Node.js

Die offizielle Node.js-Richtlinie zu globalen Bibliothekspaketen lautet: „…wenn möglich, sollten globale Bibliothekspakete vermieden werden.“ In Fedora vertreten wir dieselbe Ansicht bezüglich unserer Node.js-Pakete. Sie können ein Paket bereitstellen, das Node.js verwendet, sollten dann aber alle benötigten Node.js-Bibliotheken mitliefern.

Was paketiert werden kann

  • Der Interpreter, Entwicklungs-Header/Bibliotheken und die verschiedenen Werkzeuge zur Verwaltung von Installationen auf Projektebene.

    • Beispiele: nodejs, npm, yarn

  • Pakete, die Anwendungen bereitstellen, die Benutzer in ihrer Shell verwenden möchten.

    • Beispiele: uglify

Richtlinien zur Benennung

  • Anwendungspakete, die hauptsächlich Werkzeuge (und nicht Bibliotheken) bereitstellen, die für Node.js geschrieben wurden, müssen stattdessen den allgemeinen Richtlinien zur Benennung folgen.

  • Der Name eines Node.js-Erweiterungs-/Bibliothekspakets muss mit nodejs- beginnen, gefolgt vom Upstream-Namen oder dem Namen aus der npm-Registry. Beispiel: nodejs-foomodule. Obwohl es unüblich ist, dass ein Paketname node enthält, muss in diesem Fall dennoch das Präfix nodejs hinzugefügt werden. Beispielsweise enthält die npm-Registry ein uuid- und ein node-uuid-Modul, die entsprechend nodejs-uuid und nodejs-node-uuid heißen müssten.

BuildRequires

Um ein Paket zu erstellen, das ein Node.js-Modul ist, Node.js-Module bündelt oder verwendet oder Node.js zum Erstellen oder Testen benötigt, benötigen Sie Folgendes:

BuildRequires: nodejs-devel

Makros

Die folgenden Makros sind für Sie definiert:

Makro Normale Definition Anmerkungen

__nodejs

%{_bindir}/node

Der Node.js-Interpreter

nodejs_version

e.g. 0.9.5

Die aktuell installierte Version von Node.js.

nodejs_sitelib

%{_prefix}/lib/node_modules

Installationsort der in reinem JavaScript geschriebenen Node.js-Module

nodejs_sitearch

%{_prefix}/lib/node_modules

Installationsort der in nativem C++ geschriebenen Node.js-Module

nodejs_symlink_deps

%{_prefix}/lib/rpm/nodejs-symlink-deps

Siehe [Symlinking Dependencies] unten.

nodejs_fixdep

%{_prefix}/lib/rpm/nodejs-fixdep

Siehe [Correcting Dependencies] unten.

nodejs_arches

%{ix86} x86_64 %{arm}

Siehe #ExclusiveArch. Dieses Makro wird ab F19 von redhat-rpm-config bereitsgestellt, daher funktioniert es korrekt mit Koji.

nodejs_default_filter

%global __provides_exclude_from ^%{nodejs_sitearch}/.*\\.node$

Filtert unerwünschte „Provides“ aus nativen Modulen. Siehe [Filtering Unwanted Provides] unten.

Diese Makros werden vom Paket nodejs-packaging bereitgestellt.

Während %install oder beim Auflisten von %files können Sie mit dem Makro %nodejs_sitelib oder %nodejs_sitearch angeben, wo die installierten Module zu finden sind. Zum Beispiel:

%files
# A pure JavaScript node module
%{nodejs_sitelib}/foomodule/
# A native node module
%{nodejs_sitearch}/barmodule/

Durch die Verwendung dieses Makros anstelle der Angabe des Verzeichnisses in der Spec-Datei wird sichergestellt, dass Ihre Spec-Datei zur installierten Node.js-Version kompatibel bleibt, selbst wenn sich die Verzeichnisstruktur radikal ändert (zum Beispiel, wenn %nodejs_sitelib nach %{_datadir} verschoben wird).

ExclusiveArch

Die von Node.js verwendete V8-JavaScript-Laufzeitumgebung nutzt einen Just-in-Time-Compiler (JIT), der speziell auf die jeweilige Architektur abgestimmt ist und manuell auf neue Architekturen portiert werden muss, die ihn unterstützen sollen. Node.js-Pakete müssen daher eine ExclusiveArch-Zeile enthalten, die sie auf diese Architekturen beschränkt.

Das Makro %{nodejs_arches} vereinfacht dies, daher müssen reine JavaScript-Pakete Folgendes verwenden:

ExclusiveArch: %{nodejs_arches} noarch

Native (binäre) Pakete müssen noarch weglassen und stattdessen nur %{nodejs_arches} oder gegebenenfalls die Architekturen auflisten.

Gebündelte Lizenzen

Die Lizenzen der gebündelten Node.js-Module müssen in der Spec-Datei enthalten sein. Wenn Sie unser Bundling-Skript verwenden, finden Sie sie in <Paket>-<Version>-bundled-licenses.txt. Es wird empfohlen, <Paket>-<Version>-bundled-licenses.txt in das RPM-Paket aufzunehmen.

Bei jeder Aktualisierung Ihres Pakets müssen Sie die enthaltenen Lizenzen anhand der Fedora-Softwarelizenzliste überprüfen. Beachten Sie, dass auch vorkompiliertes/minimiertes JavaScript enthalten sein kann; die Lizenzprüfung ist jedoch auch hierfür erforderlich (siehe JavaScript-Richtlinien).

Führen Sie alle eindeutigen Lizenzen in der Zeile „License:“ Ihrer Spec-Datei auf. Trennen Sie die einzelnen Lizenzen durch das Wort „and“.

License:  <license1> AND <license2> AND <license3>
...
Source3:        %{npm_name}-%{version}-bundled-licenses.txt
...
%prep
...
cp %{SOURCE3} .
...
%files
%license LICENSE %{npm_name}-%{version}-bundled-licenses.txt

Bei weiteren Fragen lesen Sie die Fedora Lizenzierungsrichtlinien.

Verwendung von Tarballs aus der npm-Registry

Die gängigste Methode zum Ausliefern der meisten Node-Module ist das Herunterladen von Tarballs aus der npm-Registry. Die Source0-Adresse solcher Module sollte folgendes Format haben: https://registry.npmjs.org/<Modulname>/-/<Modulname>-<Version>.tgz. Zum Beispiel:

Source0:  https://registry.npmjs.org/npm/-/npm-1.1.70.tgz

Diese Methode sollte der Verwendung von Git-Checkouts oder automatisch von GitHub generierten Tarballs vorgezogen werden.

Diese Tarballs speichern den Inhalt des Moduls in einem package-Verzeichnis. Daher sollte jedes Paket, das diese Tarballs verwendet, Folgendes in %prep verwenden:

%prep
%setup -q -n package

Tarballs für Bündelung verwenden

Für die Bündelung von Node-Modulabhängigkeiten empfiehlt sich die Verwendung von Tarballs. Diese Tarballs sollten unabhängig vom Hauptpaketquellcode sein. Es sollten zwei Tarballs vorhanden sein: einer für das Binärpaket zur Laufzeit und einer für Tests während des Paketbaus. Dadurch wird ein kleineres Installationspaket erzeugt.

Diese Tarballs speichern die gebündelten Module namens node_modules_prod und node_modules_dev in einem Verzeichnis.

Falls Ihre Pakete keinen der Tarballs benötigen, passen Sie diese Anweisungen entsprechend an. Falls sie den Produktions-Tarball nicht benötigen, entfernen Sie Source1 sowie die Abschnitte %build und %install. Falls sie den Entwicklungs-Tarball nicht benötigen, entfernen Sie Source2 und den Abschnitt %check.

Hinweis 1: Die Einrichtung der Produktions- und Entwicklungs-Tarballs wird demnächst über ein Makro erfolgen. Zum jetzigen Zeitpunkt ist dies noch nicht der Fall.

Hinweis 2: Der Tarball mit den Entwicklungsabhängigkeiten muss in %check und nicht in %prep entpackt werden, um zu vermeiden, dass versehentlich die nicht paketierten Abhängigkeiten mitgebündelt werden, die nur zum Testen benötigt werden.

...
Source1:       %{npm_name}-%{version}-nm-prod.tgz
Source2:       %{npm_name}-%{version}-nm-dev.tgz
...
%prep
...
# Setup bundled runtime(prod) node modules
tar xfz %{SOURCE1}
mkdir -p node_modules
pushd node_modules
ln -s ../node_modules_prod/* .
ln -s ../node_modules_prod/.bin .
popd
...
%install
...
# Copy over bundled nodejs modules
cp -pr node_modules node_modules_prod %{buildroot}%{nodejs_sitelib}/%{npm_name}
...
%check
%{__nodejs} -e 'require("./")'
# Setup bundled dev node_modules for testing
#   Note: this cannot be in %%prep or the dev node_modules
#            can get pulled into the regular rpm
tar xfz %{SOURCE2}
pushd node_modules
ln -s ../node_modules_dev/* .
popd
pushd node_modules/.bin
ln -s ../../node_modules_dev/.bin/* .
popd
# Example test run using the binary in ./node_modules/.bin/
./node_modules/.bin/vows --spec --isolate
...

Installation von Modulen

Die meisten Node-Module verfügen nicht über einen eigenen Installationsmechanismus. Stattdessen nutzen sie npm für die Installation. npm darf nicht zur Installation von Modulen in Fedora-Paketen verwendet werden, da hierfür üblicherweise ein Netzwerkzugriff erforderlich ist.

Installieren Sie die Dateien stattdessen manuell am richtigen Ort mit install oder cp. Die meisten Dateien sollten in %{nodejs_sitelib}/ installiert werden, die Dokumentation hingegen über %doc. Falls das Modul architekturunabhängige Inhalte außer JavaScript-Code enthält, sollten diese in %{_datadir} installiert und das Modul entsprechend angepasst werden.

Client-seitiges JavaScript

Viele Node-Module enthalten JavaScript, das sowohl client- als auch serverseitig verwendet werden kann, und es ist mitunter schwierig, Code zu identifizieren, der ausschließlich für die Verwendung im Browser bestimmt ist. Da es derzeit keine Richtlinien für die Paketierung von clientseitigem JavaScript gibt und das Bündeln solchen Codes in Fedora derzeit zulässig ist, ist es erlaubt, clientseitiges JavaScript mit Node.js-Modulen in %{nodejs_sitelib} zu bündeln.

Automatische „Requires“ und „Provides“

Das nodejs-Paket enthält einen automatischen Generator für Requires und Provides, der automatisch versionierte Abhängigkeiten basierend auf den Informationen in der package.json-Datei eines Moduls und der gebündelten Abhängigkeiten hinzufügt. Zusätzliche Requires werden nativen (binären) Modulen hinzugefügt, um ABI-Brüche in Node oder der V8-JavaScript-Laufzeitumgebung zu verhindern. Zusätzlich wird für e eine Zeile „Provides: bundled()“ hinzugefügt.

„Provides“ für npm

Es fügt außerdem virtuelle Bereitstellungen in der Form npm(<Modulname>) hinzu, um Module zu kennzeichnen, die in der npm-Registry gelistet sind (das Modul ist auf npmjs.org gelistet). Ist ein Modul nicht in der npm-Registry gelistet, darf es dieses „Provides:“ nicht bereitstellen. Module, die nicht in der npm-Registry gelistet sind, sollten private in ihrer package.json-Datei auf true setzen. Anderenfalls muss die package.json-Datei entsprechend angepasst werden.

„Provides“ für Bündelungen

Es fügt außerdem automatisch gebündelte „Provides“ in der Form bundled(<gebündelter_Modulname>) = <gebündelte_Modulversion> hinzu, um gebündelte Module zu identifizieren. Gebündelte Module müssen sich entweder im Verzeichnis node_modules oder node_modules_prod befinden, um automatisch hinzugefügt zu werden.

Korrigieren der Abhängigkeiten

Nur für nicht-gebündelte Module.

Gelegentlich sind die in der Datei package.json aufgeführten Abhängigkeiten fehlerhaft. Beispielsweise kann das Modul mit einer neueren Version einer Abhängigkeit funktionieren als der in der package.json explizit angegebenen. Um dies zu korrigieren, verwenden Sie das RPM-Makro %nodejs_fixdep. Dieses Makro sollte in %prep verwendet werden und korrigiert die package.json, so dass sie die korrekten Abhängigkeitsinformationen enthält.

Um eine Abhängigkeit so zu ändern, dass keine bestimmte Version angegeben wird, rufen Sie einfach %nodejs_fixdep mit dem npm-Modulnamen der Abhängigkeit auf. Dadurch wird die Version in package.json auf * geändert (oder eine Version hinzugefügt, falls diese noch nicht aufgeführt war). Beispiel:

%prep
%setup -q -n package
%nodejs_fixdep foomodule

Sie können auch eine Version angeben:

%prep
%setup -q -n package
%nodejs_fixdep foomodule '>2.0'

Das zweite Argument für %nodejs_fixdep muss ein gültiger package.json-Versionsbezeichner sein, wie in \`man npm json\++` erläutert.

Sie können auch eine Abhängigkeit entfernen:

%prep
%setup -q -n package
%nodejs_fixdep -r foomodule

Symbolisches Verlinken von Abhängigkeiten

Nur für nicht-gebündelte Module.

Node.js und npm erfordern, dass Abhängigkeiten explizit in ein node_modules-Verzeichnis innerhalb des Modulverzeichnisses eingebunden oder verlinkt werden. Um dies zu vereinfachen, stellt das Makro %nodejs_symlink_deps bereit, das automatisch einen node_modules-Baum mit symbolischen Links für jede in der package.json aufgeführte Abhängigkeit erstellt. Dieses Makro sollte im %install-Abschnitt jedes Node.js-Modulpakets aufgerufen werden.

Native Module mit node-gyp erstellen

Die meisten nativen Module verwenden das Werkzeug`node-gyp` für die Erstellung. Dieses Werkzeug konfiguriert und verwendet das Build-Framework gyp, um Add-on-Module zu erstellen, die mit Node.js und dem von diesem verwendeten V8 JavaScript-Interpreter interagieren können.

Das Build-Framework WAF wurde vom Upstream-Projekt aufgegeben und wird in Fedora nicht unterstützt.

BuildRequires

Um ein natives Modul zu erstellen, das für die Erstellung mit node-gyp konzipiert ist, fügen Sie BuildRequires: node-gyp sowie BuildRequires: nodejs-devel und -devel-Pakete für alle vom Modul benötigten gemeinsam genutzten Bibliotheken hinzu.

%build

Manche native Module verfügen über Makefiles oder andere Build-Prozesse, die spezielle Anforderungen des Moduls erfüllen, wie beispielsweise die Verknüpfung mit Systemversionen von Abhängigkeiten. Falls vorhanden, sollten diese verwendet werden. Informationen darüber, welchen Befehl npm zum Erstellen dieser Module ausführt, finden Sie in der Datei package.json des Moduls.

Die meisten Module verwenden das Standard-node-gyp und enthalten möglicherweise keine Bauanweisungen in der Datei package.json. Um diese zu erstellen, verwenden Sie einfach Folgendes:

%build
export CXXFLAGS="%{optflags}"
node-gyp rebuild

Beachten Sie, dass einige Module etwas wie node-gyp configure && node-gyp build angeben können. Dies entspricht node-gyp rebuild.

%install

node-gyp erstellt im Rahmen seines Bauprozesses eine Shared-Object-Datei mit der Erweiterung .node, die sich im Verzeichnis build/Release befinden sollte. Diese Datei kann als Haupteinstiegspunkt für die Bibliothek verwendet oder von JavaScript-Wrapper-Code genutzt werden, der im Modul enthalten ist.

Wird das gemeinsam genutzte Objekt als Haupteinstiegspunkt verwendet, muss es unter %{nodejs_sitelib}/<Modulname>/index.node installiert werden. Die Funktion require() lädt es automatisch, falls keine entsprechende index.js-Datei oder kein anderer Einstiegspunkt in package.json definiert ist, der diese überschreibt. Beispiel:

%install
mkdir -p %{buildroot}%{nodejs_sitelib}/foomodule
cp -p build/Release/index.node package.json %{buildroot}%{nodejs_sitelib}/foomodule/

Wird das gemeinsam genutzte Objekt von JavaScript-Wrapper-Code aufgerufen, ist die Situation etwas komplizierter.

Wenn das Modul das npm-Modul bindings verwendet, sollte die Shared-Object-Datei in %{nodejs_sitelib}/<Modulname>/build/<Modulname>.node installiert werden. Dieses Verzeichnis befindet sich am Anfang des Suchpfads von bindings, und dort erstellt node-gyp üblicherweise einen symbolischen Link zum eigentlichen Speicherort der Shared-Object-Datei. Beispiel:

%install
mkdir -p %{buildroot}%{nodejs_sitelib}/foomodule/build
cp -p package.json wrapper.js %{buildroot}%{nodejs_sitelib}/foomodule/
cp -p build/Release/foomodule.node %{buildroot}%{nodejs_sitelib}/foomodule/build/

Wenn das Modul build/Release/<Modulname>.node fest codiert, sollte das Modul so gepatcht werden, dass stattdessen build/<Modulname>.node verwendet wird. Außerdem sollte dem Upstream-Projekt empfohlen werden, das Modul bindings zu verwenden, da ihr Modul bei der Verwendung von Debug-Builds von Node.js möglicherweise nicht mehr funktioniert.

Wenn das Modul eigene Makefiles verwendet, um die gemeinsam genutzten Objektdateien an einem bestimmten Ort zu lokalisieren, dann sollten diese Dateien an diesem Ort installiert werden.

Filtern unerwünschter „Provides“

RPM fügt den gemeinsam genutzten Objektdateien nativer Module automatisch unerwünschte virtuelle „Provides“ hinzu. Um diese zu entfernen, fügen Sie %{?nodejs_default_filter} am Anfang der Spec-Datei des Pakets ein. Weitere Informationen finden Sie hier.

Bautest in %check

Alle Spec-Dateien für Node.js-Module müssen einen %check-Abschnitt mit (mindestens) folgender Zeile enthalten:

%{__nodejs} -e 'require("./")'

Dieser Test stellt sicher, dass das Modul tatsächlich geladen werden kann, wodurch Situationen vermieden werden, in denen eine neue Upstream-Version eine neue Abhängigkeit hinzugefügt hat, ohne dass der Paketierer dies bemerkt.

Alle weiteren vom Upstream-Projekt bereitgestellten Tests sollten nach Möglichkeit ebenfalls ausgeführt werden.

Zur Vereinfachung akzeptiert %nodejs_symlink_deps auch das Argument --check, wodurch es im aktuellen Arbeitsverzeichnis anstatt im Buildroot ausgeführt wird. Sie können dies im Abschnitt %check verwenden, um Abhängigkeiten für die Ausführung von Tests verfügbar zu machen. Bei Verwendung dieses Arguments werden auch die Entwicklungsabhängigkeiten, die im Schlüssel "devDependencies" in der Datei package.json aufgeführt sind, verlinkt.

Bündelungsskript

Es wird empfohlen, das im Fedora-Paket nodejs-packaging-bundler enthaltene Skript nodejs-packaging-bundler zu verwenden. Weitere Informationen dazu finden Sie unter Fedora nodejs-packaging repo.

Beispiel-Spec-Datei

%global npm_name tape

Name:           nodejs-%{npm_name}
Version:        5.1.1
Release:        1%{?dist}
Summary:        Tap-producing test harness for Node.js and browsers

License:        MIT AND ISC
URL:            https://github.com/substack/tape
Source0:        https://registry.npmjs.org/%{npm_name}/-/%{npm_name}-%{version}.tgz
Source1:        %{npm_name}-%{version}-nm-prod.tgz
Source2:        %{npm_name}-%{version}-nm-dev.tgz
Source3:        %{npm_name}-%{version}-bundled-licenses.txt

BuildArch:      noarch
ExclusiveArch:  %{nodejs_arches} noarch

Requires:       nodejs
BuildRequires:  nodejs-devel

%description
%{summary}.


%prep
%setup -q -n package
cp %{SOURCE3} .
# Setup bundled runtime(prod) node modules
tar xfz %{SOURCE1}
mkdir -p node_modules
pushd node_modules
ln -s ../node_modules_prod/* .
ln -s ../node_modules_prod/.bin .
popd

%build
#nothing to do

%install
mkdir -p %{buildroot}%{nodejs_sitelib}/%{npm_name}
cp -pr package.json index.js lib/ \
    %{buildroot}%{nodejs_sitelib}/%{npm_name}
# Copy over bundled nodejs modules
cp -pr node_modules node_modules_prod \
    %{buildroot}%{nodejs_sitelib}/%{npm_name}

mkdir -p %{buildroot}%{nodejs_sitelib}/tape/bin
install -p -D -m0755 bin/tape %{buildroot}%{nodejs_sitelib}/tape/bin/tape
mkdir -p %{buildroot}%{_bindir}
ln -sr %{nodejs_sitelib}/tape/bin/tape %{buildroot}%{_bindir}/tape

%check
%{__nodejs} -e 'require("./")'
# Setup bundled dev node_modules for testing
#   Note: this cannot be in %%prep or the dev node_modules
#            can get pulled into the regular rpm
tar xfz %{SOURCE2}
pushd node_modules
ln -s ../node_modules_dev/* .
popd
pushd node_modules/.bin
ln -s ../../node_modules_dev/.bin/* .
popd
# Run tests
./node_modules/.bin/tap test/*.js


%files
%doc readme.markdown
%license LICENSE %{npm_name}-%{version}-bundled-licenses.txt
%{nodejs_sitelib}/tape
%{_bindir}/tape


%changelog