文章目录
总览
CMakelist.txt是输入CMake编译系统的一种文件格式,用于编译package。任何适用于cmake的package都应该包含一个或者多个CMakelist.txt,通常用来描述如何编译,以及在哪里进行安装。用于编译catkin工程的CMakelist.txt是包含一些额外约束的标准vanilla CMakelist.txt文件。
总体结构和顺序
CMakelist.txt文件必须按照以下的格式书写,否则将不会正确编译。配置的顺序也必须按照格式书写。
- 所需CMake版本(cmake_minimum_required)
- 工程名,或package名(project())
- 寻找编译所需其他CMake/Catkin包(find_pack())
- 启用Python模组支持(catkin_python_setup())
- Message/Service/Action生成器(add_message_files(),add_service_files(),add_action_files())
- 调用Message/Service/Action生成器 (generate_message())
- 指定package信息导出(catkin_package())
- 编译库/可执行文件 (add_library(),add_executable(),target_link_libraries())
- 测试编译 (catkin_add_gtest())
- 安装规则 (install())
CMake版本
每一个catkin CMakelist.txt必须与要求的CMake版本号作为开头,catkin需要2.8.3或者更高版本的CMake。
工程名或package名
通过project()函数确定package或者工程的名字,比如假设我们的包名为 robot_brain。
project(robot_brain)
注意 在CMakelist.txt中,可以用过 ${PROJECT_NAME} 来引用工程名
寻找依赖CMake Package
使用find_packge()函数来指定哪些其他的CMake package需要用于编译本package,该函数至少包含一项对catkin的依赖。
find_package(catkin REQUIRED)
如果你的工程还依赖于其他wet packages(即通过catkin生成的package,dry package是指通过ros build生成的package),它们将会自动转换为catkin的组件(components of catkin),如果你指定其作为组件(component),那将会是整个编译变得容易一些,而不是使用find_package找到那些packages。比如,如果你需要用到 nodelet
find_package(catkin REQUIRED COMPONENTS nodelet)
注意 :这里使用find_package作为catkin component的包应该是编译时依赖,而不是运行时的依赖。
你也可以使用如下方式:
find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)
接下来你就会明白,这么做其实是一件非常繁琐的事情。
find_package()用来做什么?
如果一个package通过CMake的find_package()找到了,其结果将会创建一系列关于该package的信息CMake环境变量,并传递给CMake脚本,在接下来的编译过程将会使用。这一系列环境变量主要描述了package产生的头文件在何处、package的源文件在何处、package依赖于哪些库(libraries),并且这些库在何处。环境变量名通常遵循_格式:
- _FOUND - 如果找到库时为真,否则为假
- _INCLUDE_DIRS 或 _INCLUDE - package输出的include目录
- _LIBRARIES 或 _LIBS - package输出的库
为什么将catkin packge指定为组件(component)?
catkin package并非是指catkin的组件,而是利用了CMake的组件特点,来设计catkin从来简化了你宝贵的打字时间。
对于catkin package而言,如果你将它们作为catkin的组件,那么就会生成一系列以 catkin_ 开头的环境变量,例如,如果你将在你的代码中用到nodelet,那么推荐的编译方式是:
find_package(catkin REQUIRED COMPONENT nodelet)
这将会使得nodelet生成的include路径、库路径以及其他输出都会以 catkin_ 开头,举个例子, catkin_INCLUDE_DIR 不仅包含catkin的include路径,也包含了nodelet的路径,从而简化你的打字输入。
如果我们使用单独find_packge()来找nodelet:
find_package(nodelet)
结果只会生成以 nodelet_ 开头的include路径,库路径,而这些路径在使用find_package()作为catkin组件的时候也会生成。
Boost库
如果使用C++ Boost库,你需要使用find_package()函数找到Boost库,并指定你需要使用的部分作为其组件,举个例子,如果你想要使用Boost库的thread,你应该这样表示:
find_package(Boost REQUIRED COMPONENTS thread)
catkin_package()函数
catkin_package()是catkin提供的CMake宏命令,主要是用于指定特定的catkin信息给编译系统,编译系统将会使用这些信息来生产pkg配置文件和CMake文件。
该函数必须在使用add_library()和add_executable()声明目标文件前使用。该函数一共有五个可选参数:
- INCLUDE_DIRS - packge输出的include路径
- LIBRARIES - 该project输出的库
- CATKIN_DEPENDS - 该project依赖的其他catkin project
- DEPENDS - 该project依赖的其他非catkin的CMake project
- CFG_EXTRAS - 额外的配置选项
完整的宏命令文档在这里
举个例子:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
package目录下的输出文件目录“include”表明了输出头文件的存放位置。 ${PROJECT_NAME}对应于project()函数里的名字,比如这里是“robot_brain”,该package运行或编译时,会用到“roscpp”和“nodelet”catkin package,“eigen”和“opencv”是该package需要在编译或运行时使用到的其他系统依赖包。
指定编译目标
编译生成的目标有许多形式,但通常可能用到的是以下两种:
- 可执行目标 - 即我们能够运行的程序
- 库目标 - 其他程序在编译或者运行时能够使用的库文件
目标名称
非常重要的一点是,编译的目标名必须唯一,不论是将它编译或安装到哪个文件夹。这是CMake的要求之一,但是这里的唯一是指在CMake内部的唯一。通过使用 set_target_properties( ) 函数可以将目标重命名为其他名称。如:
set_target_properties(rviz_image_view
PROPERTIES OUTPUT_NAME image_view
PREFIX "")
这将使得目标名在编译和安装过程中,从“rviz_image_view”改变为“image_view”。
自定义输出目录
虽然默认的可执行目标或者库目标的目录取得是一个比较合理的值,但在一些特定的情况下,也必须重新自定义输出目录,如:必须将python绑定的库放置在不同的文件夹,因为这样python才能从中导入。
set_target_properties(python_modle_library
PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})
Include路径和Library路径
在指定目标之前,你需要指定该目标所需要资源的位置,尤其是头文件和库文件:
- Include路径 - 在该路径下能够找到编译所需要的头文件
- Library路径 - 在该路径下能够找到编译所需要的库文件
使用方法:
- include_directories(,,…,)
- link_directories(,,…,)
include_directories( )
include_directories( )函数的参数是调用find_package( )生成的 *_INCLUDE_DIRS 变量或者是其他需要被包含的文件。比如,如果你正在使用catkin和Boost,那么你的调用方法如下:
include_directories(include
${Boost_INCLUDE_DIRS}
${catkin_INCLUDE_DIRS})
第一个参数“include”表明了package中的“include/directory”也是路径中的一部分
link_directories( )
CMake的link_directories( )函数可以用来添加其他额外需要的库路径,但是并不推荐使用该方法。因为所有的catkin和CMake packages会在调用find_package( )函数后自动地将他们的链接(link)信息添加到CMake中来。只需要简单的链接到target_link_libraries()中的库。如:
link_directories(~/my_libs)
target_link_libraries( )和link_directories( )的详细区别可见这里。
可执行目标文件
指定需要编译生成的可执行文件,必须使用add_executable( )CMake函数:
add_executable(myProgram src/main.cpp src/some_file.cpp src/another_file.cpp)
这条命令将会通过三个源文件,生成一个“myProgram”的可执行文件。
库目标文件
add_library( )函数用来指定需要生成的库文件,默认情况下,catkin会生成公共的库文件。
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_SRCS})
target_link_libraries
使用target_link_libraries( )函数来指定可执行文件链接所需要的库文件,典型的使用方法是在add_executable( )函数后调用该函数,添加${catkin_LIBRARIES}
当ros没有找到。
语法:
target_link_libraries(<executableTargetName>,<lib1>,<lib2>,...,<libN>)
例如:
add_executble(foo src/foo.cpp)
add_library(moo src/moo.cpp)
target_link_libraries(foo moo) #这里将foo链接到libmoo.so
注意大多数情况下不需要使用link_directories( ),因为使用find_package( )函数将会自动地引入链接信息。
Message,Service,Action 目标
Message(.msg),Service(.srv),和actions(.action)文件需要一个特殊的预处理器编译步骤才能够被编译并被其他ROS package使用。这些宏命令的目的是生成能够被特定语言使用的特定语言文件的msg、srv、action。编译系统会使用可用的生成器(gencpp, genpy, genlistp, 等等)来生产绑定。
对应提供的宏命令有:
- add_message_files
- add_service_files
- add_action_files
并且必须在这些宏命令之后调用以下宏命令来生成:
generate_messages( )
重要的先决条件/约束条件
- 生成以上文件的宏命令必须在 catkin_package( ) 宏命令之前调用,才能正确地编译。正确顺序:
find_package(catkin REQUIRED COMPONENTS ...)
add_message_files(...)
add_service_files(...)
add_aciton_files(...)
generate_messages(...)
catkin_package(...)
- 生产以上文件时,catkin_package( )宏命令必须有一个对 message_runtime 的CATKIN_DENPENS,比如:
catkin_package(...
CATKIN_DEPENDS message_runtime ...
...)
- 并且必须在find_package( )函数中找到message_generation包,可以单独寻找,或者作为catkin组件
find_package(catkin REQUIRED COMPONETS message_generation)
- package包下的 package.xml 文件必须有message_generation的build依赖,以及对message_runtime的runtime依赖。如果这些依赖从其他包传递性地引入到了package内,则这些要求就不是必须了。
- 如果你有一个目标(甚至是传递性的目标)依赖于需要编译message/service/action的其他目标,则需要添加对catkin_EXPORTED_TARGETS的显示依赖,以便于他们能够按照正确地顺序编译。这种情况常常出现,除非你的package并不包含ROS的其他部分。不幸的是,这种依赖并不能自动传递。如下,其中some_target是通过函数add_executable( )生成的名字:
add_dependencies(some_target ${catkin_EXPORTED_TARGETS})
- 如果你的package在生成message/service的同时,也会生成可执行目标并且会使用到这些message和service,那么你需要构建一个显式得自动生成message目标,以便能够按照正确顺序编译。如下,其中some_target是通过函数add_executable()生产的名字
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS})
- 如果你的package同时满足上面两种条件,你需要同时增加两个依赖:
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
实际例子
举个CMakelist.txt的例子,假如你现在有个package,其中有个msg文件夹内,包含两个message文件:“MyMessage1.msg”和“MyMessage.msg”并且这两个message依赖于 std_msgs和 sensor_msgs包;同时,package内还有一个srv文件夹,其中包含service文件“MyService.srv”。现生成一个message_program的可执行文件,同时使用以上messages和service,生成一个does_not_use_local_message_program可执行文件,仅用到ros的其他部分,不涉及以上的message和service。那么你的CMakelist.txt应该形如:
# 获取该package在build时需要的依赖信息
find_package(catkin REQUIRED COMPONENTS
message_generation
std_msgs
sensor_msgs)
# 声明需要用到的msg文件
add_message_files(FILES
MyMessage1.msg
MyMessage2.msg )
# 声明需要用到的srv文件
add_service_files(FILES
MyService.srv )
# 编译生成特定语言使用的特定语言文件
generate_messages(DEPENDENCIES
std_msgs
sensor_msgs)
# 声明catkin package在run时间的依赖
catkin_package(CATKIN_DEPENDS
message_runtime
std_msgs
sensor_msgs )
# 利用msg和srv文件生成可执行文件
add_executable(message_program src/main.cpp)
add_dependencies(message_program ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 不适用msg和srv文件生成可执行文件
add_executable(does_not_use_local_messages_program src/main.cpp)
add_dependencies(does_not_use_local_messages_program ${catkin_EXPORTED_TARGETS})
除此之外,如果你还有编译构建actionlib的action,那么你应该通过find_package()函数找到actionlib_msgs后,指定actionlib_msgs将“MyAction.action”声明为其组件,最后再调用generate_message()函数。
启用python模块支持
如果你的ROS package提供一些Python模块的话,你应该创建一个setup.py并且在调用generate_message( )和catkin_package()之前调用如下函数:
catkin_python_setup( )
单元测试
CMake还提供了处理gtest-based单元测试的catkin宏命令叫做:catkin_add_gtest()
if(CATKIN_ENABLE_TESTING
catkin_add_gtest(myUnitTest test/utest.cpp)
endif( )
可选步骤:指定可安装目标
build结束以后,目标文件被放在了catkin工作空间的devel目录下,然而通常我们想要把目标安装到系统中去(安装路径的信息在REP 122中可以查到),从而以便其他人或本地文件夹来测试系统级安装。换句话说,如果你希望能够在你的代码中使用“make install”命令,那么你需要制定你生成目标的去处。
通常是使用CMake install()函数实现,并且该函数可以接受一下参数:
- TARGETS - 制定需要安装的是哪个目标
- ARCHIVE DESTINATION - 静态库和DLL(Windows).lib 存根
- LIBRARY DESTINATION - 非-DLL共有库和模块
- RUNTIME DESTINATION -可执行目标文件和DLL(Windows)形式的公用库
举个共有库的例子:
install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
)
下面是另一个可执行目标的例子:
install(TARGETS ${PROJECT_NAME}_node
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
除了标准的目标地址以外,一些文件也必须安装到特定的目录,比能被Python调用的绑定必须安装在不同的文件夹中:
install(TARGETS python_module_library
ARCHIVE DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
)
安装Python可执行脚本
对于Python代码而言,安装的规则有些不同,因为CMake并没有提供Python脚本的add_library( )和add_executable( )函数来决定哪些文件作为目标文件,以及作为哪种目标文件。相反,你可以使用以下命令:
catkin_install_python(PROGRAMS scripts/myscript
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
安装Python脚本和模块的详细信息以及最佳练习的文件分布可以在catkin manual中找到
如果你只是安装Python脚本文件,而不提供模块的话,那么你既不需要创建前面提到的 setup.py文件,也不需要调用catkin_python_setup( )函数。
安装头文件
头文件必须被安装到“include”文件目录下,通常通过安装一整个目录的文件实现(除了特定的文件名或SVN的子目录以外),通过以下的安装规则实现:
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE)
或者是如果include下的文件不匹配文件名要求:
install(DIRECTORY include/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE)
安装roslaunch或其他资源
比如launchfiles这样的其他资源,可以被安装到${CATKIN_PACKAGE_SHARE_DESTINATION}:
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
PATTERN ".svn" EXCLUDE)
来源:https://blog.csdn.net/qq_35503971/article/details/102754613