<<Modern CMake>> 翻譯 2. CMake 基礎
<<Modern CMake>> 翻譯 2. CMake 基礎
最低版本
這是每個 CMakeLists.txt
檔案的第一行。CMakeLists.txt
是 CMake 所需的配置檔名稱:
cmake_minimum_required(VERSION 3.1)
我們來了解一點 CMake 語法。 命令名稱 cmake_minimum_required
不區分大小寫,因此通常的做法是使用小寫。1 這裡 VERSION 是該命令所需的特殊關鍵字。 版本號緊跟在 VERSION 關鍵字之後。 與本書中的任何其他地方一樣,你只需單擊命令名稱即可連結到官方文件,然後可以使用下拉列表切換不同版本的 CMake 文件。
這一行很特別!2 版本號也同時指明瞭 CMake 的行為變化。 因此,如果你設定 minimum_required
為 VERSION 2.8
,在macOS上你就會獲得錯誤的連結行為, 例如,在最新的 CMake 版本中也是如此。 如果你把版本設定為 3.3 或更低,你會得到錯誤的符號隱藏行為,等等。 在 policies 有一個策略和版本列表。
在 CMake 3.12 中,可以這樣寫來指定支援的 CMake 版本範圍,例如 VERSION 3.1...3.12
; 這意味著您最低支援 3.1,同時也測試過並支援到 3.12 的新策略。 這對於需要更好設定的使用者來說很不錯,並且由於語法上的技巧,它向後相容舊版本的 CMake(儘管實際執行 CMake 3.2-3.11 只會在此示例中設定 3.1 版本的策略)。 新版本的策略對於 macOS 和 Windows 使用者來說往往是最重要的,他們通常也有最新版本的 CMake。
新專案應該這樣寫:
cmake_minimum_required(VERSION 3.1...3.15)
if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()
如果 CMake 版本小於 3.12,則 if 塊將為 true,並且策略將設定為當前 CMake 版本。 如果 CMake 為 3.12 或更高,if 塊將為 false,此時新語法 cmake_minimum_required
警告:MSVC 的 CMake 伺服器模式最初在讀取此格式時有一個 bug, 因此如果您需要支援舊版 MSVC 的非命令列 Windows 版本,則需要執行以下操作:
cmake_minimum_required(VERSION 3.1)
if(${CMAKE_VERSION} VERSION_LESS 3.15)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.15)
endif()
如果您確實需要在此處設定較低的版本,則可以使用 cmake_policy
有條件地增加策略級別或設定特定策略。 請至少為您的 macOS 使用者執行此操作!
設定專案
現在,每個頂級 CMake 檔案(CMakeLists.txt
)都有下面這一行:
project(MyProject VERSION 1.0
DESCRIPTION "非常出色的專案"
LANGUAGES CXX)
現在我們看到了更多的新語法。 字串被引號包圍起來,空格可多可少,專案名稱是第一個引數(位置引數)。 這裡的所有的關鍵字引數都是可選的。 通過 VERSION
引數,這會設定了一堆變數,比如 MyProject_VERSION
和 PROJECT_VERSION
。 LANGUAGE
可以是 C,CXX,Fortran 和 CUDA(CMake 3.7+)。 C CXX 是預設值。 在 CMake 3.9 中,DESCRIPTION 新增進來設定對專案的描述。 你可以參考 project
這個文件。
你可以通過使用 #
開頭來新增 註釋。 CMake 也有註釋的內聯語法,但很少需要,因為空格並不重要。
專案名稱沒什麼特別之處。此時不新增任何目標。
生成可執行檔案
雖然連結庫更有趣,通常我們大部分時間都在生成連結庫,但這裡我們要從一個簡單的可執行檔案開始。
add_executable(one two.cpp three.h)
這裡有幾件要說明的事情。 one
是生成的可執行檔案的名稱,同時也是建立的 CMake 目標的名稱(我保證你會很快聽到很多關於目標的資訊)。 可執行檔名稱後緊接的是原始檔列表,您可以根據需要列出任意數量的原始檔列表。 CMake 很聰明,會根據副檔名正確識別原始檔,所以列表中的標頭檔案會被 CMake 理解並忽略。 大多數時候,我們在原始檔列表中列出標頭檔案的唯一原因是讓它們出現在 IDE 中。 有關通用構建系統和目標的更多資訊,請參見 buildsystem。
生成連結庫
使用 add_library
生成連結庫, 也是非常簡單:
add_library(one STATIC two.cpp three.h)
您可以選擇連結庫型別:STATIC,SHARED 或 MODULE。 如果沒有設定,CMake 會根據變數 BUILD_SHARED_LIBS
的值在 STATIC 和 SHARED 之間選擇。
正如您將在下一節中看到的那樣,通常您需要建立一個偽目標,也就是一個不需要編譯任何檔案的目標,例如,對於僅包含標頭檔案的庫。這也可以稱為 INTERFACE 庫; 唯一的區別是介面庫不能跟檔名。
您還可以用一個現有的連結庫生成一個 ALIAS
連結庫,該庫簡單地為您提供目標的新名稱。這樣做的一個好處是你可以生成一個名稱中帶 ::
的連結庫(稍後會看到)。3
配置構建目標
現在我們已經指定了目標,然後我們怎麼給它新增相關資訊呢?例如,它可能需要一個 include
目錄:
target_include_directories(one PUBLIC include)
target_include_directories
將 include
目錄新增到目標. PUBLIC
對可執行檔案來說意義不大; 對於一個連結庫,它讓 CMake 知道連結到這個目標的任何目標也必須包含該目錄。 其他選項是 PRIVATE
(僅影響當前目標,而不影響依賴項)和 INTERFACE
(僅限依賴項所需)。
現在,我們可以把目標串聯起來:
add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
target_link_libraries
可能是 CMake 中最有用也最令人困惑的命令。 它需要一個target(another)並新增依賴目標項。 如果名為 one
的目標不存在,則它會新增指向路徑上的一個叫做 one
連結庫(即命令的名稱)。 或者你可以給它一個完整的連結庫路徑。或連結器標誌。 最後還有一點容易混淆的東西,那就是經典的 CMake 允許你忽略關鍵字 PUBLIC
,等等。 如果目標已經連結完成,嘗試在鏈中進一步混合樣式,你會收到錯誤。
主要關注在任何地方使用目標和關鍵字,這就對了。
目標可以包含目錄,連結庫(或連結目標),編譯選項,編譯定義,編譯特徵(參見 C++11 章節)等。 正如您將在兩個包括專案章節中看到的那樣,您通常可以使用目標(並始終制作目標)來表示所有你使用的連結庫。 即使那不是真正的連結庫的,比如 OpenMP,也可以用目標來表示。 這就是現代 CMake 很棒的原因!
開始動手實踐
看看您是否可以理解以下檔案操作。 它建立了一個簡單的 C++11 連結庫和一個使用它的程式。 沒有依賴。 我稍後將使用 CMake 3.8 系統討論更多 C++ 標準選項。
cmake_minimum_required(VERSION 3.8)
project(Calculator LANGUAGES CXX)
add_library(calclib STATIC src/calclib.cpp include/calc/lib.hpp)
target_include_directories(calclib PUBLIC include)
target_compile_features(calclib PUBLIC cxx_std_11)
add_executable(calc apps/calc.cpp)
target_link_libraries(calc PUBLIC calclib)
1. 在這本書中,我將盡可能避免向你展示錯誤的做事方式; 你可以在網上找到很多這方面的例子。我偶爾會提到替代做法,但除非絕對必要,否則不推薦這些。通常它們只是幫助您閱讀較老的 CMake 程式碼。 ↩
2. 你有時會看到 FATAL_ERROR
, 在 CMake <2.6 版本中,需要使用它來支援失敗,現在已經不需要了。↩
3.::
語法最初用來生成INTERFACE IMPORTED
連結庫, 但是,正因為如此,大多數target_*
命令都不適用於IMPORTED
連結庫。這使得它們很難自行設定。所以現在不要使用IMPORTED
關鍵字,請使用ALIAS
構件目標; 這在你匯出目標前都能正常工作。此限制已經在 CMake 3.11 中修復。 ↩