頂層CMake與公共編譯選項
本節講兩個文件:
- 頂層
CMakeLists.txt cmake/ProjectOptions.cmake
這兩個文件決定了整個工程的基本身份和公共編譯規則。
頂層 CMakeLists.txt
本模板的頂層文件如下:
cmake_minimum_required(VERSION 3.25)
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
include(GNUInstallDirs)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
add_subdirectory(src)
頂層 CMakeLists.txt 的原則是:只做總控,不堆具體業務和第三方依賴。
cmake_minimum_required
cmake_minimum_required(VERSION 3.25)
作用:
- 指定項目需要的最低 CMake 版本。
- 告訴 CMake 使用對應版本的策略行為。
- 太老的 CMake 會直接報錯,不會繼續配置。
可以填什麼:
cmake_minimum_required(VERSION 3.20)
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 常見版本 |
3.25 | 本模板使用,和 preset version 6 搭配 |
3.28+ | 適合較新的系統,但模板通用性下降 |
教學模板建議不要無限追新。只要滿足當前寫法即可。
project
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
作用:
- 設置項目名。
- 設置項目版本。
- 聲明項目使用哪些語言。
- 初始化一批 CMake 變量。
項目名
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
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、生成版本頭文件,這些變量會很有用。
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 都能參與構建。
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}
)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
作用:讀取另一個 CMake 文件。
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") | 更穩,不受當前工作目錄影響 |
CMAKE_CURRENT_LIST_DIR
表示當前正在處理的 CMake 文件所在目錄。
在頂層 CMakeLists.txt 中,它就是項目根目錄。
所以:
"${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake"
表示:
项目根目录/cmake/ProjectOptions.cmake
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 文件管理。
cmake/ProjectOptions.cmake
本模板內容如下:
set(CMAKE_CXX_STANDARD 17)
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)
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_17)
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
-Wall
-Wextra
-Wpedantic
)
這個文件只放項目公共選項,不放 Eigen、OpenCV 等第三方依賴。
CMAKE_CXX_STANDARD
set(CMAKE_CXX_STANDARD 17)
設置默認 C++ 標準。
常見值:
| 值 | 標準 |
|---|---|
11 | C++11 |
14 | C++14 |
17 | C++17 |
20 | C++20 |
23 | C++23 |
本模板使用 C++17:
set(CMAKE_CXX_STANDARD 17)
如果想用 C++20:
set(CMAKE_CXX_STANDARD 20)
target_compile_features(project_options INTERFACE cxx_std_20)
注意兩個地方最好一起改。
CMAKE_CXX_STANDARD_REQUIRED
set(CMAKE_CXX_STANDARD_REQUIRED ON)
作用:要求編譯器必須支持指定的 C++ 標準。
可以填什麼:
| 值 | 説明 |
|---|---|
ON | 必須使用指定標準,不支持就報錯 |
OFF | 不支持時可能退回較低標準 |
模板建議寫 ON。否則你以為在用 C++17,實際編譯器可能退回舊標準,錯誤會更隱蔽。
CMAKE_CXX_EXTENSIONS
set(CMAKE_CXX_EXTENSIONS OFF)
作用:控制是否使用編譯器擴展標準。
可以填什麼:
| 值 | 編譯選項傾向 | 説明 |
|---|---|---|
ON | gnu++17 | 允許 GNU 擴展 |
OFF | c++17 | 更標準、更可移植 |
Linux-only 項目也建議先寫 OFF,這樣代碼更接近標準 C++。
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 標準相關設置。
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,也默認開啓。
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++ 標準要求。
target_compile_features
target_compile_features(project_options INTERFACE cxx_std_17)
作用:聲明使用這個 target 的目標需要 C++17。
可以填什麼:
| 寫法 | 説明 |
|---|---|
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++17?
set(CMAKE_CXX_STANDARD 17)
是全局默認值。
target_compile_features(project_options INTERFACE cxx_std_17)
是目標級要求。現代 CMake 更推薦用目標級要求,因為它能隨着 target 鏈接關係傳播。
add_library(project_warnings INTERFACE)
add_library(project_warnings INTERFACE)
創建一個專門保存 warning 選項的 interface target。
這樣每個 target 可以選擇是否鏈接:
target_link_libraries(my_target
PRIVATE
project_warnings
)
target_compile_options
target_compile_options(project_warnings INTERFACE
-Wall
-Wextra
-Wpedantic
)
作用:給目標添加編譯選項。
本模板選項:
| 選項 | 作用 |
|---|---|
-Wall | 開啓一組常見警告 |
-Wextra | 開啓更多警告 |
-Wpedantic | 提醒不符合標準的寫法 |
常見可追加選項:
| 選項 | 説明 |
|---|---|
-Wconversion | 類型轉換可能丟失信息時警告 |
-Wshadow | 變量名遮蔽時警告 |
-Werror | 把 warning 當 error |
不建議模板默認加 -Werror。因為不同編譯器、不同第三方頭文件可能產生不同 warning,初學階段容易被卡住。
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。