CMake教程

CMake教程


版權聲明:本文翻譯自CMake tutorial v3.16》。未經做者容許嚴禁用於商業出版不然追究法律責任。網絡轉載請註明出處!!!php


CMake教程提供了逐步指南,涵蓋了CMake能夠幫助解決的常見構建系統問題。 瞭解示例項目中各個主題如何協同工做將很是有幫助。 示例的教程文檔和源代碼可在CMake源代碼樹的Help/guide/tutorial目錄中找到。 每一個步驟都有其本身的子目錄,其中包含能夠用做起點的代碼。 教程示例是漸進式的,所以每一個步驟都爲上一步提供了完整的解決方案。html

1 基本起點(第1步)

最基本的項目是從源代碼文件構建一個可執行文件。 對於簡單的項目,只需三行CMakeLists.txt文件。 這是本教程的起點。 在Step1目錄中建立一個CMakeLists.txt文件,以下所示:ios

cmake_minimum_required(VERSION 3.10)

# set the project name
project(Tutorial)

# add the executable
add_executable(Tutorial tutorial.cxx)

請注意,此示例在CMakeLists.txt文件中使用小寫的命令。 CMake支持大寫,小寫和大小寫混合的命令。 Step1目錄中提供了tutorial.cxx的源代碼,可用於計算數字的平方根。c++

1.1 添加版本號和配置頭文件

咱們將添加的第一個功能是爲咱們的可執行文件和項目提供版本號。 雖然咱們能夠僅在源代碼中執行此操做,可是使用CMakeLists.txt能夠提供更大的靈活性。shell

首先,修改CMakeLists.txt文件來設置版本號。windows

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)


###早期版本的寫法
### project(Tutorial)
### set (Tutorial_VERSION_MAJOR 1)
### set (Tutorial_VERSION_MINOR 0)

而後,配置一個頭文件,將版本號傳遞給源代碼:緩存

configure_file(TutorialConfig.h.in TutorialConfig.h)

###早期版本的寫法
### configure_file ("${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" "${PROJECT_BINARY_DIR}/TutorialConfig.h")

因爲配置的文件將被寫入二進制樹中,因此咱們必須將該目錄添加到搜索include文件的路徑列表中。在CMakeLists.txt文件的末尾添加如下行:安全

#必須在add_excutable以後
target_include_directories(Tutorial PUBLIC  "${PROJECT_BINARY_DIR}")


###早期版本的寫法:
###能夠位於任意位置,通常放在add_excutable以前
### include_directories("${PROJECT_BINARY_DIR}")

使用您喜歡的編輯器在源碼目錄中建立TutorialConfig.h.in,內容以下:網絡

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

當CMake配置這個頭文件時,@Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@的值將被替換。app

接下來,修改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;
  }

完整的CMakeLists.txt以下:

cmake_minimum_required(VERSION 3.10)

#set project name and version
project(Tutorial VERSION 1.0)

configure_file(TutorialConfig.h.in TutorialConfig.h)

#add the executable
add_executable(Tutorial tutorial.cxx)

target_include_directories(Tutorial PUBLIC  "${PROJECT_BINARY_DIR}"  )

1.2 指定c++標準

接下來,經過在tutorial.cxx中用std::stod替換atof,將一些C ++ 11功能添加到咱們的項目中。 同時,刪除#include <cstdlib>

const double inputValue = std::stod(argv[1]);

咱們須要在CMake代碼中明確聲明應使用正確的標誌。 在CMake中啓用對特定C ++標準的支持的最簡單方法是使用CMAKE_CXX_STANDARD變量。 對於本教程,請將CMakeLists.txt文件中的CMAKE_CXX_STANDARD變量設置爲11,並將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)

1.3 構建和測試

運行cmake或cmake-gui以配置項目,而後使用所選的構建工具進行構建。

例如,從命令行咱們能夠導航到CMake源代碼樹的Help /guide/tutorial目錄並運行如下命令:

mkdir Step1_build
cd Step1_build
cmake ../Step1
cmake --build .

導航到構建教程的目錄(多是make目錄或Debug或Release構建配置子目錄),而後運行如下命令:

Tutorial 4294967296
Tutorial 10
Tutorial

