CMake学习笔记
- CMake编译概述
- CMake学习资源
- CMake编译
- 项目架构
- cmake指令
- CMakeList基础准则
- CMakeList编写
- 项目构建
- cmake_minimum_required() 和 project()
- set()
- find_package()
- add_executable()
- aux_source_directory()
- 连接库文件
- include_directories()和target_include_directories()
- add_library()
- add_subdirectory()
- target_link_libraries()
- target_link_directories()
- 安装与日志
- install target
- install Files和install programs
- install Directories
- message()
CMake编译概述
CMake学习资源
- CMake官方推荐教程:CMake Tutorial
- 知乎博客(偏向实践,以项目方式说明):全网最细的CMake教程!(强烈建议收藏)
- 简书博文(博主对官方文档进行中文说明并配上相关例子):Cmake
对于CMake推荐在项目使用中学习,通过查阅文档和实际测试每个指令的用处能有效的掌握CMake。
CMake编译
项目架构
CMake是一种常用的C++项目编译工具,通常一个完备的CMake项目包括了如下几部分:
src
:源代码文件夹include
:头文件文件夹doc
:项目文档文件夹build
:编译文件夹devel
:ROS1用于保存编译后程序的文件夹run.sh
:项目启动脚本(ROS中无)CMakeList.txt
:CMake文件,应在项目根目录和各级子文件夹内存在(例如ROS内就是在工作空间根目录和各个功能包根目录下存在)
对于一个C++项目,在编译完成后还可能需要使用make install
将其安装至指定位置。
cmake指令
对于一个项目会构建一个build
文件夹用于存放编译生成的中间文件,从而避免污染工作空间:
mkdir build & cd build # 新建build目录并进入
cmake .. # 构建MakeFile(build父目录)
make -j5 # 编译
cmake ..
表示使用cmake
构建当前文件夹build
的父文件夹(项目根目录)。上述指令还可简化为使用参数--build
:
mkdir build & cd build # 新建build目录并进入
cmake --build .. # 构建并编译(build父目录)
cmake指令的更多参数和使用可参考官方手册:cmake
CMakeList基础准则
CMakeList
文件的编写主要遵从如下几条原则:
${变量名}
被用于引用CMake变量,使用IF控制语句除外- 指令参数被括弧括起,用空格分开:
project(projectname [cxx])
- 指令是大小写无关的(官方推荐小写),参数、变量是大小写相关的
- 参数中包括空格,则应用双引号括起:
"fn nc.c"
- 使用
#
注释
CMakeList编写
项目构建
cmake_minimum_required() 和 project()
cmake_minimum_required
用于指定所使用CMake的最低版本号,置于CMakeList.txt
的开头;project
指令则用于指定项目的名称,指令的用法如下:
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
project(<PROJECT-NAME> [<language-name>...])
VERSION
:关键字,表明后续添加为版本号
-min
: 设置所使用CMake的最低版本号,格式为major.minor[.patch[.tweak]]
policy_max
:设置所使用的CMake的最高版本号,应高于min
<PROJECT-NAME>
:设置项目名称<language-name>
:设置项目支持的语言,不加表示默认所有编程语言
使用示例如下所示:
cmake_minimum_required(VERSION 3.2.4)
project(Demo)
project
指令同时隐式的定义了几个变量:
<projectname>_BINARY_DIR
:项目编译目录的绝对路径<projectname>_SOURCE_DIR
:项目源目录的绝对路径PROJECT_BINARY_DIR
:自动绑定<projectname>_BINARY_DIR
的内容PROJECT_SOURCE_DIR
:自动绑定<projectname>_SOURCE_DIR
的内容
通常在调用上述CMake变量时,使用PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
进行,从而避免CMake项目名称修改后需要额外修改其余内容。
更多内容可参考官方指导手册:cmake_minimum_required、project
以及博文Cmake命令之cmake_minimum_required介绍和Cmake命令之project介绍
set()
用于设置variable
的值为 value
,指令用法如下:
set(<variable> <value>... [PARENT_SCOPE])
variable
:被设置的参数value
:设置的值
例如在CMake
中指定使用的C++
标准为C++ 14
,则可定义变量${CMAKE_CXX_STANDARD}
的值为14:
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
更多用法(环境变量、缓存变量、定义域等)可查看官方手册:set,以及博文Cmake命令之set介绍
find_package()
用于查找包(通常由项目外部的第三方库提供),并加载其包特定的详细信息。指令使用方式如下:
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])
PackageName
:需要查找的包名version
:可指定查找包的版本要求versionMin...versionMax
:如1.1.1.1…1.2.2.1(包括1.2.2.1)versionMin...[<]versionMax
:如1.1.1.1…<1.2.2.1(不包括1.2.2.1)
QUIET
:禁止输出查找日志信息REQUIRED
:当未找到满足条件的包时停止构建COMPONENTS
:指定要查找的组件
例如如下即为常见的ROS查找catkin包相关组件:
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
message_generation
nav_msgs
roscpp
rospy
std_msgs
tf
visualization_msgs
)
更多使用方法可以参考博文Cmake命令之find_package介绍
以及官方手册:find_package
add_executable()
用于从源码文件编译得到一个可执行文件,指令用法如下:
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
name
:编译所得的可执行文件命名source
:用于编译的源文件[EXCLUDE_FROM_ALL]
:设置后,表明该可执行文件将被排除在all
外,需要手动确定才能执行生成
通常仅指定构建的可执行文件名和使用的源码,如:
add_executable(Demo main.c)
更多用法参考博文Cmake命令之add_executable介绍
以及官方手册:add_executable
aux_source_directory()
当一个文件夹内存在多个源代码时,可使用该指令将其定义为一个变量,从而方便调用:
aux_source_directory(<dir> <variable>)
dir
:源代码路径variable
:变量名
例如,下列示例将当前目录下所有源代码加入至DIR_SRCS
,并用其编译生成可执行文件Demo
cmake_minimum_required (VERSION 2.8)
project (Demo2)
aux_source_directory(. DIR_SRCS)
add_executable(Demo ${DIR_SRCS})
更多用法可参考官方手册:aux_source_directory
连接库文件
include_directories()和target_include_directories()
两者都用于将给定目录添加到编译器用于搜索包含文件的目录中,不同之处在于include_directories
将对整个CMakeList
内每个目标添加搜索路径,而target_include_directories
则仅针对指定目标添加搜索路径,两者用法如下:
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
<target>
:target_include_directories
中的指定目标[AFTER|BEFORE]
:可显式指定添加头文件搜索路径的位置- 在原有搜索路径的最后(AFTER)
- 在原有搜索路径的最前(BEFORE)
dir
:include_directories
中添加的搜索路径,相对路径将被解释为基于当前源目录开始<INTERFACE|PUBLIC|PRIVATE>
INTERFACE
:仅target
的头文件可使用该路径PUBLIC
:target
的头文件和源文件均可以使用PRIVATE
:target
对应的源文件可以使用
items
:target_include_directories
中添加的搜索路径,相对路径将被解释为基于当前源目录开始
此处可参考博文CMake 添加头文件搜索路径 include_directories, target_include_directories
以及官方手册:include_directories、target_include_directories
add_library()
用于从源码文件编译得到一个库文件,指令用法如下:
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
<name>
:构建的库文件的名称,最终生成的库文件名字将根据本机惯例进行产生,如:lib<name>.lib
或<name>.a
[STATIC | SHARED | MODULE]
:构建库文件的类型STATIC
:静态库文件SHARED
:动态库文件MODULE
:模块库文件
source
:用于编译的源文件[EXCLUDE_FROM_ALL]
:设置后,表明该库将被排除在all
外,需要手动确定才能执行生成- 生成的库文件相关
CMake
参数以INTERFACE_
开头
生成的库文件将被输出至${ARCHIVE_OUTPUT_DIRECTORY}
、${LIBRARY_OUTPUT_DIRECTORY}
和${RUNTIME_OUTPUT_DIRECTORY}
中:
ARCHIVE_OUTPUT_DIRECTORY
:静态库文件存储位置LIBRARY_OUTPUT_DIRECTORY
:Lib文件存储位置RUNTIME_OUTPUT_DIRECTORY
:动态库文件存储位置
若需要导入一个已经生成的库文件还可以使用如下方式:
add_library(<name> [STATIC | SHARED | MODULE]
IMPORTED [GLOBAL])
IMPORTED
关键字用于说明该库文件已生成无需编译GLOBAL
:设置后表明该库文件全局可见- 使用此方法生成的库文件相关
CMake
参数以IMPORTED_
开头
更多用法可以参考博文:cmake : add_library详解或者官方手册:add_library
add_subdirectory()
用于添加需要被构建的子目录,指令用法如下:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
source_dir
:指定子目录,其内应包含CMakeList.txt
文件、源码文件binary_dir
:指定输出可执行文件位置
例如项目内包含子文件夹src
,其内包括CMakeList.txt
和相关源码。则在项目根目录CMakeList.txt
中添加如下指令使其被构建:
add_subdirectory(src)
更多用法可以参考博文Cmake命令之add_subdirectory介绍以及官方手册:add_subdirectory
target_link_libraries()
用于连接可执行文件和对应的库文件:
target_link_libraries(<target> ... <item>... ...)
target
:被连接的可执行文件item
:库文件名称、路径等
例如,可执行文件main
连接生成的库文件B
:
add_executable(main main.c)
add_library(B SHARED b.c)
target_link_libraries(main B)
更多使用方式可参考官方手册:target_link_libraries
target_link_directories()
用于连接可执行文件和用于查找依赖库文件的路径,指令用法如下:
target_link_directories(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
更多使用方式可参考官方手册:target_link_directories
安装与日志
在CMake中安装使用install
指令实现,针对不同的文件类型,该指令具备不同的使用方式,可参考官方手册查看详细内容:install
install target
install(TARGETS targets... [EXPORT <export-name>]
[RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET <set-name>|CXX_MODULES_BMI]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
TARGETS
:关键字,表明安装的对象为Targettargets
:指定目标文件名[ARCHIVE|LIBRARY|RUNTIME……]
:指定文件类型ARCHIVE
:静态库LIBRARY
:动态库RUNTIME
:可执行目标二进制文件
[DESTINATION <dir>]
:指定安装路径- 若路径
<dir>
为绝对路径,以"/"开头 - 若路径
<dir>
为相对路径,则实际安装路径为:${CMAKE_INSTALL_PREFIX}<dir>
- 若路径
[PERMISSIONS permissions...]
:用户权限,最高777(参考liunx权限)OWNER_WRITE
:拥有者写入权限OWNER_READ
:拥有者读取权限OWNER_EXECUTE
:组成员执行权限GROUP_WRITE
:组成员写入权限GROUP_READ
:组成员读取权限GROUP_EXECUTE
:组成员执行权限WORLD_WRITE
:其他人写入权限WORLD_READ
:其他人读取权限WORLD_EXECUTE
:其他人执行权限
例如,如下使用方式:
install(TARGETS myExe mySharedLib myStaticLib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
install(TARGETS mySharedLib DESTINATION /some/full/path)
上述例子实现如下目标:
- 二进制文件(RUNTIME)
myExe
安装至${CMAKE_INSTALL_PREFIX}/bin
下 - 动态库文件(LIBRARY)
mySharedLib
安装至${CMAKE_INSTALL_PREFIX}/lib
和/some/full/path
下 - 静态库文件(ARCHIVE)
myStaticLib
安装至${CMAKE_INSTALL_PREFIX}/lib/static
下
install Files和install programs
install(<FILES|PROGRAMS> files...
TYPE <type> | DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])
<FILES|PROGRAMS>
:关键字,表明安装的对象类型为FILES
或PROGRAMS
,应选择其一填写FILES
:权限为拥有者读写、组成员读、其余人读(644)PROGRAMS
:权限为拥有者读写执行、组成员读执行、其余人读执行(755)
files…
:指定文件名称TYPE
:关键字,表明文件的类型[DESTINATION <dir>]
:指定安装路径
文件类型可选如下所示:
install Directories
install(DIRECTORY dirs...
TYPE <type> | DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>] [EXCLUDE_FROM_ALL]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
[FILE_PERMISSIONS permissions...]
:目录内文件权限[DIRECTORY_PERMISSIONS permissions...]
:目录本身权限[USE_SOURCE_PERMISSIONS]
:若未指定FILE_PERMISSIONS permissions...
,则根据源文件权限赋予[PATTERN <pattern>
:采用模式匹配进行筛选内容<pattern>
REGEX <regex>
:采用正则匹配进行筛选内容<regex>
[EXCLUDE]
:安装时,排除筛选得到的文件[PERMISSIONS permissions...]
:指定筛选得到的文件的权限
例如使用该命令执行如下:
INSTALL(DIRECTORY icons scripts/
DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*" PERMISSIONS
OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ WORLD_EXECUTE
)
上述指令执行如下操作:
- 将目录
icons
安装至${CMAKE_INSTALL_PREFIX}/share/myproj
- 将目录
scripts/
中的内容安装${CMAKE_INSTALL_PREFIX}/share/myproj
- 在进行安装时,排除名字包含CVS 的文件
- 在进行安装时,将
scripts/*
文件的权限指定为731
message()
用于向终端中输出用户定义的编译日志信息,指令用法如下:
message([<mode>] "message text" ...)
mode
:日志类型FATAL_ERROR
:红色CMake Error,将终止CMake编译SEND_ERROR
:CMake Error,将跳过生成但不影响编译WARNING
:黄色CMake Warning,不影响CMake编译AUTHOR_WARNING
:CMake Warning (dev),不影响CMake编译STATUS
:输出一些编译过程中的简明信息VERBOSE
:输出一些编译过程中的详细信息
message text
:输出的日志内容
更多使用方式可参考官方手册:message