RPM 打包︰由一竅不通到動手濫用 (二)
教你如何閱讀、編寫打包用的 .spec 檔案
上一篇文章教過大家打包一個沒甚麼營養的程式,這篇文章教大家怎樣理解 SPEC 檔,令它進化,變成沒甚麼營養的 systemd 長駐程式(喂)。
上一篇文章的 tick5 程式打包,使用這一個 SPEC 檔案︰
到底這檔案各部份是甚麼意思?我們來解剖一下。
SPEC 檔的基本結構
RPM 的 SPEC 檔基本分為「表頭」(Preamble) 和「內文」(Body) 兩部份。
表頭欄的格式︰
欄名: 單行內容
欄名: 單行內容
內文欄的格式︰
%欄名
多行內容
多行內容%欄名
多行內容
多行內容
所有表頭欄目,必需在放於第一個內文欄目之前,否則將被視為它身處的內文欄的文字內容。
SPEC 檔︰表頭 (Preamble)
tick5.spec 開始這 5 行,便是表頭︰
Name: tick5
Summary: Print a message every 5s
Version: 0.1
Release: 1
License: MIT
根據 RedHat 官方指引,表頭包括下列欄目(* 為必需)︰
Name
*
所包裝軟件的名稱。Version
*
軟件的版本編號。Release
*
同一版本軟件的發佈次數。預設為1%{?dist}
,每打包一次應該 +1,直到版本編號更新為止。Summary
*
軟件的簡短介紹。License
*
軟件的使用授權模式,像是「GPLv3」或者「MIT」之類。URL
軟件的官方網站。Source0
下載軟件源代碼的路徑或完整網址。如果有多於一個,可繼續用 Source1, Source2, … SourceX 去定義。Patch0
下載第一個源代碼補丁的網址。只在有需要時定義。同樣可以有 Patch1, Patch2, … PatchX。BuildArch
如果只支援在特定硬體架構編譯,需要在這裏定義,像是「x86_64」之類。BuildRequires
定義編譯軟件所需要的套件。需要用逗號或空白分隔套件名稱,亦可以分開多次定義 BuildRequires。Requires
定義執行軟件所需要的套件。需要用逗號或空白分隔套件名稱,亦可以分開多次定義 BuildRequires。ExcludeArch
如果軟件不能在特定架硬件架構下運作,需要在此定義。
SPEC 檔︰內文 (Body) 的基本內容
就是餘下的,包括︰
%description
tick5 is a simple useless script that echos a message every 5 seconds.%install
mkdir -p %{buildroot}/usr/bin
cp -pdf tick5 %{buildroot}/usr/bin/tick5
chmod 755 %{buildroot}/usr/bin/tick5%files
/usr/bin/tick5
根據官方指引,內文包括有下列基本欄目(* 為必需)︰
%description
*
該 rpm 套件包的完整介紹,與表頭的Summary
不同,可以斷行分段。%prep
預備編譯的 shell 程序。比如將 Soruce0 的內容下載、解壓縮等等。一般建議是將源碼放置到%_builddir
裏頭。%build
編譯軟件的 shell 程序。%install
安裝編譯好軟件的 shell 程序。一般包括將你要安裝的執行檔、軟件庫、設定檔、說明文件等等,由編譯的資料匣(如%_builddir
)移放到%buildroot
去。在理想的世界裏,是預備好~/rpmbuild/BUILDROOT
的資料匣結構,然後把檔案由~/rpmbuild/BUILD
搬到~/rpmbuild/BUILDROOT
的程序。%check
檢查軟件的 shell 程序,一般擺放 unit test 的指令。%files
需要打包的檔案列表,每行一個定義,可以使用 bash 通用的「*
」萬用字元 。留意這裏的檔案路徑以%buildroot
定義的資料匣為根目錄,換言之沒放在這裏的檔案,是無法被打包的。%changelog
軟件變化的紀錄,定義不同版本 (version) 或建置版本 (build) 的變化。
比較有趣的是,內文只要有 %description
,就可以正常執行,只是不會產生任何 .rpm 檔案而已。另外若果 %buildroot
資料匣內有檔案不被包含在 %files
列表之中,會發生打包錯誤。
所以若要打包一個最最最基本的 .rpm 檔,必需︰
- 要有
%description
和%files
,並且; - 要在
%prep
、%build
或%install
的指令中,把要打包的檔案先行搬到%buildroot
內。
回去看看我們 tick.spec 檔,就是一個最低限度的 SPEC 檔了(茶)。另外一點留意,每次打包完成後,%_builddir
會被刪掉,確保包裝環境不被先前程序污染。
rpmbuild 建置指令
我們重溫一下先前使用的建置指令︰
rpmbuild -bb tick5.spec
如果你詢問一下「男人」(就是用man rpmbuild
查文檔),你會找到這一段︰
The general form of an rpm build command isrpmbuild -bSTAGE|-rSTAGE|-tSTAGE [rpmbuild-options] FILE ...The argument used is -b if a spec file is being used to build the package, -r if a source package is to be rebuild and -t if rpmbuild should look inside of a (possibly compressed) tar file for the spec file to use. (...)
稍下面有這一段︰
-bb Build a binary package (after doing the %prep, %build, and %install stages).
所以我們的指令 -bb
,就是進行 b
(binary) 這個 STAGE
的建置。在此之前,會先執行 %prep
、%build
和 %install
三個內文欄目的內容。
我們的 tick5.spec 其實沒有乖乖的定義 %prep
和 %build
,只有 %install
一段︰
%install
mkdir -p %{buildroot}/usr/bin
cp -pdf tick5 %{buildroot}/usr/bin/tick5
chmod 755 %{buildroot}/usr/bin/tick5
如果乖一點,根據 SPEC 的原意,我們大概這樣應該分做三部份︰
%prep
# 預備 packaging workspace
mkdir -p tick5 %{_builddir}/usr/src%build
# 在 packaging workspace 中進行編譯
cp -pdf %{_builddir}/usr/src/tick5 %{_builddir}/usr/bin/tick5
chmod 755 %{_builddir}/usr/bin/tick5%install
# 將編譯結果,由 packaging workspace 搬到 buildroot 預備打包
cp -pdf %{_builddir}/usr/bin/tick5 %{buildroot}/usr/bin/tick5
當然這只是邏輯劃分,在我們的例子沒有必要。事實上,若果你只想要 .rpm 安裝檔,根本你把程序全寫到 %prep
、 %build
或者 %install
都能正常運作。
關於 rpmbuild
指令的介紹到這裏為止,想更加濫用它的話,還是去問「男人」(man) 吧。
關於 %buildroot
資料匣、 %files
定義和安裝路徑
在建置的最後,會將 %files
定義的檔案,由 %buildroot
資料匣加入到 .rpm 裏頭。重看上面的 SPEC 檔,不難發現我們「編譯」的執行檔是這個︰
%{buildroot}/usr/bin/tick5
而在 %files 裏面,我們重新指明︰
/usr/bin/tick5
前面說過,我們只打包 %buildroot
內的檔案,而眼利的朋友,可能已經發現,這個 %buildroot
內的路徑 /usr/bin/tick5
,其實等於最終 .rpm 安裝它的路徑。
所以說在寫 %install
的時候,不妨把 %buildroot
視為最終安裝任何檔案的 /
目錄。如果你想把一個檔案安裝為 /usr/lib/foo/bar
,你需要︰
- 先在
%install
把它搬到%buildroot/usr/lib/foo/bar
;然後 - 在
%files
加入/usr/lib/foo/bar
那 rpm 就會自動把檔案安裝到位。
關於 %files
定義與套件衝突
其實在每一個 .rpm 檔案內,都有一個自己的檔案清單,會在安裝時用作比對,防止套件衝突——這清單內容就等於這裏的 %files
。如果你在建置時用似 /usr/*
去定義 %files
打包,雖然可以成功,但由於路徑會與現有套件衝突,安裝時會出現錯誤,無法安裝︰
Preparing... ################################# [100%]
file /usr/bin from install of tick5-0.1-1.x86_64 conflicts with file from package filesystem-x.x-x.somedistro.x86_64
所以寫 %files
時,如果你安裝檔案的資料匣並非你的軟件獨有,比較理想的做法是逐個檔案定義。
把沒營養的程序,變成沒營養的常駐程序
在 systemd 的世界,如果想把一個命令行程序變成常駐程式,其實很簡單,只需要定義好 unit 檔案,像這樣︰
關於 systemd unit 的詳細解釋留待日後的文章。簡單來講,上面的檔案定義了︰
- 我有一個服務;
- 執行請跑
/usr/bin/tick5 come on, James
; - 掛掉了就請等 5 秒,再重新跑一次程序吧;
- 所有輸出請給
syslog
。
我們的目標,是把這個 unit 檔放到 rpm 裏頭,當安裝 rpm 時,把檔案自動安裝為 /usr/lib/systemd/system/tick5.service
。
大家不妨重溫一下先前談及的 SPEC 檔格式、建置過程,想想應該怎樣做?
不確定的話,不妨動手做一做,到心裏有數時,再到下面看我的答案。
.
.
.
.
.
.
.
.
.
.
.
我的解答
可行做法有很多。
最簡單直接的做法,是先在 tick5 源碼的資料匣內,加入 tick5.service 的檔案,再用 SPEC 複製到 %buildroot/usr/lib/systemd/system/tick5.service
打包。
我的做法稍為巧妙——直接在 SPEC 檔內生成 %buildroot/usr/lib/systemd/system/tick5.service
︰
我們可以依程序安裝(Debian 或 Ubuntu 用戶請把 rpm -ivh
改成 alien -iv
)︰
rpmbuild -bb tick5.spec
sudo rpm -ivh $HOME/rpmbuild/RPMS/x86_64/tick5-0.1-1.x86.rpm
sudo systemctl start tick5
跑起來的話,可以用 journalctl -u tick5.service -f
查看。如果一些正常,syslog 會每 5 秒收到一個 “come on, James” 的訊息,像這樣(看夠請按 Ctrl-C 關掉)︰
Jul 27 20:04:12 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:17 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:22 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:27 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:32 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:37 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:42 user-machine tick5.service[3316]: come on, James
Jul 27 20:04:47 user-machine tick5.service[3316]: come on, James
...
你可能會問︰有必要在 SPEC 裏面做嗎?
在目前的狀況下,是沒有必要的。但若果這樣做,我們將來可以在生成 tick5.service 時,使用 SPEC 的變量和巨集。
試想想︰
- 萬一你想修改 tick5 的安裝路徑到
/usr/local/bin/tick5
- 萬一你想建置時修改 “come on, James” 那一句
- 萬一你想修改版本、建置編號
- 萬一想平衡安裝不同版本的 tick5,想修改檔名
在目前的 SPEC 格式內,通通要逐次修改。尤其,如果你的 systemd 的 unit 檔案是獨立的檔案,當 tick5 路徑變動時,都需手動修正。
但是若果使用變量和巨集功能,這些通通可以在打包時推翻、修改。
下一篇文章,我會談談變量和巨集,敬請留意。
喜歡本文的話,別忘了按「Like」和「Clap」支持啊 :-)