章節概覽#

本章介紹如何從 C 原始碼編譯與安裝軟體。大多數非專有的第三方 Unix 軟體以原始碼形式發布,原因有二:Unix 平台種類繁多,難以為所有平台提供二進位套件;開放原始碼的發布方式鼓勵社群貢獻修正與新功能。

雖然 Linux 發行版提供了方便的套件管理系統來更新核心元件,但你可能仍需自行編譯安裝軟體,理由包括:

  • 自訂設定選項
  • 將軟體安裝到任意位置,甚至同時安裝多個版本
  • 控制安裝的版本(發行版不一定提供最新版)
  • 更深入了解套件的運作方式

16.1 軟體建置系統 (Software Build Systems)#

Linux 上有多種程式語言與建置系統,從傳統的 C 到 Python 等直譯式語言,每種都有自己的套件建置與安裝方式。

本章聚焦於 GNU autotools 產生的 configuration scripts,這是最廣泛使用且穩定的建置系統之一。從 C 原始碼安裝套件的基本步驟為:

  1. 解壓原始碼壓縮檔
  2. 設定套件 (configure)
  3. 執行 make 編譯程式
  4. 執行 make install 安裝套件

閱讀本章前,建議先理解第 15 章關於開發工具的基礎知識。

16.2 解壓 C 原始碼套件 (Unpacking C Source Packages)#

原始碼通常以 .tar.gz.tar.bz2.tar.xz 格式發布。解壓前應先用 tar tvftar ztvf 檢查壓縮檔內容,確認檔案是否都在同一個子目錄中。

# 檢查壓縮檔內容
tar tvf package-1.23.tar.gz

需注意的事項#

  • 若檔案沒有共同的上層目錄,解壓會弄亂當前目錄——先建立新目錄再解壓
  • 若壓縮檔包含絕對路徑(如 /etc/passwd),代表可能含有惡意程式碼,應立即移除

解壓後的檔案分類#

