CMake 零基础入门

1,119 阅读3分钟

最近学习 opencv、ffmpeg,这些优秀的库都是使用 c/c++ 来编写的,这些项目都是由多个项目组成的,如果没有一个很好的工具去管理它们之间的关系,这对于每个开发者来说,如果没有原作者的帮助都是不可能完成搭建的。因此就引入了 CMake ,我们在下载它们的源码时也会带着 CMakeLists.txt 文档,这样开发者只要使用 CMake 工具就可以完成源码的构建工作。

那么下面就分几个步骤讲述一般用法

单个源文件构建

我们需要将源文件 main.c 构建成可执行文件,只需要3行配置就可以完成。这里的 main.c 是求数的次方,这两个数由运行时手动给出。其代码如下:

#include <stdio.h>
#include <stdlib.h>
/**
 * power - Calculate the power of number.
 * @param base: Base value.
 * @param exponent: Exponent value.
 *
 * @return base raised to the power exponent.
 */
double power(double base, int exponent)
{
    int result = base;
    int i;
    
    if (exponent == 0) {
        return 1;
    }
    
    for(i = 1; i < exponent; ++i){
        result = result * base;
    }
    return result;
}

int main(int argc, char *argv[])
{
    if (argc < 3){
        printf("Usage: %s base exponent \n", argv[0]);
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);
    double result = power(base, exponent);
    printf("%g ^ %d is %g\n", base, exponent, result);
    return 0;
}

接着要确保源文件和 CMakeLists.txt 文档在同级目录, 如下图所示。

CMakeLists.txt 配置文件

# 支持 cmake 最小版本为 2.6
cmake_minimum_required(VERSION 2.6)

# 项目名称
project(Demo1)

# 指定源代码,生成可执行文件 Demo1
add_executable(Demo1 main.c)

通过这几行配置就可以生成一个可执行文件 Demo1, 你需要使用如下命令构建

// 在 CMakeLists.txt 统计目录执行
1. cmake .
2. make 

多目录源文件构建

多数情况下,我们的代码都是由很多目录构成。我们需要在构建的子目录下也加入 CMakeLists.txt 文档来描述源码的关系。例如:我们将 main.c 中的 power 函数抽离到一个新目录 math 下,如下图所示:

math/CMakeLists.txt 内容如

# 查找所有源文件, 将其存放到 SRC_DIR 变量中.
aux_source_directory(. SRC_DIR)

# 将源文件生成链接库
add_library(MathFunctions ${SRC_DIR})

主目录下的 CMakeLists.txt 加入以下几行

# 将源文件下 math 目录头文件添加到编译器头文件搜索目录.
include_directories ("${PROJECT_SOURCE_DIR}/math")

# 将 math 目录加入到 cmake 构建. 会执行子目录中的 CMakeLists.txt
add_subdirectory(math)

# 指定源代码,生成可执行文件 Demo2
add_executable(Demo2 main.c)

# 将 MathFunctions 链接到 Demo1
target_link_libraries(Demo2 MathFunctions)

最后就是执行 cmake . 和 make 命令即可生成可执行文件。

自定义构建配置

在某些情况下,我们需要对源码进行控制,比如我们上面的代码中,求数的次方。我们也可以使用库函数来实现。我们可以在构建的时候进行控制。那么我们改进下主目录下的 CMakeLists.txt 文件。

# 引入头文件,对源码进行配置, 从 config.h.in 配置的变量会输出到 config.h 中
# 这点就是利用宏定义 #define 
configure_file(
    "${PROJECT_SOURCE_DIR}/config.h.in"
    "${PROJECT_BINARY_DIR}/config.h"
)

# 可选配置,可对变量 MY_MATH_FLAG 进行设置开关 ON/OFF
option(MY_MATH_FLAG 
    "是否使用自定义函数实现开平方计算"
    OFF
)

if (MY_MATH_FLAG)
    # 将源文件下 math 目录头文件添加到编译器头文件搜索目录.
    include_directories ("${PROJECT_SOURCE_DIR}/math")
    # 将 math 目录加入到 cmake 构建. 会执行子目录中的 CMakeLists.txt
    add_subdirectory(math)
    set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif(MY_MATH_FLAG)


