CMake 教程 Step by Step
本教程涉及的源碼可在CMake源碼的Help/guide/tutorial目錄中找到驱还,每個步驟對應(yīng)一個以該步驟命名的目錄号枕,可以用這些目錄作為各個步驟的起始點啰扛。
基本起始點 (Step1)
一個最基本的項目:從源文件構(gòu)建可執(zhí)行程序。
在Step1
目錄中創(chuàng)建一個CMakeLists.txt
文件:
cmake_minimum_required(VERSION 3.10)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cxx)
tutorial.cxx
可在Step1
目錄中找到窿锉,它實現(xiàn)了計算平方根氮唯。
添加項目版本號和配置頭文件
首先,修改CMakeLists.txt
文件良风,設(shè)置項目版本號:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
然后谊迄,設(shè)置配置一個頭文件,用于接收版本號:
configure_file(TutorialConfig.h.in TutorialConfig.h)
在二進制樹(binary tree)中將會生成TutorialConfig.h
頭文件烟央,因此我們需要將該目錄添加到頭文件搜索路徑中统诺,在CMakeLists.txt
的末尾添加:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
在源碼目錄中創(chuàng)建TutorialConfig.h.in
文件,包含如下內(nèi)容:
// 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++標準
讓我們給項目添加一些C++11特性。
修改atof
為std::stod
挺物,同時移除#include <cstdlib>
這一行:
const double inputValue = std::stod(argv[1]);
在CMake中懒浮,使用CMAKE_CXX_STANDARD
變量來指定C++版本。對于本例识藤,設(shè)置CMAKE_CXX_STANDARD
為11嵌溢,設(shè)置CMAKE_CXX_STANDARD_REQUIRED
為True:
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)
構(gòu)建、執(zhí)行
執(zhí)行下列命令構(gòu)建項目:
mkdir Step1_build
cd Step1_build
cmake ../Step1
cmake --build .
執(zhí)行程序:
$ ./Tutorial 300
The square root of 300 is 17.3205
$ ./Tutorial
./Tutorial Version 1.0
Usage: ./Tutorial number
添加庫 (Step2)
現(xiàn)在要給項目添加庫(library)蹋岩。在庫中實現(xiàn)平方根函數(shù)赖草,可執(zhí)行程序使用庫中的平方根函數(shù)替代標準庫中的。
我們把庫的實現(xiàn)放在MathFunctions
子目錄中剪个,包括一個頭文件MathFunctions.h
和一個源文件mysqrt.cxx
秧骑。
在MathFunctions
目錄中添加CMakeLists.txt
文件:
add_library(MathFunctions mysqrt.cxx)
為了使MathFunctions
中的CMakeLists.txt
在構(gòu)建的時候被執(zhí)行,需要在頂層的CMakeLists.txt
中調(diào)用add_subdirectory
添加庫到可執(zhí)行程序扣囊,添加MathFunctions
目錄到頭文件搜索路徑乎折。CMakeLists.txt
的最后幾行應(yīng)該是這樣:
# 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"
)
現(xiàn)在,我們把MyFunctions
庫設(shè)置為可選的侵歇。對于本教程這么簡單的例子骂澄,其實沒有必要這樣做,但對于一個很大的項目惕虑,經(jīng)常會這么做坟冲。第一步是在頂層CMakeLists.txt
中添加一個選項(option)。
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健提。用戶設(shè)置的值會保存在緩存(cache)中,再次運行cmake命令時不必再次指定伟叛。
下一步私痹,需要使編譯和連接MathFunctions
庫成為條件觸發(fā)的,通過更改頂層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
用來保存需要連接進可執(zhí)行程序的可選庫统刮。變量EXTRA_INCLUDES
用來保存可選的頭文件搜索路徑紊遵。這是處理可選組件的經(jīng)典方法,我們將在下一步使用新式的方法侥蒙。
需要對源代碼也進行相應(yīng)的修改暗膜,首先,在tutorial.cxx
中包含MathFunctions.h
:
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
然后辉哥,在同一個文件中桦山,使用USE_MYMATH
來控制調(diào)用的是哪個平方根函數(shù):
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
因為源文件里面使用了USE_MYMATH
宏攒射,所以我們需要在TutorialConfig.h.in
中添加這一行:
#cmakedefine USE_MYMATH
運行以下命令編譯可執(zhí)行程序:
mkdir Step1_build
cd Step1_build
cmake -DUSE_MYMATH=ON ../Step1
cmake --build .
watermark: this document is translated by xianchen.peng
為庫添加使用要求 (Step3)
使用要求庫對庫和可執(zhí)行程序的連接、包含命令行提供了更好的控制恒水,也使CMake內(nèi)傳遞目標屬性更加可控会放。會影響到使用要求的主要命令有:
target_compile_definitions
target_compile_options
target_include_directories
target_link_libraries
讓我們通過使用要求來重構(gòu)Step2
中的代碼。首先要說明一下钉凌,任何使用MathFunctions
的實體都需要包含其代碼目錄咧最,但是MathFunctions
自己不需要。這個概念被稱作INTERFACE
使用要求御雕。
INTERFACE
是指消費者需要矢沿、但生產(chǎn)者不需要的那些東西。在MathFunctions/CMakeLists.txt
最后添加:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
至此酸纲,我們已經(jīng)為MathFunctions
添加了使用要求捣鲸,我們可以安全地刪除頂層CMakeLists.txt
中對EXTRA_INCLUDES
的使用,這里:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
## list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions") ## 刪除這行
endif()
和這里:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
## ${EXTRA_INCLUDES} ## 刪除這行
)
做完了上面的事情之后闽坡,就可以執(zhí)行cmake配置栽惶、構(gòu)建項目啦。
安裝和測試 (Step4)
現(xiàn)在疾嗅,我們將向項目中添加安裝規(guī)則和測試支持外厂。
安裝規(guī)則
對于MathFunctions
,我們希望安裝庫文件和頭文件代承,對于應(yīng)用程序我們希望安裝可執(zhí)行文件和配置頭文件汁蝶。
所以,在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
)
這就是創(chuàng)建一個基本的本地安裝需要做的全部工作论悴。
然后掖棉,運行cmake配置、構(gòu)建項目意荤,通過命令cmake --install
來執(zhí)行安裝啊片,將會安裝合適的頭文件、庫玖像、可執(zhí)行文件。
測試支持
現(xiàn)在齐饮,讓我們測試應(yīng)用程序捐寥。在頂層CMakeLists.txt
的最后啟用測試,并且添加一些基本的測試來驗證應(yīng)用程序是否工作正確祖驱。
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")
第一個測試簡單的測試應(yīng)用程序能夠運行握恳,不會發(fā)生斷錯誤或其他崩潰,并且返回0捺僻。這是CTest的基本形式乡洼。
下一個測試通過使用PASS_REGULAR_EXPRESSION
屬性來驗證應(yīng)用程序的輸出崇裁。在這里,驗證當輸入錯誤的參數(shù)數(shù)量時應(yīng)用程序能夠輸出使用說明束昵。
最后拔稳,定義了一個名為do_test
的函數(shù),該函數(shù)運行應(yīng)用程序并且驗證輸出的平方根與給定的結(jié)果相同锹雏。
每個do_test
調(diào)用通過參數(shù)來指定測試的程序名稱巴比、輸入、和期望輸出的結(jié)果礁遵,每個do_test
調(diào)用都會往項目中添加測試轻绞。
重新構(gòu)建項目,切換到二進制目錄佣耐,然后運行ctest -N
和ctest -vv
政勃。
添加系統(tǒng)內(nèi)省 (Step5)
讓我們在源碼中添加一些目標平臺可能不支持的特性。下面的這個例子兼砖,我們將會根據(jù)目標平臺是否支持log
和exp
函數(shù)來選擇是否包含一些代碼到項目中奸远。雖然幾乎所有的平臺都支持這兩個函數(shù),我們還是假設(shè)一下某個特殊的平臺沒有這兩個函數(shù)掖鱼。
如果平臺支持log
和exp
函數(shù)然走,我們將在mysqrt
函數(shù)中使用它們來計算平方根。我們首先在頂層CMakeLists.txt
中使用CheckSymboExists
模塊來測試函數(shù)的可用性戏挡。
include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
我們將使用兩個新的宏芍瑞,所以在TutorialConfig.h.in
添加定義:
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
修改mysqrt.cxx
使其包含cmath
,然后還是在這個文件中褐墅,我們根據(jù)平臺是否支持log
和exp
函數(shù)來提供一個替代的實現(xiàn)拆檬,使用下面的代碼(不要忘了在最終返回result之前輸入#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;
運行cmake配置、構(gòu)建程序妥凳,然后運行程序竟贯。你將會發(fā)現(xiàn)程序沒有使用log
和exp
,即使平臺實際上支持這兩個函數(shù)逝钥。很快屑那,我們意識到我們忘了在msqrt.cxx
中包含TutorialConfig.h
。
我們還需要更新MathFunctions/CMakeLists.txt
艘款,使得mysqrt.cxx
知道TutorialConfig.h
這個文件的位置:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
再次配置持际、構(gòu)建程序,運行哗咆。如果log
和exp
函數(shù)任然沒有被使用蜘欲,查看生成的TutorialConfig.h
文件,可能它們在當前的系統(tǒng)上確實不支持晌柬。
指定編譯時宏定義
如果不使用TutorialConfig.h
來存放HAVE_LOG
和HAVE_EXP
姥份,是否有其他更好的方式呢郭脂?答案是:使用target_compile_definitions
。
首先澈歉,刪除TutorialConfig.h.in
中的HAVE_LOG
和HAVE_EXP
定義展鸡,不再需要在mysqrt.cxx
中包含TutorialConfig.h
了,也不需要在MathFunctions/CMakeLists.txt
中指定該頭文件的路徑了闷祥。
然后娱颊,移動頂層CMakeLists.txt
中檢查HAVE_LOG
和HAVE_EXP
的部分到MathFunctions/CMakeLists.txt
中,然后指定這些值為PRIVATE
編譯時宏定義凯砍。
include(CheckSymbolExists)
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_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
在做了這些修改之后箱硕,重新構(gòu)建項目。運行程序悟衩,發(fā)現(xiàn)結(jié)果與前面的相同剧罩。
添加自定義命令、生成文件 (Step6)
假如我們根本就不想使用平臺自帶的log
和exp
函數(shù)座泳,而是想通過預(yù)先生成的一個數(shù)據(jù)表來計算平方根惠昔。在這里,我們將在構(gòu)建的過程中生成這個表格挑势,然后把這個表格編譯到最終的程序中镇防。
首先,讓我們移除MathFunctions/CMakeLists.txt
中檢查log
和exp
的部分潮饱,然后移除mysqrt.cxx
中檢查HAVE_LOG
和HAVE_EXP
宏的代碼来氧,同時,也移除mysqrt.cxx
中#include <cmath>
香拉。
在MathFunctions
目錄中啦扬,提供了一個用來生成該表格的源碼文件MakeTable.cxx
。
通過分析該源代碼凫碌,可以發(fā)現(xiàn)該源碼用于生成數(shù)據(jù)表扑毡,輸出的文件名稱通過命令行參數(shù)來指定。
下一步盛险,讓我們在MathFunctions/CMakeLists.txt
中添加合適的命令來編譯MakeTable瞄摊,并在構(gòu)建項目的過程中運行這個可執(zhí)行文件。
首先苦掘,在MathFunctions/CMakeLists.txt
的頂部泉褐,添加一個命令用于編譯可執(zhí)行文件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
,通過在MathFunctions的源文件列表中添加Table.h
來實現(xiàn)挺邀。
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
我們還需要把當前二進制目錄添加到包含路徑中揉忘,使得Table.h
可以被搜索到跳座。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
現(xiàn)在,我們在mysqrt.cxx
中使用Table.h
泣矛。首先疲眷,在mysqrt.cxx
中包含Table.h
,然后更改mysqrt函數(shù)的實現(xiàn):
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重新配置您朽、構(gòu)建項目狂丝。
當項目構(gòu)建時,首先會構(gòu)建MakeTable
程序哗总,然后運行MakeTable
生成Table.h
几颜,最后編譯mysqrt.cxx
生成MathFunctions庫。
好了讯屈,請運行Tutorial程序蛋哭,驗證一下它是否是使用數(shù)據(jù)表來計算平方根的。
生成一個安裝器 (Step7)
假如我們想把項目發(fā)布給其他人使用涮母,我們想在多個不同平臺同時提供二進制和源碼包谆趾,這和我們在“Step4:安裝和測試”中所做的事情不太一樣。在這個例子中叛本,我們將構(gòu)建二進制安裝包沪蓬,支持包管理。我們將使用CPack來創(chuàng)建特定平臺的安裝器来候。需要在頂層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)
以上就是所有需要做的事情,現(xiàn)在解釋一下各行的含義吠勘。最開始的include(InstallRequiredSystemLibraries)
性芬,這個模塊將會把項目在當前平臺上需要的運行時庫包含進來。然后我們設(shè)置了一些CPack變量:在哪里保存License文件以及項目版本號剧防。請預(yù)先將License.txt文件放置到項目的頂層目錄中植锉,版本號的值之前的步驟中我們已經(jīng)設(shè)置過了。最后峭拘,我們包含CPack模塊俊庇,這個模塊將會使用我們設(shè)置的CPack變量以及其他一些當前系統(tǒng)的屬性來生成一個安裝器。
下一步就是構(gòu)建項目鸡挠,然后運行cpack生成給一個二進制發(fā)布包:
cpack
如果想指定生成安裝包的格式辉饱,使用-G選項。對于有多種配置的構(gòu)建拣展,使用-C來指定打包哪一種構(gòu)建彭沼。例如:
cpack -G ZIP -C Debug
如果想創(chuàng)建一個源碼發(fā)布包,輸入:
cpack --config CPackSourceConfig.cmake
運行生成的安裝器备埃,運行安裝好的應(yīng)用程序來驗證是否安裝成功。
添加Dashboard支持 (Step8)
我們在Step4中定義了一些測試,現(xiàn)在我們需要運行這些測試并且把結(jié)果提交到dashboard拐纱。為了支持dashboards我們需要在頂層CMakeLists.txt
中添加CTest模塊。
替換:
# enable testing
enable_testing()
為:
# enable dashboard scripting
include(CTest)
CTest模塊會自動調(diào)用enable_testing()
敦冬,所以我們可以從CMakeLists文件中刪除它。
我們還需要在頂層目錄中創(chuàng)建一個CTestConfig.cmake
文件唯沮,在這個文件中指定項目的名稱和提交dashboard到哪里去脖旱。
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會在運行的時候讀這個文件,為了創(chuàng)建一個簡單的dashboard介蛉,你需要運行cmake配置項目萌庆,但不要構(gòu)建它。然后在二進制目錄中甘耿,運行:
ctest [-VV] -D Experimental
對于多配置的生成器踊兜,配置類型必須指定:
ctest [-VV] -C Debug -D Experimental
CTest將會構(gòu)建并且測試項目,然后提交測試結(jié)果到Kitware公共dashboard佳恬。測試結(jié)果將被提交到Kitware’s公共dashboard的這個地址:https://my.cdash.org/index.php?project=CMakeTutorial.
融合動態(tài)庫和靜態(tài)庫 (Step9)
在這一節(jié)將介紹如何使用BUILD_SHARED_LIBS
變量來控制add_library
的行為和未指明類型的library如何構(gòu)建捏境。
我們需要在頂層CMakeLists.txt
中添加BUILD_SHARED_LIBS
,并且使用option
命令毁葱,這樣用戶能夠選擇是否啟用BUILD_SHARED_LIBS
垫言。
然后我們修改MathFunctions,使它變成一個封裝了使用mysqrt
或sqrt
的庫倾剿,而不是由調(diào)用者來決定使用哪個函數(shù)筷频。這意味著USE_MYMATH
將不再用于控制MathFunctions的構(gòu)建,但仍然會用于控制這個庫的行為前痘。
第一步是更改頂層CMakeLists.txt
的開始部分如下:
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)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
我們已經(jīng)使MathFunctions始終會被用到凛捏,現(xiàn)在我們需要更新這個庫的邏輯。在MathFunctions/CMakeLists.txt
中我們需要創(chuàng)建一個根據(jù)USE_MYMATH
條件構(gòu)建的SqrtLibrary庫芹缔。在本教程中坯癣,我們指定SqrtLibrary
作為靜態(tài)庫構(gòu)建。
最終的結(jié)果是MathFunctions/CMakeLists.txt
內(nèi)容如下:
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# library that just does sqrt
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# state that we depend on our binary dir to find Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
# install rules
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
然后最欠,更新MathFunctions/mysqrt.cxx
使用MathFunctions.h
頭文件和detail
命名空間:
#include <iostream>
#include "MathFunctions.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
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;
}
}
}
我們需要對tutorial.cxx
做些改變示罗,它不再使用USE_MYMATH
:
始終包含
MathFunctions.h
始終使用
mathfunctions::sqrt
不需要包含
cmath
最后,我們更新MathFunctions/MathFunctions.h
芝硬,對dll符號導(dǎo)出:
#if defined(_WIN32)
# if defined(EXPORTING_MYMATH)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double x);
}
這時蚜点,如果你構(gòu)建項目,將會發(fā)現(xiàn)連接失敗拌阴,因為我們將一個非代碼位置無關(guān)的靜態(tài)庫與一個代碼位置無關(guān)的庫連接绍绘。解決方案就是設(shè)置SqrtLibrary
庫的POSITION_INDEPENDENT_CODE
屬性為True,不論這個庫是什么構(gòu)建類型。
# state that SqrtLibrary need PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
添加生成器表達式 (Step10)
生成器表達式在構(gòu)建過程中計算脯倒,用來指定構(gòu)建配置信息实辑。
生成表達式可以用到很多target屬性上,比如:LINK_LIBRARIES
藻丢、INCLUDE_DIRECTORIES
、COMPILE_DEFINITIONS
等等摄乒,他們也可以用在給這些屬性添加值的命令上悠反,比如:target_link_libraries()
、target_include_directories()
馍佑、target_complie_definitions()
等等斋否。
生成器表達式可以用來進行條件連接、條件宏定義拭荤、條件頭文件路徑等茵臭。條件可以基于構(gòu)建配置、target屬性舅世、平臺信息旦委、以及很多其他可以查詢的信息。
有不同類型的生成器表達式雏亚,包括:邏輯表達式缨硝、信息表達式、輸出表達式罢低。
邏輯表達式用來創(chuàng)建條件輸出查辩,最基本的表達式是 0 和 1表達式。一個$<0:...>
結(jié)果是一個空字符串网持,<1:...>
結(jié)果是字符串"..."宜岛。他們可以嵌套。
生成器表達式通常用來條件添加編譯標志功舀,比如語言級別的警告標志萍倡。一個好模式是把這些信息關(guān)聯(lián)到INTERFACE
目標上,使得這些信息可以傳遞日杈。讓我們構(gòu)建一個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
生成器表達式來控制哪些標志會添加到給定的語言和編譯器:
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>>"
)
我們發(fā)現(xiàn)涨冀,警告標志被封裝在BUILD_INTERFACE
表達式里面填硕。這樣做可以使得安裝我們項目的消費者不會繼承這些警告標志。
添加導(dǎo)出配置 (Step11)
在Step4我們?yōu)轫椖刻砑恿税惭b庫和頭文件的能力,在Step7我們?yōu)轫椖刻砑恿税l(fā)布部署包的能力扁眯。
下一步壮莹,我們將添加一些必要信息使得其他CMake項目可以通過構(gòu)建目錄、本地安裝或安裝包來使用我們的項目姻檀。
首先更新install(TARGETS)
命令命满,不僅指定DESTINATION
,還指定EXPORT
绣版。EXPORT
關(guān)鍵字生成一個CMake文件到安裝目錄胶台,該CMake文件中的代碼從安裝目錄導(dǎo)入目標。讓我們更新MathFunctions/CMakeLists.txt
文件中的instal
命令杂抽,導(dǎo)出MathFunctions這個庫:
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
現(xiàn)在诈唬,MathFunctions庫已經(jīng)被導(dǎo)出了,我們還需要明確安裝生成的MathFunctionsTargets.cmake
文件缩麸,在頂層CMakeLists.txt
文件底下添加:
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
這時嘗試運行CMake铸磅,如果一切都設(shè)置正確,你將會看到CMake產(chǎn)生一個錯誤:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
造成這個錯誤的原因是杭朱,在生成導(dǎo)出信息的過程中導(dǎo)出了一個當前機器的路徑阅仔,這個路徑在其他機器上可能是無效的。解決方案是痕檬,改變target_include_directories
MathFunctions霎槐,使得它明白當在構(gòu)建目錄內(nèi)使用和通過一個安裝包使用時,需要的是不同的INTERFACE
位置梦谜。也就是說丘跌,需要把target_include_directories
MathFunctions轉(zhuǎn)換為這樣:
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
更新之后,我們可以再次運行CMake唁桩,它不會再報錯了闭树。
現(xiàn)在,CMake已經(jīng)能正確地打包需要的目標信息了荒澡,但我們?nèi)匀恍枰梢粋€MathFunctionsConfig.cmake
使得CMakefind_package
命令能找到我們的項目报辱。讓我們繼續(xù),添加一個Config.cmake.in
文件到項目的頂層目錄单山,內(nèi)容如下:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
然后碍现,為了能正確地配置和安裝這個文件,需要添加以下內(nèi)容到頂層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
)
這是米奸,我們已經(jīng)生成了一個可以在項目安裝或打包以后可用的可重定位CMake配置昼接,如果希望項目同時可以在一個構(gòu)建目錄中使用,我們只需要將以下內(nèi)容添加到頂層CMakeLists.txt
的最后:
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
通過這個導(dǎo)出項悴晰,我們可以生成一個Targets.cmake
慢睡,使得在構(gòu)建目錄中生成的MathFunctionsConfig.cmake
可以被其他項目使用逐工,并不需要安裝。
導(dǎo)入一個CMake工程 (消費者)
這個例子展示了一個項目如何發(fā)現(xiàn)其他生成Config.cmake
文件的CMake包漂辐,同時也展示了在生成一個Config.cmake
的時候如何聲明項目的外部依賴泪喊。
打包Debug和Release (MultiPackage)
缺省情況下,一個構(gòu)建目錄中只包含一個配置:Debug髓涯、Release袒啼、MinSizeRel、或RelWithDebInfo复凳。
但是可以設(shè)置一個項目包含多個配置瘤泪,可以在一次構(gòu)建包的過程中打包多個構(gòu)建目錄。
首先需要建立一個目錄multi_config
育八,里面包含了將被打包在一起的所有構(gòu)建。
然后在multi_config
目錄下創(chuàng)建debug
和release
目錄赦邻。最終的目錄布局像這樣:
─ multi_config
├── debug
└── release
現(xiàn)在我們需要設(shè)置debug和release構(gòu)建髓棋,大致如同下面的:
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ../../MultiPackage/
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ../../MultiPackage/
cmake --build .
cd ..
現(xiàn)在debug和release構(gòu)建都完成了,我們可以使用一個自定義的MultiCPackConfig.cmake
文件把這兩個構(gòu)建打包到一個發(fā)布中:
cpack --config ../../MultiPackage/MultiCPackConfig.cmake