從 0 到 1 學(xué) CMake:開啟高效跨平臺構(gòu)建之旅
在當(dāng)今軟件開發(fā)生態(tài)系統(tǒng)中,構(gòu)建工具宛如幕后的 “大管家”,掌控著從源代碼到可執(zhí)行程序或庫的復(fù)雜流程。它們不僅要應(yīng)對不同編程語言的特性,還需適配各類操作系統(tǒng)與編譯器組合,其重要性不言而喻。今天,我們要聚焦一款在跨平臺構(gòu)建領(lǐng)域熠熠生輝的工具 ——CMake。
你是否曾為在 Windows、Linux 和 macOS 等不同平臺上,將代碼順利轉(zhuǎn)化為可運行程序而絞盡腦汁?是否在面對復(fù)雜項目中眾多源文件、庫依賴時,感到構(gòu)建過程猶如一團亂麻,無從下手?CMake 的誕生,正是為了化解這些棘手難題。它就像一位經(jīng)驗豐富、足智多謀的項目經(jīng)理,能有條不紊地管理項目構(gòu)建的全過程,讓你的代碼跨越平臺的界限,在各種環(huán)境中都能精準(zhǔn)無誤地編譯和運行。
無論是初涉編程的新手,還是久經(jīng)沙場的開發(fā)老兵,尤其是那些投身 C++、C 等編譯型語言項目的開發(fā)者,掌握 CMake 都將成為你提升開發(fā)效率、邁向更高層次的關(guān)鍵一步。接下來,就讓我們一同深入 CMake 的奇妙世界,開啟這場從 0 到 1 的探索之旅 。
一、CMake簡介
1. 什么是CMake
CMake是個一個開源的跨平臺自動化建構(gòu)系統(tǒng),用來管理軟件建置的程序,并不相依于某特定編譯器;并可支持多層目錄、多個應(yīng)用程序與多個庫。它用配置文件控制建構(gòu)過程(build process)的方式和Unix的make相似,只是CMake的配置文件取名為CMakeLists.txt。
CMake并不直接建構(gòu)出最終的軟件,而是產(chǎn)生標(biāo)準(zhǔn)的建構(gòu)檔(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建構(gòu)方式使用。這使得熟悉某個集成開發(fā)環(huán)境(IDE)的開發(fā)者可以用標(biāo)準(zhǔn)的方式建構(gòu)他的軟件,這種可以使用各平臺的原生建構(gòu)系統(tǒng)的能力是CMake和SCons等其他類似系統(tǒng)的區(qū)別之處。它首先允許開發(fā)者編寫一種平臺無關(guān)的CMakeList.txt 文件來定制整個編譯流程,然后再根據(jù)目標(biāo)用戶的平臺進一步生成所需的本地化 Makefile 和工程文件,如 Unix的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once, run everywhere”。
顯然,CMake 是一個比上述幾種 make 更高級的編譯配置工具。“CMake”這個名字是"Cross platform MAke"的縮寫。雖然名字中含有"make",但是CMake和Unix上常見的“make”系統(tǒng)是分開的,而且更為高端。它可與原生建置環(huán)境結(jié)合使用,例如:make、蘋果的Xcode與微軟的Visual Studio。
2. 為什么選擇 CMake?
在軟件開發(fā)的構(gòu)建領(lǐng)域中,CMake 憑借眾多顯著優(yōu)點脫穎而出,成為眾多開發(fā)者的首選。
(1) 跨平臺性:在如今多樣化的開發(fā)環(huán)境下,一款軟件往往需要在不同的操作系統(tǒng)上運行,如 Windows、Linux、macOS 等,甚至還可能涉及到移動平臺如 Android 。CMake 強大的跨平臺特性就像是一位萬能的翻譯,它允許開發(fā)者編寫一次構(gòu)建腳本,然后就能在各種主流操作系統(tǒng)上生成對應(yīng)的構(gòu)建文件,確保軟件在不同平臺上都能順利編譯和運行 。以一個簡單的 C++ 項目為例,無論是在 Windows 系統(tǒng)下使用 Visual Studio 編譯器,還是在 Linux 系統(tǒng)下使用 GCC 編譯器,只需一份相同的 CMakeLists.txt 文件,CMake 就能根據(jù)不同的平臺環(huán)境生成合適的構(gòu)建腳本,大大提高了開發(fā)效率和代碼的可移植性。
(2) 模塊化:當(dāng)項目規(guī)模逐漸增大,代碼量不斷增多時,合理的模塊劃分對于項目的管理和維護至關(guān)重要。CMake 支持模塊化的項目配置,開發(fā)者可以將項目按照功能、模塊等維度進行拆分,每個模塊都有自己獨立的 CMakeLists.txt 文件 。這樣一來,各個模塊的構(gòu)建規(guī)則和依賴關(guān)系都能清晰地定義和管理,不僅方便了開發(fā)過程中的協(xié)作,也使得項目的結(jié)構(gòu)更加清晰,易于維護和擴展。例如,在一個大型游戲開發(fā)項目中,可能會將游戲的渲染模塊、邏輯模塊、網(wǎng)絡(luò)模塊等分別獨立管理,每個模塊都可以獨立編譯、測試和更新,而不會影響到其他模塊的正常運行。
(3) 可擴展性:隨著項目需求的不斷變化和發(fā)展,構(gòu)建系統(tǒng)也需要具備一定的靈活性和可擴展性。CMake 允許開發(fā)者編寫自定義的模塊和宏,以滿足項目特定的構(gòu)建需求 。比如,在一些對編譯優(yōu)化有特殊要求的項目中,開發(fā)者可以編寫自定義的 CMake 模塊,添加特定的編譯選項和鏈接庫;在一些需要自動化部署的項目中,開發(fā)者可以通過編寫宏來實現(xiàn)自動化的打包和發(fā)布流程。這種高度的可擴展性使得 CMake 能夠適應(yīng)各種復(fù)雜的項目場景,無論是小型的個人項目,還是大型的企業(yè)級項目,都能發(fā)揮出它的優(yōu)勢。
(4) 自動依賴管理:在項目開發(fā)過程中,依賴關(guān)系的管理往往是一個繁瑣且容易出錯的環(huán)節(jié)。CMake 能夠自動檢測項目所依賴的庫文件和頭文件,并在構(gòu)建過程中自動處理這些依賴關(guān)系 。它通過 find_package 等命令,可以在系統(tǒng)中查找指定的依賴庫,并設(shè)置相應(yīng)的變量,確保項目在編譯和鏈接時能夠正確地找到這些依賴項。例如,當(dāng)項目依賴于 OpenCV 庫時,只需在 CMakeLists.txt 文件中使用 find_package (OpenCV REQUIRED) 命令,CMake 就會自動查找系統(tǒng)中安裝的 OpenCV 庫,并將其包含路徑和庫文件路徑設(shè)置好,開發(fā)者無需手動去配置這些復(fù)雜的路徑信息,大大減少了因依賴管理不當(dāng)而導(dǎo)致的錯誤。
(5) 簡化構(gòu)建過程:相較于傳統(tǒng)的構(gòu)建方式,如手動編寫 Makefile 文件,CMake 使用一種簡潔、直觀的語法來描述項目的構(gòu)建規(guī)則 。開發(fā)者只需要在 CMakeLists.txt 文件中使用簡單的命令,如 project 定義項目名稱、add_executable 生成可執(zhí)行文件、add_library 生成庫文件等,就能清晰地定義項目的構(gòu)建過程。而且,CMake 還提供了豐富的命令和選項,方便開發(fā)者進行各種配置,如設(shè)置編譯選項、添加鏈接庫、定義自定義目標(biāo)等。這使得構(gòu)建過程更加自動化和規(guī)范化,降低了開發(fā)者的學(xué)習(xí)成本和工作量。
與傳統(tǒng)的構(gòu)建方式相比,CMake 的優(yōu)勢更加明顯。在傳統(tǒng)的 Makefile 構(gòu)建方式中,對于不同的平臺和編譯器,可能需要編寫不同的 Makefile 文件,而且Makefile的語法較為復(fù)雜,對于復(fù)雜的項目依賴關(guān)系處理起來也比較困難。而 CMake 通過統(tǒng)一的構(gòu)建腳本和強大的功能,解決了這些問題,讓構(gòu)建過程變得更加簡單、高效和可靠 。
二、CMake使用教程
1. 安裝 CMake
CMake 的安裝過程相對簡單,下面將分別介紹在 Windows、Linux 和 macOS 系統(tǒng)下的安裝步驟。
(1) Windows 系統(tǒng):
- 首先,前往CMake 官方網(wǎng)站下載 Windows 版本的安裝包,通常是.msi 文件。
- 下載完成后,雙擊.msi 文件,按照安裝向?qū)У闹甘具M行安裝。在安裝過程中,務(wù)必勾選 “Add CMake to the system PATH for all users” 選項,這樣可以將 CMake 添加到系統(tǒng)的 PATH 環(huán)境變量中,方便后續(xù)在命令行中直接使用 cmake 命令。
- 安裝完成后,打開命令提示符(CMD)或 PowerShell,輸入cmake --version,如果能正確顯示 CMake 的版本信息,說明安裝成功。例如:cmake version 3.26.4 。
(2) Linux 系統(tǒng):不同的 Linux 發(fā)行版安裝方式略有不同。
(3) Debian 和 Ubuntu 系統(tǒng):打開終端,輸入以下命令進行安裝:
sudo apt-get update
sudo apt-get install cmake
(4) Fedora 系統(tǒng):在終端中執(zhí)行:
sudo dnf install cmake
(5) Arch Linux 系統(tǒng):使用以下命令安裝:
sudo pacman -S cmake
安裝完成后,在終端輸入cmake --version驗證安裝是否成功。
(6) macOS 系統(tǒng):
通過 Homebrew 安裝:如果你的macOS 系統(tǒng)安裝了 Homebrew 包管理器,打開終端,執(zhí)行以下命令即可安裝 CMake:
brew install cmake
通過官方安裝包:訪問CMake官方網(wǎng)站,選擇 macOS 版本的.dmg 文件進行下載。下載完成后,運行.dmg 文件,將 CMake 圖標(biāo)拖動到應(yīng)用程序文件夾。
安裝成功后,命令都在/Applications/CMake.app/Contents/bin目錄下,需要將環(huán)境變量添加到.bash_profile文件中。使用 vim 進行編輯:
vim ~/.bash_profile
將以下內(nèi)容添加到文件末尾:
export PATH="/Applications/CMake.app/Contents/bin:$PATH"
添加完成后,執(zhí)行source ~/.bash_profile或者重新啟動終端。最后,在終端輸入cmake --version確認(rèn) CMake 已正確安裝。在使用 CMake 構(gòu)建項目之前,需要了解幾個重要的概念:
- CMakeLists.txt 文件:這是 CMake 構(gòu)建系統(tǒng)的核心配置文件,每個項目或子目錄都可以有一個 CMakeLists.txt 文件。它包含了一系列的 CMake 命令和指令,用于描述項目的構(gòu)建過程,例如指定項目名稱、源文件位置、鏈接庫、編譯選項等。通過編寫 CMakeLists.txt 文件,開發(fā)者可以以一種平臺無關(guān)的方式定義項目的構(gòu)建規(guī)則,CMake 會根據(jù)這些規(guī)則生成適合不同平臺的構(gòu)建文件 。
- 生成器:CMake 本身并不直接構(gòu)建項目,而是通過生成器(Generator)來生成構(gòu)建文件。生成器是一種模板驅(qū)動的機制,它根據(jù) CMakeLists.txt 文件和用戶的配置,生成特定平臺和構(gòu)建工具所需的構(gòu)建文件,如 Unix 系統(tǒng)下的 Makefile、Windows 系統(tǒng)下 Visual Studio 的項目文件(.sln 和.vcxproj)、Xcode 項目文件(.xcodeproj)等 。不同的生成器適用于不同的開發(fā)環(huán)境和構(gòu)建需求,開發(fā)者可以根據(jù)項目的實際情況選擇合適的生成器。
- 構(gòu)建目錄:構(gòu)建目錄是存放生成的構(gòu)建文件以及編譯過程中產(chǎn)生的中間文件和最終目標(biāo)文件的地方。為了保持項目源代碼的整潔,通常建議將構(gòu)建目錄與源代碼目錄分開。在構(gòu)建項目時,首先進入構(gòu)建目錄,然后執(zhí)行 CMake 命令生成構(gòu)建文件,最后使用構(gòu)建工具(如 Make、Ninja 等)在該目錄下進行項目的編譯和鏈接 。例如,在一個名為my_project的項目中,可以在項目根目錄下創(chuàng)建一個build目錄作為構(gòu)建目錄,所有的構(gòu)建相關(guān)文件都將在這個目錄中生成和處理。
2. 基礎(chǔ)的構(gòu)建步驟(步驟1)
一個最常用的基礎(chǔ)項目是從源碼中構(gòu)建一個可執(zhí)行文件。對于一個簡單的項目兩行 CMakeLists.txt 文件就能搞定:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
上面的例子中使用了小寫的命令,事實上,CMakeLists.txt 文件并不區(qū)分命令的大小寫。tutorial.cxx源碼是用來計算一個數(shù)的算數(shù)平方根,下面是其一個簡單的版本:
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf(stdout,"Usage: %s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
system("pause");
return 0;
}
譯者這里在 16 行附加了一行system("pause");,是為了程序執(zhí)行完畢后不會立刻關(guān)閉窗口。后面的代碼的示例中并不會再添加此行,如果用戶需要暫停的話,可以在自己的代碼中加入該行。
(1) 添加版本號和配置頭文件
第一個要添加的特性就是給我們的可執(zhí)行文件和項目提供一個版本號。你可以在你的源碼之外做到這一點,在 CMakeLists.txt 文件中做這些會更加靈活。為了添加一個版本號我們修改我們的 CMakeList.txt 文件如下:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
因為配置文件會被寫入到生成路徑(binary tree) 中,所以我們必須將該文件夾添加到頭文件搜索路徑中。接下來我們在源碼中創(chuàng)建一個包含以下內(nèi)容的 http://TutorialConfig.h.in 文件:
// the configured options and settings for Tutorial #define Tutorial_VERSION_MAJOR
@Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
當(dāng) CMake 配置這個頭文件的時候,@Tutorial_VERSION_MAJOR@ 和 @ Tutorial_VERSION_MINOR@ 就會用CMakeLists.txt 文件中對應(yīng)的值替換。接下來我們修改 tutorial.cxx 源碼包含配置頭文件并使用版本號,修改后的源碼如下:
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
int main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf(stdout,"%s Version %d.%d\n",
argv[0],
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
fprintf(stdout,"Usage: %s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
(2) 構(gòu)建項目并執(zhí)行文件
官方并沒有給出如何構(gòu)建項目,這里以 VS 為例介紹如何構(gòu)建以上項目并編譯執(zhí)行。在該目錄下面建立 build 文件夾,并新建 run.cmd 文件,編寫內(nèi)容如下:
echo off
echo build:
cmake -G "Visual Studio 15 2017 Win64" ..
echo compile:
devenv Tutorial.sln /build "Debug|x64"
echo run:
start ./Debug/Tutorial.exe %1
上面腳本中 echo命令主要是用來輸出提示信息,可以忽略。剩下一共有三行代碼。第3行代碼為使用 CMake 構(gòu)建工程文件.-G 參數(shù)用來指定編譯器,如果不寫這里會找到一個默認(rèn)的編譯器。我這里默認(rèn)的編譯器就是 VS2017,但是默認(rèn)構(gòu)建的程序為 32 位程序,我這里顯示的指定使用 VS2017 構(gòu)建 64 位程序。
第5行代碼是使用命令行的形式編譯 VS 的 .sln 文件。關(guān)于命令行構(gòu)建 VS 項目這里不做過多介紹,有興趣可以參考微軟官方給出的 Devenv command line switches。當(dāng)然我們也可以使用 VS 打開 .sln 文件,然后手動點擊 生成 。第7行代碼為運行程序。
3. 添加一個庫文件(步驟2)
現(xiàn)在我們將會給我們的項目添加一個庫文件。這個庫文件包含了我們自己實現(xiàn)的開方運算??蓤?zhí)行文件使用這個庫替代編譯器提供的標(biāo)準(zhǔn)開方運算。本教程中我們將其放到 MathFunctions 文件夾下,該文件夾下還有一個包含下面一行代碼的 CMakeLists.txt 文件。
add_library(MathFunctions mysqrt.cxx)
mysqrt.cxx 文件只有一個名為 mysqrt 的函數(shù),其提供了和標(biāo)準(zhǔn)庫 sqrt 相同的功能。內(nèi)容如下(官網(wǎng)官方例程中可以找到):
#include "MathFunctions.h"
#include <stdio.h>
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result;
double delta;
result = x;
// do ten iterations
int i;
for (i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
delta = x - (result * result);
result = result + 0.5 * delta / result;
fprintf(stdout, "Computing sqrt of %g to be %g\n", x, result);
}
return result;
}
對應(yīng)的頭文件為 MathFunction.h,其內(nèi)容如下:
double mysqrt(double x);
為了構(gòu)建并使用新的庫文件,我們需要在頂層 CMakeList.txt 文件添加 add_subdirectory 語句。我們需要添加額外的頭文件包含路徑,以便將包含函數(shù)原型的 MathFunctions/MathFunctions.h 頭文件包含進來。最后我們還需要給可執(zhí)行文件添加庫。
最終頂層 CMakeList.txt 文件的最后幾行如下所示:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
現(xiàn)在我們考慮將 MathFunctions 庫作為一個可選項。雖然在這里并沒有什么必要,但是如果庫文件很大或者庫文件依賴第三方庫你可能就希望這么做了。首先先在頂層 CMakeLists.txt 文件添加一個選項:
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
這個選項會在 CMake GUI 中顯示并會將默認(rèn)值設(shè)置為 ON,用戶可以根據(jù)需求修改該值。這個設(shè)置會本保存下來,所以用戶無需在每次運行 CMake 時都去設(shè)置。接下來就是將構(gòu)建和連接 MathFunctions 設(shè)置為可選項。修改頂層的 CMakeLists.txt 文件如下所示:
# add the MathFunctions library?
#
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial ${EXTRA_LIBS})
這里使用 USE_MYMATH 來決定是否編譯并使用 MathFunctions 。注意收集可執(zhí)行文件的可選連接庫所使用的變量(這里為 EXTRA_LIBS)的使用方法。這種方法在保持有許多可選組件的大型項目整潔時經(jīng)常使用。對應(yīng)的我們修改源碼如下:
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
int main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf(stdout,"%s Version %d.%d\n", argv[0],
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
fprintf(stdout,"Usage: %s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
#ifdef USE_MYMATH
double outputValue = mysqrt(inputValue);
#else
double outputValue = sqrt(inputValue);
#endif
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
在源碼中我們同樣使用了 USE_MYMATH 宏。這個宏由 CMake 通過在配置文件 TutorialConfig.h 添加以下代碼傳遞給源碼:
#cmakedefine USE_MYMATH
構(gòu)建、編譯和運行使用的代碼和上一節(jié)相同。
4. 安裝和測試(步驟3)
下一步我們將給我們的項目添加安裝規(guī)則和測試。安裝規(guī)則簡單明了。對于 MathFunctions 庫的安裝,我們通過在 MathFunction 的 CMakeLists.txt 文件中添加以下兩行來設(shè)置其庫和頭文件的安裝。
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
對于本文這個應(yīng)用通過在頂層 CMakeLists.txt 添加以下內(nèi)容來安裝可執(zhí)行文件和配置的頭文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
以上就是安裝的全部步驟。現(xiàn)在你應(yīng)該可以編譯本教程了。輸入 make install(或在 IDE 中編譯 install 項目),對應(yīng)的頭文件、庫文件和可執(zhí)行文件就會被安裝。CMake 的 CMAKE_INSTALL_PREFIX 參數(shù)可以指定安裝文件的根目錄(之前還可以加上 -D 參數(shù),具體意義可以參考 what does the parameter "-D" mean)。 添加測試過程同樣簡單明了。
在頂層 CMakeLists.txt 文件的最后我們可以添加一個基礎(chǔ)測試數(shù)據(jù)來驗證該應(yīng)用程序是否正常運行。
include(CTest)
# does the application run
add_test (TutorialRuns Tutorial 25)
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
在編譯完成之后,我們可以運行 "ctest" 命令行工具來執(zhí)行測試。第一個測試簡單的驗證了程序是否工作,是否有嚴(yán)重錯誤并且返回0.這是 Ctest 測試的基礎(chǔ)。接下來的一些測試都使用了 PASS_REGULAR_EXPRESSION 測試屬性(正則表達式)來驗證輸出中是否包含了特定的字符串。這里驗證開方是否正確并且在計算錯誤時輸出輸出對應(yīng)信息。如果你希望添加很多的測試來測試不同的輸入值,你可以考慮定義一個像下面這樣的宏:
#define a macro to simplify adding tests, then use it
macro (do_test arg result)
add_test (TutorialComp${arg} Tutorial ${arg})
set_tests_properties (TutorialComp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# do a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
每一次調(diào)用 do_test 就會根據(jù)指定的信息生成一個新的測試。
該步驟對應(yīng)的 build 文件夾下的構(gòu)建和運行腳本 run.cmd 內(nèi)容如下:
echo off
echo build:
cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=D:\project\cpp\cmake\tutorial\step3\install ..
echo compile:
devenv Tutorial.sln /build "Debug|x64"
echo install:
devenv Tutorial.sln /build "Debug|x64" /project INSTALL
echo test:
devenv Tutorial.sln /build "Debug|x64" /project RUN_TESTS
安裝位置根據(jù)自己的需要進行調(diào)整。
5. 添加系統(tǒng)自檢(步驟 4)
接下來我們考慮給我們的項目添加一些取決于目標(biāo)平臺是否有一些特性的代碼。這里我們將添加一些取決于目標(biāo)平臺是否有 log 和 exp 函數(shù)的代碼。當(dāng)然對于大多數(shù)平臺都會有這些函數(shù),但這里我們認(rèn)為這并不常見。如果平臺有 log 函數(shù)我們將在 mysqrt 函數(shù)中使用它計算平方根。我們首先在頂層 CMakeLists.txt 文件中使用 CheckFunctionExists 宏測試這些函數(shù)是否可用,代碼如下:
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
一定要在使用 configure_file 生成 TutorialConfig.h 之前測試 log 和 exp。因為 configure_file 命令會立刻使用當(dāng)前 CMake 的設(shè)置配置文件。最后根據(jù) log 和 exp 是否在我們的平臺上可用我們給 mysqrt 函數(shù)提供一個可選的實現(xiàn),代碼如下:
// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
. . .
6. 添加一個生成的文件和生成器(步驟 5)
在這一章節(jié)我們將會展示如何在構(gòu)建一個應(yīng)用的過程中添加一個生成的源文件。在本例中我們將創(chuàng)建一個預(yù)先計算的平方根表作為構(gòu)建過程的一部分,然后將其編譯到我們的應(yīng)用中。為了做到這一點我們首先需要一個能產(chǎn)生這張表的程序。在 MathFunctions 文件夾下創(chuàng)建一個新的名為 MakeTable.cxx 的文件,內(nèi)容如下:
// A simple program that builds a sqrt table
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
int i;
double result;
// make sure we have enough arguments
if (argc < 2)
{
return 1;
}
// open the output file
FILE *fout = fopen(argv[1],"w");
if (!fout)
{
return 1;
}
// create a source file with a table of square roots
fprintf(fout,"double sqrtTable[] = {\n");
for (i = 0; i < 10; ++i)
{
result = sqrt(static_cast<double>(i));
fprintf(fout,"%g,\n",result);
}
// close the table with a zero
fprintf(fout,"0};\n");
fclose(fout);
return 0;
}
注意到這張表使用 C++ 代碼生成且文件的名字通過輸入?yún)?shù)指定。下一步通過在 MathFunctions 的CMakeLists.txt 中添加合適的代碼來構(gòu)建 MakeTable 可執(zhí)行文件,并將它作為構(gòu)建的一部分運行。只需要一點代碼就能實現(xiàn)這個功能:
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# add the binary tree directory to the search path for
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h )
首先添加的 MakeTable 可執(zhí)行文件和其它可執(zhí)行文件相同。接下來我們添加一個自定義的命令來指定如何通過運行 MakeTable 生成 Table.h 文件。接下來我們必須讓 CMake 知道 mysqrt.cxx 依賴于生成的 Table.h 文件。這一點通過將生成的 Table.h 文件添加到 MathFunctions 庫的源文件列表實現(xiàn)。
我們同樣必須將當(dāng)前二進制文件路徑添加到包含路徑中,以保證 Table.h 文件被找到并被 mysqrt.cxx 包含。該項目在構(gòu)建時會首先構(gòu)建 MakeTable 可執(zhí)行文件。接下來會運行該可執(zhí)行文件并生成 Table.h 文件。最后它將會編譯包含 Table.h 的 mysqrt.cxx 文件并生成 MathFunctions 庫。此時包含了所有我們添加的特性的頂層 CMakeLists.txt 文件應(yīng)該像下面這樣:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
include(CTest)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
# should we use our own math functions
option(USE_MYMATH
"Use tutorial provided math implementation" ON)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")
# add the MathFunctions library?
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial ${EXTRA_LIBS})
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
# does the application run
add_test (TutorialRuns Tutorial 25)
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
PROPERTIES
PASS_REGULAR_EXPRESSION "Usage:.*number"
)
#define a macro to simplify adding tests
macro (do_test arg result)
add_test (TutorialComp${arg} Tutorial ${arg})
set_tests_properties (TutorialComp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endmacro (do_test)
# do a bunch of result based tests
do_test (4 "4 is 2")
do_test (9 "9 is 3")
do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645")
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
do_test (0.0001 "0.0001 is 0.01")
TutorialConfig.h 文件如下:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
最后 MathFunctions 的 CMakeLists.txt 文件如下:
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# add the binary tree directory to the search path
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
7. 構(gòu)造一個安裝器(步驟 6)
接下來假設(shè)我們想將我們的項目發(fā)布給其他人以便供他們使用。我們想提供在不同平臺上的二進制文件和源碼的發(fā)布版本。這一點和我們在之前安裝和測試章節(jié)(步驟3)略有不同,步驟三安裝的二進制文件是我們從源碼構(gòu)建的。這里我們將構(gòu)建一個支持二進制文件安裝的安裝包和可以在 cygwin,debian,RPMs 等中被找到的安裝管理特性。為了實現(xiàn)這一點我們將使用 CPack 來創(chuàng)建在 Packaging with CPack 章節(jié)中介紹過的平臺特定安裝器(platform specific installers)。我們需要在頂層 CMakeLists.txt 文件添加以下幾行內(nèi)容:
# build a CPack driven installer package
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include (CPack)
首先我們添加了 InstallRequiredSystemLibraries。該模塊會包含我們項目在當(dāng)前平臺所需的所有運行時庫(runtime libraries)。接下來我們設(shè)置了一些 CPack 變量來指定我們項目的許可文件和版本信息。版本信息使用我們在之前設(shè)置的內(nèi)容。最后我們包含 CPack 模塊,它會使用這些變量和其它你安裝一個應(yīng)用程序所需的系統(tǒng)屬性。
接下來就是正常編譯你的項目然后使用 CPack 運行它,為了編譯二進制發(fā)布版本你需要運行:
cpack --config CPackConfig.cmake
創(chuàng)建一個源文件發(fā)布版本你應(yīng)該使用下面命令:
cpack --config CPackSourceConfig.cmake
Windows 平臺下CMake 默認(rèn)會使用 NSIS 創(chuàng)建安裝包,因此我們在執(zhí)行上面命令前需要安裝該軟件。當(dāng)然我們也可以使用 WiX 包安裝工具,只需要在 include(CPack) 之前加上 set(CPACK_GENERATOR WIX) 即可。
8. 添加表盤工具(Dashboard)支持(步驟7)
添加將我們測試結(jié)果提交到儀表盤的功能非常簡單。在本教程的之前步驟中我們已經(jīng)給我們的項目定義了一些測試。我們只需要運行這些測試然后提交到儀表盤即可。為了支持儀表盤功能我們需要在頂層 CMakeLists.txt 文件中增加 CTest 模塊。
# enable dashboard scripting
include (CTest)
我們同樣可以創(chuàng)建一個 CTestConfig.cmake 文件來在表盤工具中指定本項目的名字。
set (CTEST_PROJECT_NAME "Tutorial")
CTest 會在運行時讀取該文件。你可以在你的項目上運行 CMake 來創(chuàng)建一個簡單的儀表盤,切換目錄到二進制文件夾下,然后運行 ctest -DExperimental.你儀表盤的運行結(jié)果會上傳到 Kitware 的公共儀表盤上 這里。
如果需要上傳的話還需要設(shè)置 Drop site ,具體細(xì)節(jié)可以參考官方的 ctest(1) 。
三、CMake常用命令詳解
在深入了解 CMake 的世界后,我們來到了一個關(guān)鍵的階段 —— 學(xué)習(xí) CMake 的常用命令。這些命令是我們編寫CMakeLists.txt文件的基礎(chǔ),掌握它們,就如同掌握了一門新語言的語法規(guī)則,能夠讓我們自由地構(gòu)建和管理項目。下面,我們將詳細(xì)介紹一些 CMake 的常用命令,讓你在實際項目中能夠靈活運用。
1. 工程管理命令
(1) include_directories:配置頭文件路徑
在項目中,頭文件是不可或缺的部分,它包含了函數(shù)、類的聲明等重要信息。include_directories命令用于指定頭文件的搜索路徑,確保編譯器在編譯源文件時能夠找到所需的頭文件 。其基本語法如下:
include_directories([AFTER|BEFORE][SYSTEM] dir1 [dir2 ...])
上述示例將include目錄添加到頭文件搜索路徑中,這樣在編譯時,編譯器會在include目錄中查找頭文件。不過,更推薦使用target_include_directories,它針對特定目標(biāo)設(shè)置包含目錄,作用域更明確,可維護性更好 。
(2) add_executable:此命令用于創(chuàng)建一個可執(zhí)行文件目標(biāo)
通過指定可執(zhí)行文件的名稱和源文件列表,CMake 會將這些源文件編譯鏈接成一個可執(zhí)行文件 。在一個簡單的 C 項目中,有main.c和utils.c兩個源文件,使用add_executable命令可以這樣編寫:
add_executable(my_program main.c utils.c)
這里my_program是生成的可執(zhí)行文件的名稱,main.c和utils.c是參與編譯的源文件。如果源文件較多,也可以將源文件列表放在一個變量中,然后在add_executable中使用該變量 。
(3) add_library:用于創(chuàng)建一個庫文件目標(biāo),可以生成靜態(tài)庫(STATIC)或動態(tài)庫(SHARED)
在一個 C++ 庫項目中,有l(wèi)ibrary.cpp源文件,希望生成一個動態(tài)庫,命令如下:
add_library(my_library SHARED library.cpp)
其中my_library是庫的名稱,SHARED表示生成動態(tài)庫,如果要生成靜態(tài)庫,將SHARED改為STATIC即可 。
(4) add_subdirectory:用于包含子目錄的 CMakeLists.txt 文件,從而將子目錄的項目納入到整個項目的構(gòu)建過程中
在一個大型項目中,可能會有多個模塊,每個模塊都有自己獨立的目錄和 CMakeLists.txt 文件,使用add_subdirectory可以方便地管理這些模塊的構(gòu)建 。假設(shè)項目目錄結(jié)構(gòu)如下:
project/
├── CMakeLists.txt
├── module1/
│ └── CMakeLists.txt
└── module2/
└── CMakeLists.txt
在項目根目錄的CMakeLists.txt中,可以這樣包含子目錄:
add_subdirectory(module1)
add_subdirectory(module2)
這樣,module1和module2目錄下的 CMakeLists.txt 文件會被執(zhí)行,其中定義的構(gòu)建規(guī)則和目標(biāo)會被納入到整個項目的構(gòu)建中 。
(5) include:用于引入其他.cmake文件,這些文件通常包含一些自定義的函數(shù)、宏或配置信息,通過include可以在當(dāng)前的 CMakeLists.txt 文件中復(fù)用這些內(nèi)容 。在項目中,有一個自定義的.cmake文件common.cmake,其中定義了一些常用的編譯選項和宏,在主 CMakeLists.txt 文件中可以這樣引入:
include(common.cmake)
引入后,common.cmake中的內(nèi)容就可以在當(dāng)前文件中使用了,這有助于提高代碼的復(fù)用性和項目的可維護性 。
2. 開關(guān)選項命令
(1) option:該命令用于定義一個開關(guān)選項,在項目配置時可以通過命令行或 CMake 圖形界面來設(shè)置這個選項的值,從而控制項目的構(gòu)建行為 。在一個項目中,可能希望有一個選項來控制是否啟用某個功能模塊,使用option命令可以這樣定義:
option(ENABLE_FEATURE "Enable a specific feature" OFF)
if(ENABLE_FEATURE)
# 啟用功能模塊的相關(guān)配置
add_definitions(-DENABLE_FEATURE)
include_directories(feature_include)
add_executable(my_program main.c feature.c)
else()
# 不啟用功能模塊的配置
add_executable(my_program main.c)
endif()
上述示例中,ENABLE_FEATURE是定義的選項名稱,"Enable a specific feature"是選項的描述信息,OFF表示該選項的默認(rèn)值為關(guān)閉。在項目配置時,可以通過cmake -DENABLE_FEATURE=ON ..來啟用該功能模塊 。
(2) add_definition:用于在源碼中定義宏,這些宏可以在代碼中通過#ifdef等預(yù)處理指令進行條件編譯 。在一個 C++ 項目中,希望定義一個DEBUG宏來控制調(diào)試信息的輸出,使用add_definition命令可以這樣編寫:
add_definitions(-DDEBUG)
這樣在編譯時,DEBUG宏會被定義,在代碼中就可以通過#ifdef DEBUG來判斷是否處于調(diào)試模式,并進行相應(yīng)的代碼處理 。
3. 調(diào)試信息命令
(1) message:用于在 CMake 執(zhí)行過程中向終端輸出信息,它有多種輸出模式,不同的模式用于不同的目的 。
(2) STATUS:輸出的信息會被發(fā)送到 CMake 的狀態(tài)消息流,通常用于輸出構(gòu)建過程中的狀態(tài)信息,在命令行上,這些消息會被顯示出來,幫助開發(fā)者了解構(gòu)建的進度和當(dāng)前狀態(tài) 。
message(STATUS "Building project...")
(3) INFO:輸出一般性的信息,類似于STATUS,但沒有明確的區(qū)分,通常用于輸出一些開發(fā)者希望關(guān)注的信息 。
message(INFO "This is an important information.")
(4) WARNING:輸出的信息會被發(fā)送到 CMake 的警告消息流,這些消息會被標(biāo)記為警告,通常用于提示開發(fā)者一些可能存在的問題,但不會導(dǎo)致構(gòu)建失敗 。
if(NOT SOME_LIBRARY_FOUND)
message(WARNING "Some library not found, some features may not be available.")
endif()
(5) FATAL_ERROR:輸出的信息會被發(fā)送到 CMake 的錯誤消息流,并立即停止 CMake 的處理過程,通常用于表示嚴(yán)重的錯誤,如缺少關(guān)鍵的依賴項或配置錯誤 。
if(NOT REQUIRED_TOOL_FOUND)
message(FATAL_ERROR "Required tool not found, cannot continue building.")
endif()
4. 其他重要變量
(1) CMAKE_C_FLAGS、CMAKE_CXX_FLAGS:這兩個變量分別用于控制 C 和 C++ 的編譯選項 。在一個 C++ 項目中,希望設(shè)置編譯選項為-Wall -Werror,以開啟所有警告并將警告視為錯誤,可以這樣設(shè)置:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
這樣在編譯 C++ 源文件時,就會帶上-Wall -Werror編譯選項 。如果項目中同時有 C 和 C++ 代碼,也可以分別設(shè)置CMAKE_C_FLAGS和CMAKE_CXX_FLAGS 。
(2) CMAKE_BUILD_TYPE:該變量用于指定構(gòu)建類型,常見的構(gòu)建類型有Debug、Release、MinSizeRel(最小尺寸發(fā)布,優(yōu)化目標(biāo)是減小文件大小)和RelWithDebInfo(發(fā)布版本且包含調(diào)試信息) 。在項目中,可以通過以下方式設(shè)置構(gòu)建類型:
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
上述示例中,如果沒有通過命令行或其他方式指定構(gòu)建類型,默認(rèn)設(shè)置為Release。在命令行中也可以通過cmake -DCMAKE_BUILD_TYPE=Debug ..來指定構(gòu)建類型為Debug 。
四、CMake高級應(yīng)用
1. 自定義構(gòu)建選項
在實際項目開發(fā)中,根據(jù)不同的需求和場景進行條件編譯是非常常見的操作,而 CMake 提供的option命令就為我們實現(xiàn)這一功能提供了便利。
option命令用于定義一個開關(guān)選項,它的基本語法是option(OPTION_NAME "Description" DEFAULT_VALUE),其中OPTION_NAME是選項的名稱,"Description"是對該選項的描述信息,方便用戶了解其作用,DEFAULT_VALUE則是選項的默認(rèn)值,可以是ON或OFF。
例如,在一個圖像處理項目中,可能希望有一個選項來控制是否啟用高級圖像算法。可以在CMakeLists.txt文件中這樣定義:
option(ENABLE_ADVANCED_ALGORITHM "Enable advanced image algorithm" OFF)
if(ENABLE_ADVANCED_ALGORITHM)
# 啟用高級圖像算法的相關(guān)配置
add_definitions(-DENABLE_ADVANCED_ALGORITHM)
include_directories(advanced_algorithm_include)
add_executable(image_processing main.cpp advanced_algorithm.cpp)
else()
# 不啟用高級圖像算法的配置
add_executable(image_processing main.cpp basic_algorithm.cpp)
endif()
在上述示例中,ENABLE_ADVANCED_ALGORITHM是定義的選項名稱,"Enable advanced image algorithm"是描述信息,OFF表示默認(rèn)不啟用該選項。當(dāng)通過cmake -DENABLE_ADVANCED_ALGORITHM=ON ..命令啟用該選項時,if條件判斷為真,會添加相應(yīng)的編譯定義-DENABLE_ADVANCED_ALGORITHM,并將高級圖像算法的源文件advanced_algorithm.cpp包含到可執(zhí)行文件的構(gòu)建中;如果不啟用該選項,else分支會被執(zhí)行,使用基本圖像算法的源文件basic_algorithm.cpp進行構(gòu)建。
2. 查找和管理依賴庫
在項目開發(fā)中,經(jīng)常會依賴一些外部庫來實現(xiàn)特定的功能,而 CMake 的find_package命令就是查找和管理這些依賴庫的重要工具。
以查找和使用 Boost 庫為例,find_package命令的基本語法是find_package(Boost [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [COMPONENTS [components...]] [OPTIONAL_COMPONENTS [components...]] [CONFIG|NO_MODULE|NO_CONFIG|NO_POLICY_SCOPE] [NAMES name1 [...]])。
假設(shè)我們的項目需要使用 Boost 庫的文件系統(tǒng)和線程組件,并且要求找到的 Boost 庫版本至少為 1.70,可以在CMakeLists.txt文件中這樣編寫:
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem thread)
if(Boost_FOUND)
message(STATUS "Boost include dir: ${Boost_INCLUDE_DIRS}")
message(STATUS "Boost libraries: ${Boost_LIBRARIES}")
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(your_target_name ${Boost_LIBRARIES})
else()
message(FATAL_ERROR "Boost library not found.")
endif()
上述代碼中,find_package(Boost 1.70 REQUIRED COMPONENTS filesystem thread)表示查找 Boost 庫,要求版本至少為 1.70,并且必須找到文件系統(tǒng)和線程組件。如果找到 Boost 庫,Boost_FOUND變量會被設(shè)置為真,然后通過message命令輸出 Boost 庫的包含目錄和庫文件路徑,再使用include_directories將 Boost 庫的包含目錄添加到項目中,使用target_link_libraries將 Boost 庫鏈接到項目的目標(biāo)(your_target_name)上;如果沒有找到 Boost 庫,Boost_FOUND為假,通過message(FATAL_ERROR "Boost library not found.")輸出錯誤信息并終止構(gòu)建過程。
3. 生成安裝目標(biāo)
在項目開發(fā)完成后,通常需要將生成的可執(zhí)行文件、庫文件、配置文件等安裝到指定的目錄中,以便用戶能夠方便地使用和部署。CMake 的install命令就提供了這樣的功能,它可以將各種文件和目標(biāo)安裝到指定的位置。
(1)安裝可執(zhí)行文件:
add_executable(my_app main.cpp)
install(TARGETS my_app RUNTIME DESTINATION bin)
上述代碼中,add_executable創(chuàng)建了一個名為my_app的可執(zhí)行文件,install命令將my_app的運行時文件(即生成的可執(zhí)行文件)安裝到bin目錄下。這里的bin目錄可以是絕對路徑,也可以是相對于CMAKE_INSTALL_PREFIX的相對路徑,CMAKE_INSTALL_PREFIX是 CMake 的一個預(yù)定義變量,用于指定安裝路徑的前綴,默認(rèn)情況下在 Unix 系統(tǒng)中是/usr/local,在 Windows 系統(tǒng)中是C:/Program Files (x86)/項目名稱 。如果希望安裝到自定義的絕對路徑,如/home/user/my_project/bin,可以將DESTINATION設(shè)置為/home/user/my_project/bin 。
(2)安裝庫文件:
add_library(my_lib SHARED lib.cpp)
install(TARGETS my_lib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static)
這里add_library創(chuàng)建了一個共享庫my_lib,install命令將共享庫文件安裝到lib目錄下,將歸檔文件(通常是靜態(tài)庫文件,如果有的話)安裝到lib/static目錄下。同樣,lib和lib/static可以是相對路徑或絕對路徑 。
(3)安裝普通文件:
install(FILES config.ini DESTINATION etc)
該示例將config.ini文件安裝到etc目錄下,config.ini是相對于CMakeLists.txt文件所在目錄的路徑 。
(4)安裝目錄:
install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN "*.h")
上述代碼會將include目錄下所有擴展名為.h的頭文件安裝到include目錄下,這里的兩個include目錄可以根據(jù)實際需求進行修改 。如果希望安裝整個include目錄及其所有內(nèi)容,包括子目錄,可以將FILES_MATCHING相關(guān)部分去掉 。
五、常見問題及解決方案
在使用 CMake 的過程中,開發(fā)者可能會遇到各種各樣的問題,下面將列舉一些常見問題,并提供相應(yīng)的解決方案。
1. 找不到依賴庫
在編譯項目時,CMake 可能會提示找不到某個依賴庫,這是一個比較常見的問題。比如在編譯一個使用 OpenCV 庫的項目時,可能會出現(xiàn) “Could not find a package configuration file provided by “OpenCV” with any of the following names: OpenCVConfig.cmake, opencv-config.cmake” 這樣的錯誤提示 。
原因分析:造成這個問題的原因可能有多種。首先,可能是依賴庫沒有正確安裝,例如沒有安裝 OpenCV 庫或者安裝路徑不正確;其次,CMake 可能無法自動檢測到依賴庫的位置,這可能是因為庫的安裝路徑?jīng)]有被添加到系統(tǒng)的環(huán)境變量中,或者 CMake 的搜索路徑設(shè)置不正確 。
解決方案:針對這些問題,可以采取以下解決措施。首先,確保已經(jīng)正確安裝了相應(yīng)的依賴庫。如果已經(jīng)安裝了依賴庫,但 CMake 仍然找不到它,可以通過設(shè)置 CMake 變量來指定庫的路徑 。例如,使用set()命令設(shè)置CMAKE_PREFIX_PATH變量,將庫的安裝路徑添加到其中:
set(CMAKE_PREFIX_PATH "/path/to/library" ${CMAKE_PREFIX_PATH})
另外,還可以使用find_path()或find_library()函數(shù),并明確指定依賴庫的搜索路徑 。例如:
find_package(Boost REQUIRED PATHS /path/to/boost)
如果依賴是在系統(tǒng)級安裝的,確認(rèn)相關(guān)的環(huán)境變量(如LD_LIBRARY_PATH或PYTHONPATH)已經(jīng)設(shè)置正確 。對于預(yù)打包的庫,檢查操作系統(tǒng)是否有對應(yīng)的軟件包,并使用系統(tǒng)的包管理工具安裝 。此外,查閱依賴庫的官方文檔,了解特定的 CMake 配置步驟也是很有必要的 。
2. 編譯選項錯誤
在 CMakeLists.txt 文件中設(shè)置編譯選項時,可能會因為設(shè)置錯誤而導(dǎo)致編譯失敗。比如在設(shè)置 C++ 編譯選項時,將CMAKE_CXX_FLAGS變量設(shè)置錯誤,可能會出現(xiàn) “unrecognized command line option” 這樣的錯誤提示 。
原因分析:這種錯誤通常是由于對編譯選項的不熟悉或者拼寫錯誤導(dǎo)致的。不同的編譯器支持的編譯選項可能會有所不同,如果設(shè)置了不支持的編譯選項,就會出現(xiàn)此類錯誤 。
解決方案:解決這個問題,首先要仔細(xì)檢查 CMakeLists.txt 文件中編譯選項的設(shè)置,確保拼寫正確,并且是目標(biāo)編譯器支持的選項 。例如,如果希望設(shè)置 C++ 編譯選項為-Wall -Werror,以開啟所有警告并將警告視為錯誤,可以這樣設(shè)置:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
如果不確定某個編譯選項是否被支持,可以查閱目標(biāo)編譯器的官方文檔 。另外,在設(shè)置編譯選項時,可以使用條件判斷來根據(jù)不同的編譯器或構(gòu)建類型設(shè)置不同的選項 。例如:
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX")
endif()
上述代碼中,根據(jù)編譯器類型分別設(shè)置了不同的編譯選項,對于 GCC 編譯器,設(shè)置-Wall -Werror選項,對于 MSVC 編譯器,設(shè)置/W4 /WX選項 。
3. 頭文件路徑設(shè)置錯誤
在項目中,頭文件路徑設(shè)置不正確也會導(dǎo)致編譯錯誤,例如出現(xiàn) “fatal error: xxx.h: No such file or directory” 這樣的錯誤提示 。
原因分析:這通常是因為在 CMakeLists.txt 文件中沒有正確設(shè)置頭文件的搜索路徑,或者設(shè)置的路徑與實際頭文件的位置不一致 。
解決方案:可以使用include_directories()命令將頭文件所在的路徑添加到編譯過程中。例如:
include_directories(include)
上述示例將include目錄添加到頭文件搜索路徑中 。不過,更推薦使用target_include_directories(),它針對特定目標(biāo)設(shè)置包含目錄,作用域更明確,可維護性更好 。例如:
add_executable(my_program main.c)
target_include_directories(my_program PRIVATE include)
這里PRIVATE表示該包含目錄僅對my_program目標(biāo)可見,如果頭文件路徑是公共的也可以使用PUBLIC關(guān)鍵字 。
4. 構(gòu)建類型設(shè)置錯誤
在項目中,如果構(gòu)建類型設(shè)置錯誤,可能會導(dǎo)致生成的可執(zhí)行文件或庫文件不符合預(yù)期,例如在需要生成 Release 版本的可執(zhí)行文件時,卻生成了 Debug 版本 。
原因分析:這種問題通常是由于沒有正確設(shè)置CMAKE_BUILD_TYPE變量,或者在命令行中沒有正確指定構(gòu)建類型導(dǎo)致的 。
解決方案:可以在 CMakeLists.txt 文件中設(shè)置CMAKE_BUILD_TYPE變量的默認(rèn)值 。例如:
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
上述示例中,如果沒有通過命令行或其他方式指定構(gòu)建類型,默認(rèn)設(shè)置為Release 。在命令行中也可以通過cmake -DCMAKE_BUILD_TYPE=Debug ..來指定構(gòu)建類型為Debug 。
六、CMake在Clion中的配置
1. Ubuntu 下 Clion 的安裝
clion 及相關(guān)工具安裝:
# clion 所用到的工具鏈:gcc(C),g++(C++),make(連接),cmake(跨平臺建構(gòu)系統(tǒng)), gdb(debug)
sudo apt install gcc
sudo apt install g++
sudo apt install make
sudo apt install cmake
sudo apt install gdb
# Install using the Toolbox App or Standalone installation
sudo tar -xzf jetbrains-toolbox* -C /opt
# Standalone installation
sudo tar xvzf CLion-*.tar.gz -C /opt/
sh /opt/clion-*/bin/clion.sh
# create a desktop entry, do one of the following:
- On the Welcome screen, click Configure | Create Desktop Entry
- From the main menu, click Tools | Create Desktop Entry
如果沒有桌面快捷方式,嘗試用如下方法解決:
sudo vim /usr/share/applications/clion.desktop
# 插入如下內(nèi)容,保存退出即可,在 search 里面就可以找到 clion 了
[Desktop Entry]
Encoding=UTF-8
Name=CLion
Comment=clion-2020.1.2
Exec=/opt/clion-2020.1.2/bin/clion.sh
Icnotallow=/opt/clion-2020.1.2/bin/clion.svg
Categories=Application;Development;Java;IDE
Versinotallow=2020.1.2
Type=Application
#Terminal=1
2. 如何在 clion 運行多個 cpp 文件 ?
直接修改 CMakeLists.txt 即可。
新建 CPP 文件時注意:把默認(rèn)勾選的 Add to targerts 去掉(如下圖);在項目處右擊,選擇 Reload CMake Project,在重新加載完之后可以看到運行框列表有了對應(yīng)的運行選項
cmake_minimum_required(VERSION 3.16)
project(cpp14)
set(CMAKE_CXX_STANDARD 14)
# 遞歸遍歷項目當(dāng)前目錄下所有的 .cpp 文件
file(GLOB_RECURSE files *.cpp)
foreach (file ${files})
string(REGEX REPLACE ".+/(.+)\\..*" "\\1" exe ${file}) # 正則匹配,取出文件名前綴
add_executable(${exe} ${file}) # exe 文件名, file 為文件絕對路徑
message("src file name is: " ${exe})
message(STATUS "src file path is: " ${file})
endforeach (file)