cmake教程(一)基本使用规则

查看cmake版本: cmake --version,Ubuntu自带的是3.5.1

cmake和qmake都用于产生Makefile,然后执行make命令进行编译,make还有其他参数,叫做make目标

升级 CMake

1
2
3
4
5
6
7
8
9
sudo apt-get install -y build-essential libssl-dev
wget https://github.com/Kitware/CMake/releases/download/v3.19.2/cmake-3.19.2.tar.gz
tar -zxvf cmake-3.19.2.tar.gz
cd cmake-3.19.2/
./bootstrap
make
sudo make install
hash -r
cmake --version

报错: Could not find CMAKE_ROOT !!! CMake has most likely not been installed correctly. 先执行hash -r再使用

还可参考: cmake源码安装 安装CMake和cmake-gui

cmake-gui

where is the source code里选择源代码位置,在where to build the binaries里选择编译出的文件,一般是在源代码目录里新建build文件夹。

先点一次Configure,出现配置对话框,选默认或者交叉编译(最后一个选项)。如果出现红色区域,再点一次。直到没有红色区域之后,点击configure,配置完成后点击generate,会在build文件夹下生成工程文件,然后去build文件夹里执行make

常见的make目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
make all:编译程序、库、文档等(等同于make)

make install:安装已经编译好的程序,会默认把程序安装至 /usr/local/bin

make unistall:卸载已经安装的程序。

make clean:删除由make命令产生的文件(例如object file,*.o)

make -j N : 同时允许N个任务进行,如果不指定N则进行无限个任务,结果会将CPU性能榨干。N的最大值取决于CPU性能,不要把N设置太大,更不要不指定,否则可能死机。

make check:测试刚刚编译的软件(某些程序可能不支持,有时会测试很长时间,这就没有必要)

# 有时碰到这样的情况,第一次make会出错,再次make却能成功,以下命令用于这种情况
make -i: 忽略指令执行错误,并继续执行,且如果出错的话就会生成目标文件。这个一般用在调试的时候。
make -k: 出错也不停止运行,不仅可以忽略指令错误,而且还能忽略makefile规则错误

注释方法: CMake 3.0以上版本的多行注释:从#[[开始,在块注释的另一端以]]结束。但是并不好用,似乎和Tab对齐有关。



语法规则:

  • 变量使用${}方式取值, 但是在IF控制语句中是直接使用变量名
  • 环境变量使用$ENV{}方式取值, 使用SET(ENV{VAR} VALUE)赋值
  • 指令(参数1 参数2…) 参数使用括弧括起, 参数之间使用空格或分号分开

有时候看到一些工程有一个cmake文件夹,里面放几个cmake文件,打开发现是一些cmake的命令。这是因为工程规模太大,有些库比如PCL在每个CMakeLists里使用,每次都添加太麻烦,所以做成文件形式,在用到的时候在CMakeListsinclude(cmake/PCL.cmake)

基本规则

预定义变量,设置方式是set(VARIABLE value),大小写都是敏感的,可以用message函数查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PROJECT_NAME   #通过PROJECT指令定义的项目名称
PROJECT_SOURCE_DIR #工程的根目录
PROJECT_BINARY_DIR #运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build
CMAKE_CURRENT_SOURCE_DIR #当前处理的CMakeLists.txt所在的路径
EXECUTABLE_OUTPUT_PATH #目标可执行文件的存放位置
LIBRARY_OUTPUT_PATH #目标链接库文件的存放位置

CMAKE_MAJOR_VERSION #cmake主版本号,如2.8.6中的2
CMAKE_MINOR_VERSION #cmake次版本号,如2.8.6中的8
CMAKE_PATCH_VERSION #cmake补丁等级,如2.8.6中的6
CMAKE_SYSTEM #系统名称,包含内核版本,例如Linux-2.6.22
CAMKE_SYSTEM_NAME #不包含版本的系统名,如Linux
UNIX #在所有的类UNIX平台为TRUE,包括OS X和cygwin
WIN32 #在所有的win32平台为TRUE,包括cygwin

BUILD_SHARED_LIBS #控制默认的库编译方式,默认编译生成的库都是静态库
CMAKE_C_COMPILER/CMAKE_CXX_COMPILER #指定C/C++编译器
CMAKE_CXX_FLAGS # 设置C++编译选项
CMAKE_BUILD_TYPE # 常用的编译类型 Debug Release
BUILD_SHARED_LIBS # 编译动态链接库(ON,OFF)

add_compile_options

下面的代码里,foo不会按照-Wall编译,但是bar会。

1
2
3
add_library(foo ...)
add_compile_options(-Wall 03)
add_library(bar ...)

