Package Development: Creating ROS 2 Packages
Introduction
In ROS 2, a package is the fundamental unit of organization. Think of it as a folder that contains related code, configuration files, and metadata. Whether you're building a simple sensor driver or a complex navigation system, everything lives inside packages.
In this section, you'll learn how to create Python-based ROS 2 packages, understand their structure, configure dependencies, and build them using the colcon build system.
What is a ROS 2 Package?
A ROS 2 package is a directory containing:
- Source code (Python scripts, C++ files)
- Configuration files (package.xml, setup.py/CMakeLists.txt)
- Launch files (for starting multiple nodes)
- Data files (URDF, configuration YAML files)
- Tests (unit tests, integration tests)
Key Characteristics:
- Self-Contained: Each package has its own dependencies and metadata
- Reusable: Packages can be shared across projects
- Buildable: The build system (colcon) knows how to compile and install them
- Versionable: Can specify version numbers and maintain compatibility
Package Types
ROS 2 supports three main package types:
- ament_python: Python-only packages (what we'll use)
- ament_cmake: C++ packages or mixed Python/C++
- ament_cmake_python: CMake-based Python packages (less common)
For this course, we'll focus on ament_python packages since we're working exclusively with Python.
Creating Your First Package
Step 1: Navigate to Your Workspace
First, ensure you're in the src directory of your ROS 2 workspace:
cd ~/ros2_ws/src
Workspace Structure:
ros2_ws/
├── src/ # Source code goes here
│ └── my_package/ # Your packages
├── build/ # Build artifacts (auto-generated)
├── install/ # Installed files (auto-generated)
└── log/ # Build logs (auto-generated)
Step 2: Create the Package
Use the ros2 pkg create command:
ros2 pkg create --build-type ament_python --node-name my_first_node my_robot_pkg
Command Breakdown:
ros2 pkg create: Package creation tool--build-type ament_python: Specifies Python package--node-name my_first_node: Creates a sample Python node (optional)my_robot_pkg: Package name (must be unique in your workspace)
Output:
going to create a new package
package name: my_robot_pkg
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.com>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
node_name: my_first_node
creating folder ./my_robot_pkg
creating ./my_robot_pkg/package.xml
creating source folder
creating folder ./my_robot_pkg/my_robot_pkg
creating ./my_robot_pkg/setup.py
creating ./my_robot_pkg/setup.cfg
creating folder ./my_robot_pkg/resource
creating ./my_robot_pkg/resource/my_robot_pkg
creating ./my_robot_pkg/my_robot_pkg/__init__.py
creating folder ./my_robot_pkg/test
creating ./my_robot_pkg/test/test_copyright.py
creating ./my_robot_pkg/test/test_flake8.py
creating ./my_robot_pkg/test/test_pep257.py
creating ./my_robot_pkg/my_robot_pkg/my_first_node.py
Package Structure Explained
After creation, your package has this structure:
my_robot_pkg/
├── my_robot_pkg/ # Python module (same name as package)
│ ├── __init__.py # Makes this a Python module
│ └── my_first_node.py # Sample node
├── resource/ # Package resources
│ └── my_robot_pkg # Marker file for package discovery
├── test/ # Unit tests
│ ├── test_copyright.py
│ ├── test_flake8.py
│ └── test_pep257.py
├── package.xml # Package metadata and dependencies
├── setup.py # Python package configuration
└── setup.cfg # Configuration for setup.py
Key Files
1. package.xml
This XML file contains package metadata:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>my_robot_pkg</name>
<version>0.0.1</version>
<description>My first ROS 2 package</description>
<maintainer email="your.email@example.com">Your Name</maintainer>
<license>Apache-2.0</license>
<!-- Build tool dependency -->
<buildtool_depend>ament_python</buildtool_depend>
<!-- Runtime dependencies -->
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<!-- Test dependencies -->
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
Key Elements:
<name>: Package identifier<version>: Semantic versioning (major.minor.patch)<description>: Human-readable description<maintainer>: Who maintains this package<license>: Open-source license (Apache-2.0, MIT, BSD, etc.)<buildtool_depend>: Build system dependency<exec_depend>: Runtime dependencies (rclpy, message packages)<test_depend>: Testing dependencies
2. setup.py
This file configures the Python package:
from setuptools import setup
package_name = 'my_robot_pkg'
setup(
name=package_name,
version='0.0.1',
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='Your Name',
maintainer_email='your.email@example.com',
description='My first ROS 2 package',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'my_first_node = my_robot_pkg.my_first_node:main',
],
},
)
Key Sections:
packages: List of Python modules to installdata_files: Non-Python files (package.xml, launch files, URDF)entry_points: Executable scripts (howros2 runfinds your nodes)
Entry Points Syntax:
'executable_name = package_name.module_name:function_name'
Example: 'my_first_node = my_robot_pkg.my_first_node:main'
- Run with:
ros2 run my_robot_pkg my_first_node
Adding Dependencies
When your package uses other ROS 2 packages or Python libraries, declare them in package.xml:
Example: Adding geometry_msgs Dependency
<exec_depend>geometry_msgs</exec_depend>
Common Dependencies
<!-- Core ROS 2 Python library -->
<exec_depend>rclpy</exec_depend>
<!-- Standard message types -->
<exec_depend>std_msgs</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<!-- TF2 (coordinate transformations) -->
<exec_depend>tf2_ros</exec_depend>
<exec_depend>tf2_geometry_msgs</exec_depend>
Building Your Package
Step 1: Navigate to Workspace Root
cd ~/ros2_ws
Step 2: Build with colcon
colcon build --packages-select my_robot_pkg
Build Options:
--packages-select my_robot_pkg: Build only this package (faster)--symlink-install: Symlink Python files instead of copying (useful during development)--parallel-workers 4: Use 4 parallel jobs (faster on multi-core systems)
Development Tip: Use --symlink-install for faster iteration:
colcon build --packages-select my_robot_pkg --symlink-install
With symlinks, you don't need to rebuild after modifying Python code—just re-source your workspace.
Step 3: Source the Workspace
After building, source the setup file to add your package to the ROS 2 environment:
source install/setup.bash
Important: You must source setup.bash in every new terminal, or add it to your ~/.bashrc:
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
Running Your Node
ros2 run my_robot_pkg my_first_node
Verify It's Running:
# In another terminal
ros2 node list
You should see /my_first_node in the output.
Common Package Development Workflow
-
Create package structure:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python my_package -
Add Python node files to
my_package/my_package/directory -
Update setup.py with entry points:
entry_points={
'console_scripts': [
'my_node = my_package.my_node:main',
],
}, -
Add dependencies to
package.xml -
Build the package:
cd ~/ros2_ws
colcon build --packages-select my_package --symlink-install -
Source the workspace:
source install/setup.bash -
Run your node:
ros2 run my_package my_node -
Iterate: Edit Python code, re-source workspace, re-run (no rebuild needed with --symlink-install)
Best Practices
-
Naming Conventions:
- Package names:
snake_case(e.g.,my_robot_controller) - Node names:
snake_case(e.g.,motor_controller.py) - Executable names: match node names (e.g.,
motor_controller)
- Package names:
-
One Package Per Functionality:
my_robot_description: URDF filesmy_robot_control: Control nodesmy_robot_vision: Computer vision nodes
-
Version Control:
- Add
build/,install/, andlog/to.gitignore - Only commit
src/directory
- Add
-
Documentation:
- Update
<description>in package.xml - Add README.md explaining what the package does
- Update
-
Testing:
- Keep test files in the
test/directory - Run tests with:
colcon test --packages-select my_package
- Keep test files in the
Summary
Creating ROS 2 packages involves:
- Structure: Packages are folders with Python modules, metadata (package.xml), and configuration (setup.py)
- Creation: Use
ros2 pkg createwith--build-type ament_python - Dependencies: Declare in
<exec_depend>tags in package.xml - Entry Points: Register executables in setup.py for
ros2 run - Building: Use
colcon buildto compile and install packages - Sourcing: Run
source install/setup.bashto use the package - Workflow: Create → Add code → Update metadata → Build → Source → Run
In the next section, we'll write actual ROS 2 nodes inside these packages using the rclpy library.
Review Questions
-
What are the three main files in a ROS 2 Python package?
Details
Answer
package.xml (metadata and dependencies), setup.py (Python package configuration), and setup.cfg (setup.py configuration). -
What is the purpose of the
entry_pointssection in setup.py?Details
Answer
It registers executable scripts so they can be run withros2 run. Each entry point maps an executable name to a Python function (typicallymain()). -
Why should you use
--symlink-installduring development?Details
Answer
It creates symlinks to Python files instead of copying them, so you don't need to rebuild after modifying Python code—just re-source the workspace. -
Where do you declare runtime dependencies like rclpy or geometry_msgs?
Details
Answer
In the package.xml file, using<exec_depend>tags. Example:<exec_depend>rclpy</exec_depend>. -
What command builds only a specific package named
my_package?Details
Answer
colcon build --packages-select my_package
Next: Python Nodes - Learn how to write ROS 2 nodes using rclpy