2.CMake介绍及CMakeLists.txt文件编写
文章目录
前言
如果我们的程序想要运行在不同的平台上,就需要根据不同平台的make工具规范编写对应的Makefile文件。CMake 则是一个跨平台的安装(编译)工具。CMakeList.txt是一个与平台无关的、用于定制编译流程的文件。CMake 靠的是 CMakeLists.txt 文件来生成Makefile文件。
本文将介绍CMake的使用及CMakeLists.txt文件编写实例。
提示:以下是本篇文章正文内容,下面案例可供参考
一、语法
1、定义变量
CMakeLists.txt中只有字串和字串数组两种变量。定义变量通过 set命令 来定义,使用变量时在外面加上 ${} 符号即可。如:
# 定义变量
set(name "apollo")
# 也可以从命令行给变量赋值,使用**-D后面加变量及赋值**,例如
# cmake -Dname="apollo" -P CMakeLists.txt
# -P参数 指定CMakeLists.txt脚本以直译模式解析,以直译模式解析就不会生成Makefile文件。
# 使用变量
message("My name is ${name}!")
命令不区分大小写,即set也可以替换为SET。
我们经常会在命令行配置工程为debug模式还是release模式,如:
cmake -DCMAKE_BUILD_TYPE=Debug
或
set(CMAKE_BUILD_TYPE "Release")
CMAKE_BUILD_TYPE是cmake中的一个内置变量,用于指定构建类型,可选值为:
None: 编译器默认值
Debug: 产生除错信息
Release: 进行最佳化
RelWithDebInfo: 进行最佳化,但仍然会启用 DEBUG flag
MinSizeRel: 进行程式码最小化
CMake变量查询网站:
https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/Useful-Variables
2、流程控制
(1)if
set(ARCH "x86")
if(ARCH MATCHES "x86")
message("ARCH is x86")
else()
message("ARCH is arm")
endif()
输入:
ws$ cmake -Dname=“apollo” -P CMakeLists.txt
输出:
My name is apollo!
ARCH is x86
(2)while
set(a "1")
while(${a} LESS "5")
message("${a}")
math(EXPR a "${a} + 1")
endwhile()
(3)foreach
message("for 1 =========")
foreach(i RANGE 1 5)
message("${i}")
endforeach()
3、自定义宏与函数
(1)宏
#定义名为printf的宏
macro(printf str)
message(${str})
endmacro()
printf(“hello cmake”)
(2)函数
#定义名为printf的函数
function(printf str)
message(${str})
endfunction()
printf("hello function")
(3)宏与函数的区别
函数中的变量是局部的,宏中的变量是全局的,宏中的变量在外面也可以被访问到。
# 定义名为func_printf的函数
function(func_printf str)
message(${str})
set(func_var "1111111111")
endfunction()
# 定义名为macro_printf的宏
macro(macro_printf str)
message(${str})
set(macro_var "222222222")
endmacro()
# 使用
func_printf("hello function")
message("func_var = ${func_var}")
macro_printf("hello macro")
message("macro_var = ${macro_var}")
# 输出
hello function
func_var =
hello macro
macro_var = 222222222
4、查看cmake命令说明
(1)查看所有cmake命令
cmake --help-command-list
(2)查看具体某个命令说明
比如,查看message命令说明:
cmake --help-command message
二、CMakeLists.txt构建
1、几个常用命令
下面列出几个常用的命令,在我们下面的例子中会用到。
(1)cmake_minimum_required
命令格式:
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] [FATAL_ERROR])
用于指定需要的 CMake 的最低版本。
使用示例:
cmake_minimum_required (VERSION 3.10)
(2)project
命令格式:
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])
用于指定项目的名称。
使用示例:
project (hello)
(3)add_executable
命令格式:
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
用于指定从一组源文件 source1 … 编译出一个可执行文件且命名为 name。
使用示例:
add_executable(hello main.c)
(4)aux_source_directory
命令格式:
aux_source_directory(<dir> <variable>)
用于将 dir 目录下的所有源文件的名字保存在变量 variable 中。
使用示例:
aux_source_directory(. DIR_SRCS)
(5)add_subdirectory
命令格式:
add_subdirectory(source_dir [binary_dir]
[EXCLUDE_FROM_ALL])
用于添加一个需要进行构建的子目录。
使用示例:
add_subdirectory(Lib)
(6)add_library
命令格式:
add_library(<name> INTERFACE [IMPORTED [GLOBAL]])
用于指定从一组源文件中编译出一个库文件且命名为name。
使用示例:
add_library(Lib ${DIR_SRCS})
(7)target_link_libraries
命令格式:
target_link_libraries(<target> ... <item>... ...)
用于指定 target 需要链接 item1 item2 …。
使用示例:
target_link_libraries(hello Lib)
8)include_directories
命令格式:
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
用于添加头文件路径。
使用示例:
include_directories(include)
(9)target_include_directories
命令格式:
target_include_directories(<target>
[BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
用于为特定的目标(如可执行文件或库)指定包含(头文件)目录。这些指定的目录在编译时会被添加到编译器的搜索路径中,使得编译器可以找到目标的源文件包含的头文件。
将其他库与已有的库进行链接,添加链接库,相同于指定-l参数
比如:
TARGET_LINK_LIBRARIES(myProject libeng.so)
TARGET_LINK_LIBRARIES(myProject eng)
TARGET_LINK_LIBRARIES(myProject -leng)
#以上这些库名写法都可以。
使用示例:
# 将lib目录(相对于当前CMakeLists.txt文件的路径)添加到mylib的包含目录中
target_include_directories(mylib PRIVATE lib)
(10)find_package
命令格式:
find_package(<PackageName> [version] # 指定要查找的库或者模块(版本号可选)
[EXACT] # 要求version完全匹配
[QUIET] # 无论找到与否,都不产生任何提示性消息
[REQUIRED] # 要求必须找到 xxx.cmake,找不到就提示报错
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS [components...]]
[MODULE] # 仅使用模块模式
[CONFIG|NO_MODULE] # 仅使用配置模式(两种写法是等效的)
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER]
)
用于在搜索路径和默认路径(环境变量)中查找指定的库或者模块
使用示例:
# 使用 Boost 时加载线程组件
find_package(Boost REQUIRED COMPONENTS thread)
(11)install
命令格式:
install(TARGETS <target>
[EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|FRAMEWORK|BUNDLE|PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
[EXCLUDE_FROM_ALL]])
用于指定在安装项目时要安装的文件或目录。其中,参数指定要安装的目标(即要生成的可执行文件或库文件)。${PROJECT_NAME}是CMake中一个内置的变量,表示当前项目的名称。因此,install(TARGETS ${PROJECT_NAME})命令指定安装当前项目生成的目标文件(可执行文件或库文件)。
install()命令的DESTINATION参数用于指定安装目录,PERMISSIONS参数用于指定安装文件的权限,COMPONENT参数用于指定安装组件,OPTIONAL参数用于指定是否要将安装过程作为错误处理。这些参数都是可选的,根据需要使用。
使用install()命令可以方便地将项目构建结果(可执行文件或库文件)安装到指定的目录中,以便用户或其他项目使用。
2、实例
下面介绍工程中接口文件编译实例
(1)msg、srv文件编译
cmake_minimum_required(VERSION 3.10.1)#所需CMake版本
project(demo)#软件包名称
find_package(catkin REQUIRED COMPONENTS#查找需要的其它包(依赖包)
geometry_msgs
nav_msgs
roscpp
rospy
sensor_msgs
std_msgs
message_generation
)
#roscpp: 用C++ 语言进行ros开发要用到的包
#rospy: 用python 语言进行ros开发要用到的包
#std_msgs: 基本的数据类型int 、string、float、double等的依赖包
#像message service 和action的定义需要在catkin_package()之前
add_message_files(
FILES
BinaryData.msg
}
add_service_files(
FILES
StringMessage.srv
BinarySrv.srv
)
add_action_files(FILES Action1.action )
generate_messages(
DEPENDENCIES
geometry_msgs nav_msgs sensor_msgs std_msgs
)
#用来向编译系统指明catkin-specific的信息,格式如下:
catkin_package(
#INCLUDE_DIRS include # 此项打开之后该软件包的include文件可以被其它包所引用
#LIBRARIES ${PROJECT_NAME} #同理, 导出项目中的库
CATKIN_DEPENDS roscpp rospy message_generation geometry_msgs nav_msgs sensor_msgs std_msgs
#DEPENDS eigen opencv
)
include_directories(
include
${catkin_INCLUDE_DIRS}
)
#其中include是指包含本软件包下的头文件, ${catkin_INCLUDE_DIRS}是指ROS下其它包的头文件,include需要写在${catkin_INCLUDE_DIRS}前面
install(FILES ros_proto.h
DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}/demo
)
#允许开发者详细地定义安装规则,包括但不限于,文件的复制、权限的设置、目标的安装等
参数说明:
TARGETS:表示要安装的目标文件,指定库文件或可执行文件,可以是多个目标名称,也可以是通配符表达式;
EXPORT:表示该目标文件的导出所使用的名称;
RUNTIME:表示可执行文件要安装到的目录,默认值是 bin;
LIBRARY:表示动态库要安装到的目录,默认值是 lib;
ARCHIVE:表示静态库要安装到的目录,默认值是 lib;
INCLUDES:表示头文件要安装到的目录,默认值是 ${CMAKE_INSTALL_INCLUDEDIR};
OBJECTS:表示存储在目标目录下的 ${CMAKE_INSTALL_FULL_OBJECTS} 子目录中的目标文件的目录。默认情况下它是空,但在使用CMAKE_OBJECT_PATH_EXTENSION时它默认包含 '.obj';
DIRECTORY:表示目录,指定要安装的目录名称或路径;
FILES:表示获取要安装的文件列表,指定要安装的文件列表,支持多个文件名称或路径名,以空格分隔;
EXPORT:表示库的导出文件,指定要导出的库的名称;
NAMESPACE:表示需要完整限定的命名系统名,如果省略此参数,则默认为导出名称的首部;
FILE:表示导出文件的完整路径,这将在导出文件中嵌入文件路径以指定相对路径。
(2)proto文件编译
做 ROS 相关开发的,应该都知道 ros msg 有个非常大的槽点:
ros msg 扩展性较差,即如果 msg 的字段发生变化,则程序前后版本不兼容
因此,google 的 protobuf 相对就是一个更好的选择。在拥有更好的扩展性的同时,还能给对数据进行压缩,减少 rosbag 的体积。
cmake_minimum_required(VERSION 2.8.3)
project(demo)
find_package(catkin REQUIRED COMPONENTS
roscpp
)
find_package(Protobuf REQUIRED)
# 设置编译器flags,设置C++标准, O3优化, 生成调试信息,显示所有警告
set(CMAKE_CXX_FLAGS "-std=c++11 -O3 -g -Wall ${CMAKE_CXX_FLAGS}")
catkin_package(
INCLUDE_DIRS include proto ${PROJECT_SOURCE_DIR}/.. #- 导出包的include路径
LIBRARIES ${PROJECT_NAME} ${PROJECT_NAME}_proto protobuf #- 导出项目中的库
)
add_subdirectory(proto) # proto目录下必须要有CMakeLists.txt
include_directories(
${catkin_INCLUDE_DIRS}
include
)
set(proto_dir ${PROJECT_SOURCE_DIR}/proto)
# 获取匹配指定模式的文件列表
file(GLOB proto_files "${proto_dir}/*.proto")
message(STATUS "Proto Source Dir: ${proto_dir}")
message(STATUS "Proto Source Files: ${proto_files}")
catkin_destinations()
# 设置生成目标代码文件的路径
set(proto_gen_py_dir ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})
file(MAKE_DIRECTORY ${proto_gen_py_dir})
# 这步很重要,让目标路径变为 python 的 package
# 否则的话,会出现 import 异常
file(WRITE ${proto_gen_py_dir}/__init__.py)
# Create lists of files to be generated.
set(proto_gen_py_files "")
foreach(proto_file ${proto_files})
get_filename_component(proto_name ${proto_file} NAME_WE)
list(APPEND proto_gen_py_files ${proto_gen_py_dir}/${proto_name}_pb2.py)
endforeach(proto_file ${proto_files})
message(STATUS "Generated proto files: ${proto_gen_py_files}")
# Run protoc and generate language-specific headers.
add_custom_command(
OUTPUT ${proto_gen_py_files}
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --proto_path=${proto_dir} --python_out=${proto_gen_py_dir} ${proto_files}
DEPENDS ${PROTOBUF_PROTOC_EXECUTABLE} ${proto_files}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
set_source_files_properties(${proto_gen_py_files} PROPERTIES GENERATED TRUE)
target_link_libraries(${PROJECT_NAME}
${catkin_LIBRARIES}
${PROJECT_NAME}_proto
glog yaml-cpp
)
# 可执行文件安装:
install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
# 将生成的 py 文件拷贝到 install 对应的路径下
install(DIRECTORY ${proto_gen_py_dir}/
DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
FILES_MATCHING PATTERN "*.py"
)
总结
本文是对上一篇接口文件编译1.ros的xx.msg、xx.srv、xx.proto编译的CMakeLists.txt编写的详细说明,编译生成的接口文件可以供其他模块使用。