[Physical AI W1D2 · 6/6]
로봇 소프트웨어는 패키지 단위로 관리된다. Catkin과 Colcon의 차이를 이해하고, Python(ament_python)과 C++(ament_cmake) 패키지를 각각 생성·빌드·실행하며, setup.py와 CMakeLists.txt, colcon 옵션, 실무 패키지 설계까지 — ROS 2 개발의 기본기를 마무리한다.
이 글에서 마무리하는 것
- ROS 패키지·워크스페이스란, Catkin vs Colcon
- Python 패키지(
ament_python) 생성·빌드·실행- C++ 패키지(
ament_cmake) 생성·빌드·실행setup.pyvsCMakeLists.txt, colcon 빌드 옵션, 실무 패키지 설계
(Day2 ROS 2 블록의 마지막 편. 4·5편이 "코드 작성"이었다면, 이번엔 그 코드를 담는 "그릇=패키지" 자체를 다룹니다.)
1. ROS 패키지와 워크스페이스
ROS 패키지는 ROS 프로그램을 기능 단위로 묶은 폴더입니다. 이동 로봇이라면 camera_driver, lidar_driver, robot_navigation, robot_control, object_detection처럼 기능별로 나눕니다. 모든 코드를 한 폴더에 넣으면 관리·디버깅이 어렵지만, 나누면 필요한 부분만 고치고 재사용할 수 있습니다.
패키지가 보통 담는 것:
| 파일/폴더 | 설명 |
|---|---|
package.xml |
패키지 이름·버전·의존성·유지보수자 정보 |
setup.py |
(Python) 실행 파일 등록·설치 설정 |
CMakeLists.txt |
(C++) 빌드 설정 |
패키지명/ |
Python 코드 폴더 |
src/ |
C++ 소스 폴더 |
launch/·config/ |
노드 일괄 실행·파라미터 설정 |
msg/·srv/·action/ |
사용자 정의 메시지·서비스·액션 |
워크스페이스는 패키지들을 모아 빌드하는 작업 공간입니다.
ros2_ws/
├── src/ ← 사용자가 만들거나 받은 패키지 (직접 만드는 유일한 폴더)
├── build/ ← 빌드 중간 파일 (colcon build가 자동 생성)
├── install/ ← 빌드 결과(실행물) 설치
└── log/ ← 빌드·오류 로그
처음엔 src만 만들고, build/install/log는 colcon build가 자동 생성합니다.
2. Catkin vs Colcon
ROS는 버전에 따라 빌드 도구가 다릅니다.
| 구분 | Catkin (ROS 1) | Colcon (ROS 2) |
|---|---|---|
| 대표 명령 | catkin_make, catkin build |
colcon build |
| 워크스페이스 | src, build, devel |
src, build, install, log |
| 빌드 방식 | Catkin 패키지 중심 | 여러 빌드 타입 지원 |
| 결과 반영 | source devel/setup.bash |
source install/setup.bash |
💡 왜 둘 다 알아야 하나 — 현재 ROS 2는 Colcon이 기본이지만, 기존 산업·연구 코드와 오픈소스 예제 상당수가 ROS 1(Catkin) 기반입니다. ROS 1 → ROS 2 이전 작업이 흔해서, 두 구조를 읽을 수 있어야 합니다. (참고: ROS 1은
catkin_create_pkg my_pkg roscpp rospy std_msgs→catkin_make→source devel/setup.bash)
3. 실습 준비 + 워크스페이스 생성
source /opt/ros/humble/setup.bash
echo $ROS_DISTRO # humble (ROS 2 환경이 잡혔는지 확인)
colcon --help # 없으면: sudo apt install -y python3-colcon-common-extensions
# 빌드 실습용 워크스페이스
mkdir -p ~/ros2_build_ws/src
cd ~/ros2_build_ws
ls # src (아직 build/install/log 없음)
4. Python 패키지 (ament_python)
cd ~/ros2_build_ws/src
ros2 pkg create py_robot_pkg --build-type ament_python --dependencies rclpy std_msgs
tree ~/ros2_build_ws/src/py_robot_pkg
생성 구조:
py_robot_pkg
├── package.xml # 패키지 정보·의존성
├── py_robot_pkg/ # 실제 Python 코드 폴더
│ └── __init__.py
├── resource/
├── setup.cfg
├── setup.py # 실행 파일 등록
└── test/
노드를 작성합니다.
cd ~/ros2_build_ws/src/py_robot_pkg/py_robot_pkg
nano status_publisher.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class StatusPublisher(Node):
def __init__(self):
super().__init__('status_publisher')
self.publisher_ = self.create_publisher(String, 'robot_status', 10)
self.count = 0
self.timer = self.create_timer(1.0, self.publish_status)
def publish_status(self):
msg = String()
msg.data = f'Robot package build test message: {self.count}'
self.publisher_.publish(msg)
self.get_logger().info(msg.data)
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = StatusPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
setup.py의 entry_points에 등록합니다.
entry_points={
'console_scripts': [
'status_publisher = py_robot_pkg.status_publisher:main',
],
},
| 항목 | 설명 |
|---|---|
packages=find_packages() |
Python 모듈 자동 탐색 |
data_files |
ROS 2 패키지 인덱스에 등록 |
entry_points |
ros2 run으로 실행할 명령 등록 |
package.xml에는 의존성이 들어 있어야 합니다(<depend>rclpy</depend>, <depend>std_msgs</depend>).
빌드·실행:
cd ~/ros2_build_ws
colcon build
ls # build install log src
source install/setup.bash
ros2 pkg list | grep py_robot_pkg # py_robot_pkg
ros2 run py_robot_pkg status_publisher
# [INFO] [status_publisher]: Robot package build test message: 0 ...
5. C++ 패키지 (ament_cmake)
cd ~/ros2_build_ws/src
ros2 pkg create cpp_robot_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
tree ~/ros2_build_ws/src/cpp_robot_pkg
구조(Python과 다릅니다 — CMakeLists.txt·src/·include/):
cpp_robot_pkg
├── CMakeLists.txt
├── include/cpp_robot_pkg/
├── package.xml
└── src/
C++ 노드를 작성합니다.
cd ~/ros2_build_ws/src/cpp_robot_pkg/src
nano cpp_status_publisher.cpp
#include <chrono>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class CppStatusPublisher : public rclcpp::Node
{
public:
CppStatusPublisher()
: Node("cpp_status_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("cpp_robot_status", 10);
timer_ = this->create_wall_timer(
1s, std::bind(&CppStatusPublisher::publish_status, this));
}
private:
void publish_status()
{
auto message = std_msgs::msg::String();
message.data = "C++ ROS 2 package build test message: " + std::to_string(count_);
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "%s", message.data.c_str());
count_++;
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<CppStatusPublisher>());
rclcpp::shutdown();
return 0;
}
C++은 CMakeLists.txt에 실행 파일을 직접 등록해야 합니다. 그런데 ⚠️ ros2 pkg create가 만들어 준 CMakeLists.txt에는 실행 파일 등록 줄이 없습니다. 생성 직후의 파일은 대략 이렇습니다(핵심만).
cmake_minimum_required(VERSION 3.8)
project(cpp_robot_pkg)
# ...
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED) # --dependencies로 만들면 이미 들어있음
find_package(std_msgs REQUIRED)
if(BUILD_TESTING)
# ... 린트 설정 ...
endif()
ament_package() # ← 여기 위에 실행 파일 등록이 '없다'
이대로 빌드하면 빌드는 성공해도 실행 파일이 안 생겨, 나중에 ros2 run 시 No executable found가 납니다. 그래서 ament_package() 바로 위에 다음 세 블록을 추가합니다.
# 1) 소스를 컴파일해 실행 파일 생성
add_executable(cpp_status_publisher src/cpp_status_publisher.cpp)
# 2) 실행 파일에 ROS 2 의존성(rclcpp·std_msgs) 연결
ament_target_dependencies(cpp_status_publisher rclcpp std_msgs)
# 3) ros2 run이 찾도록 lib/<패키지명>에 설치
install(TARGETS cpp_status_publisher
DESTINATION lib/${PROJECT_NAME})
| CMake 항목 | 의미 |
|---|---|
add_executable() |
소스를 컴파일해 실행 파일 생성 |
ament_target_dependencies() |
실행 파일에 ROS 2 의존성 연결 |
install(TARGETS …) |
ros2 run이 찾도록 설치 — 이게 빠지면 No executable found |
💡
find_package(rclcpp REQUIRED)·find_package(std_msgs REQUIRED)는--dependencies rclcpp std_msgs로 패키지를 만들었다면 이미 있습니다.if(BUILD_TESTING)블록은 지우지 말고 그대로 두세요(테스트용).
빌드·실행:
cd ~/ros2_build_ws
colcon build # 전체
# colcon build --packages-select cpp_robot_pkg # 특정 패키지만
source install/setup.bash
ros2 run cpp_robot_pkg cpp_status_publisher
⚠️ 흔한 함정 — No executable found (빌드는 성공했는데 실행이 안 됨)
colcon build가 Finished <<< cpp_robot_pkg로 끝났는데 ros2 run이 다음을 뱉으면:
~/ros2_build_ws# ros2 run cpp_robot_pkg cpp_status_publisher
No executable found
원인: CMakeLists.txt에 위 3블록(add_executable·ament_target_dependencies·install)을 추가하지 않았거나, 추가 후 다시 빌드/source 하지 않은 것입니다. C++ 빌드는 실행 파일 등록이 없어도 Finished로 끝나기 때문에 놓치기 쉽습니다(가장 흔한 실수).
해결 — 3블록을 넣었는지 확인하고 깨끗이 다시 빌드:
cd ~/ros2_build_ws
rm -rf build/ install/ log/ # 이전 빌드 잔재 제거(권장)
colcon build --packages-select cpp_robot_pkg
source install/setup.bash
ros2 run cpp_robot_pkg cpp_status_publisher
install/을 지웠다가 다시 빌드하는 과정에서source install/setup.bash시AMENT_PREFIX_PATH ... doesn't exist같은 경고가 잠깐 보일 수 있는데, 빌드가 끝나면 사라지는 무해한 경고입니다. (확인:ls ~/ros2_build_ws/src/cpp_robot_pkg/CMakeLists.txt에서add_executable·install줄이 있는지cat으로 점검)
⚠️ 흔한 함정 — ModuleNotFoundError: No module named 'cmake' (C++ 빌드 실패)
Colab 등 일부 환경에서 C++ 패키지를 colcon build 할 때 다음 오류가 납니다.
--- stderr: cpp_robot_pkg
Traceback (most recent call last):
File "/usr/local/bin/cmake", line 4, in <module>
from cmake import cmake
ModuleNotFoundError: No module named 'cmake'
---
Failed <<< cpp_robot_pkg [0.06s, exited with code 1]
원인: /usr/local/bin/cmake가 사실은 pip로 설치된 cmake의 래퍼 스크립트인데, 정작 그 cmake 파이썬 모듈이 깨져 있어서입니다. 이 래퍼가 PATH에서 정상 apt cmake(/usr/bin/cmake, 3.22)를 가립니다. 그래서 apt install cmake를 해도 "already newest version"으로 무시되어 안 고쳐집니다(엉뚱한 cmake를 보고 있는 것).
해결 (검증됨):
# 1) pip 자체가 깨져 있을 수 있으니 먼저 복구
sudo apt update
sudo apt install -y python3-pip
# 2) pip cmake 패키지 재설치 → /usr/local/bin/cmake 래퍼 정상화
pip3 install cmake
# 3) 다시 빌드
cd ~/ros2_build_ws
colcon build --packages-select cpp_robot_pkg
성공하면 Finished <<< cpp_robot_pkg가 뜹니다. 이때 함께 보이는
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 3.10 will be removed from a future version of CMake.
경고는 무해합니다(빌드는 성공). 최신 cmake가 아주 낮은 최소 버전을 권장하지 않는다는 안내일 뿐입니다. 거슬리면 CMakeLists.txt 첫 줄을 cmake_minimum_required(VERSION 3.10) 이상으로 올리면 사라집니다.
💡 대안 — 깨진 래퍼를 치우기: pip 래퍼 대신 apt cmake를 쓰고 싶다면
pip3 uninstall -y cmake && hash -r후which cmake가/usr/bin/cmake로 잡히는지 확인하세요(단, pip가 깨졌다면 위 1)을 먼저 실행). 핵심은 "colcon이 어떤 cmake를 보고 있는가" 입니다 —which cmake로 확인하는 습관이 좋습니다.
6. Colcon 빌드 옵션 & 환경 설정
| 명령 | 설명 |
|---|---|
colcon build |
워크스페이스 전체 빌드 |
colcon build --packages-select 패키지명 |
특정 패키지만(시간 절약) |
colcon build --symlink-install |
Python 소스(.py) 수정 시 재빌드 부담 감소 (단, setup.py의 entry_points 변경이나 새 파일 추가 시엔 재빌드 필요) |
colcon list |
패키지 목록 |
colcon test |
테스트 실행 |
빌드 뒤에는 항상 환경을 반영해야 합니다.
# 일반적인 순서(매 터미널)
source /opt/ros/humble/setup.bash # ROS 2 기본
cd ~/ros2_build_ws
source install/setup.bash # 방금 빌드한 내 워크스페이스
⚠️
.bashrc에 특정 워크스페이스를 고정하면 여러 워크스페이스를 오갈 때 혼동이 생깁니다. 실습 중엔 직접source하며 "지금 어느 워크스페이스인지" 의식하는 습관이 좋습니다.
7. Python vs C++ 패키지
| 구분 | Python (ament_python) |
C++ (ament_cmake) |
|---|---|---|
| 주요 파일 | setup.py |
CMakeLists.txt |
| ROS 클라이언트 | rclpy |
rclcpp |
| 실행 등록 | entry_points |
add_executable + install |
| 장점 | 작성이 쉽고 빠름 | 실행 속도·성능 |
| 주 사용 | 실습·프로토타입·데이터 처리 | 제어·센서 드라이버·실시간 노드 |
→ 실제 로봇 시스템에선 Python과 C++ 패키지가 함께 쓰입니다(빠른 개발은 Python, 성능이 중요한 제어·드라이버는 C++).
8. ROS 2 패키지 개발 흐름
워크스페이스 생성
→ src에 패키지 생성(ros2 pkg create)
→ package.xml 의존성 확인
→ 코드 작성(Python 또는 C++)
→ 실행 파일 등록(setup.py / CMakeLists.txt)
→ colcon build
→ source install/setup.bash
→ ros2 run으로 실행
→ ros2 node / ros2 topic으로 확인
이 흐름은 이후 모든 ROS 2 실습에서 반복됩니다 — 센서 처리, 제어, Gazebo 연동, 강화학습 통합까지 전부 이 패키지·빌드 구조 위에서 진행됩니다.
9. 흔한 빌드 오류
| 오류 | 원인 / 해결 |
|---|---|
colcon: command not found |
sudo apt install -y python3-colcon-common-extensions |
Package not found |
빌드/소스 누락 → colcon build → source install/setup.bash |
No executable found |
Python: setup.py의 entry_points 누락 / C++: CMakeLists.txt에 add_executable·install(TARGETS) 누락 (빌드는 성공해도 실행 파일이 없음 → 위 5절 함정) |
ModuleNotFoundError: No module named 'py_robot_pkg.xxx' |
파일명·setup.py 등록 오타, 또는 빌드 후 source 미실행 |
rclcpp/rclcpp.hpp: No such file |
find_package(rclcpp REQUIRED) 누락 |
ModuleNotFoundError: No module named 'cmake' (C++ 빌드) |
pip의 깨진 cmake 래퍼(/usr/local/bin/cmake)가 apt cmake를 가림 → sudo apt install -y python3-pip && pip3 install cmake 후 재빌드 (위 5절 함정 참고) |
| 수정했는데 결과 안 바뀜 | 재빌드 안 함 → colcon build(Python은 --symlink-install 편리) |
10. 실무 관점 — 패키지를 어떻게 나누나
실제 로봇 시스템은 기능과 책임으로 패키지를 나눕니다. 예: 물류 로봇.
| 패키지 | 역할 |
|---|---|
warehouse_robot_description |
URDF·XACRO·mesh(로봇 모델) |
warehouse_robot_bringup |
전체 노드 실행 launch |
warehouse_robot_navigation |
경로 계획·이동 |
warehouse_robot_perception |
카메라·LiDAR 인식 |
warehouse_robot_control |
바퀴·모터·그리퍼 제어 |
warehouse_robot_interfaces |
사용자 정의 msg·srv·action |
warehouse_robot_simulation |
Gazebo 시뮬레이션 |
warehouse_robot_rl |
강화학습 환경·정책 |
설계 기준: 기능 분리(한 패키지 = 한 책임), 재사용성, 의존성 최소화, 인터페이스 분리(msg·srv·action은 별도 패키지). 프로젝트가 커져도 관리가 쉬워집니다.
Day2 마무리
이번 6편(Day2)에서 우리는:
- 리눅스 명령줄(1·2편)으로 기초 체력을 다지고,
- ROS 2 통신 4종(3편 개념 → 4편 토픽 → 5편 서비스·액션)을 직접 구현하고,
- 패키지·빌드 구조(6편)로 그 코드를 담는 그릇을 익혔습니다.
워크스페이스 → 패키지 → 코드 → 등록 →
colcon build→source→ros2 run
이 흐름이 ROS 2 로봇 개발의 기본기입니다. 앞으로의 센서 처리, 로봇 제어, Gazebo 연동, 강화학습 통합 실습은 모두 이 토대 위에서 확장됩니다.
📚 Week1 Day2 전체 목차 (총 6편)
- 1/6 리눅스 기초 — 쉘·파일시스템·핵심 명령어
- 2/6 리눅스 실전 8 시나리오
- 3/6 ROS 2 통신 4종 개념 — 노드·토픽·서비스·액션
- 4/6 ROS 2 토픽 Pub/Sub 직접 만들기
- 5/6 ROS 2 서비스·액션 구현
- 6/6 ROS 2 패키지 빌드 — Python·C++·colcon — 이번 글
'피지컬AI' 카테고리의 다른 글
| [Physical AI W2D1] 2/6 — RViz 개념 ②: TF·좌표계·URDF·센서 Display·디버깅 (0) | 2026.06.20 |
|---|---|
| [Physical AI W2D1] 1/6 — RViz로 로봇을 눈으로 보다: 시각화 흐름·Fixed Frame·Display (0) | 2026.06.20 |
| [Physical AI W1D2] 5/6 — ROS 2 서비스·액션 구현: 요청/응답과 목표/피드백 (0) | 2026.06.14 |
| [Physical AI W1D2] 4/6 — ROS 2 토픽 직접 만들기: Publisher·Subscriber (0) | 2026.06.14 |
| [Physical AI W1D2] 3/6 — ROS 2 통신의 4가지 길: 노드·토픽·서비스·액션 (0) | 2026.06.14 |