ROS 2 에서 CMakeLists.txt 이해하기

1. CMakeLists.txt란?

CMakeLists.txt는 ROS 2 패키지를 빌드할 때 사용하는 설정 파일입니다.

쉽게 말하면 다음 역할을 합니다.

이 패키지를 어떻게 빌드할지,
어떤 ROS 2 패키지를 사용할지,
어떤 실행 파일을 만들지,
어디에 설치할지 알려주는 파일

ROS 2 Humble에서는 보통 colcon build 명령으로 워크스페이스를 빌드합니다.

cd ~/ros2_study
colcon build

이때 C++ 패키지나 인터페이스 패키지의 CMakeLists.txt를 읽어서 빌드 규칙을 처리합니다.

2. Python 패키지는 보통 CMakeLists.txt를 사용하지 않는다

ROS 2에서 Python 패키지는 보통 ament_python 빌드 타입을 사용합니다.

예를 들어 my_first_package가 Python 패키지라면 일반적으로 다음 파일들을 중심으로 동작합니다.

my_first_package/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
│   └── my_first_package
└── my_first_package/
    └── __init__.py

Python 패키지는 보통 CMakeLists.txt가 핵심 파일이 아닙니다.

Python 패키지에서 중요한 파일은 다음입니다.

                                   파일                                                            역할

setup.pyPython 노드 실행 등록
setup.cfg실행 파일 설치 경로 설정
package.xml패키지 정보와 의존성 선언

따라서 my_first_package 같은 Python 패키지에서는 일반적으로 CMakeLists.txt를 수정하지 않습니다.

Python 노드를 실행 가능하게 만드는 설정은 보통 setup.py에서 합니다.

예를 들어 Python 노드를 등록할 때는 다음처럼 작성합니다.

entry_points={
    'console_scripts': [
        'my_python_node = my_first_package.my_python_node:main',
    ],
},

그러면 다음 명령으로 실행할 수 있습니다.

ros2 run my_first_package my_python_node

정리하면 다음과 같습니다.

Python 패키지 = setup.py 중심
C++ 패키지 = CMakeLists.txt 중심
인터페이스 패키지 = CMakeLists.txt 중심

3. C++ 패키지 my_cpp_package의 CMakeLists.txt

my_cpp_package는 C++ 패키지입니다.

C++ 패키지는 보통 ament_cmake를 사용합니다.

그래서 CMakeLists.txt가 매우 중요합니다.

기본 구조는 다음과 같습니다.

cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(my_cpp_node src/my_cpp_node.cpp)

ament_target_dependencies(my_cpp_node
  rclcpp
)

install(TARGETS
  my_cpp_node
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()

4. C++ 패키지 : CMakeLists.txt 분석

1) cmake_minimum_required

cmake_minimum_required(VERSION 3.8)

이 패키지를 빌드하기 위해 필요한 최소 CMake 버전을 지정합니다.

ROS 2 Humble에서는 기본 템플릿에서 보통 3.8을 사용합니다.

2) project

project(my_cpp_package)

현재 CMake 프로젝트 이름을 지정합니다.

이 이름은 보통 package.xml의 패키지 이름과 같아야 합니다.

package.xml에 다음처럼 되어 있다면,

<name>my_cpp_package</name>

CMakeLists.txt도 다음처럼 맞추는 것이 좋습니다.

project(my_cpp_package)

패키지 이름이 서로 다르면 빌드나 설치 경로에서 헷갈릴 수 있습니다.

3) 컴파일 경고 옵션

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

C++ 컴파일러가 GCC 또는 Clang일 때 경고 옵션을 추가합니다.

                          옵션                                                                     의미

-Wall기본 경고 활성화
-Wextra추가 경고 활성화
-WpedanticC++ 표준에 엄격한 경고 활성화

실습 단계에서는 경고가 귀찮아 보일 수 있지만, 실제 개발에서는 경고를 무시하면 나중에 버그로 돌아옵니다.

4) find_package

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

find_package()는 사용할 패키지를 찾는 명령입니다.

find_package(ament_cmake REQUIRED)

이 줄은 ROS 2의 CMake 빌드 시스템을 사용하겠다는 의미입니다.

find_package(rclcpp REQUIRED)

이 줄은 C++에서 ROS 2 노드를 만들기 위해 rclcpp를 사용하겠다는 의미입니다.

C++ 코드에서 다음 헤더를 사용한다면,

#include "rclcpp/rclcpp.hpp"

