4.3 Travis CI

Travis CI is a continuous integration service that can be used to build and test software projects hosted on GitHub. To set up Travis CI you need to login at travis-ci.com (using your GitHub account) and provide authorization via GitHub (see Travis CI Tutorial).

Travis CI used to be a very established, mature and popular tool in the open-source source community, before a recent change of policy made it less focused on open-source, offering only limited free trial plans.

4.3.1 Standard CI setup

To setup Travis CI in a project use:

usethis::use_travis() # use ext = "com" if usethis < 1.6.0

This will generate a generic .travis.yml file

# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r

language: R
cache: packages

As default, Travis CI takes care of package dependency installation and performs the typical package build & check you would run locally via e.g. devtools::check(). Such a CI pipeline is triggered by any push event on any branch on the GitHub repo, including pull requests.

Default Travis Continuous Integration pipeline for an R package

4.3.2 Using renv for your project

If your project relies on the package renv for tracking dependencies via an renv.lock file, you should override the default installation package dependencies and make sure cacheing is adjusted accordingly, as described in the Using renv with Continuous Integration vignette:

cache:
  directories:
  - $HOME/.local/share/renv
  - $TRAVIS_BUILD_DIR/renv/library

install:
  - Rscript -e "if (!requireNamespace('renv', quietly = TRUE)) install.packages('renv')"
  - Rscript -e "renv::restore()"

4.3.3 Automated deployment

Travis CI can be setup to perform a deployment (e.g. publish a shiny app on shinyapps.io) upon any push to the master branch, provided the CI checks pass.

This is achieved for a shinyapps.io deployment by specifying in .travis.yml an additional deploy: section as

deploy:
  provider: script
  skip_cleanup: true # strictly necessary only for the renv case
  script:
  - >-
    Rscript
    -e 'account_info <- lapply(paste0("SHINYAPPS_", c("ACCOUNT", "TOKEN", "SECRET")), Sys.getenv)'
    -e 'do.call(rsconnect::setAccountInfo, account_info)'
    -e 'rsconnect::deployApp(appName = "ShinyCICD")'
  on:
    branch: master

where SHINYAPPS_ACCOUNT, SHINYAPPS_TOKEN, SHINYAPPS_SECRET are secure variables defined on Travis CI holding your account name and corresponding tokens for shinyapps.io.

It is in fact more convenient to write an R script, saved as e.g. deploy/deploy-shinyapps.R (build-ignored via usethis::use_build_ignore("deploy")) defining the deployment commands:

# deploy/deploy-shinyapps.R
# usethis::use_build_ignore("deploy")
if (!interactive()) {
  rsconnect::setAccountInfo(
    Sys.getenv("SHINYAPPS_ACCOUNT"),
    Sys.getenv("SHINYAPPS_TOKEN"),
    Sys.getenv("SHINYAPPS_SECRET")
  )
}
# Add here any additional files/directories the app needs
app_files = c(
  "app.R",
  "DESCRIPTION",
  "NAMESPACE",
  "R/",
  "inst/"
)
rsconnect::deployApp(
  appName = "ShinyCICD", appFiles = app_files, forceUpdate = TRUE
)

and then simply execute it as deploy script:

deploy:
  provider: script
  skip_cleanup: true # strictly necessary only for the renv case
  script: Rscript deploy/deploy-shinyapps.R
  on:
    branch: master

4.3.4 Putting it all together

The final .travis.yml file (for the non-renv case) would look like

# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r

language: R
cache: packages

deploy:
  provider: script
  script: Rscript deploy/deploy-shinyapps.R
  on:
    branch: master

As visible from the run logs, all the CI/CD pipeline steps are performed, despite only the deployment step being explicitly defined.

Travis Continuous Integration / Continuous Deployment pipeline for a packaged Shiny app