前言
学习目的
1) SLAM中需要使用cmake管理代码
2)Linux开发中管理C程序项目代码必须要使用的工具
学习路线
1)Cjacker的《Cmake 实践》
2)Ubuntu16.04下运行cmake
3)用到哪部分内容就学哪部分,后期不断补全新内容
一 简介
Cmake是Linux下的一款可以用来编译c程序和管理项目的工具。
英文分类是:build generator
特点:
二 内容
2.1 简单入门
2.1.1 下载cmake
Linux的终端中输入:
1 | cmake |
它就会提示你怎么安装,很简单。
2.1.2 hello world
A 准备工作
- 建立一个工作目录
1 | mkdir backup/cmake |
然后在t1目录下建立
main.c
和CMakeLists.txt
(注意文件名大小写)- main.c内容
1
2
3
4
5
6
7// main.c
int main()
{
printf("Hello World from t1 Main!\n");
return 0;
}- CMakeLists.txt内容
1
2
3
4
5PROJECT (HELLO)
SET (SRC_LIST main.c)
MESSAGE (STATUS "This is BINARY dir" ${HELLO_BINARY_DIR})
MESSAGE (STATUS "This is SOURCE dir" ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
B 开始构建
- 所有的文件创建完成后,t1目录中应该存在main.c和CMakeLists.txt两个文件,接下来我们构建这个工程,在这个目录运行:
1 | cmake . |
输出大概是这个样子
t1目录会多一些文件:
CMakeFiles, CMakeCache.txt, cmake_install.cmake
等文件,并且生成了Makefile
这些文件大部分没用,最关键的是,它自动生成了Makefile
- 再进行工程的实际构建,在这个目录输入make命令:
1 | make |
输出大概是这个样子
- 这时候,我们需要的目标文件hello已经构建完成,位于当前目录,尝试运行一下:
1 | ./hello |
得到输出:
Hello World from Main
2.1.3 简单的解释
解析CMakeLists.txt,该文件名大小写相关,如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。(主工程文件夹通过CMakeLists.txt来连接各个子文件夹)
1 | PROJECT (HELLO) |
- 作用:你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言 。
- 注:这个指令隐式的定义了两个 cmake 变量:
<projectname>_BINARY_DIR
以及<projectname>_SOURCE_DIR
,这里就是HELLO_BINARY_DIR
和HELLO_SOURCE_DIR
(所以CMakeLists.txt
中两个MESSAGE
指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所
在路径/backup/cmake/t1
,后面我们会讲到外部编译,两者所指代的内容会有所不同。 - 同时 cmake 系统也帮助我们预定义了
PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
变量,他们的值分别跟HELLO_BINARY_DIR
与HELLO_SOURCE_DIR
一致。 - 为了统一起见,建议以后直接使用
PROJECT_BINARY_DIR
,PROJECT_SOURCE_DIR
,即
使修改了工程名称,也不会影响这两个变量。如果使用了<projectname>_SOURCE_DIR
,修改工程名称后,需要同时修改这些变量。
1 | SET(SRC_LIST main.c) |
- 作用:定义变量
- 指令:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
- 特点:比如我们用到的是 SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:
SET(SRC_LIST main.c t1.c t2.c)
1 | MESSAGE(STATUS "This is BINARY dir" ${HELLO_BINARY_DIR}) |
- 作用:用于向终端输出用户定义的信息,包含三种类型:
SEND_ERROR
和SATUS
和FATAL_ERROR
- 指令:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
1 | ADD_EXECUTABLE(hello ${SRC_LIST}) |
- 作用:定义了这个工程会生成一个文件名为hello的可执行文件,相关的源文件是 SRC_LIST 中
定义的源文件列表, 本例中你也可以直接写成ADD_EXECUTABLE(hello main.c)
2.1.4 基本语法规则
1,变量使用${}
方式取值,但是在IF
控制语句中是直接使用变量名
2,指令(参数 1 参数 2…)
参数使用括弧括起,参数之间使用空格或分号分开。
以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成:
ADD_EXECUTABLE(hello main.c func.c)或者
ADD_EXECUTABLE(hello main.c;func.c)
3,指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。
4, 这里需要特别解释的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的。
hello 定义了可执行文件的文件名,你完全可以写成:
ADD_EXECUTABLE(t1 main.c)
编译后会生成一个 t1 可执行文件。
5,清理工程:
跟经典的 autotools 系列工具一样,运行:1
make clean
即可对构建结果进行清理。
2.2 更好一点的Hello World
2.2.1 更好表现在:
更好一点的Hello World,就是要让:
- 编译结果都放在
build
文件夹内:cmake ..
- 源文件放在
src
文件夹下- 将构建后的目标文件放入
build
目录的bin
子目录 :add_subdirectory(src bin)
- 利用
install
指令安装文件于特定文件夹下
2.2.2 指令学习
A 指令add_subdirectory
该指令是构建文件联系的关键指令,在工程文件的根目录的CMakeLists.txt
加入该指令,指向存放源文件的文件夹。
B 指令install
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
学习时,需要安装什么样的文件,直接去查看指令例子,跟随安装即可
B.1 目标文件的安装
指令介绍
1 | install( ) |
举个栗子
B.2 普通文件的安装
B.3 非目标文件的可执行程序安装(比如脚本之类)
C 注意
想要完成安装,在执行完cmake
和make
之后,还需执行make install
1 | cmake .. |
2.2.3 问题
A cmake的整体流程
需要看哔哩哔哩的视频
- 工程中的各个文件夹之间,通过
CMakeLists.txt
文件联系起来 - cmake想要执行一个
main.c
的程序,应该包括- 指令
add_executable
用于生成可执行二进制文件 - 指令
add_subdirectory
包含下一级文件夹,放在工程根目录的CMakeLists.txt
中。有了这个指令,你就可以把源文件main.c
放到其他文件夹里去啦。 - 指令
install
安装的意思,我目前理解,就是把想要的文件,复制到你想要存放的目标文件夹中去。
- 指令
- cmake编译和链接指令
cmake .
make
- 库的创建
add_library
- 其他辅助指令:
set_target_properties
get_target_property
- 库的使用
include_directories
加入头文件搜索路径,这样头文件就不用放在.c
文件一起了link_directories
加入库文件搜索路径target_link_libraries
将库文件链接到目标文件中
2.3 静态库和动态库
2.3.1 简述静态库和动态库
库文件是干啥的,库文件就是,一个项目程序,不止一个main.c
程序,还有函数文件程序,比如hello.c
文件和对应的hello.h
文件。
库要利用hello.c
文件制作静态库文件.a
或动态库文件.so
。电
动态库和静态库的区别在于:静态库在编译的时候会直接整合到目标程序中,生成的可执行文件不需要依赖额外的库来运行,它一个人就能行。动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。
更详细内容见问题B
2.3.2 指令学习
在lib
目录下建立CMakeLists.txt
,内容如下
1 | set(LIBHELLO_SRC hello.c) |
bash
中通过:
1 | cmake -DCMAKE_INSTALL_PREFIX=/usr .. |
A 指令add_library
指令
1
2
3add_library(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 .. sourceN)库类型
- SHARED, 动态库
- STATIC, 静态库
- MODULE, 在使用dyld的系统有效,如果不支持dyld,则被当做SHARED对待
- 动态库和静态库的区别:静态库是编译的时候就加入程序,动态库是调用的时候加入程序
B 指令set_target_properties
指令
1
2
3set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
C 指令get_target_property
指令
1
get_target_property(VAR target property)
举例
1
2
3get_target_property(OUTPUT_VALUE hello_static OUTPUT_NAME)
message(STATUS "This is the hello_static
OUTPUT_NAME:"${OUTPUT_VALUE})
2.3.3 问题
A 安装共享库和头文件问题
问题描述
1 | # lib/CMakeLists.txt 中内容 |
在运行到make install
时出错。
1 | ayzp@ayzp-Lenovo-G40-45:~/backup/cmake/t3/build$ make install |
解决
有些安装需要root密码,因而使用指令:
1 | sudo make install |
B 静态库和动态库的区别
引:最近做了算法产品化相关的一些工作,其中涉及到算法库封装的相关工作,封装为动态库。总结动态库和静态库区别和对应使用原则。
区别:静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。
分别介绍:
静态(函数)库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态函数库
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
总结:综上,不能看出:
从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。
————————————————
版权声明:本文为CSDN博主「wonengguwozai」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wonengguwozai/article/details/93195827
2.4 如何使用外部共享库和头文件
2.4.1 简述
通过add_library
构建好动态库、静态库以后。怎么使用这些库呢?
1 | include_directories() #加入非标准的头文件搜索路径 |
2.4.2 指令学习
A 引入头文件搜索路径
如何通过include_directories
指令加入非标准的头文件搜索路径
作用:用于搜索系统头文件
.h
指令说明
1
include_directories( dir1 dir2 ... )
举例
现在我们在
src/CMakeLists.txt
中添加一个头文件搜索路径、方式很简单,加入:1
include_directories(/usr/include/hello)
B 为target添加共享库
将目标文件链接到libhello。(也就是将hello.c构建的动态库或静态库链接到hello.h)
- 链接的是
.c
文件,目的是将.c文件,链接到你main
函数要执行的文件中。
B.1 如何通过link_directories
指令加入非标准的库文件搜索路径
1 | link_directories(dir1 dir2 ...) |
B.2 如何通过target_link_libraries
为库或可执行二进制加入库连接
1 | target_link_libraries(target library1 |
作用:将目标文件链接到libhello
举例:
例1:链接到动态库
1
2
3
4
5target_link_libraries(main hello)
#或者
target_link_libraries(main libhello.so)
# 这里main是文件夹src/下的文件main.c,主函数main(){}就在main.c中。
# libhello.so是上一讲创建的动态库,里面有hello.c这个函数。
例2:链接到静态库target_link_libraries(main libhello.a)
三 未完待续
参考文献
[1] 《Cmake Practice》 — Cjacker
[2] wonengguwozai. 通俗理解动态库与静态库区别. CSDN. 2019. https://blog.csdn.net/wonengguwozai/article/details/93195827