跳到正文

Wiki

頂層CMake與公共編譯選項

約 12 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

本節先講兩個文件:

  1. 頂層 CMakeLists.txt
  2. 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)

作用:

  1. 指定項目需要的最低 CMake 版本。
  2. 告訴 CMake 使用對應版本的策略行為。
  3. 太老的 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)

作用:

  1. 設置項目名。
  2. 設置項目版本。
  3. 聲明項目使用哪些語言。
  4. 初始化一批 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
)

含義:

  1. lib1_src_lib 自己需要 project_options。
  2. 鏈接 lib1_src_lib 的目標也會得到 project_options。
  3. project_warnings 只用於編譯 lib1_src_lib 自己,不強迫下游也開啓相同 warning。

20.cmake/WindowsRuntime.cmake

這個文件負責安裝 MinGW 工具鏈中找到的常用 GCC 運行庫。開發時先保證工具鏈的 bin/ 位於 PATH 中;驗證安裝結果時,按模板的安裝步驟執行,再整體移動安裝目錄檢查程序能否運行。

日常練習保留這個文件即可。新增第三方庫後,還要按依賴自身的安裝要求檢查運行環境。Linux 繼續使用系統運行庫和本模板的 RPATH 配置。