RPM 打包︰由一竅不通到動手濫用 (二)

教你如何閱讀、編寫打包用的 .spec 檔案

Koala Yeung
Linux 入門筆記
12 min readJul 27, 2018

--

上一篇文章教過大家打包一個沒甚麼營養的程式,這篇文章教大家怎樣理解 SPEC 檔,令它進化,變成沒甚麼營養的 systemd 長駐程式(喂)。

上一篇文章tick5 程式打包,使用這一個 SPEC 檔案︰

​tick5.spec

到底這檔案各部份是甚麼意思?我們來解剖一下。

SPEC 檔的基本結構

圖片來源︰Realistic Art Resources

RPM 的 SPEC 檔基本分為「表頭」(Preamble) 和「內文」(Body) 兩部份。

表頭欄的格式︰

欄名: 單行內容
欄名: 單行內容

內文欄的格式︰

%欄名
多行內容
多行內容
%欄名
多行內容
多行內容

所有表頭欄目,必需在放於第一個內文欄目之前,否則將被視為它身處的內文欄的文字內容。

SPEC 檔︰表頭 (Preamble)

tick5.spec 開始這 5 行,便是表頭︰

Name: tick5
Summary: Print a message every 5s
Version: 0.1
Release: 1
License: MIT

根據 R​edHat 官方指引,表頭包括下列欄目(* 為必需)︰

  • Name*
    所包裝軟件的名稱。
  • Version *
    軟件的版本編號。
  • Release *
    同一版本軟件的發佈次數。預設為 1%{?dist},每打包一次應該 +1,直到版本編號更新為止。
  • S​ummary *
    軟件的簡短介紹。
  • 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 程序。一般包括將你要安裝的執行檔、軟件庫、設定檔、說明文件等等,由編譯的資料匣(如 %_build​dir)移放到 %buildroot 去。在理想的世界裏,是預備好 ~/rpmbuild/BUILDROOT 的資料匣結構,然後把檔案由 ~/rpmbuild/BUILD 搬到 ~/rpmbuild/BUILDROOT 的程序。
  • %check
    檢查軟件的 shell 程序,一般擺放 unit test 的指令。
  • %files
    需要打包的檔案列表,每行一個定義,可以使用 bash 通用的「*」萬用字元 。留意這裏的檔案路徑以 %buildroot 定義的資料匣為根目錄,換言之沒放在這裏的檔案,是無法被打包的。
  • %changelog
    軟件變化的紀錄,定義不同版本 (version) 或建置版本 (build) 的變化。

比較有趣的是,內文只要有 %description,就可以正常執行,只是不會產生任何 .rpm 檔案而已。另外若果 %buildroot 資料匣內有檔案不被包含在 %files 列表之中,會發生打包錯誤。

所以若要打包一個最最最基本的 .rpm 檔,必需︰

  1. 要有 %description%files,並且;
  2. 要在 %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」支持啊 :-)

--

--

Koala Yeung
Linux 入門筆記

An FOSS enthusiast. Lives in Hong Kong. Writes Go, Javascript, PHP and occasionally fictions.