CMakeLists.txt 是用 CMake 构建系统构建 ROS 程序包的输入文件。任何兼容 CMake 的包都包含一个或多个 CMakeLists.txt 文件,用以描述怎样构建和安装代码。catkin 项目采用标准的 vanilla CMakeLists.txt 文件,并带有一些额外的约束。
你的 CMakeLists.txt 文件必须遵从以下格式,否则你的程序包将无法正确编译。这些配置命令是有顺序的。
每个 catkin CMakeLists.txt 文件必须以所需 CMake 最低版本开头。 Catkin 需要 2.8.3 或更高版本。
cmake_minimum_required(VERSION 2.8.3)
接下来一项是程序包名,由 CMake 的 project 函数指定。比如说构建一个 robot_brain 的程序包:
project(robot_brain)
注意,在 CMake 中,如果有需要,你可以使用变量 ${PROJECT_NAME} 在脚本的任意地方引用项目名。
然后我们需要使用 find_package 函数指出在构建我们的项目前需要查找哪些 CMake 程序包。我们总是至少有一个依赖项,那就是 catkin:
find_package(catkin REQUIRED)
如果你的项目还依赖于其他的包,他们会自动变成 catkin 的组件(就 CMake 而言)。将那些包指定为组件,而不是对其使用 find_package,会使过程变得更简单。例如,如果你使用了 nodelet 程序包:
find_package(catkin REQUIRED COMPONENTS nodelet)
注意:你只需要找出在构建目标时依赖的组件,而不需要找出运行时依赖。
你也可以使用下面的方式:
find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)
但是,你会发现这样颇有不便。
如果通过 find_package 查找到某个程序包,其结果就是创建一些 CMake 环境变量,这些变量给出了有关找到的程序包的信息。接下来可以在 CMake 脚本中使用这些环境变量。这些环境变量描述了程序包的外部头文件的位置,程序包所依赖于哪些库,以及库所在的路径。变量的命名规则通常为
Catkin 程序包并不真的是 catkin 的组件,相反,使用 CMake 组件功能这种设计简化了你的输入过程。
对于 catkin 程序包,将他们作为 catkin 的组件是有好处的,因为可以使用 catkin_ 前缀统一创建一组环境变量。假如你在代码中用到了程序包 nodelet,查找依赖包的建议方法如下:
find_package(catkin REQUIRED COMPONENTS nodelet)
这意味着 nodelet 导出的包含路径、库路径等也会附加到 catkin_ 变量。例如,catkin_INCLUDE_DIRS 变量不仅包含了 catkin 的相关路径,也包含了 nodelet 的相关路径。这将在之后派上用场。
我们也可以单独对 nodelet 应用 find_package:
find_package(nodelet)
如果使用 C++ 和 Boost,则需要在 Boost 上调用 find_package(),并指出你正在使用 Boost 哪些方面的组件。例如你想使用 Boost threads,你需要这样写:
find_package(Boost REQUIRED COMPONENTS thread)
catkin_package() 是一个由 catkin 提供的宏。这是用来指定与编译系统有关的 catkin 专用信息的,这些信息被用来生成 pkg-config 和 CMake 文件。
此函数必须在用 add_library() 或 add_executable() 生成任何目标前调用。该函数具有 5 个可选参数:
可到此处查看关于全部宏的文档。
举个例子:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
这表示头文件将导出到程序包文件夹中的“include”文件夹。其中 CMake 环境变量 ${PROJECT_NAME} 的值为你先前传递给 project() 函数的值。“roscpp” + “nodelet” 是构建/运行此程序包时需要存在的包,“eigen” + “opencv” 是构建/运行此程序包时需存在的系统依赖项。
构建目标有多种形式,但是通常是以下两种可能中的一个:
要强调的是,catkin 中的构建目标的名称必须是唯一的,无论它们是构建/安装到哪个文件夹。这是 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_module_library
PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})
在指定目标前,你需要为目标指出可找到的资源的位置,特别是头文件和库:
include_directories 的参数应该是由调用 find_package 产生的 *_INCLUDE_DIRS 变量,以及其他需要包含的目录。如果你使用了 catkin 和 Boost,你的 include_directories() 调用看起来如下所示:
include_directories(include ${Boost_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS})
第一个参数“include”表示程序包的 include/ 目录也是包含路径的一部分。
CMake link_directories() 函数可以被用来添加额外的库路径,然而并不建议这样做。所有的 catkin 和 CMake 程序包的连接信息已经在使用 find_package 时添加。只需要在 target_link_libraries() 中连接这些库就行了。
例如:
link_directories(~/my_libs)
要指定必须构建的可执行目标,我们必须使用 add_executable() CMake 函数。
add_executable(myProgram src/main.cpp src/some_file.cpp src/another_file.cpp)
这将构建一个名为 myProgram 的目标可执行文件,它由 3 个源文件构建而来:src/main.cpp,src/some_file.cpp 和 src/another_file.cpp。
add_library() 函数用于指定要构建的库目标。默认情况下 catkin 生成共享库。
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_SRCS})
使用 target_link_libraries() 函数指定可执行目标要链接的库。 这通常在 add_executable() 调用之后使用。 如果找不到ros,请添加$ {catkin_LIBRARIES}。
语法:
target_link_libraries(, , , ... )
示例:
add_executable(foo src/foo.cpp)
add_library(moo src/moo.cpp)
target_link_libraries(foo moo) -- This links foo against libmoo.so
注意,在大多数用例中是不需要使用 link_directories(),因为相关信息已经通过 find_package() 自动添加。
在被 ROS 程序包构建和使用之前,ROS 中的 Messages (.msg),services (.srv) 和 actions (.action) 文件需要特殊的预处理构建步骤。这些宏的关键作用是生成由特定编程语言编写的文件,以便可以在所选编程语言中使用这些 Messages,services 和 actions。构建系统将使用所有可用的生成器(例如 gencpp,genpy,genlisp 等)生成绑定。
有三个宏来分别处理 messages,services 和 actions:
在调用上述宏之后必须调用下边的宏:
generate_messages()
find_package(catkin REQUIRED COMPONENTS ...)
add_message_files(...)
add_service_files(...)
add_action_files(...)
generate_messages(...)
catkin_package(...)
...
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
find_package(catkin REQUIRED COMPONENTS message_generation)
package.xml 文件必须包含对 message_generation 的 build 依赖以及对 message_runtime 的 runtime 依赖。 如果依赖关系是从其他包传递的,那么这不是必需的。
如果你有一个目标依赖于(甚至传递地依赖于)需要构建 messages/services/actions 文件的其他目标,则需要对目标 catkin_EXPORTED_TARGETS 添加显式依赖,以便它们以正确的顺序构建。 除非你的软件包确实不使用 ROS 的任何部分,否则这种情况几乎总是适用。不幸的是,这种依赖关系不能自动传播。 示例如下(some_target 是 add_executable() 设置的目标的名称):
add_dependencies(some_target ${catkin_EXPORTED_TARGETS})
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS})
add_dependencies(some_target ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
如果你的程序包在 “msg” 目录中有两条 messages,分别名为 “MyMessage1.msg” 和 “MyMessage2.msg”,并且这些消息依赖于 std_msgs 和 sensor_msgs,在 “srv” 的目录中的有一个 service 名为 “MyService.srv”, 定义使用这些 messages 和 service 的可执行文件 message_program,以及可执行文件 do_not_use_local_messages_program,它使用 ROS 的某些部分,但不使用此程序包中定义的messages/service,那么你需要在 CMakeLists.txt 中使用以下内容:
# Get the information about this package's buildtime dependencies
find_package(catkin REQUIRED
COMPONENTS message_generation std_msgs sensor_msgs)
add_message_files(FILES
MyMessage1.msg
MyMessage2.msg
)
add_service_files(FILES
MyService.srv
)
generate_messages(DEPENDENCIES std_msgs sensor_msgs)
catkin_package(
CATKIN_DEPENDS message_runtime std_msgs sensor_msgs
)
add_executable(message_program src/main.cpp)
add_dependencies(message_program KaTeX parse error: Expected '}', got 'EOF' at end of input: {{PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_executable(does_not_use_local_messages_program src/main.cpp)
add_dependencies(does_not_use_local_messages_program ${catkin_EXPORTED_TARGETS})
另外,如果要构建 actionlib 动作,并在 “action” 目录中有一个名为 “MyAction.action” 的 action 规范文件,则必须将 actionlib_msgs 添加到使用 catkin find_packaged 的组件列表中,并在调用 generate_messages() 前添加以下调用:
add_action_files(FILES
MyAction.action
)
此外,package.xml 中必须具有对 actionlib_msgs 的 build 依赖。
如果你的 ROS 程序包提供一些 Python 模块,你必须创建 setup.py 文件,并调用
catkin_python_setup()
有一个 catkin 专用的宏用于处理基于 gtest 的单元测试,称为 catkin_add_gtest()。
if(CATKIN_ENABLE_TESTING)
catkin_add_gtest(myUnitTest test/utest.cpp)
endif()
构建之后,目标文件被放在 catkin 工作空间的 devel 空间中。然而,我们通常希望将目标文件安装到系统中以使其他人可以使用,或者安装到一个本地文件夹中以测试系统级的安装。换句话说,如果你想对你的代码进行 “make install” 操作,你需要指定目标文件应当被安装到哪个位置。
这通过使用 CMake install() 函数完成,带有以下参数:
示例如下:
install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
除了这些标准目标目录,一些文件需要被安装到特殊的文件夹。例如包含 Python 绑定的库必须安装到一个不同的文件夹才能在 Python 中导入:
install(TARGETS python_module_library
ARCHIVE DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
)
对于 Python 代码,由于没有使用 add_library() 和 add_executable() 函数,CMake 无法确定哪些是目标文件以及目标文件类型,所以安装规则有所不同。这种情况下,在 CMakeLists.txt 文件中使用以下内容:
catkin_install_python(PROGRAMS scripts/myscript
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
有关 python 脚本安装的详细信息,以及更好地实践文件系统分层,请参考 catkin 手册。
如果你只是安装 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_GLOBAL_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
其他资源,例如启动文件可以被安装到 ${CATKIN_PACKAGE_SHARE_DESTINATION}:
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
PATTERN ".svn" EXCLUDE)