Skip to content

Commit 954e372

Browse files
Module 6 docs (#14)
* First draft of the guide Signed-off-by: JesusSilvaUtrera <jesus.silva@ekumenlabs.com> Add CI files and refined structure * attempt to fix ci workflow * add back git credentials * another attempt to solve credentials * remove commented modules in ci action * Address comments from PR * Refine the guide * Add checkout step to specific CI * add copyright to tool * improve github actions note --------- Co-authored-by: JesusSilvaUtrera <jesus.silva@ekumenlabs.com> Co-authored-by: Xavier Ruiz <xavier.ruiz@ekumenlabs.com>
1 parent c5f37bb commit 954e372

4 files changed

Lines changed: 250 additions & 0 deletions

File tree

.github/workflows/complete-ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: ROS 2 complete CI
2+
3+
# Controls when the workflow will run
4+
on:
5+
pull_request: # Run on all pull requests
6+
push:
7+
branches: [main] # Also run on pushes to the main branch
8+
9+
jobs:
10+
build_and_test_ros2:
11+
runs-on: ubuntu-latest
12+
container:
13+
image: rostooling/setup-ros-docker:ubuntu-noble-latest
14+
steps:
15+
- name: Build and run tests
16+
uses: ros-tooling/action-ros-ci@v0.4
17+
with:
18+
package-name: |
19+
module_1
20+
module_2
21+
target-ros2-distro: jazzy
22+
import-token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/specific-ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: ROS 2 specific CI
2+
3+
# Controls when the workflow will run
4+
on:
5+
pull_request: # Run on all pull requests
6+
push:
7+
branches: [main] # Also run on pushes to the main branch
8+
9+
jobs:
10+
build_and_test:
11+
name: Build and Test
12+
# The type of virtual machine to run the job on
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
# Set up the ROS 2 environment cleanly
22+
- name: Setup ROS 2
23+
uses: ros-tooling/setup-ros@v0.7
24+
with:
25+
required-ros-distributions: jazzy
26+
27+
# Get the list of changed files in this PR
28+
- name: Get changed files
29+
id: changed_files
30+
uses: tj-actions/changed-files@v47
31+
with:
32+
files_from_last_commit: false
33+
34+
# Map changed files to ROS packages using our script
35+
- name: Get changed ROS packages
36+
id: changed_packages
37+
run: |
38+
PKGS=$(python3 tools/get_changed_packages.py ${{ steps.changed_files.outputs.all_changed_files }})
39+
echo "Changed packages: $PKGS"
40+
echo "names=$PKGS" >> $GITHUB_OUTPUT
41+
42+
# Install dependencies, build, and test ONLY the changed packages
43+
- name: Build and Test Changed Packages
44+
if: steps.changed_packages.outputs.names != ''
45+
shell: bash
46+
run: |
47+
# Source the ROS setup script provided by the setup-ros action
48+
source /opt/ros/jazzy/setup.bash
49+
50+
# Install dependencies for the entire workspace
51+
rosdep install --from-paths src --ignore-src -y
52+
53+
# Build and test only the packages that changed
54+
colcon build --packages-select ${{ steps.changed_packages.outputs.names }} --symlink-install --event-handlers console_direct+
55+
colcon test --packages-select ${{ steps.changed_packages.outputs.names }} --event-handlers console_direct+
56+
colcon test-result --verbose

modules/module_6/README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Module 6 – Continuous Integration (CI)
2+
3+
In this final module, the focus will be on **how to automate the building, linting, and testing processes** using Continuous Integration (CI) with GitHub Actions.
4+
5+
- [Module 6 – Continuous Integration (CI)](#module-6--continuous-integration-ci)
6+
- [Objectives](#objectives)
7+
- [Motivation](#motivation)
8+
- [What is Continuous Integration?](#what-is-continuous-integration)
9+
- [Introduction to GitHub Actions](#introduction-to-github-actions)
10+
- [Anatomy of a ROS 2 Workflow File](#anatomy-of-a-ros-2-workflow-file)
11+
- [Viewing CI Results](#viewing-ci-results)
12+
- [Enforcing CI with branch protection rules](#enforcing-ci-with-branch-protection-rules)
13+
- [References](#references)
14+
15+
## Objectives
16+
17+
By the end of the module, participants will be able to:
18+
19+
- Understand the purpose and benefits of Continuous Integration.
20+
- Set up a basic CI workflow for a ROS 2 project using GitHub Actions.
21+
- Configure a workflow to automatically run `colcon build` and `colcon test` on every pull request.
22+
- Interpret the results of a CI run to approve or reject code changes.
23+
24+
## Motivation
25+
26+
So far, all the quality checks (linters, tests, etc.) have been run manually on the local machines. This works for solo development but quickly becomes unreliable in a team setting or for long-term projects.
27+
28+
What if a collaborator forgets to run the tests before pushing their code?
29+
30+
What if a change works on the machine but fails on a different operating system?
31+
32+
How can someone be sure that a new feature doesn't accidentally break an old one?
33+
34+
**Continuous Integration (CI)** is the practice of automating these checks.
35+
36+
## What is Continuous Integration?
37+
38+
CI is a software development practice where developers frequently merge their code changes into a central repository. After each merge, an automated build and test sequence is run. The goal is to find and address bugs quicker, improve software quality, and reduce the time it takes to validate and release new software updates.
39+
40+
It's important to distinguish between CI and CD. While CI focuses on testing and validation, CD (Continuous Delivery/Deployment) extends this by automatically deploying code once it passes all checks. These concepts are complementary, and a good project should have both.
41+
42+
Integrating CI into the workflow is essential because it:
43+
44+
- **Enforces Quality**: It guarantees that every pull request passes all linter and test checks before it can be merged, maintaining a high-quality main branch.
45+
- **Prevents Regressions**: It automatically catches bugs and breaking changes, giving the team immediate feedback.
46+
- **Automates Repetitive Work**: It saves developers from the tedious, error-prone task of manually running tests.
47+
- **Creates a Single Source of Truth**: The CI server becomes the unbiased judge of whether code is ready to be merged.
48+
49+
For projects hosted on GitHub, the easiest and most popular way to implement CI is with **GitHub Actions**.
50+
51+
### Introduction to GitHub Actions
52+
53+
GitHub Actions is a CI/CD platform built directly into GitHub. Automation workflows are defined in a **YAML file** in a special directory in the repository: `.github/workflows/`. GitHub automatically detects these files and runs them based on a set of custom-defined rules or triggers, such as when code is pushed, a pull request is opened, or a scheduled job is due.
54+
55+
For ROS 2, the community has created a set of pre-made "actions" that make setting up a CI workflow incredibly simple. The most important one is `ros-tooling/action-ros-ci`, which encapsulates the entire colcon build, colcon lint, and colcon test process into a single step. This one is actively maintained by the ROS 2 tooling working group, so it's a trusted source to use in the community.
56+
57+
There are plenty of other choices to host the CI workflow available, it always depends on the specific use case on hand. Some of the most popular ones are Jenkins, AWS, Gitlab CI/CD, Bitbucket pipelines...
58+
59+
> [!NOTE]
60+
>
61+
> - The same GitHub Actions workflow can also be executed on **self-hosted runners**, allowing the CI pipeline to run on an organization’s own infrastructure instead of GitHub’s servers.
62+
>
63+
> - Note that `ament_cmake` and `colcon` commands in CI behave exactly like local runs, so the developer could always **debug possible failures locally first**, before relying on CI.
64+
65+
### Anatomy of a ROS 2 Workflow File
66+
67+
Here is a complete, minimal file for a typical ROS 2 workspace. This file would be placed at `.github/workflows/ci.yml` in the repository.
68+
69+
```yaml
70+
# .github/workflows/ci.yml
71+
72+
name: ROS 2 CI
73+
74+
# Controls when the workflow will run
75+
on:
76+
pull_request: # Run on all pull requests
77+
push:
78+
branches: [ main ] # Also run on pushes to the main branch
79+
80+
jobs:
81+
build_and_test_ros2:
82+
runs-on: ubuntu-latest
83+
container:
84+
image: rostooling/setup-ros-docker:ubuntu-noble-latest
85+
steps:
86+
- name: Build and run tests
87+
uses: ros-tooling/action-ros-ci@v0.4
88+
with:
89+
package-name: |
90+
my_package_1
91+
my_package_2
92+
my_package_3
93+
...
94+
target-ros2-distro: jazzy
95+
import-token: ${{ secrets.GITHUB_TOKEN }}
96+
```
97+
98+
This approach is designed for a simple use case of building and testing an entire workspace at once. If the workspace is too large, it's recommended to develop a more advanced approach and run these checks only for the changed packages, to reduce the time needed for each build and make the process faster. For that, it would be required to create other helper scripts. A full example about this can be found in [this file](../../.github/workflows/specific-ci.yml).
99+
100+
> [!IMPORTANT]
101+
> Always be careful when using pre-made actions from other sources, security issues might arise if these actions are updated or modified. See [this post](https://discourse.openrobotics.org/t/notice-tj-actions-changed-files-3rd-party-github-action-compromised/42540) for an example. This risk can be minimized by pinning Github actions to specific commits, to avoid them getting automatically updated in the workflow.
102+
103+
It's important to mention that if support for several distros (e.g., `jazzy` and `humble`) is required, it's not necessary to create separate files or jobs, Github actions has a powerful feature called a **"strategy matrix"** that can run the same job with different parameters.
104+
105+
Another good feature available is to configure periodic builds using these Github actions. A schedule can be added to them, specifying if they should be run nightly, weekly, monthly... This is really useful, as it makes easier the maintanance of the project, and allows to detect integration or dependency issues.
106+
107+
> [!TIP]
108+
> For performance in larger projects, CI runs can significantly speed up by caching downloaded dependencies and build artifacts. The `ros-tooling/setup-ros` action supports this out-of-the-box with tools like `ccache`. This can reduce build times from several minutes to just a few.
109+
110+
### Viewing CI Results
111+
112+
Once the workflow is set up, a "check" will automatically appear on every new pull request. It's possible to see the status directly on the PR page:
113+
114+
- Green checkmark ✅: All checks passed! The code is safe to merge.
115+
- Red X ❌: One or more checks failed. It's possible to click on "Details" to see the full logs and find out which colcon command failed.
116+
117+
### Enforcing CI with branch protection rules
118+
119+
A green checkmark is great, but what stops someone from merging a pull request when the tests are failing? GitHub's **branch protection rules** are the answer.
120+
121+
By setting up a protection rule for the `main` branch, status checks are required to pass before merging. This makes it impossible to merge code that breaks the build or fails tests. It's the final step that turns CI from a helpful suggestion into an enforceable quality gate.
122+
123+
The required checks can be configured in the GitHub repository under `Settings -> Branches -> Add branch ruleset`.
124+
125+
This simple setting is a cornerstone of maintaining a healthy, high-quality codebase in a team environment.
126+
127+
## References
128+
129+
- [GitHub Actions Documentation](https://docs.github.com/en/actions/get-started/quickstart)
130+
- [ros-tooling/action-ros-ci repository](https://github.com/ros-tooling/action-ros-ci)
131+
- [changed-files action](https://github.com/marketplace/actions/changed-files)

tools/get_changed_packages.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2025 Ekumen, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import sys
15+
from pathlib import Path
16+
17+
18+
def get_changed_packages(changed_files):
19+
"""
20+
Identifies unique ROS 2 packages that contain the changed files.
21+
"""
22+
changed_packages = set()
23+
for file_path in changed_files:
24+
p = Path(file_path)
25+
# Walk up the directory tree from the changed file
26+
while p != p.parent:
27+
# If we find a package.xml, we've found the package root
28+
if (p / "package.xml").is_file():
29+
changed_packages.add(p.name)
30+
break
31+
p = p.parent
32+
return changed_packages
33+
34+
35+
if __name__ == "__main__":
36+
# The first argument (sys.argv[0]) is the script name, so we skip it.
37+
# The changed files are passed as space-separated arguments.
38+
files = sys.argv[1:]
39+
packages = get_changed_packages(files)
40+
# Print the package names as a space-separated string for colcon
41+
print(" ".join(sorted(list(packages))))

0 commit comments

Comments
 (0)