依据官网教程步步为营学习CmakeLists文件编写

本文详细介绍了如何使用CMake构建一个简单的工程,从最基础的设置开始,逐步增加复杂性,包括添加版本信息、配置头文件、构建动态库、处理可选库、添加测试用例以及生成文件。此外,还展示了如何根据系统特性选择不同实现,并添加自动生成的源文件到构建过程。

1. 最简单的一个Cmake工程

下面的CMakeLists.txt文件描述了一个最简单的工程,一共就这么3行。

  cmake_minimum_required (VERSION 2.6)
  project (Tutorial) 
  add_executable (Tutorial tutorial.cxx)

注意到这里都是用的小写的命令,在cmake文件里面大小写不严格区分,都可以用。上面的工程名Tutorial和下面的可执行程序的Tutorial没有什么关系,可以完全不同。

add_executable添加一个可编译的目标到工程里面

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               source1 [source2 ...])
  • name: 工程所要构建的目标名称
  • WIN32/..: 目标程序运行的平台
  • source1:用于构建目标程序的源文件

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, "Uage: %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;
  }

编写完上面两个文件以后,在工程目录下新建一个build目录:
.
├── build
├── CMakeLists.txt
└── tutorial.cxx

然后运行如下命令

  $ cd buld
  $ cmake ..
  $ make
  • cmake会自动加载上级目录里面的CMakeLists.txt文件,编译所需的文件都会生成在build目录下
  • make之后会生成可执行文件Tutorial

添加一个版本号和配置的头文件

修改CMakeList.txt来添加version number:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# 版本号.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# 配置一个头文件来传递一些CMake设置到源代码
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# 添加TutorialConfig.h的路径到头文件的搜索路径
include_directories("${PROJECT_BINARY_DIR}")
 
# 添加目标可执行文件
add_executable(Tutorial tutorial.cxx)

configure_file会拷贝一个文件到另一个目录并修改文件内容:

configure_file(<input> <output>
               [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
               [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

cmake会自动定义两个变量

  • ${PROJECT_SOURCE_DIR}: 当前工程最上层的目录,也就是的当前工程的根目录
  • ${PROJECT_BINARY_DIR}: 当前工程的构建目录(本例中新建的build目录)

在这个例子里,configure_file命令的源文件是TutorialConfig.h.in,手动创建这个文件:

// Tutorial工程的配置选项和设置
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

调用cmake的时候会在build目录下生成新的头文件(也就是把工程根目录下的TutorialConfig.h.in复制到build目录下,并重命名为TutorialConfig.h),并且使用CMakeList.txt中定义的值(Tutorial_VERSION_MAJOR为1,Tutorial_VERSION_MINOR为0)来替换@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@这两个变量。相当于(TutorialConfig.h内容为):

// Tutorial工程的配置选项和设置
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0

下一步要在源文件tutorial.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);//可以直接使用这2个宏
      fprintf(stdout, "Uage: %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;
  }

运行如下命令查看结果

  $ cmake ..
  $ make
  $ ./Tutorial

这个时候控制台会打印出来版本号

./Tutorial Version 1.0
Uage: ./Tutorial number

2. 增加难度,引入动态库

添加库文件

现在我们尝试添加一个库文件到我们的工程。这个库文件提供一个自定义的计算平方根的函数,用来替换编译器提供的函数。
生成库的源文件放到一个叫MathFunctions的子目录中,在该目录下新建CMakeList.txt文件,添加如下的一行

 add_library(MathFunc mysqrt.cxx) #将mysqrt.cxx文件编译成一个函数库MathFunc

源文件mysqrt.cxx包含一个函数mysqrt用于计算平方根。代码如下

#include "MathFunctions.h"
#include <stdio.h>

// a hack square root calculation using simple operations
double mysqrt(double x)
{
  if (x <= 0) {
    return 0;
  }

  double result;
  double delta;
  result = x;

  // do ten iterations
  int i;
  for (i = 0; i < 10; ++i) {
    if (result <= 0) {
      result = 0.1;
    }
    delta = x - (result * result);
    result = result + 0.5 * delta / result;
    fprintf(stdout, "Computing sqrt of %g to be %g\n", x, result);
  }
  return result;
}

还需要添加一个头文件MathFunctions.h以提供接口给main函数调用

double mysqrt(double x);

现在的目录结构
.
├── build
├── CMakeLists.txt
├── MathFunctions
│ ├── CMakeLists.txt
│ ├── MathFunctions.h
│ └── mysqrt.cxx
├── TutorialConfig.h.in
└── tutorial.cxx

CMakeLists.txt文件需要相应做如下改动

  • 添加一行add_subdirectory来保证新加的library在工程构建过程中被编译。//支持嵌套,先编译子目录中的CMakeLists.txt文件
  • 添加新的头文件搜索路径MathFunction/MathFunctions.h。//增加.h文件查找路径
  • 添加新的library到executable。

CMakeList.txt的最后几行变成了这样:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")#增加一个.h文件搜索路径
add_subdirectory (MathFunctions) #编译子目录

# 添加executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Turorial MathFunc) #生成Tutorial这个可执行程序需要链接前面生成的MathFunc库

最后要修改tutorial.cxx文件来调用自定义的mysqrt函数

  // A simple program that computes the square root of a number
  #include <stdio.h>
  #include <stdlib.h>
  //#include <math.h>
  #include "MathFunctions.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);//可以直接使用这2个宏
      fprintf(stdout, "Uage: %s number\n", argv[0]);
      return 1;
    }
    double inputValue = atof(argv[1]);
    double outputValue = mysqrt(inputValue);//使用库里面的函数
    fprintf(stdout, "The square root of %g is %g\n",
              inputValue, outputValue);
    return 0;
  }

