CMake 教學¶
目錄
簡介¶
CMake 教學提供逐步指南,涵蓋 CMake 協助解決的常見建置系統問題。在一個範例專案中了解各種主題如何協同運作會非常有幫助。教學文件和範例的原始碼可以在 CMake 原始碼樹的 Help/guide/tutorial
目錄中找到。每個步驟都有自己的子目錄,其中包含可用作起點的程式碼。教學範例是循序漸進的,因此每個步驟都為前一個步驟提供完整的解決方案。
基本的起點(步驟 1)¶
最基本的專案是從原始碼檔案建置的可執行檔。對於簡單的專案,只需要三行的 CMakeLists.txt
檔案。這將是我們教學的起點。在 Step1
目錄中建立一個 CMakeLists.txt
檔案,如下所示:
cmake_minimum_required(VERSION 3.10)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cxx)
請注意,此範例在 CMakeLists.txt
檔案中使用小寫指令。CMake 支援大寫、小寫和混合大小寫的指令。tutorial.cxx
的原始碼在 Step1
目錄中提供,可以用來計算數字的平方根。
加入版本號和設定檔頭檔¶
我們將加入的第一個功能是為我們的可執行檔和專案提供版本號。雖然我們可以完全在原始碼中執行此操作,但使用 CMakeLists.txt
可以提供更大的彈性。
首先,修改 CMakeLists.txt
檔案,使用 project
指令來設定專案名稱和版本號。
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
然後,設定一個標頭檔,將版本號傳遞給原始碼
configure_file(TutorialConfig.h.in TutorialConfig.h)
由於設定的檔案將寫入二元樹,我們必須將該目錄新增到搜尋標頭檔的路徑清單中。將以下幾行新增到 CMakeLists.txt
檔案的末尾
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
使用您最愛的編輯器,在原始碼目錄中建立一個名為 TutorialConfig.h.in
的檔案,內容如下
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
當 CMake 設定此標頭檔時,@Tutorial_VERSION_MAJOR@
和 @Tutorial_VERSION_MINOR@
的值將被取代。
接下來修改 tutorial.cxx
以包含設定的標頭檔 TutorialConfig.h
。
最後,讓我們透過更新 tutorial.cxx
如下來印出可執行檔名稱和版本號
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
指定 C++ 標準¶
接下來,讓我們透過將 tutorial.cxx
中的 atof
取代為 std::stod
,在專案中加入一些 C++11 功能。同時,移除 #include <cstdlib>
。
const double inputValue = std::stod(argv[1]);
我們需要在 CMake 程式碼中明確聲明它應該使用正確的旗標。在 CMake 中啟用對特定 C++ 標準支援的最簡單方法是使用 CMAKE_CXX_STANDARD
變數。對於本教學,將 CMAKE_CXX_STANDARD
變數在 CMakeLists.txt
檔案中設定為 11,並將 CMAKE_CXX_STANDARD_REQUIRED
設定為 True。請確保在呼叫 add_executable
之前加入 CMAKE_CXX_STANDARD
宣告。
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
建置與測試¶
執行 cmake
可執行檔或 cmake-gui
來設定專案,然後使用您選擇的建置工具來建置它。
例如,從命令列我們可以導覽至 CMake 原始碼樹的 Help/guide/tutorial
目錄,並建立一個建置目錄
mkdir Step1_build
接下來,導覽至建置目錄並執行 CMake 以設定專案並產生原生建置系統
cd Step1_build
cmake ../Step1
然後呼叫該建置系統以實際編譯/連結專案
cmake --build .
最後,嘗試使用這些指令來使用新建立的 Tutorial
Tutorial 4294967296
Tutorial 10
Tutorial
加入函式庫(步驟 2)¶
現在,我們將為專案加入一個函式庫。這個函式庫將包含我們自己計算數字平方根的實作。然後,可執行檔可以使用此函式庫,而不是編譯器提供的標準平方根函式。
在本教學中,我們將把函式庫放在一個名為 MathFunctions
的子目錄中。此目錄已包含標頭檔 MathFunctions.h
和原始碼檔案 mysqrt.cxx
。原始碼檔案有一個名為 mysqrt
的函式,其功能與編譯器的 sqrt
函式類似。
將以下一行的 CMakeLists.txt
檔案新增到 MathFunctions
目錄
add_library(MathFunctions mysqrt.cxx)
若要使用新的函式庫,我們將在頂層 CMakeLists.txt
檔案中新增一個 add_subdirectory
呼叫,以便建置該函式庫。我們將新的函式庫新增至可執行檔,並新增 MathFunctions
作為包含目錄,以便可以找到 mysqrt.h
標頭檔。頂層 CMakeLists.txt
檔案的最後幾行現在應該如下所示
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
現在,讓我們讓 MathFunctions 函式庫成為可選的。雖然對於本教學來說,實際上沒有任何這樣做的必要,但對於較大的專案來說,這很常見。第一步是在頂層 CMakeLists.txt
檔案中新增一個選項。
option(USE_MYMATH "Use tutorial provided math implementation" ON)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
此選項將顯示在 cmake-gui
和 ccmake
中,預設值為 ON,使用者可以變更。此設定將儲存在快取中,以便使用者每次在建置目錄上執行 CMake 時都不需要設定該值。
下一個變更是有條件地建置和連結 MathFunctions 函式庫。若要執行此操作,我們將頂層 CMakeLists.txt
檔案的結尾變更為如下所示
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
請注意使用變數 EXTRA_LIBS
來收集稍後將連結到可執行檔的任何可選函式庫。變數 EXTRA_INCLUDES
也類似地用於可選的標頭檔。這是處理許多可選元件時的經典方法,我們將在下一步中介紹現代方法。
對應的原始碼變更非常簡單。首先,在 tutorial.cxx
中,如果我們需要,則包含 MathFunctions.h
標頭
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
然後,在同一個檔案中,讓 USE_MYMATH
控制要使用哪個平方根函式
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
由於原始碼現在需要 USE_MYMATH
,我們可以透過以下程式碼行將其新增到 TutorialConfig.h.in
#cmakedefine USE_MYMATH
練習:為什麼在 USE_MYMATH
的選項之後設定 TutorialConfig.h.in
很重要?如果我們反轉這兩個會發生什麼事?
執行 cmake
可執行檔或 cmake-gui
來設定專案,然後使用您選擇的建置工具來建置它。接著執行建置好的 Tutorial 可執行檔。
現在讓我們更新 USE_MYMATH
的值。最簡單的方法是使用 cmake-gui
或如果您在終端機中使用 ccmake
。或者,如果您想從命令列變更選項,請嘗試
cmake ../Step2 -DUSE_MYMATH=OFF
重新建置並再次執行教學程式。
哪個函數能提供較好的結果,sqrt 或 mysqrt?
為函式庫新增使用需求 (步驟 3)¶
使用需求允許更好地控制函式庫或可執行檔的連結和包含行,同時也更好地控制 CMake 內部目標的傳遞屬性。主要的使用需求命令為
讓我們重構來自新增函式庫 (步驟 2) 的程式碼,以使用現代 CMake 的使用需求方法。我們首先聲明任何連結到 MathFunctions 的都需要包含目前的原始碼目錄,而 MathFunctions 本身則不需要。因此這可以變成一個 INTERFACE
使用需求。
請記住 INTERFACE
表示使用者需要但產生者不需要的東西。將以下程式碼加入到 MathFunctions/CMakeLists.txt
的末尾
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
現在我們已經為 MathFunctions 指定了使用需求,我們可以安全地從頂層 CMakeLists.txt
中移除我們對 EXTRA_INCLUDES
變數的使用,在此處
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
以及此處
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
完成後,執行 cmake
可執行檔或 cmake-gui
來設定專案,然後使用您選擇的建置工具或從建置目錄使用 cmake --build .
來建置它。
安裝和測試 (步驟 4)¶
現在我們可以開始為我們的專案新增安裝規則和測試支援。
安裝規則¶
安裝規則非常簡單:對於 MathFunctions,我們想要安裝函式庫和標頭檔,而對於應用程式,我們想要安裝可執行檔和設定好的標頭。
因此,我們將以下程式碼加入到 MathFunctions/CMakeLists.txt
的末尾
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
並將以下程式碼加入到頂層 CMakeLists.txt
的末尾
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
這就是建立教學程式基本本地安裝所需的全部內容。
現在,執行 cmake
可執行檔或 cmake-gui
來設定專案,然後使用您選擇的建置工具來建置它。
接著從命令列使用 cmake
命令的 install
選項(在 3.15 中引入,較舊版本的 CMake 必須使用 make install
)來執行安裝步驟。對於多組態工具,請不要忘記使用 --config
引數來指定組態。如果使用 IDE,只需建置 INSTALL
目標即可。此步驟將安裝適當的標頭檔、函式庫和可執行檔。例如
cmake --install .
CMake 變數 CMAKE_INSTALL_PREFIX
用於判斷檔案將安裝到的根目錄。如果使用 cmake --install
命令,可以使用 --prefix
引數覆寫安裝前置詞。例如
cmake --install . --prefix "/home/myuser/installdir"
導覽至安裝目錄並驗證安裝的 Tutorial 是否可以執行。
測試支援¶
接下來讓我們測試我們的應用程式。在頂層 CMakeLists.txt
檔案的末尾,我們可以啟用測試,然後新增一些基本測試,以驗證應用程式是否正常運作。
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
第一個測試只是驗證應用程式是否可以執行,不會發生記憶體區段錯誤或其他崩潰,並且具有零傳回值。這是 CTest 測試的基本形式。
下一個測試使用 PASS_REGULAR_EXPRESSION
測試屬性,以驗證測試的輸出是否包含特定的字串。在此情況下,驗證在提供不正確的引數數量時是否印出使用訊息。
最後,我們有一個名為 do_test
的函數,它會執行應用程式並驗證計算出的平方根對於給定的輸入是否正確。對於每次呼叫 do_test
,都會根據傳遞的引數,將另一個具有名稱、輸入和預期結果的測試新增至專案中。
重新建置應用程式,然後 cd 到二進位目錄並執行 ctest
可執行檔:ctest -N
和 ctest -VV
。對於多組態產生器(例如 Visual Studio),必須指定組態類型。例如,若要以偵錯模式執行測試,請從建置目錄(而不是偵錯子目錄!)使用 ctest -C Debug -VV
。或者,從 IDE 建置 RUN_TESTS
目標。
新增系統內省 (步驟 5)¶
讓我們考慮在我們的專案中加入一些取決於目標平台可能不具有的功能的程式碼。在此範例中,我們將新增一些取決於目標平台是否具有 log
和 exp
函數的程式碼。當然幾乎每個平台都有這些函數,但為了本教學課程,假設它們並不常見。
如果平台具有 log
和 exp
,那麼我們將使用它們來計算 mysqrt
函數中的平方根。我們首先使用 MathFunctions/CMakeLists.txt
中的 CheckSymbolExists
模組來測試這些函數的可用性。在某些平台上,我們需要連結到 m 函式庫。如果最初找不到 log
和 exp
,請要求 m 函式庫並再次嘗試。
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
unset(HAVE_LOG CACHE)
unset(HAVE_EXP CACHE)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_link_libraries(MathFunctions PRIVATE m)
endif()
endif()
如果可以的話,使用 target_compile_definitions
來將 HAVE_LOG
和 HAVE_EXP
指定為 PRIVATE
編譯定義。
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
如果系統上有 log
和 exp
可用,那麼我們將使用它們來計算 mysqrt
函數中的平方根。將以下程式碼新增到 MathFunctions/mysqrt.cxx
中的 mysqrt
函數中(別忘了在返回結果之前加上 #endif
!)。
#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = exp(log(x) * 0.5);
std::cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std::endl;
#else
double result = x;
我們還需要修改 mysqrt.cxx
來包含 cmath
。
#include <cmath>
執行 cmake
可執行檔或 cmake-gui
來配置專案,然後使用您選擇的建置工具來建置它,並執行 Tutorial 可執行檔。
現在哪個函數的結果更好,sqrt 還是 mysqrt?
新增自訂命令和產生檔案(步驟 6)¶
假設,為了本教學的目的,我們決定永遠不使用平台 log
和 exp
函數,而是想要產生一個預先計算好的數值表,以便在 mysqrt
函數中使用。在本節中,我們將在建置過程中建立表格,然後將該表格編譯到我們的應用程式中。
首先,讓我們移除 MathFunctions/CMakeLists.txt
中對 log
和 exp
函數的檢查。然後從 mysqrt.cxx
中移除對 HAVE_LOG
和 HAVE_EXP
的檢查。同時,我們可以移除 #include <cmath>
。
在 MathFunctions
子目錄中,已提供一個名為 MakeTable.cxx
的新原始碼檔案,用於產生表格。
檢閱檔案後,我們可以發現該表格是以有效的 C++ 程式碼形式產生,並且輸出檔案名稱作為引數傳入。
下一步是將適當的命令新增到 MathFunctions/CMakeLists.txt
檔案中,以建置 MakeTable 可執行檔,然後將其作為建置過程的一部分執行。需要幾個命令才能完成此操作。
首先,在 MathFunctions/CMakeLists.txt
的頂部,像新增任何其他可執行檔一樣新增 MakeTable
的可執行檔。
add_executable(MakeTable MakeTable.cxx)
然後,我們新增一個自訂命令,該命令指定如何透過執行 MakeTable 來產生 Table.h
。
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
接下來,我們必須讓 CMake 知道 mysqrt.cxx
依賴於產生的檔案 Table.h
。這是透過將產生的 Table.h
新增到 MathFunctions 程式庫的來源清單中來完成的。
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
我們還必須將目前的二進位目錄新增到包含目錄清單中,以便 mysqrt.cxx
可以找到並包含 Table.h
。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
現在讓我們使用產生的表格。首先,修改 mysqrt.cxx
以包含 Table.h
。接下來,我們可以重寫 mysqrt 函數以使用表格
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
執行 cmake
可執行檔或 cmake-gui
來配置專案,然後使用您選擇的建置工具來建置它。
建置此專案時,它將首先建置 MakeTable
可執行檔。然後,它將執行 MakeTable
以產生 Table.h
。最後,它將編譯包含 Table.h
的 mysqrt.cxx
,以產生 MathFunctions 程式庫。
執行 Tutorial 可執行檔並驗證它是否正在使用表格。
建置安裝程式(步驟 7)¶
接下來,假設我們想要將我們的專案發佈給其他人,以便他們可以使用它。我們想要在各種平台上提供二進位和原始碼發佈。這與我們先前在 安裝和測試(步驟 4) 中所做的安裝略有不同,我們在那裡安裝的是我們從原始碼建置的二進位檔。在此範例中,我們將建置支援二進位安裝和套件管理功能的安裝套件。為了實現這一點,我們將使用 CPack 來建立特定於平台的安裝程式。具體來說,我們需要在頂層 CMakeLists.txt
檔案的底部新增幾行。
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)
這就是全部的內容。我們先包含 InstallRequiredSystemLibraries
。此模組將包含專案在目前平台上所需的任何執行時間程式庫。接下來,我們設定一些 CPack 變數,這些變數指示我們為此專案儲存授權和版本資訊的位置。版本資訊已在本教學稍早設定,而 license.txt
已包含在此步驟的頂層原始碼目錄中。
最後,我們包含 CPack 模組
,該模組將使用這些變數和目前系統的其他一些屬性來設定安裝程式。
下一步是以通常的方式建置專案,然後執行 cpack
可執行檔。若要建置二進位發佈,請從二進位目錄執行
cpack
若要指定產生器,請使用 -G
選項。對於多配置建置,請使用 -C
來指定配置。例如
cpack -G ZIP -C Debug
若要建立原始碼發佈,您將輸入
cpack --config CPackSourceConfig.cmake
或者,從 IDE 執行 make package
或右鍵按一下 Package
目標,然後按一下 Build Project
。
執行在二進位目錄中找到的安裝程式。然後執行已安裝的可執行檔並驗證其是否正常運作。
新增儀表板的支援(步驟 8)¶
新增將我們的測試結果提交到儀表板的支援很簡單。我們已經在 測試支援 中為我們的專案定義了許多測試。現在我們只需要執行這些測試並將它們提交到儀表板。若要包含對儀表板的支援,我們需要在頂層 CMakeLists.txt
中包含 CTest
模組。
將
# enable testing
enable_testing()
替換為
# enable dashboard scripting
include(CTest)
CTest
模組將自動呼叫 enable_testing()
,因此我們可以將其從我們的 CMake 檔案中移除。
我們還需要在頂層目錄中建立一個 CTestConfig.cmake
檔案,我們可以在其中指定專案的名稱和提交儀表板的位置。
set(CTEST_PROJECT_NAME "CMakeTutorial")
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
set(CTEST_DROP_SITE_CDASH TRUE)
當 ctest
執行檔執行時,會讀取此檔案。要建立一個簡單的儀表板,您可以執行 cmake
執行檔或 cmake-gui
來設定專案,但先不要建置它。請改為將目錄變更至二元樹,然後執行
ctest [-VV] -D Experimental
請記住,對於多組態產生器(例如 Visual Studio),必須指定組態類型
ctest [-VV] -C Debug -D Experimental
或者,從 IDE 中,建置 Experimental
目標。
ctest
執行檔將會建置並測試專案,並將結果提交到 Kitware 的公開儀表板:https://my.cdash.org/index.php?project=CMakeTutorial。
新增產生器表達式 (步驟 10)¶
產生器 表達式
在建置系統產生期間進行評估,以產生特定於每個建置組態的資訊。
產生器 表達式
允許用於許多目標屬性的內容中,例如 LINK_LIBRARIES
、 INCLUDE_DIRECTORIES
、COMPILE_DEFINITIONS
和其他屬性。它們也可用於在使用命令來填入這些屬性時,例如 target_link_libraries
、target_include_directories
、target_compile_definitions
和其他命令。
產生器 表達式
可用於啟用條件連結、編譯時使用的條件定義、條件包含目錄等。條件可能基於建置組態、目標屬性、平台資訊或任何其他可查詢的資訊。
有不同類型的 產生器 表達式
,包括邏輯、資訊和輸出表達式。
邏輯表達式用於建立條件輸出。基本表達式是 0 和 1 表達式。$<0:...>
會產生空字串,而 <1:...>
會產生「…」的內容。它們也可以巢狀。
產生器 表達式
的常見用法是有條件地新增編譯器旗標,例如語言層級或警告的旗標。一種很好的模式是將此資訊與 INTERFACE
目標相關聯,允許此資訊傳播。讓我們從建構 INTERFACE
目標並指定所需的 C++ 標準層級 11
開始,而不是使用 CMAKE_CXX_STANDARD
。
因此,以下程式碼
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
將被取代為
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
接下來,我們新增專案所需的編譯器警告旗標。由於警告旗標會因編譯器而異,因此我們使用 COMPILE_LANG_AND_ID
產生器表達式來控制要套用哪些旗標,給定語言和一組編譯器 ID,如下所示
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
查看此程式碼,我們看到警告旗標封裝在 BUILD_INTERFACE
條件內。這樣做是為了讓已安裝專案的使用者不會繼承我們的警告旗標。
練習:修改 MathFunctions/CMakeLists.txt
,使所有目標都具有對 tutorial_compiler_flags
的 target_link_libraries
呼叫。
新增匯出組態 (步驟 11)¶
在教學的安裝和測試 (步驟 4)中,我們新增了讓 CMake 安裝專案的程式庫和標頭的功能。在建置安裝程式 (步驟 7)中,我們新增了將此資訊封裝起來的功能,以便可以分發給其他人。
下一步是新增必要的資訊,以便其他 CMake 專案可以使用我們的專案,無論是從建置目錄、本機安裝還是封裝時。
第一步是更新我們的 install(TARGETS)
命令,不僅要指定 DESTINATION
,還要指定 EXPORT
。EXPORT
關鍵字會產生並安裝一個 CMake 檔案,其中包含從安裝樹匯入 install 命令中列出的所有目標的程式碼。因此,讓我們繼續並明確地 EXPORT
MathFunctions 函式庫,方法是更新 MathFunctions/CMakeLists.txt
中的 install
命令,使其看起來像這樣
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs}
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
現在我們已經匯出 MathFunctions,我們還需要明確安裝產生的 MathFunctionsTargets.cmake
檔案。這可以透過在頂層 CMakeLists.txt
的底部新增以下內容來完成
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
此時,您應該嘗試執行 CMake。如果一切設定正確,您會看到 CMake 會產生如下的錯誤
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
CMake 想要表達的是,在產生匯出資訊時,它會匯出一個本質上與目前機器相關的路徑,並且在其他機器上無效。解決方案是更新 MathFunctions target_include_directories
,使其理解從建置目錄和從安裝/套件使用時,需要不同的 INTERFACE
位置。這意味著將 MathFunctions 的 target_include_directories
呼叫轉換成如下所示
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
更新完成後,我們可以重新執行 CMake 並驗證它不再發出警告。
此時,我們已讓 CMake 正確封裝所需的目標資訊,但我們仍然需要產生一個 MathFunctionsConfig.cmake
,以便 CMake find_package
命令可以找到我們的專案。因此,讓我們繼續在專案的頂層新增一個名為 Config.cmake.in
的檔案,其內容如下
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
然後,為了正確設定和安裝該檔案,請在頂層 CMakeLists.txt
的底部新增以下內容
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/example"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# generate the version file for the config file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
# install the configuration file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
DESTINATION lib/cmake/MathFunctions
)
此時,我們已為我們的專案產生一個可重新定位的 CMake 設定,該設定可在專案安裝或封裝後使用。如果我們希望我們的專案也可以從建置目錄中使用,我們只需在頂層 CMakeLists.txt
的底部新增以下內容即可
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
透過此匯出呼叫,我們現在產生一個 Targets.cmake
,允許其他專案使用建置目錄中設定的 MathFunctionsConfig.cmake
,而無需安裝。
封裝偵錯和發行版本(步驟 12)¶
注意:此範例適用於單一設定產生器,不適用於多設定產生器(例如 Visual Studio)。
依預設,CMake 的模型是建置目錄只包含一個單一設定,無論是偵錯 (Debug)、發行 (Release)、最小大小發行 (MinSizeRel) 或帶偵錯資訊發行 (RelWithDebInfo)。但是,可以設定 CPack 來捆綁多個建置目錄,並建構一個包含同一專案多個設定的套件。
首先,我們要確保偵錯和發行版本針對將要安裝的可執行檔和函式庫使用不同的名稱。讓我們將 d 作為偵錯可執行檔和函式庫的後綴。
在頂層 CMakeLists.txt
檔案的開頭附近設定 CMAKE_DEBUG_POSTFIX
set(CMAKE_DEBUG_POSTFIX d)
add_library(tutorial_compiler_flags INTERFACE)
以及 tutorial 可執行檔上的 DEBUG_POSTFIX
屬性
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(Tutorial PUBLIC MathFunctions)
讓我們也為 MathFunctions 函式庫新增版本編號。在 MathFunctions/CMakeLists.txt
中,設定 VERSION
和 SOVERSION
屬性
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
從 Step12
目錄中,建立 debug
和 release
子目錄。佈局看起來會像這樣
- Step12
- debug
- release
現在我們需要設定偵錯和發行版本建置。我們可以使用 CMAKE_BUILD_TYPE
來設定組態類型
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
現在偵錯和發行版本建置都已完成,我們可以使用自訂設定檔將兩個版本建置封裝到單一發行版本中。在 Step12
目錄中,建立一個名為 MultiCPackConfig.cmake
的檔案。在此檔案中,首先包含由 cmake
可執行檔建立的預設設定檔。
接下來,使用 CPACK_INSTALL_CMAKE_PROJECTS
變數來指定要安裝的專案。在這種情況下,我們想要安裝偵錯和發行版本。
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)
從 Step12
目錄中,執行 cpack
,使用 config
選項指定我們的自訂設定檔
cpack --config MultiCPackConfig.cmake