CI/CD Workshop for R Packages

R/Pharma

November 4th, 2022

Hello CI/CD 👋


This workshop aims to discuss and show you how to implement 8 simple CI/CD workflows for a bare bones R package 📦

  • Emphasis on the simple and the bare bones 🦴
  • Which means that we can focus on the workflows 🎯

What to expect for next 3 hours?


  • Setting up GitHub and RStudio (20 mins) 🎬
    • Totally fine to sit back and watch the magic as well 🪄
  • Discussion on Flow of the Workshop (5 mins) 🌊
  • Discussion on why CI/CD is helpful for developing software and our background (10 mins) 💪
  • Work through the 8 workflows 🏗
  • Break for 5 mins after every 2 worfklows ☕️

Preparation for the Workshop

Setup

  • Set up GitHub to run our CI/CD checks through GitHub Actions
  • Set up RStudio Cloud Classroom for both ease of access and setup
  • Quick overview of the bare bones R package as our learning tool
  • We will do each step with you both for Setup and Workflows

GitHub Setup

Setup

RStudio Cloud Setup

Setup

  • We recommend using RStudio Cloud for ease of use ☁️
    • We’ll share the RStudio Classroom link in Chat 🔗
    • However, it’s okay to use other RStudio infrastructure
  • Start a new project in RStudio Cloud 📁
    • With the https link to your repo
  • Install the {devtools} package ⏬
  • Use devtools::install_dev_deps() to install all necessary packages

The R Package for the Workshop

Type: Package
Package: cicdworkshop
Title: Demo Package for R/Pharma CI/CD Workshop
Version: 0.0.1
Authors@R: 
    person("Open", "Source", , "osauthor@noreply.pharmaverse.com", role = c("aut", "cre"))
Description: Intended to demo CI/CD pipelines on GitHub.
License: Apache License (>= 2)
URL: https://pharmaverse.github.io/cicdworkshop.rinpharma2022
Depends: 
    R (>= 3.5)
Imports: 
    lifecycle
Suggests:
    covr,
    devtools,
    knitr,
    lintr,
    pkgdown,
    rmarkdown,
    roxygen2,
    spelling,
    styler,
    testthat
VignetteBuilder: 
    knitr
Encoding: UTF-8
Language: en-US
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1

The R Package for the Workshop

..
├── DESCRIPTION
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R
   └── hello.R
├── README.md
├── _pkgdown.yml
├── inst
   └── WORDLIST
├── man
   └── hello.Rd
└── tests
    ├── testthat
    └── testthat.R

Flow for Workshop

flowchart TD
  A(Discuss workflow <br> purpose) --> B(Create <br> workflow)
  B --> C(Push <br> workflow)
  C --> D(Observe <br> feedback)
  D --> E(Address <br> feedback)
  E --> F(Discuss <br> implementation)
  F --> A

Why use CI/CD for a R package?

  • Multiple Contributors on your R Package ⌨️
  • User base on multiple OSes and multiple R versions
  • Faster turnaround on Pull Requests ☑️
  • Catch issues (bugs) early on
  • Enforce style conventions and preferences
  • Measure test coverage for new code
  • Keep docs up-to-date

The 8 workflows we’ll implement

1 2 3 4 5 6 7 8

  1. R CMD Check    
  2. Link & URL Checks    
  3. Spelling Checks    
  4. Linter    
  1. Manual Pages    
  2. Code Style    
  3. Test Coverage    
  4. Publishing a pkgdown site    

R CMD Check

1 2 3 4 5 6 7 8


Why have a workflow that checks the Package Build?

  • Check to make sure it runs on multiple versions of R
  • Check to make sure it runs on multiple snapshots of R packages
  • Check to make sure it runs on different OSes
  • Check integrity of code from contributors before you review the Pull Request

R CMD Check Workflow

1 2 3 4 5 6 7 8

name: R CMD check
on:
  pull_request: {branches: ['main']}
jobs:
  Check:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Check out repository
        uses: actions/checkout@v3
      - name: Install dependencies
        run: devtools::install_dev_deps()
        shell: Rscript {0}
      - name: Build package
        run: R CMD build .
      - name: Check package
        run: R CMD check --no-manual *.tar.gz