CMakeLists.txt에는 반드시 다음이 있어야 합니다.

find_package(rclcpp REQUIRED)

5) add_executable

add_executable(my_cpp_node src/my_cpp_node.cpp)

이 줄은 C++ 소스 파일을 빌드해서 실행 파일을 만드는 명령입니다.

형식은 다음과 같습니다.

add_executable(실행파일이름 소스파일경로)

예를 들어 다음처럼 작성하면,

add_executable(my_cpp_node src/my_cpp_node.cpp)

src/my_cpp_node.cpp 파일을 빌드해서 my_cpp_node라는 실행 파일을 만듭니다.

이 실행 파일은 나중에 다음 명령으로 실행합니다.

ros2 run my_cpp_package my_cpp_node

단, ros2 run으로 실행하려면 아래의 install() 설정도 필요합니다.

6) ament_target_dependencies

ament_target_dependencies(my_cpp_node
  rclcpp
)

이 줄은 my_cpp_node 실행 파일이 rclcpp 패키지에 의존한다고 알려줍니다.

즉, 다음 관계입니다.

my_cpp_node 실행 파일
└── rclcpp 필요

만약 C++ 노드에서 std_msgs도 사용한다면 다음처럼 추가해야 합니다.

find_package(std_msgs REQUIRED)

ament_target_dependencies(my_cpp_node
  rclcpp
  std_msgs
)

규칙은 단순합니다.

C++ 코드에서 어떤 ROS 2 패키지를 include하면
CMakeLists.txt에도 find_package와 ament_target_dependencies를 추가한다.

7)  install

install(TARGETS
  my_cpp_node
  DESTINATION lib/${PROJECT_NAME}
)

이 줄은 빌드된 실행 파일을 ROS 2가 찾을 수 있는 위치에 설치합니다.

이 설정이 없으면 빌드는 성공해도 다음 명령이 실패할 수 있습니다.

ros2 run my_cpp_package my_cpp_node

C++ 실행 파일은 보통 다음 위치에 설치합니다.

DESTINATION lib/${PROJECT_NAME}

실제 설치 위치는 대략 다음과 같습니다.

~/ros2_study/install/my_cpp_package/lib/my_cpp_package/my_cpp_node

8) ament_package

ament_package()

ament_package()는 현재 패키지를 ROS 2 패키지로 등록하는 명령입니다.

보통 CMakeLists.txt의 가장 마지막에 작성합니다.

쉽게 말하면 다음 의미입니다.

이 패키지는 ROS 2 패키지입니다.
빌드 시스템에 등록해 주세요.

5. 인터페이스 패키지 my_first_package_msgs의 CMakeLists.txt

my_first_package_msgs는 인터페이스 패키지입니다.

인터페이스 패키지는 보통 다음 파일을 만듭니다.

my_first_package_msgs/
├── msg/
│   └── MyMsg.msg
├── srv/
│   └── MySrv.srv
├── action/
│   └── MyAction.action
├── CMakeLists.txt
└── package.xml

인터페이스 패키지는 C++ 실행 파일을 만드는 패키지가 아닙니다.

대신 .msg, .srv, .action 파일을 기반으로 Python과 C++에서 사용할 수 있는 코드를 자동 생성합니다.

1) 기본 CMakeLists.txt 예시

cmake_minimum_required(VERSION 3.8)
project(my_first_package_msgs)

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MyMsg.msg"
  "srv/MySrv.srv"
)

ament_export_dependencies(rosidl_default_runtime)

ament_package()

2) rosidl_default_generators

find_package(rosidl_default_generators REQUIRED)

이 줄은 메시지, 서비스, 액션 코드를 생성하기 위한 패키지를 찾습니다.

인터페이스 패키지에서는 거의 필수입니다.

3) rosidl_generate_interfaces

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MyMsg.msg"
  "srv/MySrv.srv"
)

이 부분이 인터페이스 패키지의 핵심입니다.

msg/MyMsg.msg 파일과 srv/MySrv.srv 파일을 읽어서 Python과 C++에서 사용할 수 있는 코드를 자동 생성합니다.

예를 들어 MyMsg.msg가 있다면 C++에서는 다음처럼 사용할 수 있습니다.

#include "my_first_package_msgs/msg/my_msg.hpp"

Python에서는 다음처럼 사용할 수 있습니다.

from my_first_package_msgs.msg import MyMsg

즉, my_first_package_msgs 패키지는 Python 패키지와 C++ 패키지 사이에서 공통 메시지 타입을 제공하는 역할을 합니다.