2 添加庫(第2步)

如今,咱們將添加一個庫到咱們的項目中。 該庫是咱們本身的實現的用於計算數字的平方根的庫。 可執行文件可使用此庫,而不是使用編譯器提供的標準平方根函數。

在本教程中,咱們將庫放入名爲MathFunctions的子目錄中。 該目錄已包含頭文件MathFunctions.h和源文件mysqrt.cxx。 源文件具備一個稱爲mysqrt的函數,該函數提供與編譯器的sqrt函數相似的功能。

將如下一行CMakeLists.txt文件添加到MathFunctions目錄中:

add_library(MathFunctions mysqrt.cxx)

爲了使用新的庫,咱們將在頂層CMakeLists.txt文件中添加add_subdirectory調用,以便構建該庫。 咱們將新的庫添加到可執行文件,並將MathFunctions添加爲include目錄,以即可以找到mqsqrt.h頭文件。 頂級CMakeLists.txt文件的最後幾行如今應以下所示:

# add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(Tutorial tutorial.cxx)

#必須位於add_excutable以後
target_link_libraries(Tutorial PUBLIC MathFunctions)

###早期版本的寫法
### target_link_libraries(Tutorial MathFunctions)

#add the binary tree to the search path for include files so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/MathFunctions")

如今讓咱們將MathFunctions庫設爲可選。 雖然對於本教程而言確實不須要這樣作,可是對於大型項目來講,這是很常見的。 第一步是向頂層CMakeLists.txt文件添加一個選項。

option(USE_MYMATH "Use tutorial provided math implementation" ON)

# configure a header file to pass some of the CMake settings to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)

此選項將顯示在CMake GUI和ccmake中,默認值ON,可由用戶更改。 此設置將存儲在緩存中,所以用戶沒必要每次在構建目錄上運行CMake時設置該值。

下一個更改是使構建和連接MathFunctions庫成爲布爾選項。 爲此,咱們將頂層CMakeLists.txt文件的結尾更改成以下所示:

if(USE_MYMATH)
  add_subdirectory(MathFunctions)
  list(APPEND EXTRA_LIBS MathFunctions)
  list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()

# add the executable
add_executable(Tutorial tutorial.cxx)

#必須位於add_executable以後
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})

###早期版本的寫法
### if(USE_MYMATH)
### include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
### add_subdirectory (MathFunctions)
### set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
### endif(USE_MYMATH)
### include_directories("${PROJECT_BINARY_DIR}")
### add_executable(Tutorial tutorial.cxx)
### target_link_libraries(Tutorial ${EXTRA_LIBS})

請注意,使用變量EXTRA_LIBS來收集任意可選庫,以供之後連接到可執行文件中。 變量EXTRA_INCLUDES相似地用於可選的頭文件。 當處理許多可選組件時,這是一種經典方法,咱們將在下一步中介紹現代方法。

對源代碼的相應更改很是簡單。 首先,若是須要,在tutorial.cxx中包含MathFunctions.h頭文件:

#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif

而後,在同一文件中,使USE_MYMATH控制使用哪一個平方根函數:

#ifdef USE_MYMATH
  const double outputValue = mysqrt(inputValue);
#else
  const double outputValue = sqrt(inputValue);
#endif

因爲源代碼如今須要USE_MYMATH,所以可使用如下行將其添加到TutorialConfig.h.in中:

#cmakedefine USE_MYMATH

練習:爲何在USE_MYMATH選項以後配置TutorialConfig.h.in如此重要? 若是咱們將二者倒置會怎樣?

運行cmake或cmake-gui以配置項目,而後使用所選的構建工具進行構建。 而後運行構建的Tutorial可執行文件。

使用ccmake或CMake GUI更新USE_MYMATH的值。 從新生成並再次運行本教程。 sqrt或mysqrt哪一個函數可提供更好的結果?

完整的CMakeLists.txt文件以下:

cmake_minimum_required(VERSION 3.5)                                                                                  
# 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)
 
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)
 
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)

#必須位於add_executable以後
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})

3 添加庫的使用要求(第3步)

