제4부 빌드·패키징·배포
4.6 rosdep과 시스템 의존성
4.6.1 이 장에서 배우는 것 / 선수 지식
rosdep install --from-paths src -y --ignore-src — ROS 2를 쓰면 반드시 치게 되는 명령이다. 그런데 이것이 정확히 무엇을 하는지, 왜 가끔 실패하는지, 사내 패키지는 어떻게 등록하는지를 아는 사람은 적다.
rosdep이 푸는 문제는 명확하다. package.xml에 <depend>eigen</depend>라고 적었을 때, Ubuntu에서는 libeigen3-dev를, RHEL에서는 다른 이름을 설치해야 한다. 이 이름 변환 문제를 rosdep이 담당한다.
배우는 것:
- rosdep의 동작 원리 — 키에서 OS 패키지로의 변환 사슬.
- 데이터베이스 구조 — rosdistro 저장소와 YAML 규칙.
- 주요 명령 —
init·update·install·check·resolve. - 사내 패키지 등록 — 커스텀 rosdep 소스.
- 소스 목록의 우선순위 규칙(4.6.11) — 작은 번호가 우선한다, 그리고 그 이유.
- 에어갭·오프라인 환경 — 인터넷 없이 운용하기.
- CI·Docker에서의 사용 — 캐시와 재현성.
- 흔한 실패와 진단.
선수 지식: 4.4(package.xml 의존·rosdep 키), 4.5(colcon과의 역할 분담), 0.4(설치).
정직: rosdep 데이터베이스는 온라인 rosdistro 저장소에 의존하며 내용이 계속 바뀐다. 아래 키 이름·경로는 일반적 형태이며 실제 값은
rosdep resolve로 확인해야 한다(확인 필요). 이 환경에서 대부분 실행 검증하지 않았다(목차 3) — 예외는 소스 목록의 우선순위 규칙으로, 4.6.11에서 실행 확인하고 이 장의 서술을 정정했다.
4.6.2 동작 원리
핵심은 --ignore-src 옵션이다.
rosdep install --from-paths src -y --ignore-src
| 옵션 | 의미 |
|---|---|
--from-paths src | 이 경로의 package.xml들을 읽는다 |
--ignore-src | 워크스페이스 소스에 있는 패키지는 건너뛴다 |
-y | 설치 확인 프롬프트 자동 승인 |
--ignore-src가 없으면 워크스페이스에서 소스로 빌드할 패키지까지 apt로 설치하려 해서 실패하거나, 원치 않는 바이너리 버전이 설치된다.
데이터베이스 형식:
# rosdistro/rosdep/base.yaml 의 한 항목
eigen:
ubuntu: [libeigen3-dev]
fedora: [eigen3-devel]
rhel: [eigen3-devel]
# 배포판별 분기
libopencv-dev:
ubuntu:
jammy: [libopencv-dev]
noble: [libopencv-dev]
세 개의 주요 파일:
| 파일 | 내용 |
|---|---|
base.yaml | 시스템 라이브러리 |
python.yaml | Python 패키지 |
ruby.yaml 등 | 언어별 |
ROS 패키지는 별도로 처리된다 — rosdistro의 distribution.yaml이 릴리스된 ROS 패키지 목록을 갖고, rosdep이 이를 ros-jazzy-<패키지명> 형태로 변환한다.
rosdep resolve rclcpp
# #apt
# ros-jazzy-rclcpp
4.6.3 주요 명령
# 최초 1회 (시스템 전역 — sudo 필요)
sudo rosdep init
# 데이터베이스 갱신 (sudo 없이!)
rosdep update
# 의존 설치
rosdep install --from-paths src -y --ignore-src
# 무엇이 설치될지 미리 보기
rosdep install --from-paths src --ignore-src --simulate
# 누락된 의존 확인 (설치하지 않음)
rosdep check --from-paths src --ignore-src
# 키 하나 해석
rosdep resolve eigen
rosdep resolve python3-numpy
# 특정 배포판 기준으로 해석
rosdep resolve eigen --os=ubuntu:noble
# 특정 ROS 배포판 대상
rosdep install --from-paths src -y --ignore-src --rosdistro jazzy
함정:
sudo rosdep update를 하면 안 된다. 캐시가 root 소유로 생겨 이후 일반 사용자의rosdep update·install이 실패한다. 오류 메시지가 권한 문제로 나오지 않아 혼란스럽다.
# 잘못 실행했다면 복구
sudo rm -rf ~/.ros/rosdep /root/.ros/rosdep
rosdep update # sudo 없이
rosdep check가 CI에서 유용하다 — 설치하지 않고 누락만 보고하므로, 의존 선언이 완전한지 검증할 수 있다(4.4.3).
# CI: 의존 선언 누락 탐지
rosdep check --from-paths src --ignore-src || echo "의존 선언 확인 필요"
4.6.4 사내 패키지 등록
공개 rosdistro에 없는 패키지를 쓰려면 자체 rosdep 소스를 등록한다. 사내 라이브러리·상용 SDK·특수 드라이버가 여기 해당한다.
① 규칙 파일 작성:
# my-rosdep-rules.yaml
mycompany_sdk:
ubuntu:
noble: [mycompany-sdk-dev]
debian:
bookworm: [mycompany-sdk-dev]
vendor_camera_driver:
ubuntu: [libvendorcam-dev, libvendorcam1]
② 소스 목록에 등록:
# /etc/ros/rosdep/sources.list.d/50-mycompany.list
# 로컬 파일
yaml file:///opt/mycompany/rosdep/my-rosdep-rules.yaml
# 또는 사내 서버
yaml https://internal.example.com/rosdep/my-rosdep-rules.yaml
sudo tee /etc/ros/rosdep/sources.list.d/50-mycompany.list <<'EOF'
yaml file:///opt/mycompany/rosdep/my-rosdep-rules.yaml
EOF
rosdep update
rosdep resolve mycompany_sdk # 확인
번호 접두어가 우선순위를 정한다. 기본 목록은 20-default.list이며, 파일은 이름 순으로 읽히고 먼저 선언한 쪽이 우선한다 — 따라서 더 작은 번호가 우선한다(88차 실측, 4.6.11). 이 책은 앞서 반대로 적었다.
③ 사내 ROS 패키지:
사내에서 릴리스한 ROS 패키지(ros-jazzy-my-internal-pkg)를 쓰려면 rosdistro 형식의 사내 인덱스가 필요하다.
# ROSDISTRO_INDEX_URL 로 사내 인덱스 지정
export ROSDISTRO_INDEX_URL=https://internal.example.com/rosdistro/index-v4.yaml
rosdep update
견해: 사내 rosdep 소스는 배포 이미지에 미리 넣어 두는 것이 좋다. 개발자가 각자 설정하게 하면 환경이 갈리고, "내 머신에서는 되는데" 문제가 생긴다. Docker 이미지·프로비저닝 스크립트에 포함시킨다(14.8).
4.6.5 에어갭·오프라인 환경
인터넷이 없는 환경(폐쇄망 공장·현장 로봇)에서 rosdep을 쓰는 방법이다.
문제: rosdep update가 온라인 저장소를 받아야 한다.
① 캐시 복사
# 온라인 머신에서
rosdep update
tar czf rosdep-cache.tar.gz -C ~/.ros rosdep
# 오프라인 머신에서
mkdir -p ~/.ros
tar xzf rosdep-cache.tar.gz -C ~/.ros
rosdep install --from-paths src -y --ignore-src # 캐시 사용
정직: 캐시 형식·경로는 rosdep 버전에 의존하며, 버전이 다르면 호환되지 않을 수 있다
(확인 필요). 온·오프라인 머신의 rosdep 버전을 맞추는 것이 안전하다.
② 완전 로컬 소스
# 온라인에서 rosdistro 저장소를 받아 사내 서버에 미러
git clone https://github.com/ros/rosdistro.git
# 사내 HTTP 서버에 배치 후
export ROSDISTRO_INDEX_URL=http://internal/rosdistro/index-v4.yaml
③ rosdep을 우회한다
가장 실용적인 접근일 수 있다. 필요한 apt 패키지 목록을 추출해 두고 직접 설치한다.
# 온라인 머신에서 목록 생성
rosdep install --from-paths src --ignore-src --simulate \
| grep -oP '(?<=apt-get install -y ).*' | tr ' ' '\n' | sort -u > deps.txt
# 오프라인: 미리 받아 둔 .deb 로 설치
sudo dpkg -i /opt/debs/*.deb
견해: 에어갭 환경에서는 "빌드 환경을 컨테이너 이미지로 고정"하는 접근이 가장 견고하다(14.8). 의존이 이미 설치된 이미지를 온라인에서 만들어 반입하면, 현장에서 rosdep을 실행할 필요가 없다. 의존 해석을 배포 시점이 아니라 이미지 빌드 시점으로 옮기는 것이다.
4.6.6 CI·Docker에서의 사용
FROM ros:jazzy-ros-base
WORKDIR /ws
# ① 의존 해석을 위해 package.xml만 먼저 복사 (레이어 캐시 활용)
COPY src/*/package.xml /tmp/pkgs/
RUN mkdir -p src && \
cd /tmp/pkgs && for f in *.xml; do \
d=/ws/src/$(basename $f .xml); mkdir -p $d; cp $f $d/package.xml; done
# ② 의존 설치
RUN apt-get update && \
rosdep install --from-paths src -y --ignore-src && \
rm -rf /var/lib/apt/lists/*
# ③ 소스 복사 후 빌드 (소스가 바뀌어도 ①②는 캐시됨)
COPY src/ /ws/src/
RUN . /opt/ros/jazzy/setup.sh && colcon build --merge-install
package.xml만 먼저 복사하는 것이 핵심 기법이다. 소스 코드를 고쳐도 의존이 바뀌지 않았다면 Docker 레이어 캐시가 유지되어 빌드가 훨씬 빠르다.
정직: 위 스크립트는 개념을 보이기 위한 것이며 디렉터리 구조에 따라 조정이 필요하다. 실제로는
package.xml의 원래 경로 구조를 보존해야 colcon이 올바르게 인식한다.
공식 ROS 이미지에는 rosdep이 초기화되어 있다 — rosdep init을 다시 실행하면 오류가 난다.
# ❌ 공식 이미지에서는 불필요·오류
RUN rosdep init
# ✅ 갱신만
RUN rosdep update
CI에서의 재현성:
# rosdep 데이터베이스는 계속 바뀌므로, 어제 되던 빌드가 오늘 깨질 수 있다
# 대책: 베이스 이미지를 태그로 고정 + 의존 설치 결과를 이미지로 저장
FROM ros:jazzy-ros-base-20260801 # 날짜 태그 고정 (확인 필요: 태그 형식)
견해: CI에서
rosdep update를 매번 실행하면 외부 저장소 상태에 빌드가 종속된다. 저장소 장애나 규칙 변경으로 CI가 갑자기 깨질 수 있다. 의존이 설치된 이미지를 주기적으로 빌드해 두고 CI는 그것을 쓰는 구조가 안정적이다(4.10).
4.6.7 흔한 실패와 진단
| 증상 | 원인 | 대처 |
|---|---|---|
Cannot locate rosdep definition for [X] | 키가 DB에 없음(오타·미등록) | rosdep resolve X·사내 소스 등록 |
rosdep update 권한 오류 | sudo rosdep update 실행 이력 | 캐시 삭제 후 재실행 |
ERROR: unable to process source | 소스 목록 URL 접근 불가 | 네트워크·URL 확인 |
| 설치했는데 여전히 없다고 함 | 캐시가 오래됨 | rosdep update |
| 워크스페이스 패키지를 apt로 설치 시도 | --ignore-src 누락 | 옵션 추가 |
| 배포판이 틀리게 인식됨 | OS 판별 실패 | --os=ubuntu:noble 명시 |
| ROS 패키지를 못 찾음 | --rosdistro 미지정·ROS_DISTRO 미설정 | 환경 확인 |
# 진단 명령들
rosdep --version
cat /etc/ros/rosdep/sources.list.d/*.list
ls -la ~/.ros/rosdep/
rosdep resolve <문제의_키> --os=ubuntu:noble
rosdep db | grep <키> # (확인 필요: db 서브커맨드 존재 여부)
# OS 판별 확인
python3 -c "from rospkg.os_detect import OsDetect; print(OsDetect().detect_os())"
"키가 없다"가 가장 흔하다. 원인은 셋 — ① 오타, ② 그 라이브러리의 rosdep 키가 존재하지 않음, ③ 사내 패키지인데 등록 안 함.
②의 경우 rosdistro에 규칙을 기여할 수 있다(15.5). 공개 라이브러리인데 키가 없다면 PR을 보내는 것이 생태계에 기여하는 방법이다.
4.6.8 rosdep을 쓰지 않는 선택
모든 의존을 rosdep으로 관리해야 하는 것은 아니다.
| 상황 | 대안 |
|---|---|
| Python 라이브러리 다수 | requirements.txt + pip(단, 시스템 패키지와 충돌 주의) |
| 특수 버전이 필요한 라이브러리 | 소스 빌드 후 워크스페이스에 포함(4.7) |
| 상용 SDK | 설치 스크립트·프로비저닝 |
| 컨테이너 배포 | Dockerfile에 직접 명시 |
견해: 컨테이너로 배포한다면 rosdep의 가치가 줄어든다. Dockerfile에
apt install을 직접 쓰면 무엇이 설치되는지 명확하고 재현성이 높다. rosdep의 강점은 여러 OS·배포판을 지원할 때이며, 단일 플랫폼에 배포한다면 직접 명시가 더 단순할 수 있다.
다만 package.xml의 의존 선언은 그대로 유지하라 — colcon의 빌드 순서(4.5.3)와 릴리스(4.9)가 그것을 읽는다. rosdep을 쓰지 않는 것과 의존을 선언하지 않는 것은 다르다.
함정: pip와 apt를 섞으면 Python 패키지 충돌이 생길 수 있다. Ubuntu 24.04는 시스템 Python 환경 보호(PEP 668)를 적용하므로
pip install이 거부될 수 있다(확인 필요: ROS 환경에서의 권장 방식). 가상환경이나--break-system-packages를 쓰게 되는데, 후자는 이름 그대로 위험하다. 가능하면 apt 패키지(python3-*)를 쓰는 편이 안전하다.
4.6.11 소스 목록의 우선순위는 작은 번호가 우선한다 — 실측
이 장은 "더 큰 번호가 나중에 읽혀 덮어쓸 수 있다"고 적어 두었다. 거꾸로였다. 같은 키를 서로 다르게 정의한 목록 파일 둘을 넣고 확인했다(실행 확인 — Jazzy · ros:jazzy-ros-base 컨테이너 · rosdep 2.x).
4.6.11.1 같은 키를 두 파일이 다르게 정의하면
# /etc/ros/rosdep/sources.list.d/
# 10-rjlow.list → yaml file:///tmp/rd/low.yaml (rjtest: [PKG-FROM-10])
# 20-default.list
# 30-rjhigh.list → yaml file:///tmp/rd/high.yaml (rjtest: [PKG-FROM-30])
$ rosdep resolve rjtest
#apt
PKG-FROM-10 ← 작은 번호가 이겼다
대조군으로 10-을 지우면 PKG-FROM-30이 나온다 — 30번 파일 자체는 정상이며, 둘이 함께 있을 때 10번이 우선한 것이다.
4.6.11.2 규칙의 이름은 "먼저 선언한 쪽이 우선한다"
rosdep의 소스에 그 규칙이 문장으로 적혀 있다.
"…are first entry to declare a key wins. … This rule logic is modelled after the apt sources"
apt의 소스 목록을 본떴다. 파일은 이름을 정렬해 순서대로 읽고, 어떤 키를 처음 정의한 파일이 확정한다. 나중 파일의 같은 키는 무시된다(override를 명시하지 않는 한).
*그래서 사내 규칙으로 기본 데이터베이스를 덮어쓰려면 번호를
20보다 작게 줘야 한다.30-company.list로 만들면 기본값이 이미 이겨 있어 아무 일도 일어나지 않는다 — 그리고 오류도 경고도 없다. "분명히 등록했는데 적용되지 않는다"*의 흔한 원인이다.이 책이 반대로 적고 있었다는 사실 자체가 시사적이다 —
20-default.list라는 이름이 "기본값이니 가장 먼저 읽히고 나중 것이 덮어쓴다"는 인상을 준다. 실제로는 20보다 작은 번호를 위해 비워 둔 자리다.
4.6.9 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | sudo rosdep update | 이후 명령 실패 | 캐시 삭제 후 sudo 없이 |
| 2 | --ignore-src 누락 | 소스 패키지를 apt로 설치 시도 | 옵션 추가 |
| 3 | 존재하지 않는 키 | 설치 실패 | rosdep resolve로 사전 확인 |
| 4 | 사내 소스를 개발자별로 설정 | 환경 불일치 | 이미지·프로비저닝에 포함 |
| 5 | 공식 이미지에서 rosdep init | 오류 | update만 |
| 6 | CI에서 매번 rosdep update | 외부 저장소에 종속 | 의존 설치 이미지 사용 |
| 7 | 캐시 버전 불일치(에어갭) | 캐시 인식 실패 | rosdep 버전 통일 |
| 8 | pip와 apt 혼용 | Python 패키지 충돌 | python3-* apt 우선 |
| 9 | rosdep을 안 쓴다고 의존 선언도 생략 | 빌드 순서·릴리스 문제 | package.xml은 유지 |
| 10 | 의존 검증 없이 배포 | 새 로봇에서 실행 실패 | rosdep check·클린 환경 |
| 11 | 사내 목록을 30-으로 등록 | 기본값이 먼저 이겨 조용히 무시된다 | 20보다 작은 번호로(4.6.11.2) |
4.6.10 체크포인트
- rosdep이 푸는 문제를 한 문장으로 말할 수 있는가.
--ignore-src가 없으면 무슨 일이 생기는가. - rosdep 데이터베이스의 형식과 주요 파일 세 가지를 말할 수 있는가. ROS 패키지는 어떻게 처리되는가.
sudo rosdep update가 왜 문제이며, 잘못 실행했다면 어떻게 복구하는가.rosdep check가 CI에서 유용한 이유는 무엇인가.- 사내 패키지를 등록하는 두 단계와, 그 설정을 어디에 두어야 하는지 말할 수 있는가.
- 에어갭 환경에서의 세 가지 접근과, 가장 견고한 방식은 무엇인가.
- Dockerfile에서
package.xml만 먼저 복사하는 기법의 이유는? - CI에서 매번
rosdep update를 실행하는 것의 위험과 대안은? - "키를 찾을 수 없다"의 원인 세 가지를 구분할 수 있는가.
- 컨테이너 배포에서 rosdep의 가치가 줄어드는 이유는? 그래도
package.xml의존 선언을 유지해야 하는 이유는? - 사내 rosdep 규칙으로 기본 데이터베이스를 덮어쓰려 한다. 파일 이름을
10-으로 할까30-으로 할까, 왜 그런가(4.6.11).
다음 장 4.7은 바이너리 설치를 넘어 소스에서 빌드하는 영역이다 — vcstool로 여러 저장소를 관리하고, 워크스페이스를 계층으로 쌓고, 재현 가능한 빌드를 만드는 전략.