Lesson 4.1: Workspaces and Packages
Welcome to your first hands-on ROS 2 experience! Until now, you've explored ROS 2 through the command line — running existing nodes, examining the node graph, calling services. Now it's time to create your own code and organize it properly.
Before you can write a ROS 2 node, you need to understand how ROS 2 projects are structured. Think of a ROS 2 workspace as a folder that contains all your source code, build artifacts, and compiled binaries. Packages within that workspace are individual projects — each one a self-contained module with its own code, configuration, and metadata.
In this lesson, you'll create your first workspace from scratch, understand what lives inside a package, and learn how to build your code with colcon.
Understanding Workspaces
A ROS 2 workspace is simply a directory that follows a specific structure. When you build a workspace, ROS 2 tools know how to find your code, compile it, and set up your environment so you can run it.
Typical Workspace Structure
When you create a workspace, it has three main directories:
ros2_ws/
├── src/ # Your source code lives here
│ └── my_first_package/
│ ├── my_first_package/
│ ├── setup.py
│ ├── setup.cfg
│ ├── package.xml
│ └── resource/
├── build/ # Build artifacts (created by colcon)
├── install/ # Compiled binaries and scripts
└── log/ # Build logs
The critical directories:
- src/: Where you write code. This is the only directory you typically edit by hand.
- build/: Temporary build files. You can safely delete this — colcon will recreate it.
- install/: The compiled output. This is what you actually run.
Why This Structure?
This three-directory approach separates concerns:
- src/ = "what I wrote"
- build/ = "temporary files while compiling"
- install/ = "what I can run"
This means you can delete build/ and install/ without losing any work. It also means you can have multiple workspaces on the same machine, each with their own build and install directories.
Creating Your First Workspace
Let's create a workspace. You'll do this once, then add packages inside it repeatedly.
Step 1: Create the Workspace Structure
Open a terminal and run:
# Create the workspace directory
mkdir -p ~/ros2_ws/src
# Navigate into it
cd ~/ros2_ws
That's it. You've created a workspace. ROS 2 will automatically recognize this folder when you build packages inside src/.
If you're using cloud ROS 2 (TheConstruct):
- Open your ROS 2 Humble workspace terminal
- Run the same commands above
Step 2: Verify the Environment
Before you build anything, verify that ROS 2 is properly installed:
# Check that ros2 command is available
echo $ROS_DISTRO
Expected output: humble
If you get an error, you may need to source the ROS 2 setup script:
# For local installation
source /opt/ros/humble/setup.bash
# Verify again
echo $ROS_DISTRO
Step 3: Build Your Workspace (Even Though It's Empty)
Navigate to your workspace and build it:
cd ~/ros2_ws
colcon build
This creates the build/ and install/ directories. Expected output:
Starting >>> ros2_ws
Finished <<< ros2_ws [0.05s]
Summary: 0 packages processed
You haven't added any packages yet, so it says "0 packages processed." That's normal.
Creating Your First Package
Now let's create a package inside your workspace. A package is ROS 2's unit of organization — one folder containing code, dependencies, and metadata.
Creating a Package
Use the ros2 pkg create command:
cd ~/ros2_ws
ros2 pkg create my_first_package --build-type ament_python
This creates a package named "my_first_package" using Python. Let's break down what just happened:
Directory created:
src/
└── my_first_package/
├── my_first_package/ # Python package directory
│ └── __init__.py
├── test/ # For unit tests (we'll skip for now)
├── setup.py # Python package metadata
├── setup.cfg # Python package config
├── package.xml # ROS 2 package metadata
└── resource/
└── my_first_package
The important file is package.xml — it tells ROS 2 what this package is and what it depends on.
Understanding package.xml
Let's look at what was created:
<?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_first_package</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="you@example.com">you</maintainer>
<license>TODO</license>
<depend>rclpy</depend>
<depend>std_msgs</depend>
<test_depend>ament_cmake_pytest</test_depend>
<exec_depend>ros2launch</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
What each section means:
| Section | Purpose |
|---|---|
<name> | Package identifier (used in imports and commands) |
<version> | Semantic version (0.0.0 is typical for new packages) |
<description> | Human-readable description |
<maintainer> | Who maintains this package |
<license> | License type (MIT, Apache-2.0, etc.) |
<depend> | Runtime dependencies (code you import) |
<test_depend> | Test-only dependencies |
<exec_depend> | Executable dependencies (like ros2launch) |
<build_type> | Either ament_python or ament_cmake |
The <depend> entries are important. When you write code that imports rclpy (ROS 2's Python library), you need to list rclpy as a dependency. ROS 2 uses this to:
- Check if dependencies are installed
- Build packages in the correct order (if A depends on B, build B first)
- Set up the Python import path correctly
Building Your Workspace
Now that you have a package, build the entire workspace:
cd ~/ros2_ws
colcon build
Expected output:
Starting >>> my_first_package
Compiling Cython modules...
Finished <<< my_first_package [2.34s]
Summary: 1 package processed
What happened?
- colcon found 1 package in src/ (my_first_package)
- It ran the Python build process
- It installed the package into install/
You can safely delete build/ and install/ anytime. To rebuild, just run colcon build again.
Sourcing the Workspace
Before you can run any code from this workspace, you need to source the setup script. This modifies your shell environment to find your packages:
source ~/ros2_ws/install/setup.bash
Now your shell knows about everything in install/. If you write a Python script or launch file, ROS 2 will find it.
Important: You must source the workspace BEFORE running your code. If you open a new terminal, you need to source again.
Verify Sourcing Worked
echo $ROS_PACKAGE_PATH
You should see a path that includes ~/ros2_ws/install. That confirms the workspace is in your search path.
Workflow: Create → Build → Source → Run
This is the workflow you'll repeat whenever you add code:
1. Create/edit files in src/
└── Edit Python code, package.xml, etc.
2. Build the workspace
└── cd ~/ros2_ws && colcon build
3. Source the workspace
└── source ~/ros2_ws/install/setup.bash
4. Run your node
└── ros2 run my_first_package my_node_name
Common Troubleshooting
Error: "colcon: command not found"
- You need to install colcon:
pip install colcon-common-extensions - Or use cloud ROS 2 (TheConstruct) where it's pre-installed
Error: "package.xml not found"
- Make sure you're in the src/ directory when you run
ros2 pkg create - Package folders must be direct children of src/
Error: "No such file or directory: ./install/setup.bash"
- Make sure you ran
colcon buildFIRST - Source the correct path:
source ~/ros2_ws/install/setup.bash
Error: "colcon build" finds no packages
- Check that your package has a package.xml in the right place
- Verify folder structure:
~/ros2_ws/src/my_first_package/package.xml
Why This Structure Matters
This three-directory structure (src/, build/, install/) is used industry-wide:
- CMake projects use the same pattern
- ROS projects (both ROS 1 and ROS 2) use the same pattern
- Professional C++ projects often use the same pattern
Understanding this structure now prepares you for professional robotics development.
Try With AI
Now you have a workspace and a package. Let's explore what AI can teach you about package management.
Ask your AI:
"I have a package called 'my_first_package' with dependencies on rclpy and std_msgs. What other dependencies might I need to add as my code grows? Give me a checklist of common ROS 2 dependencies and when to use each one."
Expected outcome: AI will suggest dependencies like:
geometry_msgsfor position/velocity datasensor_msgsfor camera, LIDAR, IMU datatf2for coordinate transformations (advanced)rclcppfor C++ nodes (not Python, but good to know exists)
Notice how AI knows patterns that took humans time to discover. You didn't have to read 50 ROS 2 tutorials — AI suggested the key dependencies upfront.
Challenge your assumption:
"Is it better to depend on everything I might use, or add dependencies only when needed?"
Expected outcome: AI will explain the trade-off:
- Add early: Simpler dependency list, but larger build
- Add only when needed: Cleaner dependency list, but you might miss something
- Best practice: Add when needed, document why
This is the kind of reasoning that comes from experience. By asking AI, you're learning from patterns it's seen across thousands of ROS 2 projects.
Reflect
Consider these questions:
-
Why does ROS 2 use workspaces? What problems would arise if all packages were installed globally?
-
Why does colcon separate build/ and install/? What benefit does this separation provide during development?
-
What happens if you forget to source? How would you diagnose the error "package not found" after a successful build?
In the next lesson, you'll write your first ROS 2 node — a publisher that sends data to a topic.