본문으로 바로가기

ROS 2 Python 패키지의 setup.py 이해하기

category 강좌/ROS2 2026. 5. 24. 03:26

ROS 2에서 Python 패키지를 만들면 거의 항상 setup.py 파일이 함께 생성됩니다.

처음 보면 단순한 설정 파일처럼 보이지만, 실제로는 Python 코드를 ROS 2 패키지로 설치하고, ros2 run 명령으로 실행 가능하게 만드는 핵심 파일입니다.

 

실습 환경은 다음과 같습니다.

 
ROS 2 배포판: Humble
워크스페이스: ~/ros2_study
Python 패키지: my_first_package
C++ 패키지: my_cpp_package
인터페이스 패키지: my_first_package_msgs
 

 

여기서 setup.py는 Python 패키지인 my_first_package에서 사용하는 파일입니다.

 

my_cpp_package는 C++ 패키지이므로 CMakeLists.txt를 중심으로 빌드합니다.
my_first_package_msgs는 메시지, 서비스, 액션 같은 인터페이스를 정의하는 패키지입니다.

 

 

 

 

1. setup.py가 필요한 이유

ROS 2 Python 패키지에서 setup.py는  Python 패키지를 어떻게 설치하고, 어떤 실행 명령을 만들고, 어떤 파일을 ROS 2 시스템에 등록할지 알려주는 파일입니다.

 

C++ 패키지에서 CMakeLists.txt가 빌드 규칙을 담당한다면, Python 패키지에서는 setup.py가 그 역할을 합니다.

ROS 2 Humble에서 Python 패키지는 보통 ament_python 빌드 타입을 사용합니다.

 

이미 패키지를 만들어 사용 중이라면 구조는 대략 다음과 같습니다.

 
~/ros2_study/src/my_first_package/
├── package.xml
├── resource/
│   └── my_first_package
├── setup.cfg
├── setup.py
├── test/
│   ├── test_copyright.py
│   ├── test_flake8.py
│   └── test_pep257.py
└── my_first_package/
    └── __init__.py
 

 

여기서 중요한 파일은 다음과 같습니다.

                                         파일                                                                       역할
package.xml ROS 2 패키지 정보와 의존성 선언
setup.py Python 패키지 설치 규칙
setup.cfg 실행 스크립트 설치 위치 설정
resource/my_first_package ROS 2가 패키지를 찾기 위한 마커 파일
my_first_package/__init__.py Python 패키지 디렉터리 표시

 

 

 

 

2. 기본 setup.py 구조

my_first_package의 기본적인 setup.py는 보통 다음과 비슷합니다.

 
from setuptools import setup

package_name = 'my_first_package'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        (
            'share/ament_index/resource_index/packages',
            ['resource/' + package_name]
        ),
        (
            'share/' + package_name,
            ['package.xml']
        ),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dragon',
    maintainer_email='dragon@example.com',
    description='ROS 2 Humble Python package example',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        ],
    },
)
 

 

이제 각 항목을 하나씩 보겠습니다.

 

 

 

 

3. from setuptools import setup

 
from setuptools import setup
 

 

setuptools는 Python 패키지를 설치하기 위한 도구입니다.

 

ROS 2의 ament_python 패키지는 내부적으로 Python 표준 패키징 방식을 사용합니다.

 

흐름은 다음과 같습니다.

Python 코드 작성
↓
setup.py로 설치 규칙 정의
↓
colcon build 실행
↓
install 디렉터리에 패키지 설치
↓
ros2 run으로 노드 실행
 

 

즉, setup.py는 ROS 2 Python 패키지가 빌드되고 실행되기 위한 설치 설명서입니다.

 

 

 

 

4. package_name

 
package_name = 'my_first_package'
 

 

패키지 이름을 변수로 저장합니다.

 

이 이름은 보통 다음 항목들과 같아야 합니다.

ROS 2 패키지 폴더 이름: my_first_package
Python 모듈 폴더 이름: my_first_package
package.xml의 <name>: my_first_package
setup.py의 package_name: my_first_package
 

 

