ROS 2 — 패키지란 무엇이고 왜 이렇게 생겼나 (워크스페이스·colcon·의존성)
패키지는 단순한 폴더가 아니라 ROS의 근본 단위다. 모듈성·재사용이라는 설계 철학에서 출발해 매니페스트·의존성·워크스페이스·오버레이·빌드 시스템이 왜 그렇게 생겼는지 구조 중심으로 풀어본다.
ROS로 노드 하나를 python3 로 돌려보면 “이게 다야?” 싶다. 하지만 실제 ROS 프로젝트는 코드를 그렇게 두지 않는다. 전부 패키지(package) 라는 단위로 묶는다. 우리가 띄운 거북이(turtlesim)도, 파이썬으로 노드를 짜게 해준 rclpy도, 자율주행 스택 같은 거대한 것들도 — ROS의 모든 것이 패키지다.
그래서 패키지는 “코드를 담는 폴더” 정도가 아니라, ROS라는 세계가 돌아가는 근본 단위다. 이 글은 명령어를 외우기보다, 패키지가 무엇이고 왜 이런 모양인지 를 구조와 철학 중심으로 본다. 이게 잡히면 나중에 어떤 ROS 프로젝트를 열어도 길을 잃지 않는다.
1. 왜 “패키지” 단위인가 — 모듈성과 재사용
로봇 소프트웨어는 거대하다. 카메라 처리, 지도 작성, 경로 계획, 모터 제어… 이걸 한 덩어리로 짜면 손댈 수도, 고칠 수도, 나눠 맡을 수도 없다.
ROS의 해법은 모듈성(modularity) 이다 — 큰 시스템을 독립적인 작은 조각으로 쪼갠다. 그 조각 하나가 패키지다. 한 패키지는 보통 한 가지 책임을 진다 (“카메라 드라이버”, “경로 계획” 처럼). 이렇게 쪼개면 세 가지가 가능해진다.
- 독립 개발·교체 — 경로 계획 패키지만 더 좋은 걸로 갈아끼워도 나머지는 그대로
- 재사용 — 전 세계가 만들어 공개한 패키지를 가져다 조립한다. 바닥부터 짤 필요가 없다
- 생태계 호환 — 내 패키지가 ROS의 표준 모양을 따르면, RViz·rosbag 같은 도구들이 자동으로 내 패키지를 인식한다
실제 로봇은 내가 만든 패키지 몇 개 + 남이 만든 패키지 수십·수백 개를 조립한 결과물이다. 패키지는 그 조립의 최소 단위다.
이 “쪼개고 조립한다”는 철학이 패키지 구조 전체를 결정한다. 아래 구성요소들은 전부 이 철학을 떠받치기 위해 존재한다.
2. 패키지의 정체 — 코드 + 매니페스트
패키지를 열면 코드 파일들만 있는 게 아니라, 자기 자신을 설명하는 파일이 같이 있다. 패키지는 이렇게 정의된다.
패키지 = 코드 + 매니페스트(manifest).
매니페스트는 패키지의 자기소개서다. ROS 2에서는 package.xml 파일이 그 역할을 한다. 여기엔 패키지의 신원과 기대는 것이 적혀 있다.
- 신원 — 이름, 버전, 설명, 만든 사람, 라이선스
- 의존성(dependency) — 이 패키지가 동작하려면 필요한 다른 패키지들
이 중 의존성이 핵심 철학이다. 내 패키지가 거북이 메시지 타입을 쓴다면, “나는 turtlesim 패키지에 기댄다” 고 매니페스트에 선언한다. 그러면 ROS는 이 선언을 읽어서:
- 누가 누구에게 기대는지 관계도를 파악하고
- 빌드할 때 기대는 쪽을 먼저 빌드하도록 순서를 정하고
- 빠진 의존성이 있으면 미리 알려준다
핵심은 “선언하면 ROS가 알아서 관리한다” 는 점이다. 의존 관계를 코드 곳곳에 숨기지 않고 매니페스트 한 곳에 명시 하는 것 — 이게 큰 시스템을 조립 가능하게 만드는 비결이다.
3. 워크스페이스 — 패키지들이 모여 일하는 작업장
패키지 하나하나는 조각일 뿐이고, 그 조각들을 모아서 한꺼번에 빌드하는 작업 공간이 필요하다. 그게 워크스페이스(workspace) 다. 보통 ~/ros2_ws 같은 폴더를 만들고, 그 안은 네 부분으로 나뉜다.
ros2_ws/
├── src/ ← 내가 쓰는 곳: 패키지들의 소스 코드
├── build/ ← 빌드 도구의 작업 공간 (중간 산출물)
├── install/ ← 빌드 결과물: 실제로 실행에 쓰는 것
└── log/ ← 빌드 기록
여기서 중요한 건 src 와 install 의 분리다.
src/는 사람이 쓰는 원본install/은 빌드가 만들어낸 실행용 결과물
원본과 결과물을 섞지 않기 때문에, build/·install/ 을 통째로 지우고 다시 빌드해도 내 소스는 멀쩡 하다. (그래서 빌드가 꼬이면 이 둘만 지우고 다시 빌드하는 게 흔한 처방이다.)
4. 오버레이 — 시스템 ROS 위에 내 것을 얹는다
워크스페이스에서 가장 멋진 발상이 오버레이(overlay) 다.
컴퓨터에는 이미 시스템 ROS(/opt/ros/jazzy 등)가 깔려 있다. turtlesim, rclpy 같은 기본 패키지가 다 거기 있다. 이 바닥 층을 언더레이(underlay) 라 한다. 그리고 내가 만든 워크스페이스는 그 위에 얹는 오버레이 가 된다.
이 계층을 쌓는 행위가 바로 source 다. 새 터미널에서
source /opt/ros/jazzy/setup.bash→ 언더레이(시스템 ROS)를 깔고source ~/ros2_ws/install/setup.bash→ 그 위에 오버레이(내 워크스페이스)를 얹는다
겹치는 패키지가 있으면 위(오버레이)가 우선 한다. 이 구조 덕분에 시스템 ROS를 전혀 안 건드리고도 내 패키지를 위에 얹어 실험하거나, 기본 패키지를 내 버전으로 덮어쓸 수 있다. ROS의 안전하고 유연한 개발은 이 오버레이 철학에서 나온다.
5. colcon과 ament — 빌드가 하는 일
패키지를 src/ 에 넣었다고 바로 쓸 수 있는 게 아니다. 빌드(build) 를 거쳐야 한다. 여기엔 두 이름이 나온다.
- ament(에이먼트) — ROS 2의 빌드 시스템. 패키지 하나를 어떻게 빌드·설치할지 정한 규칙
- colcon(콜콘) — 워크스페이스 전체를 의존성 순서대로 빌드해주는 명령 도구. 매니페스트(§2)에 적힌 의존 관계를 읽어 “누구를 먼저 빌드할지” 정한다
그런데 파이썬은 컴파일이 필요 없는데 왜 “빌드”를 할까? 여기서 빌드는 코드를 기계어로 바꾸는 게 아니라, 패키지를 install/ 공간에 정리하고, 바깥에서 부를 수 있는 실행 명령을 등록하는 작업이다. 빌드가 끝나야 비로소 install/ 에 실행 가능한 형태가 갖춰지고, 그걸 source 해서 ros2 run 으로 부를 수 있다.
진입점(entry point) — 패키지가 바깥에 노출하는 실행 명령을 가리킨다. 파이썬 패키지에서는
setup.py에 “이 실행 이름은 이 함수를 부른다” 를 선언해 둔다. 빌드는 이 선언을 읽어ros2 run 패키지 실행이름이 동작하도록 연결해 준다.
6. 직접 만들며 구조 확인하기
이제 위에서 말한 것들이 실제 파일로 어떻게 나타나는지 손으로 본다. (노드 코드 자체는 이미 다룬 영역이라, 여기서는 패키지 구조에만 집중한다.)
패키지 뼈대를 만든다.
$ ros2 pkg create --build-type ament_python my_robot --dependencies rclpy turtlesim
→ 한 줄로 아래 구조가 생긴다. 각 파일이 앞 절의 개념과 정확히 대응한다.
my_robot/
├── package.xml ← §2 매니페스트 (신원 + 의존성: rclpy, turtlesim)
├── setup.py ← §5 진입점을 선언하는 곳
├── setup.cfg
└── my_robot/ ← 노드 코드(.py)를 두는 곳
└── __init__.py
--build-type ament_python→ 이 패키지는 ament 규칙(§5)을 따르는 파이썬 패키지--dependencies rclpy turtlesim→ 의존성(§2)을 매니페스트에 미리 선언
노드 파일을 안쪽 my_robot/ 에 두고, 그 노드를 바깥에 노출하려면 setup.py 의 진입점에 한 줄을 선언한다.
entry_points={
'console_scripts': [
'draw_circle = my_robot.draw_circle:main', # 실행이름 = 패키지.파일:함수
],
},
이 한 줄의 의미는 “draw_circle 이라고 부르면 my_robot/draw_circle.py 의 main 을 실행하라” 다 — §5에서 말한 진입점 선언이 바로 이것이다.
이제 워크스페이스 최상단에서 빌드한다.
$ cd ~/ros2_ws
$ colcon build
Starting >>> my_robot
Finished <<< my_robot [2.1s]
Summary: 1 package finished
→ colcon이 매니페스트의 의존성을 읽어 빌드하고, 결과를 install/ 에 만든다. 이제 오버레이를 얹고(§4) 실행한다.
$ source install/setup.bash
$ ros2 run my_robot draw_circle
→ ros2 run 은 §5에서 등록한 진입점을 찾아 실행한다. python3 파일.py 와 달리, 이제 폴더 어디에 있든 패키지 이름만으로 부를 수 있다. 방금 한 모든 단계가 앞에서 설명한 철학·구조의 실물 인 셈이다.
7. 구조 ↔ 의미 한눈에
| 구성요소 | 무엇인가 | 왜 있나 (철학) |
|---|---|---|
| 패키지 | 코드 + 매니페스트의 묶음 | 큰 시스템을 쪼개고 조립하는 최소 단위 (모듈성) |
| package.xml | 매니페스트(자기소개서) | 신원과 의존성을 선언 → ROS가 관계·순서를 자동 관리 |
| 워크스페이스 | 패키지들을 모아 빌드하는 작업장 | src(원본)와 install(결과)을 분리 |
| 오버레이/언더레이 | 시스템 ROS 위에 내 것을 얹는 계층 | 시스템을 안 건드리고 안전하게 개발·교체 |
| ament / colcon | 빌드 시스템 / 빌드 명령 도구 | 의존성 순서대로 빌드, install에 정리 |
| 진입점(setup.py) | 외부에 노출하는 실행 명령 | ros2 run이 함수를 찾도록 연결 |
한 줄로 박아둘 것
- ROS의 모든 것이 패키지다. 패키지는 폴더가 아니라, 쪼개고 조립한다는 모듈성 철학의 최소 단위
- 패키지 = 코드 + 매니페스트(
package.xml). 매니페스트가 신원과 의존성을 선언하면 ROS가 관계를 자동 관리한다 - 워크스페이스는 패키지들의 작업장.
src(원본)와install(결과)을 분리한다 - 오버레이 — 시스템 ROS(언더레이) 위에 내 워크스페이스를 얹는 구조.
source가 그 층을 쌓고, 겹치면 위가 우선 - 빌드(colcon/ament) 는 컴파일이 아니라, 패키지를
install에 정리하고 진입점을 등록하는 일 - 이 구조를 알면 어떤 ROS 프로젝트를 열어도
package.xml·setup.py·워크스페이스에서 길을 찾을 수 있다