GitHub actions

Learning objectives:

  • Recognise the utility of CI/CD for RAP.
  • Understand the basic structure and set-up of GitHub actions.
  • Explore some example actions that could be relevant for your research.

Relevant reproducibility guidelines:

  • NHS Levels of RAP (🥇): Repository automatically runs tests etc. via CI/CD or a different integration/deployment tool e.g. GitHub Actions.

Pre-reading:

This page assumes your simulation project is in a GitHub repository - set-up guidance available on the Version control page. We include some example actions which run Tests and Linting.

Continuous Integration, Continuous Delivery, and Continuous Deployment (CI/CD)

Definition

CI/CD processes (Continuous Integration, Continuous Delivery, and Continuous Deployment) are a valuable tool for reproducible analytical pipelines. These processes are typically defined as follows:

Continuous integration (CI): A process where team members regularly merge their code changes into a shared branch. Each integration triggers automated tests (e.g., unit and integration tests) to ensure that new commits do not break existing functionality. By committing and testing changes frequently, teams can identify and resolve issues earlier, and avoid large merges with lots of conflicts.

Continuous delivery (CD): Builds on CI by running more extensive automated checks (e.g., end-to-end tests) to ensure a repository is ready for deployment. Deployment can refer to updating a package, publishing a website, or release new results. This can be released when approved by a team member.

Continuous deployment (CD): Extends CD by releasing changes automatically to production as soon as all tests pass, with no manual approval required.

Further information

Relevance for your simulation RAP

CI will be relevant for all projects. CI automates checks on your code and outputs to ensure reliability and code quality. This can include tests, linting, formatting and other checks.

CD may not be needed for every project, but it can be useful:

  • If you choose to create a documentation website to accompany your projcet, you can use CD to automatically rebuild and deploy it whenever changes are merged.
  • If you choose to create an analytical pipeline that automates running the model and generating a report, CD can be used to trigger the generation and sharing of up-to-date results.

There are many available CI/CD tools, but GitHub actions is a popular choice because it’s integrated with GitHub, easy to set up, and free for public repositories.

GitHub actions

GitHub actions are defined in .yaml files stored within .github/workflows/ in your project repository. This is an example of a basic action structure:

1name: Example action

2on:
  push:
    branches: [main]

3jobs:
  checkout-echo:
    name: Checkout code and run basic echo command
4    runs-on: ubuntu-latest
5    permissions:
      contents: read
6    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Run basic echo command
        run: echo "Hello from GitHub Actions!"
1
name: This is the name for your workflow that gets displayed in the Actions tab of your GitHub repository.
2
on: This determines what triggers the workflow to run. For example, on push to specific branches, pull_request, or workflow_dispatch (allows you to manually trigger the action from GitHub when you want to - very useful!). You can combine multiple triggers:
3
jobs: You can run one or multiple jobs within a GitHub action, and these can run sequentially or in parallel.
4
runs-on: Specifies the operating system for that job (e.g., ubuntu-latest, windows-latest, macos-latest). Most CI workflows use ubuntu, but running tests across can be handy for checking your code works across different operating systems.
5
permissions: Sets permissions for the workflow - e.g., access to repository content or packages, or write access if it is editing or saving files.
6
steps: Define the tasks within the job. They can use pre-made actions via the uses: keyword or custom shell commands with run:.

Example: Tests

This simple action will install Python 3.13 and dependencies in a requirements.txt file, then run pytest().

Note: We’d recommend using requirements.txt for GitHub actions as it is much simpler than working with conda for these. We typically maintain an environment.yaml file for working with conda locally, and then an equivalent requirements.txt for GitHub actions.

name: tests

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Install python and dependencies
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'
          cache: 'pip'

      - name: Install requirements
        run: pip install -r requirements.txt

      - name: Run tests
        run: pytest

If you have structured your research as a package, then the R-CMD-check action can be used to check your package and run your tests all in one. This workflow is adapted from https://github.com/r-lib/actions/tree/v2/examples.

name: R-CMD-check.yaml

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions: read-all

jobs:
  R-CMD-check:
    runs-on: ubuntu-latest
    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      R_KEEP_PKG_SOURCE: yes
    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-pandoc@v2

      - uses: r-lib/actions/setup-r@v2
        with:
          r-version: 'release'
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck
          needs: check

      - uses: r-lib/actions/check-r-package@v2