정상적인 구조는 다음과 같습니다.

 
my_first_package/
├── package.xml
├── setup.py
└── my_first_package/
    ├── __init__.py
    └── my_node.py
 

 

이름이 서로 다르면 빌드는 되더라도 ros2 run이나 Python import에서 문제가 생길 수 있습니다.

 

 

 

5. setup() 함수

 
setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[...],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dragon',
    maintainer_email='dragon@example.com',
    description='...',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={...},
)
 

 

setup() 함수 안에는 이 Python 패키지를 어떻게 설치할지에 대한 정보가 들어갑니다.

 

ROS 2 Python 패키지에서 특히 중요한 항목은 다음 3개입니다.

 
packages=[package_name]
 
 

 

data_files=[...]
 
 
entry_points={
    'console_scripts': [
    ],
}
 

 

 

 

6. name

 
name=package_name,
 

 

설치되는 Python 패키지의 이름입니다.

 

현재 실습에서는 다음과 같습니다.

 
name='my_first_package'
 

 

보통 package.xml의 패키지 이름과 동일하게 둡니다.

 

 

 

7. version

 
version='0.0.0',
 

 

패키지 버전입니다.

처음 실습용 패키지는 보통 0.0.0으로 시작합니다.

 

예를 들어 기능이 어느 정도 추가되면 다음처럼 바꿀 수 있습니다.

 
version='0.1.0',
 

 

일반적으로는 다음 흐름으로 관리합니다.

0.0.0  초기 생성
0.1.0  기능 추가
1.0.0  안정 버전
 
 
 
 

8. packages

 
packages=[package_name],
 

 

설치할 Python 패키지 폴더를 지정합니다.

 

현재 패키지 구조가 다음과 같다고 하겠습니다.

 
my_first_package/
├── setup.py
└── my_first_package/
    ├── __init__.py
    ├── simple_talker.py
    └── simple_listener.py
 

 

이때:

 
packages=[package_name]
 

 

는 내부의 my_first_package/ Python 모듈 폴더를 설치하겠다는 뜻입니다.

 

설치 후에는 다음처럼 Python 코드에서 import할 수 있습니다.

 
from my_first_package.simple_talker import main
 

 

초급 실습에서는 packages=[package_name] 방식이 가장 이해하기 쉽습니다.

 

패키지 내부에 하위 폴더가 많아지면 다음과 같이 find_packages()를 사용할 수도 있습니다.

 
from setuptools import setup, find_packages

setup(
    packages=find_packages(),
)
 

 

하지만 처음 ROS 2 Humble을 배우는 단계에서는 packages=[package_name]으로 시작하는 것이 좋습니다.

find_packages()는 현재 폴더 아래에서 __init__.py가 있는 Python 패키지 폴더를 자동으로 찾아줍니다. 단 test 패키지는 제외됩니다.

 

 

9. data_files

ROS 2 Python 패키지에서 가장 헷갈리는 부분이 data_files입니다.

 
data_files=[
    (
        'share/ament_index/resource_index/packages',
        ['resource/' + package_name]
    ),
    (
        'share/' + package_name,
        ['package.xml']
    ),
],
 

 

이 부분은 Python 코드가 아닌 파일을 어디에 설치할지 정합니다.

 

 

 

 

10. ROS 2 패키지 마커 파일 설치

 
(
    'share/ament_index/resource_index/packages',
    ['resource/' + package_name]
),
 

 

이 코드는 다음 파일을 설치합니다.

 
resource/my_first_package
 

 

설치 후에는 대략 다음 위치로 들어갑니다.

 
~/ros2_study/install/my_first_package/share/ament_index/resource_index/packages/my_first_package
 

 

이 파일은 내용이 중요하지 않습니다.
중요한 것은 파일이 존재한다는 사실입니다. my_first_package 파일은 비어 있습니다.

 

ROS 2는 이 마커 파일을 통해 다음을 판단합니다.

my_first_package라는 ROS 2 패키지가 설치되어 있다.
 

 

