CMakeLists
2026/7/10 12:47:53 网站建设 项目流程

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/endifwhile/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}") # 打印 A math(EXPR T "${A} + ${B}") # 计算 A + B 的值,并存储在变量 T set(A "${B}") # 设置变量 A 的值为 B set(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_RPATHCMAKE_BUILD_WITH_INSTALL_RPATHCMAKE_INSTALL_RPATHCMAKE_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) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fPIC -std=c++17 -g -Wall -fdiagnostics-color=auto") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fPIC -std=c++17 -Wall -fdiagnostics-color=auto") endif() # 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 3.4 REQUIRED) find_package(OpenCV REQUIRED) find_package(glog REQUIRED) find_package(GTSAM REQUIRED) find_package(Ceres REQUIRED) find_package(PCL 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(${GLOG_INCLUDE_DIRS}) include_directories(${PCL_INCLUDE_DIRS}) # 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} ${CERES_LIBRARIES} 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询