CMake官方文檔翻譯(1) CMake教程 Step by Step

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特性。

修改atofstd::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 -Nctest -vv

添加系統(tǒng)內(nèi)省 (Step5)

讓我們在源碼中添加一些目標(biāo)平臺可能不支持的特性。下面的這個(gè)例子,我們將會根據(jù)目標(biāo)平臺是否支持logexp函數(shù)來選擇是否包含一些代碼到項(xiàng)目中。雖然幾乎所有的平臺都支持這兩個(gè)函數(shù),我們還是假設(shè)一下某個(gè)特殊的平臺沒有這兩個(gè)函數(shù)。

如果平臺支持logexp函數(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ù)平臺是否支持logexp函數(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)程序沒有使用logexp,即使平臺實(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)行。如果logexp函數(shù)任然沒有被使用,查看生成的TutorialConfig.h文件,可能它們在當(dāng)前的系統(tǒng)上確實(shí)不支持。

指定編譯時(shí)宏定義

如果不使用TutorialConfig.h來存放HAVE_LOGHAVE_EXP,是否有其他更好的方式呢?答案是:使用target_compile_definitions

首先,刪除TutorialConfig.h.in中的HAVE_LOGHAVE_EXP定義,不再需要在mysqrt.cxx中包含TutorialConfig.h了,也不需要在MathFunctions/CMakeLists.txt中指定該頭文件的路徑了。

然后,移動頂層CMakeLists.txt中檢查HAVE_LOGHAVE_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)

假如我們根本就不想使用平臺自帶的logexp函數(shù),而是想通過預(yù)先生成的一個(gè)數(shù)據(jù)表來計(jì)算平方根。在這里,我們將在構(gòu)建的過程中生成這個(gè)表格,然后把這個(gè)表格編譯到最終的程序中。

首先,讓我們移除MathFunctions/CMakeLists.txt中檢查logexp的部分,然后移除mysqrt.cxx中檢查HAVE_LOGHAVE_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è)封裝了使用mysqrtsqrt的庫,而不是由調(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:

  1. 始終包含MathFunctions.h

  2. 始終使用mathfunctions::sqrt

  3. 不需要包含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_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_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 ,還指定EXPORTEXPORT關(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)建debugrelease目錄。最終的目錄布局像這樣:

─ 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評論 2 380