4.2 GitHub Actions

GitHub Actions is a service for running highly-customizable and flexible automated workflows, fully integrated with GitHub and very suitable to CI/CD pipelines. Workflows use YAML syntax and should be stored in the .github/workflows directory in the root of the repository. Workflows are constituted of jobs and each job is a set of steps to perform individual tasks, e.g. commands or actions.

The next sections describe in detail the relevant workflow steps of a typical CI/CD pipeline for a packages Shiny app, also covering the usage of renv to track package dependencies. Finally, we will show how you can use the convenience function usethis::use_github_action() for including such workflows in you project.

4.2.1 Workflow steps

A workflow should have an identifying name and an on section that indicates upon which events the workflow should be triggered. It should include at least one job and each job will have a set of steps fully specifying what to execute. Such steps can be an action (predefined, sourcing from GitHub repos that contain such actions) or custom shell commands. With the introduction of composite Actions, most of the relevant workflow steps for an R project are covered by actions provided by the r-lib/actions.

4.2.1.1 Setup

Using renv

If your project relies on package renv for tracking dependencies via an renv.lock file, caching and installation of R package dependencies requires a different setup, as described in the Using renv with Continuous Integration vignette. As shown in the complete workflow files below:

  • system requirements are installed explicitly (for the ubuntu runner defined for the workflow) based on remotes::system_requirements()
  • dependencies tracked by renv are installed (including caching) using r-lib/actions/setup-renv

4.2.1.2 Package check

  • Check the package via rcmdcheck::rcmdcheck().

4.2.1.3 Deployment

  • Continuous deployment to shinyapps.io is automated upon any push to the master branch
    • In order to provide credentials for the deployment, account name and corresponding tokens for shinyapps.io are defined as environment variables SHINYAPPS_ACCOUNT, SHINYAPPS_TOKEN and SHINYAPPS_SECRET, specified / accessible as GitHub secrets.
    • A convenience R script, e.g. deploy/deploy-shinyapps.R (build-ignored via usethis::use_build_ignore("deploy")), defines the deployment commands based on the environment variables.
# deploy/deploy-shinyapps.R
# usethis::use_build_ignore("deploy")
rsconnect::setAccountInfo(
  Sys.getenv("SHINYAPPS_ACCOUNT"),
  Sys.getenv("SHINYAPPS_TOKEN"),
  Sys.getenv("SHINYAPPS_SECRET")
)
rsconnect::deployApp(appName = "ShinyCICD")

4.2.2 Workflow file

The steps described in the previous section are defined in the .yml workflow file as follows:

# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help

# Name of the workflow => usethis::use_github_actions_badge("CI-CD")
name: CI-CD

on:
  # Triggered on push and pull request events
  push:
  pull_request:
  # Allow manual runs from the Actions tab
  workflow_dispatch:

jobs:
  CI-CD:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      # We keep a matrix for convenience, but we would typically just run on one
      # single OS and R version, aligned with the target deployment environment
      matrix:
        config:
          - {os: ubuntu-latest, r: 'release'}

    env:
      # Access token for GitHub
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      # Preserve package sources for informative references in case of errors
      R_KEEP_PKG_SOURCE: yes

    steps:

      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          # Enable RStudio Package Manager to speed up package installation
          use-public-rspm: true

      - name: Install and cache dependencies
        uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck

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

      - name: Deploy to shinyapps.io
        # Continuous deployment only for pushes to the main / master branch
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
        env:
          SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
          SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
          SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
        run: Rscript deploy/deploy-shinyapps.R

As visible from the run logs that can be found in the GitHub repository under the Actions tab, all the CI/CD pipeline steps are performed subsequently, and are identifiable by the name field. See the example below, showing how the deployment step is skipped for a run not triggered by a push action the main (or master) branch:

GitHub Actions Continuous Integration / Continuous Deployment pipeline for a packaged Shiny app

4.2.3 Complete workflows and usethis::use_github_action()

Full YAML workflows for CI and CI/CD pipelines, with and without renv, are shown below and provided as part of this guide.

In order to setup and use CI/CD GitHub Actions workflows as described above, you can simply include the relevant workflow file your project via:

usethis::use_github_action(url = paste0(
  "https://github.com/miraisolutions/techguides/blob/master/",
  "shiny-ci-cd/actions/ci-cd.yml"
  # "shiny-ci-cd/actions/ci-cd-renv.yml"
  # "shiny-ci-cd/actions/ci.yml"
  # "shiny-ci-cd/actions/ci-renv.yml"
))
usethis::use_github_actions_badge("CI-CD") # or "CI"

4.2.3.1 Complete workflow files

shiny-ci-cd/actions/ci-cd.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help

# Name of the workflow => usethis::use_github_actions_badge("CI-CD")
name: CI-CD

on:
  # Triggered on push and pull request events
  push:
  pull_request:
  # Allow manual runs from the Actions tab
  workflow_dispatch:

