# Python Package Index(PyPI)와의 연동 기초

#### PyPI의 개념과 역할

Python Package Index(PyPI)는 파이썬 패키지를 업로드하고 배포할 수 있는 표준 중앙 저장소 역할을 한다. ROS2 Humble 환경에서 파이썬 기반의 기능을 개발하고, 이를 전 세계 사용자들과 공유하려면 PyPI를 적극적으로 활용할 수 있다. 예를 들어, 자율주행 로봇에서 특정 알고리즘을 파이썬 라이브러리 형태로 구축하였다면, 해당 라이브러리를 PyPI로 배포해 다른 ROS2 개발자들이 손쉽게 가져다 쓰도록 할 수 있다.

PyPI를 활용하면 다음과 같은 장점을 얻을 수 있다.

* 간단한 설치: 파이썬 환경에서 $pip$ 명령만으로 패키지를 빠르게 설치 가능
* 표준화된 배포: 많은 파이썬 사용자 커뮤니티가 이미 정립해 둔 표준 배포 가이드를 그대로 활용 가능
* 종속성 관리: 요구되는 종속 패키지들을 자동으로 설치하게 하여 일관성 있는 개발환경을 구성 가능

#### 기본 준비사항

PyPI 배포를 위해서는 다음과 같은 준비가 필요하다.