使用要求能夠更好地控制庫或可執行文件的連接和include行,同時還能夠更好地控制CMake內部目標的傳遞屬性。 利用使用要求的主要命令是:

  • target_compile_definitions
  • target_compile_options
  • target_include_directories
  • target_link_libraries

讓咱們從第2步中重構代碼,以利用現代的CMake方法編寫使用要求。 咱們首先聲明,連接到MathFunctions的任何東西都須要包括當前源碼目錄,而MathFunctions自己不須要。 所以,這能夠成爲INTERFACE使用要求。

請記住,INTERFACE是指消費者須要的,而生產者不須要東西。 將如下行添加到MathFunctions/CMakeLists.txt的末尾:

target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR)

如今,咱們已經指定了MathFunction的使用要求,咱們能夠安全地從頂級CMakeLists.txt中刪除對EXTRA_INCLUDES變量的使用:

if(USE_MYMATH)
  add_subdirectory(MathFunctions)
  list(APPEND EXTRA_LIBS MathFunctions)
endif()
... ...
... ...

target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")

4 安裝與測試(第4步)

如今,咱們能夠開始向項目添加安裝規則和測試支持。

4.1 安裝規則

安裝規則很是簡單:對於MathFunctions,咱們要安裝庫和頭文件,對於應用程序,咱們要安裝可執行文件和配置的頭文件。

所以,在MathFunctions/CMakeLists.txt的末尾添加:

install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

而後在頂級cmakelt .txt的末尾添加

install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)

這就是建立本教程的基本本地安裝所需的所有工做。

運行cmake或cmake-gui以配置項目,而後使用所選的構建工具進行構建。 從命令行鍵入cmake --install進行安裝(自3.15中引入,較早版本的CMake必須使用make install),或從IDE構建INSTALL目標。 這將安裝適當的頭文件,庫和可執行文件。

CMake變量CMAKE_INSTALL_PREFIX用於肯定文件的安裝根目錄。 若是使用cmake --install,則能夠經過--prefix參數指定自定義安裝目錄。 對於多配置工具,請使用--config參數指定配置。

驗證已安裝的Tutorial能夠運行。

4.2 測試支持

接下來,測試咱們的應用程序。 在頂級CMakeLists.txt文件的末尾,咱們能夠啓用測試,而後添加一些基本測試以驗證應用程序是否正常運行。

enable_testing()

# does the application run
add_test(NAME Runs COMMAND Tutorial 25)

# does it sqrt of 25
add_test (NAME Comp25 COMMAND Tutorial 25)
set_tests_properties (Comp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")

# 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")

###早期版本的寫法
### include(CTest)
### add_test (TutorialRuns Tutorial 25)
###
### add_test (TutorialComp25 Tutorial 25)
### set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
###
### add_test (TutorialUsage Tutorial)
### set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
###
### #define a macro to simplify adding tests, then use it
### macro (do_test arg result)
### add_test (TutorialComp${arg} Tutorial ${arg})
### set_tests_properties (TutorialComp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
### endmacro (do_test)
###
### do_test(4 "4 is 2")
### do_test(9 "9 is 3")
### do_test(5 "5 is 2.236")
### do_test(7 "7 is 2.645")
### do_test(25 "25 is 5")
### do_test(-25 "-25 is [-nan|nan|0]")
### do_test(0.0001 "0.0001 is 0.01")

第一個測試只是驗證應用程序你可否運行,沒有段錯誤或其餘崩潰,而且返回值爲零。 這是CTest測試的基本形式。

下一個測試使用PASS_REGULAR_EXPRESSION測試屬性來驗證測試的輸出是否包含某些字符串。 在這種狀況下,驗證在提供了錯誤數量的參數時是否打印了用法消息。

最後,咱們有一個名爲do_test的函數,該函數運行應用程序並驗證所計算的平方根對於給定輸入是否正確。 對於do_test的每次調用,都會基於傳遞的參數將另外一個測試添加到項目中,該測試具備名稱,輸入和預期結果。

從新構建應用程序,而後cd到二進制目錄並運行ctest -Nctest -VV。 對於多配置生成器(例如Visual Studio),必須指定配置類型。 例如,要在「調試」模式下運行測試,請從構建目錄(而不是「調試」子目錄!)中使用ctest -C Debug -VV。 或者,從IDE構建RUN_TESTS目標。

早期版本的另外一種寫法是:

 
  

5 添加系統自檢(第5步)

讓咱們考慮向咱們的項目中添加一些代碼,這些代碼取決於目標平臺可能不具有的功能。 對於此示例,咱們將添加一些代碼,具體取決於目標平臺是否具備logexp函數。 固然,幾乎每一個平臺都具備這些函數,但對於本教程而言,假設它們並不常見。

若是平臺具備logexp,那麼咱們將使用它們來計算mysqrt函數中的平方根。 咱們首先使用頂級CMakeLists.txt中的CheckSymbolExists模塊測試這些函數的可用性。 咱們將在TutorialConfig.h.in中使用新定義,所以請確保在配置該文件以前進行設置。

include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)

