1 概述
CMakeLists.txt文件是构建软件包所必备的文件 ,其描述了如何构建程序以及在哪里安装程序包。任何一个文件包通常都会包含一个或者多个CMakeLists.txt文件。CMakeLists.txt文件遵守了vanilla 标准,用于一个catkin项目,含有一定的约束条件。
2 整体结构和结构
CMakeListx.txt文件必须准守下面的这些守则,否则的话,程序包将不能被正确的构建。在配置文件中,下面的顺序很重要。
-
Required CMake Version (cmake_minimum_required)
-
Package Name (project())
-
Find other CMake/Catkin packages needed for build (find_package())
-
Enable Python module support (catkin_python_setup())
-
Message/Service/Action Generators (add_message_files(), add_service_files(), add_action_files())
-
Invoke message/service/action generation (generate_messages())
-
Specify package build info export (catkin_package())
-
Libraries/Executables to build (add_library()/add_executable()/target_link_libraries())
-
Tests to build (catkin_add_gtest())
扫描二维码关注公众号,回复: 3640327 查看本文章 -
Install rules (install())
3 CMake的版本
每一个CMakeLists.txt文件都是以所需要的CMake的版本开始。Catkin需要的CMake版本至少2.8.3。
cmake_minimum_required(VERSION 2.8.3)
4 软件包的名字
然后的条款是由CMake 项目函数指定包的名称。比如,我们构建了一个叫做robot_brain的安装包。
project(robot_brain)
需要注意的是,在之后的CMake脚本中,你可以使用${PROJECT_NAME}引用项目名称。
5 找到依赖的CMake包
我们使用CMake find_package函数来指定一些必备的用来构建我们项目的CMake软件包。通常存在有至少一个被依赖的软件包:catkin。
find_package(catkin REQUIRED)
如果你的项目也依赖于其他的一些软件包,可以将他们自动的被转化为catkin的组件;并不是直接使用find_package于这些软件包,而是将它们转化为组件,这会令任务更简单。例如,假如你想使用nodelet软件包。
find_package(catkin REQUIRED COMPONENTS nodelet)
注意:find_package 的组件功能仅用于当你想要找到构建项目的软件包,不要添加 项目runtime时的依赖。
你也可以这样做:
find_package(catkin REQUIRED)
find_package(nodelet REQUIRED)
当然,这是一种很不方便的做法。
5.1 find_package()做了什么
如果一个软件包被CMake通过find_package函数找到,那么系统会自动的生成一系列描述软件包的CMake 环境变量。这些环境变量会稍后被CMake脚本使用。与此同时,这些环境变量描述了软件包会把头文件导出到了那里,以及哪些被软件包依赖的库和这些库的路径。环境变量的命名通常是:<PACKAGE NAME>_<PROPERTY>:
<NAME>_FOUND 当库被找到的时候,被设置为true;否则,设置为false;
<NAME>_INCLUDE_DIRS 或者 <NAME>_INCLUDES 包含了软件包导出的路径
<NAME>_LIBRARIES 或者 <NAME>_LIBS 被软件包导出的库
<NAME>_DEFINITIONS ?
5.2 Catkin Packages 为什么要被指定为组件?
Catkin软件包并不是catkin真正的组件。不过设计出来的CMake的组件功能却可以明显的节约你的输入时间。
对于catkin 软件包,当使用find_package找到它们并把它们当做catkin组件,则这种方法的优势很明显,而且其创建了一系列有着catkin_前缀的环境变量。举个例子,当想要在程序中使用nodelet软件包的时候,一种比较建议的方法如下:
find_package(catkin REQUIRED COMPONENTS nodelet)
这意味着包含路径,库,等等被nodelet软件包导出的变量也被添加到了catkin_variables。比如,catkin_INCLUDE_DIRS不仅仅包含了catkin的路径同时也包含了nodelet的路径。这在之后用很有用。
当然我们也可以使用下面的这种方法:
find_package(nodelet)
然而这意味着nodelet的路径,库等等并不会添加到catkin_variables中。
这会产生了nodelet_INCLUDE_DIRS,nodelet_LIBRARIES等等一系列的变量。
同样的变量也可以使用下面方法创建:
find_package(catkin REQUIRED COMPONENTS nodelet)
5.3 Boost
如果使用C++和Boost,你需要援引find_package()于Boost并且需要特别指定Boost的那些方面。例如,当你想要使用Boost threads时,你要如下这样做:
find_package(Boost REQUIRED COMPONENTS thread)
6 catkin_package()
catkin_package()是一个catkin_provided 的CMake macro文件。构建系统必须要指定catkin特定的信息,而系统又用于生成类pkg-config和CMake文件。
在使用add_library()和add_executable()函数声明目标之前,必须的调用这个函数。这个函数有5个可选的参数。
INCLUDE_DIRS - 包含了导出软件包头文件的路径
LIBRARIES - 项目导出的库
CATKIN_DEPENDS - 这个项目依赖的其他catkin项目
DEPENDS - 这个项目依赖的其他non-catkin项目。更详细的讲解,参考this explanation.
CFG_EXTRAS - 其他额外的一些配置选择
比较详细的macros 说明文件可以参考 here.
举个例子:
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
其中inlcude 中是软件包导出的头文件路径。CMake环境变量${PROJECT_NAME}就是之前传递给project()函数的参数,在这个例子中,它就是“robot_brain”。"roscpp" + "nodelet"是需要构建这个软件包所必须的catkin软件包,而"eigen" + "opencv"是构建这个软件包的系统依赖,也就是非non-catkin软件包。
7 指定生成目标
有很多的方式可以生成目标文件。但通常具有代表性的是下面。
1.可执行目标。我们可以运行的程序。
2.库目标。被可执行目标在构建或者运行时间时使用的库。
7.1 目标命名
非常重要的是需要注意,catkin中构建目标的名称必须是唯一的,不管它们是构建/安装到哪个文件夹。这是CMake的要求。但是,目标的名称仅在CMake内部是必需的。可以使用set_target_properties()函数将目标重命名为其他目标:
例:
set_target_properties(rviz_image_view
PROPERTIES OUTPUT_NAME image_view
PREFIX "")
这将在构建和安装输出中将目标rviz_image_view的名称更改为image_view。
7.2 自定义输出目录
虽然可执行文件和库的默认输出目录通常被设置为合理的值,但在某些情况下必须自定义。比如,包含Python绑定的库必须放在不同的文件夹中,以便可以在Python中导入,如下:
例:
set_target_properties(python_module_library
PROPERTIES LIBRARY_OUTPUT_DIRECTORY $ {CATKIN_DEVEL_PREFIX} / $ {CATKIN_PACKAGE_PYTHON_DESTINATION})
7.3 包含路径和库路径
在指定目标之前,需要指定可以构建目标的资源位置,特别是头文件和库:
- 包含路径 - 在哪里可以找到正在构建的代码(在C / C ++中最常见)的头文件
- 库路径 - 可执行目标构建的库位于何处?
-
include_directories(<dir1>,<dir2>,...,<dirN>)
-
link_directories(<dir1>,<dir2>,...,<dirN>)
7.3.1 include_directories()
include_directories的参数应该是find_package调用二生成的* _INCLUDE_DIRS变量以及其他需要包含的任何其他目录。如果您使用catkin和Boost,则include_directories()调用应如下所示:
include_directories(include $ {Boost_INCLUDE_DIRS} $ {catkin_INCLUDE_DIRS})
第一个参数“include”表示包中的include 目录也是路径的一部分(其中包含了头文件的路径)。
7.3.2 link_directories()
CMake link_directories()函数可用于添加其他库路径,但并不建议这样做。所有catkin和CMake软件包在find_packaged时自动添加其链接信息。只需链接到target_link_libraries()中的库即可。
例:
link_directories(〜/ my_libs)
请参阅本cmake的教程可以看到使用的详细例子使用target_link_libraries()以及link_directories() 。
7.4 可执行目标
要指定必须构建的可执行目标,我们使用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。
7.5 库目标
add_library()这个 CMake函数用来指定库来构建。默认情况下,catkin会构建共享库。
例:
add_library($ {PROJECT_NAME} $ {$ {PROJECT_NAME} _SRCS})
7.6 target_link_libraries
使用target_link_libraries() 函数可以指定可执行目标链接的库。
这通常在add_executable()调用之后完成。如果ROS 找不到的话,则应该添加$ {catkin_LIBRARIES}。
句法:
target_link_libraries(<executableTargetName>,<lib1>,<lib2>,... <libN>)
例:
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()自动引入的。
8 消息,服务和操作目标
ROS中的messages(.msg),services(.srv)和action(.action)文件在构建和使用ROS包之前,需要进行特殊的预处理器构建步骤。这些宏是生成指定编程语言的文件的关键,以便可以使用所选编程语言中的消息,服务和操作。构建系统可以使用所有可用的生成(例如gencpp,genpy,genlisp等)。
提供了三个宏来分别处理消息,服务和操作:
-
add_message_files
-
add_service_files
-
add_action_files
必须在调用生成的宏之后调用这些宏:
generate_messages()
8.1 重要的先决条件和限制
-
这些macros必须在catkin_package()函数使用之前调用,以便生成正常工作。
find_package(catkin REQUIRED COMPONENTS ...)
add_message_files(...)
add_service_files(...)
add_action_files(...)
generate_messages(...)
catkin_package(...)
...
-
catkin_package()函数必须包含这种CATKIN_DEPENDS对message_runtime依赖关系。
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
-
find_package()必须包含有包messagese_generation,可以单独使用,也可以作为catkin的一个组件使用:
find_package(catkin REQUIRED COMPONENTS message_generation)
-
package.xml文件构建时的依赖必须包含:message_generation;运行时的依赖必须包含:message_runtime。如果依赖性是从其他包传递的,那么这不是必需的。
-
如果您有一个目标(甚至传递)依赖一些其他需要构建消息/服务/操作的目标,则需要在目标catkin_EXPORTED_TARGETS上添加显式依赖项,以便它们以正确的顺序构建。除非您的软件包确实不使用ROS的任何部分,否则这种情况几乎总是适用。不幸的是,这种依赖关系不能自动传播。( some_target 是被add_executable()构建的目标名称):
add_dependencies(some_target $ {catkin_EXPORTED_TARGETS})
-
如果您有一个构建消息和/或服务的包以及使用这个包的可执行文件,则需要在自动生成的消息目标上创建显式依赖项,以便以正确的顺序构建它们。( some_target 是被add_executable()构建的目标名称):
add_dependencies(some_target $ {$ {PROJECT_NAME} _EXPORTED_TARGETS})
-
如果您的包同时满足上述两个条件,则需要添加两个依赖项,即:
add_dependencies(some_target $ {$ {PROJECT_NAME} _EXPORTED_TARGETS} $ {catkin_EXPORTED_TARGETS})
8.2 例子
软件包在名为“msg”的目录下分别有两个消息文件:“ MyMessage1.msg ”和“ MyMessage2.msg ” ,这些消息依赖于std_msgs和sensor_msgs。软件包在名为“srv”的目录中有一个服务文件:“ MyService.srv ”。使用这些消息和服务的可执行文件为message_program。只使用ROS部分的功能和不使用这个软件包中定义的消息/服务一个可执行文件do_not_use_local_messages_program,那么您需要在CMakeLists.txt中使用以下内容:
# Get the information about this package's buildtime dependencies
find_package(catkin REQUIRED
COMPONENTS message_generation std_msgs sensor_msgs)
# Declare the message files to be built
add_message_files(FILES
MyMessage1.msg
MyMessage2.msg
)
# Declare the service files to be built
add_service_files(FILES
MyService.srv
)
# Actually generate the language-specific message and service files
generate_messages(DEPENDENCIES std_msgs sensor_msgs)
# Declare that this catkin package's runtime dependencies
catkin_package(
CATKIN_DEPENDS message_runtime std_msgs sensor_msgs
)
# define executable using MyMessage1 etc.
add_executable(message_program src/main.cpp)
add_dependencies(message_program ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# define executable not using any messages/services provided by this package
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 ” 的操作规范文件,则必须将actionlib_msgs添加到使用catkin find_packaged的组件列表中,并在之前添加以下调用对generate_messages(...)的调用:
add_action_files(FILES
MyAction.action
)
包必须具有对actionlib_msgs的构建(build)依赖性。
9 启用Python模块支持
如果你的ROS包提供了一些Python模块,你应该创建一个setup.py文件并调用
catkin_python_setup()
使用上述函数需要在调用generate_messages()和catkin_package()之前。
10 单元测试
有一个特定于catkin的宏用于处理名为catkin_add_gtest()的基于gtest的单元测试。
if(CATKIN_ENABLE_TESTING)
catkin_add_gtest(myUnitTest test / utest.cpp)
endif()
11 可选步骤:指定可安装目标
经过构建之后,需要将目标放置到catkin工作空间的开发空间中。但是,我们通常希望在系统中安装目标(有关安装路径的信息可以在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_PACKAGE_BIN_DESTINATION}
)
除了这些标准目标,有时一些文件必须安装到特殊文件夹。即,必须将包含Python绑定的库安装到可在Python中导入的其他文件夹中:
install(TARGETS python_module_library
ARCHIVE DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
)
11.1 安装Python的可执行脚本
对于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()。
11.2 安装头文件
头文件也必须安装到“include”文件夹中,这通常通过安装整个文件完成(可以选择使用文件名模式排除无需安装的子文件,比如排除SVM后缀的子文件)。可以通过如下的安装规则完成:
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
或者当子文件夹的名称与包的名称相冲突的时候
install(DIRECTORY include/
DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}
PATTERN ".svn" EXCLUDE
)
11.3 安装roslaunch文件和其他的一些资源
其他的一些资源,比如launchfiles可以被安装这个文件下:${CATKIN_PACKAGE_SHARE_DESTINATION}:
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
PATTERN ".svn" EXCLUDE)