CMake是跨平台编译工具,比make更高级一些。其编译的主要工作是生成CMakeLists.txt文件,然后根据该文件生成Makefile,最后调用make来生成可执行程序或者动态库。所以基本步骤就只有两步

  • cmake生成Makefile件
  • make执行编译工作

1. 常用语法

1.1. 消息输出

message("Hello world!")

1.2. 设置变量

cmake 并没有类,但是我们可以通过定义一组变量,该组变量以相同的前缀来模拟类,并使用嵌套的 ${} 来引用这些变量。例如:

set(LEO_NAME "Leo Lee")file(GLOB_RECURSE FLANN_LIBRARIES ${THIRDPARTY}/flann-1.9.1/lib/lib*)

其中

set用于显示地指定变量,它是静态的,在语句中写的是什么就是什么;

file是动态的,它到系统路径中去找文件,找到了即进行赋值。

1.3. 算术运算

math(EXPR MY_SUM "1 + 1")                   math(EXPR DOUBLE_SUM "${MY_SUM} * 2")

1.4. 流程控制命令

流程控制命令包括 if/endif 和 while/endwhile

如,判断当前环境是否为 win32:

if(WIN32)message("You're running CMake on Windows.")
else ()message("You aren't running CMake on Windows.")
endif()

例如,使用 while/endwhile 循环打印出所有小于 20 的斐波那契数列:

set(A "1")
set(B "1")
while(${A} LESS "20")message("${A}")                        # 打印 Amath(EXPR T "${A} + ${B}")             # 计算 A + B 的值,并存储在变量 Tset(A "${B}")                           # 设置变量 A 的值为 Bset(B "${T}")                           # 设置变量 B 的值为 T
endwhile()

在 cmake 中,变量与数字进行比较的语法与其他语言不同,例如上面的小于比较,就使用了 LESS,其他比较条件的使用。

输出:

1
1
2
3
5
8
13

1.5. 列表:分号分隔的字符串

直接以例子说明。以下例子中,将三个参数传递给 math

set(ARGS "EXPR;T;1 + 1")
math(${ARGS})  # 等价于 math(EXPR T "1 + 1")
message(${T})

如果引用变量 ${} 外部添加双引号,则 cmake 会将整个字符串作为一个参数,并保留分号:

set(ARGS "EXPR;T;1 + 1")
message("${ARGS}")

输出:

EXPR;T;1 + 1

如果 ${} 不带双引号,例如:

set(ARGS "EXPR;T;1 + 1")
message(${ARGS})

则输出:

EXPRT1 + 1

如果有两个以上的参数传递给 set 命令,则它们会被分号连接,然后传递给指定的变量:

set(MY_LIST Please visit leehao.me)
message("${MY_LIST}")

输出:

Please;visit;leehao.me

可以使用 list 命令处理列表:

set(MY_LIST Please visit url leehao.me)
list(REMOVE_ITEM MY_LIST "url")
message("${MY_LIST}")

输出:

Please;visit;leehao.me

可以使用 foreach/endforeach 处理列表,迭代除第一个参数外的列表的所有项,并将每项赋值给第一个参数变量:

foreach(ARG Please visit url leehao.me)message("${ARG}")
endforeach()

输出:

Please
visit
url
leehao.me

1.6. 定义函数

在 cmake 中,可以使用 function/endfunction 来定义一个函数,例如下面的函数将参数值 * 2 后输出:

function(doubleIt VALUE)math(EXPR RESULT "${VALUE} * 2")message("${RESULT}")
endfunction()doubleIt("4")

输出:

8

函数中定义的变量不会影响调用方的作用域,如果需要返回值,可以将变量传递给函数,然后使用 set 命令,并指定 PARENT_SCOPE 参数:

function(doubleIt VARNAME VALUE)math(EXPR RESULT "${VALUE} * 2")set(${VARNAME} "${RESULT}" PARENT_SCOPE)    # 设置返回值
endfunction()doubleIt(RESULT "4")                    # RESULT 变量存储函数的返回值
message("${RESULT}")                    # 输出:8

cmake 中,使用 macro/endmacro 定义宏。与函数不同,宏内改变变量的值会影响调用方的作用域:

macro(doubleIt VARNAME VALUE)math(EXPR ${VARNAME} "${VALUE} * 2")
endmacro()doubleIt(RESULT "4")
message("${RESULT}")

