ROS 2 CI with GitHub Actions

Ted Kern

on 6 March 2020

The ROS 2 Tooling Working Group (chaired by AWS RoboMaker) has been hard at work producing a neat set of GitHub Actions for building and testing ROS packages on a variety of different systems. They work great on Ubuntu targets and preliminary support is already present for MacOS and Windows, making them a great option for verifying your code works across all supported ROS 2 platforms. We’ve been using them ourselves in NoDL, a library/tool we’re developing to allow developers to specify the topics, services, actions, and parameters for a given ROS 2 node, and have been thrilled with how easy it is to get unit and integration tests running directly in GitHub.

GitHub’s workflows are defined in a file within the repository itself, similar to GitLab CI/CD workflows. If you’re familiar with the latter, getting started with GitHub Actions shouldn’t be too hard (they can be defined with a similar YAML format), so you may want to skip ahead to the example configuration.

What is CI?

To those unfamiliar with the term, Continuous Integration/Continuous Deployment (CI/CD) is the process by which automated systems monitor and act on a codebase.

A common example would be a test runner that is attached to source control. This runner can be scripted to intake new commits, proposed merges, etc. and invoke whatever test suite the user defines on the code, showing the results to other contributors and possibly blocking changes if they don’t meet requirements. Other examples might include scripts that output packages or executables whenever a new release is tagged, or upload documentation to a web host whenever markdown or html files are touched

Source code hosting providers like GitHub and GitLab offer tools to define these workflows and run them on your code for free (with limitations, of course). Other solutions include the external service Travis-CI (formerly the de-facto solution for GitHub) and the self hosted automation suite Jenkins.

What are GitHub Actions?

GitHub Actions are programs that serve as the building blocks of a repository’s workflow. While a workflow can be defined line by line with a large shell script, actions can automate large chunks of script.

A GitHub action for running a specific linter (for instance, ESLint) would handle installing and invoking the linter, while an action for uploading files to an S3 bucket would take arguments for destination url and keys and handle authenticating and uploading the target. Actions can also run on events in GitHub itself, like watching for magic words in issues or PRs (/revert, /submodules, etc).

ROS Github Actions

A number of actions are being maintained by the tooling working group specifically for working with ROS packages. We’ll focus on the two that every testing workflow should include, setup-ros and action-ros-ci.
The action-ros-ci-template repository contains a minimal example of running a number of linters across the codebase. However, If you want to ensure code style is enforced and users are aware of the formatting requirements, we recommend instead implementing explicit tests for the linters. That way, other contributors are explicitly notified of your code style conventions, and will be able acquire the linters with rosdep.

CI Example

In your package, create a directory named .github/workflows. In it, create a workflow.yaml file (any name works). Paste the following into it:

name: Test Example

on:
  pull_request:
  push:
    branches:
      - master

jobs:
  build-and-test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
      fail-fast: false
    steps:
    - name: Setup ROS 2
      uses: ros-tooling/setup-ros@0.0.15
    with:
        required-ros-distributions: eloquent

    - name: Run Tests
      uses: ros-tooling/action-ros-ci@0.0.13
      with:
        package-name: example_package

    - name: Upload Logs
      uses: actions/upload-artifact@v1
      with:
        name: colcon-logs
        path: ros_ws/log
      if: always()

Going through this step-by-step:

name: Test Example

Name of the workflow contained in this file. There can be multiple files with workflows in them.

on:
  pull_request:
  push:
    branches:
      - master

Triggers to run this workflow. Here we state that any commit in a pull request, as well as any commit pushed to master will trigger this workflow.

jobs:

Block containing all the discrete jobs to run. Each job starts in a fresh container, and can only receive data from other jobs through artifacts.

  build-and-test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
      fail-fast: false

We define a single job, build-and-test, and set low-level configuration options for it. In this case, we want to run the test on the mainline supported operating systems, so we define a matrix, which means the job will run for each permutation of values in the matrix. In addition, we set fail-fast to false, so a failure on one platform does not cancel the others.

We use the matrix configuration in the runs-on option, setting the value to ${{ matrix.os }} which will expand to the value in os that the current matrix is running.

  steps:
    - name: Setup ROS2
      uses: ros-tooling/setup-ros@0.0.15
    with:
        required-ros-distributions: eloquent

We now enter the steps of the workflow itself, beginning with invoking the setup-ros2 action. We create a step, give it a name, and for its contents simply state that it uses the action in question, at a fixed version (0.0.15, though if you’re copy-pasting you should lookup the latest release and use that instead). We use the with block to pass arguments to the setup-ros action, specifying that it should setup ROS 2 Eloquent only.

    - name: Run Tests
      uses: ros-tooling/action-ros-ci@0.0.13
      with:
        package-name: example_package

This step is the meat of our pipeline – the action-ros-ci action will collect our package (no need to use the checkout action), grab any dependencies, setup a workspace, and build and test our package. We pass our packages’ names (as in their package.xmls) as the package-name argument, and let the action handle the rest.

    - name: Upload Logs
      uses: actions/upload-artifact@v1
      with:
        name: colcon-logs
        path: ros_ws/log
      if: always()

Finally, we create and export an artifact, a compressed copy of the log directory for further inspection, using the action/upload-artifact action. We use the if: always() clause to ensure that the log uploads even when the previous steps fail (since that’s likely when we’ll actually want to see them!).

Internet of Things

From home control to drones, robots and industrial systems, Ubuntu Core and Snaps provide robust security, app stores and reliable updates for all your IoT devices.

Newsletter signup

Select topics you’re
interested in

In submitting this form, I confirm that I have read and agree to Canonical’s Privacy Notice and Privacy Policy.

Are you building a robot on top of Ubuntu and looking for a partner? Talk to us!

Contact Us

Related posts

Simulate the TurtleBot3

Interested in getting started in robotics? There’s no need to purchase expensive hardware before you try some things out: simulate a TurtleBot3 instead! The...

ROS Security Benchmark open for public comment

We’re pleased to announce that the Center for Internet Security (CIS) has publicly released the ROS Security Benchmark for community discussion. When...

Robotics Recap: Learning, Programming & Snapping ROS 2

Robotics@Canonical puts a strong focus on the migration from ROS to ROS 2. ROS 2 benefits from many improvements, especially robot security. Our goal is to...