Packaging

Photo by Kira auf der Heide on Unsplash

Arch Linux 採用類似 ports 的構建系統,使用 PKGBUILD 這種軟體包建立描述檔案,用 makepkg 從原始碼構建二進制軟體包。

最近參加了軟創比賽,官方要求必須使用快應用開發,雖然 IDE 有提供 Linux 版,但只有 deb 格式,AUR 也沒人打包,身為一位 Arch Linux 的愛好者怎麼能因此而放棄呢,於是我便打算親自打包,用 Arch Linux 這麼多年了,Arch Linux 的倉庫以軟體多聞名,大部分軟體都已經有人打包好了,不用自己打包,沒想到我也有需要自己打包的一天,第一次打包成功的那刻非常有成就感,我也能變成 Arch Linux 的貢獻者了。

建議先閱讀 Arch Wiki 上的打包教學PKGBUILD 規範,下面內容大多翻譯、擷取自 Arch Wiki。

準備工作

先檢查是否已安裝 base-devel 包,裡面有打包需要的工具,包含 makemakepkg 等工具。

1
sudo pacman -S base-devel

然後把你要打包的軟體上游下載回來編譯,大部分軟體都是用這三步安裝,主要還是看軟體的 README 怎麼寫。

1
2
3
./configure
make
make install

新建 PKGBUILD

當你執行 makepkg 時,會在當前目錄尋找 PKGBUILD 檔案,並根據 PKGBUILD 的內容去下載軟體的原始碼,再根據 PKGBUILD 的指令編譯,如果成功,會產生一個 pkgname.pkg.tar.xz 的壓縮檔,內含該軟體的二進制檔案及元資料(metadata),描述該包的版本、依賴,可以使用 pacman -U <package file> 來安裝。

PKGBUILD 的原型範例放在 /usr/share/pacman/,找個適合你的原型複製下來。

1
2
3
4
5
6
7
$ ls -l /usr/share/pacman/
總計 20
drwxr-xr-x 2 root root 4096 Nov  2 23:03 keyrings
-rw-r--r-- 1 root root  918 Nov  1 12:08 PKGBUILD.proto
-rw-r--r-- 1 root root 1319 Nov  1 12:08 PKGBUILD-split.proto
-rw-r--r-- 1 root root 2031 Nov  1 12:08 PKGBUILD-vcs.proto
-rw-r--r-- 1 root root  689 Nov  1 12:08 proto.install
  • PKGBUILD.proto: 經典原型
  • PKGBUILD-split.proto: 分包原型
  • PKGBUILD-vcs.proto: 如果你要打包的軟體原始碼上源來自 Git, SVN, Mercurial 這類版本控制系統(Version control systems, VCS),請參考這份原型作為基礎

編寫 PKGBUILD

下面是從 Arch Wiki 擷取的 PKGBUILD 的 Prototype。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Maintainer: Your Name <youremail@domain.com>
pkgname=NAME
pkgver=VERSION
pkgrel=1
pkgdesc=""
arch=()
url=""
license=('GPL')
groups=()
depends=()
makedepends=()
optdepends=()
provides=()
conflicts=()
replaces=()
backup=()
options=()
install=
changelog=
source=($pkgname-$pkgver.tar.gz)
noextract=()
md5sums=() #autofill using updpkgsums

build() {
  cd "$pkgname-$pkgver"

  ./configure --prefix=/usr
  make
}

package() {
  cd "$pkgname-$pkgver"

  make DESTDIR="$pkgdir/" install
}

