[翻譯]CMAKE官方教程

BB8發表於2019-08-01

本篇文章講對官方教程進行翻譯。本篇文章將構建完整的CMAKE專案分成了7個步驟。

第一步

一個最基礎的專案就是從原始碼中構建一個可執行程式。對一個簡單的專案來說,CMakeLists.txt有兩行程式碼是必需的。我們的教程就從這裡開始。CMakeLists.txt檔案看上去是這樣的:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
複製程式碼

我們注意到,這個例子的指令都是小寫的。實際上,CMAKE支援小寫命令,大寫命令或者大小寫混用的命令。tutorial.cxx的將實現一個計算一個數字的平方根的功能,它的第一個版本是非常簡單的:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}
複製程式碼

增加版本號&配置標頭檔案

我們要往CMakeLists.txt新增的第一個特性為我們的可執行檔案和我們的專案增加版本號。你完全可以在原始碼中做這件事情,但是在CMakeLists.txt中會更加的靈活。為了增加版本號,我們需要對CMakeLists.txt做一些改動:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
 
# add the executable
add_executable(Tutorial tutorial.cxx)
複製程式碼

既然配置檔案會被寫入二進位制樹,因此我們必須在搜尋路徑中新增配置檔案所在路徑。緊接著,我們在原始碼樹中建立TutorialConfig.h,內容如下:

// 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@時,會從CMakeLists.txt中獲取值進行替換。 接著,我們修改tutotial.cxx引用標頭檔案,從而可以使用版本號。原始碼如下:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n",
            argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}
複製程式碼

主要的變化是引用了TutorialConfig.h標頭檔案和在使用資訊中列印了版本號。

引用庫(第二步)

現在,我們將為專案新增庫。這個庫將包含我們對計算平方根的實現。可執行檔案將引用這個庫用以取代編譯器提供的標準平方根實現。在這個教程中,我們會將庫新增到名為MathFunctions的子路徑。這在CMakeLists.txt需要一行指令:

add_library(MathFunctions mysqrt.cxx)
複製程式碼

mysqrt.cxx有一個名為mysqrt的函式,提供了與編譯器提供平方根計算相似的功能。為了能夠使用新的庫,我們在 CMakeLists.txt中新增了子路徑,使得這個庫能夠被CMAKE編譯。我們也新增了其他的搜尋目錄,使得MathFunctions/MathFunctions.h標頭檔案能夠被引用。最後一個變化是新增了新的庫到可執行檔案中。CMakeLists.txt的最後幾行是這樣的:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions) 
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
複製程式碼

現在,讓我們考慮一下為MathFunctions庫提供選項。在這個教程中,我們沒什麼必要這麼做,但是對於一個更大的庫或者需要依賴其他第三方庫的庫來說,這可能很有必要。第一步是在CMakeLists.txt中新增選項:

# should we use our own math functions?
option (USE_MYMATH 
        "Use tutorial provided math implementation" ON) 
複製程式碼

這麼做,會讓CMAKE GUI工具顯示一個可供使用者修改的選項,預設值是on。設定會被儲存在快取中,使用者不必每次都進行設定。下一個變化是條件編譯連結MathFunctions。為了實現這個功能,我們在CMakeLists.txt的最後幾行進行修改:

# add the MathFunctions library?
#
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
複製程式碼

使用了USE_MYMATH來決定MathFunctions是否需要被編譯和使用。注意到使用變數(本案例是EXTRA_LIBS)為後續連結成可執行程式收集可選庫。這個做法會使得更大的專案更加清晰的處理可選元件問題。相應的,我們需要在原始碼中進行處理:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n", argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
 
  double inputValue = atof(argv[1]);
 
#ifdef USE_MYMATH
  double outputValue = mysqrt(inputValue);
#else
  double outputValue = sqrt(inputValue);
#endif
 
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}
複製程式碼

在原始碼中,我們同樣使用了USE_MYMATH.在ToturialConfig.h標頭檔案中新增以下程式碼,就可以為CMAKE提供該變數。

#cmakedefine USE_MYMATH
複製程式碼

安裝和測試(第三步)

下一步我們新增安裝規則和為專案增加測試支援。安裝規則相當的簡單。對MathFunctions庫來說,我們在CMakeLists.txt中新增兩行就可以完成對庫和標頭檔案的配置。

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
複製程式碼

對應用來說,在CMakeLists.txt中新增以下的幾行完成安裝可執行檔案和配置標頭檔案:

# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)
複製程式碼

這就是全部了。在這個點上,你應該可以構建這個教程了,完成安裝。這個專案會安裝合適的標頭檔案,依賴庫和可執行檔案。CMAKE中的變數CMAKE_INSTALL_PREFIX會決定檔案被安裝的根路徑。新增測試支援同樣很簡單。在CMakeLists.txt我們可以新增一系列的基礎測試保證應用正常執行。

include(CTest)

# does the application run
add_test (TutorialRuns Tutorial 25)
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
複製程式碼

構建完成後,可以在命令列中執行ctest執行測試。第一個測試簡單的見證應用執行,沒有段錯誤或者其他崩潰,並且返回0.這個是基本的CTEST測試。接著幾個測試會使用PASS_REGULAR_EXPRESSION測試屬性驗證測試的輸出是否包含字串。在這個例子彙總,驗證計算平方根的結果和列印的使用資訊中的版本號是否正是引數提供的版本號。如果你希望新增其他測試,測試不同的輸入值,你可以需要考慮建立像下面所示的巨集定義:

