Understanding YAML and GitHub Actions#
Overview
Teaching: 15 min
Questions
What is the GitHub Actions specification?
Objectives
Learn where to find more details about everything for the GitHub Actions.
Understand the components of GitHub Actions YAML file.
Try and see if the CI/CD can catch problems with our code.
GitHub Actions YAML#
The GitHub Actions configurations are specified using YAML files stored in the.github/workflows/ directory. Here is an example of a YAML file:
Overall Structure#
Every single parameter we consider for all configurations are keys under jobs. The YAML is structured using job names. For example, we can define two jobs that run in parallel (more on parallel/serial later) with different sets of parameters.
name: <name of your workflow>
on: <event or list of events>
jobs:
job_1:
name: <name of the first job>
runs-on: <type of machine to run the job on>
steps:
- name: <step 1>
run: |
<commands>
- name: <step 2>
run: |
<commands>
job_2:
name: <name of the second job>
runs-on: <type of machine to run the job on>
steps:
- name: <step 1>
run: |
<commands>
- name: <step 2>
run: |
<commands>
name: GitHub displays the names of your workflows on your repository’s actions page. If you omit name, GitHub sets it to the YAML file name.on: Required. Specify the event that automatically triggers a workflow run. This example uses the push event, so that the jobs run every time someone pushes a change to the repository.
For more details, check this link.<job_id>: Each job must have an id to associate with the job, job_1 in the above example. The key job_id is a string that is unique to the jobs object. It must start with a letter or _ and contain only alphanumeric characters, -, or _. Its value is a map of the job’s configuration data.runs-on: Required. Each job runs in a particular type of machine (called a “runner”) that we choose with this key. There are options for the major operating systems (Linux, Windows, and macOS) and different versions of them. The available options can be found here.steps: Specify sequence of tasks. A step is an individual task. It either runs shell commands (run:) or uses an action (uses:) — a reusable unit of code published on GitHub. Each step in a job executes on the same runner, allowing the steps in that job to share data with each other. If you do not provide aname, the step name will default to the text specified in theruncommand.
Specify the job(s) to be run. Jobs run in parallel by default. To run jobs sequentially, you have to define dependencies on other jobs. We’ll cover this in a later section.
Reference
The reference guide for all GitHub Actions pipeline configurations is found at workflow-syntax-for-github-actions. This contains all the different parameters you can assign to a job.
Time To Skim#
The Naive Attempt#
As of right now, your .github/workflows/main.yml should look like
name: example
on: push
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- run: echo hello world
Let’s go ahead and teach our CI to build our code. Let’s add another job (named build_skim) that runs in parallel for right now, and runs the compiler ROOT uses.
Let’s give a try.
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
The compilation will result in an output binary called skim.
How do we change the CI in order to add a new job that compiles our code?
name: example
on: push
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- run: echo hello world
build_skim:
runs-on: ubuntu-latest
steps:
- name: build
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
Check jobs#
Let’s commit and push the changes we made, and let’s go to GitHub to check if both jobs ran.
git add .github/workflows/main.yml
git commit -m "add build skim job"
git push origin main
No root-config?#
Ok, so maybe we were a little naive here. GitHub runners come pre-installed with a wide variety of software that is commonly
needed in CI workflows (e.g. for Ubuntu 24.04 runners — which is what ubuntu-latest currently gives you — the list can be
found here).
ROOT is not pre-installed, so we will have to add a step to install it ourselves. After reading the ROOT documentation, we find that a convenient way to run it on various systems is using a Docker container.
There are several tools that are used for containerization, like Docker, Podman, and Apptainer (formerly Singularity). For this tutorial you don’t need to know anything about containerization. You can just think of this as the base software set that comes pre-installed on the system that runs your code.
We will be using the Docker images hosted at the rootproject/root Docker Hub. Let’s start by using the image
with tag 6.32.04-ubuntu24.04.
build_skim:
runs-on: ubuntu-latest
container: rootproject/root:6.32.04-ubuntu24.04
steps:
- name: build
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
Note the extra line container: rootproject/root:6.32.04-ubuntu24.04 that specifies the container image that we want to use.
Since it comes pre-packaged with ROOT, we do not need to have a step to install it.
This image also contains other tools that
we will need for the rest of the tutorial, including Python 3 — which we will invoke explicitly as python3 later on.
Using Actions#
What’s that?
error: skim.cxx: No such file or directory
It seems the job cannot access the repository. We need to instruct GitHub actions to checkout the repository.
We will use the actions/checkout action to checkout the repository.
This action checks out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
steps:
- name: checkout repository
uses: actions/checkout@v6
Actions
In GitHub CI/CD, an Action is a reusable script that performs a specific step in your software development
workflow, such as checking out a repository (actions/checkout), setting up a tool (actions/setup-python),
deploying pages (actions/deploy-pages), etc.
The actions/checkout action checks out your repository under the workspace, so your workflow can access it.
Let’s go ahead and tell our CI to checkout the repository:
build_skim:
runs-on: ubuntu-latest
container: rootproject/root:6.32.04-ubuntu24.04
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: build
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
Ways to get software#
As we saw before, GitHub pre-installs many common software packages and libraries that people might need,
but often we need to install additional software. There are often actions we can use for this,
like actions/setup-python to install python or mamba-org/setup-micromamba to install
Mamba (an alternative to Conda, an environment manager).
These actions are simply repositories that contain scripts to install or perform certain actions. You can find more information
about these actions by going to github.com/<name-of-action>. For example, for mamba-org/setup-micromamba you can
find more information at mamba-org/setup-micromamba.
If we wanted to use Conda instead of Docker, our build_skim job would look like this:
build_skim:
runs-on: ubuntu-latest
defaults:
run:
shell: bash -el {0}
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: Install ROOT
uses: mamba-org/setup-micromamba@v2
with:
environment-name: env
create-args: root
- name: build
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
Building multiple versions#
Great, so we finally got it working… Let’s build both the version of the code we’re testing and also test that the latest ROOT image (rootproject/root:latest) works with our code. Call this new job build_skim_latest.
Adding the build_skim_latest job
What does the .github/workflows/main.yml look like now?
Solution
name: example
on: push
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- run: echo hello world
build_skim:
runs-on: ubuntu-latest
container: rootproject/root:6.32.04-ubuntu24.04
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: build
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
build_skim_latest:
runs-on: ubuntu-latest
container: rootproject/root:latest
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: latest
run: |
COMPILER=$(root-config --cxx)
FLAGS=$(root-config --cflags --libs)
$COMPILER -g -O3 -Wall -Wextra -Wpedantic -o skim skim.cxx $FLAGS
Dependabot for updating GitHub Action versions#
GitHub Actions are accompanied by tags (“@v5”…) which are versions/tags of that action. One might need to update these tags, for example from “@v5” to “@v6”, because the GitHub Actions developers may fix existing bugs to the action or there may be other updates.
However, this process can be automated by using “Dependabot” which ensures that the workflow references the updated version of the action. If that is not the case, the Dependabot will open a pull request updating the tag of the GitHub Action.
Dependabot can be enabled in a GitHub repository by creating the file dependabot.yml in the .github/ folder. The content of the file looks like this (Link to the dependabot.yml):
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
where interval is the frequency of looking for updates to GitHub Actions.
For more information on Dependabot, see e.g., here.
Key Points
You should bookmark the GitHub Actions reference. You’ll visit that page often.
Steps run shell commands (
run:) or invoke reusable actions (uses:); steps combine into jobs.Workflows are made up of one or more jobs and can be scheduled or triggered.