ROS2 learning (3) colcon compiles a package and provides it to other packages for use

    Simple ROS2 example learning may not encounter this problem. But we still inevitably think about why ROS2 officially provides packages such as rclcpp. When we use these packages, we can directly add dependencies and use find_package(rclcpp REQUIRED) to find them. Whether the packages we compile ourselves can also be used by other packages.
    If a slightly larger project will be developed by many people, then it is possible that the package developed by yourself is a common package. I hope to provide you for other people to use. How can I do that?
    This article uses an example to illustrate how to solve this dependency problem. The example is to make two packages in a ROS2 project, a common package and a sample package, and the sample package depends on the common library.

To achieve this effect, several issues must be considered.

  1. How to compile the common package through
  2. ROS2 parallel compilation, how to ensure that the common package is compiled before the sample package
  3. How to know the header file path and dynamic library path that should include the common package when the sample package is used

     The first question above is the most basic one. If we want to provide it to others, we must compile it ourselves.

     The second question, if you have a slightly larger project with many packages, you will see that several packages will be compiled at the same time when using colcon build, which is for faster compilation. But there is no way to guarantee that your own package will be compiled before others use it. Of course, even if it is not compiled in parallel, using the default method, there is no way to guarantee that all packages can be compiled sequentially.
     To solve this problem, you need to add dependencies in the package.xml of the package. For example, the sample package depends on the common package, then add it to the package.xml of the sample package

<depend>common</depend>

    Tell the compiler that package B depends on package A when compiling package B, and package A needs to be compiled first. In fact, you can also see the dependency on rclcpp from package.xml

  <depend>rclcpp</depend>

It's just that rclcpp has been compiled long ago and installed in the system.

    For the third question, because the implementation in the sample package needs to include the header files in the common package and link the dynamic library, how to let the sample package know the header file path and the dynamic library path of the common package? For those who know about CMake, they will definitely know that it can be solved by directly setting variables in the following way.

set(COMMON_INCLUDE_DIR xxx/xxx/include)
set(COMMON_LIBRARY_PATH xxx/xxx/lib)

    If you have a better understanding of Cmake and the relative directory of the project, you can write it. However, it feels more crude this way. And it is often written as an absolute path, and it cannot be found when the directory changes.

    Cmake provides the find_package() function to find the corresponding package and add the relevant header files and library environment variables into it.
    How does find_package(xxxx) achieve that? The principle is to find the cmake folder of the xxx package in the search directory of CMake, which generally exists, xxxConfig.cmake, or Findxxx.cmake file, as long as one of the files is found, all the environment variables of xxx can be located, and then loaded into cmake . The detailed details are not mentioned here, just know the general principle, because we rarely write such a Findxxx.cmake file by ourselves, most of the libraries we install will generate this file when we execute install.
Using find_package() feels more elegant than directly setting environment variables, and we also do this when we rely on the ROS2 library. For example, when we write ROS2 programs, we often see the following usage

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

    Now what we have to think about is how to package our own package into a package like rclcpp, so that other people can directly use find_package() to find it.
    In fact, the reason is very simple, that is, how to export the path of the header file and the path of the dynamic library. This way other packages can be used.
Two functions of ament of ROS2 are used here.

ament_export_include_directories(include)
ament_export_libraries(your_library)

    One of these two functions is to export the header file, and the other is to export the dynamic library. In this way, when colcon compiles this package, the xxxConfig.cmake file will be generated. Other packages can be found by find_package().

The implementation principle is explained clearly, and the following is an example.
The code structure is as follows,

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

Just built two packages, one common and one sample.
The cpp in the common package only has a simple implementation as follows, and the header file is the definition of this class:

#include "common/common.h"
#include <iostream>
using namespace std;

CommonLib::CommonLib()
{
    
    
        cout << "construct.."<<endl;
}

CommonLib::~CommonLib()
{
    
    
}

void CommonLib::Publish()
{
    
    
        cout << "this is common package func.."<< endl;
}

The CmakeList.txt of the common package is as follows:

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()

This CMakeList.txt mainly made two installs, and installed the header files and dynamic libraries to the install directory.

install(DIRECTORY include/
  DESTINATION include
)
install(
        TARGETS common
        LIBRARY DESTINATION lib
        )

Then export the directory to commonConfig.cmake through ament_export, so that the sample package can be used.

ament_export_include_directories(include)
ament_export_libraries(${PROJECT_NAME})

How to write the sample package

#include "sample/sample.h"
#include <iostream>

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;
}

The sample package mainly contains header files

#include "sample/sample.h"

There is also the use of the class CommonLib defined in common, these are very common ways of writing.
Then look at how to write the CMakeList.txt of the sample

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()

It is directly used here

find_package(common REQUIRED)

To find the common package, you can know whether the package is found through the variable common_FOUND, and then you can add ${common_INCLUDE_DIRS} to the header file path when using it below, and add common to the link dependency ament_target_dependencies, and you can link normally.
Finally, add dependencies in the package.xml of the sample as follows, so that the order of compilation can be guaranteed.

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>sample</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="[email protected]">yangcb</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>common</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Guess you like

Origin blog.csdn.net/yangcunbiao/article/details/131969985