openSUSE:Packaging nodejs

Jump to: navigation, search


Icon-obsolete.png
This article or section refers to the version 'NPM packaging' and it is now obsolete!
Please refer to this article's discussion page for more information.
This guide will tell you how to package nodejs packages (modules) for openSUSE.

NPM

Very much like CPAN for Perl/PYPI for python, nodejs has a smiliar archive network, hosting nodejs modules, which is npm. Also npm is the pip-in-python like command in nodejs to install modules. Unlike python, most of the nodejs modules don't have any official website. This fact makes npmjs.org the best place to find latest versions and development porals for nodejs modules.

Devel repository

All nodejs packages in openSUSE are developed at devel:languages:nodejs repository on Open Build Service.

A simple example using npm installation

Most of the nodejs modules can be installed through npm, with some still using the legacy `configure && make && sudo make install` method, which is rather rare.

The example below are taken from the nodejs-minimist package in devel:languages:nodejs repository.

NOTE: This method can only be used for nodejs modules that have no dependency on any other nodejs modules. Read below.

#
# spec file for package nodejs-minimist
#
# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via http://bugs.opensuse.org/
#

Name:           nodejs-minimist
Version:	0.2.0
Release:	0
License:	MIT
Summary:	Parse argument options
Url:	https://github.com/substack/minimist
Group:	Development/Libraries/Other

Source:	minimist-%{version}.tar.gz
BuildRequires:	nodejs
BuildRequires:	nodejs-packaging 
BuildRoot:      %{_tmppath}/%{name}-%{version}-build
BuildArch:	noarch
%{?nodejs_requires}

%description
This module is the guts of optimist's argument parser 
without all the fanciful decoration.

%prep
%setup -q -n minimist-%{version}

%build

%install
%nodejs_install
rm %{buildroot}%{nodejs_modulesdir}/minimist/LICENSE
rm %{buildroot}%{nodejs_modulesdir}/minimist/readme.markdown

%files
%defattr(-,root,root)
%doc LICENSE readme.markdown
%{nodejs_modulesdir}/minimist

%changelog

Naming scheme

nodejs packages in openSUSE are named like Python's and Perl's, starting with `nodejs-`.

Note: the downloaded source tarball might be in node-mkdirp-version.tar.gz or minimist-version.tar.gz format, so it requires to use -n to explicitly specify the unpack directory in the %setup process:

%setup -q -n node-mkdirp-%{version}

Most of the nodejs modules are architecture-independent

Because they are JavaScript instead of C, so like python, "pure" nodejs package are all arch-independent, which requires this line in the specfile:

BuildArch: noarch

nodejs RPM macros

All nodejs related RPM macros are inside /etc/rpm/macros.nodejs and /etc/rpm/macros.coffeescript. Most common:

%{?nodejs_requires}

Use as Requires, equals to Requires: nodejs.

%{nodejs_modulesdir}

Equals to /usr/lib/node_modules.

%nodejs_install

Equals to:

mkdir -p %{buildroot}%{nodejs_modulesdir} \
npm_config_prefix=%{buildroot}%{_prefix} npm install -g %{S:0}

We have to talk about npm again here. NPM can install from online URL or from local file. With a specified prefix, it will install stuff into the ./usr/lib/node_modules directory, which is %{nodejs_modulesdir}, of our Buildroot. The '-g' option means "Globally install" instead of "Locally by user", which will install into /usr/local. %{S:0} is a shortname for %{SOURCE0}, which is the content of the Source tag normally.

%cake_build

`cake build` in coffeescript.

%cake_install

`cake --prefix %{buildroot}%{nodejs_modulesdir} install` in coffeescript, will install into /usr/lib/node_modules.

NPM "dependencies" issues in package.json

Every nodejs package that can be installed through npm must have a package.json file inside its tarball. Actually, it is a npm instruction guides npm to install the package. Here is the problem:

If the package.json has a tag "Dependencies" with some other nodejs packages as dependency, the build process on OBS will certainly fail.

This reason is a combination of the two below:

  • the build virtual machine on OBS does not have any network (so as to ensure the Island Test succeeds), unlike AUR.
  • npm needs to check registry.npmjs.org for dependencies anytime, no matter whether you have had the dependencies installed or not. If you once have installed nodejs modules locally, you will find the "└── minimist@0.0.8"-like symbol, this is just the symbol that proves npm does query stuff online no matter whether you have it or not (because our minimist version is 0.2.0).

That is why you can install on your machine, or build a rpm on your machine, but cannot do the same thing on OBS.

Here are the dirty workarounds:

  • Install the package on your own machine, and use `install -d <nodejs_modulesdir>; install -m 0644 <file>` method (Manual Copying) on OBS. but considering npm is a common method, such a workaround seems low...
  • Like this:
    • Unpack the tarball
    • copy package.json to the top directory
    • patch the package.json to remove the "Dependencies" tag
    • npm install the patched directory
    • install the original package.json to the BuildRoot (So end users can still have depdencies)
    • Use the RPM requires to track the actual dependencies

