catkin/CMakelist.txt文件说明

眉间皱痕 提交于 2019-12-02 11:01:18

总览

CMakelist.txt是输入CMake编译系统的一种文件格式,用于编译package。任何适用于cmake的package都应该包含一个或者多个CMakelist.txt,通常用来描述如何编译,以及在哪里进行安装。用于编译catkin工程的CMakelist.txt是包含一些额外约束的标准vanilla CMakelist.txt文件。

总体结构和顺序

CMakelist.txt文件必须按照以下的格式书写,否则将不会正确编译。配置的顺序也必须按照格式书写。

  1. 所需CMake版本(cmake_minimum_required)
  2. 工程名,或package名(project())
  3. 寻找编译所需其他CMake/Catkin包(find_pack())
  4. 启用Python模组支持(catkin_python_setup())
  5. Message/Service/Action生成器(add_message_files(),add_service_files(),add_action_files())
  6. 调用Message/Service/Action生成器 (generate_message())
  7. 指定package信息导出(catkin_package())
  8. 编译库/可执行文件 (add_library(),add_executable(),target_link_libraries())
  9. 测试编译 (catkin_add_gtest())
  10. 安装规则 (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_msgssensor_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)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!