npm包對應的rpm軟體包製作
rpm軟體包製作
從一個前端角度來說,搞這個事可真夠冷門的。。。但是確實遇到了需求去做了就記錄一下。
目的:稍微有點規模的公司對自己的伺服器都有運維規範,譬如外網許可權,一般是需要申請才能訪問的。然而像pm2這樣通用的工具,在我司是node應用程序管理標準採用的工具,如果都要每個開發者去申請外網許可權來 npm install
實在是麻煩,不如做成rpm包放到內部私有yum源上,也方便運維配成node應用的標準基線,無需大家手動安裝。(要是npm私有源也能這麼玩被我司支援能預設安裝其上的包我就不用這麼折騰了。。。)=.=
所以文章會介紹rpm包的製作流程,然後已pm2包的製作為示例,演示如何把npm包搞成rpm包。需要有基礎的linux系統知識。
rpm包是什麼,pm2又是什麼
不專業解釋,rpm包是linux上通用軟體安裝包的一種格式。pm2是node程序管理的一種命令列工具,命令列工具其實是可執行命令,對應著背後的可執行檔案,可執行檔案可能是多種語言的譬如bash,他們最終都會翻譯成機器碼由作業系統去執行(不繼續了,繼續我也說不下去了…)。
rpm包製作
官網文件教程最清晰: ofollow,noindex" target="_blank">https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn
製作步驟
-
安裝
rpmbuild
:yum install rpm-build
-
切換到一個普通使用者身份, 不要用root身份製作rpm ,不然讀寫檔案許可權都是root,使用者安裝包時會存在很多許可權問題。
useradd simpleuser su simpleuser
-
建立需要的工作目錄
mkdir -pv ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
- 將原始碼包放置到
~/rpmbuild/SOURCES
- 將寫好的
spec
檔案放置到~/rpmbuild/SPECS
- 打包:
rpmbuild -ba NAME.spec
rpm包包含原始碼包SRPM 和 應用包RPM , -ba
是同時構建這2種類型的包。只構建RPM包 -bb
,只構建SRPM包 -bs
build過程
build的過程是根據spec的檔案描述來進行的。
我僅簡單官網解釋下這個過程。build分為6個階段,每個階段操作內容都在之前建立的那些目錄 BUILD,RPMS,SOURCES,SPECS,SRPMS
,每個階段都在一個特定的資料夾下進行,都將之前階段生成的內容先copy到當前階段對應的資料夾下,再進行其他操作。
spec檔案裡的每個步驟都有一些內檢指令和巨集變數可用。如果這個步驟不需要特殊的操作,指令空著就好。
%prep
-
讀取的目錄:%_sourcedir
-
寫入的目錄:%_builddir
-
預設動作:讀取位於 %_sourcedir 目錄的原始碼和 patch 。之後,解壓原始碼至 %_builddir 的子目錄並應用所有 patch。
這一步其實主要做解壓的操作。可以用 %autosetup
,有一些引數如, -n
指定 SOURCE
軟體包解壓後的名稱,如果與軟體名也就是 .spec
檔案中定義的 name
不一致,是必須指定的,譬如 pm2
的tgz包解壓後的資料夾名稱就是 package
,這裡就要指定 %autosetup -n package
。
centos7以前是不支援這個指令的,只能用 %setup
,但是它就沒辦法指定 -n
別名了。
%build
-
讀取的目錄:%_builddir
-
寫入的目錄:%_builddir
-
預設動作:編譯位於 %_builddir 構建目錄下的檔案。通過執行類似 “./configure && make” 的命令實現。
這是個構建的步驟,如果你對原始碼要執行一系列構建步驟,可以在 .makefile
裡指定,然後在這一步執行 make
指令構建。
%install
-
讀取的目錄:%_builddir
-
寫入的目錄:%_buildrootdir
-
預設動作:讀取位於 %_builddir 構建目錄下的檔案並將其安裝至 %_buildrootdir 目錄。這些檔案就是使用者安裝 RPM 後,最終得到的檔案。
這裡要注意的是,你希望最終你的安裝包檔案被存放到哪裡,在這步裡就要把它先copy到 %{buildroot}
下的該目錄下。一般軟體包的安裝位置都是有規範的,預設不做這步就直接安裝到了根目錄下了,這樣肯定太好。
%check
-
讀取的目錄:%_builddir
-
寫入的目錄:%_builddir
-
預設動作:檢查軟體是否正常執行。通過執行類似 “make test” 的命令實現。很多軟體包都不需要此步。
bin
-
讀取的目錄:%_buildrootdir
-
寫入的目錄:%_rpmdir
-
預設動作:讀取位於 %_buildrootdir 最終安裝目錄下的檔案,以便最終在 %_rpmdir 目錄下建立 RPM 包。在該目錄下,不同架構的 RPM 包會分別儲存至不同子目錄, “noarch” 目錄儲存適用於所有架構的 RPM 包。這些 RPM 檔案就是使用者最終安裝的 RPM 包。
這步是內建的步驟,不對開發者透出
src
-
讀取的目錄:%_sourcedir
-
寫入的目錄:%_srcrpmdir
-
預設動作:建立原始碼 RPM 包(簡稱 SRPM,以.src.rpm 作為字尾名),並儲存至 %_srcrpmdir 目錄。SRPM 包通常用於稽核和升級軟體包。
原始碼RPM包也不是必須的。
可以關注下build時的日誌來更好的瞭解這個過程:
spec檔案
也是隻記幾個要注意的點:
- Name : 軟體包名,應與 SPEC 檔名一致。命名必須符合 軟體包命名規定。
- Version : 上游版本號。請檢視 版本標籤規定。如果包含非數字字元,您可能需要將它們包含在 Release 標籤中。如果上游採用日期作為版本號,請考慮以:yy.mm[dd] (例如 2008-05-01 可變為 8.05) 格式作為版本號。
- Release : 發行編號。初始值為 1%{?dist}。每次製作新包時,請遞增該數字。當上遊釋出新版本時,請修改 Version 標籤並重置 Release 的數字為 1。具體參考打包規定中的 Release 標籤部分,以及 Dist tag。
- Summary: 一行簡短的軟體包介紹。
- Group
- License
- URL: 該軟體包的專案主頁。
- Source0 : 軟體原始碼包的 URL 地址。不一定是url,也可以是
SOURCE
資料夾下的原始碼壓縮包名。”Source” 與 “Source0” 相同。強烈建議提供完整 URL 地址,檔名用於查詢 SOURCES 目錄。如果可能,建議 使用 %{name} 和 %{version} 替換 URL 中的名稱/版本 ,這樣更新時就會自動對應。下載原始碼包時,需要 保留時間戳。如果有多個原始碼包,請用 Source1,Source2 等依次列出。如果你需要新增額外檔案,請將它們列在後面。 - Patch0
- BuildArch
- BuildRoot
- BuildRequires: 編譯軟體包所需的依賴包列表,以逗號分隔。此標籤可以多次指定。編譯依賴 不會 自動判斷,所以需要列出編譯所需的所有依賴包。常見的軟體包可省略,例如 gcc。如果有必要,你可以指定需要的最低版本(例:”ocaml >= 3.08”)。如果你需要找到包含 /EGGS 檔案的軟體包,可執行 “rpm -qf /EGGS”。如果你需要找到包含 EGGS 程式的軟體包,可執行 “rpm -qf which EGGS”。請保持最小依賴(例如,如果你不需要 perl 的功能,可使用 sed 代替),但請注意,如果不包含相關依賴,某些程式會禁用一些功能;此時,你需要新增這些依賴。
- Requires: 安裝軟體包時所需的依賴包列表,以逗號分隔。請注意, BuildRequires 標籤是編譯所需的依賴,而 Requires 標籤是安裝/執行程式所需的依賴。 大多數情況下,rpmbuild 會自動探測依賴 ,所以可能不需要 Requires 標籤。然而,你也可以明確標明需要哪些軟體包,或由於未自動探測所需依賴而需要手動標明。
- %description
- %prep: 打包準備階段執行一些命令(如,解壓原始碼包,打補丁等),以便開始編譯。一般僅包含 “%autosetup”;如果原始碼包需要解壓並切換至 NAME 目錄,則輸入 “%autosetup -n NAME”。
- %build: 包含構建階段執行的命令,構建完成後便開始後續安裝。程式應該包含有如何編譯的介紹。
- %install: 包含安裝階段執行的命令。命令將檔案從 %{_builddir} 目錄安裝至 %{buildroot} 目錄。
- %check: 包含測試階段執行的命令。
- %clean: 清理安裝目錄的命令。一般只包含:
rm -rf %{buildroot}
。 - %files: 需要被打包/安裝的檔案列表。
- %changelog: RPM 包變更日誌。不是軟體本身的變更日誌。
%files
比較需要注意的是 %files
:
此部分列出了需要被打包的檔案和目錄。所以需要最終在使用者安裝後系統裡出現的檔案都必須在這裡定義。 %{buildroot}資料夾下的最終檔案都要在這裡列出 。可以使用glob萬用字元。
如果存在以下情況,可能引發錯誤:
- 未匹配到任何檔案或目錄。也就意味著你的rpm安裝完後,使用者系統裡沒有任何檔案變更,那你的軟體包功能是?
- 檔案或目錄被多次列出。避免重複
- 未列出 %{buildroot} 下的某個檔案或目錄
您也可以使用 %exclude 來排除檔案。這對於使用萬用字元來列出全部檔案時會很有用,注意如果未匹配到任何檔案也會造成失敗。
Scriptlets
當用戶安裝 RPM 時,您可能想要執行一些命令。這可以通過 scriptlets 完成。
指令碼片段可以:
- 在軟體包安裝之前 (
%pre
) 或之後 (%post
) 執行 - 在軟體包解除安裝之前 (
%preun
) 或之後 (%postun
) 執行 - 在事務開始 (
%pretrans
) 或結束 (%posttrans
) 時執行
譬如我一開始做pm2的rpm包方式就是將pm2原始碼的tgz包,在 %post
步驟裡 npm install -g pm2-2.3.2.tgz
一把來實現。後來還藉助npm來install有點多此一舉。
巨集
- %{_bindir}: /usr/bin
- %{_builddir}: ~/rpmbuild/BUILD
- %{buildroot}: ~/rpmbuild/BUILDROOT
測試
製作好了後當然需要自己先安裝測試下了
安裝: sudo rpm -ihvv ~/rpmbuild/RPMS/${name}.rpm
。
你構建的軟體包究竟叫什麼可以去 ~/rpmbuild/RPMS
下檢視,譬如我製作的pm2就在 ~/rpmbuild/RPMS/x86_64/pm2-3.2.2-1.el7.centos.x86_64.rpm
。
-ihv
引數就能安裝, -ihvv
會列印安裝過程中每步的資訊,方便debug。
解除安裝rpm包: sudo rpm -e ${name}
,萬一你寫了Scriptlets並且跟我一樣悲催的在初始開發時寫的 %preun
指令碼有問題,那麼執行報錯會導致解除安裝失敗,可以通過 sudo rpm -e --noscripts ${name}
來不執行指令碼單純解除安裝
更多查詢rpm包相關資訊的命令
查詢所有安裝過的包含某個字串的軟體包: rpm -qa | grep ${string_name}
檢視rpm包中的檔案安裝位置: rpm -ql ${name}
檢視某rpm包中包含檔案列表: rpm -ql ${name}.rpm
檢視某可執行命令是哪個軟體包安裝的:
rpm -qf `which 命令名`#返回軟體包的全名 rpm -qif `which 命令名`#返回軟體包的有關資訊 rpm -qlf `which 命令名`#返回軟體包的檔案列表
檢視某個檔案是哪個軟體包安裝的:
whereis ${filename} rpm -qf ${filepath}
npm包轉成rpm包
npm install 幹了什麼
- 從遠端下載壓縮包tgz
- 解壓縮到規範的指定目錄,譬如,npm安裝在
/usr/local/nodejs/lib/node_modules
下 - 安裝該包的依賴包
- 如果有可執行命令,建立可執行命令所在檔案(package.json裡的
bin
所指向的檔案)的軟連線到可被系統查詢到的命令目錄,一般npm包的都是/usr/local/nodejs/bin
下
清楚了這個也就能清楚製作rpm包時需要做什麼了。
獲取npm包原始碼
直接從npm源找到的tgz包是不包含該包的依賴包, npm pack
在原包安裝目錄下打的也是一樣。一般做rpm包我們是把完整的原始碼整個拉下來,免得build的過程中再去拉取。
打出包含node_modules資料夾的安裝包可以藉助 npm-bundle
這個npm包工具。
以 pm2
來舉例:
- 安裝pm2:
npm install -g pm2
- 進入包原始碼目錄:
cd /usr/local/lib/node_modules/pm2
- 執行
npm-bundle
打出包含node_modules目錄依賴的tgz原始碼壓縮包。然後放到上面說的rpmbuild/SOURCE
,供後續rpmbuild
使用
pm2 的rpm包spec檔案示例
centos7版:
Name:pm2 Version:3.2.2 Release:1%{?dist} Summary:pm2 program Summary(zh_CN): pm2 程式 License:AGPL-3.0 URL:https://pm2.io/doc/en/runtime/overview/ Source0:%{name}-%{version}.tgz Requires(post): nodejs Requires(preun): nodejs %global __requires_exclude ^/sbin/openrc-run$ %description node pm2 for rpm version %prep %autosetup -n package %build %install mkdir -p %{buildroot}/usr/local/nodejs/lib/node_modules/pm2 cp -af . %{buildroot}/usr/local/nodejs/lib/node_modules/pm2 %clean rm -rf %{buildroot} %files /usr/local/nodejs/lib/node_modules/pm2/* /usr/local/nodejs/lib/node_modules/pm2/.[a-z0-9_]* %post ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2 /usr/local/bin/pm2 ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-dev /usr/local/bin/pm2-dev ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-docker /usr/local/bin/pm2-docker ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-runtime /usr/local/bin/pm2-runtime %preun if [ $1 = 0 ] ; then rm /usr/local/bin/pm2 /usr/local/bin/pm2-dev /usr/local/bin/pm2-docker /usr/local/bin/pm2-runtime fi %changelog
在 %post
中做了軟連線的操作,軟鏈目標路徑只要是$PATH中存在的路徑就好,npm install時雖然是用了 /usr/local/nodejs/bin
,但是這個路徑不一定在$PATH中,為了簡化處理,我直接軟鏈到了更通用的 /usr/local/bin
,當然用更更通用的 /usr/bin
更保險
遇到的一些問題
指定安裝目錄
要在 %install
裡建立目標安裝目錄,將原始碼檔案移到這個目錄裡。
安裝時報 .
和 ..
檔案或資料夾找不到
之前 %files
定義的是:
/usr/local/nodejs/lib/node_modules/pm2/* /usr/local/nodejs/lib/node_modules/pm2/.*
這樣會把 /usr/local/nodejs/lib/node_modules/pm2/.
和 /usr/local/nodejs/lib/node_modules/pm2/..
包含進去,且不能通過 %exclude
指令排除掉,後改成了:
%files /usr/local/nodejs/lib/node_modules/pm2/* /usr/local/nodejs/lib/node_modules/pm2/.[a-z0-9_]*
安裝時報目標機上 /sbin/openrc-run
依賴找不到
rpm確實有依賴自動識別機制,能識別到這個依賴說明pm2裡用了它,一搜發現有個模板檔案.tpl中含有 #!/sbin/openrc-run
,所有這種指令都會被自動依賴識別,譬如 #!/bin/bash
會被識別依賴 sh
。但是pm2並不是真的依賴 openrc-run
這個系統初始化工具啊,他是會判斷宿主機環境,再去判斷用哪一種的,譬如我的測試機上用的就是 systemd
而不是 openrc-run
,所以這並不是一個強依賴。
需要有個姿勢告訴rpm的自動依賴識別系統過濾它。方案就是centos7上是有 __requires_exclude
和 __requires_exclude_from
的用法的。詳情見 https://fedoraproject.org/wiki/Packaging:AutoProvidesAndRequiresFiltering
相容問題
製作rpm包還要注意打包機和安裝機的系統版本差異,不同版本對rpm支援度不一樣。
在centos6中 %global __requires_exclude
巨集不支援,無奈,部分文件中提的 %define __requires_exclude
我試驗了也不支援,我只能通過 AutoReq: no
暴力關掉所有自動依賴分析。
centos6中也不支援 %autosetup
,不能解決rpm包和解壓後文件目錄名不一致問題,只能先將 npm-bundle
打的壓縮包解壓,將package資料夾名改成 pm2-3.2.2
,再通過 tar -zcvf pm2-3.2.2.tgz pm2-3.2.2
壓縮回去。
參考
-
這篇文章裡的圖非常有助於理解: