Linux——從C代碼編譯出軟件

從C代碼編譯出軟件

Linux系統上,幾乎所有的東西都有它的源代碼——從內核、C庫,到網頁瀏覽器等等。你甚至可以使用源代碼來(重新)安裝你係統的某部分,來更新和加強你的系統。但是,你不應該讓所有東西都從源代碼構建並安裝,除非你真的很享受這個過程或者某些特殊原因使然。

Linux發行版的核心部分,如/bin中的程序,一般都不難更新,而且,Linux的安全問題通常都修復得很快。然而,不要期望你的發行版能爲你提供一切。以下這些原因就解釋了爲何你需要自行安裝某些包。

  • 你可按自己的需要進行設置。
  • 你可自己決定安裝位置。甚至你還可以安裝同一個包的不同版本。
  • 你可自行控制版本。發行版不一定會自帶所有包的最新版本,尤其是那些附屬的軟件包(如Python的庫)。
  • 你可瞭解該包如何運作。

軟件的構建系統

Linux上的編程環境有很多種:從傳統的C語言到如Python的解釋型腳本語言。它們各自都有至少一套區別於Linux發行版自帶工具的構建和安裝系統。

從C代碼安裝一個軟件,通常涉及這些步驟:

  • 將源代碼的歸檔解包;
  • 對包進行設置;
  • 運行make來構建程序;
  • 運行make install或者發行版特定的安裝命令來安裝該包。

解開C源碼包

一個軟件包的源碼包,通常是一個.tar.gz、.tar.bz2或.tar.xz文件,不過在此之前,還是先用tar tvf或tar ztvf來檢查下里面的內容,因爲有些包解開時不會建立自己的目錄。

如果輸出是這樣,那就可以解開它了:

package-1.23/Makefile.in
package-1.23/README
package-1.23/main.c
package-1.23/bar.c
--snip--

不過,它也有可能沒有將文件放在一個目錄(如上例的package-1.23)裏:

Makefile
README
main.c
--snip--

將上面這種包直接解包的話,會使你的當前目錄變得一團亂。爲了避免這種情況,你要先創建一個目錄,並cd進去,再在裏面進行解包。最後一點,你還要留意那些包含絕對路徑名的文件。

從哪裏開始

當你解包完,並看到一堆文件出現時,請先去找找README和INSTALL文件。無論如何,README文件都是要首先看的,因爲裏面含有關於該包的描述、手冊、安裝提示,以及其他有用的信息。很多包都會提供INSTALL文件,裏面是編譯和安裝軟件的指令。請注意其中特別的編譯器選項和定義。

除了README和INSTALL,你還會看到以下三類文件。

  • 與make系統相關的文件,例如Makefile、Makefile.in、configure、CMakeList.txt等。有些舊
    的軟件的Makefile可能需要你自己去改,但現在大多都會用GNU autoconf或CMake之類的配置工具。這些工具有其腳本或配置文件(如configure或CMakeList.txt),可根據你的系統設定和配置選項來幫你從Makefile.in中生成Makefile。
  • 以.c、.h或.cc結尾的源碼文件。包裏到處都會有C源碼文件。而C++源碼文件則通常以.cc、.C或.cxx結尾。
  • 以.o結尾的對象文件,或者二進制文件。一般來說,源碼包中是沒有對象文件的,但如果該包的維護者無權釋放源代碼而只能提供對象文件的話,那你就要自己處理它們了。大多數情況下,源碼包裏含有對象文件(或可執行的二進制文件),就意味着該軟件打包有問題,你需要運行make clean來進行全新的編譯。

GNU autoconf

雖然C代碼一般都是可移植的,但我們還是難以只靠一個Makefile來適應各平臺的差異。早期的解決方法是爲不同的操作系統提供不同的Makefile,或者是提供一個易於修改的Makefile。這種做法後來演變成了使用腳本來分析系統(這種分析以往只用於構建軟件包),再產生出Makefile。

GNU autoconf就是一套流行的、用於自動產生Makefile的系統。使用此套系統的包都會帶有configure、Makefile.in和config.h.in文件。其中.in文件是模板。它的做法是,運行configure腳本來分析你係統的特性,然後在.in文件的基礎上做一些替換,最後創建出真正的構建文件。對於終端用戶來說,這個過程是簡單的,只需要像下面這樣執行configure就能從Makefile.in產生出Makefile:

$ ./configure

因爲該腳本會先檢查你的系統,所以它會輸出一大堆診斷信息。如果一切正常,configure就會創建出一個或多個Makefile、一個config.h文件以及一個緩存文件config.cache。緩存文件能使你下次configure時不必再做一次系統檢查。

現在你可以運行make來編譯那個包了。雖然configure成功不代表make也能成功,但它作爲第一步來說還是很重要的。

一個autoconf的例子

