章 3. 修改原始碼

內容目錄

3.1. 設置 quilt
3.2. 修復上游 Bug
3.3. 把文件安裝到目的位置
3.4. 不一樣的函式庫名稱

請注意這裏沒有足夠的篇幅來描述修改上游原始碼的 全部 細節,但是這裏介紹了基本的步驟和常見的問題。

quilt 程序爲 Debian 打包工作提供了記錄上游源碼修改的基本方法。對默認配置加以少許修改往往非常有用,所以我們來創建一個別名 dquilt,以用於打包: 添加以下幾行內容到 ~/.bashrc 文件中。其中第二行可以給 dquilt 命令提供與 quilt 命令相同的 shell 補全特性:

alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
complete -F _quilt_completion $_quilt_complete_opt dquilt

現在按下面的方法來創建 ~/.quiltrc-dpkg 文件:

d=. ; while [ ! -d $d/debian -a `readlink -e $d` != / ]; do d=$d/..; done
if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then
    # if in Debian packaging tree with unset $QUILT_PATCHES
    QUILT_PATCHES="debian/patches"
    QUILT_PATCH_OPTS="--reject-format=unified"
    QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto"
    QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index"
    QUILT_COLORS="diff_hdr=1;32:diff_add=1;34:diff_rem=1;31:diff_hunk=1;33:diff_ctx=35:diff_cctx=33"
    if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi
fi

參見 quilt(1) 以及 /usr/share/doc/quilt/quilt.pdf.gz 來獲取有關 quilt 命令用法的信息。

讓我們假設你在上游的 Makefile 文件中找到了一個錯誤,其中的 install: gentoo 應該是 install: gentoo-target 才正確。

