简单的ROS2的例子学习可能不会遇到这个问题。但是我们仍然难免会思考为什么ROS2官方提供的包比如rclcpp,这些包我们使用的时候可以直接添加依赖,使用find_package(rclcpp REQUIRED),就可以找到。我们自己编译的包是否也可以提供给其他包使用。
如果稍微大一些的工程,会有很多人开发,那么就有可能自己开发的包是一个共通的包,希望提供您给其他人使用,如何才能做的那?
本文通过一个例子来说明如何解决这种依赖问题。例子是在一个ROS2的工程中做了两个包一个common包,一个sample包,sample包依赖于common的库。
要实现这样的效果,就要考虑几个问题。
上面的第一个问题,也就是最基本的,我们要提供给别人使用,肯定是要自己编译通过才可以。
第二个问题,如果在稍微大一些得工程,有很多个包的时候,使用colcon build 就会看到,几个包会同时编译,这样是为了编译的更快。但是这就没有办法保证自己的包会在其他人使用之前编译出来。当然即使不是并行编译,使用默认的方法,也没有办法保证所有的包能够按顺序的编译。
为解决这个问题,就需要在包的package.xml中添加依赖关系。比如sample包依赖common包,那么就在sample包的package.xml中添加
<depend>commondepend>
告诉编译器,编译B包的时候依赖A包,A包需要先编译出来。其实从package.xml中也可以看到依赖rclcpp
<depend>rclcppdepend>
只不过rclcpp早就编译好,安装到系统里了。
对于第三个问题,因为sample包里的实现是需要包含common包中的头文件和链接动态库,如何让sample包知道common包的头文件路径和动态库路径?对于CMake有所了解的人,肯定会知道,要么通过下面这种方式直接设置变量解决。
set(COMMON_INCLUDE_DIR xxx/xxx/include)
set(COMMON_LIBRARY_PATH xxx/xxx/lib)
如果对Cmake比较了解,并且对工程的相对目录比较了解的话,是可以写出来的。不过感觉这种方式比较粗暴。而且往往会写成绝对路径,当目录一变化就找不到了。
Cmake提供了find_package()函数可以找到对应的包,并把相关的头文件和库的环境变量加进去。
find_package(xxxx)如何实现的那?原理是在CMake的查找目录下找到xxx包的的cmake文件夹,一般都会存在,xxxConfig.cmake,或者Findxxx.cmake文件,只要找到其中一个文件就能够定位xxx所有的环境变量,然后加载到cmake中。详细的细节不在这里说了,知道大概原理即可,因为我们也很少会自己去写一个这样的Findxxx.cmake文件,大部分我们安装的库,执行install的时候都会生成这个文件。
使用find_package()的方式感觉上比直接设置环境变量更优雅一些,而且我们依赖ROS2库的时候,也是这样做的,比如我们写ROS2的程序的时候会经常看到下面这样使用
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
现在我们要思考的就是如何把自己的包打包成一个像rclcpp一样的包,能够让其他人使用的时候直接使用find_package()就可以找到。
其实道理也很简单,就是怎么导出头文件路径和动态库路径。这样其他包就能够使用了。
这里用到了ROS2的ament两个函数。
ament_export_include_directories(include)
ament_export_libraries(your_library)
这两个函数一个是导出头文件的,一个是导出动态库的。这样colcon编译这个包的时候,就会生成xxxConfig.cmake文件。其他包就可以通过find_package()找到了。
实现原理讲清楚了,下面就是例子。
代码结构如下,
crabe@crabe-virtual-machine:~/Desktop/sample$ tree sample2/
sample2/
└── src
├── common
│ ├── CMakeLists.txt
│ ├── include
│ │ └── common
│ │ └── common.h
│ ├── package.xml
│ └── src
│ └── common.cpp
└── sample
├── CMakeLists.txt
├── include
│ └── sample
│ └── sample.h
├── package.xml
└── src
└── sample.cpp
就是建了两个包,一个common,一个sample。
common包里的cpp只做了一个简单的实现如下,头文件是这个类的定义:
#include "common/common.h"
#include
using namespace std;
CommonLib::CommonLib()
{
cout << "construct.."<<endl;
}
CommonLib::~CommonLib()
{
}
void CommonLib::Publish()
{
cout << "this is common package func.."<< endl;
}
common包的CmakeList.txt如下:
cmake_minimum_required(VERSION 3.5)
project(common)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
#add_executable(common src/common.cpp)
add_library(common SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/common.cpp)
target_include_directories(common PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(
common
"rclcpp"
)
ament_export_include_directories(include)
install(DIRECTORY include/
DESTINATION include
)
install(
TARGETS common
LIBRARY DESTINATION lib
)
ament_export_libraries(${PROJECT_NAME})
#ament_export_targets(common)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
这个CMakeList.txt主要做了两个install,将头文件和动态库安装到了install目录。
install(DIRECTORY include/
DESTINATION include
)
install(
TARGETS common
LIBRARY DESTINATION lib
)
然后通过ament_export将目录导出到commonConfig.cmake中,这样,sample包就可以使用了。
ament_export_include_directories(include)
ament_export_libraries(${PROJECT_NAME})
sample包的写法
#include "sample/sample.h"
#include
using namespace std;
Sample::Sample()
{
lib_ = make_shared<CommonLib>();
}
Sample::~Sample()
{
}
void Sample::Publish()
{
lib_->Publish();
}
int main(int argc, char ** argv)
{
Sample sample;
sample.Publish();
return 0;
}
sample包,主要包含了头文件
#include "sample/sample.h"
还有使用了common中定义的类CommonLib,这些都是很常见的写法。
然后看sample的CMakeList.txt如何写
cmake_minimum_required(VERSION 3.5)
project(sample)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -std=c++11)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(common REQUIRED)
if(common_FOUND)
message("find the lib common")
else()
message("not find lib common")
endif()
add_executable(sample src/sample.cpp)
target_include_directories(sample PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
${common_INCLUDE_DIRS})
ament_target_dependencies(
sample
"rclcpp"
common
)
install(TARGETS sample
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
这里面就直接使用了
find_package(common REQUIRED)
来找common包,通过变量common_FOUND就可以知道是否找到包,然后下面使用的时候就可以添加 ${common_INCLUDE_DIRS}来扎到头文件路径,在链接依赖ament_target_dependencies中增加common,就可以正常链接了。
最后再sample的package.xml中如下增加依赖,这样就能保证编译的先后顺序了。
<package format="3">
<name>samplename>
<version>0.0.0version>
<description>TODO: Package descriptiondescription>
<maintainer email="[email protected]">yangcbmaintainer>
<license>TODO: License declarationlicense>
<buildtool_depend>ament_cmakebuildtool_depend>
<depend>rclcppdepend>
<depend>commondepend>
<test_depend>ament_lint_autotest_depend>
<test_depend>ament_lint_commontest_depend>
<export>
<build_type>ament_cmakebuild_type>
export>
package>