Wiki
頂層CMake與公共編譯選項
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
本節先講兩個檔案:
- 頂層
CMakeLists.txt cmake/ProjectOptions.cmake
這兩個檔案決定了整個工程的基本身份和公共編譯規則。最後再介紹 Windows 安裝時使用的 cmake/WindowsRuntime.cmake。
1.頂層 CMakeLists.txt
本模板的頂層檔案如下:
cmake_minimum_required(VERSION 3.22)
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
include(GNUInstallDirs)
include(CTest)
option(BUILD_SHARED_LIBS "Build shared project libraries" ON)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
add_subdirectory(src)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/WindowsRuntime.cmake")
if(BUILD_TESTING)
add_test(NAME eigen_example COMMAND ${PROJECT_NAME})
set_tests_properties(eigen_example PROPERTIES
PASS_REGULAR_EXPRESSION "Determinant = -2"
)
endif()
頂層 CMakeLists.txt 的原則是:只做總控,不堆具體業務和第三方依賴。
2.cmake_minimum_required
cmake_minimum_required(VERSION 3.22)
作用:
- 指定專案需要的最低 CMake 版本。
- 告訴 CMake 使用對應版本的策略行為。
- 太老的 CMake 會直接報錯,不會繼續配置。
可以填什麼:
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.22)
cmake_minimum_required(VERSION 3.25)
cmake_minimum_required(VERSION 3.28)
選擇建議:
| 寫法 | 說明 |
|---|---|
3.10 |
太老,很多現代 CMake 寫法不方便 |
3.16 |
Ubuntu 20.04 常見版本,相容性好 |
3.22 |
本模板使用,相容 Ubuntu 22.04 的 CMake 3.22.1,搭配預設格式版本 3 |
3.25 |
使用預設格式版本 6 時需要,本模板當前沒有使用其新增功能 |
3.28 |
Ubuntu 24.04 常見版本,系統軟體源提供 CMake 3.28.3 |
4.2 |
Ubuntu 26.04 常見版本,系統軟體源提供 CMake 4.2.3 |
在 Ubuntu 24.04、26.04 上使用較新的 CMake 時,工程仍可保留 VERSION 3.22 的最低要求。只有實際使用了更高版本才支援的功能時,才需要提高最低版本。
最低版本應根據實際使用的功能和驗證環境確定。本模板在 CMake 3.22.1 下已驗證配置、構建、安裝與執行,無需提高到 3.25。調整完整工程的最低要求時,還應同步檢查 CMakePresets.json 的格式版本和 cmakeMinimumRequired。
3.project
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
作用:
- 設定專案名。
- 設定專案版本。
- 宣告專案使用哪些語言。
- 初始化一批 CMake 變數。
3.1.專案名
project(cmake_template)
專案名會影響這些變數:
| 變數 | 值 |
|---|---|
PROJECT_NAME |
cmake_template |
CMAKE_PROJECT_NAME |
頂層專案名,通常也是 cmake_template |
本模板在 src/CMakeLists.txt 中使用:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
所以最終執行檔名就是:
cmake_template
如果把專案名改成:
project(robot_app VERSION 1.0.0 LANGUAGES C CXX)
最終執行檔就會變成:
robot_app
3.2.VERSION
VERSION 1.0.0
專案版本號。
可以填什麼:
VERSION 0.1.0
VERSION 1.0.0
VERSION 2.3.4
CMake 會生成這些變數:
| 變數 | 示例值 |
|---|---|
PROJECT_VERSION |
1.0.0 |
PROJECT_VERSION_MAJOR |
1 |
PROJECT_VERSION_MINOR |
0 |
PROJECT_VERSION_PATCH |
0 |
以後如果做安裝包、匯出 CMake package、生成版本標頭檔案,這些變數會很有用。
3.3.LANGUAGES
LANGUAGES C CXX
宣告專案啟用哪些語言。
常見值:
| 值 | 說明 |
|---|---|
C |
C 語言 |
CXX |
C++ |
CUDA |
CUDA |
ASM |
彙編 |
Fortran |
Fortran |
如果專案只有 C++,也可以寫:
project(cmake_template VERSION 1.0.0 LANGUAGES CXX)
本模板保留 C CXX,是為了允許 src/*.c 和 src/*.cpp 都能參與構建。
4.include(GNUInstallDirs)
include(GNUInstallDirs)
作用:載入 GNU 風格的安裝目錄變數。
常用變數:
| 變數 | 常見值 | 用途 |
|---|---|---|
CMAKE_INSTALL_BINDIR |
bin |
執行檔 |
CMAKE_INSTALL_LIBDIR |
lib 或 lib64 |
動態庫、靜態庫 |
CMAKE_INSTALL_INCLUDEDIR |
include |
標頭檔案 |
CMAKE_INSTALL_DATADIR |
share |
資料檔案 |
CMAKE_INSTALL_DOCDIR |
share/doc/项目名 |
文件 |
為什麼不用手寫:
install(TARGETS app DESTINATION bin)
install(TARGETS lib DESTINATION lib)
因為不同 Linux 發行版可能使用 lib 或 lib64。用 GNUInstallDirs 後,CMake 會根據系統習慣設定。
模板中的使用示例:
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
庫安裝:
install(TARGETS ${PREFIX}_src_lib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
標頭檔案安裝:
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/inc/"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
5.選擇庫型別和啟用測試
頂層先提供庫型別選項:
option(BUILD_SHARED_LIBS "Build shared project libraries" ON)
option 建立一個可以在配置時選擇的開關。這裡預設開啟,Linux 預設也明確設定為 ON;Windows 預設設定為 OFF。後面的庫模組不寫死庫型別,就可以使用這項設定。
頂層還載入:
include(CTest)
CTest 使工程可以用 ctest 執行測試,並提供 BUILD_TESTING 選項。頂層的 eigen_example 測試會運行當前程式,再檢查原始 Eigen 示例的輸出。它不增加業務原始碼。
add_subdirectory(src) 後加載的 WindowsRuntime.cmake 用於安裝常用 GCC 執行庫,後面單獨解釋。
6.include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
作用:讀取另一個 CMake 檔案。
6.1.include(...)
include 會在當前作用域執行指定的 CMake 指令碼。
可以寫:
include(GNUInstallDirs)
include(cmake/ProjectOptions.cmake)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
區別:
| 寫法 | 說明 |
|---|---|
include(GNUInstallDirs) |
載入 CMake 內建模組 |
include(cmake/ProjectOptions.cmake) |
相對路徑寫法 |
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake") |
更穩,不受當前工作目錄影響 |
6.2.CMAKE_CURRENT_LIST_DIR
表示當前正在處理的 CMake 檔案所在目錄。
在頂層 CMakeLists.txt 中,它就是專案根目錄。
所以:
"${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake"
表示:
项目根目录/cmake/ProjectOptions.cmake
7.add_subdirectory(src)
add_subdirectory(src)
作用:進入 src 子目錄,繼續讀取 src/CMakeLists.txt。
可以填什麼:
add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(third_party/some_lib)
常見寫法:
add_subdirectory(<源码目录>)
也可以指定構建目錄:
add_subdirectory(src src_build)
但一般不需要。
頂層只寫:
add_subdirectory(src)
表示具體的執行檔、庫、依賴,都交給 src 下面的 CMake 檔案管理。
8.cmake/ProjectOptions.cmake
本模板內容如下:
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Keep the Windows executable in a predictable location.
if(WIN32)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
"$<$<COMPILE_LANG_AND_ID:C,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
)
這個檔案只放專案公共選項,不放 Eigen、OpenCV 等第三方依賴。
9.CMAKE_CXX_STANDARD
set(CMAKE_CXX_STANDARD 20)
設定預設 C++ 標準。
常見值:
| 值 | 標準 |
|---|---|
11 |
C++11 |
14 |
C++14 |
17 |
C++17 |
20 |
C++20 |
23 |
C++23 |
本模板預設使用 C++20:
set(CMAKE_CXX_STANDARD 20)
如果想用 C++23:
set(CMAKE_CXX_STANDARD 23)
target_compile_features(project_options INTERFACE cxx_std_23)
注意兩個地方最好一起改。
CMake 版本和 C++ 標準版本需要分別判斷。CMake 3.22 已能識別 cxx_std_20,但這表示選擇相應的編譯器標準模式,不代表編譯器和標準庫已經實現全部 C++20 功能;具體說明見 CMake 編譯特性文件。模板當前的 Eigen 示例已使用 GCC 11.4 驗證,後續程式用到的新語法和標準庫功能仍需對應的編譯器支援。
10.CMAKE_CXX_STANDARD_REQUIRED
set(CMAKE_CXX_STANDARD_REQUIRED ON)
作用:要求編譯器必須支援指定的 C++ 標準。
可以填什麼:
| 值 | 說明 |
|---|---|
ON |
必須使用指定標準,不支援就報錯 |
OFF |
不支援時可能退回較低標準 |
模板建議寫 ON,使不支援所選標準模式的編譯器明確報錯,避免靜默退回舊標準。
11.CMAKE_CXX_EXTENSIONS
set(CMAKE_CXX_EXTENSIONS OFF)
作用:控制是否使用編譯器擴充套件標準。
可以填什麼:
| 值 | 編譯選項傾向 | 說明 |
|---|---|---|
ON |
gnu++20 |
允許 GNU 擴充套件 |
OFF |
c++20 |
使用所選標準的非 GNU 擴充套件模式 |
表中以模板預設的 C++20 為例,具體引數拼寫由 CMake 根據編譯器選擇。Linux-only 專案也建議先寫 OFF,這樣程式碼更接近標準 C++。
12.C 標準相關選項
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
含義和 C++ 對應選項類似。
常見 C 標準:
| 值 | 標準 |
|---|---|
99 |
C99 |
11 |
C11 |
17 |
C17 |
23 |
C23 |
如果專案沒有 .c 檔案,也可以只啟用 C++:
project(cmake_template VERSION 1.0.0 LANGUAGES CXX)
然後刪除 C 標準相關設定。
13.CMAKE_EXPORT_COMPILE_COMMANDS
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
作用:生成 compile_commands.json。
可以填:
| 值 | 說明 |
|---|---|
ON |
生成 |
OFF |
不生成 |
這個選項在 CMakePresets.json 中也寫了:
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
兩個地方都寫不衝突。preset 的好處是 IDE 和命令列都能明確看到這個配置;ProjectOptions.cmake 的好處是即使不用 preset,也預設開啟。
14.Windows 的執行檔案輸出目錄
if(WIN32)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
WIN32 表示當前構建目標是 Windows,也包含 64 位 Windows。這裡在建立各 target 之前,統一指定執行檔案的輸出位置。
Windows 主程式會進入 build/windows-debug/bin/。兩個專案庫按預設選擇靜態連結;Linux 不進入這個輸出目錄分支,保留原有構建目錄佈局。
15.add_library(project_options INTERFACE)
add_library(project_options INTERFACE)
建立一個 interface target。
INTERFACE target 沒有原始碼,不會生成 .a 或 .so。它只用來攜帶編譯特性、巨集定義、include 路徑、連結依賴等"使用要求"。
add_library 常見型別:
| 型別 | 是否有原始碼 | 是否生成產物 | 用途 |
|---|---|---|---|
STATIC |
有 | .a |
靜態庫 |
SHARED |
有 | .so |
動態庫 |
MODULE |
有 | 外掛式動態模組 | |
OBJECT |
有 | .o 集合 |
複用物件檔案 |
INTERFACE |
無 | 無 | 傳遞配置 |
本模板用:
project_options
儲存 C++ 標準要求。
16.target_compile_features
target_compile_features(project_options INTERFACE cxx_std_20)
作用:宣告使用這個 target 的目標至少需要 C++20 標準模式。
可以填什麼:
| 寫法 | 說明 |
|---|---|
cxx_std_11 |
至少 C++11 |
cxx_std_14 |
至少 C++14 |
cxx_std_17 |
至少 C++17 |
cxx_std_20 |
至少 C++20 |
cxx_std_23 |
至少 C++23 |
為什麼這裡又寫了一遍 C++20?
set(CMAKE_CXX_STANDARD 20)
是當前目錄及其子目錄中後續建立目標的預設值。
target_compile_features(project_options INTERFACE cxx_std_20)
是目標級要求。現代 CMake 更推薦用目標級要求,因為它能隨著 target 連結關係傳播。
17.add_library(project_warnings INTERFACE)
add_library(project_warnings INTERFACE)
建立一個專門儲存 warning 選項的 interface target。
這樣每個 target 可以選擇是否連結:
target_link_libraries(my_target
PRIVATE
project_warnings
)
18.target_compile_options
target_compile_options(project_warnings INTERFACE
"$<$<COMPILE_LANG_AND_ID:C,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
)
作用:給目標新增編譯選項。
本模板選項:
| 選項 | 作用 |
|---|---|
-Wall |
開啟一組常見警告 |
-Wextra |
開啟更多警告 |
-Wpedantic |
提醒不符合標準的寫法 |
外面的 $<...> 是生成器表示式。第一行只在用 GNU 或 Clang 編譯 C 原始檔時新增這些引數,第二行對應 C++ 原始檔。Windows 的 MinGW-w64 GCC 同樣屬於 GNU,因此兩種平臺都能使用這一組警告選項。表示式用引號括起來,讓其中用分號分隔的選項作為一個整體交給 CMake 處理。參見 CMake 生成器表示式說明。
常見可追加選項:
| 選項 | 說明 |
|---|---|
-Wconversion |
型別轉換可能丟失資訊時警告 |
-Wshadow |
變數名遮蔽時警告 |
-Werror |
把 warning 當 error |
不建議模板預設加 -Werror。因為不同編譯器、不同第三方標頭檔案可能產生不同 warning,初學階段容易被卡住。
19.PUBLIC、PRIVATE、INTERFACE 的直覺
後面的 CMake 檔案會大量出現:
PUBLIC
PRIVATE
INTERFACE
它們控制"屬性是否傳遞給使用者"。
| 關鍵字 | 自己用 | 傳給別人 |
|---|---|---|
PRIVATE |
是 | 否 |
PUBLIC |
是 | 是 |
INTERFACE |
否 | 是 |
例如:
target_link_libraries(lib1_src_lib
PUBLIC
project_options
PRIVATE
project_warnings
)
含義:
lib1_src_lib自己需要project_options。- 連結
lib1_src_lib的目標也會得到project_options。 project_warnings只用於編譯lib1_src_lib自己,不強迫下游也開啟相同 warning。
20.cmake/WindowsRuntime.cmake
這個檔案負責安裝 MinGW 工具鏈中找到的常用 GCC 執行庫。開發時先保證工具鏈的 bin/ 位於 PATH 中;驗證安裝結果時,按模板的安裝步驟執行,再整體移動安裝目錄檢查程式能否執行。
日常練習保留這個檔案即可。新增第三方庫後,還要按依賴自身的安裝要求檢查執行環境。Linux 繼續使用系統執行庫和本模板的 RPATH 配置。