4) ament_export_dependencies

ament_export_dependencies(rosidl_default_runtime)

이 줄은 다른 패키지에서 my_first_package_msgs의 메시지를 사용할 수 있도록 런타임 의존성을 내보내는 설정입니다.

인터페이스 패키지에서는 보통 다음 줄을 넣습니다.

ament_export_dependencies(rosidl_default_runtime)

6. C++ 패키지에서 인터페이스 패키지 사용하기

my_cpp_packagesms에서 my_first_package_msgs의 메시지를 사용하려면 CMakeLists.txt에 의존성을 추가해야 합니다.

예시는 다음과 같습니다.

cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(my_first_package_msgs REQUIRED)

add_executable(my_cpp_node src/my_cpp_node.cpp)

ament_target_dependencies(my_cpp_node
  rclcpp
  my_first_package_msgs
)

install(TARGETS
  my_cpp_node
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()

핵심은 두 줄입니다.

find_package(my_first_package_msgs REQUIRED)
ament_target_dependencies(my_cpp_node
  rclcpp
  my_first_package_msgs
)

이 설정이 있어야 C++ 코드에서 다음처럼 메시지를 include할 수 있습니다.

#include "my_first_package_msgs/msg/my_msg.hpp"

7. Python 패키지에서 인터페이스 패키지 사용하기

Python 패키지인 my_first_package는 보통 CMakeLists.txt가 아니라 package.xml과 setup.py를 중심으로 설정합니다.

Python 코드에서는 다음처럼 메시지를 import합니다.

from my_first_package_msgs.msg import MyMsg

이때 my_first_package의 CMakeLists.txt를 수정하는 것이 아니라, 보통 package.xml에 의존성을 추가합니다.

<depend>my_first_package_msgs</depend>

Python 패키지에서는 메시지 사용을 위해 CMakeLists.txt를 건드리는 방식이 아닙니다.

정리하면 다음과 같습니다.

                                           상황                                                                          수정할 파일

C++ 패키지에서 메시지 사용CMakeLists.txt, package.xml
Python 패키지에서 메시지 사용주로 package.xml, Python 코드
메시지 자체를 생성인터페이스 패키지의 CMakeLists.txt

8. ROS 2 Humble 기준 빌드 순서

워크스페이스는 ros2_study입니다.

cd ~/ros2_study
colcon build
source install/setup.bash

특정 패키지만 빌드하려면 다음처럼 할 수 있습니다.

colcon build --packages-select my_first_package_msgs
colcon build --packages-select my_cpp_packagesms

하지만 인터페이스 패키지를 사용하는 경우에는 보통 전체 빌드를 권장합니다.

cd ~/ros2_study
colcon build
source install/setup.bash

빌드 순서는 개념적으로 다음과 같습니다.

1. my_first_package_msgs
   └── msg, srv 코드 생성

2. my_cpp_package
   └── 생성된 메시지를 사용해서 C++ 노드 빌드

3. my_first_package
   └── Python 노드에서 생성된 메시지 import

9. 최종 정리

ROS 2 Humble에서 CMakeLists.txt의 역할은 패키지 종류에 따라 다릅니다.

                           패키지                               CMakeLists.txt 중요도                                                설명

my_cpp_package매우 중요C++ 노드 빌드, 의존성 연결, 실행 파일 설치
my_first_package낮음Python 패키지는 보통 setup.py 중심
my_first_package_msgs매우 중요msg, srv, action 코드 자동 생성

핵심만 기억하면 됩니다.

C++ 패키지에서는 CMakeLists.txt가 빌드의 중심이다.
Python 패키지에서는 setup.py가 실행 등록의 중심이다.
인터페이스 패키지에서는 CMakeLists.txt가 메시지 생성의 중심이다.

my_cpp_package에서 중요한 명령은 다음입니다.

find_package()
add_executable()
ament_target_dependencies()
install()
ament_package()

my_first_package_msgs에서 중요한 명령은 다음입니다.

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MyMsg.msg"
)

ament_export_dependencies(rosidl_default_runtime)

실무 기준으로 보면, CMakeLists.txt는 그냥 형식 파일이 아닙니다.

C++ 노드가 빌드되지 않거나, 메시지를 include하지 못하거나, ros2 run에서 실행 파일을 찾지 못하는 문제는 대부분 CMakeLists.txt 설정 누락에서 나옵니다.

Leave a Comment