openSUSE:Packaging 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 做过的任何事情了。