###早期版本的寫法
### include (CheckFunctionExists)
### check_function_exists (log HAVE_LOG)
### check_function_exists (exp HAVE_EXP)

如今,將這些定義添加到TutorialConfig.h.in中,以便咱們能夠從mysqrt.cxx中使用它們:

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

修改mysqrt.cxx以包括cmath。 接下來,在mysqrt函數的同一文件中,咱們可使用如下代碼提供基於logexp(若是在系統上可用)的替代實現(在return 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或cmake-gui來配置項目,而後使用所選的構建工具進行構建並運行Tutorial可執行文件。

您會注意到,咱們也沒有使用logexp,即便咱們認爲它們應該是可用。 咱們應該很快意識到,咱們忘記在mysqrt.cxx中包含TutorialConfig.h

咱們還須要更新MathFunctions/CMakeLists.txt,以便mysqrt.cxx知道此文件的位置:

target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${CMAKE_BINARY_DIR})

完成此更新後,繼續並再次構建項目,而後運行構建的Tutorial可執行文件。 若是仍未使用logexp,請從構建目錄中打開生成的TutorialConfig.h文件。 也許它們在當前系統上不可用?

哪一個函數如今能夠提供更好的結果,sqrtmysqrt

5.1 指定編譯定義

除了在TutorialConfig.h中保存HAVE_LOGHAVE_EXP值,對咱們來講還有更好的地方嗎? 讓咱們嘗試使用target_compile_definitions

首先,從TutorialConfig.h.in中刪除定義。 咱們再也不須要包含mysqrt.cxx中的TutorialConfig.hMathFunctions/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()

完成這些更新後,繼續並從新構建項目。 運行內置的Tutorial可執行文件,並驗證結果與本步驟前面的內容相同。

6 添加自定義命令和生成的文件(第6步)

出於本教程的目的,假設咱們決定再也不使用平臺logexp函數,而是但願生成一個可在mysqrt函數中使用的預計算值表。 在本節中,咱們將在構建過程當中建立表,而後將該表編譯到咱們的應用程序中。

首先,讓咱們刪除MathFunctions/CMakeLists.txt中對logexp函數的檢查。 而後從mysqrt.cxx中刪除對HAVE_LOGHAVE_EXP的檢查。 同時,咱們能夠刪除#include <cmath>

MathFunctions子目錄中,提供了一個名爲MakeTable.cxx的新的源文件以生成表。

查看完文件後,咱們能夠看到該表是做爲有效的C++代碼生成的,而且輸出文件名做爲參數傳入。

下一步是將適當的命令添加到MathFunctions/CMakeLists.txt文件中,以構建MakeTable可執行文件,而後在構建過程當中運行它。 須要一些命令來完成此操做。

首先,在MathFunctions/CMakeLists.txt的頂部,添加MakeTable的可執行文件,就像添加任何其餘可執行文件同樣。

add_executable(MakeTable MakeTable.cxx)

而後,咱們添加一個自定義命令,該命令指定如何經過運行MakeTable生成Table.h

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  )

接下來,咱們必須讓CMake知道mysqrt.cxx依賴於生成的文件Table.h。 這是經過將生成的Table.h添加到庫MathFunctions的源列表中來完成的。

add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h )

咱們還必須將當前的二進制目錄添加到include目錄列表中,以便mysqrt.cxx能夠找到幷包含Table.h