解壓後先閱讀 READMEINSTALL 檔案,了解套件描述與安裝說明。套件檔案大致分為三類:

  • 建置相關檔案MakefileMakefile.inconfigureCMakeLists.txt
  • 原始碼檔案:副檔名為 .c.h.cc(C++ 則為 .cc.C.cxx
  • 目的檔 (object files):副檔名為 .o 或二進位檔(正常的原始碼發布不應包含,若有代表套件品質可能有問題)

16.3 GNU Autoconf#

C 原始碼雖然相當可攜,但各平台的差異使得單一 Makefile 無法適用於所有系統。GNU autoconf 是一套自動產生 Makefile 的系統,套件通常包含 configureMakefile.inconfig.h.in 等檔案。

核心流程:執行 configure 腳本分析系統特性,再根據 .in 模板檔產生實際的建置檔案。

./configure
make
make install

16.3.1 Autoconf 範例#

以 GNU coreutils 為例,示範完整的建置流程:

# 設定安裝到家目錄下的 mycoreutils
./configure --prefix=$HOME/mycoreutils

# 編譯
make

# 執行測試
make check

# 預覽安裝內容(乾跑)
make -n install

# 實際安裝
make install

安裝完成後,mycoreutils 目錄下會包含 binshare 等子目錄。

16.3.2 使用套件管理工具安裝#

可以透過發行版的套件工具來管理自行編譯的軟體:

  • Debian/Ubuntu:使用 checkinstall 工具取代 make install,自動建立 .deb 套件
# 用 checkinstall 取代 make install
checkinstall make install
  • RPM 系統:使用 rpmdev-setuptree 建立目錄結構,再用 rpmbuild 打包

16.3.3 configure 腳本選項#

--prefix 是最常用的選項,用來指定安裝目錄。預設的安裝前綴為 /usr/local

常用選項:

  • --prefix=directory:設定安裝前綴目錄
  • --bindir=directory:指定可執行檔安裝位置
  • --sbindir=directory:指定系統可執行檔安裝位置
  • --libdir=directory:指定函式庫安裝位置
  • --disable-shared:不建置 shared libraries
  • --with-package=directory:指定依賴套件的位置

可以建立獨立的 build directory 來實驗不同的設定選項,從該目錄執行原始碼目錄中的 configure 腳本即可。這樣原始碼目錄不會被修改,也方便為不同平台建置。

16.3.4 環境變數#

可以透過環境變數影響 configure 的行為。最重要的變數為 CPPFLAGSCFLAGSLDFLAGS

# 定義 preprocessor 巨集
CPPFLAGS=-DDEBUG ./configure

# 指定額外的 include 路徑
CPPFLAGS=-I/include_dir ./configure

# 指定額外的 library 路徑
LDFLAGS=-L/lib_dir ./configure

# 同時指定 library 路徑與 rpath
LDFLAGS="-L/lib_dir -Wl,-rpath=/lib_dir" ./configure

設定變數時要格外小心,一個小錯誤就可能導致 configure 失敗。例如 -I 中忘了加 - 就會導致「C compiler cannot create executables」錯誤。建議使用 CPPFLAGS 而非 CFLAGS 來指定 header file 目錄,因為 configure 常獨立於 compiler 執行 preprocessor。

16.3.5 Autoconf Targets#

configure 產生的 Makefile 除了標準的 allinstall 之外,還有幾個實用的 target:

  • make clean:移除所有 object files、executables 和 libraries
  • make distclean:類似 make clean,但額外移除所有自動產生的檔案(Makefiles、config.hconfig.log 等),讓原始碼目錄恢復到剛解壓的狀態
  • make check:執行套件的測試套件
  • make install-strip:類似 make install,但會 strip 掉 symbol table 和除錯資訊,產生更小的二進位檔

16.3.6 Autoconf 記錄檔#

configure 失敗時,可以檢查 config.log 來找出問題。由於 config.log 通常很大,建議的做法是:

  • 跳到檔案末尾,向上搜尋錯誤訊息
  • 搜尋 for more details 等文字片段,通常能快速定位到失敗的 configure 輸出附近

16.3.7 pkg-config#

當系統上有多個第三方函式庫安裝在不同位置時,pkg-config 工具可以幫助你找到正確的 include 路徑和 linker flags。

# 查詢 zlib 需要的 linker flags
pkg-config --libs zlib

# 列出所有已知的函式庫
pkg-config --list-all

pkg-config 的運作原理是讀取 .pc 設定檔(通常位於 lib/pkgconfig 目錄)。這些 .pc 檔包含函式庫的前綴路徑、include 路徑、linker flags 等資訊。

許多函式庫的 .pc 檔只在安裝了 development packages 後才會出現。例如在 Ubuntu 上,需要安裝 libssl-dev 才能取得 openssl.pc

.pc 檔位於非標準位置,有兩種解決方式:

  • 建立 symbolic link 到中央 pkgconfig 目錄
  • 設定 PKG_CONFIG_PATH 環境變數(不建議系統層級使用)

16.4 安裝實務 (Installation Practice)#

了解如何建置軟體很重要,但知道何時何處安裝同樣關鍵。

自行安裝的優點#

  • 自訂套件的預設設定
  • 安裝過程中能更深入了解套件
  • 自行控制版本
  • 更容易備份自訂套件
  • 便於在網路中分發

自行安裝的缺點#

  • 可能覆蓋系統既有的重要檔案
  • 耗時
  • 不會自動升級,需關注安全性更新
  • 可能設定錯誤

16.4.1 安裝位置#

預設的安裝前綴 /usr/local 是本機安裝軟體的傳統目錄。作業系統升級會忽略此目錄,適合小規模的本機軟體安裝。但若安裝了大量自訂軟體,/usr/local 可能變得雜亂,此時建議使用 16.3.2 節的套件管理方式。

16.5 套用修補檔 (Applying a Patch)#

Patch(又稱 diff)是對原始碼的修改,格式如下:

--- src/file.c.orig    2015-07-17 14:29:12.000000000 +0100
+++ src/file.c    2015-09-18 10:22:17.000000000 +0100
@@ -2,16 +2,12 @@

套用 patch 的方法:

# 直接套用(不剝離路徑前綴)
patch -p0 < patch_file

# 剝離一層路徑前綴(常用於不同版本號的情況)
patch -p1 < patch_file

套用 patch 前,注意 patch 檔開頭的路徑,確認你在正確的目錄下執行。若 patch 詢問「File to patch:」,通常代表你的工作目錄不正確,或原始碼與 patch 不匹配。

16.6 疑難排解編譯與安裝問題 (Troubleshooting Compiles and Installations)#

閱讀 make 的輸出時,要區分真正的錯誤可忽略的錯誤

# 真正的錯誤
make: *** [target] Error 1

# 可忽略的錯誤
make: *** [target] Error 1 (ignored)

在大型專案中,GNU make 會遞迴呼叫自身,錯誤訊息會標記 [N]。關鍵是找到第一個 make error 之前的 compiler error message。

16.6.1 常見編譯錯誤#

問題:型別衝突 (conflicting types)

src.c:22: conflicting types for 'item'
/usr/include/file.h:47: previous declaration of 'item'

修正方式:移除或註解掉 src.c 中重複宣告的那一行。

問題:未宣告的識別字 (undeclared identifier)

src.c:37: 'time_t' undeclared (first use this function)

修正方式:找出該型別所屬的 header file(透過 man 查詢),並在原始碼開頭加入對應的 #include

問題:找不到 header file

src.c:4: pkg.h: No such file or directory

修正方式:安裝缺少的開發套件,或透過 CPPFLAGS 加入 -I include 路徑。在 Debian 系統可用 apt-file search pkg.h 找到對應的套件。

問題:找不到指令 (Command not found)

make: prog: Command not found

修正方式:安裝缺少的工具(如 ccgccld),或修改 Makefile 指定工具的完整路徑。

16.7 展望 (Looking Forward)#

本章只涵蓋了建置軟體的基礎。進一步學習的方向包括:

  • 學習 CMakeSCons 等其他建置系統
  • 為自己的軟體設定建置系統(可參考 Autotools, 2nd edition, John Calcote)
  • 編譯 Linux kernel——其建置系統完全獨立,有自己的設定系統用於自訂 kernel 和 modules
  • 探索發行版特有的 source packages,如 Debian 的 debuild 和 RPM 的 mock

建置軟體是學習程式開發的踏腳石。透過本章與前一章的工具知識,你可以進一步深入原始碼、進行修改,甚至創建自己的軟體。