最近学习 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 区分不同的分之。可以切换到对应的分之查看。