1.7. CMAKE_CXX_FLAGS设置

# set compiler
# -O2 or -O3 优化
# -fPIC 产生与位置无关代码
# -g 调试信息
# -Wall 开启所有的编译警告
# -ffast-math -ffast-math -use_fast_math 浮点数计算加速
# -fdiagnostics-color 颜色设置
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fPIC -std=c++14 -g -Wall -ffast-math -ffast-math -use_fast_math -fdiagnostics-color=auto")

1.8. 警告

  • -w

禁止编译警告的打印。这个警告不建议使用。大约2012年底,公司代码进行一次大重构,另外从Codeblock集成开发环境转向Makefile管理,Makefile里面默认使用了-w,因而代码一直没有警告,今年个别项目开发中发现一些代码笔误导致的BUG,而这些问题可以从编译警告中知道。前几个月,领导安排我来fix这些警告。为了自己,为了后人,不建议使用-w选项。

  • -Werror

将所有的警告当成错误处理。此选项谨慎建议加上。有的开源库警告很多(大名鼎鼎的ffmpeg也有很多警告呢),一一改掉耗时耗人力,必要性也不大。最后,公司代码加入了一个开源库,里面有很多代码警告,可能领导又安排我来fix了。

  • -Wfatal-errors

遇到第一个错误就停止,减少查找错误时间。建议加上。很多人遇到错误,没有意识到从第一个开始排查。不管是编译错误,还是程序运行出错,从最开始的错误查起,是个好的做法。

-Wall开启“所有”的警告。强烈建议加上,并推荐该选项成为共识。如case语句没有default处理,有符号、无符号处理,未使用变量(特别是函数有大量未使用的数组,占用栈空间,测试发现,开辟一个未使用的8MB的数组,程序有coredump),用%d来打印地址,或%s打印int值等,都可以发出警告。

  • -Wextra

除-Wall外其它的警告。建议加上。

在GCC编译时,加上必要的警告选项,可以避免很多低级错误引发的问题,我就在实际工程代码中遇到用“==”来赋值,我自己写的代码也出现过把“=”当成判断的。但是,有些错误却不是用GCC选项能解决的。比如一般项目都会自定义调试信息打印函数,但在处理可变参数类型时,往往不注意。

1.9. 位置无关代码

PIC就是position independent code,告诉编译器产生与位置无关代码。即产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

PIC使.so文件的代码段变为真正意义上的共享。如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。

不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目。
因此,不用fPIC编译so并不总是不好。
如果你满足以下4个需求/条件:

  • 该库可能需要经常更新
  • 该库需要非常高的效率(尤其是有很多全局量的使用时)
  • 该库并不很大.
  • 该库基本不需要被多个应用程序共享

如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:

  • gcc默认开启-fPIC选项
  • loader使你的代码位置无关

从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。

1.10. 链接

target_link_libraries(hello A B.a C.so)

在上面的命令中,libA.so可能依赖于libB.a和libC.so,如果顺序有错,链接时会报错。还有一点,B.a会告诉CMake优先使用静态链接库libB.a,C.so会告诉CMake优先使用动态链接库libC.so,也可直接使用库文件的相对路径或绝对路径。使用绝对路径的好处在于,当依赖的库被更新时,make的时候也会重新链接。

gcc中库的链接顺序是从右往左进行,所以要把最基础实现的库放在最后,这样左边的lib就可以调用右边的lib中的代码。同时,当一个函数的实现代码在多个lib都存在时,最左边的lib代码最后link,所以也将最终保存下来。

2. RPATH

2.1. 什么是RPATH

在Linux环境下,使用动态链接的程序在运行时会自动链接 ld.so 这个库(OS X上是 dyld),然后通过 ld.so 来查找链接其它的库。而 RPATH 就是编译的时候链接到执行文件的链接库路径。OS X在 RPATH 的设置上和Linux还是有点出入的,OS X的 RPATH 采用的是绝对路径。

