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의 차이
| 대상 | 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를 안정적으로 빌드하고 실행할 수 있습니다.
'강좌 > ROS2' 카테고리의 다른 글
| ROS 2 C++ Action Server / Client 실습 #1 (0) | 2026.05.24 |
|---|---|
| ROS 2 에서 CMakeLists.txt 이해하기 (0) | 2026.05.24 |
| ROS 2 package.xml 이해하기 : Python 패키지와 C++ 패키지 기준 (0) | 2026.05.24 |
| C++ ROS 2 패키지 생성하기 (0) | 2026.05.24 |
| ROS 2 개발자를 위한 C/C++ 기초강의 #2 (0) | 2026.05.24 |