章 3. 修改原始碼

# 章 3. 修改原始碼

本教程文件已被重寫為另外的 Debian 維護者指導 文件，其中包含了更新的內容與更多實際例子。請使用新的教程作為主要的教程文件。

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

## 3.1. 設置 quilt

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

    alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
    . /usr/share/bash-completion/completions/quilt
    complete -F _quilt_completion -o filenames 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 命令用法的信息。

## 3.2. 修復上游 Bug

假設你在上游的 `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
    ... 描述補丁

## 3.3. 把文件安裝到目的位置

大多數第三方程序將其本身安裝在 `/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_configure 和 dh_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`，依此類推。注意，`gentoo` 的 `Makefile` 裏沒有提及手冊頁，而按照 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` 頂部定義變量之外的目錄引用妥當修改。

原始的 `gentoo` 的 `install` 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/*` 都提交到上游。

## 3.4. 不一樣的函式庫名稱

還有另外一個常見的問題：不同平臺之間的庫名稱常常因平臺而異。例如一個 `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 時生成的。而在這個例子中我們新創建它，因爲假設的是在更新一個已存在的軟件包。

[23] 參見 GNU Coding Standards: 7.2.4 DESTDIR: Support for Staged Installs。

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

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

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

章 2. 第一步 |  | 章 4. `debian` 目錄中的必須內容