ld.so 搜索路径的优先级是这样的:

  • RPATH ,编译链接时加入 -rpath 参数指明所谓的 RUNPATH ,这样可执行文件(或者依赖其他动态链接库的动态链接库)就能告诉 ld.so 到哪里去搜索对应的动态链接库了。
  • LD_LIBRARY_PATH ,对于没有设定 RPATH 的可执行文件或者动态链接库,我们可以用 LD_LIBRARY_PATH 这个环境变量通知 ld.so 往哪里查找链接库。
  • /etc/ld.so.conf ,系统对 ld.so 的路径配置文件。
  • /usr/lib 、 /lib 和 /usr/local/lib ,系统默认路径。

2.2. Cmake和RPATH

在分发程序的时候,执行文件使用的链接库在系统内不一定会有,或者自带了的版本不对,一般都会在程序文件夹内都会附带相应的链接库,所以最好还是把 RPATH 加上。Cmake对RPATH提供了很多选项支持,我们一般只关注这几个变量就好了: CMAKE_SKIP_BUILD_RPATH 、 CMAKE_BUILD_WITH_INSTALL_RPATH 、 CMAKE_INSTALL_RPATH 和 CMAKE_INSTALL_RPATH_USE_LINK_PATH 。

2.3. 默认RPATH设置

set(CMAKE_SKIP_BUILD_RPATH FALSE)                 # 编译时加上RPATH
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)         # 编译时RPATH不使用安装的RPATH
set(CMAKE_INSTALL_RPATH "")                       # 安装RPATH为空
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)      # 安装的执行文件不加上RPATH  

Cmake在默认情况下, make install 会把安装的执行文件的 RPATH 删掉的,所以就会出现上面我执行安装好的执行文件报错的问题。

2.4. 加上完整的RPATH

Cmake的默认设置我们肯定是不能使用的,我们需要一个安装的时候也要带上 RPATH 的设置

set(INSTALL_LIB_DIR "${PROJECT_BINARY_DIR}/lib") # 假设安装目录在编译目录的lib子目录内set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)# 确保链接库不在系统默认安装的目录上时更改到项目lib上
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES ${CMAKE_INSTALL_RPATH} isSystemDir)
if("${isSystemDir}" STREQUAL "-1")  set(CMAKE_INSTALL_RPATH "${INSTALL_LIB_DIR}")
endif("${isSystemDir}" STREQUAL "-1")  

3. 示例

project(test) # version requirement
cmake_minimum_required(VERSION 2.8)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fPIC -std=c++14 -g -Wall -ffast-math -ffast-math -use_fast_math -fdiagnostics-color=auto")# set path of cmake files
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/modules)# set path of executable files
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)# set path of library
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)# find third party libraries
find_package(Boost COMPONENTS thread filesystem date_time system REQUIRED)
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(glog REQUIRED)
find_package(GTSAM REQUIRED)
find_package(Ceres REQUIRED)# report path of third party libraries
message(${EIGEN3_INCLUDE_DIR})# header files
include_directories(${Boost_INCLUDE_DIR})
include_directories(${EIGEN3_INCLUDE_DIR})
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${CERES_INCLUDE_DIRS})
include_directories(include)# set source files
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/src SOURCE_FILES)add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
target_link_libraries( ${PROJECT_NAME}${PCL_LIBRARIES}${Boost_LIBRARIES}${OpenCV_LIBS}glog${libLAS_LIBRARIES}
)add_executable(${PROJECT_NAME} main/test.cpp)
# link libraries
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} glog ${CERES_LIBRARIES})

如果需要链接自己在其它工程下编译的链接库,则需加下下列代码

# 指定链接库目录,链接库的名为common
link_directories(${CMAKE_SOURCE_DIR}/../common/lib/)
link_libraries(common.so)set(${PROJECT_NAME}common
)

参考文献

CMake 语言 15 分钟入门教程_Leo的博客-CSDN博客_cmake语言

gcc编译参数-fPIC的一些问题

GCC编译警告选项的学习_李迟的专栏-CSDN博客

[CMAKE] 详解CMakeLists.txt文件 - VictoKu - 博客园

CMake命令target_link_libraries链接库的顺序_zhujianwei31415的专栏-CSDN博客_target_link_libraries

CMAKE和RPATH_章志强的专栏-CSDN博客_cmake rpath