你需要先把GNU coreutils包安裝在自己的root目錄裏(以確保不會影響整個系統)。該包可在http://ftp.gnu.org/gnu/coreutils/(通常最新的就是最好的)獲取,然後解開它,進入它的目錄中,進行以下配置:

$ ./configure --prefix=$HOME/mycoreutils
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
--snip--
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile

接着make它

$ make
GEN lib/alloca.h
GEN lib/c++defs.h
--snip--
make[2]: Leaving directory '/home/juser/coreutils-8.22/gnulib-tests'
make[1]: Leaving directory '/home/juser/coreutils-8.22'

下一步試着運行某個剛剛創建出的可執行文件,如./src/ls,再試着運行make check,來對該包進行一系列的檢查。(這會花一些時間,但是很有趣。)

最後,可以安裝該包了。先用-n選項空跑一次,看看它準備安裝些什麼:

$ make -n install

檢查一下輸出,如果沒有什麼異常(例如它準備安裝在mycoreutils以外的地方),就實施安裝:

$ make install

使用打包工具來安裝

大部分Linux發行版都帶有軟件打包工具,它創建出的安裝包能對由該包安裝的軟件進行後期維護。基於Debian的發行版(如Ubuntu)或許是最簡單的,就像下面這樣,使用checkinstall,而不單是使用make install:

# checkinstall make install

你可以用--pkgname=name選項來指定新包的名稱。創建RPM包會更復雜一點,因爲你得先創建一個目錄樹以便製作包。你可以使用rpmdev-setuptree命令來實現,然後再用rpmbuild工具來完成剩下的工作。

configure腳本的選項

剛纔你已看到了configure腳本最有用的選項之一:使用–prefix來指定安裝位置。autoconf默認生成的Makefile 中的install 目標都是使用/usr/local 作爲前綴: 二進制程序會去到/usr/local/bin,庫會去到/usr/local/lib,等等。更改前綴可執行如下命令:

$ ./configure --prefix=new_prefix

configure的大多數版本都有–help選項,可以列出其他配置選項。不幸的是,該列表實在太長了,難以得知哪些是重點。

  • --bindir=directory:將可執行程序裝在directory目錄。
  • --sbindir=directory:將系統級的可執行程序裝在directory目錄。
  • --libdir=directory:將庫裝在directory目錄。
  • --disable-shared:不構建共享庫(要看具體是什麼庫)。不構建的話,或許能避免一些後續的麻煩
  • --with-package=directory:告訴configure需要用到directory目錄的包。當某個庫不在標準位置時,這個選項是比較好用的。但不幸的是,並非所有的configure腳本都能識別這個選項,而且,它的語法不明確。

使用不同的構建目錄

首先在任意一個地方新建一個目錄,然後在新目錄運行原目錄的configure腳本。這樣所產生的Makefile將會依然使用原目錄的源碼,但make出的東西卻留在新目錄。(有些開發者更希望你這麼做,因爲這樣不僅不會在原目錄產生新的東西,而且還有利於使用相同的源碼爲不同平臺或使用不同配置選項進
行構建。)

環境變量

你可以通過修改一些會被configure腳本當成make變量的環境變量來影響configure的行爲。最重要的環境變量是CPPFLAGS、CFLAGS和LDFLAGS。但要小心的是,configure對環境變量是很挑剔的。比如說,對於頭文件目錄,你應該使用CPPFLAGS而不是CFLAGS,因爲configure經常會單獨運行預處理器。

在bash中,爲configure設置環境變量的最簡單的做法就是在./configure前放置變量的聲明。

$ CPPFLAGS=-DDEBUG ./configure

也可以用選型的形式來傳遞變量。

$ ./configure CPPFLAGS=-DDEBUG

使用環境變量來指引configure查找第三方include文件和庫也是很方便的

$ CPPFLAGS=-Iinclude_dir ./configure

要使連接器到lib_dir裏查找

$ LDFLAGS=-Llib_dir ./configure

autoconf的目標

若configure成功執行,你會發現它所生成的Makefile裏除了有標準的all和install,還包含如下所列的一些有用的目標。

  • make clean:它會清除所有對象文件、可執行程序和庫。
  • make distclean:它與make clean很像,只不過它清除的是所有自動產生的東西,包括Makefile、config.h、config.log等等。也就是說,它使整個源碼目錄就像剛解包出來一樣。
  • make check:有些包會自帶一些用於檢查所編譯出的程序是否正確的測驗;而make check就運行這些測驗。
  • make install-strip:它與make install很像,只不過是在安裝時,它會將可執行程序和庫內的符號表及其他調試信息都移除掉。移除之後,程序佔的空間會少很多。

autoconf的日誌文件

如果configure的執行過程出了錯,但你卻看不出哪裏有錯,那麼你可以檢查一下config.log。

pkg-config

