Getting started with CI for R

Prerequisites

Continuous Integration (CI) is a huge field in software development. There are a lot of resources out there that try to explain what it can do and why you need it (and usually why their particular company/service does it best). If you are just getting started with software development or git based project work you might think:

“Ah I don’t need all of this overhead, I am fine doing X and Y manually for this project. The overhead learning this next additional”tool” is not worth it.

Reading all vignettes of this package will help you understand what {tic} does to simplify all of that specifically for the R language.

However, there is a lot of advanced information in these vignettes which might be a bit overwhelming at first if you are just getting started. Therefore we will explain a few of the most important terms first to give you a kickstart:

Runner

One build job (possibly among many others) on any operating system that executes certain commands specified in the YAML config file.

YAML config file

A file written in the YAML language telling the runner what to do after a push to the repository.

Build matrix

Specification how many runners are started and their specification (operating system, custom environment variables, etc.)

CI Provider

A company that offers ready-to-use virtual images for runners which are started after a certain action (.e.g Circle CI). However, code hosting sites like GitHub or GitLab also have their own CI integration meanwhile.

Deployment

CI builds cannot just perform specific checks on a package/project, they can also be used to (re-)build certain files in every run. With the appropriate permissions these files can then be pushed to a repository via git without the need to take manual actions by the user. This can save a lot of time and is commonly used to ensure that documentation is always up-to-date.

CI Client packages

R packages that interface the command line API of CI providers to simplify the execution of certain tasks (scraping build logs, enabling repos, etc.), e.g. {circle}.

CI Badges

Badges in the README of a repository showing the current build status of the project: Did the last build fail or finish successfully?

DSL

“DSL” stands for “domain-specific language” and essentially means the implementation of a general concept for a specific programming language.

Initialization/Setup

The easiest way to use {tic} for CI services is to call tic::use_tic(). This will initialize a setup wizard which will guide you through all possibilities of the offered CI providers by {tic}.

Several yes/no questions need to be answered. Based on the replies we’ll select specific templates.

Besides the question which CI system you want to use, you’ll be asked if you want to

  • Deploy from builds to GitHub (e.g. if you are building a {pkgdown} site)

  • Test your package on multiple R versions

Last, we’ll add a tic.R file to the project root.

After this, your project is ready for continuous integration. The next push to GitHub will create a build on Circle CI. See the Troubleshooting section in case anything doesn’t work as expected.

Quickstart

If you are a new user, run

tic::use_tic()

If you already use {tic} and want to configure a new CI provider, do

## Circle CI
circle::use_circle_deploy() # (optional for deployment)
tic::use_circle_yml(<option here>)

If you are open to try out new things, Circle CI comes with some advantages that might simplify your CI experience. However, all providers come with pros and cons and we cannot provide an exhaustive list comparing all providers here.

See the CI Client Packages article for more detailed information on how {tic} and the CI client packages work together.

The role of the tic.R file

After having called tic::use_tic() you will find

  • .circleci/config.yml
  • .github/workflows

and a tic.R file in your repo, depending on the choices you made during use_tic(). The latter will always be present because it will be the main CI config file for all providers from now on. Usually you do not need to touch the YAML files anymore. All build customization is done in tic.R and applies to all providers. For more information about the build lifecycle in general, check the Build lifecycle article.

The basic tic.R template looks as follows:

do_package_checks()

if (ci_on_ghactions()) {
  do_pkgdown()
}

tic.R file has a declarative nature: It should consist of “stages”, “steps” and “macro” functions (see below). These functions will only have an effect when specified in tic.R and should not be used standalone as they will only run in a (simulated) CI run following a certain order.

To run plain R code within the build, encapsulate it within add_code_step(<code>) and add it to a certain build stage. See the Build Lifecycle article for detailed info about how to do this.

Macros

{tic} builds on the “macro” idea. Macros are essentially wrappers of a sequence of steps for often used tasks on the CI system:

  • Checking a package (R CMD check)
  • Building and deploying a pkgdown site
  • Build and deploy a bookdown project

They can be distinguished from other functions by their do_ prefix. The following ones are currently implemented:

list_macros()
#> [1] "do_blogdown"       "do_bookdown"       "do_drat"          
#> [4] "do_package_checks" "do_pkgdown"        "do_readme_rmd"

If you have a good use case for a macro, let us know by opening an issue.

do_package_checks()

do_package_checks()

do_package_checks() adds essential steps to various stages of a CI run. Most importantly, it adds step_rcmdcheck() to the “script” stage. This step performs the check of an R package. Afterwards, the code coverage is being checked using covr::codecov(). See ?do_package_checks() for more information.

# step_install_deps() in the "install" stage, using the repos argument.
#
# step_rcmdcheck() in the "script" stage, using the warnings_are_errors,
#  notes_are_errors, args, and build_args arguments.
#
# A call to covr::codecov() in the "after_success" stage (only if the codecov flag is set)

do_pkgdown()

The other macro in the default template is do_pkgdown().

if (ci_on_ghactions()) {
  do_pkgdown()
}

do_pkgdown() adds five steps to the build process:

# step_install_deps() in the "install" stage, using the repos argument.
#
# step_setup_ssh() in the "before_deploy" to setup the upcoming deployment (if deploy is set),
#
# step_setup_push_deploy() in the "before_deploy" stage (if deploy is set),
#
# step_build_pkgdown() in the "deploy" stage, forwarding all ... arguments.
#
# step_do_push_deploy() in the "deploy" stage.

By default this currently happens only on GitHub Actions, because ci_on_ghactions() is used as a condition. Why do we do this? Building the pkgdown site on multiple CI services has no added benefit and might even cause problems due to race conditions during deployment.

ci_on_ghactions() can be replaced by one of its sibling functions like ci_on_circle().

do_readme_rmd()

Some projects rely on a dynamic README.Rmd file which contains R code. Sometimes the output of such README’s will change over time if the code driving it changes due to updates. To always stay up-to-date without needing to take manual action, you can use this macro. It will render README.Rmd and deploy README.md to the default repo branch. A deployment will only be made if the rendered output differs from the one stored upstream.

This macro requires that you have set up deployment for your selected provider beforehand.

Blogdown

As a show case, we explain a “blogdown” project in more detail. blogdown is an R package for publishing websites. Under the hood, it uses the framework Hugo which gets installed by the respective tic.R template in the “install” section:

get_stage("install") %>%
  add_code_step(blogdown::install_hugo())

Next the website is built and deployed. The blogdown::build_site() function for websites is the equivalent to pkgdown::build_site() for R packages.

get_stage("deploy") %>%
  add_code_step(blogdown::build_site()) %>%
  add_step(step_push_deploy())

Steps and stages differ between projects (e.g. between a “blogdown” website and a “package”). {tic} is smart enough to detect your project automatically when calling tic::use_tic() and will add the correct template.

Note: Currently, publishing to https://figshare.com/ doesn’t work. Also, publishing to https://zenodo.org/ is work in progress.

{tic} projects from the community

The templates we provide with {tic} are minimal working examples. By querying tic.R on GitHub one can see who else uses {tic} for their CI runs: https://github.com/search?p=5&q=filename%3Atic.R&type=Code

Still got questions?

Have a look at the list of articles we wrote to shine more light on all the parts {tic} covers.

If you face issues, make sure to also check out the FAQ vignette or browse the issue tracker.