CMAKE介绍
CMAKE是一种跨平台的编译配置工具,它可以在不同的平台下基于同样的源代码文件生成对应的工程文件.例如Makefile和VS工程. 使用CMake的通用流程如下:
- 编写CMakeLists.txt
- 执行cmake PATH或ccmake PATH生成Makefile
- 使用make进行编译.
本文来源于另一位博主,重新敲一边,加深记忆.
代码网址在此.
案例一:单个源文件
现在我们有一个源文件:main.cc,它包含一个函数power,main中调用了它.
#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;
}
下面在main.c同目录下编写CMakeLists.txt文件.
# CMake最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(Demo main.cc)
CMakeLists.txt的语法较为简单,由命令 注释和空格组成,其中命令是不区分大小写的.符号#后面的内容被认为是注释.命令由命令名称 小括号和参数组成,参数之间使用空格进行间隔.
上面的CMakeLists.txt文件出现了一下命令:
- cmake_minimum_required:指定运行此配置文件所需的CMake的最低版本.
- project: 参数值Demo1,表示此项目的名称是Demo1.
- add_executable:将名为mian.cc的文件编译成一个名为Demo的可执行文件.
在当前目录执行cmake . cmake就会根据平台自动生成工程文件.
可以通过-g参数指定编译工具.
例如在windows下可以通过以下代码使用mingw进行编译和测试(-DCMAKE_BUILD_TYPE==Debug的作用是指定Debug模式):
mkdir build
cd build
cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug ..
mingw32-make
.\Demo1.exe 2 3
会得到输出:
2 ^ 3 is 8
cmake在windows下的默认工程类型是已安装的最新的vs,而且没有提供修改选项.
通过在系统目录添加一个名为cmake.bat内容如下的脚本,就不用每次调用的时候指定工程类型了.
@cmake.exe -G"MinGW Makefiles" %*
也可以在CMakeLists.txt文件中添加以下代码来指定DEBUG模式.
set(CMAKE_BUILD_TYPE on)
案例二 多文件 同目录
把power函数单独写进一个MathFunctions.c的源文件中,工程目录如下:
./Demo2
|
+--main.cc
|
+--MathFunctions.cc
|
+--MathFunctions.h
此时CMakeLists.txt修改如下:
# CMake最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)
唯一的改动是在add_executable命令中增加了一个MathFunctions.cc,如果源文件太多,可以使用aux_source_directory命令,该命令会查找指定目录下的所有源文件,然后把结果存进指定变量名.语法如下:
aux_source_directory( <dir> <variable>)
然后通过${variable}来使用变量. 因此可以修改CMakeLists.txt如下:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
案例三 多文件 多目录
现在将MathFunctions.h和MathFunctions.cc文件移动到单独的Math目录下
./Demo3
|
+--main.cc
|
+--math/
|
+--MathFunctions.cc
|
+--MathFunctions.h
对于这种情况,需要在根目录和math下各建立一个CMakeLists.txt文件,先将math目录下的文件编译成静态库再由main函数调用.
根目录中的CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
# 添加链接库
target_link_libraries(Demo MathFunctions)
该文件第三行使用命令add_subdirectory指明项目包含一个子目录math,这样math目录下的CMakeList.txt 和源代码也会被处理. 第六行,使用命令 target_link_librarys指明可执行文件main需要链接一个名为MathFunctions的链接库.
子目录中的CMakeLists.txt:
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成 MathFunctions 链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
在该文件中使用命令add_library将src目录中的源文件编译为静态链接库.
案例四 自定义编译选项
CMake允许为项目增加编译选项,从而根据用户的环境和需求选择最合适的编译方案.
例如,可以把MathFunctions库设为一个可选的库,如果该选项为ON,就是用该库的数学函数来进行计算.否则就调用标准库中的数学函数库.
我们要做的第一步是在顶层的CMakeLists.txt文件中添加该选项:
# CMake最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project(Demo4)
# 是否使用自己的MathFunctions库
option (USE_MYMATH
"Use provided math implementation" ON)
# 加入一个配置头文件,用于处理CMake对源码的设置
configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${}PROJECT_BINARY_DIR}/config.h"
)
# 是否加入MathFunctions库
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 将名称保存到DIR_SRCS变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})
- 其中第七行的configure_file命令用于加入一个配置文件config.h,这个文件由CMake从config.h.in生成,通过这样的机制,可以通过预定义一些参数和变量来控制代码的生成.
- 第13行的option命令添加了一个USE_MYMATH选项,并且默认值是ON
- 第17行根据USE_MYMATH变量的值决定是否使用math目录下的MathFunctions库.
- option选项必须放置在coufibure_file 前,否则会按照没有定义USE_MYMATH来生成config.h.
修改main.cc文件
之后修改main.cc文件,让其根据USE_MYMATH的预定义值来决定调用标准库还是MathFunctions库.
#include <stdio.h>
#include <stdlib.h>
#include <config.h>
#ifdef USE_MYMATH
#include <MathFunctions.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 USE_MYMATH
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
另一种写法 上面的程序引用了一个config.h,config.h控制是否定义了USE_MYMATH.
我们创建一个config.h.in文件,内容如下:
#cmakedefine USE_MYMATH
CMake会自动根据CMakeLists配置文件中的设置生产config.h文件.
现在我们使用CMake进行编译即可得到使用MYMATH的编译版本.修改CMakeLists.txt中的USE_MYMATH为OFF即可使用库函数中的函数.
案例五 安装和测试
CMake也可以指定安装规则,以及添加测试.这两个功能分别可以通过在产生Makefile后使用make install和make test来执行.
在之前的GNU Makefile中,需要为此编写install和test两个伪目标和相应的规则,但在CMake中,这样的工作同样只需要简单的调用几条命令.
定制安装规则
首先在math/CMakeLists.txt的结尾添加下面两行:
# 指定 MathFunctions库的安装路径
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
上面的代码指明了MathFunctions库的安装路径,之后修改根目录的CMakeLists文件,在末尾添加下面几行:
# 指定安装路径
install (TARGETS Demo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)
通过上面的设置,生成的Demo文件和MathFunctions函数库libMathFunctions.o将会被复制到/usr/local/bin中,而MathFunctions.h和生成的config.h文件将会被复制到/usr/local/include中.
在windows下,默认安装目录是C:\Program Files (x86)\Demo5,Demo5是项目名
这里的/usr/local是默认安装目录,可以通过修改CMAKE_INSTALL_PREFIX变量的值来制定安装目录.
为工程添加测试
添加测试也很简单. CMake提供了一个称为CTest的测试工具.我们要做的只是在项目根目录的CMakeLists.txt文件中调用一系列的add_test命令.
# 启用测试
enable_testing()
#测试程序是否成功运行
add_test (test_run Demo 5 2)
# 测试帮助信息是否可以正常提示 PASS_REGULAR_EXPRESSION的作用是判断程序输出是否包含后面的字符串
add_test (test_usage Demo)
set_tests_properties (test_usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
# 测试5的平方
add_test (test_5_2 Demo 5 2)
set_tests_properties (test_5_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
# 测试10的5次方
add_test (test_10_5 Demo 10 5)
set_tests_properties (test_10_5 PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
# 测试2的10次方
add_test (test_2_10 Demo 2 10)
set_tests_properties (test_2_10 PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
上面的代码包含了四个测试.第一个测试 test_run用来测试程序是否成功运行并返回0值.剩下的三个测试分别用来测试5的平方 10的5次方 2的10次方是否能得到正确的结果.
其中PASS_REGULAR_EXPRESSION用来测试输出是否包含后面跟着的字符串.
测试结果如下:
PS D:\cmake_project\Demo5\build> make test
Running tests...
Test project D:/cmake_project/Demo5/build
Start 1: test_run
1/5 Test #1: test_run ......................... Passed 0.01 sec
Start 2: test_usage
2/5 Test #2: test_usage ....................... Passed 0.01 sec
Start 3: test_5_2
3/5 Test #3: test_5_2 ......................... Passed 0.01 sec
Start 4: test_10_5
4/5 Test #4: test_10_5 ........................ Passed 0.01 sec
Start 5: test_2_10
5/5 Test #5: test_2_10 ........................ Passed 0.01 sec
100% tests passed, 0 tests failed out of 5
Total Test time (real) = 0.08 sec
如果有大量测试,可以通过编写宏来简化:
macro (do_test arg1 arg2 result)
add_test (test_${arg1}_${arg2} Demo ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
do_test(5 2 "is 25")
do_test(10 5 "is 100000")
do_test(2 10 "is 1024")
支持gdb
让CMake支持gdb的设置也很容易,只需要指定Debug模式下开启-g选项:
set (CMAKE_BUILD_TYPE "Debug")
set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
然后就可以对生成的程序使用gdb进行调试.
添加环境检查
有时候我们需要对系统环境做些检查,例如要使用一个平台相关的特性的时候.
在这个例子中,我们检查系统是否自带pow函数.如果有,就使用它;否则使用我们自己定义的power函数.
添加CheckFunctionExists宏
首先在定层CMakeLists文件的configure_file前添加CheckFunctionExists.cmake宏,并调用check_function_exists命令测试链接器是否能在链接阶段找到pow函数.
在CMakeLists.txt中可以通过if(HAVE_POW)和if(NOT HAVE_POW)来判断系统是否提供了pow函数.在源代码中可以通过config.h中是否define HAVE_POW来判断系统是否提供了pow函数.
# 检查系统是否支持pow函数
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
预定义相关宏变量
接下来修改configure.h.in,预定义相关的宏变量.
// does the platform provide pow function?
#cmakedefine HAVE_POW
在代码中使用宏和函数
最后一步是修改main.cc,在代码中使用宏和函数:
#ifdef HAVE_POW
printf("Now we use the standrad library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = pow(base, exponent);
#endif
添加版本号
给项目添加和维护版本号是一个好习惯,这样有利于用户了解每个版本的维护情况,并及时了解当前多用的版本是否过时,或是否可能出现不兼容的情况.
首先修改顶层CMakeLists文件,在project命令后添加如下两行:
set (Demo_VERSION_MAJOR 1)
set (Demo_VERSION_MINOR 0)
分别指定当前项目的主版本号和副版本号.
之后,为了在代码中获取项目信息,我们可以修改config.h.in文件,添加两个预定义变量:
//the configured options and settings for Tutorial
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@
这样就可以直接在代码中打印版本信息了:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
// print version info
printf("%s Version %d.%d\n",
argv[0],
Demo_VERSION_MAJOR,
Demo_VERSION_MINOR);
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#if defined (HAVE_POW)
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
生成安装包
本节将学习如何配置生成各种平台上的安装包,包括二进制安装包和源码安装包.为了完成这个任务,我们需要用到CPack,它是CMake提供的一个工具,专门用于打包.
首先在顶层的CMakeLists.txt文件尾部添加下面几行:
# 构建一个CPack安装包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
${CMAKE_CURRENT_SOURCE_DIR}/License.txt)
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)
上面的代码做了一下几个工作:
- 导入InstallRequeredSystemLibraries模块,以便之后导入CPack模块;
- 设置一些CPack相关变量,包括版权信息和版本信息,其中版本信息用了上一节定义的版本号;
- 导入CPack模块.
接下来的工作是像往常一样构建工程,并执行cpack命令.
- 生成二进制安装包
cpack -C CPackConfig.cmake
- 生成源代码安装包
cpack -C CPackSourceConfig.cmake
我们可以试一下,执行cpack -C CPackConfig.cmake命令:
PS D:\cmake_project\Demo8\build> cpack -C .\CPackConfig.cmake CPack: Create package using NSIS CPack: Install projects CPack: - Run preinstall target for: Demo8 CPack: - Install project: Demo8 CPack: Create package CPack: - package: D:/cmake_project/Demo8/build/Demo8-1.0.1-win32.exe generated.
然后就可以使用Demo8-1.0.1-win32.exe文件来进行安装了,效果类似于make install,但是即使设置了CMAKE_INSTALL_PREFIX,安装目录也是C:\Program Files (x86)/project_name.
- 生成二进制安装包
导入第三方库
在CMake中导入第三方库可以通过include_directories和link_directories设置第三方库的路径,然后使用target_link_libraries进行链接.
在尝试对OpenCV进行编译的时候,笔者发现OpenCV自带了一个OpenCVConfig.CMAKE文件,只要在CMakeLists.txt中include这个文件,就能直接使用已经设置好的CMAKE变量.
project (yinxie_opencv)
cmake_minimum_required(VERSION 3.8)
SET (CMAKE_BUILD_TYPE "Debug")
SET (CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
include( "C:/Users/cat/Documents/code/opencv/build_mingw_debug/OpenCVConfig.cmake" ) # 直接引入 cmake 文件
# 在opencv的bin目录中有OpenCVConfig.cmake文件,也可以使用 find_package(OpenCV REQUIRED) 代替本行
message( STATUS "OpenCV library status:" ) # 输出一下得到的变量
message( STATUS " version: ${OpenCV_VERSION}" )
message( STATUS " libraries: ${OpenCV_LIBS}" )
message( STATUS " include path: ${OpenCV_INCLUDE_DIRS}" )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable(Demo main.cpp)
target_link_libraries( Demo ${OpenCV_LIBS} ) # exe 链接 OpenCV
install(TARGETS Demo DESTINATION .)
链接本地库文件
link_directories并不推荐,官方推荐使用find_library获得库绝对目录然后接target_link_libraries.
find_library(VI_LIB vi ./vi/lib)
target_link_libraries(${PROJECT_NAME} ${VI_LIB})
将其他平台的项目迁移到CMake
CMake可以很轻松的构建出在适合各个平台执行的工程环境,如果当前的工程环境不是CMAKE,而是其它平台,也是有可能一直成功的.下面是几个常用平台的迁移方案.
autotools
- am2cmake可以将autotools系的项目转换到CMake,这个工具的成功案例是KDE.
- AlterNative Automake2CMake可以转换使用automake的KDevelop工程项目.
- Converting autoconf tests
使用CMake编译QT项目
使用mingw编译QT后就可以像opencv一样加入到CMakeLists.txt文件中.
实例如下:
set(CMAKE_INCLUDE_CURRENT_DIR ON)#qt建议包含当前目录
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)#这两个是qt的自动工具
find_package(Qt5 COMPONENTS Core Quick Widgets Gui PrintSupport REQUIRED)#去寻找qt库的cmake配置文件,可以根据需要添加对应的模块
#无需 include_directories()
FILE(GLOB FORMS "form/*.ui")
QT5_WRAP_UI(FORMS_UIC ${FORMS})#等价于 uic -o ui_*.h *.h
FILE(GLOB MOCS "inc/*.h")
QT5_WRAP_CPP(HEADERS_MOC ${MOCS})
FILE(GLOB RES "qrc/*.qrc")
QT5_ADD_RESOURCES(RES_RCC ${RES})
aux_source_directory(. DIR_SRCS)
add_executable(${PROJECT_NAME} ${DIR_SRCS} ${FORMS_UIC} ${HEADERS_MOC} ${RES_RCC})
target_link_libraries(
${PROJECT_NAME}
Qt5::Core
Qt5::Quick
Qt5::Widgets
Qt5::Gui
Qt5::PrintSupport
${OpenCV_LIBS})
#如果没有下面的代码 QT程序在某些情况下会默认生成一个命令行窗口
# generate proper GUI program on specified platform
# if(WIN32) # Check if we are on Windows
# if(MSVC) # Check if we are using the Visual Studio compiler
# set_target_properties(${PROJECT_NAME} PROPERTIES
# WIN32_EXECUTABLE YES
# LINK_FLAGS "/ENTRY:mainCRTStartup"
# )
# elseif(CMAKE_COMPILER_IS_GNUCXX)
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows") # Not tested
# else()
# message(SEND_ERROR "You are using an unsupported Windows compiler! (Not MSVC or GCC)")
# endif(MSVC)
# elseif(APPLE)
# set_target_properties(${PROJECT_NAME} PROPERTIES
# MACOSX_BUNDLE YES
# )
# elseif(UNIX)
# # Nothing special required
# else()
# message(SEND_ERROR "You are on an unsupported platform! (Not Win32, Mac OS X or Unix)")
# endif(WIN32)