從 0 到 1 學 CMake:開啟高效跨平臺構建之旅
在當今軟件開發生態系統中,構建工具宛如幕后的 “大管家”,掌控著從源代碼到可執行程序或庫的復雜流程。它們不僅要應對不同編程語言的特性,還需適配各類操作系統與編譯器組合,其重要性不言而喻。今天,我們要聚焦一款在跨平臺構建領域熠熠生輝的工具 ——CMake。
你是否曾為在 Windows、Linux 和 macOS 等不同平臺上,將代碼順利轉化為可運行程序而絞盡腦汁?是否在面對復雜項目中眾多源文件、庫依賴時,感到構建過程猶如一團亂麻,無從下手?CMake 的誕生,正是為了化解這些棘手難題。它就像一位經驗豐富、足智多謀的項目經理,能有條不紊地管理項目構建的全過程,讓你的代碼跨越平臺的界限,在各種環境中都能精準無誤地編譯和運行。
無論是初涉編程的新手,還是久經沙場的開發老兵,尤其是那些投身 C++、C 等編譯型語言項目的開發者,掌握 CMake 都將成為你提升開發效率、邁向更高層次的關鍵一步。接下來,就讓我們一同深入 CMake 的奇妙世界,開啟這場從 0 到 1 的探索之旅 。
一、CMake簡介
1. 什么是CMake
CMake是個一個開源的跨平臺自動化建構系統,用來管理軟件建置的程序,并不相依于某特定編譯器;并可支持多層目錄、多個應用程序與多個庫。它用配置文件控制建構過程(build process)的方式和Unix的make相似,只是CMake的配置文件取名為CMakeLists.txt。
CMake并不直接建構出最終的軟件,而是產生標準的建構檔(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建構方式使用。這使得熟悉某個集成開發環境(IDE)的開發者可以用標準的方式建構他的軟件,這種可以使用各平臺的原生建構系統的能力是CMake和SCons等其他類似系統的區別之處。它首先允許開發者編寫一種平臺無關的CMakeList.txt 文件來定制整個編譯流程,然后再根據目標用戶的平臺進一步生成所需的本地化 Makefile 和工程文件,如 Unix的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once, run everywhere”。
顯然,CMake 是一個比上述幾種 make 更高級的編譯配置工具。“CMake”這個名字是"Cross platform MAke"的縮寫。雖然名字中含有"make",但是CMake和Unix上常見的“make”系統是分開的,而且更為高端。它可與原生建置環境結合使用,例如:make、蘋果的Xcode與微軟的Visual Studio。
2. 為什么選擇 CMake?
在軟件開發的構建領域中,CMake 憑借眾多顯著優點脫穎而出,成為眾多開發者的首選。
(1) 跨平臺性:在如今多樣化的開發環境下,一款軟件往往需要在不同的操作系統上運行,如 Windows、Linux、macOS 等,甚至還可能涉及到移動平臺如 Android 。CMake 強大的跨平臺特性就像是一位萬能的翻譯,它允許開發者編寫一次構建腳本,然后就能在各種主流操作系統上生成對應的構建文件,確保軟件在不同平臺上都能順利編譯和運行 。以一個簡單的 C++ 項目為例,無論是在 Windows 系統下使用 Visual Studio 編譯器,還是在 Linux 系統下使用 GCC 編譯器,只需一份相同的 CMakeLists.txt 文件,CMake 就能根據不同的平臺環境生成合適的構建腳本,大大提高了開發效率和代碼的可移植性。
(2) 模塊化:當項目規模逐漸增大,代碼量不斷增多時,合理的模塊劃分對于項目的管理和維護至關重要。CMake 支持模塊化的項目配置,開發者可以將項目按照功能、模塊等維度進行拆分,每個模塊都有自己獨立的 CMakeLists.txt 文件 。這樣一來,各個模塊的構建規則和依賴關系都能清晰地定義和管理,不僅方便了開發過程中的協作,也使得項目的結構更加清晰,易于維護和擴展。例如,在一個大型游戲開發項目中,可能會將游戲的渲染模塊、邏輯模塊、網絡模塊等分別獨立管理,每個模塊都可以獨立編譯、測試和更新,而不會影響到其他模塊的正常運行。
(3) 可擴展性:隨著項目需求的不斷變化和發展,構建系統也需要具備一定的靈活性和可擴展性。CMake 允許開發者編寫自定義的模塊和宏,以滿足項目特定的構建需求 。比如,在一些對編譯優化有特殊要求的項目中,開發者可以編寫自定義的 CMake 模塊,添加特定的編譯選項和鏈接庫;在一些需要自動化部署的項目中,開發者可以通過編寫宏來實現自動化的打包和發布流程。這種高度的可擴展性使得 CMake 能夠適應各種復雜的項目場景,無論是小型的個人項目,還是大型的企業級項目,都能發揮出它的優勢。
(4) 自動依賴管理:在項目開發過程中,依賴關系的管理往往是一個繁瑣且容易出錯的環節。CMake 能夠自動檢測項目所依賴的庫文件和頭文件,并在構建過程中自動處理這些依賴關系 。它通過 find_package 等命令,可以在系統中查找指定的依賴庫,并設置相應的變量,確保項目在編譯和鏈接時能夠正確地找到這些依賴項。例如,當項目依賴于 OpenCV 庫時,只需在 CMakeLists.txt 文件中使用 find_package (OpenCV REQUIRED) 命令,CMake 就會自動查找系統中安裝的 OpenCV 庫,并將其包含路徑和庫文件路徑設置好,開發者無需手動去配置這些復雜的路徑信息,大大減少了因依賴管理不當而導致的錯誤。
(5) 簡化構建過程:相較于傳統的構建方式,如手動編寫 Makefile 文件,CMake 使用一種簡潔、直觀的語法來描述項目的構建規則 。開發者只需要在 CMakeLists.txt 文件中使用簡單的命令,如 project 定義項目名稱、add_executable 生成可執行文件、add_library 生成庫文件等,就能清晰地定義項目的構建過程。而且,CMake 還提供了豐富的命令和選項,方便開發者進行各種配置,如設置編譯選項、添加鏈接庫、定義自定義目標等。這使得構建過程更加自動化和規范化,降低了開發者的學習成本和工作量。
與傳統的構建方式相比,CMake 的優勢更加明顯。在傳統的 Makefile 構建方式中,對于不同的平臺和編譯器,可能需要編寫不同的 Makefile 文件,而且Makefile的語法較為復雜,對于復雜的項目依賴關系處理起來也比較困難。而 CMake 通過統一的構建腳本和強大的功能,解決了這些問題,讓構建過程變得更加簡單、高效和可靠 。
二、CMake使用教程
1. 安裝 CMake
CMake 的安裝過程相對簡單,下面將分別介紹在 Windows、Linux 和 macOS 系統下的安裝步驟。
(1) Windows 系統:
- 首先,前往CMake 官方網站下載 Windows 版本的安裝包,通常是.msi 文件。
- 下載完成后,雙擊.msi 文件,按照安裝向導的指示進行安裝。在安裝過程中,務必勾選 “Add CMake to the system PATH for all users” 選項,這樣可以將 CMake 添加到系統的 PATH 環境變量中,方便后續在命令行中直接使用 cmake 命令。
- 安裝完成后,打開命令提示符(CMD)或 PowerShell,輸入cmake --version,如果能正確顯示 CMake 的版本信息,說明安裝成功。例如:cmake version 3.26.4 。
(2) Linux 系統:不同的 Linux 發行版安裝方式略有不同。
(3) Debian 和 Ubuntu 系統:打開終端,輸入以下命令進行安裝:
sudo apt-get update
sudo apt-get install cmake
(4) Fedora 系統:在終端中執行:
sudo dnf install cmake
(5) Arch Linux 系統:使用以下命令安裝:
sudo pacman -S cmake
安裝完成后,在終端輸入cmake --version驗證安裝是否成功。
(6) macOS 系統:
通過 Homebrew 安裝:如果你的macOS 系統安裝了 Homebrew 包管理器,打開終端,執行以下命令即可安裝 CMake:
brew install cmake
通過官方安裝包:訪問CMake官方網站,選擇 macOS 版本的.dmg 文件進行下載。下載完成后,運行.dmg 文件,將 CMake 圖標拖動到應用程序文件夾。
安裝成功后,命令都在/Applications/CMake.app/Contents/bin目錄下,需要將環境變量添加到.bash_profile文件中。使用 vim 進行編輯:
vim ~/.bash_profile
將以下內容添加到文件末尾:
export PATH="/Applications/CMake.app/Contents/bin:$PATH"
添加完成后,執行source ~/.bash_profile或者重新啟動終端。最后,在終端輸入cmake --version確認 CMake 已正確安裝。在使用 CMake 構建項目之前,需要了解幾個重要的概念:
- CMakeLists.txt 文件:這是 CMake 構建系統的核心配置文件,每個項目或子目錄都可以有一個 CMakeLists.txt 文件。它包含了一系列的 CMake 命令和指令,用于描述項目的構建過程,例如指定項目名稱、源文件位置、鏈接庫、編譯選項等。通過編寫 CMakeLists.txt 文件,開發者可以以一種平臺無關的方式定義項目的構建規則,CMake 會根據這些規則生成適合不同平臺的構建文件 。
- 生成器:CMake 本身并不直接構建項目,而是通過生成器(Generator)來生成構建文件。生成器是一種模板驅動的機制,它根據 CMakeLists.txt 文件和用戶的配置,生成特定平臺和構建工具所需的構建文件,如 Unix 系統下的 Makefile、Windows 系統下 Visual Studio 的項目文件(.sln 和.vcxproj)、Xcode 項目文件(.xcodeproj)等 。不同的生成器適用于不同的開發環境和構建需求,開發者可以根據項目的實際情況選擇合適的生成器。
- 構建目錄:構建目錄是存放生成的構建文件以及編譯過程中產生的中間文件和最終目標文件的地方。為了保持項目源代碼的整潔,通常建議將構建目錄與源代碼目錄分開。在構建項目時,首先進入構建目錄,然后執行 CMake 命令生成構建文件,最后使用構建工具(如 Make、Ninja 等)在該目錄下進行項目的編譯和鏈接 。例如,在一個名為my_project的項目中,可以在項目根目錄下創建一個build目錄作為構建目錄,所有的構建相關文件都將在這個目錄中生成和處理。
2. 基礎的構建步驟(步驟1)
一個最常用的基礎項目是從源碼中構建一個可執行文件。對于一個簡單的項目兩行 CMakeLists.txt 文件就能搞定:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
上面的例子中使用了小寫的命令,事實上,CMakeLists.txt 文件并不區分命令的大小寫。tutorial.cxx源碼是用來計算一個數的算數平方根,下面是其一個簡單的版本:
// 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");,是為了程序執行完畢后不會立刻關閉窗口。后面的代碼的示例中并不會再添加此行,如果用戶需要暫停的話,可以在自己的代碼中加入該行。
(1) 添加版本號和配置頭文件
第一個要添加的特性就是給我們的可執行文件和項目提供一個版本號。你可以在你的源碼之外做到這一點,在 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) 中,所以我們必須將該文件夾添加到頭文件搜索路徑中。接下來我們在源碼中創建一個包含以下內容的 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@
當 CMake 配置這個頭文件的時候,@Tutorial_VERSION_MAJOR@ 和 @ Tutorial_VERSION_MINOR@ 就會用CMakeLists.txt 文件中對應的值替換。接下來我們修改 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) 構建項目并執行文件
官方并沒有給出如何構建項目,這里以 VS 為例介紹如何構建以上項目并編譯執行。在該目錄下面建立 build 文件夾,并新建 run.cmd 文件,編寫內容如下:
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 參數用來指定編譯器,如果不寫這里會找到一個默認的編譯器。我這里默認的編譯器就是 VS2017,但是默認構建的程序為 32 位程序,我這里顯示的指定使用 VS2017 構建 64 位程序。
第5行代碼是使用命令行的形式編譯 VS 的 .sln 文件。關于命令行構建 VS 項目這里不做過多介紹,有興趣可以參考微軟官方給出的 Devenv command line switches。當然我們也可以使用 VS 打開 .sln 文件,然后手動點擊 生成 。第7行代碼為運行程序。
3. 添加一個庫文件(步驟2)
現在我們將會給我們的項目添加一個庫文件。這個庫文件包含了我們自己實現的開方運算。可執行文件使用這個庫替代編譯器提供的標準開方運算。本教程中我們將其放到 MathFunctions 文件夾下,該文件夾下還有一個包含下面一行代碼的 CMakeLists.txt 文件。
add_library(MathFunctions mysqrt.cxx)
mysqrt.cxx 文件只有一個名為 mysqrt 的函數,其提供了和標準庫 sqrt 相同的功能。內容如下(官網官方例程中可以找到):
#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;
}
對應的頭文件為 MathFunction.h,其內容如下:
double mysqrt(double x);
為了構建并使用新的庫文件,我們需要在頂層 CMakeList.txt 文件添加 add_subdirectory 語句。我們需要添加額外的頭文件包含路徑,以便將包含函數原型的 MathFunctions/MathFunctions.h 頭文件包含進來。最后我們還需要給可執行文件添加庫。
最終頂層 CMakeList.txt 文件的最后幾行如下所示:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
現在我們考慮將 MathFunctions 庫作為一個可選項。雖然在這里并沒有什么必要,但是如果庫文件很大或者庫文件依賴第三方庫你可能就希望這么做了。首先先在頂層 CMakeLists.txt 文件添加一個選項:
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
這個選項會在 CMake GUI 中顯示并會將默認值設置為 ON,用戶可以根據需求修改該值。這個設置會本保存下來,所以用戶無需在每次運行 CMake 時都去設置。接下來就是將構建和連接 MathFunctions 設置為可選項。修改頂層的 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 。注意收集可執行文件的可選連接庫所使用的變量(這里為 EXTRA_LIBS)的使用方法。這種方法在保持有許多可選組件的大型項目整潔時經常使用。對應的我們修改源碼如下:
// 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
構建、編譯和運行使用的代碼和上一節相同。
4. 安裝和測試(步驟3)
下一步我們將給我們的項目添加安裝規則和測試。安裝規則簡單明了。對于 MathFunctions 庫的安裝,我們通過在 MathFunction 的 CMakeLists.txt 文件中添加以下兩行來設置其庫和頭文件的安裝。
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
對于本文這個應用通過在頂層 CMakeLists.txt 添加以下內容來安裝可執行文件和配置的頭文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
以上就是安裝的全部步驟。現在你應該可以編譯本教程了。輸入 make install(或在 IDE 中編譯 install 項目),對應的頭文件、庫文件和可執行文件就會被安裝。CMake 的 CMAKE_INSTALL_PREFIX 參數可以指定安裝文件的根目錄(之前還可以加上 -D 參數,具體意義可以參考 what does the parameter "-D" mean)。 添加測試過程同樣簡單明了。
在頂層 CMakeLists.txt 文件的最后我們可以添加一個基礎測試數據來驗證該應用程序是否正常運行。
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" 命令行工具來執行測試。第一個測試簡單的驗證了程序是否工作,是否有嚴重錯誤并且返回0.這是 Ctest 測試的基礎。接下來的一些測試都使用了 PASS_REGULAR_EXPRESSION 測試屬性(正則表達式)來驗證輸出中是否包含了特定的字符串。這里驗證開方是否正確并且在計算錯誤時輸出輸出對應信息。如果你希望添加很多的測試來測試不同的輸入值,你可以考慮定義一個像下面這樣的宏:
#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")
每一次調用 do_test 就會根據指定的信息生成一個新的測試。
該步驟對應的 build 文件夾下的構建和運行腳本 run.cmd 內容如下:
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
安裝位置根據自己的需要進行調整。
5. 添加系統自檢(步驟 4)
接下來我們考慮給我們的項目添加一些取決于目標平臺是否有一些特性的代碼。這里我們將添加一些取決于目標平臺是否有 log 和 exp 函數的代碼。當然對于大多數平臺都會有這些函數,但這里我們認為這并不常見。如果平臺有 log 函數我們將在 mysqrt 函數中使用它計算平方根。我們首先在頂層 CMakeLists.txt 文件中使用 CheckFunctionExists 宏測試這些函數是否可用,代碼如下:
# 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 命令會立刻使用當前 CMake 的設置配置文件。最后根據 log 和 exp 是否在我們的平臺上可用我們給 mysqrt 函數提供一個可選的實現,代碼如下:
// 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)
在這一章節我們將會展示如何在構建一個應用的過程中添加一個生成的源文件。在本例中我們將創建一個預先計算的平方根表作為構建過程的一部分,然后將其編譯到我們的應用中。為了做到這一點我們首先需要一個能產生這張表的程序。在 MathFunctions 文件夾下創建一個新的名為 MakeTable.cxx 的文件,內容如下:
// 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++ 代碼生成且文件的名字通過輸入參數指定。下一步通過在 MathFunctions 的CMakeLists.txt 中添加合適的代碼來構建 MakeTable 可執行文件,并將它作為構建的一部分運行。只需要一點代碼就能實現這個功能:
# 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 可執行文件和其它可執行文件相同。接下來我們添加一個自定義的命令來指定如何通過運行 MakeTable 生成 Table.h 文件。接下來我們必須讓 CMake 知道 mysqrt.cxx 依賴于生成的 Table.h 文件。這一點通過將生成的 Table.h 文件添加到 MathFunctions 庫的源文件列表實現。
我們同樣必須將當前二進制文件路徑添加到包含路徑中,以保證 Table.h 文件被找到并被 mysqrt.cxx 包含。該項目在構建時會首先構建 MakeTable 可執行文件。接下來會運行該可執行文件并生成 Table.h 文件。最后它將會編譯包含 Table.h 的 mysqrt.cxx 文件并生成 MathFunctions 庫。此時包含了所有我們添加的特性的頂層 CMakeLists.txt 文件應該像下面這樣:
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. 構造一個安裝器(步驟 6)
接下來假設我們想將我們的項目發布給其他人以便供他們使用。我們想提供在不同平臺上的二進制文件和源碼的發布版本。這一點和我們在之前安裝和測試章節(步驟3)略有不同,步驟三安裝的二進制文件是我們從源碼構建的。這里我們將構建一個支持二進制文件安裝的安裝包和可以在 cygwin,debian,RPMs 等中被找到的安裝管理特性。為了實現這一點我們將使用 CPack 來創建在 Packaging with CPack 章節中介紹過的平臺特定安裝器(platform specific installers)。我們需要在頂層 CMakeLists.txt 文件添加以下幾行內容:
# 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。該模塊會包含我們項目在當前平臺所需的所有運行時庫(runtime libraries)。接下來我們設置了一些 CPack 變量來指定我們項目的許可文件和版本信息。版本信息使用我們在之前設置的內容。最后我們包含 CPack 模塊,它會使用這些變量和其它你安裝一個應用程序所需的系統屬性。
接下來就是正常編譯你的項目然后使用 CPack 運行它,為了編譯二進制發布版本你需要運行:
cpack --config CPackConfig.cmake
創建一個源文件發布版本你應該使用下面命令:
cpack --config CPackSourceConfig.cmake
Windows 平臺下CMake 默認會使用 NSIS 創建安裝包,因此我們在執行上面命令前需要安裝該軟件。當然我們也可以使用 WiX 包安裝工具,只需要在 include(CPack) 之前加上 set(CPACK_GENERATOR WIX) 即可。
8. 添加表盤工具(Dashboard)支持(步驟7)
添加將我們測試結果提交到儀表盤的功能非常簡單。在本教程的之前步驟中我們已經給我們的項目定義了一些測試。我們只需要運行這些測試然后提交到儀表盤即可。為了支持儀表盤功能我們需要在頂層 CMakeLists.txt 文件中增加 CTest 模塊。
# enable dashboard scripting
include (CTest)
我們同樣可以創建一個 CTestConfig.cmake 文件來在表盤工具中指定本項目的名字。
set (CTEST_PROJECT_NAME "Tutorial")
CTest 會在運行時讀取該文件。你可以在你的項目上運行 CMake 來創建一個簡單的儀表盤,切換目錄到二進制文件夾下,然后運行 ctest -DExperimental.你儀表盤的運行結果會上傳到 Kitware 的公共儀表盤上 這里。
如果需要上傳的話還需要設置 Drop site ,具體細節可以參考官方的 ctest(1) 。
三、CMake常用命令詳解
在深入了解 CMake 的世界后,我們來到了一個關鍵的階段 —— 學習 CMake 的常用命令。這些命令是我們編寫CMakeLists.txt文件的基礎,掌握它們,就如同掌握了一門新語言的語法規則,能夠讓我們自由地構建和管理項目。下面,我們將詳細介紹一些 CMake 的常用命令,讓你在實際項目中能夠靈活運用。
1. 工程管理命令
(1) include_directories:配置頭文件路徑
在項目中,頭文件是不可或缺的部分,它包含了函數、類的聲明等重要信息。include_directories命令用于指定頭文件的搜索路徑,確保編譯器在編譯源文件時能夠找到所需的頭文件 。其基本語法如下:
include_directories([AFTER|BEFORE][SYSTEM] dir1 [dir2 ...])
上述示例將include目錄添加到頭文件搜索路徑中,這樣在編譯時,編譯器會在include目錄中查找頭文件。不過,更推薦使用target_include_directories,它針對特定目標設置包含目錄,作用域更明確,可維護性更好 。
(2) add_executable:此命令用于創建一個可執行文件目標
通過指定可執行文件的名稱和源文件列表,CMake 會將這些源文件編譯鏈接成一個可執行文件 。在一個簡單的 C 項目中,有main.c和utils.c兩個源文件,使用add_executable命令可以這樣編寫:
add_executable(my_program main.c utils.c)
這里my_program是生成的可執行文件的名稱,main.c和utils.c是參與編譯的源文件。如果源文件較多,也可以將源文件列表放在一個變量中,然后在add_executable中使用該變量 。
(3) add_library:用于創建一個庫文件目標,可以生成靜態庫(STATIC)或動態庫(SHARED)
在一個 C++ 庫項目中,有library.cpp源文件,希望生成一個動態庫,命令如下:
add_library(my_library SHARED library.cpp)
其中my_library是庫的名稱,SHARED表示生成動態庫,如果要生成靜態庫,將SHARED改為STATIC即可 。
(4) add_subdirectory:用于包含子目錄的 CMakeLists.txt 文件,從而將子目錄的項目納入到整個項目的構建過程中
在一個大型項目中,可能會有多個模塊,每個模塊都有自己獨立的目錄和 CMakeLists.txt 文件,使用add_subdirectory可以方便地管理這些模塊的構建 。假設項目目錄結構如下:
project/
├── CMakeLists.txt
├── module1/
│ └── CMakeLists.txt
└── module2/
└── CMakeLists.txt
在項目根目錄的CMakeLists.txt中,可以這樣包含子目錄:
add_subdirectory(module1)
add_subdirectory(module2)
這樣,module1和module2目錄下的 CMakeLists.txt 文件會被執行,其中定義的構建規則和目標會被納入到整個項目的構建中 。
(5) include:用于引入其他.cmake文件,這些文件通常包含一些自定義的函數、宏或配置信息,通過include可以在當前的 CMakeLists.txt 文件中復用這些內容 。在項目中,有一個自定義的.cmake文件common.cmake,其中定義了一些常用的編譯選項和宏,在主 CMakeLists.txt 文件中可以這樣引入:
include(common.cmake)
引入后,common.cmake中的內容就可以在當前文件中使用了,這有助于提高代碼的復用性和項目的可維護性 。
2. 開關選項命令
(1) option:該命令用于定義一個開關選項,在項目配置時可以通過命令行或 CMake 圖形界面來設置這個選項的值,從而控制項目的構建行為 。在一個項目中,可能希望有一個選項來控制是否啟用某個功能模塊,使用option命令可以這樣定義:
option(ENABLE_FEATURE "Enable a specific feature" OFF)
if(ENABLE_FEATURE)
# 啟用功能模塊的相關配置
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表示該選項的默認值為關閉。在項目配置時,可以通過cmake -DENABLE_FEATURE=ON ..來啟用該功能模塊 。
(2) add_definition:用于在源碼中定義宏,這些宏可以在代碼中通過#ifdef等預處理指令進行條件編譯 。在一個 C++ 項目中,希望定義一個DEBUG宏來控制調試信息的輸出,使用add_definition命令可以這樣編寫:
add_definitions(-DDEBUG)
這樣在編譯時,DEBUG宏會被定義,在代碼中就可以通過#ifdef DEBUG來判斷是否處于調試模式,并進行相應的代碼處理 。
3. 調試信息命令
(1) message:用于在 CMake 執行過程中向終端輸出信息,它有多種輸出模式,不同的模式用于不同的目的 。
(2) STATUS:輸出的信息會被發送到 CMake 的狀態消息流,通常用于輸出構建過程中的狀態信息,在命令行上,這些消息會被顯示出來,幫助開發者了解構建的進度和當前狀態 。
message(STATUS "Building project...")
(3) INFO:輸出一般性的信息,類似于STATUS,但沒有明確的區分,通常用于輸出一些開發者希望關注的信息 。
message(INFO "This is an important information.")
(4) WARNING:輸出的信息會被發送到 CMake 的警告消息流,這些消息會被標記為警告,通常用于提示開發者一些可能存在的問題,但不會導致構建失敗 。
if(NOT SOME_LIBRARY_FOUND)
message(WARNING "Some library not found, some features may not be available.")
endif()
(5) FATAL_ERROR:輸出的信息會被發送到 CMake 的錯誤消息流,并立即停止 CMake 的處理過程,通常用于表示嚴重的錯誤,如缺少關鍵的依賴項或配置錯誤 。
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++ 項目中,希望設置編譯選項為-Wall -Werror,以開啟所有警告并將警告視為錯誤,可以這樣設置:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
這樣在編譯 C++ 源文件時,就會帶上-Wall -Werror編譯選項 。如果項目中同時有 C 和 C++ 代碼,也可以分別設置CMAKE_C_FLAGS和CMAKE_CXX_FLAGS 。
(2) CMAKE_BUILD_TYPE:該變量用于指定構建類型,常見的構建類型有Debug、Release、MinSizeRel(最小尺寸發布,優化目標是減小文件大小)和RelWithDebInfo(發布版本且包含調試信息) 。在項目中,可以通過以下方式設置構建類型:
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
上述示例中,如果沒有通過命令行或其他方式指定構建類型,默認設置為Release。在命令行中也可以通過cmake -DCMAKE_BUILD_TYPE=Debug ..來指定構建類型為Debug 。
四、CMake高級應用
1. 自定義構建選項
在實際項目開發中,根據不同的需求和場景進行條件編譯是非常常見的操作,而 CMake 提供的option命令就為我們實現這一功能提供了便利。
option命令用于定義一個開關選項,它的基本語法是option(OPTION_NAME "Description" DEFAULT_VALUE),其中OPTION_NAME是選項的名稱,"Description"是對該選項的描述信息,方便用戶了解其作用,DEFAULT_VALUE則是選項的默認值,可以是ON或OFF。
例如,在一個圖像處理項目中,可能希望有一個選項來控制是否啟用高級圖像算法。可以在CMakeLists.txt文件中這樣定義:
option(ENABLE_ADVANCED_ALGORITHM "Enable advanced image algorithm" OFF)
if(ENABLE_ADVANCED_ALGORITHM)
# 啟用高級圖像算法的相關配置
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表示默認不啟用該選項。當通過cmake -DENABLE_ADVANCED_ALGORITHM=ON ..命令啟用該選項時,if條件判斷為真,會添加相應的編譯定義-DENABLE_ADVANCED_ALGORITHM,并將高級圖像算法的源文件advanced_algorithm.cpp包含到可執行文件的構建中;如果不啟用該選項,else分支會被執行,使用基本圖像算法的源文件basic_algorithm.cpp進行構建。
2. 查找和管理依賴庫
在項目開發中,經常會依賴一些外部庫來實現特定的功能,而 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 [...]])。
假設我們的項目需要使用 Boost 庫的文件系統和線程組件,并且要求找到的 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,并且必須找到文件系統和線程組件。如果找到 Boost 庫,Boost_FOUND變量會被設置為真,然后通過message命令輸出 Boost 庫的包含目錄和庫文件路徑,再使用include_directories將 Boost 庫的包含目錄添加到項目中,使用target_link_libraries將 Boost 庫鏈接到項目的目標(your_target_name)上;如果沒有找到 Boost 庫,Boost_FOUND為假,通過message(FATAL_ERROR "Boost library not found.")輸出錯誤信息并終止構建過程。
3. 生成安裝目標
在項目開發完成后,通常需要將生成的可執行文件、庫文件、配置文件等安裝到指定的目錄中,以便用戶能夠方便地使用和部署。CMake 的install命令就提供了這樣的功能,它可以將各種文件和目標安裝到指定的位置。
(1)安裝可執行文件:
add_executable(my_app main.cpp)
install(TARGETS my_app RUNTIME DESTINATION bin)
上述代碼中,add_executable創建了一個名為my_app的可執行文件,install命令將my_app的運行時文件(即生成的可執行文件)安裝到bin目錄下。這里的bin目錄可以是絕對路徑,也可以是相對于CMAKE_INSTALL_PREFIX的相對路徑,CMAKE_INSTALL_PREFIX是 CMake 的一個預定義變量,用于指定安裝路徑的前綴,默認情況下在 Unix 系統中是/usr/local,在 Windows 系統中是C:/Program Files (x86)/項目名稱 。如果希望安裝到自定義的絕對路徑,如/home/user/my_project/bin,可以將DESTINATION設置為/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創建了一個共享庫my_lib,install命令將共享庫文件安裝到lib目錄下,將歸檔文件(通常是靜態庫文件,如果有的話)安裝到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目錄可以根據實際需求進行修改 。如果希望安裝整個include目錄及其所有內容,包括子目錄,可以將FILES_MATCHING相關部分去掉 。
五、常見問題及解決方案
在使用 CMake 的過程中,開發者可能會遇到各種各樣的問題,下面將列舉一些常見問題,并提供相應的解決方案。
1. 找不到依賴庫
在編譯項目時,CMake 可能會提示找不到某個依賴庫,這是一個比較常見的問題。比如在編譯一個使用 OpenCV 庫的項目時,可能會出現 “Could not find a package configuration file provided by “OpenCV” with any of the following names: OpenCVConfig.cmake, opencv-config.cmake” 這樣的錯誤提示 。
原因分析:造成這個問題的原因可能有多種。首先,可能是依賴庫沒有正確安裝,例如沒有安裝 OpenCV 庫或者安裝路徑不正確;其次,CMake 可能無法自動檢測到依賴庫的位置,這可能是因為庫的安裝路徑沒有被添加到系統的環境變量中,或者 CMake 的搜索路徑設置不正確 。
解決方案:針對這些問題,可以采取以下解決措施。首先,確保已經正確安裝了相應的依賴庫。如果已經安裝了依賴庫,但 CMake 仍然找不到它,可以通過設置 CMake 變量來指定庫的路徑 。例如,使用set()命令設置CMAKE_PREFIX_PATH變量,將庫的安裝路徑添加到其中:
set(CMAKE_PREFIX_PATH "/path/to/library" ${CMAKE_PREFIX_PATH})
另外,還可以使用find_path()或find_library()函數,并明確指定依賴庫的搜索路徑 。例如:
find_package(Boost REQUIRED PATHS /path/to/boost)
如果依賴是在系統級安裝的,確認相關的環境變量(如LD_LIBRARY_PATH或PYTHONPATH)已經設置正確 。對于預打包的庫,檢查操作系統是否有對應的軟件包,并使用系統的包管理工具安裝 。此外,查閱依賴庫的官方文檔,了解特定的 CMake 配置步驟也是很有必要的 。
2. 編譯選項錯誤
在 CMakeLists.txt 文件中設置編譯選項時,可能會因為設置錯誤而導致編譯失敗。比如在設置 C++ 編譯選項時,將CMAKE_CXX_FLAGS變量設置錯誤,可能會出現 “unrecognized command line option” 這樣的錯誤提示 。
原因分析:這種錯誤通常是由于對編譯選項的不熟悉或者拼寫錯誤導致的。不同的編譯器支持的編譯選項可能會有所不同,如果設置了不支持的編譯選項,就會出現此類錯誤 。
解決方案:解決這個問題,首先要仔細檢查 CMakeLists.txt 文件中編譯選項的設置,確保拼寫正確,并且是目標編譯器支持的選項 。例如,如果希望設置 C++ 編譯選項為-Wall -Werror,以開啟所有警告并將警告視為錯誤,可以這樣設置:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
如果不確定某個編譯選項是否被支持,可以查閱目標編譯器的官方文檔 。另外,在設置編譯選項時,可以使用條件判斷來根據不同的編譯器或構建類型設置不同的選項 。例如:
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()
上述代碼中,根據編譯器類型分別設置了不同的編譯選項,對于 GCC 編譯器,設置-Wall -Werror選項,對于 MSVC 編譯器,設置/W4 /WX選項 。
3. 頭文件路徑設置錯誤
在項目中,頭文件路徑設置不正確也會導致編譯錯誤,例如出現 “fatal error: xxx.h: No such file or directory” 這樣的錯誤提示 。
原因分析:這通常是因為在 CMakeLists.txt 文件中沒有正確設置頭文件的搜索路徑,或者設置的路徑與實際頭文件的位置不一致 。
解決方案:可以使用include_directories()命令將頭文件所在的路徑添加到編譯過程中。例如:
include_directories(include)
上述示例將include目錄添加到頭文件搜索路徑中 。不過,更推薦使用target_include_directories(),它針對特定目標設置包含目錄,作用域更明確,可維護性更好 。例如:
add_executable(my_program main.c)
target_include_directories(my_program PRIVATE include)
這里PRIVATE表示該包含目錄僅對my_program目標可見,如果頭文件路徑是公共的也可以使用PUBLIC關鍵字 。
4. 構建類型設置錯誤
在項目中,如果構建類型設置錯誤,可能會導致生成的可執行文件或庫文件不符合預期,例如在需要生成 Release 版本的可執行文件時,卻生成了 Debug 版本 。
原因分析:這種問題通常是由于沒有正確設置CMAKE_BUILD_TYPE變量,或者在命令行中沒有正確指定構建類型導致的 。
解決方案:可以在 CMakeLists.txt 文件中設置CMAKE_BUILD_TYPE變量的默認值 。例如:
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
上述示例中,如果沒有通過命令行或其他方式指定構建類型,默認設置為Release 。在命令行中也可以通過cmake -DCMAKE_BUILD_TYPE=Debug ..來指定構建類型為Debug 。
六、CMake在Clion中的配置
1. Ubuntu 下 Clion 的安裝
clion 及相關工具安裝:
# clion 所用到的工具鏈:gcc(C),g++(C++),make(連接),cmake(跨平臺建構系統), 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
# 插入如下內容,保存退出即可,在 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 文件時注意:把默認勾選的 Add to targerts 去掉(如下圖);在項目處右擊,選擇 Reload CMake Project,在重新加載完之后可以看到運行框列表有了對應的運行選項
cmake_minimum_required(VERSION 3.16)
project(cpp14)
set(CMAKE_CXX_STANDARD 14)
# 遞歸遍歷項目當前目錄下所有的 .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)