Skip to Content

evolution of R package on travis


This post was written before the introduce of Docker on 2018-12-09.


For me, the most benefit of mastering Travis CI is to test R packages.

This post aims to provide some explanation for my .travis.yml, focusing on how the code evolves.

I will arrange things according to different parts, though a time order may present the relationship between them better.


The core rational is that ensure user can successful install my package rather than that I can successfully build it.

Although the general purpose of a CI is to test the program can be built and works as aspect, I want to go further and make sure there won’t be any additional problems when user install my package.


The first version is adopted from official workflow:

  - R -e "r_package <- c('devtools', 'roxygen2', 'testthat'); lapply(r_package, function(x) {if (!(x %in% .packages(T))) install.packages(x)})"
  - R -e 'devtools::install_deps(dep = T, upgrade = T)'
  - R -e 'devtools::document()'
  - R CMD build .
  - R -e "if (dir.exists('tests/testthat')) devtools::test()"

Then I use devtools::install_local() to make it more similar to devtools::install_github() which user would use:

  - R -e "r_package <- c('devtools', 'roxygen2', 'testthat'); lapply(r_package, function(x) {if (!(x %in% .packages(T))) install.packages(x)})"
  - R -e "devtools::install_local(getwd())"
  - R -e "devtools::update_packages('testthat'); if (dir.exists('tests/testthat')) devtools::test()"

version and platform

One advantage of R language by Travis community is multiple versions on multiple platforms:

language: r
os: [linux, osx]
r: [release, devel, oldrel]
  fast_finish: true
  - r: devel
  - r: oldrel

We can skip devel and oldrel on OS X to save time (include would use first value as default, that means devel and oldrel are only added for linux):

language: r
os: [linux, osx]
r: release
    - r: devel
    - r: oldrel

clean cache

If tests run for more than 10 minutes, developers will often start a new task rather than wait for the tests to finish. If the tests then fail, the developer now has to switch back to the original task. Constantly switching between tasks dramatically reduces productivity.

Caching installed package saves a lot of time, but brings the need to clean it periodically.

In the beginning, I clean cache via (monthly) cron build:

  - R -e "if (Sys.getenv('TRAVIS_EVENT_TYPE') == 'cron') unlink(dir(.libPaths()[1], full.names = T), recursive = T)"

Later I use cron job to continuously build my package weekly, so I check if the date is the first week to clean cache monthly:

  # combined with a weekly cron job, package cache would be updated monthly
  - R -e "if (as.integer(substr(Sys.Date(), 9, 10)) < 8 && Sys.getenv('TRAVIS_EVENT_TYPE') == 'cron') unlink(dir(.libPaths()[1], full.names = T), recursive = T)"

As the R packages become more and more, I decide to clean cache quarterly 1 (first week of first month of each quarter)

  - if [ "$TRAVIS_EVENT_TYPE" == "cron" ]; then R --slave -e "date <- Sys.Date(); day <- as.integer(substr(date, 9, 10)); month <- as.integer(substr(date, 6, 7)); clean <- month %% 3L == 1L && day <= 7L; if (clean) unlink(dir(.libPaths()[1], full.names = T), recursive = T)"; fi;

  1. The R code during development is:

    date <- as.Date('2018-04-06') #Sys.Date()
    day <- as.integer(substr(date, 9, 10))
    month <- as.integer(substr(date, 6, 7))				  
    clean <- month %% 3L == 1L && day <= 7L