-Wall显示所有警告。
O3(字母O)是编译优化的程度选择,有O1 O2 O3。数字越大编译优化越多,程序执行速度越快 但编译时间越长,不过一般程序看不出来。

CMAKE_CXX_STANDARD

CMake 3.1或者更高版本可使用set(CMAKE_CXX_STANDARD 11),不能用17

最好把add_compile_options(-std=c++17) 改为跨平台的写法: set(CMAKE_CXX_STANDARD 17),也就是x86arm平台

./configure命令

一般第三方库里都有个configure文件,它是个Shell脚本,内容很多../configure 是用来检测你的安装平台的目标特征的,比如它会检测你是不是有CC或GCC。主要功能是生成 Makefile,为下一步的编译做准备,你可以通过在./configure后加上参数来对安装进行控制,比如./configure --prefix=/usr是将该软件安装在 /usr 下面,执行文件就会安装在/usr/bin(而不是默认的 /usr/local/bin),资源文件就会安装在 /usr/share(而不是默认的/usr/local/share)

message函数

1
2
3
message(STATUS  "12345")   #如果不加STATUS,不会在前面加--标志
message(STATUS " include dirs: ${catkin_INCLUDE_DIRS}" ) #有一个--开头
message(STATUS " include dirs:" ${catkin_INCLUDE_DIRS} ) # 同上面等价

如果在执行cmake时,遇到一个错误情况需要停止执行,可以用FATAL_ERROR

1
2
3
if( SOME_COND )
message( FATAL_ERROR "You can not do this at all, CMake will exit." )
endif()

如果出现这种情况还要继续编译,那么就换成SEND_ERROR


指令最好全用大写
路径名中不要用\,而是用/,例如include_directories(F:/Eigen)

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 这必须是CMakeLists的第一行,限定cmake版本,Catkin需要2.8.3或更高版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.3)

PROJECT #指定工程名称

add_compile_options(-std=c++11) # 支持C++ 11

set(CMAKE_BUILD_TYPE "Release") # 指定cmake版本为Release,或者用Debug
if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(STATUS "build type is Release")
endif()

#SET 定义变量,可定义多个变量,例如: set(SRC_FILES main.cpp mainwindow.cpp mainwindow.h)
#MESSAGE 输出用户定义的信息

include_directories #指定头文件的搜索路径
link_directories #指定动态链接库的搜索路径
ADD_EXECUTABLE(bin_file_name ${SRC_FILES}) #生成可执行文件
add_definitions #添加编译参数 add_definitions("-Wall -g")
ADD_LIBRARY #生成动态库或静态库
link_libraries(lib1 lib2) #所有编译目标链接相同的库

SET_TARGET_PROPERTIES #设置输出的名称,设置动态库的版本和API版本
ADD_SUBDIRECTORY #向当前工程添加存放源文件的子目录

获取环境变量

获取bash.rc中的环境变量,通过ENV前缀来访问环境变量,读取环境变量使用$ENV{JAVA_HOME},查看环境变量:

1
message(STATUS " java home: $ENV{JAVA_HOME}" )

写环境变量如下:

1
set( ENV{PATH} /home/martink )

cmake判断操作系统和架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message(${CMAKE_HOST_SYSTEM_NAME})
message(${CMAKE_HOST_SYSTEM_PROCESSOR})

if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
message("this is Linux")
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Android")
message("this is Android")
endif()

if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64")
message("this is aarch64 cpu")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64")
message("this is x86_64 cpu")
endif()

获得include的文件路径

1
2
3
4
5
6
include_directories(
include
/usr/include/eigen3
)
get_property(dirs DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
MESSAGE("include path: ${dirs}")

指定生成文件的输出路径

使用SET命令重新定义EXECUTABLE_OUTPUT_PATHLIBRARY_OUTPUT_PATH变量来指定最终的二进制文件的位置

1
2
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

上面的两条命令通常紧跟ADD_EXECUTABLEADD_LIBRARY,与其写在同一个CMakeLists.txt即可

include_directories 和 target_include_directories

都是添加头文件搜索路径。差别:

  • include_directories的影响范围最大,可以为CMakelists.txt后的所有项目添加头文件目录。一般写在最外层CMakelists.txt中影响全局

  • 影响范围可以自定义,加关键子PRIVATEPUBLIC。一般引用库路径使用这个命令,作为外部依赖项引入进来,target是自己项目生成的lib。

PRIVATE:target对应的源文件使用
PUBLIC:target对应的头文件、源文件都使用

如果有不同目录相同名称的头文件会产生影响,所以这里建议针对特定的 target 使用 target_include_directories

参考:
CMake基本教程
子工程和链接静态库
CMake使用进阶