# 패키지 디렉터리 구조와 확장 전략

#### 기본 디렉터리 구조 개요

ROS2 Humble에서 권장하는 패키지 디렉터리 구조는 표준적인 ROS2의 구조를 따른다. 일반적으로 다음과 같은 최상위 디렉터리를 구성하게 된다.

* **CMakeLists.txt** 패키지 빌드 설정을 위한 CMake 구성이 담긴 파일이다. ROS2 빌드를 위해 필요한 `find_package`, `ament_package`, `install` 지시어 등이 선언된다.
* **package.xml** 패키지 의존성, 라이선스, 이름, 버전 정보 등의 메타데이터를 기술하는 XML 파일이다. 로컬 개발 환경에서 사용할 ROS2 패키지 정보뿐만 아니라, 패키지 배포 시에도 중요한 정보를 담습니다.
* **src/** 소스 코드가 위치하는 디렉터리이다. C++ 또는 Python 코드가 들어간다. C++의 경우에는 .cpp 파일을 모아두고, Python의 경우에는 파이썬 스크립트를 배치하거나 패키지 형식으로 구성한다.
* **include/** 헤더 파일을 모아두는 디렉터리이다. C++의 경우, 노드를 모듈화하거나 라이브러리를 구성할 때 header를 이 디렉터리에 위치시킨다.
* **launch/** roslaunch(ROS1)에서 `launch` 파일을 사용하듯, ROS2에서는 Python 기반의 `.launch.py` 파일로 노드 실행 시나리오를 작성한다. 여러 노드를 동시에 실행하거나 매개변수를 전달하는 로직을 이 디렉터리에 둔다.
* **resource/** 패키지 실행에 필요한 리소스(아이콘, URDF, 메시 등)를 모아두는 디렉터리이다. 3D 모델이나 시각 자료가 포함될 수 있다.
* **param/** 파라미터 YAML 파일을 모아두는 디렉터리이다. 노드 초기 실행 시, 매개변수를 설정하기 위해 참조되는 YAML 또는 다른 설정 파일들이 여기에 위치한다.
* **test/** 패키지 단위 테스트를 작성하기 위한 디렉터리이다. GTest 기반의 단위 테스트나, ROS2의 launch\_testing을 활용한 통합 테스트 스크립트를 두는 경우도 있다.

이러한 구조는 ROS2 패키지를 확장하거나 협업을 진행할 때 혼선을 줄이고, 빌드와 실행 환경을 일관성 있게 유지하기 위해 매우 중요하다.

#### CMakeLists.txt와 package.xml의 역할

**CMakeLists.txt**:

CMake를 활용하여 빌드 과정을 정의한다. 예를 들어 C++로 작성된 ROS2 노드를 컴파일할 때, 다음과 같이 사용할 수 있다.

```cmake
cmake_minimum_required(VERSION 3.8)
project(my_ros2_package)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(my_node src/my_node.cpp)
ament_target_dependencies(my_node rclcpp)

install(TARGETS
  my_node
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()
```

**package.xml**:

패키지 이름, 버전, 라이선스, 의존 패키지 등이 XML로 정의된다. 예시로 다음과 같이 구성할 수 있다.

```xml
<package format="3">
  <name>my_ros2_package</name>
  <version>0.0.1</version>
  <description>Example ROS2 Humble package</description>
  <maintainer email="user@example.com">Username</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
```

#### Python 패키지 구조

Python으로 ROS2 패키지를 구성할 경우에는 조금 다른 방식을 사용한다. `setup.py` 파일과 함께 Python 모듈 방식을 따른 후, `entry_points`를 활용하여 노드를 등록한다. 일반적인 구성은 다음과 같다.

```
my_ros2_python_package/
├── package.xml
├── setup.py
├── my_ros2_python_package/
│   ├── __init__.py
│   └── my_node.py
└── resource/
```

이 때 `setup.py`에는 다음과 같이 작성하여, ROS2에서 실행 파일처럼 동작하는 Python 노드를 등록한다:

```python
from setuptools import setup

package_name = 'my_ros2_python_package'

setup(
    name=package_name,
    version='0.0.1',
    packages=[package_name],
    data_files=[
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='User',
    maintainer_email='user@example.com',
    description='Example Python ROS2 package',
    license='Apache-2.0',
    entry_points={
        'console_scripts': [
            'my_node = my_ros2_python_package.my_node:main'
        ],
    },
)
```

#### 다중 노드 패키지 확장

ROS2 애플리케이션 규모가 커지면, 하나의 패키지 내부에서 여러 노드를 관리해야 할 수 있다. 이 경우 다음과 같은 전략을 고려해볼 수 있다.

**src 디렉터리 하위 구조 세분화**: 여러 노드가 한 패키지에 공존하는 경우, 노드별 혹은 기능별로 서브디렉터리를 만들어 관리하면 좋다. 예를 들어 “이미지 처리”와 “메시지 브리지” 기능이 한 패키지에 들어가야 한다면 다음과 같은 구조를 취할 수 있다.

```
my_ros2_package/
├── src/
│   ├── image_processing/
│   │   ├── image_filter.cpp
│   │   └── image_publisher.cpp
│   └── msg_bridge/
│       ├── can_subscriber.cpp
│       └── ros_publisher.cpp
```

이렇게 구분하면, 기능별로 소스 코드가 격리되어 유지보수가 용이해진다.

**독립된 빌드 타겟 구성**: 각 기능별로 실행 파일을 만들 때 CMakeLists.txt에서 빌드 타겟을 따로 지정하고, 필요한 의존성만 각각 연결하는 방식을 사용할 수 있다. 예:

```cmake
add_executable(image_filter src/image_processing/image_filter.cpp)
ament_target_dependencies(image_filter rclcpp sensor_msgs)

add_executable(can_subscriber src/msg_bridge/can_subscriber.cpp)
ament_target_dependencies(can_subscriber rclcpp std_msgs)
```

여러 노드를 등록하되, 불필요한 의존성 중복을 피하고자 하는 목적도 있다.

#### 라이브러리(target) 분리와 재사용

노드 간 공유해야 하는 로직(예: 공통 수학 연산, 메시 변환 로직 등)이 있으면, 패키지 내에서 라이브러리 형태로 모듈화해두는 것이 좋다.

**라이브러리 빌드**: 다음과 같이 라이브러리를 만들고, 필요한 곳에서 link해 사용할 수 있다.

```cmake
add_library(my_shared_lib SHARED
  src/common/math_utils.cpp
  src/common/filter_utils.cpp
)
ament_target_dependencies(my_shared_lib rclcpp)

add_executable(advanced_node src/nodes/advanced_node.cpp)
target_link_libraries(advanced_node my_shared_lib)
ament_target_dependencies(advanced_node rclcpp)
```

**include 디렉터리 구조**: 라이브러리를 효율적으로 관리하기 위해 공용 헤더를 `include/` 디렉터리에 배치하고, `#include <my_ros2_package/math_utils.hpp>` 형태로 재사용하도록 구성할 수 있다. 패키지명과 동일한 최상위 디렉터리를 포함 경로로 삼으면 충돌을 줄이고 가독성이 좋아진다.

#### Launch와 Param 구조 확장

대형 프로젝트에서 launch 파일과 파라미터 파일 또한 복잡해질 수 있다. 유지보수와 확장성을 위해 별도의 디렉터리 구조를 만들어 관리하는 것이 좋다.

**launch 구조 예시**:

```
my_ros2_package/
└── launch/
    ├── image_processing.launch.py
    ├── msg_bridge.launch.py
    ├── combined_system.launch.py
```

* `image_processing.launch.py` 파일 내부에서 이미지 처리 관련 노드만 실행
* `combined_system.launch.py` 파일에서는 모든 노드를 종합적으로 실행

**param 구조 예시**:

```
my_ros2_package/
└── param/
    ├── image_processing.yaml
    ├── bridge_config.yaml
    └── global_params.yaml
```

여러 파라미터 파일을 목적별로 나누어 필요할 때마다 서로 합성하거나 독립적으로 사용할 수 있도록 관리한다.

#### 메시/서비스/액션 정의 위치

ROS2에서는 메시, 서비스, 액션을 .msg, .srv, .action 파일로 정의한다. 자체 메시 타입을 만들어 사용해야 하는 경우라면, 기존 패키지 내부에 `msg/`, `srv/`, `action/` 디렉터리를 추가로 구성할 수 있다.

```
my_ros2_package/
├── msg/
│   └── CustomData.msg
├── srv/
│   └── ComputeTask.srv
└── action/
    └── RobotMove.action
```

이를 정의하고 사용하려면 `package.xml`과 `CMakeLists.txt` 내에서 `message_generation`, `action_msgs`, `rclcpp`, `rclpy` 등의 의존 관계를 명시하고, 메시 빌드 설정을 추가로 기입해주어야 한다.

#### 확장 전략과 모노레포(monorepo) 구성

규모가 더욱 커지면, 여러 ROS2 패키지를 한 저장소(레포지토리)에 함께 두는 모노레포(monorepo) 방식을 쓰기도 한다. 예를 들어 다음과 같은 구조를 구성할 수 있다:

```
my_ros2_workspace/
├── src/
│   ├── camera_drivers/
│   │   ├── camera_driver_1/
│   │   └── camera_driver_2/
│   ├── perception/
│   │   ├── perception_core/
│   │   └── deep_learning_inference/
│   ├── control/
│   │   ├── controller_core/
│   │   └── robot_interface/
│   └── my_ros2_package/
└── ...
```

이처럼 여러 관련 패키지를 한곳에서 관리하면, 버전 동기화나 개발 워크플로우를 통합하기가 수월해진다. 다만, 불필요한 의존 관계가 생기지 않도록 유의해야 한다.

#### 추가적인 테스트 디렉터리 구조

테스트 규모가 커지거나 다양한 테스트 방식을 적용해야 하는 상황에서는, 단순히 `test/` 디렉터리 하위에 모든 테스트 코드를 모으는 대신 목적에 따라 구조를 세분화하기도 한다.

```
my_ros2_package/
└── test/
    ├── unit/
    │   ├── test_math_utils.cpp
    │   └── test_filter_utils.cpp
    ├── integration/
    │   └── test_system_integration.launch.py
    └── launch/
        └── test_multinode.launch.py
```

* **unit**: 유닛 테스트. 로직 단위(함수, 클래스)별 테스트를 작성하여 내부 동작을 검증한다.
* **integration**: 여러 기능을 동시에 테스트하거나, 주로 노드 간 상호작용을 검증한다.
* **launch**: ROS2의 launch\_testing 기반 테스트 스크립트로 작성할 때 구분할 수 있다.

이렇게 분리해두면 테스트 스코프가 명확해져서 유지보수 및 CI/CD 구성이 쉬워진다.

#### 벤더(Vendor) 패키지 디렉터리 구조

서드파티 라이브러리나 센서·하드웨어 제조사에서 제공하는 소프트웨어를 그대로 로컬에 넣어 빌드해야 할 때에는, 별도의 “벤더(vendor)” 패키지 구조를 사용할 수 있다. ROS2 개발자들은 외부 종속성을 관리하기 위해 다음과 같은 구조를 종종 구성한다.

```
my_vendor_package/
├── CMakeLists.txt
├── package.xml
├── vendor/
│   ├── third_party_library/
│   │   ├── include/
│   │   ├── src/
│   │   └── CMakeLists.txt
└── patches/
    └── third_party_library.patch
```

* **vendor/third\_party\_library**: 외부 라이브러리 원본 코드를 이 디렉터리에 복사한다.
* **patches**: 경우에 따라, 외부 라이브러리에 대한 패치를 따로 보관해두고, 빌드 시점에 자동 적용할 수도 있다.

이때 `my_vendor_package/CMakeLists.txt`에서 외부 코드를 빌드하도록 호출(예: `add_subdirectory(vendor/third_party_library)`)하고, 필요한 경우 경로 설정이나 컴파일 옵션을 수정해준다.

#### 다언어 혼합 패키지 구조

ROS2는 C++과 Python 모두를 지원하므로, 필요에 따라 하나의 패키지 안에서 C++ 노드와 Python 노드를 함께 개발할 수도 있다. 이 경우 아래와 같은 구조를 취해볼 수 있다.

```
my_mixed_package/
├── CMakeLists.txt
├── package.xml
├── src/
│   ├── cpp_nodes/
│   │   └── my_cpp_node.cpp
│   └── py_nodes/
│       └── my_py_node.py
├── include/
│   └── my_mixed_package/
│       └── my_header.hpp
├── setup.py
└── ...
```

* `CMakeLists.txt`는 C++ 관련 빌드를 담당한다.
* `setup.py`는 Python 관련 빌드를 담당한다.
* `package.xml`에는 C++과 Python 빌드 의존성이 모두 기술된다.

단, `ament_cmake_python`을 함께 사용하거나, `setuptools`의 `entry_points` 설정을 주의 깊게 해야 한다. 실제 배포 환경에서 노드 실행 시 충돌이 없는지, ROS2 런처에서 두 언어를 동시에 다룰 때 문제가 없는지 확인하는 과정이 필수이다.

#### 네이밍 규칙과 버전 관리

디렉터리, 노드 이름, 메시 타입, 라이브러리 이름 등은 가독성·일관성을 위해 팀 내부 규칙을 갖추는 것이 좋다.

**디렉터리, 파일명**: 하위 호환성과 유지보수를 위해, 최대한 소문자와 언더바(\_)를 사용하거나, 카멜케이스(camelCase)/파스칼케이스(PascalCase) 등 팀에서 합의된 방식을 따른다.

**버전 태깅**: 패키지 개발 과정에서 `package.xml`의 `<version>`을 주기적으로 업데이트하고, Git 태그나 release 브랜치 등으로 관리하면 안정적인 릴리즈가 가능한다.

**API 안정성**: 노출하는 API(헤더, 클래스, 함수, 메시 타입 등)가 바뀔 때마다 의미 있는 버전 번호를 부여해야 다른 패키지들과의 의존성을 안정적으로 유지할 수 있다.

#### Docker 및 CI/CD 관점에서의 구조

ROS2 패키지를 Docker 컨테이너에서 빌드하거나, CI(Continuous Integration)를 활용하여 자동 빌드·테스트 파이프라인을 구성할 때에는 디렉터리 구조가 특히 중요해진다.

**Dockerfile 예시**:

```dockerfile
FROM ros:humble
WORKDIR /colcon_ws
COPY . ./src/my_ros2_package/
RUN apt-get update && apt-get install -y \
    ros-humble-rclcpp \
    ...
RUN colcon build
```

이처럼 패키지 구조가 정돈되어 있으면, `COPY` 로 복사한 뒤

```
colcon build
```

단 한 번으로 일괄 빌드 가능한다.

* **CI 작업** GitHub Actions, GitLab CI 등에서 Docker 이미지나 ROS2 빌드 환경을 사용해 패키지를 빌드·테스트하려면, 디렉터리 구조와 패키지 메타정보(package.xml, CMakeLists.txt)가 정확해야 한다.

#### 프로젝트 구조

프로젝트 디렉터리 구성을 간단히 시각화하면 다음과 같다:

{% @mermaid/diagram content="flowchart TB
A\[package.xml] --- B\[CMakeLists.txt]
A --- C\[include/]
A --- D\[src/]
A --- E\[launch/]
A --- F\[param/]
A --- G\[test/]
D --- H\[\[node1.cpp]]
D --- I\[\[node2.cpp]]
F --- J\[\[config.yaml]]
G --- K\[\[test\_example.cpp]]" %}

이 예시에서는 `package.xml`과 `CMakeLists.txt`가 최상위 구조를 이루고, 소스 코드(`src/`), 헤더 파일(`include/`), 실행 스크립트(`launch/`), 매개변수(`param/`), 테스트(`test/`) 디렉터리가 서로 연결된 형태로 구성됨을 보여준다.

#### 크로스 컴파일(Cross-compilation) 환경에서의 디렉터리 구조

ROS2 Humble 패키지를 임베디드 보드나 특정 아키텍처(ARM, RISC-V 등) 환경에 배포해야 할 경우, 크로스 컴파일 환경을 마련해야 한다. 패키지 디렉터리 구조가 깔끔하면 타겟 환경에 맞춰 빌드 스크립트를 작성하거나, CMake 툴체인 파일을 적용하기가 한결 수월한다.

**툴체인 파일(toolchain file) 사용**: 일반적으로 `$COLCON_DEFAULTS_FILE`, `CMAKE_TOOLCHAIN_FILE` 옵션 등을 활용하여 원하는 크로스 컴파일 환경을 설정한다. 이 때 패키지 내부의 CMakeLists.txt에 필요 이상의 설정을 하드코딩하기보다는, 외부 툴체인 파일에서 빌드 옵션을 제어하도록 구성하는 편이 좋다.

독립된 아티팩트(artifact) 생성: 크로스 컴파일 후 결과물이 일반 x86\_64 바이너리와 섞이지 않도록, 빌드 결과물을 별도 디렉터리에 저장하거나 Docker 컨테이너를 사용한다. 예를 들어 다음과 같이 구성할 수 있다.

```
colcon build --merge-install --base-paths src/ \
             --build-base build_arm/ \
             --install-base install_arm/
```

이렇게 하면 x86\_64 용 빌드 결과물과 ARM 용 빌드 결과물을 분리하여 관리할 수 있다.

#### 표준화된 빌드 스크립트와 QA(품질 관리)

규모가 큰 ROS2 프로젝트에서는 기업 또는 오픈소스 팀 단위로 일괄된 빌드 스크립트를 제공하기도 한다. 예컨대 Bash 스크립트를 만들어 다음 항목들을 자동으로 처리하도록 구성할 수 있다.

**준비 단계**:

```bash
#!/usr/bin/env bash
set -e
source /opt/ros/humble/setup.bash
```

ROS2 환경 설정 및 의존 패키지 설치 등을 스크립트에 넣을 수 있다.

**빌드 단계**:

```bash
colcon build --symlink-install
```

라이브러리와 노드 간 빠른 연동 테스트를 위해 `--symlink-install` 옵션 사용 등.

**테스트 단계**:

```bash
colcon test --event-handlers console_cohesion+
colcon test-result --verbose
```

단위/통합/launch 테스트를 모두 자동으로 수행하고 결과를 요약한다.

**코드 품질 점검**:

```
ament_cpplint .
ament_uncrustify .
ament_flake8 .
```

C++ 코드에 cpplint, uncrustify, Python 코드에 flake8 등을 적용하여 스타일이나 문법을 자동 검사한다.

이와 같은 빌드·테스트 스크립트를 공유 저장소 최상위에 두면, 모든 팀원이 동일한 방식으로 패키지를 빌드·실행·테스트할 수 있어, 디렉터리 구조나 확장 전략과 시너지를 낼 수 있다.

#### 고수준 설계를 위한 추가 고려사항

**플러그인(Plugin) 구조**: RViz2, Nav2, rclcpp Components 등 플러그인 메커니즘을 사용하는 경우, 소스 디렉터리를 플러그인별로 나누고, `plugin_description.xml`이나 CMakeLists.txt에서 대응되는 플러그인을 등록할 수 있다.

**멀티 로봇 및 분산 노드**: 여러 로봇(혹은 여러 디바이스)에서 노드를 배포해야 하는 경우, 공용 라이브러리와 로봇별 스펙이 다른 실행 환경을 고려해 디렉터리 구조를 설계한다. 파라미터 파일에도 로봇별 설정을 나누는 전략을 적용할 수 있다.

#### 인라인 수식 예시

ROS2와 직접적인 관련이 없지만, 패키지 내에서 일부 수학 연산을 수행한다고 가정해보겠다. 수식이 필요한 경우는 다음과 같이 표시할 수 있다:

* 인라인 예시: $x(t) = A \sin(\omega t + \phi)$
* 블록 수식 예시:

$$
J = \int\_{0}^{T} \bigl( \mathbf{x}(t)^T Q \mathbf{x}(t) + u(t)^2 \bigr),dt
$$

이처럼 ROS2 프로젝트의 구성 요소 중 제어, 경로 계획, SLAM 등의 기능을 구현할 때 수학적 모델이 들어간다면, 관련 라이브러리를 모듈화하여 관리하고, 수식 문서를 코드로 전환하기 좋도록 패키지 구조를 유지한다.