# 指定源代码,生成可执行文件 Demo3
# 这里也可以使用 aux_source_directory(. SRC_DIR) 来替换,这样的好处就是不用写多个源文件
add_executable(Demo3 main.c)

# 将 MathFunctions 链接到 Demo3
target_link_libraries(Demo3 ${EXTRA_LIBS})

我们可以使用 ccmake 命令来进行编译,通过它可以指定 MY_MATH_FLAG 变量 ON/OFF 状态。然后最终就会决定 config.h 中生成的的内容,如果 OFF 会生成

/* #undef MY_MATH_FLAG */

为 ON 会生成

 #define MY_MATH_FLAG

有了 config.h,我们就可以在源码中进行条件编译

#include <stdio.h>
#include <stdlib.h>
#include "config.h"

// MY_MATH_FLAG 条件编译
#ifdef MY_MATH_FLAG
    #include "CalcPower.h"
#else
    #include <math.h>
#endif

int main(int argc, char *argv[])
{
    if (argc < 3){
        printf("Usage: %s base exponent \n", argv[0]);
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);

    #ifdef MY_MATH_FLAG
        double result = power(base, exponent);
        printf("使用自定义函数实现平方计算: %g ^ %d is %g\n", base, exponent, result);
    #else
        double result = pow(base, exponent);
        printf("使用系统函数实现平方计算: %g ^ %d is %g\n", base, exponent, result);
    #endif
        printf("%g ^ %d is %g\n", base, exponent, result);
    
    return 0;
}

添加测试

一些情况下,我们需要构建完成后进行测试,我们可以使用 make install 来实现,改造一下主目录下的 CMakeLists.txt 文件

# 引入头文件,对源码进行配置
configure_file(
    "${PROJECT_SOURCE_DIR}/config.h.in"
    "${PROJECT_BINARY_DIR}/config.h"
)

# 可选配置,可对变量 MY_MATH_FLAG 进行设置开关 ON/OFF
option(MY_MATH_FLAG 
    "是否使用自定义函数实现开平方计算"
    ON
)

if (MY_MATH_FLAG)
    # 将源文件下 math 目录头文件添加到编译器头文件搜索目录.
    include_directories ("${PROJECT_SOURCE_DIR}/math")
    # 将 math 目录加入到 cmake 构建. 会执行子目录中的 CMakeLists.txt
    add_subdirectory(math)
    set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif(MY_MATH_FLAG)


# 指定源代码,生成可执行文件 Demo1
# 这里也可以使用 aux_source_directory(. SRC_DIR) 来替换,这样的好处就是不用写多个源文件
add_executable(Demo4 main.c)

# 将 MathFunctions 链接到 Demo1
target_link_libraries(Demo4 ${EXTRA_LIBS})

# 指定 Demo4 安装路径
install (TARGETS Demo4 DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"        
         DESTINATION include)


# 添加测试
enable_testing()
#[[
add_test (test_run5_2 Demo4 5 2)
set_tests_properties (test_run5_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 25")

add_test (test_run2_2 Demo4 2 2)
set_tests_properties (test_run2_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 4")

add_test (test_run3_2 Demo4 3 2)
set_tests_properties (test_run3_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 9")

add_test (test_run10_2 Demo4 10 2)
set_tests_properties (test_run10_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 100")
]]

# 使用宏定义简化测试配置的书写
macro(do_test arg1 arg2 result)
    add_test(test_run${arg1}_${arg2} Demo4 ${arg1} ${arg2})
    set_tests_properties(test_run${arg1}_${arg2} PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )    
endmacro(do_test arg1 arg2 result)

do_test(5 2 "is 25")
do_test(2 2 "is 4")
do_test(3 2 "is 9")
do_test(10 2 "is 100")

先记录到这里,后续再补充。有需要代码的可以 点击这里, 代码都通过按 tag 区分不同的分之。可以切换到对应的分之查看。