cmake翻译手册详解
cmake FAQ问题
一 目录结构
++++++++++++++目录结构-->'最佳实践'++++++++++++++
1)对于一个优秀的程序员而言,不仅仅是'编写代码的能力'很强,'技术'高深,还有着强大的'组织文件结构'以及'程序版式'能力
2)这些虽然'不会影响'着程序的运行效果,但是能体现一个'优秀程序员'的专业素质,更能够帮助我们清晰的'理解'和'阅读'
思考: 是否要将'.h对应的c|cpp'文件单独放一个目录(lib)、而将'业务逻辑'的'c|cpp'放到src下?
思考:将.h和.cpp放到一个目录下,然后通过'文件后缀'的形式
cmake里面怎样'搜索'指定目录下'特定后缀'的文件:file(GLOB SEL_FILES ${PATH}/*.properties)
二 相关指令
cmake FAQ汇总
(1) target相关
① add_executable
Cmake命令之add_executable介绍
作用: 指定的'源文件'来生成'目标可执行'文件
add_executable (Demo demo1.cpp demo2.cpp)
+++++++++++++++'多个源文件时候'+++++++++++++++
1)将该目录下所有的'源文件'以'列表'的形式保存到'变量'中
aux_source_directory(${PROJECT_SOURCE_DIR}/src} SRC_LIST)
add_executable(main ${SRC_LIST})
备注: 一定要把构成'可执行文'件的'所有源文件'都添加进去,否则就会'犯错'
补充: '默认'情况下,可执行文件将会在'构建树(BUild Tree)'的'路径下'被创建,'对应于'该命令被调用的'源文件树'的路径
强调: add_executable'不包括'头'(.h)'文件
CMake生成.o目标文件然后去链接静态库
add_executable错误总结
Clion 自动添加add_executable
② add_library
cmake BUILD_SHARED_LIB变量
说明1: 每个'add_library'只能生成一个库-->'静态|动态',默认是'静态'库
说明2: 一个'.h'头文件对应的'.c|.cpp'可能有多个,这个时候需要'aux_source_directory'的指令-->'同上'
SET(LIBHELLO_SRC hello.c java.c) -->'再多就要使用' -->'aux_source_directory'
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
报错
(2)编译原理相关
① include_directories
include_directories:指定'查找头文件'的搜索路径;相当于指定'gcc的-I(include)'参数
附加: 一般'.h'和对应'.c和.cpp'文件放在'不同的目录'下
1)cmake本身'不提供'任何'搜索库'的便捷方法,所有搜索库并'给变量赋值'的操作必须由'cmake代码'完成
++++++++++++++'案例讲解'++++++++++++++
1)'比如'我现在想要#include "cv.h",但是这个cv.h的路径是/usr/local/include/opencv
2)'那么'我'总不能'在主函数头前写#include "/usr/local/include/opencv/cv.h"吧
3)这个时候就'用到include_directories了',它提供了一个'搜索头文件暂时的根目录',可以在Cmakelists.txt中写上include_directories(/usr/local/include)来让库文件搜索以'/usr/local/include'为基础
4)'效果'即在main函数前'以相对路径'写上#include “opencv/cv.h"即可
++++++++++++++'分割线'++++++++++++++
include_directories (${HELLO_SOURCE_DIR}/Hello)
效果: '增加'Hello为include目录
为什么是'(add)增加'呢: 因为'默认'会在'/usr/local/include/'和'/usr/include/'中寻找
备注: 涉及""和 '<>'搜索的区别
补充: 官网'不推荐'使用INCLUDE_DIRECTORIES
include_directories 和target_include_directories的区别
② 头文件和库的区别
1)头文件作为一种包含功能函数、数据'接口声明'的载体文件,主要用于'保存'程序的'声明'declaration
2)头文件的'本身'不需要包含程序的逻辑实现代码,它只起'描述性'作用,用户程序只需要按照头文件中的'接口声明'来'调用'相关函数或变量,'链接器'会从库中'寻找'相应的实际定义代码
3)而'定义文件(c|cpp)'用于保存程序的'实现'implementation
.h和.cpp文件的区别
++++++++++'.h 和.cpp'是'怎么关联'在一起的++++++++++
1)'.h文件'叫头文件,'#include'是包含一个文件
备注: 这个文件'并非一定要.h文件',即使'普通的文本文件'也可以的,只是大家'约定俗成'的,可以理解为'规范'
2)当'编译器'看到'#include以后'就会'查找这个包含进来的文件','找到'就'连同对应的.cpp一起编译',没有找到就'报错'
③ link_directories
link_directories:'动态'链接库或'静态'链接库的'搜索'路径;相当于'gcc的-L(Library)'参数;也相当于环境变量中增加'LD_LIBRARY_PATH'的路径的作用
核心: 查看'库文件(Linux下是.a和.so文件)'路径
1)该指令的作用'主要'是指定'要链接的库文件(static|dynamic)'的路径
备注: 该指令有时候'不一定需要',因为'find_package'和'find_library指令'可以得到'库文件的绝对路径'
2)最佳'实践':自己'写'的动态库文件放在'自己新建的目录下'时,可以用该指令'指定该目录的路径'以便工程能够'找到'
3)link_directories (${PROJECT_BINARY_DIR}/Hello) '增加'Hello为link目录
补充: 可以指定'多个路径',以'空格'分开
备注: link_directories仅对'其后面'的targets起作用
LINK_DIRECTORIES不起作用,报错
④ target_link_libraries
target_link_libraries:添加'链接库';相同于gcc'指定-l'参数
target_link_libraries(demo Hello) 将'可执行文件'与'Hello连接'成'最终'文件demo
细节: 换句话说'add_executable(demo ${SRC_LIST})'还不是最终的'demo',没有经过'链接'的环节
细节: 'TARGET_LINK_LIBRARIES'要写在'ADD_EXECUTABLE'后面
target_link_libraries里库文件的顺序'符合gcc链接顺序'的规则,即'被依赖的库'放在'依赖它的库'的后面
1)cmake中链接库的顺序是'a依赖b',那么b放在a的后面。
2)例如'二进制可执行程序test'依赖a库,b库, 'a库又依赖b 库', 那么'顺序如下':
TARGET_LINK_LIBRARIES(test a;b)
备注: 空格或';'都可以
cmake 的link_libraries和target_link_libraries
target_link_libraries链接库的顺序
(3)C/C++程序编译过程详解
① 编译四个阶段
1)预处理(Preprocessing)
2)编译(Compilation)
3)汇编(Assemble) -->'as'
4)链接(Linking) -->'ld'
备注: 对应'gcc'的编译'参数'
② 编译图
③ 具体分析
参考博客
④ 继续探究
⑤ 小结
平时使用gcc命令的时候'没有关心中间结果','但'每次程序的编译都'少不了'这几个步骤
c语言编译过程详解 预处理、编译、汇编、链接
四 cmake脚本和CMakeLists.txt
(1)二者的区别和联系
(2)CMAKE关于PATH的
(3)cmake_include_path和cmake_library_path
① 引入
② 区别
说明: 添加'头文件目录'和'库文件目录'的'搜索'路径 --> 'bash'的方式
export CMAKE_INCLUDE_PATH=/usr/include/hello
export CMAKE_LIBRARY_PATH=/usr/include/hello
备注: '区别'于cmake指令中'include_directories'和' link_directories'指令,选择'哪种方式'根据自己的'喜好'
③ FIND_PATH和FIND_LIBRARY结合使用
说明: '以头文件目录为案例'
+++++++++++++(1)'linux中设置'+++++++++++++
cat >> ~/.bashrc <自动使用'CMAKE_INCLUDE_PATH'系统环境变量'查找hello.h'所在的'路径',然后'保存'到变量中
INCLUDE_DIRECTORIES(${myHeader}) -->'/usr/include/hello/'
FIND_LIBRARY(HELLO_LIBRARY libhello.so) -->自动使用'CMAKE_LIBRARY_PATH'的系统环境变量'查找'然后'保存'到变量中
TARGET_LINK_LIBRARIES(main ${HELLO_LIBRARY}) -->将'库'和'二进制可执行程序'进行'链接'
'强调':如果你'不使用' FIND_PATH,CMAKE_INCLUDE_PATH 变量的设置是'没有作用'的,你'不能指望'它会直接为'编译器命令添加参数'
备注: '不推荐'这种方式,'库和头文件'不在'C|C++工程结构中',和操作系统'强关联'
Linux中环境变量PATH,CMAKE_PREFIX_PATH,LIBRARY_PATH与LD_LIBRARY_PATH区别,以及顺序
CMakeList 中 find_library 用法
FIND_PACKAGE
FIND_PACKAGE 其实'内部'主要还是通过'FIND_PATH','FIND_LIBRARY'等'基础命令'实现的,换句话说还需要'环境变量'
④ FIND_PACKAGE采用两种模式搜索库
FIND_PACKAGE 命令用于'搜索'并加载'外部工程',其'隐含的变量'用于'标识'是否'搜索'到所需的'package_name'
find_package命令详解
1)Module模式
CMAKE_MODULE_PATH
备注: 这里'标识'有错'误','CMAKE_MODULE_PATH'是'cmake自身的内置变量'
Module模式:搜索'CMAKE_MODULE_PATH'指定路径下的'FindXXX.cmake'文件,'执行该文件'从而'找到XXX库'
备注: 具体'查找库'并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个'变量赋值的操作'由FindXXX.cmake模块完成
++++++++++++++'CMAKE_MODULE_PATH'指定的路径++++++++++++++
1)/share/cmake-x/Mdodules
备注1:x表示'大'版本号,centos7中如果是'2.x.x'系列,则不指定;'3.x.x'系列指定为'3'
++++++++++++++'CMAKE_ROOT'的路径++++++++++++++
1)其中'CMAKE_ROOT'是你在'安装cmake'的时候的系统路径
2)如果'没有指定'安装路径,则是'系统默认'的路径,在我的系统中(Centos7.7)系统的默认路径是'/usr/loacl'
3)如果你在安装的过程中使用了'cmake -DCMAKE_INSTALL_PREFIX=自己dir路径' ,那么此时'CMAKE_ROOT'就代表那个你'写入'的路径
备注: '$CMAKE_ROOT'的具体值可以通过CMake中'message命令'输出
小结: 这称为'模块(Module)'模式,也是'默认'的模式
++++++++++++++'实践'++++++++++++++
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
说明: 基于'原来的','添加'一个'查找'路径
2)Config模式
Config模式:搜索'XXX_DIR'指定路径下的'XXXConfig.cmake'文件,'执行'该文件从而'找到'XXX库
备注: 其中具体查找库'并给XXX_INCLUDE_DIRS'和'XXX_LIBRARIES'两个变量'赋值的操作'由'XXXConfig.cmake'模块完成
+++++++++++Config '搜索路径'+++++++++++
1)搜索xxx_DIR '指定'的路径
2)如果在CMakeLists.txt中'没有设置'这个cmake变量,也就是说'没有下面的指令':
set(xxx_DIR "xxxConfig.cmkae文件所在的路径")那么Cmake'就不会搜索'xxx_DIR指定的路径
3)Cmake 会在'/usr/local/lib/cmake/xxx/'、 '/usr/local/share/xxx' 中的xxxConfig.cmake文件
备注: 这个路径'不同的操作系统'存在差异,当前'Centos7.7'是'/usr/share/cmake{,3}/Modules/'
3)实践
场景: 由于Caffe安装时'没有安装'到系统目录,因此'无法自动找到'CaffeConfig.cmake,在'CMakeLists.txt'最前面'添加了一句话'之后就可以了
+++++++++++++++++++'配置步骤'+++++++++++++++++++
(1)'添加'CaffeConfig.cmake的搜索路径
set(Caffe_DIR /home/wzj/projects/caffe/build)
(2)查找'Caffe'模块
find_package(Caffe REQUIRED)
指令含义: 表示此包是'必须的(required)',如果'没有找到',构建程序'报错'并'终止'
首先:采用默认的'Module'模式,在'CMAKE_MODULE_PATH'中寻找'FindCaffee.cmake'文件,没有找到,则转下
然后:采用'Config'模式,在上面定义的'Caffe_DIR'路径中,搜索'CaffeConfig.cmkae文件'
备注:这里假设'Config'模式
(3)模块'是否'找到 -->'包状态变量判断'
if(NOT Caffe_FOUND)
message(FATAL_ERROR"Caffe Not Found!")
endif(NOT Caffe_FOUND)
(4)找到则'加入''include_directories'目录
include_directories(${Caffe_INCLUDE_DIRS})
add_executable(useSSD ssd_detect.cpp)
target_link_libraries(useSSD${Caffe_LIBRARIES})
4)小结
find_package与CMake如何查找链接库详解
1)cmake'默认'采取'Module'模式
2)如果Module模式'未找到'库,才会'采取Config'模式
也即:如果在Module的搜索路径'中没有找到'对应的'cmake file',则使用config模式
3)总之,Config模式是一个'备选策略'
补充:通常'库安装时'会拷贝一份'XXXConfig.cmake'到'系统'目录中,因此在'没有显式'指定'搜索路径时'也可以顺利'找到'
(3)指令顺序
target_link_libraries 要在 add_executable '之后',link_libraries 要在 add_executable '之前'
FIND_系列的变量
FIND_系列指令主要包含一下指令:
FIND_FILE( name1 path1 path2 ...)
VAR变量代表找到的文件全路径,包含文件名
FIND_LIBRARY( name1 path1 path2 ...)
VAR变量表示找到的库全路径,包含库文件名
FIND_PATH( name1 path1 path2 ...)
VAR变量代表包含这个文件的路径。
FIND_PROGRAM( name1 path1 path2 ...)
VAR变量代表包含这个程序的全路径。
⑤ 编译图
project(Install VERSION 1.0)
VERSION ${PACKAGE_VERSION}
cmake指令的先后顺序
add_definitions:添加编译参数 -->'添加编译宏定义'
>> add_definitions(-DDEBUG)将在gcc命令行添加DEBUG宏定义;
>> add_definitions( “-Wall -ansi –pedantic –g”)
⑤⑥ ⑦
set_target_properties( ... ): lots of properties... OUTPUT_NAME, VERSION, ....
⑧⑨
⑩
2.3 FIND_PACKAGE(VTK REQUIRED)
FIND_PACKAGE 命令用于搜索并加载外部工程,其'隐含的变量'用于'标识'是否'搜索'到所需的'package_name'
(4)LINK_DIRECTORIES (link_directories):静态链接库目录,供linker(链接器)使用
(5)ADD_EXECUTABLE (add_executable):可执行文件
(6)INCLUDE_DIRECTORIES(include_directories): .h头文件
(7)TARGET_LINK_LIBRARIES(target_link_libraries): .so、.a文件
#生成可执行文件 注意顺序,不然会有link错误,或未定义的声明
ADD_EXECUTABLE(CMakeFF ./src/ffplay.c ./src/cmdutils.c main.c)
#查找当前目录下的所有源文件,并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
列表(Lists)只是分号分隔的字符串;foreach可以迭代列表
了解'cmake的基本指令'以及CMakeLists.txt的写法非常重要,其基础是了解'编译原理'。另外对cmake编译的代码进行'调试',需要了解CMakeList.txt的'语法'
cmake命令之add_executable
实践+原理从文件或终端读写yaml和txt文件
1)场景:如果'源文件很多',把所有源文件的名字都加进去将是一件'烦'人的工作
2)更'省事'的方法是使用 'aux_source_directory' 命令 -->'源文件目录'
特点: 该命令会'查找指定目录下'的所有'源文件',然后将'结果'存进'指定变量名'
3)效果:cmake 会将当前目录'所有源文件'的'文件名'赋值给变量 DIR_SRCS ,再'指示变量' DIR_SRCS 中的源文件需要'编译成'一个名称为 Demo 的'可执行文件'
备注: SRC_LIST
# 4.source directory,源文件目录
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
+++++++++++++++'分割线'+++++++++++++++
add_library、add_executable、target_link_libraries -->多个文件
configure_file、options指令使用
如何通过 ADD_LIBRARY 指令构建动态库和静态库;
如何通过 SET_TARGET_PROPERTIES 同时构建同名的动态库和静态库;
如何通过 SET_TARGET_PROPERTIES 控制动态库版本;
引入'头文件搜索路径' INCLUDE_DIRECTORIES(/usr/include/hello)
LINK_DIRECTORIES(directory1 directory2 ...) 添加'非标准的共享库'搜索路径
ADD_DEPENDENCIES,定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
+++++++++++++++++'分割线'+++++++++++++++++
模块的使用和自定义模块
FIND_PACKAGE(CURL) 这句指令会'自动产生一些变量',如CURL_FOUND CURL_INCLUDE_DIR CURL_LIBRARY,每一个模块都会定义以下几个变量:
_FOUND
_INCLUDE_DIR or _INCLUDES
_LIBRARY or _LIBRARIES
FIND_PACKAGE([package name] REQUIRED) REQUIRED 参数,其含义是指这个'共享库'是否是工程必须的,如果使用了这个参数,说明这个链接库是'必备库',如果找不到这个链接库,则工程'不能编译'
MAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止
4,CMAKE_CURRRENT_BINARY_DIR
如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 target 编译目录。
使用我们上面提到的 ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。
使用 SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
5,CMAKE_CURRENT_LIST_FILE
输出调用这个变量的 CMakeLists.txt 的完整路径
6,CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行
7,CMAKE_MODULE_PATH
这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。
比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
这时候你就可以通过 INCLUDE 指令来调用自己的模块了。
8,EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。
+++++++++++++++'分割线'+++++++++++++++
2,ADD_DEPENDENCIES
定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
ADD_DEPENDENCIES(target-name depend-target1
depend-target2 ...)
让一个顶层目标依赖于其他的顶层目标。一个顶层目标是由命令ADD_EXECUTABLE,ADD_LIBRARY,或者ADD_CUSTOM_TARGET产生的目标。为这些命令的输出引入依赖性可以保证某个目标在其他的目标之前被构建。查看ADD_CUSTOM_TARGET和ADD_CUSTOM_COMMAND命令的DEPENDS选项,可以了解如何根据自定义规则引入文件级的依赖性。查看SET_SOURCE_FILES_PROPERTIES命令的OBJECT_DEPENDS选项,可以了解如何为目标文件引入文件级的依赖性。
3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY
ADD_EXECUTABLE(可执行文件名 生成该可执行文件的源文件)
说明源文件需要编译出的可执行文件名
例:
ADD_EXECUTABLE(hello ${SRC_LIST})
说明SRC_LIST变量中的源文件需要编译出名为hello的可执行文件
5,AUX_SOURCE_DIRECTORY
基本语法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。
7,EXEC_PROGRAM
在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。
具体语法为:
EXEC_PROGRAM(Executable [directory in which to run]
[ARGS ]
[OUTPUT_VARIABLE ]
[RETURN_VALUE ])
用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量.
这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。
举个简单的例子,我们要在 src 目录执行 ls 命令,并把结果和返回值存下来。
可以直接在 src/CMakeLists.txt 中添加:
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE
LS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)
在 cmake 生成 Makefile 的过程中,就会执行 ls 命令,如果返回 0,则说明成功执行,那么就输出 ls *.c 的结果。关于 IF 语句,后面的控制指令会提到。
变量高级参考
++++++++++++'安装yaml-cpp库'++++++++++++
git clone https://github.com/jbeder/yaml-cpp.git
cd yaml-cpp
mkdir build && cd build
cmake -DYAML_BUILD_SHARED_LIBS=ON ..
make -j4
sudo make install