Check out our example models (linked below) for a more complex example of this action which:

  • Runs on ubuntu, windows and mac.

  • Allows you to choose which operating system to use when triggering with workflow dispatch.

  • Runs tests with coverage, generating and uploading an updated coverage badge to the repository.

It also has a test-coverage.yaml action which runs tests with coverage, and updates the codecov coverage badge for the README.

Example: Test coverage

The test action can be expanded to include a calculation of code coverage (proportion of code executed by tests), by running pytest with the --cov flag. Using genbadge, we can also create and upload a coverage badge displayed in the README.

As we save the badge, the action needs write permissions. When the action runs, GitHub will generate a token GITHUB_TOKEN that is accessed using ${{ secrets.GITHUB_TOKEN}} and allows the Actions bot to push the updated badge to the repository with each run.

name: tests

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  permissions: 
    contents: write 
  tests:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Install python and dependencies
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'
          cache: 'pip'

      - name: Install requirements
        run: pip install -r requirements.txt

      - name: Run tests with coverage 
        run: pytest --cov --cov-report=xml 

      - name: Generate coverage badge 
        run: genbadge coverage -i coverage.xml -o images/coverage-badge.svg 

      - name: Upload coverage badge 
        uses: actions/upload-artifact@v4 
        with: 
          name: coverage-badge 
          path: images/coverage-badge.svg 

      - name: Commit coverage badge 
        run: | 
          git config --local user.email "github-actions[bot]@users.noreply.github.com" 
          git config --local user.name "github-actions[bot]" 
          git add images/coverage-badge.svg 
          git commit -m "ci(tests): update coverage badge" || echo "No changes to commit" 
          git push 
        env: 
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 

We can create an action that calculates code coverage (proportion of code executed by tests) with usethis.

To create the workflow file and add a badge to the README, run:

usethis::use_github_action("test-coverage")

Then, login to codecov.io with your GitHub account. If the repository is in an organisation, you’ll need to click the button to add the app to your organisation and authorise access. In codecov.io, you can then switch to the organisation from the top left dropdown.

In the GitHub repository, you need to set up GitHub secrets. Go to Settings > Secrets and variables > Actions > New token and copy in the information.

You can then run the workflow via GitHub actions and it will update the coverage badge.

Example: Linting

This action assumes you have a lint.sh which contains commands for linting the directory. If you don’t use this though, you can run things directly (e.g. pylint .).

name: lint

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  lint:
      runs-on: ubuntu-latest
      steps:

        - name: Check out repository
          uses: actions/checkout@v4

        - name: Install python and dependencies
          uses: actions/setup-python@v4
          with:
            python-version: '3.13'
            cache: 'pip'
        - run: pip install -r requirements.txt

        - name: Lint
          run: |
            bash lint.sh
# Workflow adapted from https://github.com/r-lib/actions/tree/v2/examples

on:
  push:
    branches: [main]
  workflow_dispatch:

name: lint

permissions: read-all

jobs:
  lint:
    runs-on: ubuntu-latest
    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::lintr, any::cyclocomp, local::.
          needs: lint

      - name: Lint package
        run: lintr::lint_package()
        shell: Rscript {0}
        env:
          LINTR_ERROR_ON_LINT: true

      - name: Lint rmarkdown
        run: lintr::lint_dir("rmarkdown")
        shell: Rscript {0}
        env:
          LINTR_ERROR_ON_LINT: true

Explore the example models

GitHub Click to visit pydesrap_mms repository

GitHub Click to visit pydesrap_stroke repository

Both examples have the same workflows: .github/workflows/lint.yaml and .github/workflows/tests.yaml.

As mentioned above, the testing action is more complex than the provided example on this page (runs on multiple operating systems which you can choose between, and calculates test coverage with badge).

GitHub Click to visit rdesrap_mms repository

GitHub Click to visit rdesrap_stroke repository

Both examples have the same workflows: .github/workflows/R-CMD-check.yaml, .github/workflows/lint.yaml and .github/workflows/test-coverage.yaml.

As mentioned above, the testing action is more complex than the provided example on this page (runs on multiple operating systems which you can choose between, and calculates test coverage with badge in a separate action).

Test yourself

If you’ve never used GitHub actions before, have a go with our very basic “Example action” to begin with.

Then, try adding a testing and/or linting action to your model repository.