install: gentoo
        install ./gentoo $(BIN)
        install icons/* $(ICONS)
        install gentoorc-example $(HOME)/.gentoorc

讓我們使用 dquilt 修復這個問題,並把補丁命名爲 fix-gentoo-target.patch[22]

$ mkdir debian/patches
$ dquilt new fix-gentoo-target.patch
$ dquilt add Makefile

現在將 Makefile 修改爲如下的樣子:

install: gentoo-target
        install ./gentoo $(BIN)
        install icons/* $(ICONS)
        install gentoorc-example $(HOME)/.gentoorc

使用 dquilt 將補丁生成到 debian/patches/fix-gentoo-targe.patch 並根據 DEP-3: Patch Tagging Guidelines 添加描述:

$ dquilt refresh
$ dquilt header -e
... 描述補丁

大多數第三方程序將其本身安裝在 /usr/local 目錄下。在 Debian 中,這是保留給系統管理員的私有位置,因此 Debian 軟件包不可以使用比如/usr/local/bin 這樣的目錄,而應當使用系統目錄比如 /usr/bin, 以遵循文件系統層級結構標準: Filesystem Hierarchy Standard (FHS)。

通常在自動編譯程序時使用 make(1) 程序,接着執行 make install 就可以把程序直接按照 Makefile 文件中的 install target 安裝到指定的位置。爲了使 Debian 能夠提供編譯好的二進制軟件包,編譯系統將文件安裝到一個臨時目錄中創建的文件系統樹的鏡像中,而非直接安裝到實際的目標位置。

普通程序安裝過程和 Debian 打包安裝過程二者的區別可以由 debhelper 軟件包中的 dh_auto_configuredh_auto_install 透明地處理。但必須滿足以下條件:

  • Makefile 文件應當遵循 GNU 的規定支持 $(DESTDIR) 變量[23]

  • 源代碼必須遵循文件系統層級標準(FHS)。

使用 GNU autoconf 的程序自動遵守了GNU 的規定,這多少有利於打包過程的自動化。通過這項特點和其他啓發式處理,估計 debhelper 軟件可以直接打包約 90% 的軟件包而不需對編譯系統做出大的改變。所以打包也不是看起來那樣複雜。

如果你需要修改 Makefile 文件,就要確保其支持 $(DESTDIR) 變量。雖然默認情況下 $(DESTDIR) 變量沒有設置並且在程序安裝時會前置到每個文件的路徑中。打包腳本會將 $(DESTDIR) 設置爲臨時目錄。

對於從源碼包生成單個二進制包, dh_auto_install 將臨時目錄設置爲 debian/package[24] 臨時目錄中的全部文件都將在安裝軟件包時被安裝到用戶系統,唯一的區別是 dpkg 會把文件安裝到真實的根目錄樹中,而不是你的工作目錄。

請記住,即使你的程序正確安裝到了debian/package,仍然要考慮將 .deb 軟件包文件安裝到根目錄下的情形。所以絕對不允許構建系統將諸如 /home/me/deb/package-version/usr/share/package 這種詭異的內容硬編碼到軟件包文件中。

以下是 gentoo 軟件包的 Makefile 文件中的相關部分[25]

# Where to put executable commands on 'make install'?
BIN     = /usr/local/bin
# Where to put icons on 'make install'?
ICONS   = /usr/local/share/gentoo

可以看到文件被放到了 /usr/local 下。按照上邊的解釋,該目錄被 Debian 保留作本地用途,所以請把他們改爲:

# Where to put executable commands on 'make install'?
BIN     = $(DESTDIR)/usr/bin
# Where to put icons on 'make install'?
ICONS   = $(DESTDIR)/usr/share/gentoo

二進制文件、圖標和文檔等的更確切位置均已在文件層級標準(FHS)中作出了詳盡描述。本教程建議你快速瀏覽相關章節以獲取你打包需要用到的內容。

因此,我們應當把可執行二進制文件安裝到 /usr/bin 而非 /usr/local/bin,而 man 手冊頁則應放在 /usr/share/man/man1 而非 /usr/local/man/man1,依此類推。注意,gentooMakefile 裏沒有提及手冊頁,而按照 Debian Policy 的要求,每個程序都應當有一個手冊頁,我們將在稍後製作一個並安裝到 /usr/share/man/man1

有些程式不使用 Makefile 變量定義路徑,這意味着你可能需要去編輯 C 程式原始碼來使他們使用正確的路徑。但是到哪裏去搜索,哪些纔是呢?你可以通過以下的方法找到它們:

$ grep -nr --include='*.[c|h]' -e 'usr/local/lib' .

grep 會遞歸搜索整個原始碼樹並告訴你所有匹配項的檔案名和行號。

編輯那些文件,在那些行中用 usr/lib 替換 usr/local/lib。這個過程可以用如下方法自動化完成:

$ sed -i -e 's#usr/local/lib#usr/lib#g' \
        $(find . -type f -name '*.[c|h]')

如果你想要確認每一個替換操作,那麼下邊的方法可以讓你交互式地達成:

$ vim '+argdo %s#usr/local/lib#usr/lib#gce|update' +q \
        $(find . -type f -name '*.[c|h]')

緊接着你應該找到 install target (通常搜索以 install: 開頭的行即可),並把除 Makefile 頂部定義變量之外的目錄引用妥當修改。

原始的 gentooinstall target 是這樣:

install: gentoo-target
        install ./gentoo $(BIN)
        install icons/* $(ICONS)
        install gentoorc-example $(HOME)/.gentoorc

讓我們來修復這個上游BUG,並把修改使用 dquilt 命令記錄到debian/patches/install.patch

$ dquilt new install.patch
$ dquilt add Makefile

使用你喜歡的編輯器按照以下內容爲 Debian 軟件包作修改:

install: gentoo-target
        install -d $(BIN) $(ICONS) $(DESTDIR)/etc
        install ./gentoo $(BIN)
        install -m644 icons/* $(ICONS)
        install -m644 gentoorc-example $(DESTDIR)/etc/gentoorc

你一定會注意到規則裏在其他命令前有了一個 install -d 命令。原始的 Makefile 文件中沒有它,因爲通常情況下當你執行 make install 命令時, /usr/local/bin 和用到的其他目錄早已存在於系統中。然而當我們要向新建的私有目錄樹中安裝時,我們必須創建其中的每一個目錄。

我們還可以在末尾添加上其他的內容,比如上游作者有時會省略的附加文件:

        install -d $(DESTDIR)/usr/share/doc/gentoo/html
        cp -a docs/* $(DESTDIR)/usr/share/doc/gentoo/html

仔細檢查後如果沒有問題,使用 dquilt 創建 debian/patches/install.patch 補丁文件並添加對它的描述:

$ dquilt refresh
$ dquilt header -e
... 描述補丁

現在你有了一格系列的補丁。

  1. 修復上游 Bug:debian/patches/fix-gentoo-target.patch

  2. Debian 特有的打包修改:debian/patches/install.patch

當進行任何非 Debian 特有的修改時,比如 debian/patches/fix-gentoo-target.patch,一定要向上游作者進行反饋,以便上游作者方便在下一版本中以使更多人受益。同時請記住 不要 作特別針對 Debian 或 Linux (甚至是 Unix!)的修改,要使其可以移植,這會使你的修改更容易被接受。

注意你不一定要把 debian/* 都提交到上游。

還有另外一個常見的問題:不同平臺之間的庫名稱常常因平臺而異。例如一個 Makefile 中可能引了用一個 Debian 系統中不存在的庫。這種情況下我們需要將其修改爲 Debian 中存在的、提供完全相同功能的庫。

如果你手中程序的 Makefile(或 Makefile.in)中有如下的行。

LIBS = -lfoo -lbar

如果你的程序由於 foo 庫不存在,而 Debian 系統中的 foo2 庫提供了其等效,那麼你可以修復這個構建問題並將修改記錄到 debian/patches/foo2.patch 中,只需要將 foo 切換到 foo2:[26]

$ dquilt new foo2.patch
$ dquilt add Makefile
$ sed -i -e 's/-lfoo/-lfoo2/g' Makefile
$ quilt refresh
$ quilt header -e
... 描述補丁


[22] debian/patches 目錄應當是在你按照前面所述的步驟運行 dh_make 時生成的。而在這個例子中我們新創建它,因爲假設的是在更新一個已存在的軟件包。

[24] 對於單個源碼包生成多個二進制包,dh_auto_install 將臨時目錄設置爲debian/tmp,而 dh_install 命令則將文件按照 debian/package-1.installdebian/package-2.install 等文件的描述將 debian/tmp 中的文件分別裝入 debian/package-1debian/package-2 等臨時目錄用以創建 package-1_*.debpackage-2_*.deb 等二進制軟件包。

[25] 這只是一個演示 Makefile 正常形態的例子。如果 Makefile 是通過 ./configure 命令生成的,那麼修復該類 Makefile 的方法是通過 dh_auto_configure 來執行 ./configure,並帶上包括 --prefix=/usr 的默認選項。

[26] 如果從 foo 庫切換到 foo2 庫時要更改應用程序接口(API),這就要求我們修改源代碼來符合新的 API。