이 항목이 빠지면 빌드는 되더라도 다음 명령에서 패키지를 못 찾을 수 있습니다.

 
ros2 pkg list
ros2 run my_first_package 실행파일이름
 

 

 

 

11. package.xml 설치

 
(
    'share/' + package_name,
    ['package.xml']
),
 

 

이 코드는 package.xml을 설치합니다.

 

설치 후 위치는 대략 다음과 같습니다.

 
~/ros2_study/install/my_first_package/share/my_first_package/package.xml
 

 

package.xml은 ROS 2 패키지의 이름, 버전, 설명, 의존성 정보를 담고 있습니다.

따라서 setup.py의 data_files에 반드시 포함되어야 합니다.

 

 

 

 

12. install_requires

 
install_requires=['setuptools'],
 

 

Python 패키지 설치 시 필요한 Python 의존성을 적는 곳입니다.

기본적으로는 setuptools가 들어갑니다.

 

예를 들어 my_first_package 안의 Python 노드에서 numpy를 사용한다면 다음처럼 적을 수 있습니다.

 
install_requires=[
    'setuptools',
    'numpy',
],
 

 

다만 ROS 2에서는 의존성을 두 곳에서 관리합니다.

                                                   위치                                                                                         역할
package.xml ROS 2 패키지 의존성
setup.py의 install_requires Python 패키지 의존성

 

예를 들어 rclpy는 ROS 2 패키지 의존성이므로 보통 package.xml에 적습니다.

 
<depend>rclpy</depend>
 

 

만약 인터페이스 패키지인 my_first_package_msgs의 메시지를 Python 노드에서 사용한다면 package.xml에 의존성을 추가해야 합니다.

 
<depend>my_first_package_msgs</depend>
 

 

setup.py의 install_requires에 무조건 넣는 것이 아니라, ROS 2 패키지 의존성은 기본적으로 package.xml에서 관리하는 것이 정석입니다.

 

 

 

 

13. zip_safe

 
zip_safe=True,
 

 

Python 패키지를 zip 형태로 설치해도 안전한지 나타내는 옵션입니다.

순수 Python 코드만 있다면 True여도 큰 문제는 없습니다.

하지만 실무에서는 launch 파일, config 파일, yaml 파일 등을 패키지 경로에서 직접 읽는 경우가 많습니다.

 

그런 경우에는 다음처럼 두는 것이 더 안전합니다.

 
zip_safe=False,
 
 

실습 초반에는 기본값으로 둬도 되지만, launch/config 파일까지 설치하는 패키지라면 False를 권장합니다.

 

 

 

 

14. maintainer, maintainer_email

 
maintainer='dragon',
maintainer_email='dragon@example.com',
 

 

패키지 유지보수자 정보입니다.

개인 실습이면 임의로 둬도 됩니다.

하지만 공개 저장소나 협업 프로젝트에서는 실제 담당자 정보를 적는 것이 좋습니다.

 

 

 

 

15. description

 
description='ROS 2 Humble Python package example',
 

 

패키지 설명입니다.

 

나쁜 예:

 
description='test'
 

 

좋은 예:

 
description='ROS 2 Humble Python package for publisher and subscriber practice.'
 

 

현재 실습 패키지라면 다음처럼 쓸 수 있습니다.

 
description='ROS 2 Humble Python practice package in ros2_study workspace.'
 

 

 

 

 

 

16. license

 
license='Apache-2.0',
 

 

패키지 라이선스입니다.

ROS 2 패키지에서는 Apache-2.0을 많이 사용합니다.

개인 실습용이면 그대로 둬도 됩니다.

 

다만 회사 프로젝트나 상용 프로젝트에서는 라이선스를 함부로 정하면 안 됩니다.
회사 정책이나 프로젝트 정책에 맞춰야 합니다.

 

 

 

 

17. tests_require

 
tests_require=['pytest'],
 

 

테스트 실행에 필요한 Python 패키지를 적는 부분입니다.

 

ROS 2 Python 패키지를 만들면 보통 test/ 폴더가 같이 생성됩니다.

 
test/
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
 

 