target_include_directories(MathFunctions
          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
          PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
          )

如今,咱們來使用生成的表。 首先,修改mysqrt.cxx以包含Table.h。 接下來,咱們能夠重寫mysqrt函數以使用該表:

double mysqrt(double x) {
  if (x <= 0) {
    return 0;
  }

  // use the table to help find an initial value
  double result = x;
  if (x >= 1 && x < 10) {
    std::cout << "Use the table to help find an initial value " << std::endl;
    result = sqrtTable[static_cast<int>(x)];
  }

  // do ten iterations
  for (int i = 0; i < 10; ++i) {
    if (result <= 0) {
      result = 0.1;
    }
    double delta = x - (result * result);
    result = result + 0.5 * delta / result;
    std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
  }

  return result;
}

運行cmake或cmake-gui以配置項目,而後使用所選的構建工具進行構建。

構建此項目時,它將首先構建MakeTable可執行文件。 而後它將運行MakeTable來生成Table.h。 最後,它將編譯包括了Table.hmysqrt.cxx,以生成MathFunctions庫。

運行Tutorial可執行文件,並驗證它是否正在使用該表。

:wq# 7 構建一個安裝程序(第7步)

接下來,假設咱們想將項目分發給其餘人,以便他們可使用它。 咱們但願在各類平臺上提供二進制和源代碼。 這與咱們以前在「安裝和測試」(第4步)中進行的安裝有些不一樣,在「安裝和測試」中,咱們是安裝根據源代碼構建的二進制文件。 在此示例中,咱們將構建支持二進制安裝和包管理功能的安裝程序包。 爲此,咱們將使用CPack建立平臺特定的安裝程序。 具體來講,咱們須要在頂級CMakeLists.txt文件的底部添加幾行。

include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)

這就是所有須要作的事。 咱們首先包含InstallRequiredSystemLibraries。 該模塊將包括項目當前平臺所需的任何運行時庫。 接下來,咱們將一些CPack變量設置爲存儲該項目的許可證和版本信息的位置。 版本信息是在本教程的前面設置的,而且license.txt已包含在此步驟的頂級源目錄中。

最後,咱們包含CPack模塊,該模塊將使用這些變量和當前系統的其餘一些屬性來設置安裝程序。

下一步是以常規方式構建項目,而後在其上運行CPack。 要構建二進制發行版,請從二進制目錄運行:

cpack

要指定生成器,請使用-G選項。 對於多配置構建,請使用-C指定配置。 例如:

cpack -G ZIP -C Debug

要建立源碼分發,您能夠輸入:

cpack --config CPackSourceConfig.cmake

或者,運行make package或在IDE中右鍵單擊Package目標和Build Project

運行在二進制目錄中找到的安裝程序。 而後運行已安裝的可執行文件,並驗證其是否有效。

8 添加Dashboard支持(第8步)

添加支持以將測試結果提交到Dashboard很是容易。 咱們已經在「測試支持」中爲咱們的項目定義了許多測試。 如今,咱們只須要運行這些測試並將其提交到Dashboard便可。 爲了包含對Dashboard的支持,咱們在頂層CMakeLists.txt中包含了CTest模塊。

# enable dashboard scripting
include(CTest)

替換

# enable testing
enable_testing()

CTest模塊將自動調用enable_testing(),所以咱們能夠將其從CMake文件中刪除。

咱們還須要在頂級目錄中建立一個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將在運行時讀入該文件。 要建立一個簡單的Dashboard,您能夠運行cmake或cmake-gui來配置項目,但不構建它。 而是,將目錄更改成二進制樹,而後運行:

ctest [-VV] -D Experimental

請記住,對於多配置生成器(例如Visual Studio),必須指定配置類型:

ctest [-VV] -C Debug -D Experimental

或者,從IDE中構建Experimental目標。

ctest將構建和測試項目,並將結果提交給Kitware公共儀表板Dashboard。 Dashboard的結果將被上傳到Kitware的公共Dashboard:https://my.cdash.org/index.php?project=CMakeTutorial

9 混合靜態和動態庫(第9步)

