[翻译]CMAKE官方教程

2,088 阅读8分钟

本篇文章讲对官方教程进行翻译。本篇文章将构建完整的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会读取文件中的配置并且运行。