제4부 빌드·패키징·배포
4.4 package.xml format 3
4.4.1 이 장에서 배우는 것 / 선수 지식
package.xml은 ROS 패키지의 유일한 필수 파일이다(0.6). 그리고 가장 자주 부정확하게 작성되는 파일이기도 하다 — 예제를 복사해 이름만 바꾸고, 의존은 전부 <depend>로 적고, 빌드가 되면 넘어간다.
그런데 이 파일은 세 가지 다른 소비자를 갖는다. colcon은 빌드 순서를 여기서 읽고(4.5), rosdep은 시스템 패키지를 여기서 찾고(4.6), 빌드팜은 릴리스 의존을 여기서 계산한다(4.9). 의존 태그를 부정확하게 쓰면 로컬에서는 되는데 CI나 릴리스에서 깨진다.
배우는 것:
- format 3 스키마 — 필수·선택 요소 전수.
- 의존 태그 일곱 종류 — 각각의 정확한 의미와 선택 기준.
- 조건부 의존 —
condition속성으로 배포판·플랫폼 분기. - 그룹 의존 —
member_of_group·group_depend의 용도. - export 섹션 — 빌드 타입·플러그인·기타 메타데이터.
- rosdep 키 — ROS 패키지 이름과 시스템 패키지 키의 구분.
- 버전·라이선스·유지보수자 — 릴리스에 필요한 것들.
선수 지식: 4.1(ament), 4.2·4.3(빌드 타입별 사용). rosdep은 4.6, colcon은 4.5, 릴리스는 4.9에서 이어받는다.
정직: format 3 스키마는 REP 149에 정의된다.
condition속성의 평가 규칙과 사용 가능한 변수는 도구 버전에 의존하며(확인 필요), 아래는 일반적으로 통용되는 사용법이다. 이 환경에서 대부분 빌드·rosdep 검증하지 않았다(목차 3) — 예외는<build_type>기본값으로, 88차에 실행 확인해catkin임을 밝혔다(4.4.5).
4.4.2 최소 형태와 전체 구조
최소 형태:
<?xml version="1.0"?>
<package format="3">
<name>my_pkg</name>
<version>0.1.0</version>
<description>한 줄 설명</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache-2.0</license>
</package>
이 다섯 요소(name·version·description·maintainer·license)가 필수다.
실무 형태:
<?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>my_robot_driver</name>
<version>1.2.0</version>
<description>
시리얼 기반 모터 드라이버. ros2_control 하드웨어 인터페이스를 제공한다.
</description>
<maintainer email="[email protected]">Robotics Team</maintainer>
<author email="[email protected]">Alice</author>
<license>Apache-2.0</license>
<url type="repository">https://github.com/org/my_robot_driver</url>
<url type="bugtracker">https://github.com/org/my_robot_driver/issues</url>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>sensor_msgs</depend>
<depend>hardware_interface</depend>
<build_depend>eigen</build_depend>
<exec_depend>ros2launch</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
<export>
<build_type>ament_cmake</build_type>
<hardware_interface plugin="${prefix}/hardware_plugins.xml"/>
</export>
</package>
함정:
<?xml-model?>줄은 선택이지만 넣는 편이 좋다. 에디터가 스키마 검증을 해 주고,ament_xmllint(4.2.7)가 문법 오류를 검출한다.
4.4.3 의존 태그 일곱 종류
이것이 이 장의 핵심이다. 각 태그는 "언제 필요한가" 를 선언한다.
| 태그 | 언제 필요 | 전형적 예 |
|---|---|---|
<buildtool_depend> | 빌드 도구 자체 | ament_cmake, rosidl_default_generators |
<buildtool_export_depend> | 이 패키지를 쓰는 쪽의 빌드 도구 | 드묾 |
<build_depend> | 이 패키지를 컴파일할 때 | 헤더만 쓰는 라이브러리 |
<build_export_depend> | 이 패키지를 쓰는 쪽이 컴파일할 때 | 공개 헤더에 등장하는 것 |
<exec_depend> | 실행할 때 | 런타임 라이브러리, launch 도구, Python 모듈 |
<depend> | build + build_export + exec 축약 | 대부분의 ROS 의존 |
<test_depend> | 테스트할 때 | 린터, gtest, pytest |
<doc_depend> | 문서 생성 시 | doxygen 등 |
<depend>가 셋을 합친 것이라는 사실이 중요하다. 편해서 대부분 이것을 쓰지만, 정확하지 않을 수 있다.
선택 기준:
| 상황 | 태그 |
|---|---|
| C++ 공개 헤더에 등장 | <depend> (또는 build+build_export+exec) |
C++ 구현에만 사용(.cpp) | <build_depend> + <exec_depend>(확인 필요: 동적 링크면 exec도 필요) |
| 헤더 전용 라이브러리(Eigen 등) | <build_depend> + <build_export_depend> |
| Python 모듈 임포트 | <exec_depend>(4.3.6) |
| launch 파일에서 다른 패키지 노드 실행 | <exec_depend> |
| 메시지 생성 도구 | <buildtool_depend> |
| 인터페이스 패키지 런타임 | <exec_depend>rosidl_default_runtime</exec_depend> |
견해:
<depend>를 기본으로 쓰되, 다음 두 경우는 구분하라. ① Python 패키지의 의존은<exec_depend>— 빌드 단계가 없으므로. ② launch 파일에서만 쓰는 패키지는<exec_depend>— 컴파일에 필요 없으므로. 이 둘만 지켜도 의존 그래프가 훨씬 정확해지고, 빌드 순서와 릴리스 의존이 부풀지 않는다(4.5).
부정확한 의존이 만드는 실제 문제:
| 문제 | 원인 |
|---|---|
| 빌드 순서가 불필요하게 직렬화 | 과도한 build_depend |
| 릴리스 시 불필요한 패키지 요구 | 과도한 exec_depend |
| CI에서만 빌드 실패 | 누락된 build_depend(로컬엔 이미 설치돼 있어 안 드러남) |
| 로봇에서만 런타임 실패 | 누락된 exec_depend |
마지막 둘이 핵심이다. 개발 머신에는 온갖 패키지가 이미 설치돼 있어 의존을 빠뜨려도 빌드·실행이 된다. 클린 환경(CI·새 로봇)에서만 드러난다 — 그래서 CI가 필요하다(4.10).
# 의존 누락 검증: 클린 컨테이너에서
docker run --rm -v $PWD:/ws -w /ws ros:jazzy bash -c \
"rosdep install --from-paths src -y --ignore-src && colcon build"
4.4.4 조건부 의존
<!-- 특정 배포판에서만 -->
<depend condition="$ROS_DISTRO == jazzy">some_jazzy_only_pkg</depend>
<depend condition="$ROS_DISTRO == humble">some_humble_pkg</depend>
<!-- Python 버전 분기 -->
<exec_depend condition="$ROS_PYTHON_VERSION == 3">python3-numpy</exec_depend>
| 변수 | 내용 |
|---|---|
$ROS_DISTRO | jazzy·humble 등 |
$ROS_VERSION | 1·2 |
$ROS_PYTHON_VERSION | 2·3 |
연산자: ==, !=, and, or, 괄호.
<depend condition="$ROS_DISTRO == jazzy or $ROS_DISTRO == kilted">new_api_pkg</depend>
용도: 여러 배포판을 하나의 브랜치로 지원할 때(15.1). 배포판마다 패키지 이름이 바뀌거나, 어떤 배포판에만 존재하는 의존이 있을 때 쓴다.
정직:
condition으로 표현할 수 있는 것은 제한적이다. 플랫폼(arm64 vs amd64)이나 세밀한 버전 비교는 지원되지 않거나 도구 의존이다(확인 필요). 복잡한 분기가 필요하면 배포판별 브랜치를 나누는 편이 명확하다.
4.4.5 그룹 의존
두 가지 다른 메커니즘이 있어 혼동되기 쉽다.
① <member_of_group> — "나는 이 그룹의 일원이다"
<member_of_group>rosidl_interface_packages</member_of_group>
1.2.5에서 본 것이다. 인터페이스 패키지가 자신을 표시해, 코드 생성기들이 이 패키지를 인식하게 한다. 누락이 인터페이스 패키지 빌드 실패의 최다 원인이다.
② <group_depend> — "이 그룹의 모든 구성원에 의존한다"
<group_depend>rosidl_typesupport_c_packages</group_depend>
그룹에 속한 패키지 전부를 의존으로 지정한다. 동적으로 확장되는 의존 집합을 표현할 때 쓴다 — 예를 들어 "설치된 모든 typesupport 구현"처럼, 목록을 미리 알 수 없는 경우.
| 태그 | 방향 |
|---|---|
<member_of_group> | 나 → 그룹 (소속 선언) |
<group_depend> | 그룹 → 나 (전체 의존) |
견해: 일반 애플리케이션 패키지에서
<group_depend>를 쓸 일은 드물다.<member_of_group>은 인터페이스 패키지를 만들 때 반드시 필요하므로 그것만 기억하면 된다.
4.4.6 export 섹션
<export>
<build_type>ament_cmake</build_type>
<!-- pluginlib 플러그인 선언 (4.2.6) -->
<nav2_core plugin="${prefix}/planner_plugins.xml"/>
<hardware_interface plugin="${prefix}/hardware_plugins.xml"/>
<!-- 기타 도구별 메타데이터 -->
<rosdoc2>rosdoc2.yaml</rosdoc2>
</export>
| 요소 | 용도 |
|---|---|
<build_type> | ament_cmake·ament_python·ament_cmake_python·cmake(4.1.6) |
<{패키지명} plugin="..."> | pluginlib 플러그인 등록 |
| 임의 요소 | 도구가 자기 메타데이터를 넣는 자리 |
${prefix}는 패키지 소스 디렉터리로 치환된다.
함정:
<build_type>을 빠뜨리면 기본값은catkin이다(88차 실행 확인 — Jazzy ·catkin_pkg). ROS 2 패키지에서 이것은 거의 항상 의도와 다르다.colcon list가(ros.catkin)으로 표시하고, 빌드하면 이렇게 실패한다:CMake Error: The source directory "…/nobt" does not appear to contain CMakeLists.txt.오류가
build_type을 가리키지 않는다 — "CMakeLists.txt가 없다"고만 말하므로,ament_python패키지를 만들다 이 함정에 빠지면 잘못된 데를 찾게 된다. 항상 명시하라.
pluginlib 선언의 요소 이름은 "기반 패키지 이름"이다. nav2_core의 플러그인을 제공하면 <nav2_core plugin="..."/>. 이것을 틀리면 플러그인이 발견되지 않는다(4.2.6).
4.4.7 rosdep 키 vs ROS 패키지 이름
의존 태그 안에 들어가는 이름은 두 종류다.
<depend>rclcpp</depend> <!-- ① ROS 패키지 이름 -->
<depend>eigen</depend> <!-- ② rosdep 키 (시스템 패키지) -->
<exec_depend>python3-numpy</exec_depend> <!-- ② rosdep 키 -->
| 종류 | 해석 | 예 |
|---|---|---|
| ROS 패키지 이름 | 워크스페이스나 설치본에서 찾음 | rclcpp, sensor_msgs |
| rosdep 키 | rosdep 데이터베이스에서 OS 패키지로 변환 | eigen, boost, python3-numpy, libopencv-dev |
같은 이름 공간을 공유하므로 구분이 안 보인다. 도구가 먼저 ROS 패키지를 찾고, 없으면 rosdep 키로 해석한다.
# rosdep 키 확인
rosdep resolve eigen
# #apt
# libeigen3-dev
rosdep resolve python3-numpy
rosdep 키를 찾는 법:
# 데이터베이스 검색
grep -rn "^eigen:" /etc/ros/rosdep/sources.list.d/ 2>/dev/null
# 또는 온라인 rosdistro 저장소의 rosdep/base.yaml · python.yaml 확인
함정: 존재하지 않는 rosdep 키를 쓰면
rosdep install이 실패한다. 오타나 추측한 이름이 원인인 경우가 많다.rosdep resolve <키>로 먼저 확인하는 습관이 좋다. 사내 패키지라면 자체 rosdep 소스를 등록해야 한다(4.6).
4.4.8 버전·라이선스·유지보수자
릴리스를 고려하면 이 셋이 중요해진다(4.9).
버전:
<version>1.2.0</version>
- 시맨틱 버저닝(major.minor.patch)을 따르는 것이 관행.
CHANGELOG.rst와 함께 관리된다 —catkin_generate_changelog·catkin_prepare_release가 이 값을 높인다(4.9).- Python 패키지는
setup.py의version과 일치시켜야 한다(4.3.6).
라이선스:
<license>Apache-2.0</license>
<license file="LICENSE">BSD-3-Clause</license> <!-- 파일 지정 -->
- SPDX 식별자를 쓰는 것이 권장된다(
Apache-2.0,BSD-3-Clause,MIT). - 여러 라이선스면 태그를 여러 개 쓴다.
- 제품 출하 시 법무 검토 대상이므로 정확해야 한다(14.7).
유지보수자:
<maintainer email="[email protected]">Robotics Team</maintainer>
<author email="[email protected]">Alice</author>
maintainer는 필수이며 현재 관리 주체다. 릴리스 도구가 이 주소를 쓴다.author는 선택이며 원저자를 기록한다.
견해:
maintainer에 개인 이메일 대신 팀·조직 주소를 쓰라. 담당자가 바뀌어도 패키지 메타데이터를 고칠 필요가 없고, 퇴사한 사람 이름이 남는 상황을 피한다. 공개 패키지에서 특히 중요하다.
4.4.9 검증
# XML 문법·스키마
xmllint --noout package.xml
# ROS 도구로 파싱 확인
ros2 pkg xml my_pkg
ros2 pkg xml my_pkg --tag version
ros2 pkg xml my_pkg --tag license
# 의존 해석
rosdep check --from-paths src --ignore-src
rosdep install --from-paths src -y --ignore-src --simulate
# colcon이 읽은 의존 그래프
colcon graph --dot | dot -Tpng -o graph.png # (확인 필요: colcon-graph 확장 필요)
colcon list --topological-order
colcon list --topological-order가 유용하다 — 의존이 정확한지, 빌드 순서가 의도대로인지 보여준다(4.5).
클린 환경 검증(가장 확실):
docker run --rm -v $PWD:/ws -w /ws ros:jazzy bash -c '
apt update &&
rosdep install --from-paths src -y --ignore-src &&
colcon build &&
source install/setup.bash &&
ros2 pkg list | grep my_pkg
'
이것이 의존 선언의 정확성을 검증하는 유일하게 확실한 방법이다. 개발 머신에서는 이미 설치된 것들 때문에 누락이 드러나지 않는다(4.4.3).
4.4.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | <member_of_group>rosidl_interface_packages</member_of_group> 누락 | 인터페이스 생성 안 됨 | 추가(1.2.5) |
| 2 | <build_type> 누락 | 의도와 다른 빌드 | 명시 |
| 3 | 의존 누락(로컬엔 설치돼 있음) | CI·새 로봇에서만 실패 | 클린 환경 검증 |
| 4 | 전부 <depend>로 선언 | 의존 그래프 부풀림 | Python·launch는 exec_depend |
| 5 | 존재하지 않는 rosdep 키 | rosdep install 실패 | rosdep resolve로 확인 |
| 6 | package.xml·setup.py 버전 불일치 | 릴리스 도구 혼란 | 동기화(4.3.6) |
| 7 | pluginlib export 요소 이름 오류 | 플러그인 미발견 | 기반 패키지 이름 사용 |
| 8 | 개인 이메일을 maintainer로 | 담당 변경 시 부채 | 팀 주소 |
| 9 | 라이선스 부정확 | 법무 문제 | SPDX 식별자·검토 |
| 10 | CMakeLists.txt에만 find_package | rosdep이 의존을 모름 | 양쪽에 선언(4.2.2) |
| 11 | condition으로 복잡한 분기 시도 | 표현 한계 | 배포판별 브랜치 |
| 12 | XML 스키마 미검증 | 오타가 늦게 발견 | xmllint·ament_xmllint |
| 13 | 유지보수자 이메일에 아무 값 | 빌드 실패(Invalid email) | 형식 검증을 받는다(14.5.12.4) |
10번을 강조한다. CMakeLists.txt의 find_package와 package.xml의 의존 선언은 별개이며 둘 다 필요하다. 전자는 CMake가, 후자는 rosdep·colcon·빌드팜이 읽는다. 한쪽만 있으면 로컬 빌드는 되는데 의존 설치나 빌드 순서가 어긋난다.
4.4.11 체크포인트
package.xml의 필수 요소 다섯 가지를 말할 수 있는가.- 의존 태그 일곱 종류의 의미를 각각 한 문장으로 말할 수 있는가.
<depend>는 무엇의 축약인가. - Python 패키지와 launch 전용 의존에 어떤 태그를 쓰는가. 그 이유는?
- 부정확한 의존이 만드는 문제 네 가지를 들 수 있는가. 그중 개발 머신에서 드러나지 않는 둘은?
- 의존 선언의 정확성을 검증하는 확실한 방법은 무엇인가.
condition속성으로 무엇을 할 수 있고, 무엇이 어려운가.<member_of_group>과<group_depend>의 방향 차이를 설명할 수 있는가. 실무에서 반드시 필요한 것은?- export 섹션에 들어가는 것 세 종류는? pluginlib 요소 이름은 무엇으로 정해지는가.
- 의존 태그 안의 이름 두 종류를 구분할 수 있는가. rosdep 키를 확인하는 명령은?
maintainer에 팀 주소를 쓰라는 조언의 근거는?CMakeLists.txt의find_package와package.xml의 의존이 왜 둘 다 필요한가.
다음 장 4.5는 이 파일들을 읽어 실제로 빌드를 수행하는 도구 — colcon의 의존 그래프·선택 옵션·mixin·확장을 심층적으로 다룬다.