在本節中,咱們將展現如何使用BUILD_SHARED_LIBS變量來控制add_library的默認行爲,並容許控制如何構建沒有顯式類型(STATIC,SHARED,MODULE或OBJECT)的庫。

爲此,咱們須要將BUILD_SHARED_LIBS添加到頂級CMakeLists.txt。 咱們使用option命令,由於它容許用戶能夠選擇該值是On仍是Off。

接下來,咱們將重構MathFunctions使其成爲使用mysqrt或sqrt封裝的真實庫,而不是要求調用代碼執行此邏輯。 這也意味着USE_MYMATH將不會控制構建MathFuctions,而是將控制此庫的行爲。

第一步是將頂級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)

既然咱們已經使MathFunctions始終被使用,咱們將須要更新該庫的邏輯。 所以,在MathFunctions/CMakeLists.txt中,咱們須要建立一個SqrtLibrary,當啓用USE_MYMATH時有條件地對其進行構建。 如今,因爲這是一個教程,咱們將明確要求SqrtLibrary是靜態構建的。

最終結果是MathFunctions/CMakeLists.txt應該以下所示:

# 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以使用mathfunctionsdetail命名空間:

#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導出定義:

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

此時,若是您構建了全部內容,則會注意到連接失敗,由於咱們將沒有位置獨立代碼的靜態庫與具備位置獨立代碼的庫組合在一塊兒。 解決方案是不管構建類型如何,都將SqrtLibrary的POSITION_INDEPENDENT_CODE目標屬性顯式設置爲True。

# 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)

練習:咱們修改了MathFunctions.h以使用dll導出定義。 使用CMake文檔,您能夠找到一個幫助器模塊來簡化此過程嗎?

10 添加生成器表達式(第10步)

在構建系統生成期間會評估生成器表達式,以生成特定於每一個構建配置的信息。

在許多目標屬性(例如LINK_LIBRARIES,INCLUDE_DIRECTORIES,COMPLIE_DEFINITIONS等)的上下文中容許生成器表達式。 在使用命令填充這些屬性(例如target_link_libraries(),target_include_directories() ,target_compile_definitions()等)時,也可使用它們。

生成器表達式可用於啓用條件連接,編譯時使用的條件定義,條件包含目錄等。 條件能夠基於構建配置,目標屬性,平臺信息或任何其餘可查詢信息。

生成器表達式有不一樣類型,包括邏輯,信息和輸出表達式。

邏輯表達式用於建立條件輸出。 基本表達式是0和1表達式。$<0:...>致使空字符串,而<1:...>致使內容「…」。 它們也能夠嵌套。

生成器表達式的常見用法是有條件地添加編譯器標誌,例如用於語言級別或警告的標誌。 一個不錯的模式是將該信息與一個INTERFACE目標相關聯,以容許該信息傳播。 讓咱們從構造一個INTERFACE目標並指定所需的C++標準級別11開始,而不是使用CMAKE_CXX_STANDARD

因此,下面的代碼:

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

將被替換爲:

add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

接下來,咱們爲項目添加所需的編譯器警告標誌。 因爲警告標誌根據編譯器的不一樣而不一樣,所以咱們使用COMPILE_LANG_AND_ID生成器表達式來控制在給定一種語言和一組編譯器ID的狀況下應應用的標誌,以下所示:

set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
 "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
 "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

查看此內容,咱們看到警告標誌封裝在BUILD_INTERFACE條件內。 這樣作是爲了使咱們已安裝項目的使用者不會繼承咱們的警告標誌。

練習:修改MathFunctions/CMakeLists.txt,以便全部目標都具備對tutorial_compiler_flags target_link_libraries()調用。

11 增長輸出配置(第11步)

在本教程的「安裝和測試」(第4步)中,咱們添加了CMake的功能,以安裝項目的庫和頭文件。 在"構建安裝程序"(第7步)期間,咱們添加了打包此資料的功能,以即可以將其分發給其餘人。

下一步是添加必要的信息,以便其餘CMake項目可使用咱們的項目,不管是從構建目錄,本地安裝仍是打包的文件。