最后编译一下试试

  $ cmake ..
  $ make
  $ ./Tutorial

看一下make过程的输出:

Scanning dependencies of target MathFunc
[ 50%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cxx.o
Linking CXX static library libMathFunc.a
[ 50%] Built target MathFunc
Scanning dependencies of target Tutorial
[100%] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o
Linking CXX executable Tutorial
[100%] Built target Tutorial

这里编译生成了新的库libMathFunc.a

考虑把MathFunc库配置成可选的

首先在工程根目录下的CMakeList.txt文件中添加一个option,这个和add_definitions(-DUSE_MYMATH=1)不太一样,后者定义出来的变量是直接在.h和cpp文件中使用的。而optional定义的变量需要使用#cmakedefine USE_MYMATH声明一下,说明是在CmakeList.txt文件中定义的。

#需要用自定义的数学函数么?
option (USE_MYMATH
            "Use tutorial provided math implementation" ON)

运行cmake ..时会跳出来配置的GUI,在GUI中会看到新添加的这个选项,用户可以根据需要进行修改。但如果只是增加上面的内容,不会对编译、链接过程产生任何影响,只是弹出一个选项框而已。我们增加选项,肯定是让它跟后面的操作有关联的,也就是依据前面用户选择的配置来判断是否编译和链接MathFunc库。按照如下所示的修改CMakeList.txt的末尾几行:

# add the MathFunctions library?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  SET (EXTRA_LIBS ${EXTRA_LIBS} MathFunc) #首先得包含它自身,然后再增加MathFunc库,相当于系统环境变量的path包含自身和新增的路径
endif (USE_MYMATH)

# add executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Turorial ${EXTRA_LIBS})

这个例子里还是用了变量(EXTRA_LIBS)来收集后面链接进可执行文件的时候任意可选的库(也就是MathFunc库)。这是一个常用的方法,在工程非常大有很多optional的组件的时候,可以让这个编译文件保持干净。

代码的修改就更直接了(用宏定义隔离开):

// 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,只要在TutorialConfig.h.in里面添加一行

#cmakedefine USE_MYMATH

3. 增加make install和测试

下一步我们会添加install规则和testing到工程。install规则非常直接。对于MathFunc库,我们通过在MathFunctions目录下的CMakeList文件中添加如下两行来安装库和头文件。

install (TARGETS MathFunc DESTINATION bin)
install(FILES MathFunctions.h DESTINATION include)

对于应用程序,为了安装executable和配置头文件,需要在工程根目录下的CMakeList.txt文件中添加下面几行

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

到了这里你就可以构建整个tutorial了,通过命令make install,系统会自动安装对应的头文件,库以及可执行文件。CMake变量CMAKE_INSTALL_PREFIX用来指定这些文件需要安装到哪个根目录。这里没有设置,可以在工程根目录下给CMAKE_INSTALL_PREFIX设定,也可以在命令行中设定。

添加测试用例也很直接,只要在工程根目录下的CMakeList.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来执行这些测试用例。如果你希望添加很多测试用例来测试不同的输入值,这个时候推荐你创建一个宏,这样添加新的case会更轻松:

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

每次调用do_test,都会添加一个新的test case到工程。

4. 添加系统回顾 

下一步我们考虑在工程中添加一些代码,这些代码会依赖的某些特性在运行的目标平台上可能没有。比如说,我们添加了一些代码,这些代码需要用到log和exp函数,但某些目标平台上可能没有这些库函数。如果平台有log函数那么我们就是用log来计算平方根,我们首先通过CheckFunctionExists.cmake宏中的check_function_exists 命令测试链接器是否能够在链接阶段找到这些函数,在工程根目录下的CMakeList文件中添加如下内容。将下面这段代码放在 configure_file 命令前。

# does this system provide the log and exp functions?
include (CheckFunctionExists) #实际是include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

下一步,如果CMake发现平台有我们需要的这些函数,则需要修改TutorialConfig.h.in来定义这些值

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

有一点很重要,就是log和exp的测试工作需要在配置TutorialConfig.h前完成。最后在mysqrt函数中(mysqrt.cxx中)我们可以提供一个可选的实现方式:

// 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
  . . .

或者

#ifdef HAVE_EXP && HAVE_LOG
    printf("Now we use the standard library. \n");
    double result = exp(log(x)*0.5);
#else
    printf("Now we use our own Math library. \n");
    double result = mysqrt(x);
#endif

 5.添加生成文件和生成器

这一节中我们会演示一下怎么添加一个生成的源文件到引用程序的构建过程中。例如说,我们希望在构建过程中创建一个预先计算好的平方根表,然后把这个表格编译进我们的应用程序。首先我们需要一个能生成这张表的程序。在MathFunctions子目录中,定义一个新的源文件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,把一个数组写到一个文件里,这个文件又作为.h文件被其他程序使用
  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;
}

注意到这里的需要传递正确的输出文件给app,然后才会生成table。下一步是在MathFunctions的CMakeList.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(MathFunc mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )
  • 首先添加可执行的MakeTable。
  • 然后我们添加一个用户命令指定怎么通过允许MakeTable来生成Table.h这个文件。
  • 下一步需要让CMAKE知道mysqrt.cxx依赖生成的Table.h。把生成的Table.h添加到MathFunc库的资源列表中。
  • 还需要添加当前的bin的目录添加到include的list中,这样mysqrt.cxx编译时候可以找到Table.h。
  • 最后编译包含Table.h的mysqrt.cxx来生成MathFunc库

到这儿工程根目录下的CMakeList.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) 
include (CheckFunctionExists)
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} MathFunc)
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.in 文件如下:

// 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

MathFunctions的文件CMakeLists.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(MathFunc mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h) 
install (TARGETS MathFunc DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)


链接:https://www.jianshu.com/p/6df3857462cd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值