CMakeLists相关推荐

  1. TVM示例展示 README.md,Makefile,CMakeLists.txt

    TVM示例展示 README.md,Makefile,CMakeLists.txt TVM/README.md Open Deep Learning Compiler Stack Documentat ...

  2. CMakeLists.txt学习记录

    一.Cmake 学习地址与作用 cmake详细见:https://gitlab.kitware.com/cmake/community/-/wikis/home 是一个跨平台.开源的构建系统.它是一个 ...

  3. 一文详解CMakeLists文件编写语法规则详解

    作者丨zhanghm1995@blog 来源丨https://blog.csdn.net/zhanghm1995/article/details/80902807 编辑丨3D视觉工坊 基本语法规则 C ...

  4. CMakeLists.txt从入门到精通

    文章目录 前言 一 一般工程开头的一些设置案例 二 动态库与静态库的生成 2.1 动态库 2.2 静态库 三 优化选项的设置 四 生成库时的设置 五 常见依赖库的调用 5.1 OpenCV库 六 设置 ...

  5. CMakeLists.txt文件如何编写?(一 基础篇)

    本文首发于微信公众号「3D视觉工坊」--CMakeLists.txt文件如何写? 本文以linux平台下CMakeLists.txt文件书写方法总结. 一 开头通用模块 1.1 cmake版本要求 c ...

  6. “cmake 点点”表示在上一级目录(CMakeLists.txt所在目录)编译

    CMake学习(一)_福尔摩斯帅双的博客-CSDN博客 在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下: 编写 CMake 配置文件 CMakeLists.txt . ...

  7. Qt导入CMakeLists.txt后无法调试

    问题: Qt导入CMakeLists.txt后无法单步调试 解决方法: 在CMakeLists.txt后加入一句: SET(CMAKE_BUILD_TYPE DEBUG) 转载于:https://ww ...

  8. CMakeLists.txt的运行方法

    在学习pcl的时候,包括在别的一些学习过程中,我们经常会遇到书中提供的一些源码,但是我们不知道怎么运行,就像下面的一些代码 之前一直使用的方法就是创建VS工程,把CPP文件包含进去,然后运行,但是既然 ...

  9. CMakeLists.txt

    示例(在编写CMakeLists.txt前, 创建好src, include, lib, bin, build目录, 其中cmake ..在build目录中执行) # 一般都会先声明cmake的版本 ...

最新文章

  1. 2012 iis php mysql_Win2012 R2 IIS8.5+PHP(FastCGI)+MySQL运行环境搭建wordpress博客教程
  2. 如何编写oracle存储过程
  3. Linux服务器CPU、内存、磁盘空间、负载情况查看python脚本
  4. Vmware快速安装linux虚拟机(SUSE)
  5. SAP Commerce的Content Page,Content Slot和Component在Spartacus里的应用
  6. 帝国CMS7.5会员中心美化版V1.0GBKamp;UTF
  7. matlab转换为exe文件,MATLAB文件转为exe可执行文件(package使用)
  8. centos中安装配置nginx完成之后主机无法访问
  9. orcad中的PSpice仿真加入厂商模型
  10. Python入门学习—元组/字符串(FishC)
  11. 在线PPT—Sway初级教程
  12. android 蓝牙串口指令,蓝牙串口助手
  13. Linux环境安装ghostscript-9.25
  14. 输入等值线参数绘制等值线图python_专题复习:等值线(上)
  15. 外贸电子商务常见VISA信用卡或MasterCard等测试信用卡卡号一览
  16. MT【33】证明琴生不等式
  17. 计算机上怎么计算x的n次方,计算x的n次方(用函数)
  18. 上面两点下面一个三角形_把握字的形状,即使写得快,也很好看(三角形2)...
  19. 在职计算机培训班,计算机科学与技术在职研究生招生院校有哪些?
  20. ssm项目---人事管理系统:员工与部门、职位实现一对一

热门文章

  1. Javascript 中的神器——Promise
  2. 1112. Stucked Keyboard (20)
  3. shell中大于、等于、小于
  4. Tempdb数据库详细介绍
  5. 点滴积累【C#】---检验编号在本表中自动生成,与其他表无关
  6. android开发我的新浪微博客户端-登录页面功能篇(4.2)
  7. Ansible04-任务控制
  8. 我对Node.js Core的首次贡献中学到了什么
  9. 矩阵专职_新的篇章开始了-我将以专职技术作家的身份加入RunCloud
  10. react前端开发_是的,React正在接管前端开发。 问题是为什么。