第一步是更新咱們的install(TARGETS)命令,不只要指定DESTINATION,還要指定EXPORTEXPORT關鍵字生成並安裝一個CMake文件,該文件包含用於從安裝樹中導入install命令中列出的全部目標的代碼。 所以,讓咱們繼續,經過更新MathFunctions/CMakeLists.txt中的install命令顯式EXPORTMathFunctions庫,以下所示:

install(TARGETS MathFunctions tutorial_compiler_flags
        DESTINATION lib
        EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)

如今咱們已經導出了MathFunctions,咱們還須要顯式安裝生成的MathFunctionsTargets.cmake文件。 這是經過將如下內容添加到頂級CMakeLists.txt的底部來完成的:

install(EXPORT MathFunctionsTargets
  FILE MathFunctionsTargets.cmake
  DESTINATION lib/cmake/MathFunctions
)

此時,您應該嘗試運行CMake。 若是一切設置正確,您將看到CMake將生成以下錯誤:

Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:

  "/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"

which is prefixed in the source directory.

CMake試圖說的是,在生成導出信息的過程當中,它將導出與當前機器固有聯繫的路徑,而且在其餘機器上無效。 解決方案是更新MathFunctions target_include_directories,以瞭解從構建目錄和install/包中使用它時須要不一樣的INTERFACE位置。 這意味着將MathFunctions的target_include_directories調用轉換爲:

target_include_directories(MathFunctions
                           INTERFACE
                            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                            $<INSTALL_INTERFACE:include>
                           )

更新後,咱們能夠從新運行CMake並確認它再也不發出警告。

至此,咱們已經正確地打包了CMake所需的目標信息,但仍然須要生成MathFunctionsConfig.cmake,以便CMake find_package命令能夠找到咱們的項目。 所以,咱們繼續將名爲Config.cmake.in新文件添加到項目頂層項目的頂層目錄,其內容以下:

@PACKAGE_INIT@

include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

而後,要正確配置和安裝該文件,請將如下內容添加到頂級CMakeLists.txt的底部:

install(EXPORT MathFunctionsTargets
  FILE MathFunctionsTargets.cmake
  DESTINATION lib/cmake/MathFunctions
)

include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION "lib/cmake/example"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
  )
# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
  COMPATIBILITY AnyNewerVersion
)

# install the configuration file
install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
  DESTINATION lib/cmake/MathFunctions
  )

至此,咱們爲項目生成了可重定位的CMake配置,能夠在安裝或打包項目後使用它。 若是咱們也但願從構建目錄中使用咱們的項目,則只需將如下內容添加到頂級CMakeLists.txt的底部:

export(EXPORT MathFunctionsTargets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

經過此導出調用,咱們如今生成一個Targets.cmake,容許在構建目錄中配置的MathFunctionsConfig.cmake由其餘項目使用,而無需安裝它。

12 導入一個CMake項目(消費者)

本示例說明項目如何查找生成Config.cmake文件的其餘CMake軟件包。

它還顯示了在生成Config.cmake時如何聲明項目的外部依賴關係。

13 打包調試和發佈(多個包)

默認狀況下,CMake的模型是一個構建目錄僅包含一個配置,能夠是Debug,Release,MinSizeRel或RelWithDebInfo。

可是能夠將CPack設置爲同時捆綁多個構建目錄,以構建一個包含同一項目的多個配置的軟件包。

首先,咱們須要構建一個名爲multi_config的目錄,該目錄將包含咱們要打包在一塊兒的全部構建。

其次,在multi_config下建立一個debugrelease目錄。 最後,您應該具備以下佈局:

─ multi_config
    ├── debug
    └── release

如今,咱們須要設置調試和發佈版本,這大體須要如下內容:

cmake -DCMAKE_BUILD_TYPE=Debug ../../../MultiPackage/
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ../../../MultiPackage/
cmake --build .
cd ..

既然調試和發行版本均已完成,咱們就可使用自定義的MultiCPackConfig.cmake文件將兩個版本打包到一個發行版中。

cpack --config ../../MultiPackage/MultiCPackConfig.cmake

版權聲明:本文翻譯自CMake tutorial v3.16》。未經做者容許嚴禁用於商業出版不然追究法律責任。網絡轉載請註明出處!!!

相關文章
相關標籤/搜索