2.CMake介绍及CMakeLists.txt文件编写

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编写的详细说明,编译生成的接口文件可以供其他模块使用。

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值