下面是通常要修改的屬性。

  • pkgname: 套件的名稱,由小寫字母、數字和下面字符組成:@ . _ + -,不能以符號作為開頭。軟體名稱應要與來源壓縮檔相符,舉例來說檔名叫做 foobar-2.5.tar.gz,則 pkgname=foobar

  • pkgver: 套件版本,應與上游作者發行版本一致,可包含字母、數字、日期、下劃線,但不可包含連字符號(-),如果有請替換成下劃線

  • pkgrel: 釋出號,一個正整數,當同個套件版本的 PKGBUILD 更新,釋出號增加 1,當套件發佈新版本時,釋出號重置 1

  • pkgdesc: 套件的描述,建議少於 80 字符,建議不要使用套件名稱,除非安裝的套件名稱與該應用程式名稱不同

  • arch: 應用程式支援的架構,Arch 官方僅支援 x86_64

  • url: 套件的上源發佈網址

  • license: 該軟體、套件採用的發佈許可證,在 [core] 的 license 包中有常用的許可證,安裝後可在 /usr/share/licenses/common 找到這些許可證協議,如果套件使用的許可證是裡面其中一個,這個值應該被設為許可證的目錄名稱,如果套件使用的許可證沒有在裡面,值設為 custom

    通用許可證列表。

    1
    2
    3
    4
    
    $ ls /usr/share/licenses/common  
    AGPL    APACHE       CCPL  EPL     FDL1.3  GPL3     LGPL3  MPL2          PSF        W3C
    AGPL3   Artistic2.0  CDDL  FDL     GPL     LGPL     LPPL   PerlArtistic  RUBY       ZPL
    Apache  Boost        CPL   FDL1.2  GPL2    LGPL2.1  MPL    PHP           Unlicense
    

    授權要安裝到 /usr/share/licenses/pkgname/,把下面指令寫入 PKGBUILD。

    1
    
    install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
    
  • depends: 套件執行時所需的依賴列表,可以用比較運算符來限制依賴版本,如:depends=('foobar>=1.8.0'),不需要列出二次依賴,舉例來說,gtk2 依賴 glibcglib2,而 glib2 本來就依賴 glibcglibc 就不用加入依賴列表。

  • outdepends: 不影響軟體主要功能,提供額外特性的軟體包,要簡要說明每個包提供的功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    optdepends=(
      'cups: printing support'
      'sane: scanners support'
      'libgphoto2: digital cameras support'
      'alsa-lib: sound support'
      'giflib: GIF images support'
      'libjpeg: JPEG images support'
      'libpng: PNG images support'
    )
    
  • makedepends: 編譯時所需的依賴,可以像 depends 指定依賴版本,編譯時會將 depends 的軟體包預設作為編譯依賴,使用 makepkg 構件時 base-devel 視為已安裝,base-devel 的成員不應該出現在列表中,可用下面指令檢查一個依賴關係是否已存在 base-devel 中。

    1
    
    LC_ALL=C pacman -Si $(pactree -rl ''package'') 2>/dev/null | grep -q "^Groups *:.*base-devel"
    
  • source: 構件軟體的來源,通常是 HTTP 或 FTP 網址,可以調用前面的 pkgnamepkgver,如:source=("https://example.com/$pkgname-$pkgver.tar.gz"

  • md5sums/sha1sums/sha256sums: source 所列檔案校驗和,不需要自己填寫,用 updpkgsums 產生,或是用 makepkg -g >> PKGBUILD 產生

PKGBUILD 變數

makepkg 定義了兩個變數,在構建、安裝過程中會用到,在 packge() 函數中用的多。

srcdir

makepkg 會將來源檔案解壓縮到這個目錄,或著在此目錄產生指向 PKGBUILD 中 source 陣列中的軟連結。

pkgdir

makepkg 會把此目錄當作系統根目錄,將軟體安裝在此目錄下。

PKGBUILD 函數

當構建一個軟體包,如果 PKGBUILD 有定義下面五個函數,makepkg 將會觸發它們,而 package() 是必須被定義的,其他沒定義的函數在構建時將會跳過。

prepare()

用來執行構建來源的指令,此函數執行在 build() 之前,軟體包解壓之後,可以用 makepkg --noextract 跳過此函數執行。

pkgver()

構件 VSC 軟體包時,軟體的版本可能每隔幾小時就更新,這時用 pkgver()。

build()

這個函數使用通用 Bash 指令編譯軟體並建立軟體安裝目錄,在 build() 的第一步就是進入解壓縮原始碼後的目錄。makepkg 會在執行 build() 前進入 $srcdir,大多情況第一條指令是:

1
cd "$srcdir/$pkgname-$pkgver"

接下來編寫編譯要用到的指令,build() 會在 fakeroot 環境下執行,如果你要打包的軟體使用到了配置指令碼 (configure script),使用參數 --prefix=/usr 是個好習慣,很多軟體在手動編譯安裝的時候會安裝到 /usr/local,但所有的 Arch 包應該安裝到 /usr 目錄。

1
2
./configure --prefix=/usr
make

check()

用來執行 make check 或其他例行測試的地方,建議用 check() 去檢查軟體是否正確編譯且能正常執行。

若使用者不需要這步可以在 PKGBUILD/makepkg.conf 加入 BUILDENV+=('!check') 禁用,或是在執行 makepkg 加上參數 --nocheck

package()

最後一步就是把編譯好的檔案放到一個目錄讓 makepkg 可以檢索並打包,這個目錄通常是 pkg,一個 fakeroot 環境,pkg 目錄複製了軟體安裝根目錄的階層關係,如果你手動放置了一個檔案到根目錄,那你也要把檔案放在 pkg 中相同的層級結構中,假設你想要把檔案安裝到 /usr/bin,在 fakeroot 環境中對應的路徑應該是 $pkgdir/usr/bin,極少情況會需要使用者手動去安裝檔案,一般情況使用 make install 即可將軟體安裝到正確的路徑,最後一行通常這樣寫:

1
make DESTDIR="$pkgdir/" install

makepkg --repackage 只執行 package(),不執行 build(),如果僅修改包的依賴可以用這個指令重新打包以節省時間。

構建軟體包

終於來到最令人興奮的一步,也就是俗稱的「打包」,成敗與否就決定在此刻。

第一次構建先直接執行 makepkg -s,如果報錯後修改 PKGBUILD,下一次再構建的時候j傳入參數 --repackage 直接執行打包函數,這樣可以節省一些時間。

1
makepkg --repackage

如果都沒有問題的話會提示構建成功,注意在壓縮那步可能會久一點。

壓縮太久?

系統預設使用的壓縮演算法是 xz,速度比較慢,推薦使用 zstd 算法,雖然 xz 打包出來的檔案相對小一點,但是壓縮時間和解壓縮時間都比 zstd 長很多,官方目前也建議用 zstd 來發佈,如果只是自己電腦安裝的話可以選擇不壓縮直接安裝。

編輯 /etc/makepkg.conf,找到最下面的 PKGEXT 修改裡面的值。

不壓縮:

1
PKGEXT='.pkg.tar'

zstd 算法:

1
PKGEXT='.pkg.tar.zst'

根據 Arch Linux Chinese Messages 頻道的消息,現在已經採用 zstd 來取代 xz 算法了,下面是從簡體中文翻譯成正體中文並修正用語的公告訊息。

現在開始使用 zstd 替代 xz 進行軟體包壓縮

郵件列表上已經宣布了,從 2019 年 12 月 27 日開始,我們的套裝軟體壓縮格式已經從 xz (.pkg.tar.xz) 改為了 zstd (.pkg.tar.zst)

zstd 相較於 xz 用壓縮比換來高性能。用我們的壓縮參數調用 zstd 重新壓縮軟體包導致了總體包大小增加 ~0.8% ,相對的這些包的解壓時間總體有 ~1300% 的提速。

我們的軟體源中已經有超過 545 個 zstd 壓縮的套裝軟體了,隨著我們發布更新包,更多的會不斷加入。目前為止我們還未發現任何用戶可見的問題,所以感覺一切順利。

如果你是一名打包者,如果你在使用最新的 devtools (>= 20191227) 那麼你將自動開始打包新的 .pkg.tar.zst 包。 如果你是一名最終用戶,沒有手動操作需要做,只要你已經閱讀並遵從了去年新聞中的建議

如果你從 2018 年到現在還沒有升級過 libarchive ,還有希望拯救你的系統!在 Eli Schwartz 的個人源中提供了打包好的 pacman-static 二進位制包,用他的受信用戶(Trusted User)金鑰簽名,可以用這個完成系統升級。

測試 PKGBUILD

如果 makepkg 成功了,會在當前目錄產生一個 $pkgname-$pkgver.pkg.tar.gz 的檔案,可以使用 pacman -Upacman -A 安裝。你可以用 pacman 的查詢函數來檢驗產生的包有沒有問題,pacman -Qlp [package file] 可以列出包裡的所有檔案,pacman -Qip [package file] 列出包的描述資訊。

檢查包的邏輯性

確認包可以正常使用後,再使用 namcap 來檢查錯誤。

安裝 namcap。

1
sudo pacman -S namscap

如果你的 namcap 無法正常使用,可能是依賴的 Python 版本較新,而本機的 Python 沒有更新,-Syu 滾一下就好了。

1
2
$ namcap PKGBUILD
$ namcap <package file name>.pkg.tar.xz

Namcap 的作用:

  1. 檢查 PKGBUILD 的內容是否有常見錯誤,並檢查軟體包檔案結構中是否存在不必要、放錯的檔案
  2. ldd 檢查包裡的 ELF 檔案,自動報告缺失或可去除的依賴
  3. 啟發式搜尋遺失或多餘的依賴

要養成使用 namcap 檢查包的習慣,避免提交後再去修復的麻煩。

安裝包

寫好 PKGBUILD 且沒有問題後,用正常使用者執行 makepkg 測試可不可以正常安裝。

1
makepkg -si

參數說明:

  • s/--syncdeps: 在構建前會自動用 pacman 解析並安裝所有依賴,如果依賴 AUR 裡的包要在之前手動安裝
  • -i/--install: 當構建成功後安裝,可以用 pacman -U package_name.pkg.tar.xz 替代

其他有用的參數:

  • -r/--rmdeps: 編譯後移除編譯時需要的套件,然而這些套件可能會在軟體包更新的時候重新安裝
  • -c/--clean: 構建後清除臨時構建檔案,通常在調試過程中才會需要這些檔案

提交到 AUR

認證

要建立一組 AUR 的 SSH 金鑰,並為 aur.archlinux.org 指定金鑰位置。

編輯 ~/.ssh/config,並寫入下面內容。

1
2
3
Host aur.archlinux.org
  IdentityFile ~/.ssh/aur
  User aur

為 AUR 產生一組 SSH 金鑰。

1
ssh-keygen -f ~/.ssh/aur

如果沒有 AUR 帳戶的話先到 AUR 網站註冊一個帳戶,記得填入自己電腦的 SSH 公鑰,已註冊帳戶登入後到 [我的帳號] > [SSH 公開金鑰] 設定。

建立軟體包倉庫

如果是建立新軟體包,從 AUR clone 一個 pkgbase,下面指令中的 pkgbase 換成自己打包的套件名稱,如果該軟體包還不存在於 AUR 中,會提示該包還不存在。

1
git clone ssh://aur@aur.archlinux.org/pkgbase.git

如果已經有個軟體包了,在當前目錄初始化一個 git 倉庫並添加遠端 AUR 網址。

1
2
git init
git remote add label ssh://aur@aur.archlinux.org/pkgbase.git

提交與更新

當釋出新版本的包,更新 pkgverpkgrel 來通知所有使用者需要升級,如果 PKGBUILD 只有微小的更改(如:修正筆誤),不要去更改這兩個屬性的值。

PKGBUILD 的屬性修改了,就需要重新產生 .SRCINFO,否則 AUR 上將不會顯示更新後的版本。

上傳或更新到 AUR 的軟體包,至少要包含 PKGBUILD.SRCINFO 兩個檔案,注意第一條 commit 要包含兩個檔案

1
2
3
4
makepkg --printsrcinfo > .SRCINFO
git add PKGBUILD .SRCINFO
git commit -m "useful commit message"
git push

如果你「不幸」忘記在第一條 commit 的時候加入 .SRCINFO,沒錯,就是我,這裡提供一個解決方案。

先將 .SRCINFO add 並 commit,然後用 git rebase 來修改 commit 紀錄。

1
git rebase --root -i

會出現下面的編輯界面,把第二行的 pick 改成 s 存檔,這樣會將第二條 commit 與前一條合併。

git rebase 互動畫面

另一種方法是擷取自高見龍大大寫的《為你自己學 Git》,先將 .SRCINFO 加入追蹤,然後在 commit 的時候加上參數 --amend --no-edit,這個方法簡單點。

1
git commit --amend --no-edit

一定要加入兩個檔案,不然會報錯無法上傳到 AUR。

最後 git push 如果成功的話就會出現如下畫面。

軟體包上傳 AUR 成功

去 AUR 網站檢查一下自己提交的軟體包有沒有成功提交,下面是我提交後在 AUR 檢索到的結果。

AUR 網站查詢結果

總結

雖然整篇文章看下來很多內容,但打包主要就是兩步驟:編寫 PKGBUILD、makepkg,PKGBUILD 可以想像是一份安裝腳本,整體分成兩大部份,第一個是套件的描述、依賴,第二個就是編譯、打包函數的編寫,而在「打包」之後會產生一個壓縮檔,平常用 Pacman 安裝的套件就是安裝的包就是這種格式,可以想像成二進制,把整體概念搞懂,打包就不難了。

建議大家在打包軟體時可以先去 AUR 找類似的軟體(相同構建方式),並把該軟體的 PKGBUILD Clone 下來參考,這樣可以大大降低 PKGBUILD 的錯誤並加速編寫的速度。

希望大家能成功打包自己想用的軟體並提交到 AUR 或 Mainline 上,為 Arch Linux 做一份貢獻。

Reference