拋棄 Autotools 向 CMake 邁進(jìn)吧
CMake 是一個(gè)跨平臺(tái)的編譯、測(cè)試和打包軟件,即使你以前從來沒有使用過構(gòu)建系統(tǒng),也可以輕松上手。
在我以前的文章 Autotools 入門 一文中,我說明了如何使用 Autotools 來管理和打包代碼。這是一個(gè)強(qiáng)大且通用的平臺(tái),可輕松集成到許多打包系統(tǒng)中,包括 RPM、APT、pkgsrc 等等。它的語(yǔ)法和結(jié)構(gòu)可能會(huì)令人困惑,但幸運(yùn)的是,我們還有其他選擇,開源的 CMake 就是其中一個(gè)。
CMake 是一個(gè)用于構(gòu)建、測(cè)試和打包軟件的跨平臺(tái)套件。它使用簡(jiǎn)單而清晰的語(yǔ)法,因此即使你以前從未使用過構(gòu)建系統(tǒng),也很容易開始使用。
安裝 CMake
CMake 可能已經(jīng)安裝在你的 Linux 系統(tǒng)上。如果沒有,你可以使用發(fā)行版的程序包管理器進(jìn)行安裝:
$ sudo dnf install cmake
在 Debian 或者其他相似的系統(tǒng)上:
$ sudo apt install cmake
在 Mac 上,你可以使用 MacPorts 或者 Homebrew 來安裝:
$ sudo port install cmake
在 Windows 上,你可以使用 Chocolatey 或者直接從 CMake 網(wǎng)站 下載二進(jìn)制來安裝。
使用 CMake
對(duì)于想要從源代碼構(gòu)建軟件的開發(fā)人員或用戶來說,CMake 是一種快速簡(jiǎn)便的編譯和安裝方法。 CMake 分階段工作:
- 首先,在
cmake
步驟中,CMake 掃描計(jì)算機(jī)查看一些默認(rèn)設(shè)置。默認(rèn)設(shè)置包括庫(kù)的位置以及在系統(tǒng)上安裝軟件的位置。 - 接下來,使用系統(tǒng)上的
make
命令(在 Linux 上是 GUN Make,在 NetBSD 上是 NetBSD Make)來編譯程序。這個(gè)過程通常是將人類可讀的源代碼轉(zhuǎn)換成機(jī)器語(yǔ)言。 - 最后,在
make install
一步中,那些編譯過的文件將被拷貝到(在cmake
步驟中掃描出來的)計(jì)算機(jī)上合適的位置。
這看起來很簡(jiǎn)單,當(dāng)你使用 CMake 時(shí)就是這樣。
CMake 的可移植性
CMake 在設(shè)計(jì)時(shí)就考慮了可移植性。雖然它不能使你的項(xiàng)目在所有 POSIX 平臺(tái)上都能正常工作(這取決于作為開發(fā)者的你),但它可以確保將標(biāo)記為要安裝的文件安裝到已知平臺(tái)上最合適的位置。而且由于有了 CMake 之類的工具,對(duì)于高級(jí)用戶而言,根據(jù)其系統(tǒng)需求自定義和覆蓋任何不合適的選項(xiàng)都很容易。
使用 CMake,你只需要知道將哪些文件安裝到哪個(gè)常規(guī)位置即可。它會(huì)照顧其他一切。不再需要自定義安裝腳本,它們有可能在任何未經(jīng)測(cè)試的操作系統(tǒng)上失敗。
打包
像 Autotools 一樣,CMake 也得到了很好的打包支持。無論它們是打包成 RPM 還是 DEB 或 TGZ(或其他任何東西),將帶有 CMake 的項(xiàng)目交給打包者,他們的工作既簡(jiǎn)單又直接。打包工具支持 CMake,因此可能不需要進(jìn)行任何修補(bǔ)或者調(diào)整。在許多情況下,可以自動(dòng)將 CMake 項(xiàng)目整合到工作流中。
如何使用 CMake
要在項(xiàng)目中使用 CMake,只需在項(xiàng)目目錄中創(chuàng)建 CMakeLists.txt
文件。首先,聲明最低要求的 CMake 版本以及項(xiàng)目名稱和版本。CMake 會(huì)努力在盡可能長(zhǎng)時(shí)間內(nèi)保持兼容性,但是隨著你使用的時(shí)間越長(zhǎng),并且關(guān)注它最新的開發(fā)動(dòng)態(tài),你就會(huì)知道哪些特性是你所依賴的。
cmake_minimum_required(VERSION 3.10)
project(Hello VERSION 1.0)
如你可能已經(jīng)看到的那樣,CMake 的語(yǔ)法是一個(gè)帶有括號(hào)和參數(shù)的命令。大寫的 VERSION
字符串不是任意的,也不只是格式。它們是 project
命令中的有效參數(shù)。
在繼續(xù)之前,先寫一個(gè)簡(jiǎn)單的 C 或者 C++ 的 hello world
程序。為了簡(jiǎn)單,我就寫了六行 C 代碼,并把它保存在 hello.c
中(為了匹配我在 CMakeLists.txt
中可執(zhí)行文件的名字)。
#include <stdio.h>
int main() {
printf("Hello open source\n");
return 0;
}
不過,不要搞錯(cuò)了,CMake 不僅適用于 C 和 C++。它可以處理任意文件,并且有許多可用的命令,因此它可以幫助你維護(hù)許多不同形式的項(xiàng)目。
CMake 網(wǎng)站中記錄了所有有效的內(nèi)置命令及其可用參數(shù),因此無論你要做什么,都可以輕松發(fā)現(xiàn)所需的功能。不過,這是一個(gè)簡(jiǎn)單的示例,因此,你需要的下一個(gè)命令是必不可少的 —— 你必須為 CMake 定義要構(gòu)建的代碼:
add_executable(Hello hello.c)
這個(gè)命令指定了你編譯后的二進(jìn)制文件的名字為 Hello
。因此,它與你在終端中執(zhí)行帶有 -o Hello
的 gcc
命令是一樣的。
在一些比較復(fù)雜的項(xiàng)目中,你可能還需要使用庫(kù)文件,你可以使用 add library
命令來鏈接庫(kù)文件。
在你設(shè)置了你想要構(gòu)建和標(biāo)記為安裝的文件之后,你必須要告訴 CMake 一旦用戶安裝了程序,最終的應(yīng)用程序應(yīng)該在哪個(gè)位置。
在這個(gè)簡(jiǎn)單的例子里,你僅需要做的一件事就是在你的 CMakeLists.txt
文件里添加 install
命令。install
命令接受幾個(gè)參數(shù)。但是在這個(gè)例子中,你僅需要使用 TARGET
命令來指定你要安裝文件的名字。
install(TARGETS Hello)
向 CMake 工程添加一些文件
一個(gè)軟件項(xiàng)目向用戶交付的往往不僅僅只有代碼,還有一些其他的文件數(shù)據(jù),例如手冊(cè)或者是信息頁(yè)、示例項(xiàng)目,或者是配置文件。你可以使用與包含編譯文件時(shí)類似的工作流程,將任意數(shù)據(jù)包含在 CMake 項(xiàng)目中:在 CMakelists.txt
文件中使用 file
命令,然后說明一下這些文件要安裝在哪里。
例如,你可以在這個(gè)項(xiàng)目中包含一個(gè) assets
目錄,你可以使用 file
命令,后面跟上 COPY
和 DESTINATION
參數(shù)來告訴 CMake 將這些額外的文件復(fù)制到你的分發(fā)包中。
file(COPY assets DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
這個(gè) ${CMAKE_CURRENT_BINARY_DIR}
變量是一個(gè)特殊的 CMake 內(nèi)置變量,表示 CMake 正在處理的目錄。換句話說,你的任何文件都會(huì)被復(fù)制到編譯目錄(在你運(yùn)行 cmake
命令后,這個(gè)過程會(huì)更加清晰,到時(shí)候回過頭來看一下)。
因?yàn)檫@些額外的數(shù)據(jù)文件有些雜亂不堪(如果你不信的話,可以看一下 /usr/share
這個(gè)目錄)。對(duì)于你自己的項(xiàng)目創(chuàng)建一個(gè)子文件夾對(duì)誰(shuí)都有好處。最好也帶上版本名字。你可以通過在 CMAKE_CURRENT_BINARY_DIR
中指定一個(gè)新的目錄,使用你選擇的項(xiàng)目名稱,后面跟一個(gè)為你的項(xiàng)目命名的特殊變量和你在項(xiàng)目聲明中為它設(shè)置的 VERSION
。
file(COPY assets DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Hello-${Hello_VERSION}")
定義安裝位置
你已經(jīng)定義你要編譯的文件,因此現(xiàn)在你要告訴 CMake 你的程序要安裝在哪個(gè)位置。比如你的主程序,這個(gè)要程使用 install
命令:
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Hello-${Hello_VERSION}" TYPE DATA)
這里有一些新的參數(shù)。DIRECTORY
參數(shù)指定了數(shù)據(jù)文件是一個(gè)目錄,而不是一個(gè)文件(FILE
)或者腳本(SCRIPT
)。你使用的參數(shù)和復(fù)制一些額外文件到編譯目錄時(shí)是一樣。另外,在 install
命令中 TYPE
或者 DESTINATION
必須要指定其一。TYPE
參數(shù)指定了通用的文件類型,這些文件通常將會(huì)被放到合適的位置。在 Linux 系統(tǒng)上,TYPE DATA
一般是 /usr/local/share
或者 /usr/share
,除非用戶定義了其他的位置。
這是諸如 CMake 之類的良好構(gòu)建系統(tǒng)的強(qiáng)大功能之一。你不必?fù)?dān)心文件的確切位置,因?yàn)槟阒烙脩艨梢愿?CMake 的首選默認(rèn)設(shè)置,并且 CMake 將構(gòu)建代碼以使其正常工作。
運(yùn)行 CMake
CMake 有多種方式來讓你執(zhí)行命令,你可以在終端或者在一個(gè)可交互的程序上執(zhí)行命令,或者你也可以使用它的圖形界面(GUI)。我比較偏向于使用終端命令,但是我也喜歡使用一些其他的方式(相比與在 Makefile
中查找那些晦澀的變量然后去修改它們更勝一籌)。
對(duì)于編譯過開源 C++ 項(xiàng)目的任何人,都熟悉的第一步是創(chuàng)建一個(gè) build
目錄,進(jìn)入到該目錄,然后運(yùn)行 cmake ..
命令。 我是一個(gè)懶惰的打字員,所以我將構(gòu)建目錄命名為 b
,但是你可以使用最合適的方式:
$ mkdir b
$ cd b
$ cmake ..
-- The C compiler identification is GNU 11.1.1
-- The CXX compiler identification is GNU 11.1.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /var/home/seth/demo-hello/b
$
這或多或少相當(dāng)于經(jīng)典的 ./configure; make; make install
中的 ./configure
??匆幌履愕臉?gòu)建目錄,CMake 已經(jīng)幫你生成了幾個(gè)新的文件,來讓你的項(xiàng)目更完整。這里生成了 CMake 的數(shù)據(jù)文件、一個(gè)常規(guī)的 Makefile
文件(這是一個(gè)免費(fèi)提供的 247 行的文件,但對(duì)于越復(fù)雜的項(xiàng)目,行數(shù)要多得多),還有一個(gè)包含這個(gè)示例程序的任意非編譯數(shù)據(jù)的 Hello-1.0
目錄。
$ ls
CMakeCache.txt
CMakeFiles
Makefile
Hello-1.0
cmake_install.cmake
接下來,你可以進(jìn)行構(gòu)建。你可以使用 CMake 的 --build
選項(xiàng)來做這件事,使用當(dāng)前的構(gòu)建目錄作為源目錄。
$ cmake --build .
Scanning dependencies of target Hello
[ 50%] Building C object CMakeFiles/Hello.dir/hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
或者你可以運(yùn)行 make
命令。這將讀取由 CMake 生成的 Makefile
文件。在這個(gè)例子中,make
默認(rèn)的行為就是由源程序 hello.c
生成目標(biāo)文件。
$ make
Scanning dependencies of target Hello
[ 50%] Building C object CMakeFiles/Hello.dir/hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
$
如你所料,Hello
二進(jìn)制可執(zhí)行文件現(xiàn)在存在于當(dāng)前的構(gòu)建目錄中。因?yàn)樗且粋€(gè)簡(jiǎn)單的自包含應(yīng)用程序,所以你可以運(yùn)行它進(jìn)行測(cè)試:
$ ./Hello
Hello open source
$
最后,你可以用 --install
選項(xiàng)進(jìn)行安裝。因?yàn)槲也幌M业暮?jiǎn)單的 “hello world” 應(yīng)用程序真的被安裝到我的系統(tǒng)上,我設(shè)置了 --prefix
選項(xiàng),將 CMake 的目標(biāo)從根目錄(/
)重定向到 /tmp
的一個(gè)子目錄。
$ cmake --install . --prefix /tmp/hello/
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/local/bin/Hello
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file1
另外,你也可以運(yùn)行 make install
來調(diào)用 Makefile
的安裝動(dòng)作。同樣,為了避免在我的系統(tǒng)上安裝一個(gè)演示程序,我在這個(gè)例子中設(shè)置了 DESTDIR
變量,將安裝目標(biāo)重定向到 /tmp
的一個(gè)子目錄:
$ mkdir /tmp/dist-hello
$ make install DESTDIR=/tmp/dist-hello
[100%] Built target Hello
Install the project...
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/local/bin/Hello
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file1
看一下輸出的內(nèi)容,來確定它具體的安裝位置,這個(gè)程序已經(jīng)安裝好了。
快速自定義
CMake 的安裝前綴(由 CMAKE_INSTALL_PREFIX
變量指定)默認(rèn)是在 /usr/local
這個(gè)位置,但是所有的 CMake 變量都可以在你運(yùn)行 cmake
命令的時(shí)候,加一個(gè) -D
選項(xiàng)來改變它。
$ cmake -DCMAKE_INSTALL_PREFIX=/usr ..
$ make install DESTDIR=/tmp/dist-hello
$ make install DESTDIR=/tmp/dist-hello
[100%] Built target Hello
Install the project...
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/bin/Hello
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0/assets/file1
所有由 CMake 使用的變量都可以通過這種方式來修改。
交互式的 CMake
CMake 的交互模式是一種用于配置安裝環(huán)境的友好而有用的方法。要讓用戶知道該項(xiàng)目使用的所有可能的 CMake 變量是一件工作量很大的事,因此 CMake 交互式界面是他們無需查看 Makefile
和 CMakeLists
即可發(fā)現(xiàn)自定義選項(xiàng)的簡(jiǎn)便方法。
為了調(diào)用這個(gè)交互式的 CMake,使用 ccmake
命令,在這個(gè)簡(jiǎn)單的項(xiàng)目里沒有太多的東西。但是對(duì)于像 Rosegarden 這樣的大型項(xiàng)目,這將非常有用。
Rosegarden
CMake 的更多知識(shí)
還有很多很多的 CMake 知識(shí)需要去了解。作為一個(gè)開發(fā)者,我非常喜歡它簡(jiǎn)潔的語(yǔ)法、詳盡的文檔、可擴(kuò)展性以及便捷性。作為一個(gè)用戶我非常喜歡 CMake 友好且實(shí)用的錯(cuò)誤提示信息還有它的用戶界面,如果你的項(xiàng)目還未開始使用構(gòu)建系統(tǒng),請(qǐng)了解一下 CMake 吧。你以及以后嘗試打包你應(yīng)用程序的任何人都不會(huì)后悔。