CMakeLists

发布时间:2026/7/10 12:48:18
CMakeLists 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。如判断当前环境是否为 win32if (WIN32) message(Youre running CMake on Windows.) else () message(You arent 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其他比较条件的使用。输出112358131.5. 列表分号分隔的字符串直接以例子说明。以下例子中将三个参数传递给mathset(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()输出Pleasevisiturlleehao.me1.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}) # 输出8cmake 中使用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 -stdc14 -g -Wall -ffast-math -ffast-math -use_fast_math -fdiagnostics-colorauto)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.aC.so会告诉CMake优先使用动态链接库libC.so也可直接使用库文件的相对路径或绝对路径。使用绝对路径的好处在于当依赖的库被更新时make的时候也会重新链接。gcc中库的链接顺序是从右往左进行所以要把最基础实现的库放在最后这样左边的lib就可以调用右边的lib中的代码。同时当一个函数的实现代码在多个lib都存在时最左边的lib代码最后link所以也将最终保存下来。2. RPATH2.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) # 安装的执行文件不加上RPATHCmake在默认情况下make install会把安装的执行文件的RPATH删掉的所以就会出现上面我执行安装好的执行文件报错的问题。2.4. 加上完整的RPATHCmake的默认设置我们肯定是不能使用的我们需要一个安装的时候也要带上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 -stdc17 -g -Wall -fdiagnostics-colorauto) else() set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -O3 -fPIC -stdc17 -Wall -fdiagnostics-colorauto) 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_librariesCMAKE和RPATH_章志强的专栏-CSDN博客_cmake rpath