테스트는 다음 명령으로 실행할 수 있습니다.

 
cd ~/ros2_study
colcon test --packages-select my_first_package
colcon test-result --verbose
 

초급 단계에서는 깊게 다루지 않아도 되지만, 나중에 품질 관리나 CI/CD로 넘어가면 중요해집니다.

 

 

 

 

18. entry_points

ROS 2 Python 패키지에서 가장 중요한 부분입니다.

 
entry_points={
    'console_scripts': [
    ],
},
 

 

여기에 실행 파일을 등록해야 ros2 run 명령으로 Python 노드를 실행할 수 있습니다.

 

예를 들어 my_first_package/simple_talker.py 파일이 있고, 그 안에 main() 함수가 있다면 다음처럼 등록합니다.

 
entry_points={
    'console_scripts': [
        'simple_talker = my_first_package.simple_talker:main',
    ],
},
 

 

이제 빌드 후 다음 명령으로 실행할 수 있습니다.

 
ros2 run my_first_package simple_talker
 
 
 
 
 

19. console_scripts 구조 이해

다음 한 줄을 분해해 보겠습니다.

 
'simple_talker = my_first_package.simple_talker:main'
 
                            부분                                                           의미
simple_talker 터미널에서 실행할 이름
my_first_package Python 패키지 폴더 이름
simple_talker Python 파일 이름, 즉 simple_talker.py
main 실행할 함수 이름

 

즉, 다음 명령을 실행하면:

 
ros2 run my_first_package simple_talker
 

 

ROS 2는 내부적으로 다음 함수를 실행합니다.

 
my_first_package.simple_talker.main()
 

 

따라서 simple_talker.py 안에는 반드시 다음 함수가 있어야 합니다.

 
def main(args=None):
    ...
 

 

 

 

20. my_first_package용 setup.py 예시

아래는 ros2_study 워크스페이스에서 사용하는 my_first_package용 기본 예시입니다.

 
from setuptools import setup

package_name = 'my_first_package'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        (
            'share/ament_index/resource_index/packages',
            ['resource/' + package_name]
        ),
        (
            'share/' + package_name,
            ['package.xml']
        ),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dragon',
    maintainer_email='dragon@example.com',
    description='ROS 2 Humble Python practice package in ros2_study workspace.',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'simple_talker = my_first_package.simple_talker:main',
            'simple_listener = my_first_package.simple_listener:main',
        ],
    },
)
 

 

이 설정이 있으면 다음 명령이 가능해집니다.

 
cd ~/ros2_study
colcon build --packages-select my_first_package
source install/setup.bash
ros2 run my_first_package simple_talker
 

 

또 다른 터미널에서는 다음처럼 실행합니다.

 
source ~/ros2_study/install/setup.bash
ros2 run my_first_package simple_listener
 

 

 

 

21. 인터페이스 패키지 my_first_package_msgs를 사용할 때

현재 실습 환경에는 인터페이스 패키지로 my_first_package_msgs가 있습니다.

 

Python 패키지 my_first_package에서 이 메시지를 사용한다면 Python 코드에서는 예를 들어 다음처럼 import할 수 있습니다.

 
from my_first_package_msgs.msg import MyMessage
 

 

이 경우 중요한 것은 setup.py보다 package.xml입니다.

 

my_first_package/package.xml에 다음 의존성이 있어야 합니다.

 
<depend>my_first_package_msgs</depend>
 

 

그리고 빌드할 때는 인터페이스 패키지가 먼저 빌드되어야 합니다.

 
cd ~/ros2_study
colcon build --packages-select my_first_package_msgs my_first_package
source install/setup.bash
 

 

정리하면 다음과 같습니다.

setup.py:
Python 패키지 설치와 실행 명령 등록 담당

package.xml:
rclpy, std_msgs, my_first_package_msgs 같은 ROS 2 의존성 선언 담당
 

 

 

 

 

22. launch/config 파일이 있을 때의 setup.py

