openSUSE:Packaging nodejs

跳转至: 导航, 搜索
本文教您如何在 openSUSE 下打包 nodejs 相关软件包。

NPM

与 perl 的 CPAN 和 python 的 PYPI 相似,nodejs 也有一个类似的 Archive Network,统一存放着 nodejs 的模块,这就是 npm。同样 npm 也是 nodejs 下面与 python 的 pip 类似的安装模块的命令。多数 nodejs 软件包并没有自己的网站,因此 npm 是查找 nodejs 模块最新版本及其开发站点的最佳去处。

开发源

openSUSE 下全部 nodejs 软件包均在 devel:languages:nodejs 源中进行开发。

一个简单的使用 npm 安装的例子

大多数 nodejs 模块均可使用 npm 安装,当然不排除一些使用传统的 configure && make && sudo make install 安装方式的,不过非常少见。

下面这个例子取自 devel:languages:nodejs 源中的 nodejs-minimist 软件包。

注意: 此方法仅可用于不依赖于其它 nodejs 模块的 nodejs 模块。原因请看下面章节。

#
# 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
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

命名规则

openSUSE 中的 nodejs 软件包的命名规则与 python 和 perl 的比较相似,都是以 nodejs- 开头。

注意,下载到的源代码可能会是 node-mkdirp-<version>.tar.gz 或者 minimist-<version>.tar.gz 这样的格式,这就要求在 %setup 的时候使用 -n 指定解压出的文件夹的名称:

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

nodejs 模块多数是 noarch 的

因为它们是 js 而不是 c 嘛,所以同 python 比较类似,纯 nodejs 软件包全部是 noarch 的,因此需要在 specfile 中加上

BuildArch: noarch

nodejs 打包常用的宏

nodejs 所有的宏都在 /etc/rpm/macros.nodejs 和 /etc/rpm/macros.coffeescript 中。其中最为常见的是:

%{?nodejs_requires}

作为 Requires 使用,等效于 Requires: nodejs。

%{nodejs_modulesdir}

等效于 /usr/lib/node_modules

%nodejs_install

等效于:

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

要解释这个宏就不得不说一下 npm。npm 既可以从网络安装,也可以从本地文件安装。指定了 prefix 后,它会安装到我们的 Buildroot 目录下的 ./usr/lib/node_modules。而 -g 是表示全局安装而不是用户安装(那样会安装到 /usr/local)。%{S:0} 是 %{SOURCE0} 的缩写,一般就是 Source 标签的内容。

%cake_build

coffeescript 下的 cake build。

%cake_install

coffeescript 下的 cake --prefix %{buildroot}%{nodejs_modulesdir} install,安装到 /usr/lib/node_modules 中去。

package.json 中的 NPM "dependencies" 问题

每个可通过 npm 安装的 nodejs 包必须在其压缩包内有一个 package.json 文件。实际上这个文件指导 npm 来安装该包。于是问题就来了:

如果 package.json 里面有一个 "Dependencies" 标签,里面有一些其它的 nodejs 包作为依赖,那 OBS 的编译过程就铁定会失败。

原因是下面两者的结合:

  • 与 AUR 不同,OBS 上的编译虚拟机没有任何网络连接。
  • npm 始终需要去 registry.npmjs.org 检查依赖关系。不管你装没装这些依赖。若您已经在本地安装了这些 nodejs 模块,您会留意到 "└── minimist@0.0.8" 这样的符号,这个符号证明了不管你装没装依赖 npm 的确都会在线查询东西 (因为我们本地的 minimist 版本是 0.2.0)。

这就是您可以在本机上安装 nodejs 模块或构建一个 nodejs 软件包却无法在 OBS 上做同样事情的原因。

这里有一些「脏」方法:

  • 本机安装该软件包,然后在 OBS 上使用 `install -d <nodejs_modulesdir>; install -m 0644 <file>` 方式手动复制对应文件到对应位置。但考虑到 npm 是一种常规方法,这种安装方式太慢了。
  • 这么干:
    • 解压压缩包
    • 复制 package.json 到上层目录
    • 给 package.json 打补丁来移除 "Dependencies" 标签
    • npm install 打过补丁的文件夹
    • 安装原始的 package.json 到 Buildroot (这样终端用户的 package.json 里依然有 depdencies 标签)
    • 使用 RPM requires 来追踪真实依赖

范例 (我们可能已经把上述一些过程转换为一些 nodejs RPM 宏了:

#
# 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
Requires:	 nodejs-minimist
BuildRoot:       %{_tmppath}/%{name}-%{version}-%{release}
BuildArch:       noarch
%{?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 don't 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

失败的尝试

BuildRequires: xxx

这是一种 python 思路,"噢,setup.py 说它需要东西,我们就用 RPM BuildRequires 给它,问题解决了"。但 npm 并不买账。

npm link ./xxx

若您搜索过 "npm local dependency",您会在 stackoverflow 上找到一些「解决方案」说您可以保留 package.json 中的 "Dependencies" 标签,而去用:

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

来使用您的 minimist 副本作为本地依赖。它会与您的主包一起装到 %{nodejs_modulesdir}。然后您可以再度移除该依赖,这样 Buildroot 里就只剩下主包了。完美的思想!但不行。因为:

在 link 过程前,npm 首先会 unlink 您的 %{nodejs_modulesdir} 文件夹中的那个 minimist (这意味着,npm 实际上那个是可以找到您通过 RPM Requires 指定的依赖的!),但是 OBS 用于控制编译过程的 abuild 用户并没有这么强的权限来 unlink 系统文件夹中的 nodejs 模块。

npmrc 或 --registry

我们试过修改 npm registry 为本地文件夹:

npm install --registry %{nodejs_modulesdir}

同样还试过:

npm config set regsitry %{nodejs_modulesdir}

全都失败了,因为:

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

同样的失败理由也适用于 --proxy http://localhost(尝试指向一个空网址),因为无论如何 npm 都会查询这些网址并返回一些东西,不能返回、返回错的或者超时都会视为失败。

常见问题

npm ERR! network getaddrinfo enotfound

这是因为您的软件包在其 package.json 中依赖了一些其它 nodejs 模块。Nodejs 没有一种配置机制来在编译前检查依赖问题 (对了,RPM 依赖关系对 NPM 根本就不起作用; 这也是为什么在开始打包前您总是应该去检查 package.json的原因),所以 npm 只会在 `npm install` 过程缺失依赖时停止,给出一个与真实原因不同的、看起来很诡异并且完完全全是错误的出错原因。

一旦在 npm 安装过程中发现了依赖问题,npm 会尝试在线下载缺失的依赖,就像 Python pip 那样。但不幸的是,即使依赖关系已经被 RPM Requires 满足了,它还是需要查询 registry.npmjs.org 来再看一遍。但是 openSUSE 构建服务上的编译虚拟机没有任何网络连接,所以查询 URL:

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

铁定会失败,催生了这个 getaddrinfo 错误。

任何 nodejs 模块的依赖关系都可以在其压缩包中的 package.json 文件中的 'Dependencies' 标签 (不是 'devDependencies' 标签) 下找到,或者也可以去其在 npmjs.org 的主页上找。该问题的解决方案请看 #package.json 中的 NPM "dependencies" 问题

如何调试 npm 问题

您需要使用下面内容替换 %nodejs_install 宏:

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 ==============================="

然后您就可以在 LOG 行中间详细地看到 npm 做过的任何事情了。