An example (We may covert the procedures above to some nodejs RPM macros:

#
# spec file for package nodejs-mkdirp
#
# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via http://bugs.opensuse.org/
#


Name:            nodejs-mkdirp
Version:         0.5.0
Release:         0
License:         MIT
Summary:         Create Directory
URL:             https://github.com/substack/node-mkdirp
Group:           Development/Libraries/Other

Source:          node-mkdirp-%{version}.tar.gz
Patch:		 node-mkdirp-remove-dependency-tag.patch
BuildRequires:   nodejs
BuildRequires:	 nodejs-packaging 
BuildRoot:       %{_tmppath}/%{name}-%{version}-%{release}
BuildArch:       noarch
Requires:	 nodejs-minimist
%{?nodejs_requires}

%description
Create nested directorie, works like mkdir -p

%prep
%setup -q -n node-mkdirp-%{version}
cp -r package.json ..
%patch -p1
pushd ..
tar -czf %{SOURCE0} node-mkdirp-%{version}
popd

%build

%install
%nodejs_install
rm %{buildroot}%{nodejs_modulesdir}/mkdirp/LICENSE
rm %{buildroot}%{nodejs_modulesdir}/mkdirp/readme.markdown

# We do not need a binary that may look similiar with unix default ones.
rm -Rf %{_bindir}/mkdirp

# manual install original package.json
install -m 0644 ../package.json %{buildroot}%{nodejs_modulesdir}/mkdirp/

%files
%defattr(-,root,root)
%doc LICENSE readme.markdown
%{nodejs_modulesdir}/mkdirp

%changelog

Failed attempts

BuildRequires: xxx

It is a python thinking, "oh, setup.py said it needs something, so we give it by RPM BuildRequires and problem solved", but npm does not buy it.

npm link ./xxx

If you search "npm local dependency", you will find some "solutions" on StackOverflow said you can keep the "Dependencies" tag in package.json, while using:

cp -r %{nodejs_modulesdir}/minimist .
npm link ./minimist

to use your copy of minimist as local dependency. It will be installed into %{nodejs_modulesdir} together with your main package. Then, you can remove the dependency again to get the main package only in BuildRoot. Perfect theory!, but it will not work, because:

Before the link process, npm will at first unlink the one in your %{nodejs_modulesdir} (which means, npm can actually find the dependency you specified through RPM Requires), but the "abuild" user OBS used to control the build process does not have the permissions to do that.

npmrc or --registry

We tried to change npm registry to a local directory by using:

npm install --registry %{nodejs_modulesdir}

or:

npm config set regsitry %{nodejs_modulesdir}

all failed because:

[   54s] npm WARN invalid config registry="/usr/lib/node_modules"
[   54s] npm WARN invalid config Must be a full url with 'http://'

The same thing also applys to --proxy http://localhost because anyway it will query some URL and return something.

"Manual copy" method vs. NPM method

Packages must be manually copied (Bootstraps for NPM)

These packages in d:l:nodejs must be updated with "manual copy" method, because they're build time depedencies for npm. As explained above, "manual copy" is really not a good way to package nodejs, although it's an option. But the NPM method will certainly need npm itself. So if a package, which is needed by npm to build npm, relies on npm to build itself, it will create a cycle that never ends. So we created these prerequisites plus their runtime dependencies with "manual copy" method so that we will not run into an endless loop.

These packages can not be broken by introducing npm BuildRequires. See this table below (ignored the "nodejs-" prefix"

abbrev ansi ansicolors ansistyles archy ansi-regex
block-stream char-spinner child-process-close chmodr chownr clone
columnify defaults editor inherits strip-ansi wcwidth
fstream fstream-ignore fstream-npm github-url-from-git github-url-from-username-repo glob
graceful-fs ini inflight lru-cache minimist mkdirp
once rimraf sigmund wrappy minimatch asn1
assert-plus async aws-sign2 bl boom caseless
cmd-shim combined-stream delayed-stream core-util-is cryptiles ctype
forever-agent form-data hawk hoek http-signature init-package-json
isarray json-stringify-safe lockfile mime mime-db mime-types
mute-stream node-gyp node-uuid nopt normalize-package-data npmlog
oauth-sign opener osenv promzard punycode qs
read read-package-json readable-stream request semver sntp
string_decoder stringstream tar tough-cookie tunnel-agent which
async-some config-chain debuglog dezalgo fs-vacuum npm-cache-filename
npm-install-checks npm-package-arg npm-registry-client npm-user-validate util-extend npm
npmconf path-is-inside proto-list read-installed readdir-scoped-modules uid-number
retry sha slide sorted-object text-table -

(to be continued).

FAQ

npm ERR! network getaddrinfo enotfound

It is because your package has some dependencies on other nodejs packages in its package.json. Nodejs does not have a configure mechanism to check dependency issues before build (RPM dependencies do not work at all for NPM, BTW. That is why you should check package.json all the time before starting to package), so it can only stop when missing dependencies in `npm install` process, w/ a different, weird and totally wrong reason.

Once dependency problems are found during the npm installation process, npm will try to download the missing dependencies online, just like Python's pip. While unfortunate, even if the depdendencies are fulfilled by RPM Requires, it still needs to query registry.npmjs.org to "display" dependencies. But the build VM on openSUSE Build Service does not grant network connections, so fetching URL:

[  110s] 160 http GET https://registry.npmjs.org/minimist
[  110s] 161 info retry will retry, error on last attempt: Error: getaddrinfo ENOTFOUND

will certainly fail, thus this getaddrinfo error occurs.

Dependencies for any nodejs module can be found under the 'Dependencies' tag (not the 'devDependencies' tag) in the package.json file inside its tarball, or from its page on npmjs.org. Also check #NPM "dependencies" issues in package.json for solutions.

How to debug npm issues

You need to replace %nodejs_install macro with:

npm_config_prefix=%{buildroot}%{_prefix} npm install --loglevel=silly -g %{S:0} || true
echo "=========================== LOG STARTED ============================="
if [ -f "npm-debug.log" ] ; then
    cat npm-debug.log
elif
    echo "all fine!"
done
echo "=========================== LOG ENDED ==============================="

And you can see anything npm did verbosely between those lines.