jobs:
  CI-CD:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      # We keep a matrix for convenience, but we would typically just run on one
      # single OS and R version, aligned with the target deployment environment
      matrix:
        config:
          - {os: ubuntu-latest, r: 'release'}

    env:
      # Access token for GitHub
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      # Preserve package sources for informative references in case of errors
      R_KEEP_PKG_SOURCE: yes

    steps:

      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          # Enable RStudio Package Manager to speed up package installation
          use-public-rspm: true

      - name: Install and cache dependencies
        uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck

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

      - name: Deploy to shinyapps.io
        # Continuous deployment only for pushes to the main / master branch
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
        env:
          SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
          SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
          SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
        run: Rscript deploy/deploy-shinyapps.R
shiny-ci-cd/actions/ci-cd-renv.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help

# Name of the workflow => usethis::use_github_actions_badge("CI-CD-renv")
name: CI-CD-renv

on:
  # Triggered on push and pull request events
  push:
  pull_request:
  # Allow manual runs from the Actions tab
  workflow_dispatch:

jobs:
  CI-CD:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      # We keep a matrix for convenience, but we would typically just run on one
      # single OS and R version, aligned with the target deployment environment
      matrix:
        config:
          - {os: ubuntu-latest, r: 'release'}

    env:
      # Access token for GitHub
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      # Preserve package sources for informative references in case of errors
      R_KEEP_PKG_SOURCE: yes

    steps:

      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          # Enable RStudio Package Manager to speed up package installation
          use-public-rspm: true

      - name: Install system dependencies
        # This is not taken care of (yet) by r-lib/actions/setup-renv
        # Package distro used to get the distro for the used ubuntu-latest
        run: |
          Rscript -e "install.packages(c('remotes', 'distro'))"
          while read -r cmd
          do
            eval sudo $cmd
          done < <(Rscript -e 'writeLines(with(distro::distro(), remotes::system_requirements(id, short_version)))')

      - name: Activate renv and restore packages with cache
        uses: r-lib/actions/setup-renv@v2

      - name: Install R CMD check
        run: install.packages("rcmdcheck")
        shell: Rscript {0}

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

      - name: Deploy to shinyapps.io
        # Continuous deployment only for pushes to the main / master branch
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
        env:
          SHINYAPPS_ACCOUNT: ${{ secrets.SHINYAPPS_ACCOUNT }}
          SHINYAPPS_TOKEN: ${{ secrets.SHINYAPPS_TOKEN }}
          SHINYAPPS_SECRET: ${{ secrets.SHINYAPPS_SECRET }}
        run: Rscript deploy/deploy-shinyapps.R
shiny-ci-cd/actions/ci.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help

# Name of the workflow => usethis::use_github_actions_badge("CI")
name: CI

on:
  # Triggered on push and pull request events
  push:
  pull_request:
  # Allow manual runs from the Actions tab
  workflow_dispatch:

jobs:
  CI-CD:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      # We keep a matrix for convenience, but we would typically just run on one
      # single OS and R version, aligned with the target deployment environment
      matrix:
        config:
          - {os: ubuntu-latest, r: 'release'}

    env:
      # Access token for GitHub
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      # Preserve package sources for informative references in case of errors
      R_KEEP_PKG_SOURCE: yes

    steps:

      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          # Enable RStudio Package Manager to speed up package installation
          use-public-rspm: true

      - name: Install and cache dependencies
        uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck

      - name: Check package
        uses: r-lib/actions/check-r-package@v2
shiny-ci-cd/actions/ci-renv.yml
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help

# Name of the workflow => usethis::use_github_actions_badge("CI-renv")
name: CI-renv

on:
  # Triggered on push and pull request events
  push:
  pull_request:
  # Allow manual runs from the Actions tab
  workflow_dispatch:

jobs:
  CI-CD:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      # We keep a matrix for convenience, but we would typically just run on one
      # single OS and R version, aligned with the target deployment environment
      matrix:
        config:
          - {os: ubuntu-latest, r: 'release'}

    env:
      # Access token for GitHub
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
      # Preserve package sources for informative references in case of errors
      R_KEEP_PKG_SOURCE: yes

    steps:

      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          # Enable RStudio Package Manager to speed up package installation
          use-public-rspm: true

      - name: Install system dependencies
        # This is not taken care of (yet) by r-lib/actions/setup-renv
        # Package distro used to get the distro for the used ubuntu-latest
        run: |
          Rscript -e "install.packages(c('remotes', 'distro'))"
          while read -r cmd
          do
            eval sudo $cmd
          done < <(Rscript -e 'writeLines(with(distro::distro(), remotes::system_requirements(id, short_version)))')

      - name: Activate renv and restore packages with cache
        uses: r-lib/actions/setup-renv@v2

      - name: Install R CMD check
        run: install.packages("rcmdcheck")
        shell: Rscript {0}

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