AEGLOSLAB ROS 2 Jazzy 대전

제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 build_depend exec_depend build_export_ depend test_depend
태그언제 필요전형적 예
<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_DISTROjazzy·humble 등
$ROS_VERSION1·2
$ROS_PYTHON_VERSION2·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로 확인
6package.xml·setup.py 버전 불일치릴리스 도구 혼란동기화(4.3.6)
7pluginlib export 요소 이름 오류플러그인 미발견기반 패키지 이름 사용
8개인 이메일을 maintainer로담당 변경 시 부채팀 주소
9라이선스 부정확법무 문제SPDX 식별자·검토
10CMakeLists.txt에만 find_packagerosdep이 의존을 모름양쪽에 선언(4.2.2)
11condition으로 복잡한 분기 시도표현 한계배포판별 브랜치
12XML 스키마 미검증오타가 늦게 발견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·확장을 심층적으로 다룬다.