参考链接:
CMake 保姆级教程(上) | 爱编程的大丙 (subingwen.cn)
CMake语法—命令list - kaizenly - 博客园 (cnblogs.com)
CMakeLists概述
1. c代码的编译流程:
-
源文件:.cpp文件
-
预处理器:对应的头文件进行展开,宏进行替换,注释去掉,完成该步骤后仍是源文件:
-
编译器:gcc或g++进行编译,会得到汇编文件
-
汇编器:对生成的汇编文件进行处理,生成二进制文件(.obj(在windows下)或.o(在linux下))
-
链接器:对二进制文件进行链接,相当与做了一个打包的操作,生成可执行的二进制文件
CMake 允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需 make 编译即可,所以可以把 CMake 看成一款自动生成 Makefile 的工具。
2. 查看cmake版本
cmake --versio n
3. 块注释
使用#[[ ]] 添加多行注释
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)
CMakeLists.txt中的常用命令
1. c m a k e _ m i n i m u m _ r e q u i r e d ( ) \textcolor {red}{cmake\_minimum\_required(\ )} cmake_minimum_required( )
用于规定cmake的最低版本(可选,非必须,如果不加可能会有警告)
cmake_minimum_required(VERSION 2.8)
2. p r o j e c t ( ) \textcolor {red} {project ( \ )} project( )
定义工程名称,并可指定工程的版本、工程描述、web 主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。
# PROJECT 指令的语法是:
project(<PROJECT-NAME> )
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
#一般仅会用到一个参数,如:
project(myslam)
3. a d d _ e x e c u t a b l e ( ) \textcolor {red} {add\_executable(\ )} add_executable( )
工程会生成一个可执行程序
add_executable(可执行程序名 源文件名称)
#例子:生成的可执行文件名称为myslam
add_executable(myslam main.cpp)
4. s e t ( ) \textcolor {red} {set(\ )} set( )
在cmake中使用set来定义变量,变量的值均为字符串类型,同时该命令也可用于拼接字符串
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
- VAR:变量名
- VALUE:变量值
取出变量的值时使用${ VAR }
进行
# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c div.c main.c mult.c sub.c)
# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app ${SRC_LIST})
set指令常用方法:
(1)用于指定C++标准
C++标准在cmake中对应的宏叫做DCMAKE_CXX_STANDARD。同时,在CMake要指定C++标准有两种方式:
在 CMakeLists.txt 中通过 set 命令指定
#增加-std=c++11 set(CMAKE_CXX_STANDARD 11) #增加-std=c++14 set(CMAKE_CXX_STANDARD 14) #增加-std=c++17 set(CMAKE_CXX_STANDARD 17)
在执行 cmake 命令的时候指定出这个宏的值
#增加-std=c++11 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11 #增加-std=c++14 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14 #增加-std=c++17 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17 # build目录下 cmake .. -DCMAKE_CXX_STANDARD=11
注:-D表示指定宏名,其紧接着宏名并进行赋值
(2)用于指定输出的路径
在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置:
set(HOME /home/robin/Linux/Sort) set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
第一行:定义一个变量用于存储一个绝对路径
第二行:将拼接好的路径值设置给EXECUTABLE_OUTPUT_PATH宏
注: 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建;并且由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录,即build目录;故输出路径一般写成绝对路径。
(3)拼接字符串
如果使用set进行字符串拼接,对应的命令格式如下:
set(变量名1 ${变量名1} ${变量名2} ...)
关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。
5. a u x _ s o u r c e _ d i r e c t o r y ( ) \textcolor {red} {aux\_source\_directory(\ )} aux_source_directory( )
在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:
aux_source_directory(< dir > < variable >)
- dir:要搜索的目录
- variable:将从dir目录下搜索到的源文件列表存储到该变量中
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app ${SRC_LIST})
**注:**其中 宏PROJECT_SOURCE_DIR
与CMAKE_CURRENT_SOURCE_DIR
均为访问CMakeLists.txt所在的路径,即工程的根目录
6. f i l e ( ) \textcolor {red} {file(\ )} file( )
在CMake中使用file命令完成搜索文件的任务
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
- GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
- GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
搜索当前目录的src目录下所有的源文件,并存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
# 或
file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")
7. i n c l u d e _ d i r e c t o r i e s ( ) \textcolor {red} {include\_directories(\ )} include_directories( )
在编译项目源文件的时候,需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译,使用include_directories来包含头文件目录
include_directories(headpath)
例子:
目录结构如下:
$ tree . ├── build ├── CMakeLists.txt ├── include │ └── head.h └── src ├── add.cpp ├── div.cpp ├── main.cpp ├── mult.cpp └── sub.cpp 3 directories, 7 files
CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0) project(CALC) set(CMAKE_CXX_STANDARD 11) set(HOME /home/robin/Linux/calc) set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin/) include_directories(${PROJECT_SOURCE_DIR}/include) file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) add_executable(app ${SRC_LIST})
8. a d d _ l i b r a r y ( ) \textcolor {red} {add\_library(\ )} add_library( )
有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,在cmake中使用add_library命令实现:
add_library(库名称 库的类型 源文件1 [源文件2] ...)
(1)生成静态库
在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
add_library(库名称 STATIC 源文件1 [源文件2] ...)
(2)生成动态库
在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
add_library(库名称 SHARED 源文件1 [源文件2] ...)
(3) 设置路径生成
由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})
9. l i n k _ l i b r a r i e s ( ) 与 l i n k _ d i r e c t o r i e s ( ) \textcolor {red} {link\_libraries()与link\_directories( )} link_libraries()与link_directories()
在cmake中,链接静态库的命令为link_libraries
link_libraries(<static lib> [<static lib>...])
- 参数1:指定出要链接的静态库的名字。可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
- 参数2-N:要链接的其它静态库的名字
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以使用link_directories
命令指定静态库或动态库的路径
link_directories(<lib path>)
例子:
cmake_minimum_required(VERSION 3.0) project(CALC) # 搜索指定目录下源文件 file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) # 包含头文件路径 include_directories(${PROJECT_SOURCE_DIR}/include) # 包含静态库路径 link_directories(${PROJECT_SOURCE_DIR}/lib) # 链接静态库 link_libraries(calc) add_executable(app ${SRC_LIST})
10. t a r g e t _ l i n k _ l i b r a r i e s ( ) \textcolor {red} {target\_link\_libraries(\ )} target_link_libraries( )
在cmake中链接库文件的命令为target_link_libraries
,该命令既可以链接动态库,也可以链接静态库文件。
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
target:指定要加载动态库文件的名字。该文件可能是一个源文件;该文件可能是一个动态库文件;该文件可能是一个可执行文件。
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
-
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
-
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,其它库如果链接到该target,则无法获知private后面的库信息
target_link_libraries(A B C) target_link_libraries(D A) #D 可访问 A B C target_link_libraries(A private B C) target_link_libraries(D A) #D 可访问 A C
-
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号;即当前的target只知道链接到了某一函数,但不知道该函数来自哪个库
-
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
-
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
注:
动态库的链接和静态库是完全不同的:
-
静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
-
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后
11. m e s s a g e ( ) \textcolor {red} {message(\ )} message( )
在CMake中可以用用户显示一条消息,该命令的名字为message:
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
-
(无) :重要消息
-
STATUS :非重要消息
-
WARNING:CMake 警告, 会继续执行
-
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
-
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
-
FATAL_ERROR:CMake 错误, 终止所有处理过程
CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。
CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。
注:该命令也可用于打印定义的变量信息,方便进行调试
12. l i s t ( ) \textcolor {red} {list(\ )} list( )
该指令用于操作字符串,由如下子属性以实现不同的字符串功能。
子命令 | 功能 | 编号 |
---|---|---|
LENGTH | 用于读取列表长度 | (3) |
GET | 获取索引对应元素的子命令,可以同时指定多个索引 | (4) |
JOIN | 连接列表中的元素,并在元素间添加连接符的子命令 | (5) |
SUBLIST | 用于获取列表中的一部分(子列表) | (15) |
FIND | 查找指定元素 | (6) |
APPEND | 将数据追加到列表中,也可进行字符串拼接 | (1) |
INSERT | 在指定位置将元素(一个或多个)插入到列表中 | (7) |
PREPEND | 在列表的头部(索引为0处)插入(一个或多个)元素 | (8) |
POP_BACK | 将列表中最后元素移除(可将移除元素存入变量) | (9) |
POP_FRONT | 将列表中第一个元素移除(可将移除元素存入变量) | (10) |
REMOVE_ITEM | 将指定的元素从列表中移除 | (2) |
REMOVE_AT | 将指定索引的元素从列表中移除 | (11) |
REMOVE_DUPLICATES | 移除列表中的重复元素 | (12) |
REVERSE | 将整个列表反转 | (13) |
SORT | 将整个列表元素排序 | (14) |
(1)字符串拼接
如果使用list进行字符串拼接,对应的命令格式如下:
list(APPEND <list> [<element> ...])
- APPEND : 表示进行数据追加
- list : 为列表
- element : 为字符串,也可用${变量名}
cmake_minimum_required(VERSION 3.0) project(TEST) set(TEMP "hello,world") file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp) file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp) # 追加(拼接) list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP}) message(STATUS "message: ${SRC_1}")
注意:在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;
分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde。
set(tmp1 a;b;c;d;e) set(tmp2 a b c d e) message(${tmp1}) message(${tmp2})
结果为:
abcde abcde
(2) 字符串删除
如果使用list移除指定的字符串,对应命令:
list(REMOVE_ITEM <list> <value> [<value> ...])
- REMOVE_ITEM: 表示进行字符串移除
- list : 为列表
- value: 为字符串,也可用${变量名}
cmake_minimum_required(VERSION 3.0) project(TEST) set(TEMP "hello,world") file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp) # 移除前日志 message(STATUS "message: ${SRC_1}") # 移除 main.cpp list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp) # 移除后日志 message(STATUS "message: ${SRC_1}")
值得注意的是,list指令移除的是一个元素,即用分号分隔的一个字符串,而不是元素的一部分;如下:
# 变量打印出来的值
[cmake] ./Frame.cpp./ORBextractor.cpp./system.cpp./visual_odometry.cpp./visual_template.cpp
# 实际变量存储的值
./Frame.cpp;./ORBextractor.cpp;./system.cpp./visual_odometry.cpp;./visual_template.cpp
# list 能移除的即为分号分隔开的字符串 如:
./ORBextractor.cpp
#list 不能移除元素的一部分 如:不能移除
cpp
(3)获取 list 的长度
list(LENGTH <list> <output variable>)
- LENGTH:子命令LENGTH用于读取列表长度
- list:当前操作的列表
- output variable:新创建的变量,用于存储列表的长度,也是字符串类型
(4)读取列表中指定索引的的元素,可以指定多个索引
list(GET <list> <element index> [<element index> ...] <output variable>)
- GET : 获取索引对应元素的子命令
- list:当前操作的列表
- element index:列表元素的索引。从0开始编号,索引0的元素为列表中的第一个元素;索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推;当索引(不管是正还是负)超过列表的长度,运行会报错
- output variable:新创建的变量,存储指定索引元素的返回结果,也是一个列表。
(5)将列表中的元素用连接符(字符串)连接起来组成一个字符串
list (JOIN <list> <glue> <output variable>)
- JOIN : 连接列表中的元素,并在元素间添加连接符的子命令
- list :当前操作的列表
- glue :指定的连接符(字符串)
- output variable :新创建的变量,存储返回的字符串
(6)查找列表是否存在指定的元素,若果未找到,返回-1
list(FIND <list> <value> <output variable>)
- FIND : 查找指定元素
- list:当前操作的列表
- value:需要再列表中搜索的元素
- output variable:新创建的变量。如果列表中存在,那么返回在列表中的索引;如果未找到则返回-1。
(7)在list中指定的位置插入若干元素
list(INSERT <list> <element_index> <element> [<element> ...])
- INSERT: 插入指定元素
- list:当前操作的列表
- element_index:列表中元素的索引;插入其element_index的前一索引之后,其后的元素往后移
- element:插入的元素
(8)将元素插入到列表的0索引位置
list (PREPEND <list> [<element> ...])
(9)将列表中最后元素移除
list (POP_BACK <list> [<out-var>...])
-
list :欲移除最后元素的列表名称
-
out-var :输出变量
- 如果未指定,则仅仅只将原列表中最后一个元素移除;
- 如果指定,会将列表最后一个元素赋值给该变量,然后再将列表最后一个元素移除。
如果指定多个输出变量,则会依次将列表中最后一个元素赋值到输出变量中。
如果输出变量个数大于列表长度,则超出部分的输出变量未定义
(10)将列表中第一个元素移除
list (POP_FRONT <list> [<out-var>...])
-
list :欲移除第一个元素的列表名称
-
out-var :输出变量
- 如果未指定,则仅仅只将列表中第一个元素移除;
- 如果指定,会将列表第一个元素赋值给该变量,然后再将列表第一个元素移除。
如果指定多个输出变量,则会依次将列表中最后一个元素赋值到输出变量中。
如果输出变量个数大于列表长度,则超出部分的输出变量未定义
(11)将指定索引的元素从列表中移除
list (REMOVE_AT <list> <index> [<index> ...])
-
list :欲移除元素的列表名称
-
index :指定的索引
当指定的索引不存在的时候,会提示错误;
如果指定的索引存在重复,则只会执行一次移除动作。
(12)移除列表中的重复元素
list (REMOVE_DUPLICATES <list>)
-
list :欲移除元素的列表名称
如果列表中没有重复元素,则列表不会改变。
如果列表中所有元素都是一样的,则会保留一个。
如果有多个重复元素,则保留第一个。
(13)列表翻转
list(REVERSE <list>)
- list :欲反转元素列表名称
(14)列表排序
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
- list :欲排序列表名称
- COMPARE :指定排序方法
- SRING :按照字母顺序进行排序(默认)
- FILE_BASENAME :如果是一系列路径名,会使用basename进行排序
- NATURAL :使用自然数顺序排序
- CASE :指明是否大小写敏感
- SENSITIVE :按大小写敏感的方式进行排序(默认)
- INSENSITIVE :按大小写不敏感方式进行排序
- ORDER :排序的顺序
- ASCENDING :按照升序排序(默认)
- DESCENDING :按照降序排序
(15)获取列表中的一部分子列表
list(SUBLIST <list> <begin> <length> <out-var>)
-
list 为欲获取子列表的父列表名称;
-
begin 开始索引;
-
length 子列表长度。
若length值为0,返回子列表为空列表;
若length值为-1 或 父列表长度小于begin+length值(即索引越界或超出长度),将会把父列表中从begin索引开始的所有剩余元素返回。
-
out-var 为新创建的变量,用于存储获取结果子列表。
13. a d d _ d e f i n i t i o n s ( ) \textcolor {red} {add\_definitions(\ )} add_definitions( )
使用该命令为程序添加宏定义:
add_definitions(-D宏名称)
例子:
cmake_minimum_required(VERSION 3.0)
project(TEST)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./test.c)
可配合debug的日志文件使用
在编写C++程序时,可使用预处理命令判断宏是否存在来决定Debug的日志文件输出
#include <stdio.h>
#define NUMBER 3
int main()
{
int a = 10;
#ifdef DEBUG
printf("Debugging information...\n");
#endif
for(int i=0; i<NUMBER; ++i)
{
printf("hello, GCC!!!\n");
}
return 0;
}
14. f i n d _ p a c k a g e ( ) \textcolor {red} {find\_package(\ )} find_package( )
find_package(<Package> [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
<Package>
:要查找的软件包的名称,例如 OpenCV、Boost 等。version
:可选参数,用于指定所需的软件包版本。EXACT
:可选参数,要求版本号完全匹配。QUIET
:可选参数,如果找不到软件包,则不产生错误。MODULE
:可选参数,指定使用模块方式查找软件包。REQUIRED
:可选参数,如果找不到软件包,则产生致命错误。COMPONENTS
:可选参数,指定要查找的软件包的组件。OPTIONAL_COMPONENTS
:可选参数,指定可选的组件。NO_POLICY_SCOPE
:可选参数,指定不将策略设置限定在调用的目录。
如果找到了相应的库,则会自动定义以下几个变量:
<LibaryName>_FOUND
<LibaryName>_INCLUDE_DIR or <LibaryName>_INCLUDES
<LibaryName>_LIBRARY or <LibaryName>_LIBRARIES
<LibraryName>_FOUND
:- 这个变量用于指示是否找到了指定的库。
- 如果找到了,这个变量的值为
TRUE
;否则,值为FALSE
。
<LibraryName>_INCLUDE_DIR
或<LibraryName>_INCLUDES
:- 这个变量用于存储库的头文件所在目录的路径。
- 如果库提供了多个头文件目录,可能是一个路径列表。
<LibraryName>_LIBRARY
或<LibraryName>_LIBRARIES
:- 这个变量用于存储库文件(通常是静态库或动态库)的路径或名称。
- 如果库提供了多个库文件,可能是一个路径列表。
- 在链接目标时,应使用这些变量指定要链接的库。
find_package具有两种模式,一种是Module模式,另一种叫做Config模式。
- 在Module模式中,cmake需要找到一个叫做
Find<LibraryName>.cmake
的文件。这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。cmake搜索这个文件的路径有两个,一个cmake安装目录下的/usr/share/cmake-<version>/Modules
目录,另一个使我们指定的CMAKE_MODULE_PATH
的所在目录。 - 在Config模式中,cmake主要通过
<LibraryName>Config.cmake
or<lower-case-package-name>-config.cmake
这两个文件来引入我们需要的库。一般在/usr/local/lib/cmake
可找到一些配置文件,有些库的config文件则在share
中,如/usr/share/OpenCV/OpenCVConfig.cmake
对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录,这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供find_package使用。而对于非由cmake编译的项目,我们通常会编写一个Find<LibraryName>.cmake
,通过脚本来获取头文件、库文件等信息。通常,原生支持cmake的项目库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
15. i n s t a l l ( ) \textcolor {red} {install(\ )} install( )
该指令用于将生成的文件安装到系统目录
(1)安装目标库文件以及可执行文件
install(TARGETS targets... [EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
targets
:指定要安装的目标名称。EXPORT <export-name>
:指定一个导出集,安装的目标将被添加到该集合中。[ARCHIVE|LIBRARY]
:指定正在安装的目标的类型。- 静态库: ARCHIVE
- 动态库: LIBRARY
- 可执行二进制文件: RUNTIME
- 与库关联的PUBLIC头文件: PUBLIC_HEADER
- 与库关联的PRIVATE头文件: PRIVATE_HEADER
[DESTINATION <dir>]
:指定目标的安装目录。PERMISSIONS:
指定安装文件的权限;有效权限包括: OWNER_READ,OWNER_WRITE,OWNER_EXECUTE,GROUP_READ,GROUP_WRITE,GROUP_EXECUTE, WORLD_READ,WORLD_WRITE,WORLD_EXECUTE,SETUID和SETGID;CONFIGURATIONS:
指定安装规则适用的构建配置列表(DEBUG或RELEASE等);COMPONENT:
指定了该安装规则相关的一个安装部件的名字,如“runtime”;EXCLUDE_FROM_ALL:
指定该文件从完整安装中排除,仅作为特定于组件的安装的一部分进行安装;OPTIONAL:
如果要安装的文件不存在,则指定不是错误。
使用案例:
# 将 myslam 库安装到指定目录 install(TARGETS myslam #安装动态库使用这行指令 DESTINATION指定安装目录 LIBRARY DESTINATION /usr/local/lib #安装静态库使用这行指令 ARCHIVE DESTINATION /usr/local/lib )
(2)安装头文件目录或其它类型的文件目录
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...]] [...])
dirs:
目录名。不以/
结尾,包含目录本身,目录名以/
结尾,目录下的内容,不包括目录本身。DESTINATION <dir>:
目录的安装位置FILES_MATCHING:
进行文件匹配,匹配方式由PATTERN|REGEX:
确定PATTERN|REGEX:
以精细的粒度控制目录的安装,可以指定一个通配模式或正则表达式对输入目录过滤;PERMISSIONS:
覆盖匹配文件或目录的权限设置。
使用案例:
# 将 include 目录下的所有头文件安装到指定目录 install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION /usr/local/include/myslam # 匹配${PROJECT_SOURCE_DIR}/include 下的所有.h文件进行安装 FILES_MATCHING PATTERN "*.h")
16. a d d _ s u b d i r e c t o r y ( ) \textcolor {red} {add\_subdirectory(\ )} add_subdirectory( )
使用add_subdirectory
命令添加子目录,执行子目录下的CMakeLists.txt
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
-
source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
-
binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
-
EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。
CMake中预定义的宏名
宏名 | 指代 |
---|---|
CMAKE_CXX_STANDARD | C++标准 |
PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,工程文件夹根目录 |
PROJECT_BINARY_DIR | 执行cmake命令的目录,一般为build目录下 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件输出路径 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标可执行文件的输出路径 |
PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 |
CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 |
CMake常用方法:
1. CMake嵌套使用
如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。
当前文件的目录结构为:
.
├── bin
│ └── function_text
├── build
├── CMakeLists.txt
├── dataset
├── cmake_modules
├── include
│ ├── Frame.h
│ ├── ORBextractor.h
│ ├── ORBVocabulary.h
│ ├── system.h
│ ├── visual_odometry.h
│ └── visual_template.h
├── lib
│ └── libmyslam.so
├── src
│ ├── CMakeLists.txt
│ ├── Frame.cpp
│ ├── ORBextractor.cpp
│ ├── system.cpp
│ ├── visual_odometry.cpp
│ └── visual_template.cpp
├── test
│ ├── CMakeLists.txt
│ ├── function_text.cpp
│ └── main.cpp
└── ThirdParty
└── DBoW2
├── CMakeLists.txt
├── DBoW2
│ ├── BowVector.cpp
│ ├── BowVector.h
│ ├── FClass.h
│ ├── FeatureVector.cpp
│ ├── FeatureVector.h
│ ├── FORB.cpp
│ ├── FORB.h
│ ├── ScoringObject.cpp
│ ├── ScoringObject.h
│ └── TemplatedVocabulary.h
├── DUtils
│ ├── Random.cpp
│ ├── Random.h
│ ├── Timestamp.cpp
│ └── Timestamp.h
├── lib
│ └── libDBoW2.so
├── LICENSE.txt
└── README.txt
根目录包含的内容一般包含以下几个文件:
- bin : 存储生成的可执行二进制文件,可在CMakeLists.txt中指定路径
- build :存储查找链接库时生成的中间文件
- include:放置函数的头文件
- src : 放置函数的源文件
- lib : 放由函数文件所生成的静态库(.a)和动态库(.so)可在CMakeLists.txt中指定路径
- test : 存放主函数以及需要测试的文件(可撰写多个主函数,在CMakeLists.txt指定编译)
- cmake_modules :里边的文件以.cmake结尾,存放着库文件的路径,供find_package查询(文件名Find×××.cmake)
- dataset : 存放运行程序所需要的数据集
- CMakeLists.txt : 撰写该文件指导整个项目的编译,可存在于不同的目录下(上述在根目录,src以及text文件内均存在CMakeLists.txt)
- ThirdParty :存放一些修改过的第三方库
(1)编写顶层的CMakeLists.txt
Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。
注意:CMakeLists.txt 文件变量作用域有如下关系
根节点CMakeLists.txt中的变量全局有效
父节点CMakeLists.txt中的变量可以在子节点中使用
子节点CMakeLists.txt中的变量只能在当前节点中使用
顶层,即根目录下的CMakeLists.txt内容为:
cmake_minimum_required(VERSION 2.8)
project(myslam)
#设置编译方式
set(CMAKE_BUILD_TYPE Debug)
#设置C++标准
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall")
#设置可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#设置库文件输出路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
############### dependencies ######################
find_package(OpenCV REQUIRED)
#包含头文件的路径
include_directories(
${PROJECT_SOURCE_DIR}
${OpenCV_INCLUDE_DIRS}
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/ThirdParty
"/usr/include/eigen3"
)
#给定自定义的库文件的路径
link_directories(
${PROJECT_SOURCE_DIR}/TirdParty/DBoW2/lib)
set(THIRD_PARTY_LIBS
${OpenCV_LIBS}
libDBoW2.so
)
add_subdirectory(src)
add_subdirectory(test)
(2)编写src下的CMakeLists.txt
,用于编译库文件
#获取当前文件夹下的所有源文件
aux_source_directory( ./ SRC_LIST)
#添加库文件
add_library(myslam SHARED ${SRC_LIST})
#将生成的库文件与第三方库进行链接
target_link_libraries(myslam ${THIRD_PARTY_LIBS})
(3)编写test下的CMakeLists.txt
,用于编译主函数
add_executable(main_outcar main.cpp )
target_link_libraries(main_outcar
myslam
${THIRD_PARTY_LIBS})
add_executable(function_text function_text.cpp)
target_link_libraries(function_text
myslam
${THIRD_PARTY_LIBS})
2.自定义库(该库依赖第三方库实现),并使用find_package查找
将以上的文件修改,作为当前文件目录(不包含test文件夹):
.
├── build
├── CMakeLists.txt
├── include
│ ├── Frame.h
│ ├── ORBextractor.h
│ ├── ORBVocabulary.h
│ ├── system.h
│ ├── visual_odometry.h
│ └── visual_template.h
├── src
│ ├── CMakeLists.txt
│ ├── Frame.cpp
│ ├── ORBextractor.cpp
│ ├── system.cpp
│ ├── visual_odometry.cpp
│ └── visual_template.cpp
└── ThirdParty
└── DBoW2
├── CMakeLists.txt
├── DBoW2
│ ├── BowVector.cpp
│ ├── BowVector.h
│ ├── FClass.h
│ ├── FeatureVector.cpp
│ ├── FeatureVector.h
│ ├── FORB.cpp
│ ├── FORB.h
│ ├── ScoringObject.cpp
│ ├── ScoringObject.h
│ └── TemplatedVocabulary.h
├── DUtils
│ ├── Random.cpp
│ ├── Random.h
│ ├── Timestamp.cpp
│ └── Timestamp.h
├── lib
│ └── libDBoW2.so
├── LICENSE.txt
└── README.txt
(1)编写带安装命令的CMakeLists.txt
在根目录下编写CMakeLists.txt
为:
cmake_minimum_required(VERSION 2.8)
project(myslam)
# 使用Debug或Release都可
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-std=c++11 -O3 -fopenmp -pthread")
############### dependencies ######################
# OpenCV
find_package(OpenCV REQUIRED)
include_directories(
#opencv头文件路径
${OpenCV_INCLUDE_DIRS}
#自定义库的头文件路径
${PROJECT_SOURCE_DIR}/include
#第三方库的头文件路径(注:此处应与实际代码对应)
#如:我的代码为#include "DBoW2/DBoW2/BowVector.h";因此上面加入的路径到ThirdParty就为止了
${PROJECT_SOURCE_DIR}/ThirdParty
#eigen库的头文件,该库全是头文件,无库文件,故后续无需链接
"/usr/include/eigen3"
)
#因为该库未安装到系统目录,也没有使用find_package指令进行查找,故在此处需要该库文件的路径
link_directories(
${PROJECT_SOURCE_DIR}/TirdParty/DBoW2/lib)
set(THIRD_PARTY_LIBS
${OpenCV_LIBS}
libDBoW2.so
)
#添加子文件夹,在子文件夹中编译库文件
add_subdirectory(src)
在src目录下编写CMakeLists.txt
为:
# 将当前目录下的所有源文件添加到 SRC_LIST 变量中
aux_source_directory(./ SRC_LIST)
# 创建名为 myslam 的共享库,使用 SRC_LIST 中的源文件进行编译
add_library(myslam SHARED ${SRC_LIST})
# 将 myslam 库链接到 THIRD_PARTY_LIBS 中指定的第三方库
target_link_libraries(myslam ${THIRD_PARTY_LIBS})
# 设置 myslam 库的版本为 PROJECT_NAME
set_target_properties(myslam PROPERTIES VERSION ${PROJECT_NAME})
# 将 myslam 库安装到指定目录
install(TARGETS myslam
#安装动态库使用这行指令 DESTINATION指定安装目录
LIBRARY DESTINATION /usr/local/lib
#安装静态库使用这行指令
ARCHIVE DESTINATION /usr/local/lib
)
# 将 include 目录下的所有头文件安装到指定目录
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include
DESTINATION /usr/local/include/myslam
# 匹配${PROJECT_SOURCE_DIR}/include 下的所有.h文件进行安装
FILES_MATCHING PATTERN "*.h")
# 还可加一些install命令,让其生成对应的Findmyslam.cmake或myslamConfig.cmake文件,这样就不用自己写配置文件了;具体指令目前尚未尝试
(2)生成带安装指令的库文件并安装的系统目录
在根目录build文件夹中,执行指令:
cmake ..
make
#待成功编译后
sudo make install
生成的库文件名称为libmyslam.so
安装成功后显示:
注:第一行的libmyslam.so.myslam
,最后一个myslam即为之前指定的版本号,用的是project_name
在相应的系统路径可查询为:
(3)新建一个工作空间,使用安装到系统的myslam库文件
新的工作空间为:
├── CMakeLists.txt
├── build
├── cmake_modules
│ └── Findmyslam.cmake
└── main.cpp
主函数main.cpp
如下,该程序只使用了一个system.h
的头文件:
#include <iostream>
#include <vector>
#include <string>
#include "system.h"
void Loadimage(const string& Datafilepath,vector<string>& file_names);
int main(int argc, char **argv)
{
vector<string> file_names;
Loadimage(string(argv[1]),file_names);
System SLAM;
SLAM.TrackRGB(file_names);
SLAM.SaveTrajectory(SLAM.GetExpTrajectory(),"./coordinate.csv");
return 0;
}
新的工作空间下的CMakeLists.txt为:
cmake_minimum_required(VERSION 2.8)
project(Test_slam)
# 设置 C++ 标准为 C++11
set(CMAKE_CXX_STANDARD 11)
# 将自定义的模块路径添加到 CMake 模块路径中
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules ${CMAKE_MODULE_PATH})
# 查找并加载名为 myslam 的 CMake 配置文件
find_package(myslam REQUIRED)
# 将 myslam 的头文件路径添加到包含目录中
include_directories(${MYSLAM_INCLUDE_DIRS})
# 添加可执行文件 main_outcar,使用 main.cpp 源文件
add_executable(main_outcar main.cpp )
# 将 myslam 库链接到 main_outcar 可执行文件中
target_link_libraries(main_outcar
${MYSLAM_LIBRARIES})
Findmyslam.cmake
文件的编写如下:
注:这是固定格式的名称 Find库名.cmake
;
# 检查是否已经找到 myslam 库
if(NOT MYSLAM_FOUND)
# 使用 find_path 查找指定的 .h 文件路径,并将结果存储到 MYSLAM_INCLUDE_DIRS 变量中
# MYSLAM_INCLUDE_DIRS为变量,内存储的是路径
find_path(MYSLAM_INCLUDE_DIRS
#引入的头文件,此处不支持使用变量
"system.h"
#头文件的系统路径
/usr/local/include/myslam/include)
# 使用 find_library 查找指定的库文件,并将结果存储到 MYSLAM_LIBRARIES 变量中
# MYSLAM_LIBRARIES为变量,内存储的是路径 + lib库名.so
find_library(MYSLAM_LIBRARIES
myslam
/usr/local/lib)
# 因为本库还依赖第三方库实现,需在此处引入连接
find_package(OpenCV REQUIRED)
# 添加需要的头文件
include_directories(
${OpenCV_INCLUDE_DIRS}
"/usr/include/eigen3"
"/home/wheeltec-client/Desktop/Neuroslam/NeuroSlam_cpp/NeuroSLAM_MATLABtocpp-neuroslam_v2.0/TirdParty"
)
# 添加链接目录,链接到指定的库文件目录
link_directories(
/home/wheeltec-client/Desktop/Neuroslam/NeuroSlam_cpp/NeuroSLAM_MATLABtocpp-neuroslam_v2.0/TirdParty/DBoW2/lib
)
# 设置 MYSLAM_LIBRARIES 变量,包含 myslam 库、libDBoW2.so、以及 OpenCV 的库文件
set(MYSLAM_LIBRARIES
${MYSLAM_LIBRARIES}
libDBoW2.so
${OpenCV_LIBS}
)
# 设置库的找到标志为 TRUE
set(MYSLAM_FOUND TRUE)
endif()
最后在build目录下编译:
cmake ..
make