실무에서는 Python 노드만 있는 경우보다 launch 파일이나 config 파일을 함께 사용하는 경우가 많습니다.

 

예를 들어 구조가 다음과 같다고 하겠습니다.

 
my_first_package/
├── launch/
│   └── simple_demo.launch.py
├── config/
│   └── params.yaml
├── my_first_package/
│   ├── __init__.py
│   ├── simple_talker.py
│   └── simple_listener.py
├── package.xml
├── setup.cfg
└── setup.py
 

 

이 경우 setup.py에서 launch/config 파일도 설치해줘야 합니다.

 
import os
from glob import glob
from setuptools import setup

package_name = 'my_first_package'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        (
            'share/ament_index/resource_index/packages',
            ['resource/' + package_name]
        ),
        (
            'share/' + package_name,
            ['package.xml']
        ),
        (
            os.path.join('share', package_name, 'launch'),
            glob('launch/*.launch.py')
        ),
        (
            os.path.join('share', package_name, 'config'),
            glob('config/*.yaml')
        ),
    ],
    install_requires=['setuptools'],
    zip_safe=False,
    maintainer='dragon',
    maintainer_email='dragon@example.com',
    description='ROS 2 Humble Python package with launch and config files.',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'simple_talker = my_first_package.simple_talker:main',
            'simple_listener = my_first_package.simple_listener:main',
        ],
    },
)
 

 

여기서 추가된 부분은 다음입니다.

 
import os
from glob import glob
 

 

그리고 data_files에 다음이 추가되었습니다.

 
(
    os.path.join('share', package_name, 'launch'),
    glob('launch/*.launch.py')
),
(
    os.path.join('share', package_name, 'config'),
    glob('config/*.yaml')
),
 

 

이 설정이 있어야 빌드 후 launch/config 파일이 install 폴더로 복사됩니다.

 

확인은 다음처럼 할 수 있습니다.

 
ls ~/ros2_study/install/my_first_package/share/my_first_package/launch
ls ~/ros2_study/install/my_first_package/share/my_first_package/config
 

 

 

 

 

23. 자주 발생하는 에러

1) 에러 1: ros2 run에서 실행 파일을 못 찾음

 
No executable found
 

 

확인할 것:

 
entry_points={
    'console_scripts': [
        'simple_talker = my_first_package.simple_talker:main',
    ],
},
 

 

그리고 다시 빌드와 source를 해야 합니다.

 
cd ~/ros2_study
colcon build --packages-select my_first_package
source install/setup.bash
 
 
 
 

2) 에러 2: 패키지를 못 찾음

 
Package 'my_first_package' not found
 

 

확인할 것:

 
source ~/ros2_study/install/setup.bash
ros2 pkg list | grep my_first_package
 

 

그래도 나오지 않으면 setup.py의 data_files에 다음 항목이 있는지 확인합니다.

 
(
    'share/ament_index/resource_index/packages',
    ['resource/' + package_name]
),
 

 

그리고 실제 파일도 확인합니다.

 
ls ~/ros2_study/src/my_first_package/resource/
 

 

다음 파일이 있어야 합니다.

 
my_first_package
 

 

 

 

3) 에러 3: Python 모듈 import 실패

 
ModuleNotFoundError: No module named 'my_first_package'
 

 

확인할 것:

 
ls ~/ros2_study/src/my_first_package/my_first_package/
 

 

다음 파일이 있어야 합니다.

 
__init__.py
 

 

그리고 setup.py에는 다음이 있어야 합니다.

 
packages=[package_name],
 

 

 

 

4) 에러 4: main 함수를 못 찾음

 
AttributeError: module 'my_first_package.simple_talker' has no attribute 'main'
 

 

entry_points가 다음처럼 되어 있다면:

 
'simple_talker = my_first_package.simple_talker:main'
 

 

simple_talker.py 안에는 반드시 다음 함수가 있어야 합니다.

 
def main(args=None):
    ...
 
 
 
 

5) 에러 5: launch 파일을 못 찾음

 
file 'simple_demo.launch.py' was not found in the share directory
 

 

