CMake你该知道的事
CMake是一个开源的跨平台工具系列,用于构建、测试和打包软件。CMake用于使用简单的平台和编译器独立配置文件控制软件编译过程,并生成可在您选择的编译器环境中使用的本机makefile和工作区。CMake工具套件是由Kitware创建的,以满足对ITK和VTK等开源项目强大的跨平台构建环境的需求。CMake是Kitware的软件开发商业支持的开源平台集合的一部分。
工作需要去仔细研究了一下CMake并谈谈理解。
文章目录
CMake基本使用
简单示例
示例:求一个数的平方根,编写一个头文件cal_sqrt.h和一个.cc文件main.cc。
//目录结构
// └── demo
// ├── build
// ├── cal_sqrt.h
// ├── CMakeLists.txt
// └── main.cc
// cal_sqrt.h
#ifndef __CAL_SQRT_H__
#define __CAL_SQRT_H__
#include <cmath>
double cal_sqrt(double value);
#endif
// main.cc
#include "cal_sqrt.h"
#include <iostream>
int main() {
double value = 81.0;
double res = 0.0;
res = cal_sqrt(value);
std::cout<<value<<" sqrt result is: "<<res<<std::endl;
return 0;
}
double cal_sqrt(double value) {
return sqrt(value);
}
CMakeList.txt内容:
cmake_minimum_required(VERSION 2.8) #1 指定 cmake 的最小版本
project(cal_sqrt) #2 设置项目名
include_directories( #3 包含头文件目录
${PROJECT_SOURCE_DIR}/include
)
set(SRC #4 添加可执行源文件
${PROJECT_SOURCE_DIR}/main.cc
)
add_executable(cal_sqrt ${SRC})
使用set添加需要的源文件,将所有源文件包含到SRC中,再使用add_executable(XXX ${SRC})生成可执行文件。
注意:使用add_executable(XXX ${SRC})生成可执行文件时,SRC中包含的所有源文件中只能有一个main()函数,否则在cmake在执行add_executable(XXX ${SRC})会识别到多个main()函数,导致生成可执行文件失败。SRC中包含多个main()函数时,可使用如下方法生成多个可执行文件。
set(SRC
${PROJECT_SOURCE_DIR}/main1.cc
${PROJECT_SOURCE_DIR}/main2.cc
)
foreach (src ${SRC})
get_filename_component(name ${src} NAME_WE)
add_executable(${name} ${src})
endforeach (src ${SRC})
二、cmake常用命令
以上用到的都是cmake中比较常用的命令,cmake还有许多其他常用命令。
aux_source_directory
aux_source_directory,查找源文件并保存到相应的变量中。
# 搜索dir目录下所有的源文件并将其存储在变量VAR中。
aux_source_directory(dir VAR)
add_library
1.生成库文件
该命令的主要作用就是将指定的源文件生成库文件,然后添加到工程中去。该命令常用的语法如下。
add_library( [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2] […])
- 生成一个名为的库文件。
- 指定STATIC, SHARED, MODULE参数来指定要创建的库的类型, STATIC对应的静态库(.a),SHARED对应共享动态库(.so),MODULE库是一种不会被链接到其它目标中的插件。
- [EXCLUDE_FROM_ALL], 如果指定了这一属性,对应的一些属性会在目标被创建时被设置(指明此目录和子目录中所有的目标,是否应当从默认构建中排除, 子目录的IDE工程文件/Makefile将从顶级IDE工程文件/Makefile中排除)
- source1 source2 …用来指定源文件
2.导入已有的库
add_library( [STATIC | SHARED | MODULE | UNKNOWN] IMPORTED)
导入了一个已存在的库文件,导入库一般配合set_target_properties使用,这个命令用来指定导入库的路径,比如:
add_library(test SHARED IMPORTED)
set_target_properties( test # 指定目标库名称
PROPERTIES IMPORTED_LOCATION # 指明要设置的参数
libs/src/${ANDROID_ABI}/libtest.so # 设定导入库的路径
)
link_directories
link_directories,指定链接库搜索目录。
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/libs
)
其中,CMAKE_CURRENT_SOURCE_DIR指当前处理的 CMakeLists.txt 所在的路径。
target_link_libraries
target_link_libraries,将目标文件与库文件进行链接。该指令的语法如下.
# 将lib1, lib2,...链接到<name>上
target_link_libraries(<name> [lib1] [lib2] [...])
上述为add_executable()生成的可执行文件,lib1,lib2,…为add_library生成或导入的库。
set
set,设置cmake变量。
例子:
# 设置可执行文件的输出路径(EXCUTABLE_OUTPUT_PATH是全局变量)
set(EXECUTABLE_OUTPUT_PATH [output_path])
# 设置库文件的输出路径(LIBRARY_OUTPUT_PATH是全局变量)
set(LIBRARY_OUTPUT_PATH [output_path])
# 设置C++编译参数(CMAKE_CXX_FLAGS是全局变量)
set(CMAKE_CXX_FLAGS "-Wall std=c++11")
# 设置源文件集合(SOURCE_FILES是本地变量即自定义变量)
set(SOURCE_FILES main.cc test.cc ...)
add_subdirectory
如果当前目录下还有子目录时可以使用add_subdirectory,子目录中也需要包含有CMakeLists.txt。
例子:
# sub_dir指定包含CMakeLists.txt和源码文件的子目录位置
# binary_dir是输出路径, 一般可以不指定
add_subdirecroty(sub_dir [binary_dir])
set_property
set_property,在给定的作用域内设置一个命名的属性。
set_property(<GLOBAL |
DIRECTORY [dir] |
TARGET [target ...] |
SOURCE [src1 ...] |
TEST [test1 ...] |
CACHE [entry1 ...]>
[APPEND] [APPEND_STRING]
PROPERTY <name> [value ...])
在某个域中对零个或多个对象设置一个属性。第一个参数决定该属性设置所在的域。它必须为下面中的其中之一。
- GLOBAL域是唯一的,并且不接特殊的任何名字。
- DIRECTORY域默认为当前目录,但也可以用全路径或相对路径指定其他的目录(前提是该目录已经被cmake处理)。
- TARGET域可命名零或多个已经存在的目标。
- SOURCE域可命名零或多个源文件。注意:源文件属性只对在相同目录下的目标是可见的(CMakeLists.txt)。
- TEST域可命名零或多个已存在的测试。
- CACHE域必须命名零或多个已存在条目的cache.
必选项PROPERTY后面紧跟着要设置的属性的名字。其他的参数用于构建以分号隔开的列表形式的属性值。如果指定了APPEND选项,则指定的列表将会追加到任何已存在的属性值当中。如果指定了APPEND_STRING选项,则会将值作为字符串追加到任何已存在的属性值。
message
message,用于打印信息
message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(STATUS "this is status message" )
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败
add_definitions
add_definitions,为当前路径以及子目录的源文件加入由-D引入的define flag
# 添加FOO宏定义,在源文件中使用#if defined(FOO),可以针对定义了FOO宏的情况做额外的处理
add_definitions(-DFOO ...)
# add_definitions一般可以与option结合使用,控制代码的开启和关闭
# 在cmake时可以添加参数控制宏的开启和关闭
# 开启: cmake -DUSE_MACRO=on ..
# 关闭: cmake -DUSE_MACRO=off ..
option(USE_MACRO "Build the project using macro" OFF)
if(USE_MACRO)
add_definitions("-DUSE_MACRO")
endif(USE_MACRO)
file
file,文件操作命令。
# 将message写入filename文件中,会覆盖文件原有内容
file(WRITE filename "message")
# 将message写入filename文件中,会追加在文件末尾
file(APPEND filename "message")
# 从filename文件中读取内容并存储到var变量中,如果指定了numBytes和offset,
# 则从offset处开始最多读numBytes个字节,另外如果指定了HEX参数,则内容会以十六进制形式存储在var变量中
file(READ filename var [LIMIT numBytes] [OFFSET offset] [HEX])
# 重命名文件
file(RENAME <oldname> <newname>)
# 删除文件, 等于rm命令
file(REMOVE [file1 ...])
# 递归的执行删除文件命令, 等于rm -r
file(REMOVE_RECURSE [file1 ...])
# 根据指定的url下载文件
# timeout超时时间; 下载的状态会保存到status中; 下载日志会被保存到log; sum指定所下载文件预期的MD5
# 值,如果指定会自动进行比对,如果不一致,则返回一个错误; SHOW_PROGRESS,进度信息会以状态信息的形式被
# 打印出来
file(DOWNLOAD url file [TIMEOUT timeout] [STATUS status] [LOG log] [EXPECTED_MD5 sum] [SHOW_PROGRESS])
# 创建目录
file(MAKE_DIRECTORY [dir1 dir2 ...])
# 会把path转换为以unix的/开头的cmake风格路径,保存在result中
file(TO_CMAKE_PATH path result)
# 它会把cmake风格的路径转换为本地路径风格:windows下用"\",而unix下用"/"
file(TO_NATIVE_PATH path result)
三、常用变量
预定义变量
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置
系统信息
CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3
CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4
CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1
CMAKE_SYSTEM:系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 x86_64,aarch64
UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin
设置编译选项
BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
CMAKE_C_FLAGS:设置 C 编译选项
CMAKE_CXX_FLAGS:设置 C++ 编译选项
四、构建方式
使用cmake命令进行构建,构建分为内部构建(in-source)和外部构建(out-of-source)
内部构建
在源文件目录下构建,执行cmake .,生产的Makefile文件、以及一些cmake缓存文件与源文件同目录。
外部构建
内部构建生成的cmake的中间文件与源代码文件混杂在一起,并且cmake没有提供清理这些中间文件的命令。
所以cmake推荐使用外部构建,步骤如下。
- 在CMakeLists.txt的同级目录下,新建一个build文件夹
- 进入build文件夹,执行cmake …命令,这样所有的中间文件以及Makefile都在build目录下了
- 在buil目录下执行make就可以得到可执行文件
复杂项目示例
项目结构
├── demo
│ ├── build
│ ├── CMakeLists.txt
│ ├── main.cc
│ └── test
│ ├── CMakeLists.txt
│ ├── test.cc
│ └── test.h
项目内容
使用自定义编译选项,在编译时,如果设置-DUSE_SQRT=off,则计算9的平方,如果设置-DUSE_SQRT=on,则计算9的平方根。默认-DUSE_SQRT=on。
//test.h
#ifndef __TEST_H__
#define __TEST_H__
#include <cmath>
double calculation(double value);
#endif
//test.cc
#include "test.h"
double calculation(double value) {
#if defined(USE_SQRT)
return sqrt(value);
#else
return pow(value, 2);
#endif
}
//main.cc
#include "./test/test.h"
#include <iostream>
int main() {
double value = 9.0;
double res = 0.0;
res = calculation(value);
#if defined(USE_SQRT)
std::cout<<value<<" sqrt result is: "<<res<<std::endl;
#else
std::cout<<value<<" pow(2) result is: "<<res<<std::endl;
#endif
return 0;
}
在CMakeLists.txt中添加自定义编译选项。
# ./CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(demo)
# 设置USE_SQRT编译选项
option(USE_SQRT "Build the project with sqrt" ON)
# 如果编译选项-DUSE_SQRT=ON,则添加宏定义USE_SQRT
if(USE_SQRT)
add_definitions("-DUSE_SQRT")
endif(USE_SQRT)
include_directories(
${PROJECT_SOURCE_DIR}/test
)
# 添加子目录
add_subdirectory(test)
# 设置链接库
set(EXTRA_LIBS ${EXTRA_LIBS} test)
set(MAIN_SRC
${PROJECT_SOURCE_DIR}/main.cc
)
# 生成可执行文件
add_executable(main ${MAIN_SRC})
# 将可执行文件mian与库文件进行链接
target_link_libraries (main ${EXTRA_LIBS})
# ./test/CMakeLists.txt
# 生成动态库
add_library(test SHARED ${CMAKE_CURRENT_SOURCE_DIR}/test.cc)