第三方的庫有很多,如果都放在同一個地方,那會顯得很亂。但是,如果各自放在單獨的地方,那麼在連接時又會出現麻煩。例如,假設你要編譯OpenSSH,它需要用到OpenSSL庫,那麼你該怎樣將OpenSSL庫的位置告知OpenSSH的configure呢?

現在很多庫都用pkg-config來解決問題,它不僅可以公告include文件和庫的位置,還可以用於明確指定編譯和連接的選項。其語法如下:

$ pkg-config options package1 package2 ...

查找OpneSSL所需的庫,可用以下命令:

$ pkg-config --libs openssl

其輸出大概是這樣:

-lssl -lcrypto

想查看pkg_config所知的全部庫,用以下命令:

$ pkg-config --list-all

pkg-config的運作方式

pkg_config是通過讀取.pc配置文件來獲取包信息的。

例如,以下是Ubuntu中OpenSSL套接字庫的openssl.pc(在/usr/lib/i386-linux-gnu/pkgconfig中):

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib/i386-linux-gnu
includedir=${prefix}/include
Name: OpenSSL
Description: Secure Sockets Layer and cryptography libraries and tools
Version: 1.0.1
Requires:
Libs: -L${libdir} -lssl -lcrypto
Libs.private: -ldl -lz
Cflags: -I${includedir} exec_prefix=${prefix}

你可以修改這個文件,例如,給庫選項增加-Wl,-rpath=${libdir},以指定運行時動態連接的路徑。

使用標準位置以外的pkg-config文件

pkg-config不會在標準位置以外的地方查找.pc文件。如果一個.pc文件的所在位置不標準,如/opt/openssl/lib/pkgconfig/openssl.pc,那麼常規安裝的pkg-config是不會讀取到它的。以下是兩個基本的解決方法。

  • 將.pc文件的符號鏈接(或副本)集中到pkgconfig目錄中。
  • 使環境變量PKG_CONFIG_PATH包含那些另外的pkgconfig目錄。環境變量只在本shell及子shell內有效。

編譯和安裝的問題排查

如果你懂得區分編譯器錯誤、編譯器警告、連接器錯誤和共享庫問題,那你應該能應付構建軟件時出現的很多問題。本節會介紹一些常見的問題。雖然用autoconf的話是不太可能遇到這些問題的,但瞭解一下也無妨。

在進入細節之前,先確認自己能讀懂幾種make輸出。學會區分錯誤與被忽略的錯誤是很重要的。以下就是一個需要你檢查的真正的錯誤:

make: *** [target] Error 1

有些錯誤提示則是Makefile懷疑出錯了,但即使出錯也無害。像這類提示你可以忽略

make: *** [target] Error 1 (ignored)

大型的包中經常會出現GNUmake多次調用自身的情況。這時,每次make都會帶有一個[N],其中N是個數字。通常你能夠很快地在編譯出錯的提示中找到錯誤所在

[compiler error message involving file.c]
make[3]: *** [file.o] Error 1
make[3]: Leaving directory '/home/src/package-5.0/src'
make[2]: *** [all] Error 2
make[2]: Leaving directory '/home/src/package-5.0/src'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory '/home/src/package-5.0/'
make: *** [all] Error 2

具體錯誤

以下列舉了一些你可能會遇到的常見構建錯誤及其解釋與修復途徑。

編譯器錯誤信息:

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

src.c的第22行有對item的重複聲明。你將該行移除(使用註釋、#ifdef等等),即可修復。

編譯器錯誤信息:

src.c:37: 'time_t' undeclared (first use this function)
--snip--
src.c:37: parse error before '...'

缺少必要的頭文件。最好是從幫助手冊去獲知需要什麼頭文件

編譯器(預處理器)錯誤信息:

src.c:4: pkg.h: No such file or directory
(long list of errors follows)

編譯器對src.c運行C預處理器時,找不到include文件pkg.h。可能是有個庫沒安裝好,或者include文件在非常規位置,需要你指明。通常,你只需要爲預處理器選項(CPPFLAGS)加上-I這一include路徑選項。(你可能還需要一個連接器選項-L。)

如果出錯原因不是找不到庫,那還有一種可能,即該操作系統不支持這個代碼。你可以查看一下Makefile和README中關於平臺的要求。

如果你的發行版是基於Debian的,試試用apt-file命令來查找頭文件:

$ apt-file search pkg.h

它可能會幫你找到該包的開發版。而對於有yum的系統,則可以這樣做:

$ yum provides */pkg.h

make錯誤信息:

make: prog: Command not found

你要有prog程序才能構建該軟件。如果prog是cc、gcc或ld之類,那就說明你係統上沒有安裝開發工具。而如果你覺得你已安裝了,那就試試在Makefile中指明prog的完整路徑。

還有一種不常見的情況是make即時構建並使用prog。如果你的$PATH包含當前目錄(.)的話,那這是可以做到的。而如果你的$PATH不包含當前目錄,你可以將Makefile的prog改成./prog,或者暫時將.加到$PATH中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章