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

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

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

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

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

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

指定編譯時宏定義

如果不使用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編譯時宏定義凯砍。

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)

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

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

  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);
}

這時蚜点,如果你構(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_DIRECTORIESCOMPILE_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)建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)建都完成了,我們可以使用一個自定義的MultiCPackConfig.cmake文件把這兩個構(gòu)建打包到一個發(fā)布中:

cpack --config ../../MultiPackage/MultiCPackConfig.cmake
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載惶洲,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者按声。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恬吕,隨后出現(xiàn)的幾起案子签则,更是在濱河造成了極大的恐慌,老刑警劉巖铐料,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渐裂,死亡現(xiàn)場離奇詭異钠惩,居然都是意外死亡柒凉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事〕《ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵埋心,是天一觀的道長闲坎。 經(jīng)常有香客問我绣溜,道長罢防,這世上最難降的妖魔是什么咒吐? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任硅确,我火速辦了婚禮缭付,結(jié)果婚禮上的妖,老公的妹妹穿的比我還像新娘。我一直安慰自己忘蟹,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著很澄,像睡著了一般讯蒲。 火紅的嫁衣襯著肌膚如雪赁酝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天坛梁,我揣著相機與錄音褐缠,去河邊找鬼胡桨。 笑死呢诬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的账月。 我是一名探鬼主播谣妻,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼辈灼,長吁一口氣:“原來是場噩夢啊……” “哼棵介!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钞脂,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤抢肛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓤檐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娱节,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡挠蛉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肄满。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谴古。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖稠歉,靈堂內(nèi)的尸體忽然破棺而出掰担,到底是詐尸還是另有隱情,我是刑警寧澤怒炸,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布带饱,位于F島的核電站,受9級特大地震影響阅羹,放射性物質(zhì)發(fā)生泄漏纠炮。R本人自食惡果不足惜月趟,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恢口。 院中可真熱鬧孝宗,春花似錦、人聲如沸耕肩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猿诸。三九已至婚被,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梳虽,已是汗流浹背址芯。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窜觉,地道東北人谷炸。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像禀挫,于是被迫代替她去往敵國和親旬陡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351