#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 a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
複製程式碼

新增系統感知(第四步)

接著,讓我們考慮新增一些平臺相關的程式碼。在這個,i 子中,我們會新增一些取決於平臺是否具備logexp函式的程式碼。當然,所有平臺都有這些函式,但是在這個教程中,讓我們設想以下他們是不常見的。如果一個平臺有log,那麼我們就使用它來計算平方根。我們首先測試一些這些函式是否可用,可以在CMakeLists.txt使用CheckFunctionExists.cmake中的巨集定義:

# does this system provide the log and exp functions?
include (CheckFunctionExists)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
複製程式碼

接著,我們修改TutorialConfig.h標頭檔案的定義,定義是否生效取決於CMAKE是否在平臺上找到來他們。

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
複製程式碼

在配置前測試logexp很重要。配置檔案指令會使用CMAKE的當前設定立即配置檔案。最終,在平方根計算函式中,我們基於logexp在當前系統是否可用決定使用哪個實現。

// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
  result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
  . . .
複製程式碼

新增動態檔案生成和檔案生成器(第五步)

在這一小節,我們會展示你如何在應用構建流程中新增程式碼生成。在這個例子中,我們會在構建過程中建立來一個預先計算的品方跟表格,然後將表格編譯到應用中去。為了實現這個功能,我們首先需要一個生成表格的程式。在MathFucntions的子路徑中,有一個名為MakeTable.cxx的原始檔就是完成這個任務。

// A simple program that builds a sqrt table 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
int main (int argc, char *argv[])
{
  int i;
  double result;
 
  // make sure we have enough arguments
  if (argc < 2)
    {
    return 1;
    }
  
  // open the output file
  FILE *fout = fopen(argv[1],"w");
  if (!fout)
    {
    return 1;
    }
  
  // create a source file with a table of square roots
  fprintf(fout,"double sqrtTable[] = {\n");
  for (i = 0; i < 10; ++i)
    {
    result = sqrt(static_cast<double>(i));
    fprintf(fout,"%g,\n",result);
    }
 
  // close the table with a zero
  fprintf(fout,"0};\n");
  fclose(fout);
  return 0;
}
複製程式碼

注意到,表格是通過合法的的C++產生的,並且輸出的檔名是通過引數確定的。所以下一步就是在CMakeLists.txt中設定適當的指令去構建MakeTable的可執行程式,然後將其作為構建流程的一部分執行。只需幾行程式碼就可完成:

# 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
  )
 
# add the binary tree directory to the search path for 
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )
複製程式碼

首先,MakeTable的可執行程式將作為獨立的可執行程式。然後我們新增一個自定義的指令定義如何執行MakeTable產生Table.h。再然後,我們要讓CMAKE知道mysqrt.cxx依賴於產生的Table.h。通過將生成的Table.h新增到MathFucntions的原始碼列表中實現。另外我們也要將當前的二進位制目錄新增到標頭檔案搜尋路徑(including directories)中,這樣Table.h才能被找到,並且才能被mysqrt.cxx引用到。當這個專案被構建時,首先構建MakeTable可執行程式,然後執行該程式生成Table.h.最後,編譯已經引入來Table.hmysqrt.cxx生成MathFunctions庫。此時,CMakeLists.txt是這樣的:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
include(CTest)
 
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
 
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
 
# should we use our own math functions
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 (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")
 
# add the MathFunctions library?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
 
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)
 
# does the application run
add_test (TutorialRuns Tutorial 25)
 
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES 
  PASS_REGULAR_EXPRESSION "Usage:.*number"
  )
 
 
#define a macro to simplify adding tests
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 a bunch of result based tests
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 0")
do_test (0.0001 "0.0001 is 0.01")
複製程式碼

TutorialConfig.h. 是這樣的:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
 
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
複製程式碼

MathFunctionsCMakeLists.txt是這樣的:

# 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
  DEPENDS MakeTable
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  )
# add the binary tree directory to the search path 
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
 
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
複製程式碼

建立安裝器(第六步)

下一步,假設我們需要將程式分發給其他人使用。我們希望根據不同的平臺同時提供二進位制和原始碼分發。這和我們之前小節做的第三部略有不同。在第三步的時候,我們安裝的是從過原始碼構建出來的二進位制。在這個例子中,我們會構建支援二進位制安裝的安裝包和支援諸如cygwin, debuan, RPMs的安裝包。為來實現這個功能,我們使用CPACK建立平臺相關的安裝包。確切的說,我們需要在根CMakeLists.txt的底部新增以下幾行:

# build a CPack driven installer package
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的路徑和版本資訊。版本資訊使用到我們之前的定義。最後,我們包含了CPACK模組。

接下來,是最常見的構造方式和執行方式。為了構建二進位制包,你需要執行: cpack --config CPackConfig.cmake

為了建立原始碼包,你需要執行:cpack --config CPackSourceConfig.cmake

提供儀表盤支援(第七步)

很容易的就可以提交我們測試結果到儀表盤。在之前的步驟中,我們已經定義了若干測試用例。我們只需要執行這些用例然後提交即可。在根CMakeLists.txt中新增指令就可以增加儀表盤功能:

# enable dashboard scripting
include (CTest)
複製程式碼

我們同樣可以建立CTestConfig.cmake指定儀表盤名稱。

set (CTEST_PROJECT_NAME "Tutorial")
複製程式碼

CTEST會讀取檔案中的配置並且執行。

相關文章