확인할 것:

 
(
    os.path.join('share', package_name, 'launch'),
    glob('launch/*.launch.py')
),
 

 

빌드 후 설치 위치도 확인합니다.

 
ls ~/ros2_study/install/my_first_package/share/my_first_package/launch
 
 
 
 
 

24. setup.py와 package.xml의 차이

                    구분                                                   setup.py                                                             package.xml
대상 Python 패키징 ROS 2 패키지 메타정보
빌드 타입 setuptools 기반 ament/colcon 기반
실행 파일 등록 entry_points에서 담당 직접 담당하지 않음
Python 코드 설치 담당 담당하지 않음
ROS 2 의존성 선언 일부 가능하지만 주 역할 아님 주로 담당
예시 packages, data_files, console_scripts <depend>rclpy</depend>

 

정리하면 다음과 같습니다.

package.xml:
ROS 2 입장에서 이 패키지가 무엇이고, 어떤 의존성이 필요한지 설명한다.

setup.py:
Python 입장에서 이 패키지를 어떻게 설치하고 실행할지 설명한다.
 

 

둘 다 필요합니다.

 

 

 

 

25. setup.cfg 설명

ROS 2 Python 패키지에는 setup.py와 함께 setup.cfg 파일도 있습니다.

 

보통 다음과 비슷하게 생겼습니다.

 
[develop]
script_dir=$base/lib/my_first_package

[install]
install_scripts=$base/lib/my_first_package
 

 

이 파일은 Python 실행 스크립트가 설치될 위치를 지정합니다.

 

핵심은 다음 부분입니다.

 
install_scripts=$base/lib/my_first_package
 

 

ROS 2의 ros2 run은 Python 실행 파일을 보통 다음 위치에서 찾습니다.

 
~/ros2_study/install/my_first_package/lib/my_first_package/
 

 

따라서 setup.cfg가 잘못되어 있으면 setup.py의 entry_points를 제대로 작성해도 ros2 run에서 실행 파일을 못 찾을 수 있습니다.

 

현재 패키지 이름이 my_first_package라면 setup.cfg는 다음처럼 되어 있어야 합니다.

 
[develop]
script_dir=$base/lib/my_first_package

[install]
install_scripts=$base/lib/my_first_package
 

 

여기서 패키지 이름을 잘못 쓰면 문제가 생깁니다.

 

나쁜 예:

 
[develop]
script_dir=$base/lib/my_robot_pkg

[install]
install_scripts=$base/lib/my_robot_pkg
 

 

현재 실습 패키지는 my_first_package이므로 반드시 다음처럼 맞춰야 합니다.

 
[develop]
script_dir=$base/lib/my_first_package

[install]
install_scripts=$base/lib/my_first_package
 
 
 
 
 

26. 정리

my_first_package의 setup.py에서 반드시 기억할 부분은 3개입니다.

 

첫째, Python 패키지를 설치합니다.

 
packages=[package_name],
 

 

둘째, ROS 2 패키지로 등록합니다.

 
data_files=[
    (
        'share/ament_index/resource_index/packages',
        ['resource/' + package_name]
    ),
    (
        'share/' + package_name,
        ['package.xml']
    ),
],
 

 

셋째, ros2 run 실행 명령을 만듭니다.

 
entry_points={
    'console_scripts': [
        'simple_talker = my_first_package.simple_talker:main',
    ],
},
 

 

마지막으로 setup.cfg는 실행 스크립트가 설치될 위치를 정합니다.

 
[develop]
script_dir=$base/lib/my_first_package

[install]
install_scripts=$base/lib/my_first_package
 

 

즉, ROS 2 Humble의 Python 패키지에서:

setup.py는 설치 규칙과 실행 명령을 만든다.
setup.cfg는 실행 파일이 설치될 위치를 맞춘다.
package.xml은 ROS 2 의존성과 패키지 정보를 관리한다.
 

 

이 3개가 맞아야 my_first_package를 안정적으로 빌드하고 실행할 수 있습니다.

728x90
728x90