零基础CMake学习笔记
- 1. 名词解释
- make cmake Makefile CMakeLists.txt
- 2. CMake语法介绍
- 2.1 常用指令
- 2.1.1 project
- 2.1.2 set
- 2.1.3 message
- 2.1.4 add_executable
- 2.1.5 aux_source_directory
- 2.1.6 include_directories
- 2.1.7 add_definitions
- 2.1.8 add_subdirectory
- 2.1.9 add_library
- 2.1.10 link_directories
- 2.1.11 target_link_libraries
- 2.2 指令编写时的注意事项
- 3. CMakeLists.txt外部构建
- 4. 源码结构工程化
- 4.1 工程化思路
- 4.2 将目标文件放入构建目录的 bin 子目录
- 4.3 更改二进制的保存路径
- 5. 安装
- 5.1 两种安装方法
- 5.2 如何安装 HelloWord
- 5.3 安装文件 COPYRIGHT 和 README
- 5.4 安装脚本 runhello.sh
- 5.5 安装 doc 中的 hello.txt
- 5.6 安装指令
- 6. 静态库(lib)和动态库(dll)
- 6.1 lib 和 dll必知
- 6.2 dll 和 lib 的区别
- 6.3 lib 和 dll 实例构建(含例子和问题)
- 6.3.1 ADD_LIBRARY
- 6.3.2 同时构建静态和动态库
- 6.3.3 SET_TARGET_PROPERTIES
- 6.3.4 动态库的版本号
- 6.3.5 安装共享库和头文件
- 6.3.6 使用外部共享库和头文件
- 6.3.7 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH(特殊的环境变量)
- 6.3.8 生产debug版本的方法:
- 解决:make后头文件找不到的问题
- 解决:找到引用的函数问题
来自B站的一个大佬,有几年了,找不到了作者了,他的视频讲的不错的!
1. 名词解释
make cmake Makefile CMakeLists.txt
-
make
注意: 依赖
Makefile(makefile)
文件make命令结果: 产生可执行文件
-
自动化编译工具。当需要编译的文件较多时,使用make工具会大大提高效率
-
make 会在当前的目录下搜寻 Makefile文件,按照文件中的编译规则进行批处理编译
-
make 会自动的判别原始码是否经过变动,进而自动更新执行档
-
-
cmake
注意: 依赖
CMakeLists.txt
文件cmake命令结果: 产生一大堆文件,包括make需要用到的Makefile文件
-
高级编译配置工具。当n个人使用不同的编程语言或者编译器开发同一个项目时,最终完成只要求输出一个可执行文件或者共享库(dll,so等等),这时候就需要使用CMake
-
CMake所有操作都是通过编译
CMakeLists.txt
来完成
-
-
Makefile(makefile)文件
- 由cmake执行之后生成
- 在Linux/Windows下编译c/c++/java代码时用到的一种脚本文件,该文件包含了工程文件的编译规则
- 每一个功能都要写一个Makefile文件,如果工程很大,并且相关性比较强的话,makefile的书写就会变得相对繁琐,更要命的是如果以后需要添加新的功能或者是新人需要修改功能的话,看起来就会特别麻烦
-
CMakeLists.txt
- 该文件包含了所需要编译的源文件(c/cpp/java)的编译器、名称、依赖、路径等信息
大体结构编写流程:
cmake_minimum_required(VERSION 版本) #必须 project(工程名称) #必须add_subdirectory(子文件夹名称) #父目录必须,子目录不必 add_library(库文件名称 STATIC 文件) #通常子目录(二选一) add_executable(可执行文件名称 文件) #通常父目录(二选一)include_directories(路径) #必须 link_directories(路径) #必须 target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称) #必须
后面根据需求补充set语句、message语句和判断语句等
2. CMake语法介绍
注意是写在 CMakeLists.txt
文件中
2.1 常用指令
2.1.1 project
作用: 可以用来指定工程的名字和支持的语言,默认支持所有语言
基本语法:
project (工程名称) #指定了工程的名字,并且支持所有语言——建议
project (工程名称 CXX) #指定了工程的名字,并且支持语言是C++
project (工程名称 C CXX) #指定了工程的名字,并且支持语言是C和C++
project (工程名称 C CXX JAVA) #指定了工程的名字,并且支持语言是C、C++和JAVA
注意:
编写了"PROJECT命令"之后,该命令会隐式定义两个make变量,如下:
<projectname>_BINARY_DIR <projectname>_SOURCE_DIR
message命令 就可以直接使用者两个变量,当前都指向当前的工作目录
问题: 如果改了工程名,隐式定义两个 make变量 也会改变
解决方法: 使用预定义变量:PROJECT_BINARY_DIR
和 PROJECT_SOURCE_DIR
,去替代 project命令 隐式定义的两个 make变量
2.1.2 set
作用: 给文件名、路径名或其他字符串起别名,用 ${变量}
即可获取变量中的内容
基本语法: set(变量 文件名/路径/...)
# 举例 SRC_LIST变量包含了多个cpp文件
set(SRC_LIST xxx.cpp xxx1.cpp xxx2.cpp ...)
2.1.3 message
作用: 向终端输出用户自定义的信息
主要包含三种信息:
- SEND_ERROR: 产生错误,生成过程被跳过
- SATUS: 输出前缀为—的信息
- FATAL_ERROR: 立即终止所有 cmake 过程
2.1.4 add_executable
作用: 将.cpp/.c/.cc
文件生成可执行文件
基本语法:
add_executable(可执行文件名称 文件1 文件2 ...)
add_executable(可执行文件名称 ${变量})
,这里获取set命令
设置的变量
2.1.5 aux_source_directory
作用: 获取路径下所有的.cpp/.c/.cc
文件,并赋值给某个变量
基本语法: aux_source_directory(路径 变量)
2.1.6 include_directories
作用: 向工程添加多个特定.h
头文件搜索路径。相当于g++编译器的 -I
参数
基本语法: include_directories(dir1 dir2 ...)
2.1.7 add_definitions
作用: 添加编译选项
基本语法: add_definitions(编译选项)
2.1.8 add_subdirectory
作用: 向当前工程添加存放源文件的子目录,并可以指定 中间bin(二进制)文件 和 目标bin文件 存放的位置
注意: 子文件夹中也需要包含并编写 CMakeLists.txt 文件
基本语法:
add_subdirectory(子文件夹)
add_subdirectory(子文件夹 中间bin文件夹)
add_subdirectory(子文件夹 中间bin文件夹 目标bin文件夹)
- |==》如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在 子文件夹中
2.1.9 add_library
作用: 将cpp/.c/.cc
文件生成.a
静态库
注意: 库文件名称通常为libxxx.so
,在这里只要写xxx
即可
基本语法: add_library(库文件名称 STATIC ${变量})
2.1.10 link_directories
作用:向工程添加多个特定的.so/.a
库文件搜索路径,相当于指定g++编译器的 -L
参数
基本语法: link_directories(dir1 dir2 ...)
2.1.11 target_link_libraries
作用: 对add_library
或add_executable
生成的文件进行链接操作
基本语法:target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)
2.2 指令编写时的注意事项
- 变量使用
${变量}
方式取值,但是在if控制语句
中是直接使用变量名 - 指令
()
中的参数之间使用空格或分号分开。 - CMake命令与大小写无关,但其参数和变量是大小写相关的
- 如果源文件名中含有空格,就必须要加双引号(
"含有空格的文件名"
) add_executable(可执行文件名称 xxx)
中,xxx的后缀名可省略,它会自动去找xxx.c和xxx.cpp,最好不要这样写,可能会出现同名的源文件和文件夹
3. CMakeLists.txt外部构建
-
在与源码文件同一个文件夹下创建 build 文件夹,并进入
命令:
mkdir build && cd build
(或 先mkdir build
,再cd build
),文件夹名称并不一定使用build,但一般都用build -
使用cmake编译上级目录的CMakeLists
命令:
cmake ..
(或cmake CMakeLists.txt的绝对路径
),执行完成之后生成Makefile和其他文件(在build文件夹中) -
使用make生成可执行文件
命令:
make
-
执行可执行文件
命令:
./可执行文件名称
4. 源码结构工程化
4.1 工程化思路
-
添加子目录
- 添加一个子目录 src,用来放置工程源代码
- 添加一个子目录 doc,用来放置这个工程的文档
-
在工程目录添加文本文件
- COPYRIGHT:版权信息
- README
-
在工程目录添加一个
runhello.sh
脚本,用来调用 hello 二进制 -
将构建后的目标文件放入构建目录的 bin子目录
-
将 doc 目录的内容以及
COPYRIGHT/README
安装到/usr/share/doc/cmake/
4.2 将目标文件放入构建目录的 bin 子目录
每个目录下都要有一个CMakeLists.txt说明
[root@localhost cmake]# tree
.
├── build
├── CMakeLists.txt
└── src├── CMakeLists.txt└── main.cpp
外层CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
src下的CMakeLists.txt
ADD_EXECUTABLE(hello main.cpp)
4.3 更改二进制的保存路径
使用set 命令重新定义EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
变量,以此来指定最终的 目标bin文件 的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
问题: 应该加载哪个目录下的CMakeLists.txt文件
解决方法: 哪里要改变目标存放路径,就在哪里加入上述的定义,所以应该在 src文件夹 中的 CMakeLists.txt 下写
5. 安装
5.1 两种安装方法
- 从代码编译后直接 make install 安装
- 打包时的指定目录安装
# 简单指定目录
make install DESTDIR=/tmp/test
# 复杂指定目录
./configure –prefix=/usr
5.2 如何安装 HelloWord
使用CMake一个新的指令:INSTALL
INSTALL的安装可以包括:二进制、动态库、静态库以及文件、目录、脚本等
使用CMake一个新的变量:CMAKE_INSTALL_PREFIX
// 目录树结构
[root@localhost cmake]# tree
.
├── build
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│ └── hello.txt
├── README
├── runhello.sh
└── src├── CMakeLists.txt└── main.cpp3 directories, 7 files
5.3 安装文件 COPYRIGHT 和 README
基本语法:INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
参数解释:
-
FILES:文件
-
DESTINATION:
- 写绝对路径
- 可以写相对路径,相对路径实际路径是:
${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
CMAKE_INSTALL_PREFIX 默认是在 /usr/local/
cmake -DCMAKE_INSTALL_PREFIX=/usr
在cmake的时候指定CMAKE_INSTALL_PREFIX
变量的路径
5.4 安装脚本 runhello.sh
基本语法:INSTALL(PROGRAMS runhello.sh DESTINATION bin)
参数解释:
- PROGRAMS:非目标文件的可执行程序安装(比如脚本之类)
说明: 实际安装到的是 /usr/bin
5.5 安装 doc 中的 hello.txt
两种方法安装
- 通过在 doc 目录建立 CMakeLists.txt ,通过 install 下的 file
- 直接在工程目录通过
基本语法: INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
注意:
-
DIRECTORY 后面连接的是所在 Source 目录的相对路径
-
/
加与不加有很大的区别目录名不以
/
结尾:目录将被安装到目标路径下目录名以
/
结尾:目录中的内容安装到目标路径下
5.6 安装指令
cmake ..
#...
make
#...
make install
6. 静态库(lib)和动态库(dll)
6.1 lib 和 dll必知
-
两者的概念
- 动态链接库(Dynamic Link Library)
- Windows应用程序中,实行了模块化设计,也就是说并不是每个应用程序都编写完所有的功能代码,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,我们称之为dll文件,DLL是一个包含可由多个程序同时使用的代码和数据的库,类似于一个函数库。
- 动态链接提供了一种方法,使某个进程可以调用不属于它的可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。
- 分割成许多dll有助于促进代码重用和内存的有效使用,通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。
- 一般来说,DLL 是一种磁盘文件,以
.dll
、.DRV
、.FON
、.SYS
和许多以.exe
为扩展名的系统文件都可以是DLL。一个 DLL 在内存中只有一个实例,并且动态链接库通常都不能直接运行,也不能接收消息,它们是一些独立的文件。
- 静态链接库(Static Link Library)
- 静态编译将导出声明和实现都放在lib中,编译后是将所有代码嵌套在程序中,直接可以当成程序的一部分来使用。
- 动态链接库(Dynamic Link Library)
-
调用时两者的区别--------------------------------------------------------------------------------------------------------------
-
动态链接库
某个程序在运行中要调用某个DLL函数时,操作系统首先会查看所有正在运行的程序,看看在内存中是否已有此库函数的拷贝,如果有,则让其共享那一个拷贝,如果没有,则去链接载入DLL函数。
在程序运行的时候,被调用的DLL函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码 (Position Independent Code (PIC) )。 -
静态链接库
当要使用时,链接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库就不再需要。
-
6.2 dll 和 lib 的区别
静态库 | 动态库 | |
---|---|---|
拓展名 | 一般为.a 或.lib | 一般为.so 或.dll |
区别 | 静态库在编译时会直接整合到目标程序中, 编译成功的可执行文件可独立运行 | 态库在编译时不会放到连接的目标程序中, 编译成功的可执行文件无法单独运行 |
6.3 lib 和 dll 实例构建(含例子和问题)
任务:
1,建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出 Hello World 字符串。
2,安装头文件与共享库。
[root@localhost cmake2]# tree
.
├── build
├── CMakeLists.txt
└── lib├── CMakeLists.txt├── hello.cpp└── hello.h
hello.h中的内容
#ifndef HELLO_H
#define Hello_Hvoid HelloFunc();#endif
hello.cpp中的内容
#include "hello.h"
#include <iostream>
void HelloFunc(){std::cout << "Hello World" << std::endl;
}
项目中的cmake内容
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)
lib中CMakeLists.txt中的内容
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
6.3.1 ADD_LIBRARY
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
- hello:就是正常的库名,生成的名字前面会加上lib,最终产生的文件是libhello.so
- SHARED,动态库 STATIC,静态库
- ${LIBHELLO_SRC} :源文件
6.3.2 同时构建静态和动态库
// 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})// 修改静态库的名字,这样是可以的,但是我们往往希望他们的名字是相同的,只是后缀不同而已
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
6.3.3 SET_TARGET_PROPERTIES
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
同时构建静态和动态库
SET(LIBHELLO_SRC hello.cpp)ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})//对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
//cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
6.3.4 动态库的版本号
一般动态库都有一个版本号的关联
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
CMakeLists.txt 插入如下
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。
6.3.5 安装共享库和头文件
本例中我们将 hello 的共享库安装到/lib目录,
将 hello.h 安装到/include/hello 目录
//文件放到该目录下
INSTALL(FILES hello.h DESTINATION include/hello)//二进制,静态库,动态库安装都用TARGETS
//ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
注意:
安装的时候,指定一下路径,放到系统下
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
6.3.6 使用外部共享库和头文件
准备工作,新建一个目录来使用外部共享库和头文件
[root@MiWiFi-R4CM-srv cmake3]# tree
.
├── build
├── CMakeLists.txt
└── src├── CMakeLists.txt└── main.cpp
main.cpp
#include <hello.h>int main(){HelloFunc();
}
6.3.7 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH(特殊的环境变量)
注意:这两个是环境变量而不是 cmake 变量,可以在linux的bash中进行设置
我们上面例子中使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)
来指明include路径的位置
我们还可以使用另外一种方式,使用环境变量export CMAKE_INCLUDE_PATH=/usr/include/hello
6.3.8 生产debug版本的方法:
cmake .. -DCMAKE_BUILD_TYPE=debug
解决:make后头文件找不到的问题
PS:include <hello/hello.h>
这样include是可以,这么做的话,就没啥好讲的了,
关键字:INCLUDE_DIRECTORIES
,这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,
在CMakeLists.txt中加入头文件搜索路径:INCLUDE_DIRECTORIES(/usr/include/hello)
解决:找到引用的函数问题
报错信息:undefined reference to `HelloFunc()’
关键字:LINK_DIRECTORIES
,添加非标准的共享库搜索路径
指定第三方库所在路径,LINK_DIRECTORIES(/home/myproject/libs)
关键字:TARGET_LINK_LIBRARIES
,添加需要链接的共享库
TARGET_LINK_LIBRARIES的时候,只需要给出动态链接库的名字就行了。
在CMakeLists.txt中插入链接共享库,主要要插在executable的后面
查看main的链接情况
[root@MiWiFi-R4CM-srv bin]# ldd main linux-vdso.so.1 => (0x00007ffedfda4000)libhello.so => /lib64/libhello.so (0x00007f41c0d8f000)libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f41c0874000)libm.so.6 => /lib64/libm.so.6 (0x00007f41c0572000)libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f41c035c000)libc.so.6 => /lib64/libc.so.6 (0x00007f41bff8e000)/lib64/ld-linux-x86-64.so.2 (0x00007f41c0b7c000)
链接静态库
TARGET_LINK_LIBRARIES(main libhello.a)
end.