背景
CMake是一個(gè)跨平臺(tái)的構(gòu)建系統(tǒng),它能自動(dòng)生成各種平臺(tái)和編譯器的構(gòu)建文件,這對(duì)于C++開(kāi)發(fā)人員來(lái)說(shuō)是必須掌握使用的工具。CMake的特點(diǎn)包括:
跨平臺(tái)構(gòu)建:CMake支持多種操作系統(tǒng),包括Windows、Linux、macOS等。學(xué)會(huì)使用CMake可以讓你輕松地為不同平臺(tái)生成構(gòu)建文件,提高項(xiàng)目的可移植性。
編譯器和構(gòu)建工具的獨(dú)立性:CMake可以生成各種編譯器和構(gòu)建工具的項(xiàng)目文件,例如Visual Studio、Xcode、Makefile等。這意味著你的項(xiàng)目可以在多種開(kāi)發(fā)環(huán)境中使用,而無(wú)需為每個(gè)環(huán)境編寫(xiě)特定的構(gòu)建腳本。
簡(jiǎn)化構(gòu)建過(guò)程:CMake可以幫助你管理復(fù)雜項(xiàng)目的構(gòu)建過(guò)程。它可以自動(dòng)檢測(cè)依賴(lài)關(guān)系、生成目標(biāo)文件、編譯靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)等。通過(guò)CMake配置文件(CMakeLists.txt),你可以靈活地控制整個(gè)構(gòu)建過(guò)程,減輕手動(dòng)管理的負(fù)擔(dān)。
便于協(xié)作開(kāi)發(fā):使用CMake使得項(xiàng)目的構(gòu)建方式更加標(biāo)準(zhǔn)化,有利于多人協(xié)作開(kāi)發(fā)。團(tuán)隊(duì)成員無(wú)需花費(fèi)大量時(shí)間配置開(kāi)發(fā)環(huán)境,只需遵循CMakeLists.txt的規(guī)則,即可輕松地構(gòu)建和運(yùn)行項(xiàng)目。
開(kāi)源生態(tài)系統(tǒng):CMake是一個(gè)開(kāi)源項(xiàng)目,有龐大的社區(qū)支持。這意味著你可以找到大量的教程、示例項(xiàng)目和技巧來(lái)學(xué)習(xí)和解決問(wèn)題。此外,許多開(kāi)源庫(kù)已經(jīng)采用CMake作為構(gòu)建系統(tǒng),因此學(xué)習(xí)CMake可以讓你更方便地集成和使用這些庫(kù)。
學(xué)習(xí)CMake將使你能夠更高效地管理C++項(xiàng)目的構(gòu)建過(guò)程,提高項(xiàng)目的可移植性和協(xié)作效率。
關(guān)鍵知識(shí)
首先,我們需要弄清楚兩個(gè)兩個(gè)概念,以免在描述中發(fā)生混淆:
在命令行中輸入的 CMake 命令通常稱(chēng)為CMake 命令行參數(shù)(CMake command-line arguments)或CMake 命令行選項(xiàng)(CMake command-line options)。這些參數(shù)或選項(xiàng)用于指定生成的構(gòu)建系統(tǒng)、目標(biāo)架構(gòu)、構(gòu)建類(lèi)型等。它們控制 CMake 的行為,告訴 CMake 如何處理項(xiàng)目。
編寫(xiě) CMakeLists.txt 文件時(shí)使用的語(yǔ)法稱(chēng)為CMake 語(yǔ)法(CMake syntax)或CMake 腳本語(yǔ)言(CMake scripting language)。CMake 語(yǔ)法用于編寫(xiě) CMakeLists.txt 文件,指導(dǎo) CMake 如何為項(xiàng)目生成構(gòu)建系統(tǒng)。CMake 腳本語(yǔ)言包括指令、變量、函數(shù)、宏、控制結(jié)構(gòu)等,用于組織和控制項(xiàng)目的構(gòu)建過(guò)程。
CMakeLists.txt 是用于編寫(xiě) CMake 構(gòu)建腳本的文件。下面需要重點(diǎn)講述CMake語(yǔ)法,其語(yǔ)法主要由以下幾個(gè)部分組成:
- 注釋
使用井號(hào)(#)開(kāi)頭的行是注釋行,會(huì)被 CMake 忽略。
# 這是一個(gè)注釋
- 變量
在 CMake 中,你可以使用set()命令定義變量:
set(VARIABLE_NAME value)
讀取變量的值時(shí),使用 ${VARIABLE_NAME} 進(jìn)行引用:
set(SOURCE_FILES main.cpp)
message("Source files: ${SOURCE_FILES}") # 輸出:Source files: main.cpp
- 控制結(jié)構(gòu)
CMake 提供了類(lèi)似于其他編程語(yǔ)言的控制結(jié)構(gòu),如條件語(yǔ)句、循環(huán)語(yǔ)句等。
- 條件語(yǔ)句:
if(CONDITION)
# ...
elseif(OTHER_CONDITION)
# ...
else()
# ...
endif()
- 循環(huán)語(yǔ)句:
foreach(item IN LISTS some_list)
# ...
endforeach()
- 函數(shù)和宏
你可以定義自己的函數(shù)和宏,它們有類(lèi)似的語(yǔ)法:
- 函數(shù):
function(FUNCTION_NAME arg1 arg2)
# ...
endfunction()
- 宏:
macro(MACRO_NAME arg1 arg2)
# ...
endmacro()
- 常用命令
以下是一些常用的 CMake 命令:
project(): 定義項(xiàng)目名稱(chēng)和版本。cmake_minimum_required(): 指定 CMake 的最低版本要求。add_executable(): 生成可執(zhí)行文件。add_library(): 生成庫(kù)文件。target_link_libraries(): 鏈接庫(kù)文件。include_directories(): 添加頭文件目錄。find_package(): 尋找并加載外部庫(kù)。install(): 定義安裝規(guī)則。
這僅是 CMake 語(yǔ)法的簡(jiǎn)要概述,CMake 提供了豐富的功能和命令,具體內(nèi)容可以參考官方文檔:CMake官方文檔。不過(guò)籠統(tǒng)的概述相信并不能讓讀者掌握CMake的使用,不用擔(dān)心,接下來(lái),我們會(huì)給出一個(gè)CMakeList的編寫(xiě)例子,讓讀者對(duì)CMake語(yǔ)法有初步的認(rèn)識(shí)。在這之前,我們先給出項(xiàng)目的目錄結(jié)構(gòu)。
項(xiàng)目目錄結(jié)構(gòu)
MyApp/
├─ CMakeLists.txt
├─ src/
│ └─ main.cpp
├─ include/
│ ├─ static_lib/
│ │ └─ StaticLibHeader.h
│ └─ dynamic_lib/
│ └─ DynamicLibHeader.h
├─ libs/
│ ├─ static/
│ │ └─ libStatic.lib
│ └─ dynamic/
│ ├─ libDynamic.dll
│ └─ libDynamic.lib
├─ subproject/
│ ├─ CMakeLists.txt
│ ├─ src/
│ │ └─ subproject_main.cpp
│ └─ include/
│ └─ subproject/
│ └─ SubProjectHeader.h
└─ config.h.in
這個(gè)目錄結(jié)構(gòu)包含了以下組成部分:
-
根目錄:包含主項(xiàng)目的
CMakeLists.txt文件以及用于在構(gòu)建時(shí)生成配置文件的config.h.in文件。 -
src:存放項(xiàng)目源代碼的目錄,這里只有一個(gè)
main.cpp文件作為示例。 -
include:包含頭文件的目錄。這里有兩個(gè)子目錄,一個(gè)是
static_lib,包含靜態(tài)庫(kù)的頭文件StaticLibHeader.h;另一個(gè)是dynamic_lib,包含動(dòng)態(tài)庫(kù)的頭文件DynamicLibHeader.h。 -
libs:存放靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的目錄。這里有兩個(gè)子目錄:
static和dynamic。static目錄中包含一個(gè)名為libStatic.lib的靜態(tài)庫(kù),dynamic目錄中包含一個(gè)名為libDynamic.dll的動(dòng)態(tài)庫(kù)以及其導(dǎo)入庫(kù)libDynamic.lib。 -
subproject:一個(gè)子項(xiàng)目的目錄,包含自己的
CMakeLists.txt文件、源代碼(subproject_main.cpp)以及頭文件(SubProjectHeader.h)。
CMakeList腳本示例
# 設(shè)置 CMake 最低版本要求
cmake_minimum_required(VERSION 3.8)
# 定義項(xiàng)目名稱(chēng)和版本
project(MyApp VERSION 1.0.0 LANGUAGES CXX)
# 設(shè)置 C++ 標(biāo)準(zhǔn)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 定義用戶(hù)可配置的選項(xiàng)
option(ENABLE_DEBUG "Enable debug output" ON)
if(ENABLE_DEBUG)
add_definitions(-DDEBUG_OUTPUT)
endif()
# 自定義宏:添加 MSVC 常用編譯選項(xiàng)
macro(add_msvc_options target)
if(MSVC)
target_compile_options(${target} PRIVATE
/W4 # 設(shè)置警告級(jí)別為 4
/WX # 將警告視為錯(cuò)誤
/MP # 啟用多處理器編譯
/permissive- # 禁用不嚴(yán)格的語(yǔ)言 conformance
/Zc:__cplusplus # 啟用正確的 __cplusplus 宏值
/Zc:inline # 移除未使用的函數(shù)
/Gm- # 禁用最小生成(minimal rebuild)
/EHsc # 指定異常處理模型
)
endif()
endmacro()
# 添加源文件
set(SOURCE_FILES src/main.cpp)
# 生成可執(zhí)行文件
add_executable(MyApp ${SOURCE_FILES})
# 調(diào)用自定義宏,為 MyApp 添加 MSVC 常用編譯選項(xiàng)
add_msvc_options(MyApp)
# 為特定目標(biāo)設(shè)置頭文件目錄
target_include_directories(MyApp PRIVATE include)
# 鏈接靜態(tài)庫(kù)
find_library(STATIC_LIB libStatic.lib PATHS "${CMAKE_SOURCE_DIR}/libs/static")
target_link_libraries(MyApp PRIVATE ${STATIC_LIB})
# 鏈接動(dòng)態(tài)庫(kù)
find_library(DYNAMIC_LIB libDynamic.dll PATHS "${CMAKE_SOURCE_DIR}/libs/dynamic")
find_library(DYNAMIC_LIB_IMPORT libDynamic.lib PATHS "${CMAKE_SOURCE_DIR}/libs/dynamic")
target_link_libraries(MyApp PRIVATE ${DYNAMIC_LIB_IMPORT})
# 使用 Windows 的 DLL delay-load 機(jī)制
set_target_properties(MyApp PROPERTIES LINK_FLAGS "/DELAYLOAD:libDynamic.dll")
# 根據(jù)目標(biāo)架構(gòu)定制編譯選項(xiàng)和鏈接選項(xiàng)
if(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
message("Building for Win32 (x86) architecture")
target_compile_options(MyApp PRIVATE /arch:SSE2)
elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "x64")
message("Building for x64 architecture")
target_compile_options(MyApp PRIVATE /arch:AVX2)
else()
message(WARNING "Unknown architecture")
endif()
# 添加子項(xiàng)目
add_subdirectory(subproject)
# 在構(gòu)建時(shí)生成配置文件
configure_file(config.h.in config.h @ONLY)
# 指定安裝規(guī)則
install(TARGETS MyApp RUNTIME DESTINATION bin)
install(FILES "${CMAKE_SOURCE_DIR}/libs/dynamic/libDynamic.dll" DESTINATION bin)
接下來(lái),需要在命令行中運(yùn)行以下命令生成 Visual Studio 工程。首先,從項(xiàng)目的根目錄創(chuàng)建一個(gè)新的目錄,例如 build,用于存放構(gòu)建文件。接著,根據(jù)目標(biāo)架構(gòu)使用 -A 參數(shù)運(yùn)行 CMake 命令:
- 對(duì)于 x86 架構(gòu):
cmake -G "Visual Studio 16 2019" -A Win32 .. - 對(duì)于 x64 架構(gòu):
cmake -G "Visual Studio 16 2019" -A x64 ..
運(yùn)行之后,應(yīng)能在 build 目錄中看到生成的 Visual Studio 工程文件。可以打開(kāi)它并使用 Visual Studio 進(jìn)行構(gòu)建。
示例解析
- 設(shè)置 CMake 最低版本要求
# 設(shè)置 CMake 最低版本要求
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required 指令用于指定項(xiàng)目所需的最低 CMake 版本。這個(gè)指令確保當(dāng)前環(huán)境中的 CMake 版本滿(mǎn)足項(xiàng)目的構(gòu)建要求。如果 CMake 的版本低于指定的最低版本,CMake 會(huì)報(bào)錯(cuò)并終止構(gòu)建過(guò)程。這個(gè)指令的主要作用是確保項(xiàng)目所使用的 CMake 功能和語(yǔ)法與當(dāng)前 CMake 版本兼容。隨著 CMake 的發(fā)展,有時(shí)會(huì)引入新功能、改進(jìn)現(xiàn)有功能或者廢棄舊功能。如果用戶(hù)嘗試使用 CMake 3.8 以下的版本來(lái)構(gòu)建項(xiàng)目,將會(huì)收到錯(cuò)誤消息。
# 定義項(xiàng)目名稱(chēng)和版本
project(MyApp VERSION 1.0.0 LANGUAGES CXX)
project指令先是定義了項(xiàng)目名稱(chēng),再使用VERSION關(guān)鍵字并跟隨具體版本號(hào)1.0.0,來(lái)指定當(dāng)前項(xiàng)目版本,此處很好理解。而LANGUAGES關(guān)鍵字以及后面的參數(shù)值CXX,則代表C++語(yǔ)言,CMake 會(huì)根據(jù)當(dāng)前操作系統(tǒng)、可用編譯器和指定的編程語(yǔ)言自動(dòng)選擇合適的編譯器。CMake 支持多種編譯器,如 GCC、Clang、MSVC等。CMake 在選擇編譯器時(shí)會(huì)遵循一定的規(guī)則和優(yōu)先級(jí),匹配規(guī)則包括:
Unix-like 系統(tǒng)(如 Linux 和 macOS):對(duì)于 C++ 代碼,默認(rèn)情況下,CMake 會(huì)首先嘗試使用系統(tǒng)上可用的 GCC 編譯器。如果沒(méi)有找到 GCC,CMake 會(huì)繼續(xù)嘗試查找其他可用的編譯器,如 Clang??梢酝ㄟ^(guò)設(shè)置 CMAKE_CXX_COMPILER 變量來(lái)手動(dòng)指定編譯器。
Windows 系統(tǒng):對(duì)于 C++ 代碼,默認(rèn)情況下,CMake 會(huì)嘗試使用 MSVC作為編譯器。如果沒(méi)有找到MSVC,CMake 會(huì)繼續(xù)嘗試查找其他可用的編譯器,如 MinGW 或 Clang。與 Unix-like 系統(tǒng)類(lèi)似,也可以通過(guò)設(shè)置 CMAKE_CXX_COMPILER 變量來(lái)手動(dòng)指定編譯器。
set(CMAKE_CXX_COMPILER "/path/to/your/compiler")
某些情況下,CMake 可能無(wú)法自動(dòng)檢測(cè)到合適的編譯器,或者需要使用特定版本的編譯器,可以通過(guò)設(shè)置CMAKE_CXX_COMPILER 變量來(lái)實(shí)現(xiàn)。
- 設(shè)置 C++ 標(biāo)準(zhǔn)
# 設(shè)置 C++ 標(biāo)準(zhǔn)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
這三行 CMake 指令用于設(shè)置 C++ 項(xiàng)目的編譯選項(xiàng)。
set(CMAKE_CXX_STANDARD 17):這一行指令設(shè)置了項(xiàng)目使用的 C++ 標(biāo)準(zhǔn)版本。在這個(gè)例子中,我們選擇了 C++17 標(biāo)準(zhǔn)。CMake 支持設(shè)置多種 C++ 標(biāo)準(zhǔn)版本,如 C++11、C++14、C++17、C++20 等。可以根據(jù)項(xiàng)目的需求,選擇合適的 C++ 標(biāo)準(zhǔn)版本。
set(CMAKE_CXX_STANDARD_REQUIRED ON):這一行指令表示,如果編譯器不支持指定的 C++ 標(biāo)準(zhǔn),CMake 將報(bào)錯(cuò)并終止構(gòu)建過(guò)程。如果將此選項(xiàng)設(shè)置為 OFF,則 CMake 會(huì)盡量使用所選 C++ 標(biāo)準(zhǔn)版本進(jìn)行編譯,但如果編譯器不支持該版本,CMake 會(huì)自動(dòng)降級(jí)到編譯器支持的最接近的 C++ 標(biāo)準(zhǔn)版本。
set(CMAKE_CXX_EXTENSIONS OFF):這一行指令用于禁用編譯器特定的 C++ 語(yǔ)言擴(kuò)展。將此選項(xiàng)設(shè)置為 OFF 可確保項(xiàng)目遵循 C++ 標(biāo)準(zhǔn),并具有更好的可移植性。如果將此選項(xiàng)設(shè)置為 ON,則 CMake 允許編譯器使用其特定的 C++ 語(yǔ)言擴(kuò)展,這可能導(dǎo)致項(xiàng)目在不同編譯器之間的行為不一致。
- 定義用戶(hù)可配置的選項(xiàng)
# 定義用戶(hù)可配置的選項(xiàng)
option(ENABLE_DEBUG "Enable debug output" ON)
if(ENABLE_DEBUG)
add_definitions(-DDEBUG_OUTPUT)
endif()
option(ENABLE_DEBUG "Enable debug output" ON):此命令定義了一個(gè)名為 ENABLE_DEBUG 的用戶(hù)可配置選項(xiàng)。option() 命令用于定義一個(gè)布爾型變量,可以在 CMake 生成構(gòu)建系統(tǒng)時(shí)進(jìn)行配置。命令的第二個(gè)參數(shù)是對(duì)可配置選項(xiàng)的描述。這個(gè)描述可以幫助其他開(kāi)發(fā)者或用戶(hù)理解這個(gè)選項(xiàng)的用途。當(dāng)使用 CMake GUI 工具時(shí),這個(gè)描述將作為提示顯示在選項(xiàng)旁邊。這個(gè)描述在命令行模式下不會(huì)出現(xiàn)。在這個(gè)例子中,ENABLE_DEBUG 的默認(rèn)值為 ON。用戶(hù)可以通過(guò) CMake 命令行參數(shù)或 GUI 工具來(lái)改變這個(gè)選項(xiàng)的值。
cmake -D ENABLE_DEBUG=OFF ..
if(ENABLE_DEBUG) 和 endif():這兩個(gè)命令定義了一個(gè)條件語(yǔ)句。如果 ENABLE_DEBUG 選項(xiàng)為 ON,則條件為真,執(zhí)行語(yǔ)句塊中的命令。否則,不執(zhí)行這些命令。
add_definitions(-DDEBUG_OUTPUT):此命令僅在 ENABLE_DEBUG 為 ON 時(shí)執(zhí)行。add_definitions() 命令用于添加編譯器定義。在這個(gè)例子中,-DDEBUG_OUTPUT 添加了一個(gè)名為 DEBUG_OUTPUT 的預(yù)處理器宏定義。這個(gè)宏定義可以在源代碼中使用,以便根據(jù)其值啟用或禁用調(diào)試輸出功能。例如,在 C++ 代碼中,可以使用 #ifdef DEBUG_OUTPUT 和 #endif 來(lái)包裹調(diào)試輸出相關(guān)的代碼。
#ifdef DEBUG_OUTPUT
//只在debug模式下運(yùn)行的邏輯
std::cout << "debug mode" << std::endl;
#endif
- 自定義宏:添加 MSVC 常用編譯選項(xiàng)
# 自定義宏:添加 MSVC 常用編譯選項(xiàng)
macro(add_msvc_options target)
if(MSVC)
target_compile_options(${target} PRIVATE
/W4 # 設(shè)置警告級(jí)別為 4
/WX # 將警告視為錯(cuò)誤
/MP # 啟用多處理器編譯
/permissive- # 禁用不嚴(yán)格的語(yǔ)言 conformance
/Zc:__cplusplus # 啟用正確的 __cplusplus 宏值
/Zc:inline # 移除未使用的函數(shù)
/Gm- # 禁用最小生成(minimal rebuild)
/EHsc # 指定異常處理模型
)
endif()
endmacro()
macro(add_msvc_options target) 定義了一個(gè)名為 add_msvc_options 的宏。在 CMake 中,宏用于封裝一組命令,以便在多個(gè)地方重復(fù)使用。而add_msvc_options 宏接收一個(gè)參數(shù) target。在宏內(nèi)部,${target} 會(huì)被替換為實(shí)際傳遞的目標(biāo)名稱(chēng)。
target_compile_options() 命令用于為特定的目標(biāo)(如可執(zhí)行文件或庫(kù))添加編譯選項(xiàng)。其中的${target}是一個(gè)變量,表示調(diào)用宏時(shí)傳遞的項(xiàng)目名。而PRIVATE關(guān)鍵字表示這些編譯選項(xiàng)只對(duì)當(dāng)前目標(biāo)生效。在這個(gè)例子中,編譯選項(xiàng)僅影響 ${target} 的構(gòu)建。如果有其他目標(biāo)依賴(lài)于 ${target},這些編譯選項(xiàng)不會(huì)傳遞給那些依賴(lài)目標(biāo)。
- 鏈接靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)
# 鏈接靜態(tài)庫(kù)
find_library(STATIC_LIB libStatic.lib PATHS "${CMAKE_SOURCE_DIR}/libs/static")
target_link_libraries(MyApp PRIVATE ${STATIC_LIB})
# 鏈接動(dòng)態(tài)庫(kù)
find_library(DYNAMIC_LIB libDynamic.dll PATHS "${CMAKE_SOURCE_DIR}/libs/dynamic")
find_library(DYNAMIC_LIB_IMPORT libDynamic.lib PATHS "${CMAKE_SOURCE_DIR}/libs/dynamic")
target_link_libraries(MyApp PRIVATE ${DYNAMIC_LIB_IMPORT})
find_library() 命令用于在指定的路徑中查找?guī)煳募T谶@個(gè)例子中鏈接靜態(tài)庫(kù)的部分,它查找名為 libStatic.lib 的靜態(tài)庫(kù),并將其路徑存儲(chǔ)在變量 STATIC_LIB 中。PATHS 參數(shù)用于指定搜索庫(kù)文件的目錄,這里設(shè)置為 "${CMAKE_SOURCE_DIR}/libs/static"。
target_link_libraries() 命令用于將庫(kù)鏈接到指定的目標(biāo)。這里將 STATIC_LIB 鏈接到 MyApp 目標(biāo)。PRIVATE 關(guān)鍵字表示這個(gè)庫(kù)僅對(duì)當(dāng)前目標(biāo)(MyApp)可見(jiàn),不會(huì)傳遞給其他依賴(lài)于 MyApp 的目標(biāo)。
至于在鏈接動(dòng)態(tài)庫(kù)時(shí),為什么使用 ${DYNAMIC_LIB_IMPORT} 而不是 ${DYNAMIC_LIB},原因是在 Windows 平臺(tái)上,當(dāng)鏈接到動(dòng)態(tài)庫(kù)時(shí),需要鏈接到相應(yīng)的導(dǎo)入庫(kù)(.lib 文件),而不是直接鏈接到動(dòng)態(tài)庫(kù)(.dll 文件)。導(dǎo)入庫(kù)包含了調(diào)用動(dòng)態(tài)庫(kù)函數(shù)所需的信息,編譯器和鏈接器需要這些信息來(lái)正確生成可執(zhí)行文件。運(yùn)行時(shí),可執(zhí)行文件會(huì)自動(dòng)加載相應(yīng)的 .dll 文件。
- 修改鏈接標(biāo)志,使用延遲加載機(jī)制
# 使用 Windows 的 DLL delay-load 機(jī)制
set_target_properties(MyApp PROPERTIES LINK_FLAGS "/DELAYLOAD:libDynamic.dll")
這個(gè)命令是用于設(shè)置目標(biāo)(在這個(gè)例子中是 MyApp)的屬性。set_target_properties() 命令允許你修改一個(gè)目標(biāo)的一些屬性,例如鏈接標(biāo)志、輸出名稱(chēng)等。在這個(gè)例子中,我們修改了 MyApp 的鏈接標(biāo)志。具體來(lái)說(shuō),LINK_FLAGS 屬性表示要傳遞給鏈接器的標(biāo)志。在這里,我們將 /DELAYLOAD:libDynamic.dll 添加到鏈接標(biāo)志中。這個(gè)標(biāo)志用于指示鏈接器啟用 DLL 延遲加載機(jī)制。
延遲加載機(jī)制允許程序在運(yùn)行時(shí)按需加載 DLL,而不是在程序啟動(dòng)時(shí)立即加載。這可以降低程序啟動(dòng)時(shí)間,并且在某些情況下可以避免因缺少 DLL 導(dǎo)致的程序啟動(dòng)失敗。當(dāng)程序首次調(diào)用 DLL 中的函數(shù)時(shí),系統(tǒng)會(huì)自動(dòng)加載 DLL。/DELAYLOAD:libDynamic.dll 標(biāo)志告訴鏈接器,我們希望在運(yùn)行時(shí)延遲加載 libDynamic.dll。當(dāng)程序需要使用 libDynamic.dll 中的函數(shù)時(shí),才會(huì)加載這個(gè) DLL。
- 根據(jù)目標(biāo)架構(gòu)定制編譯選項(xiàng)和鏈接選項(xiàng)
# 根據(jù)目標(biāo)架構(gòu)定制編譯選項(xiàng)和鏈接選項(xiàng)
if(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
message("Building for Win32 (x86) architecture")
target_compile_options(MyApp PRIVATE /arch:SSE2)
elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "x64")
message("Building for x64 architecture")
target_compile_options(MyApp PRIVATE /arch:AVX2)
else()
message(WARNING "Unknown architecture")
endif()
這段代碼的目的是根據(jù)目標(biāo)架構(gòu)定制編譯選項(xiàng)和鏈接選項(xiàng)。具體來(lái)說(shuō),它根據(jù) CMAKE_GENERATOR_PLATFORM 變量的值來(lái)判斷目標(biāo)架構(gòu),并針對(duì)不同架構(gòu)設(shè)置不同的編譯選項(xiàng)。記得我們?cè)谳斎隿make命令行時(shí)的-A參數(shù)嗎?這里即是設(shè)置了這個(gè)變量的值。
cmake -G "Visual Studio 16 2019" -A Win32 ..
if(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")這個(gè)條件檢查 CMAKE_GENERATOR_PLATFORM變量是否等于 "Win32"。如果是,說(shuō)明我們正在為 Win32 (x86) 架構(gòu)構(gòu)建項(xiàng)目。target_compile_options(MyApp PRIVATE /arch:SSE2)告訴編譯器為 x86 架構(gòu)使用 SSE2 指令集。
cmake -G "Visual Studio 16 2019" -A x64 ..
若通過(guò)上面的CMake命令構(gòu)建x64任務(wù),則會(huì)走到elseif分支中,其中target_compile_options(MyApp PRIVATE /arch:AVX2)命令告訴編譯器為 x64 架構(gòu)使用 AVX2 指令集。
- 添加子項(xiàng)目
# 添加子項(xiàng)目
add_subdirectory(subproject)
使用 add_subdirectory 指令時(shí),CMake 會(huì)在指定的子目錄中查找 CMakeLists.txt 文件,并執(zhí)行其中的命令,讓 CMake 構(gòu)建系統(tǒng)繼續(xù)構(gòu)建子項(xiàng)目。這使得你可以將一個(gè)大型項(xiàng)目分解為多個(gè)較小的子項(xiàng)目,從而使項(xiàng)目的組織結(jié)構(gòu)更加清晰。
在我們的示例中,這個(gè)命令將導(dǎo)致 CMake 在構(gòu)建過(guò)程中進(jìn)入 subproject 目錄,并執(zhí)行 subproject/CMakeLists.txt 文件中的命令。子項(xiàng)目可以包含它自己的源文件、庫(kù)、可執(zhí)行文件等,并可以與主項(xiàng)目共享變量、目標(biāo)和屬性。
- 生成配置文件
# 在構(gòu)建時(shí)生成配置文件
configure_file(config.h.in config.h @ONLY)
configure_file(config.h.in config.h @ONLY) 命令的實(shí)際意義在于根據(jù)模板文件 config.h.in 生成配置文件 config.h。在這個(gè)過(guò)程中,CMake 會(huì)將模板文件中的一些變量替換為其實(shí)際值。這對(duì)于生成項(xiàng)目中使用的配置文件非常有用,特別是當(dāng)這些配置文件需要根據(jù)當(dāng)前構(gòu)建環(huán)境的某些屬性進(jìn)行調(diào)整時(shí)。
configure_file 命令的參數(shù)@ONLY表示只替換 @VARIABLE_NAME@ 形式的占位符。如果不使用這個(gè)選項(xiàng),CMake 會(huì)嘗試替換 ${VARIABLE_NAME} 形式的占位符。
舉個(gè)例子,假設(shè)你有一個(gè)項(xiàng)目,其中的一些功能取決于編譯時(shí)的選項(xiàng)。你可以使用 option() 命令定義這些選項(xiàng),并使用 configure_file() 命令將這些選項(xiàng)的值寫(xiě)入一個(gè)配置文件。然后,在源代碼中,你可以包含這個(gè)配置文件并根據(jù)選項(xiàng)值來(lái)啟用或禁用某些功能。
config.h.in 文件內(nèi)容示例:
#define ENABLE_FEATURE_X @ENABLE_FEATURE_X@
在 CMakeLists.txt 文件中,你可以使用以下命令為 ENABLE_FEATURE_X 定義一個(gè)可配置選項(xiàng),并生成 config.h 文件:
option(ENABLE_FEATURE_X "Enable feature X" ON)
configure_file(config.h.in config.h @ONLY)
在這個(gè)例子中,當(dāng) ENABLE_FEATURE_X 選項(xiàng)被設(shè)置為 ON 時(shí),生成的 config.h 文件將包含 #define ENABLE_FEATURE_X 1。當(dāng)選項(xiàng)設(shè)置為 OFF 時(shí),config.h 文件將包含 #define ENABLE_FEATURE_X 0。這樣,源代碼中就可以根據(jù) ENABLE_FEATURE_X 的值來(lái)啟用或禁用相關(guān)功能。
- 指定安裝規(guī)則
# 指定安裝規(guī)則
install(TARGETS MyApp RUNTIME DESTINATION bin)
install(FILES "${CMAKE_SOURCE_DIR}/libs/dynamic/libDynamic.dll" DESTINATION bin)
install 命令用于指定安裝規(guī)則,它定義了在構(gòu)建完成后如何將目標(biāo)文件(可執(zhí)行文件、庫(kù)等)以及其他相關(guān)文件(如動(dòng)態(tài)庫(kù)、配置文件等)安裝到指定的目錄。這對(duì)于將構(gòu)建好的項(xiàng)目打包成安裝包或?qū)㈨?xiàng)目部署到目標(biāo)系統(tǒng)上非常有用。在我們的例子中,這兩條 install 命令分別指定了將 MyApp 可執(zhí)行文件和 libDynamic.dll 動(dòng)態(tài)庫(kù)安裝到 bin 目錄下。
以下是這兩條 install 命令的詳細(xì)解釋?zhuān)?/p>
-
install(TARGETS MyApp RUNTIME DESTINATION bin)-
TARGETS MyApp:指定要安裝的目標(biāo),這里是MyApp可執(zhí)行文件。 -
RUNTIME:表示我們要安裝可執(zhí)行文件的運(yùn)行時(shí)組件。對(duì)于可執(zhí)行文件,這通常指的就是可執(zhí)行文件本身。 -
DESTINATION bin:指定安裝目標(biāo)的目錄。這里,MyApp可執(zhí)行文件將被安裝到bin目錄下。
-
-
install(FILES "${CMAKE_SOURCE_DIR}/libs/dynamic/libDynamic.dll" DESTINATION bin)-
FILES:表示我們要安裝的是文件,而不是目標(biāo)。在這個(gè)例子中,我們要安裝的文件是動(dòng)態(tài)庫(kù)libDynamic.dll。 -
"${CMAKE_SOURCE_DIR}/libs/dynamic/libDynamic.dll":指定要安裝的文件的路徑。${CMAKE_SOURCE_DIR}是一個(gè)變量,表示項(xiàng)目的根目錄。 -
DESTINATION bin:與第一個(gè)install命令相同,這里指定將libDynamic.dll安裝到bin目錄下。
-
總結(jié)
本文介紹了 CMake 的基本概念、語(yǔ)法和一些常用命令。我們了解了 CMake 的工作原理和如何編寫(xiě)一個(gè)簡(jiǎn)單的 CMakeLists.txt 文件來(lái)構(gòu)建一個(gè)包含可執(zhí)行文件和動(dòng)態(tài)庫(kù)的項(xiàng)目。本文通過(guò)一個(gè)示例,詳細(xì)解釋了常用的 CMake 命令的作用,包括 cmake_minimum_required、project、add_executable、add_library、target_link_libraries、include_directories、link_directories、find_package、find_library、find_file、configure_file 以及 install 命令,相信讀者以后會(huì)在開(kāi)發(fā)工作中經(jīng)常遇到。
通過(guò)閱讀本文,讀者應(yīng)該對(duì) CMake 有了一個(gè)基本的了解,并能夠編寫(xiě)簡(jiǎn)單的 CMake 腳本來(lái)構(gòu)建項(xiàng)目。CMake 是一個(gè)功能強(qiáng)大的構(gòu)建工具,學(xué)會(huì)使用它將有助于提高項(xiàng)目的構(gòu)建和部署效率。