Spelling Checks

1 2 3 4 5 6 7 8


Why have a workflow that checks the spelling in your package?

  • Industry-specific jargon and acronyms
  • Contributors speak different languages
  • Focus on the important parts of a Pull Request

Spelling Workflow

1 2 3 4 5 6 7 8

name: Spellcheck
on:
  pull_request: {branches: ['main']}
jobs:
  Spelling:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Run Spelling Check test
        uses: insightsengineering/r-spellcheck-action@v2

Code Style

1 2 3 4 5 6 7 8


Why have a workflow that checks your Code Style in your Package?

  • Ensures that everyone is using the same style guide
  • The code is more legible to developers who use the same guide
  • PR reviews are faster when everyone is using the same style

Code Style Workflow

1 2 3 4 5 6 7 8

name: Style
on:
  pull_request: {branches: ['main']}
jobs:
  Style:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Install styler
        run: if (!require("styler")) install.packages("styler")
        shell: Rscript {0}
      - name: Run styler
        run: styler::style_pkg(dry = "fail")
        shell: Rscript {0}

Linter

1 2 3 4 5 6 7 8


Why have a workflow that runs a Linter in your Package?

  • Static code analysis can be performed without compiling the code
  • Potential bugs can be discovered by the linter
  • Code best practices are enforced by the linter

Linter Workflow

1 2 3 4 5 6 7 8

name: Lint Code Base
on:
  pull_request: {branches: ['main']}
jobs:
  Linter:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
        with: {fetch-depth: 0}
      - name: Lint Code Base
        uses: github/super-linter/slim@v4
        env:
          VALIDATE_ALL_CODEBASE: false
          VALIDATE_R: true
          DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          LINTR_ERROR_ON_LINT: true

Manual Pages

1 2 3 4 5 6 7 8


Why have a workflow that checks your Manual pages in your Package?

  • To make sure that all documentation is up-to-date
  • Ensure that checks run without errors and warnings
  • Makes sure everyone is using the same version of {roxygen2}

Manual Pages Workflow

1 2 3 4 5 6 7 8

name: ROxygen
on:
  pull_request: {branches: ['main']}
jobs:
  ManPages:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Roxygen check
        run: |
          git config --global --add safe.directory $(pwd)
          R -s -e "roxygen2::roxygenize('.', roclets = c('rd', 'collate', 'namespace'))"
          if [[ -n `git status -s | grep -E "man|DESCRIPTION|NAMESPACE"` ]]; then
            echo "❌ Manual pages are NOT up-to-date ❌" && exit 1
          fi
          echo "Manual pages are up-to-date 😇"
        shell: bash

Test Coverage

1 2 3 4 5 6 7 8


Why have a workflow that checks the Test Coverage in your Package?

  • Most validation frameworks prefer higher code coverage
  • Builds user confidence by ensuring a social contract between devs and users

Test Coverage

1 2 3 4 5 6 7 8

name: Test Coverage
on:
  pull_request: {branches: [main]}
jobs:
  Coverage:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Install covr
        run: if (!require("covr")) install.packages("covr")
        shell: Rscript {0}
      - name: Run coverage
        run: covr::package_coverage(quiet = FALSE)
        shell: Rscript {0}

Publishing a pkgdown site

1 2 3 4 5 6 7 8


Why have a workflow that publishes a website for your Package?

  • Provides a specific destination for all your users
  • Ensure that your internal documentation is in sync with external documentation
  • Good documentation is like pizza 🍕:
    • When it’s good, it’s good
    • When it’s bad, it’s still better than nothing!

Publishing a pkgdown site

1 2 3 4 5 6 7 8

name: pkgdown
on:
  pull_request: {branches: ['main']}
  push: {branches: ['main']}
jobs:
  Generate:
    runs-on: ubuntu-latest
    container: {image: "rocker/tidyverse:4.2.1"}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Build site
        run: pkgdown::build_site_github_pages(install = TRUE, dest_dir = "public")
        shell: Rscript {0}
      - name: Deploy to GitHub pages
        if: github.event_name == 'push'
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          keep_files: true

We got 8! What’s next?