CMake 教程 Step by Step
本教程涉及的源碼可在CMake源碼的Help/guide/tutorial目錄中找到,每個(gè)步驟對應(yīng)一個(gè)以該步驟命名的目錄,可以用這些目錄作為各個(gè)步驟的起始點(diǎn)。
基本起始點(diǎn) (Step1)
一個(gè)最基本的項(xiàng)目:從源文件構(gòu)建可執(zhí)行程序。
在Step1
目錄中創(chuàng)建一個(gè)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
目錄中找到,它實(shí)現(xiàn)了計(jì)算平方根。
添加項(xiàng)目版本號和配置頭文件
首先,修改CMakeLists.txt
文件,設(shè)置項(xiàng)目版本號:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
然后,設(shè)置配置一個(gè)頭文件,用于接收版本號:
configure_file(TutorialConfig.h.in TutorialConfig.h)
在二進(jìn)制樹(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@
當(dāng)CMake配置這個(gè)頭文件時(shí),將會替換@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++標(biāo)準(zhǔn)
讓我們給項(xiàng)目添加一些C++11特性。
修改atof
為std::stod
,同時(shí)移除#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)建項(xiàng)目:
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)在要給項(xiàng)目添加庫(library)。在庫中實(shí)現(xiàn)平方根函數(shù),可執(zhí)行程序使用庫中的平方根函數(shù)替代標(biāo)準(zhǔn)庫中的。
我們把庫的實(shí)現(xiàn)放在MathFunctions
子目錄中,包括一個(gè)頭文件MathFunctions.h
和一個(gè)源文件mysqrt.cxx
。
在MathFunctions
目錄中添加CMakeLists.txt
文件:
add_library(MathFunctions mysqrt.cxx)
為了使MathFunctions
中的CMakeLists.txt
在構(gòu)建的時(shí)候被執(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è)置為可選的。對于本教程這么簡單的例子,其實(shí)沒有必要這樣做,但對于一個(gè)很大的項(xiàng)目,經(jīng)常會這么做。第一步是在頂層CMakeLists.txt
中添加一個(gè)選項(xiàng)(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)
這個(gè)選項(xiàng)會在CMake GUI 和 ccmake顯示,默認(rèn)值是ON。用戶設(shè)置的值會保存在緩存(cache)中,再次運(yùn)行cmake命令時(shí)不必再次指定。
下一步,需要使編譯和連接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
用來保存需要連接進(jìn)可執(zhí)行程序的可選庫。變量EXTRA_INCLUDES
用來保存可選的頭文件搜索路徑。這是處理可選組件的經(jīng)典方法,我們將在下一步使用新式的方法。
需要對源代碼也進(jìn)行相應(yīng)的修改,首先,在tutorial.cxx
中包含MathFunctions.h
:
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
然后,在同一個(gè)文件中,使用USE_MYMATH
來控制調(diào)用的是哪個(gè)平方根函數(shù):
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
因?yàn)樵次募锩媸褂昧?code>USE_MYMATH宏,所以我們需要在TutorialConfig.h.in
中添加這一行:
#cmakedefine USE_MYMATH
運(yùn)行以下命令編譯可執(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)傳遞目標(biāo)屬性更加可控。會影響到使用要求的主要命令有:
target_compile_definitions
target_compile_options
target_include_directories
target_link_libraries
讓我們通過使用要求來重構(gòu)Step2
中的代碼。首先要說明一下,任何使用MathFunctions
的實(shí)體都需要包含其代碼目錄,但是MathFunctions
自己不需要。這個(gè)概念被稱作INTERFACE
使用要求。
INTERFACE
是指消費(fèi)者需要、但生產(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)建項(xiàng)目啦。
安裝和測試 (Step4)
現(xiàn)在,我們將向項(xiàng)目中添加安裝規(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)建一個(gè)基本的本地安裝需要做的全部工作。
然后,運(yùn)行cmake配置、構(gòu)建項(xiàng)目,通過命令cmake --install
來執(zhí)行安裝,將會安裝合適的頭文件、庫、可執(zhí)行文件。
測試支持
現(xiàn)在,讓我們測試應(yīng)用程序。在頂層CMakeLists.txt
的最后啟用測試,并且添加一些基本的測試來驗(yàn)證應(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")
第一個(gè)測試簡單的測試應(yīng)用程序能夠運(yùn)行,不會發(fā)生斷錯(cuò)誤或其他崩潰,并且返回0。這是CTest的基本形式。
下一個(gè)測試通過使用PASS_REGULAR_EXPRESSION
屬性來驗(yàn)證應(yīng)用程序的輸出。在這里,驗(yàn)證當(dāng)輸入錯(cuò)誤的參數(shù)數(shù)量時(shí)應(yīng)用程序能夠輸出使用說明。
最后,定義了一個(gè)名為do_test
的函數(shù),該函數(shù)運(yùn)行應(yīng)用程序并且驗(yàn)證輸出的平方根與給定的結(jié)果相同。
每個(gè)do_test
調(diào)用通過參數(shù)來指定測試的程序名稱、輸入、和期望輸出的結(jié)果,每個(gè)do_test
調(diào)用都會往項(xiàng)目中添加測試。
重新構(gòu)建項(xiàng)目,切換到二進(jìn)制目錄,然后運(yùn)行ctest -N
和ctest -vv
。
添加系統(tǒng)內(nèi)省 (Step5)
讓我們在源碼中添加一些目標(biāo)平臺可能不支持的特性。下面的這個(gè)例子,我們將會根據(jù)目標(biāo)平臺是否支持log
和exp
函數(shù)來選擇是否包含一些代碼到項(xiàng)目中。雖然幾乎所有的平臺都支持這兩個(gè)函數(shù),我們還是假設(shè)一下某個(gè)特殊的平臺沒有這兩個(gè)函數(shù)。
如果平臺支持log
和exp
函數(shù),我們將在mysqrt
函數(shù)中使用它們來計(jì)算平方根。我們首先在頂層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)
我們將使用兩個(gè)新的宏,所以在TutorialConfig.h.in
添加定義:
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
修改mysqrt.cxx
使其包含cmath
,然后還是在這個(gè)文件中,我們根據(jù)平臺是否支持log
和exp
函數(shù)來提供一個(gè)替代的實(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;
運(yùn)行cmake配置、構(gòu)建程序,然后運(yùn)行程序。你將會發(fā)現(xiàn)程序沒有使用log
和exp
,即使平臺實(shí)際上支持這兩個(gè)函數(shù)。很快,我們意識到我們忘了在msqrt.cxx
中包含TutorialConfig.h
。
我們還需要更新MathFunctions/CMakeLists.txt
,使得mysqrt.cxx
知道TutorialConfig.h
這個(gè)文件的位置:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
再次配置、構(gòu)建程序,運(yùn)行。如果log
和exp
函數(shù)任然沒有被使用,查看生成的TutorialConfig.h
文件,可能它們在當(dāng)前的系統(tǒng)上確實(shí)不支持。
指定編譯時(shí)宏定義
如果不使用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
編譯時(shí)宏定義。
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)建項(xiàng)目。運(yùn)行程序,發(fā)現(xiàn)結(jié)果與前面的相同。
添加自定義命令、生成文件 (Step6)
假如我們根本就不想使用平臺自帶的log
和exp
函數(shù),而是想通過預(yù)先生成的一個(gè)數(shù)據(jù)表來計(jì)算平方根。在這里,我們將在構(gòu)建的過程中生成這個(gè)表格,然后把這個(gè)表格編譯到最終的程序中。
首先,讓我們移除MathFunctions/CMakeLists.txt
中檢查log
和exp
的部分,然后移除mysqrt.cxx
中檢查HAVE_LOG
和HAVE_EXP
宏的代碼,同時(shí),也移除mysqrt.cxx
中#include <cmath>
。
在MathFunctions
目錄中,提供了一個(gè)用來生成該表格的源碼文件MakeTable.cxx
。
通過分析該源代碼,可以發(fā)現(xiàn)該源碼用于生成數(shù)據(jù)表,輸出的文件名稱通過命令行參數(shù)來指定。
下一步,讓我們在MathFunctions/CMakeLists.txt
中添加合適的命令來編譯MakeTable,并在構(gòu)建項(xiàng)目的過程中運(yùn)行這個(gè)可執(zhí)行文件。
首先,在MathFunctions/CMakeLists.txt
的頂部,添加一個(gè)命令用于編譯可執(zhí)行文件MakeTable
:
add_executable(MakeTable MakeTable.cxx)
然后,我們添加一些命令來指定如何運(yùn)行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
來實(shí)現(xiàn)。
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
我們還需要把當(dāng)前二進(jìn)制目錄添加到包含路徑中,使得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ù)的實(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;
}
運(yùn)行cmake重新配置、構(gòu)建項(xiàng)目。
當(dāng)項(xiàng)目構(gòu)建時(shí),首先會構(gòu)建MakeTable
程序,然后運(yùn)行MakeTable
生成Table.h
,最后編譯mysqrt.cxx
生成MathFunctions庫。
好了,請運(yùn)行Tutorial程序,驗(yàn)證一下它是否是使用數(shù)據(jù)表來計(jì)算平方根的。
生成一個(gè)安裝器 (Step7)
假如我們想把項(xiàng)目發(fā)布給其他人使用,我們想在多個(gè)不同平臺同時(shí)提供二進(jìn)制和源碼包,這和我們在“Step4:安裝和測試”中所做的事情不太一樣。在這個(gè)例子中,我們將構(gòu)建二進(jìn)制安裝包,支持包管理。我們將使用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)
,這個(gè)模塊將會把項(xiàng)目在當(dāng)前平臺上需要的運(yùn)行時(shí)庫包含進(jìn)來。然后我們設(shè)置了一些CPack變量:在哪里保存License文件以及項(xiàng)目版本號。請預(yù)先將License.txt文件放置到項(xiàng)目的頂層目錄中,版本號的值之前的步驟中我們已經(jīng)設(shè)置過了。最后,我們包含CPack模塊,這個(gè)模塊將會使用我們設(shè)置的CPack變量以及其他一些當(dāng)前系統(tǒng)的屬性來生成一個(gè)安裝器。
下一步就是構(gòu)建項(xiàng)目,然后運(yùn)行cpack生成給一個(gè)二進(jìn)制發(fā)布包:
cpack
如果想指定生成安裝包的格式,使用-G選項(xiàng)。對于有多種配置的構(gòu)建,使用-C來指定打包哪一種構(gòu)建。例如:
cpack -G ZIP -C Debug
如果想創(chuàng)建一個(gè)源碼發(fā)布包,輸入:
cpack --config CPackSourceConfig.cmake
運(yùn)行生成的安裝器,運(yùn)行安裝好的應(yīng)用程序來驗(yàn)證是否安裝成功。
添加Dashboard支持 (Step8)
我們在Step4中定義了一些測試,現(xiàn)在我們需要運(yùn)行這些測試并且把結(jié)果提交到dashboard。為了支持dashboards我們需要在頂層CMakeLists.txt
中添加CTest模塊。
替換:
# enable testing
enable_testing()
為:
# enable dashboard scripting
include(CTest)
CTest模塊會自動調(diào)用enable_testing()
,所以我們可以從CMakeLists文件中刪除它。
我們還需要在頂層目錄中創(chuàng)建一個(gè)CTestConfig.cmake
文件,在這個(gè)文件中指定項(xiàng)目的名稱和提交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會在運(yùn)行的時(shí)候讀這個(gè)文件,為了創(chuàng)建一個(gè)簡單的dashboard,你需要運(yùn)行cmake配置項(xiàng)目,但不要構(gòu)建它。然后在二進(jìn)制目錄中,運(yùn)行:
ctest [-VV] -D Experimental
對于多配置的生成器,配置類型必須指定:
ctest [-VV] -C Debug -D Experimental
CTest將會構(gòu)建并且測試項(xiàng)目,然后提交測試結(jié)果到Kitware公共dashboard。測試結(jié)果將被提交到Kitware’s公共dashboard的這個(gè)地址: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,使它變成一個(gè)封裝了使用mysqrt
或sqrt
的庫,而不是由調(diào)用者來決定使用哪個(gè)函數(shù)。這意味著USE_MYMATH
將不再用于控制MathFunctions的構(gòu)建,但仍然會用于控制這個(gè)庫的行為。
第一步是更改頂層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)在我們需要更新這個(gè)庫的邏輯。在MathFunctions/CMakeLists.txt
中我們需要?jiǎng)?chuàng)建一個(gè)根據(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);
}
這時(shí),如果你構(gòu)建項(xiàng)目,將會發(fā)現(xiàn)連接失敗,因?yàn)槲覀儗⒁粋€(gè)非代碼位置無關(guān)的靜態(tài)庫與一個(gè)代碼位置無關(guān)的庫連接。解決方案就是設(shè)置SqrtLibrary
庫的POSITION_INDEPENDENT_CODE
屬性為True,不論這個(gè)庫是什么構(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)
添加生成器表達(dá)式 (Step10)
生成器表達(dá)式在構(gòu)建過程中計(jì)算,用來指定構(gòu)建配置信息。
生成表達(dá)式可以用到很多target屬性上,比如:LINK_LIBRARIES
、INCLUDE_DIRECTORIES
、COMPILE_DEFINITIONS
等等,他們也可以用在給這些屬性添加值的命令上,比如:target_link_libraries()
、target_include_directories()
、target_complie_definitions()
等等。
生成器表達(dá)式可以用來進(jìn)行條件連接、條件宏定義、條件頭文件路徑等。條件可以基于構(gòu)建配置、target屬性、平臺信息、以及很多其他可以查詢的信息。
有不同類型的生成器表達(dá)式,包括:邏輯表達(dá)式、信息表達(dá)式、輸出表達(dá)式。
邏輯表達(dá)式用來創(chuàng)建條件輸出,最基本的表達(dá)式是 0 和 1表達(dá)式。一個(gè)$<0:...>
結(jié)果是一個(gè)空字符串,<1:...>
結(jié)果是字符串"..."。他們可以嵌套。
生成器表達(dá)式通常用來?xiàng)l件添加編譯標(biāo)志,比如語言級別的警告標(biāo)志。一個(gè)好模式是把這些信息關(guān)聯(lián)到INTERFACE
目標(biāo)上,使得這些信息可以傳遞。讓我們構(gòu)建一個(gè)INTERFACE
目標(biāo),并且指定C++標(biāo)準(zhǔn)為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)
然后添加我們需要的編譯警告標(biāo)志。因?yàn)榫幾g警告標(biāo)志因不同的編譯器而異,所以我們使用COMPILE_LANG_AND_ID
生成器表達(dá)式來控制哪些標(biāo)志會添加到給定的語言和編譯器:
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),警告標(biāo)志被封裝在BUILD_INTERFACE
表達(dá)式里面。這樣做可以使得安裝我們項(xiàng)目的消費(fèi)者不會繼承這些警告標(biāo)志。
添加導(dǎo)出配置 (Step11)
在Step4我們?yōu)轫?xiàng)目添加了安裝庫和頭文件的能力,在Step7我們?yōu)轫?xiàng)目添加了發(fā)布部署包的能力。
下一步,我們將添加一些必要信息使得其他CMake項(xiàng)目可以通過構(gòu)建目錄、本地安裝或安裝包來使用我們的項(xiàng)目。
首先更新install(TARGETS)
命令,不僅指定DESTINATION
,還指定EXPORT
。EXPORT
關(guān)鍵字生成一個(gè)CMake文件到安裝目錄,該CMake文件中的代碼從安裝目錄導(dǎo)入目標(biāo)。讓我們更新MathFunctions/CMakeLists.txt
文件中的instal
命令,導(dǎo)出MathFunctions這個(gè)庫:
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
)
這時(shí)嘗試運(yùn)行CMake,如果一切都設(shè)置正確,你將會看到CMake產(chǎn)生一個(gè)錯(cuò)誤:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
造成這個(gè)錯(cuò)誤的原因是,在生成導(dǎo)出信息的過程中導(dǎo)出了一個(gè)當(dāng)前機(jī)器的路徑,這個(gè)路徑在其他機(jī)器上可能是無效的。解決方案是,改變target_include_directories
MathFunctions,使得它明白當(dāng)在構(gòu)建目錄內(nèi)使用和通過一個(gè)安裝包使用時(shí),需要的是不同的INTERFACE
位置。也就是說,需要把target_include_directories
MathFunctions轉(zhuǎn)換為這樣:
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
更新之后,我們可以再次運(yùn)行CMake,它不會再報(bào)錯(cuò)了。
現(xiàn)在,CMake已經(jīng)能正確地打包需要的目標(biāo)信息了,但我們?nèi)匀恍枰梢粋€(gè)MathFunctionsConfig.cmake
使得CMakefind_package
命令能找到我們的項(xiàng)目。讓我們繼續(xù),添加一個(gè)Config.cmake.in
文件到項(xiàng)目的頂層目錄,內(nèi)容如下:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
然后,為了能正確地配置和安裝這個(gè)文件,需要添加以下內(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)生成了一個(gè)可以在項(xiàng)目安裝或打包以后可用的可重定位CMake配置,如果希望項(xiàng)目同時(shí)可以在一個(gè)構(gòu)建目錄中使用,我們只需要將以下內(nèi)容添加到頂層CMakeLists.txt
的最后:
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
通過這個(gè)導(dǎo)出項(xiàng),我們可以生成一個(gè)Targets.cmake
,使得在構(gòu)建目錄中生成的MathFunctionsConfig.cmake
可以被其他項(xiàng)目使用,并不需要安裝。
導(dǎo)入一個(gè)CMake工程 (消費(fèi)者)
這個(gè)例子展示了一個(gè)項(xiàng)目如何發(fā)現(xiàn)其他生成Config.cmake
文件的CMake包,同時(shí)也展示了在生成一個(gè)Config.cmake
的時(shí)候如何聲明項(xiàng)目的外部依賴。
打包Debug和Release (MultiPackage)
缺省情況下,一個(gè)構(gòu)建目錄中只包含一個(gè)配置:Debug、Release、MinSizeRel、或RelWithDebInfo。
但是可以設(shè)置一個(gè)項(xiàng)目包含多個(gè)配置,可以在一次構(gòu)建包的過程中打包多個(gè)構(gòu)建目錄。
首先需要建立一個(gè)目錄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)建都完成了,我們可以使用一個(gè)自定義的MultiCPackConfig.cmake
文件把這兩個(gè)構(gòu)建打包到一個(gè)發(fā)布中:
cpack --config ../../MultiPackage/MultiCPackConfig.cmake