CMake 入门 (CMake Tutorial)

For English version you can check it from
CMake.

查看源码

环境: Ubuntu 16.04 64bit
CMake版本: 3.5.1

0. 构建

构建项目的流程:

$ cd build
$ cmake ..
$ make
$ make install          # if you want to install it 

修改配置后重新构建:

$ cd build 
$ rm -rf ./*
$ make

1. 快速开始

对于一些简单的项目,三行CMakeLists.txt就可以完成工作了。这个CMakeLists.txt是这样的:

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

第一行规定了需要的最低CMake版本是2.6;第二行告诉我们项目名称为Tutorial;第三行列出生产可执行文件所需要的依赖关系。

在CMake中可以使用小写,大写或者大小写混合使用来编写CMakeLists.txt文件。

tutorial.cxx是一个很简单的C++程序,它可以根据命令行参数来计算平方根。第一版是这样的:

// A simple program that computes the square root of a number
#include 
#include 
#include 

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 of %g is %g\n", 
        inputValue, outputValue);

    return 0;
}

构建项目

由于在CMake构建过程中会生成很多中间文件,所以在顶层目录下新建一个build文件夹来存放产生的中间文件。

$ cd build
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jim/Workspaces/CMake-tutorial/build

可以看到在build目录中生成了一个Makefile文件,接下里可以直接使用make指令生成可执行文件。

使用CMake构建项目基本上都是这个流程,后面构建项目的方法也是如此。

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

我们要使用的第一个CMake功能就是为我们的项目添加一个版本号。当然可以直接在源代码中配置,但是在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
# to the source code
include_directories("${PROJECT_BINARY_DIR}")

# Add the executable
add_executable(Tutorial tutorial.cxx)

在CMake中set指令用来定义变量,上面这个文件中定义了两个变量Tutorial_VERSION_MAJORTutorial_VERSION_MINOR

PROJECT_SOURCE_DIRPROJECT_BINARY_DIR是CMake中预先定义的两个变量,前者表示的是顶层CMakeLists.txt所在目录,后者是执行cmake的目录,对我们而言就是build目录。

configure_file命令根据第一个参数所指的文件在构建目录中生成一个头文件,由于我们需要使用这个头文件,所以要用include_directories命令包含这个目录。

我们需要创建一个TutorialConfig.h.in文件来构建头文件:

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

我们需要修改一下tutorial.cxx文件来使用所定义的变量:

// A simple program that computes the square root of a number
#include 
#include 
#include 
#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 of %g is %g\n", 
        inputValue, outputValue);

    return 0;
}

2. 链接到库

我们将在项目中添加一个包含计算平方根功能的库。最终生成的可执行文件可以使用这个库而不是使用标准数学库中的函数。

我们会在项目中创建一个MathFunctions的子目录来存放我们库的源代码,这个子目录中应该有一个这样的CMakeLists.txt文件:

add_library(MathFunctions mysqrt.cxx)

这条命令添加了一个叫MathFunctions的库,这个库依赖mysqrt.cxx这个源文件。默认会生成静态库,如果要使用动态库的话需要添加SHARED属性:

# Use shared library
add_library(MathFunctions SHARED mysqrt.cxx)

# Use static library
add_library(MathFunctions STATIC mysqrt.cxx)

这里的SHARED和STATIC必须要使用大写。之后就会在构建目录下生成MathFunctions文件夹,其中包含了需要的动态库libMathFunctions.so或者静态库libMathFunctions.a

我们需要在MathFunctions目录下创建一个mysqrt.cxx文件,它需要包含一个mysqrt函数去实现求平方根的功能:

#include "MathFunctions.h"
#include 

double mysqrt(double inputValue)
{
    return sqrt(inputValue);
}

接着还需要创建MathFunctions.h这个头文件:

#ifndef MATHFUNCTIONS_H
#define MATHFUNCTIONS_H

double mysqrt(double inputValue);

#endif

我们需要在顶层的CMakeLists.txt文件中使用add_subdirectory来使库得到构建,我们还需包含一下这个目录,以便MathFunctions.h能够被搜索到:

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
# to the source code
include_directories("${PROJECT_BINARY_DIR}")
include_directories("${PROJECT_SOURCE_DIR}/MathFunctions")

add_subdirectory(MathFunctions)

# Add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial MathFunctions)

target_link_libraries是用来给项目链接库用的,第一个参数必须使用add_executable指令,第二个参数是要连接到的库。

选择是否使用库

可以在CMake中定义是否要使用库:

# Should we use our own math functions?
option(USE_MYMATH 
    "Use tutorial provided math implementation" ON)

option命令可以定义一些选项,默认是OFF。

我们还需要修改一下顶层的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})

Tutorial.cxx也需要使用相应的宏定义来选择是否使用库:

// A simple program that computes the square root of a number
#include 
#include 
#include 
#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 of %g is %g\n", 
        inputValue, outputValue);

    return 0;
}

最后我们只需要在TutorialConfig.h.in配置头文件中添加相应的宏定义:

#cmakedefine USE_MYMATH

并且吧configure_file命令移动到宏USE_MYMATH定义下面,否则将在定义宏之前生成头文件。

当我们设置USE_MYMATH宏为ON时,可以看到生成的头文件中定义了这个宏,并且能看到我们的可执行文件链接到了库:

$ ldd Tutorial
    linux-vdso.so.1 =>  (0x00007fff039eb000)
    libMathFunctions.so => /home/jim/Workspaces/CMake-tutorial/build/MathFunctions/libMathFunctions.so (0x00007f2e8a86e000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2e8a4a4000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2e8a19b000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2e8aa70000)

3. 安装和测试

安装

我们可以给项目添加一下安装规则,这些规则目前都比较直白,如果要安装MathFunctions库,我们需要在MathFunctions目录下的CMakeLists.txt中添加一些安装规则:

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

其中TARGETS用来安装可执行文件,FILES用来安装一些文件,比如头文件。
DESTINATION是安装的根目录,默认是/usr/local,当然可以制定CMAKE_INSTALL_PREFIX来指定安装根目录。

bin和include是根目录下的子目录。

对于项目生成的可执行文件和头文件,我们需要在顶层的CMakeLists.txt中添加规则:

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

构建完成,生成可执行文件之后就可以使用make install安装了:

$ cd build 
$ cmake ..
$ make
$ sudo make install 
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/Tutorial
-- Set runtime path of "/usr/local/bin/Tutorial" to ""
-- Installing: /usr/local/include/TutorialConfig.h
-- Installing: /usr/local/bin/libMathFunctions.so
-- Installing: /usr/local/include/MathFunctions.h

可以看到文件安装到了相应的目录下。

在构建是使用CMAKE_INSTALL_PREFIX指定安装根目录:

$ mkdir install
$ cd build 
$ cmake .. -DCMAKE_INSTALL_PREFIX=~/Workspaces/CMake-tutorial/install
$ make
$ sudo make install 

测试

对于测试功能仅简要提及,如果想要深入了解,可以自行检索。
CMake中可以使用CTest来测试我们的代码,我们将在顶层的CMakeLists.txt文件中,添加几行测试代码来检验我们程序的正确性和完整性:

# Add some tests for our project
include(CTest)

# Does the application run?
add_test(TutorialRun 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可以查看测试结果:

$ ctest
Test project /home/jim/Workspaces/CMake-tutorial/build
    Start 1: TutorialRun
1/5 Test #1: TutorialRun ......................   Passed    0.00 sec
    Start 2: TutorialComp25
2/5 Test #2: TutorialComp25 ...................   Passed    0.00 sec
    Start 3: TutorialNegative
3/5 Test #3: TutorialNegative .................***Failed  Required regular expression not found.Regex=[-25 is 0
]  0.00 sec
    Start 4: TutorialSmall
4/5 Test #4: TutorialSmall ....................   Passed    0.00 sec
    Start 5: TutorialUsage
5/5 Test #5: TutorialUsage ....................   Passed    0.00 sec

80% tests passed, 1 tests failed out of 5

Total Test time (real) =   0.02 sec

The following tests FAILED:
      3 - TutorialNegative (Failed)
Errors while running CTest

如果想要测试一系列的输入量,可以定义一个宏来完成:

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

4. 平台相关性

添加一些和平台相关的特性,比如我们添加一些依赖于平台是否能提供log和exp功能的代码。当然,几乎所有的平台都支持这个特性。首先在顶层CMakeLists.txt中使用check_function_exists宏来检查是否提供这些功能:

# 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.in配置文件,定义一下宏。当平台支持这些特性时,自动生成相应的宏定义:

// 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 intertive approach
#endif

5. 使用CMake运行程序

这一章,我们将用CMake在构建过程中调用一个程序,生成一个平方根表文件。首先在MathFunctions目录中创建一个用来生成这个表的文件MakeTable.cxx:

// A simple program that builds a sqrt table
#include 
#include 
#include 

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(i));
        fprintf(fout, "%g,\n", result);
    }

    // Close the table with a zero
    fprintf(fout, "0};\n");
    fclose(fout);
    return 0;
}

这个程序根据命令行参数来确定生成文件的名字。

接下来要在MathFunctions下的CMakeLists.txt中添加相应的命令来生成平方根表文件:

# First we add the excutable that generates the table
add_executable(MakeTable MakeTable.cxx)

# Add the command to generate the source
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.cxx生成可执行文件MakeTable,然后调用这个程序,在目标目录下生成名为Table.h的文件。

构建编译完成之后就能在build/MathFunctions目录下看到生成的文件。

你可能感兴趣的:(CMake 入门 (CMake Tutorial))