문제상황
ROS2로 패키지를 나누다 보면,
- 한 패키지에서 C++ 라이브러리를 빌드하고
- 다른 패키지에서 그 라이브러리를 find_package()로 가져다 써야 하는 경우가 자주 있다.
그런데 막상 해보면 항상 비슷한 부분에서 헷갈린다.
- package.xml에 뭘 써야 하는지
- CMakeLists.txt에서 find_package()는 누구를 해야 하는지
- target_include_directories()와 install()의 역할 차이
- ament_export_targets()는 왜 필요한지
- 받는 쪽에서는 어떤 이름으로 링크해야 하는지
이번 글에서는 이 내용을 최소 skeleton 기준으로만 정리해본다.
- 불필요한 옵션은 빼고 꼭 필요한 구조만 남겼다.
- provider 패키지와 consumer 패키지가 각각 뭘 해야 하는지만 정리한다.
목표
이번 글에서서는 아래 두 개의 패키지를 만든다.
- lib_pkg
- C++ 라이브러리를 빌드해서 외부 패키지에 제공하는 패키지
- app_pkg
- lib_pkg를 가져와서 링크하고 ROS2 node를 실행하는 패키지
전체 구조
ros2_ws/
└── src/
├── lib_pkg/
│ ├── CMakeLists.txt
│ ├── package.xml
│ ├── include/
│ │ └── lib_pkg/
│ │ └── lib_a.hpp
│ └── src/
│ └── lib_a.cpp
└── app_pkg/
├── CMakeLists.txt
├── package.xml
└── src/
└── main.cpp
1. provider 패키지: lib_pkg
lib_pkg는 라이브러리를 제공하는 패키지로, 지금은 ROS2 node를 실행하지 않는다.
즉, ROS2 패키지이긴 하지만 rclcpp가 꼭 필요한 건 아니며, 여기서는 단순히 C++ 라이브러리 하나를 만들고 export한다.
1.1 package.xml
<?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>lib_pkg</name>
<version>0.0.0</version>
<description>Minimal ROS 2 C++ library package</description>
<maintainer email="you@example.com">you</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
1.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(lib_pkg)
find_package(ament_cmake REQUIRED)
# Build library target
add_library(lib_a STATIC
src/lib_a.cpp
)
# Public include paths for this target:
# - BUILD_INTERFACE: used while building in this workspace
# - INSTALL_INTERFACE: used by downstream packages after installation
target_include_directories(lib_a PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Install public headers
install(
DIRECTORY include/
DESTINATION include
)
# Install library target and export it for downstream packages
install(
TARGETS lib_a
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
# Make exported targets available to find_package() consumers
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
ament_package()
add_library(lib_a ...)
- lib_a라는 C++ 라이브러리 타깃을 생성한다.
target_include_directories(...)
라이브러리와 관련된 include 경로를 정의한다.
- BUILD_INTERFACE
- 현재 워크스페이스에서 빌드할 때 사용할 include 경로
- INSTALL_INTERFACE
- 설치 후, 다른 패키지가 이 타깃을 사용할 때 필요한 include 경로
install(
DIRECTORY include/
DESTINATION include
)
이건 패키지 내에 존재하는 헤더 파일을 실제 설치 경로로 복사하는 부분이다.
- DIRECTORY include/
- include 폴더가 아니라 그 안의 내용만 복사됨
- DESTINATION include
- 실제 설치 위치는 보통 <install-prefix>/include가 된다.
install(TARGETS ...)
이건 빌드된 라이브러리 타깃을 설치하는 부분이다.
ament_export_targets(...)
이게 있어야 downstream 패키지에서 find_package(lib_pkg REQUIRED) 후
export된 타깃을 링크해서 사용할 수 있다.
1.3 헤더 / 소스 파일
// include/lib_pkg/lib_a.hpp
#pragma once
#include <string>
namespace lib_pkg
{
std::string make_greeting(const std::string & name);
} // namespace lib_pkg
// src/lib_a.cpp
#include "lib_pkg/lib_a.hpp"
namespace lib_pkg
{
std::string make_greeting(const std::string & name)
{
return "Hello, " + name;
}
} // namespace lib_pkg
2. consumer 패키지: app_pkg
app_pkg는 lib_pkg를 사용하는 패키지다.
- ROS2 node를 실행해야 하므로 rclcpp가 필요하고
- lib_pkg의 라이브러리를 쓰므로 lib_pkg에도 의존한다.
2.1 package.xml
<?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>app_pkg</name>
<version>0.0.0</version>
<description>ROS 2 node package using lib_pkg</description>
<maintainer email="you@example.com">you</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>lib_pkg</depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
- <depend>rclcpp</depend>
- <depend>lib_pkg</depend>
이 패키지는 node 기능과 외부 라이브러리 둘 다 필요하다.
2.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(app_pkg)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(lib_pkg REQUIRED)
add_executable(app_node
src/main.cpp
)
# Link ROS 2 and exported library target from lib_pkg
target_link_libraries(app_node PRIVATE
rclcpp::rclcpp
lib_pkg::lib_a
)
# Install executable so ros2 run can find it
install(TARGETS app_node
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(lib_pkg REQUIRED)
- ament_cmake : 이 패키지 자체 빌드를 위해 필요
- rclcpp : ROS2 node 실행을 위해 필요
- lib_pkg : 앞서 작성한 외부 라이브러리
add_executable(app_node
src/main.cpp
)
실행파일을 생성한다. 여기서 app_node는 최종 node 실행파일 이름이다.
target_link_libraries(app_node PRIVATE
rclcpp::rclcpp
lib_pkg::lib_a
)
실행파일에 라이브러리를 링크한다.
- rclcpp::rclcpp : ROS2 C++ node 기능
- lib_pkg::lib_a : lib_pkg가 export한 라이브러리 타깃
find_package(lib_pkg REQUIRED)는 패키지를 찾는 단계이고,
실제로 라이브러리를 쓰는 건 target_link_libraries()에서 한다.
install(TARGETS app_node
DESTINATION lib/${PROJECT_NAME}
)
실행파일을 설치한다. 이렇게 해야 ros2 run app_pkg app_node로 실행할 수 있다.
2.3 main.cpp
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "lib_pkg/lib_a.hpp"
class AppNode : public rclcpp::Node
{
public:
AppNode()
: Node("app_node")
{
RCLCPP_INFO(this->get_logger(), "%s", lib_pkg::make_greeting("ROS2").c_str());
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<AppNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
이 예제에서는:
- lib_pkg/lib_a.hpp를 include하고
- node 생성 시 로그 한 줄을 출력하여 lib_pkg 패키지의 함수가 app_pkg의 노드 안에서 정상적으로 호출되는지를 확인한다.
3. 실행 결과
다음 명령어를 통해 app_pkg에서 빌드한 ROS2 노드를 실행한다.
ros2 run app_pkg app_node
실행 결과 다음과 같이 lib_pkg 에서 정의한 함수가 정상적으로 동작한다.

'ROS' 카테고리의 다른 글
| curl installation error (0) | 2023.12.18 |
|---|---|
| catkin build 실행 시 'ERROR: No matching distribution found for python3-empy' 에러 발생하는 경우 (0) | 2023.12.08 |
| [ROS] 파이썬 노드 및 패키지 생성 방법 (0) | 2023.10.18 |
| [ROS] rosbag 토픽 이름 변경 방법 (0) | 2023.10.18 |
| [ROS] Launch 파일에서 rosbag play 방법 (0) | 2023.07.30 |