一個(gè)簡(jiǎn)單例子,完全入門(mén)CMake語(yǔ)法與CMakeList編寫(xiě)

背景

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)包括:

  1. 跨平臺(tái)構(gòu)建:CMake支持多種操作系統(tǒng),包括Windows、Linux、macOS等。學(xué)會(huì)使用CMake可以讓你輕松地為不同平臺(tái)生成構(gòu)建文件,提高項(xiàng)目的可移植性。

  2. 編譯器和構(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)建腳本。

  3. 簡(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)。

  4. 便于協(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)目。

  5. 開(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ā)生混淆:

  1. 在命令行中輸入的 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)目。

  2. 編寫(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è)部分組成:

  1. 注釋
    使用井號(hào)(#)開(kāi)頭的行是注釋行,會(huì)被 CMake 忽略。
# 這是一個(gè)注釋
  1. 變量
    在 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
  1. 控制結(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()
  1. 函數(shù)和宏
    你可以定義自己的函數(shù)和宏,它們有類(lèi)似的語(yǔ)法:
  • 函數(shù):
function(FUNCTION_NAME arg1 arg2)
  # ...
endfunction()
  • 宏:
macro(MACRO_NAME arg1 arg2)
  # ...
endmacro()
  1. 常用命令
    以下是一些常用的 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)包含了以下組成部分:

  1. 根目錄:包含主項(xiàng)目的 CMakeLists.txt 文件以及用于在構(gòu)建時(shí)生成配置文件的 config.h.in 文件。
  2. src:存放項(xiàng)目源代碼的目錄,這里只有一個(gè) main.cpp 文件作為示例。
  3. include:包含頭文件的目錄。這里有兩個(gè)子目錄,一個(gè)是 static_lib,包含靜態(tài)庫(kù)的頭文件 StaticLibHeader.h;另一個(gè)是 dynamic_lib,包含動(dòng)態(tài)庫(kù)的頭文件 DynamicLibHeader.h
  4. libs:存放靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的目錄。這里有兩個(gè)子目錄:staticdynamicstatic 目錄中包含一個(gè)名為 libStatic.lib 的靜態(tài)庫(kù),dynamic 目錄中包含一個(gè)名為 libDynamic.dll 的動(dòng)態(tài)庫(kù)以及其導(dǎo)入庫(kù) libDynamic.lib
  5. 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_DEBUGON 時(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>

  1. 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 目錄下。
  2. 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_requiredproject、add_executable、add_library、target_link_libraries、include_directories、link_directories、find_package、find_library、find_fileconfigure_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)建和部署效率。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容