**PyPI 계정**: PyPI에 패키지를 업로드하려면 먼저 [PyPI 공식 사이트](https://pypi.org/)에서 계정을 만들어야 한다.

**프로젝트 구조**: ROS2 노드라 하더라도, 순수 파이썬 패키지 구조를 준수해야 PyPI로 업로드할 수 있다. 아래는 일반적인 파이썬 패키지 예시 구조이다.

```
my_ros2_python_pkg/
├── my_ros2_python_pkg
│   ├── __init__.py
│   └── main.py
├── tests
│   └── test_main.py
├── setup.py
└── README.md
```

**필수 메타데이터**: `$setup.py$` 또는 `$pyproject.toml$` 등에 들어가는 패키지 이름, 버전, 라이선스, 저자 정보 등을 정확히 기입해야 한다.

#### ROS2 패키지와 PyPI 패키지의 차이점

ROS2 패키지를 만들 때는 `$package.xml$`과 같은 ROS2 전용 설정 파일이 필요하다. 그러나 PyPI 배포를 위해서는 `$setup.py$` 혹은 `$pyproject.toml$`의 설정 파일이 필수다. ROS2 Humble 패키지를 PyPI로 배포하려면 결국 두 가지 설정 파일 모두를 함께 관리하는 경우가 많다.

예시:

```xml
<!-- package.xml (ROS2 Humble) -->
<package format="3">
  <name>my_ros2_python_pkg</name>
  <version>0.1.0</version>
  <description>Example Python package for ROS2 Humble</description>
  <maintainer email="you@example.com">Your Name</maintainer>
  <license>Apache-2.0</license>
  <buildtool_depend>ament_cmake</buildtool_depend>
  <exec_depend>python3</exec_depend>
  ...
</package>
# setup.py (PyPI)
from setuptools import setup

setup(
    name='my_ros2_python_pkg',
    version='0.1.0',
    packages=['my_ros2_python_pkg'],
    install_requires=[
        'setuptools',
        # 필요 라이브러리 기입
    ],
    author='Your Name',
    author_email='you@example.com',
    description='Example Python package for ROS2 Humble',
    url='https://github.com/your-repo/my_ros2_python_pkg',
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: Apache Software License',
    ],
    python_requires='>=3.7',
)
```

두 파일의 역할을 정리하면 아래와 같다.

* **package.xml**: ROS2 빌드 툴(예: `colcon`)에서 사용하는 ROS2 패키지 메타데이터
* **setup.py**: 파이썬 배포(예: PyPI)를 위한 패키지 메타데이터

#### PyPI에서 관리되는 버전 규칙

PyPI에 업로드되는 패키지는 버전 관리가 매우 중요하다. 일반적으로 파이썬 세계에서는 [SemVer(Semantic Versioning)](https://semver.org/) 방식이 널리 사용된다. 버전 표시는 주로 MAJOR.MINOR.PATCH 형태로 구성하며, 아래와 같은 의미를 가진다.

* MAJOR: 호환이 크게 달라지는 수준의 대규모 변경 시 증가
* MINOR: 새로운 기능이 추가되었으나 이전 버전과 호환성을 어느 정도 유지할 때 증가
* PATCH: 주로 버그 수정이나 호환성 문제가 없는 사소한 변경일 때 증가

예컨대 $0.1.0$에서 $0.2.0$으로 바뀌었다면, 새로운 기능이 포함되었음을 의미한다. ROS2의 빌드나 배포 환경에서도 이런 버전 규칙을 철저히 지켜야 사용자들에게 혼란을 주지 않는다.

#### PyPI 업로드 방식 개요

PyPI에 업로드하기 위해서는 일반적으로 아래 과정을 거친다:

**패키지 소스 준비**: `$setup.py$`(또는 `$pyproject.toml$`)를 갖춘 상태에서, `$README.md$` 등 문서 파일과 함께 프로젝트 디렉터리에 놓는다.

**빌드 및 배포용 파일 생성**: 다음과 같이 `setuptools` 혹은 `build` 모듈을 사용해 패키지를 빌드하고 `.whl`(wheel 파일) 및 `.tar.gz`(소스 배포용 아카이브) 파일을 생성한다.

```bash
$ python3 -m build
```

이 명령을 실행하면 `dist/` 디렉터리에 wheel 파일과 tar.gz 파일이 생긴다.

**테스트PyPI 업로드**: 정식 PyPI 업로드 전, [TestPyPI](https://test.pypi.org/)라는 테스트용 PyPI에 패키지를 올려 검증하는 과정을 거치는 것이 좋다.

```bash
$ twine upload --repository testpypi dist/*
```

**정식 PyPI 업로드**: 테스트를 마친 뒤 문제 없으면 정식 PyPI에 업로드한다.

```bash
$ twine upload dist/*
```

아래는 이 과정을 간단히 나타낸 플로차트 예시이다.

{% @mermaid/diagram content="flowchart LR
A\[소스코드\n준비] --> B\[setup.py\n/pyproject.toml\n작성]
B --> C\[빌드 및\ndist/ 생성]
C --> D\[TestPyPI\n업로드]
D --> E\[정식 PyPI\n업로드]" %}

#### ROS2 환경 내에서의 PyPI 패키지 활용

ROS2 Humble 환경에서 PyPI 패키지를 활용하는 방법은 크게 다음과 같다.

**python3 -m pip install**을 통한 설치: 단순히 `$pip$` 명령으로 패키지를 설치하고, Python import 방식으로 모듈을 불러서 ROS2 노드에서 사용한다.

**colcon build**와의 연동: 패키지 종속성을 `$setup.py$`에 명시해 두면, ROS2의 빌드 툴 `colcon`에서 해당 패키지 설치 과정을 트리거할 수도 있다.

**활용 예시**:

```python
import rclpy
from rclpy.node import Node
from my_ros2_python_pkg import some_utility   # PyPI에서 설치된 라이브러리

class MyNode(Node):
    def __init__(self):
        super().__init__('my_node')
        self.get_logger().info(some_utility.do_something())

def main(args=None):
    rclpy.init(args=args)
    node = MyNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()
```

#### PyPI 보안 토큰 관리와 자동 배포

PyPI에 업로드하기 위해서는 사용자의 인증 정보(계정, 패스워드 또는 API 토큰)가 필요하다. 이를 매번 터미널에 입력할 수도 있지만, CI/CD 환경 등에서 자동 배포를 구현하려면 다음과 같은 방법을 고려해볼 수 있다.

**PyPI API 토큰 발급**: PyPI 계정 설정(Security 탭)에서 프로젝트별 API 토큰을 발급받을 수 있다. 이 토큰을 발급받으면, 계정 비밀번호 대신 토큰을 이용해 업로드할 수 있다.

* 예: `$pypi-AgENdGVzdC5...` 형태의 긴 문자열

**환경 변수로 관리**: 배포 과정에서 민감 정보를 노출하지 않기 위해, CI 환경(예: GitHub Actions, GitLab CI/CD 등)에서 발급받은 PyPI 토큰을 암호화된 방식(Secrets)으로 저장한다. 예를 들어 GitHub Actions에서는 `PYPI_TOKEN` 같은 이름으로 시크릿을 등록하고, 워크플로에서 이를 참조한다.

**twine 설정**: `$twine upload dist/*` 명령을 실행할 때 환경변수를 활용하거나, `$~/.pypirc$` 파일에 토큰을 저장해 인증을 수행한다.

```bash
$ cat ~/.pypirc
[distutils]
index-servers =
    pypi

[pypi]
repository: https://upload.pypi.org/legacy/
username: __token__
password: pypi-AgE... # 실제 API 토큰
```

**자동화 예시(GitHub Actions)**: 아래는 GitHub Actions를 활용해 PR이 머지될 때마다 PyPI로 자동 배포하는 예시이다.

```yaml
name: Publish Python Package

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install build dependencies
        run: python -m pip install --upgrade pip build twine
      - name: Build package
        run: |
          python -m build
      - name: Publish package to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
        run: |
          python -m twine upload dist/*
```

#### pyproject.toml vs setup.py

최근 파이썬 패키징 생태계에서는 `pyproject.toml` 파일을 활용하는 방식이 권장되고 있다.

* **pyproject.toml**: 빌드 시스템 관련 설정(`build-system`, `project` 섹션 등)을 기술한다.
* **setup.py**: 기존 방식으로, 패키지의 메타데이터와 스크립트를 포함해 빌드 시 동적으로 처리하는 로직을 담을 수 있다.

ROS2 패키지를 PyPI에 배포할 때도 `pyproject.toml` 방식이 점차 보편화되는 추세이므로, 신규 프로젝트라면 다음과 같은 스켈레톤을 고려해볼 수 있다.

```toml
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my_ros2_python_pkg"
version = "0.1.0"
description = "Example Python package for ROS2 Humble"
authors = [
  {name = "Your Name", email = "you@example.com"}
]
license = "Apache-2.0"
readme = "README.md"
requires-python = ">=3.7"
dependencies = [
  "setuptools",
  # 필요 라이브러리 기입
]
urls = { 
  "Home": "https://github.com/your-repo/my_ros2_python_pkg" 
}
classifiers = [
  "Programming Language :: Python :: 3",
  "License :: OSI Approved :: Apache Software License",
]
```

기존 `setup.py` 파일 없이도, 이 `pyproject.toml` 만으로 패키징이 가능해진다. 단, ROS2 패키지 구조와 충돌되지 않도록, ROS2 관련 메타데이터(`package.xml`, `CMakeLists.txt` 등)는 별도로 유지해야 한다.

#### 테스트PyPI 환경에서의 종속성 검증

PyPI에 바로 업로드하기 전, TestPyPI를 이용해 패키지를 배포하고 설치 테스트를 수행하는 것이 좋다. 특히, ROS2와의 종속성(예: `rclpy` 버전 등)을 확인해야 한다면 TestPyPI에 업로드 후 테스트용 가상환경에서 실제 설치 시도를 해보면 된다.

```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple \
  my_ros2_python_pkg
```

* `--index-url https://test.pypi.org/simple/`로 TestPyPI에서 우선 패키지를 찾도록 설정
* `--extra-index-url https://pypi.org/simple`로, TestPyPI에 없는 종속 패키지는 정식 PyPI에서 가져오도록 설정

ROS2 의존 라이브러리도 제대로 가져와서 충돌 없이 동작하는지 확인할 수 있다.

#### Wheel 파일과 플랫폼 호환성

파이썬 패키지에서 확장 모듈(C/C++로 작성된 `.so`, `.dll`, `.pyd` 등)을 포함하거나, 특정 플랫폼(ARM, x86 등)에 한정된 로직을 포함하는 경우라면 Wheel 파일 빌드와 플랫폼 호환성 문제가 발생할 수 있다.

* pure-python 패키지: Wheel 파일 이름에 `py3-none-any.whl` 같은 형태로 표시된다. 어떠한 OS/아키텍처에도 호환되는 순수 파이썬 패키지라는 뜻이다.
* C 확장 모듈 포함 시: 예) `my_pkg-0.1.0-cp39-cp39-linux_x86_64.whl` 형태로, 파이썬 버전과 시스템 아키텍처 등이 Wheel 파일명에 표시된다.

ROS2에서 `rclpy` 확장을 사용하는 경우, 이를 PyPI에 올릴 때 특정 OS(Linux, Windows, macOS 등)마다 별도의 Wheel 파일을 만들어 배포해야 할 수도 있다. CI/CD 시스템에서 다양한 OS에 대해 Wheel을 빌드하고 `twine upload dist/*.whl` 명령으로 여러 파일을 동시에 업로드하는 식이다.

#### PyPI 배포시 유의사항

1. **버전 충돌** 이미 등록된 버전으로는 다시 업로드가 불가능하므로, $setup.py$나 $pyproject.toml$의 버전을 배포할 때마다 올바르게 업데이트해야 한다.
2. **라이선스 명시** ROS2는 Apache License 2.0을 채택하고 있으나, 종속 라이브러리에 따라 라이선스가 달라질 수 있다. 종속성 목록을 꼼꼼히 확인하고 최종 배포물의 라이선스 호환성을 체크해야 한다.
3. **코드 서명** (선택 사항) 대규모 프로젝트의 경우 배포 파일에 대한 서명(.asc 파일)을 추가해서 배포할 수도 있다.
4. **문서화** `$README.md$` 뿐만 아니라, Sphinx 등을 이용해 Read the Docs나 GitHub Pages 등으로 API 문서를 제공하면 사용자 접근성을 높일 수 있다.

#### 사설 PyPI 서버(DevPi) 활용

사내 혹은 특정 네트워크 내부에서만 접근 가능한 파이썬 패키지 저장소를 운용하고자 할 때는 사설 PyPI 서버를 세팅하는 방법을 고려할 수 있다. 대표적인 예시로 DevPi, Artifactory, Nexus 등이 있다. ROS2 Humble 환경에서도 다음과 같은 시나리오를 통해 사설 PyPI 서버를 유용하게 사용할 수 있다.

1. **내부 전용 패키지 공유** 사내 알고리즘, 회사 고유 로직, 또는 고객 전용 코드 등 외부에 공개하고 싶지 않은 파이썬 모듈들을 사설 PyPI 서버에 올려서 조직 내에서만 공유할 수 있다.
2. **버전 고정 및 검증** 불특정 다수에게 공개되는 PyPI보다는 사설 저장소에서 버전을 고정, 검증하며 내부 CI 파이프라인과 함께 관리하기 쉽다.
3. **ROS2 배포 파이프라인 연계** DevPi와 같은 사설 PyPI 서버를 GitHub Actions, GitLab CI/CD, Jenkins 등과 연동해, ROS2 패키지 빌드 성공 시 자동 업로드하고 개발팀이 즉시 설치·테스트할 수 있게 할 수 있다.

개인 서버에 DevPi를 간단히 세팅해보려면 다음과 같은 방식으로 진행할 수 있다.

```bash
$ pip install devpi-server devpi-client
$ devpi-server --start --host 0.0.0.0 --port 3141
```

서버가 동작한 뒤에는 `devpi-client`를 이용해 저장소를 구성하고, `$twine upload` 대신 `devpi upload` 등을 활용할 수 있다. 사설 PyPI이므로, 방화벽 설정과 사용자 권한 관리를 사전에 철저히 진행해야 한다.

#### PyPI 태그 활용

PyPI에서는 패키지를 업로드할 때 어떤 태그(Classifiers)를 달아서 검색 및 분류를 용이하게 할 수 있다. ROS2 같은 로봇 프레임워크나 특정 OS/하드웨어 환경을 명시해 주면, 사용자들이 패키지 호환성을 빠르게 파악할 수 있다.

* **Development Status**: 알파, 베타, 프로덕션(Production/Stable) 등 패키지 성숙도를 나타내는 태그
* **Intended Audience**: 패키지의 주요 타깃(개발자, 교육용, 산업용 등)
* **Operating System**: 리눅스, 윈도우, 맥OS 등 호환 OS
* **Topic**: 로보틱스, ROS, IoT 등

예컨대, `classifiers` 항목에 `"Topic :: Scientific/Engineering :: Robotics"`를 추가해두면 PyPI 검색 시 ROS2나 로보틱스 관련 키워드를 입력하는 사용자들에게 노출이 좋아진다.

#### 로컬 테스트와 디버깅 방법

PyPI 업로드 전에 로컬에서 `$pip install .` 명령으로 설치 테스트를 진행하는 방법이 매우 유용하다.

**로컬 환경에서 wheel/tar.gz 생성**:

```bash
$ python -m build
```

`dist/` 디렉터리에 wheel 파일과 tar.gz가 생성된다.

**로컬 설치**:

```bash
$ pip install dist/my_ros2_python_pkg-0.1.0-py3-none-any.whl
```

이렇게 설치 후, 설치된 라이브러리가 잘 import 되고, 의존성 충돌이 없는지 확인한다. ROS2 노드에서 실제 동작하는지도 테스트한다.

**Uninstall, Reinstall 루프**:

버전을 자주 수정하거나, 기능 추가·버그 수정 후 반복 테스트할 때 `$pip uninstall my_ros2_python_pkg && pip install dist/*.whl` 과정을 여러 번 반복하면서 안정화시킬 수 있다.

#### 유지보수 전략

ROS2 패키지를 PyPI로 배포하고 나면, 버그나 신규 기능 추가 시마다 꾸준히 버전을 갱신해야 한다. 아래와 같은 전략을 미리 수립해 두면 혼선을 줄일 수 있다.

**배포 채널 분리**:

예: 안정 버전(stable)과 개발 버전(dev)을 따로 관리. 필요하다면 `main` 브랜치와 `develop` 브랜치에 각각 CI를 붙여 TestPyPI와 PyPI로 구분 배포.

**릴리즈 노트(Release Notes) 관리**:

매 버전 업데이트 시 변경 내용을 문서화하여 사용자들에게 신속히 전달.

**ROS2 Humble 외 다른 ROS2 디스트로 지원 여부**:

Foxy, Galactic, Rolling 등 다른 버전의 ROS2와 호환성을 계속 유지하려면, 그에 맞춘 테스트 파이프라인도 운영해야 한다.

#### pyproject.toml 기반 멀티 플랫폼 빌드

ROS2 Humble 기반 패키지를 여러 플랫폼(리눅스, 윈도우, 맥 등)으로 동시에 빌드하여 PyPI에 업로드하려면, GitHub Actions와 같은 CI 환경에서 OS별로 워크플로를 구성하면 된다. 예:

```yaml
name: Build and Upload for Multiple Platforms

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-upload:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: [3.8, 3.9]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v3
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install build tools
        run: |
          python -m pip install --upgrade pip build twine
      - name: Build
        run: |
          python -m build
      - name: Upload to PyPI
        if: github.ref == 'refs/heads/main'
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
        run: |
          python -m twine upload dist/*
```

이런 멀티 플랫폼 빌드 설정을 통해, 각 플랫폼별로 빌드된 `.whl` 파일이 PyPI에 업로드되어 사용자들이 손쉽게 설치할 수 있게 된다.
