-
Notifications
You must be signed in to change notification settings - Fork 18
/
custom-steps.Rmd
205 lines (178 loc) · 7.47 KB
/
custom-steps.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
---
title: "Developer info: Writing custom steps"
author: "Kirill Müller, Patrick Schratz"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Developer info: Writing custom steps}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE
)
```
Most important steps running on CI systems are [already implemented](tic.html#steps) in {tic}.
The following vignette shows how new steps can be created and how {tic} defines them.
Internally, all steps are defined using the [R6](https://github.com/wch/R6) class system.
If you are not familiar with object-oriented programming using R6, the [R6 chapter](https://adv-r.hadley.nz/r6.html) in [Advanced R](https://adv-r.hadley.nz/) is a good place to start.
In most cases there should be no need to write steps on your own, because `step_run_code()` can be used to run arbitrary code when preparing and running the step, and custom conditions in `tic.R` can be used to trigger the step.
However, if you have the need for a repeated use of specific combinations of `add_code_step()`, please let us know (by opening an [issue](https://github.com/ropensci/tic/issues)) so that we can discuss whether it makes sense to implement it as a custom step.
## The `TicStep` class
A step is a subclass of the [`TicStep` class](https://github.com/ropensci/tic/blob/457a5d259c6861e322220ac51a0436436e5f214b/R/steps-base.R#L7-L38).
The `step_...` functions in {tic} are forwarded to the `new()` methods of the corresponding R6 class objects (and from there to `initialize()` member functions).
We recommend following the same pattern for your custom steps.
The `TicStep` class implements the public methods `check()`, `prepare()`, and `run()`.
These methods must be callable without arguments.
This means that you only need to override the methods you need; if you don't need a `check()` or `prepare()` method you can leave it out.
The following sections describe these methods and show examples.
### The `prepare()` method
This method will be called by `prepare_all_stages()`.
It is intended to run in the `before_script` phase of the CI run.
This method should install all dependent packages that the step requires, which then can be cached by the CI system.
You also may include further preparation code here.
For example `step_rcmdcheck` verifies that the R packages *rcmdcheck* and *remotes* are installed:
```{r}
RCMDcheck$public_methods$prepare
```
### The `run()` method
This method executes the step.
When a step is added to a stage, `run()` will be called when the stage is executed.
For example, the `run()` function of class `RCMDcheck` looks as follows:
```{r}
RCMDcheck$public_methods$run
```
### The `check()` method
This method checks whether the step is actually run.
It returns a logical scalar.
The [`step_write_text_file()`](https://github.com/ropensci/tic/blob/457a5d259c6861e322220ac51a0436436e5f214b/R/steps-write-text-file.R#L1-L24) function is an example step with the following implementation of the `check()` method:
```{r}
WriteTextFile$public_methods$check
```
## A minimal example
You can take a look at [a pull request that implements a new step](https://github.com/ropensci/tic/pull/75/files).
The most minimalist version is the "Hello World" [example step](https://github.com/ropensci/tic/blob/master/R/steps-base.R).
This class only contains a `run()` method which does nothing more than printing "Hello World" to the console.
It is initialized by calling `step_hello_world()` which creates a new instance of this class.
```{r eval = FALSE}
HelloWorld <- R6Class(
"HelloWorld",
inherit = TicStep,
public = list(
run = function() {
print("Hello, world!")
}
)
)
#' Step: Hello, world!
#'
#' The simplest step possible: prints "Hello, world!" to the console when run, does not require
#' any preparation.
#' This step may be useful to test a \pkg{tic} setup or as a starting point when implementing a
#' custom step.
#'
#' @family steps
#' @export
step_hello_world <- function() {
HelloWorld$new()
}
```
## Further information on the R6 class system
If you are unfamiliar with `R6` classes, here is a short guidance how the arguments are passed along:
Consider the `step_rcmdcheck()` function ([link to source](https://github.com/ropensci/tic/blob/master/R/steps-rcmdcheck.R)):
```{r, eval = FALSE}
RCMDcheck <- R6Class( # nolint
"RCMDcheck",
inherit = TicStep,
public = list(
initialize = function(warnings_are_errors = NULL, notes_are_errors = NULL,
args = c("--no-manual", "--as-cran"),
build_args = "--force", error_on = "warning",
repos = repo_default(), timeout = Inf,
check_dir = NULL) {
if (!is.null(notes_are_errors)) {
warning_once(
'`notes_are_errors` is deprecated, please use `error_on = "note"`'
)
if (notes_are_errors) {
error_on <- "note"
}
} else if (!is.null(warnings_are_errors)) {
warning_once(
"`warnings_are_errors` is deprecated, ",
'please use `error_on = "warning"`'
)
if (warnings_are_errors) {
error_on <- "warning"
}
}
private$args <- args
private$build_args <- build_args
private$error_on <- error_on
private$repos <- repos
private$timeout <- timeout
private$check_dir <- check_dir
super$initialize()
},
run = function() {
# Don't include vignettes if --no-build-vignettes is included
if ("--no-build-vignettes" %in% private$args) {
cat("^vignettes$\n", file = ".Rbuildignore", append = TRUE)
}
withr::with_envvar(
c(
# Avoid large version components
"_R_CHECK_CRAN_INCOMING_" = "FALSE",
# Don't check system clocks (because the API used there is flaky)
"_R_CHECK_SYSTEM_CLOCK_" = "FALSE",
# Don't force suggests
"_R_CHECK_FORCE_SUGGESTS_" = "FALSE",
# Work around missing qpdf executable
"R_QPDF" = if (Sys.which("qpdf") == "") "true"
),
res <- rcmdcheck::rcmdcheck(
args = private$args, build_args = private$build_args,
error_on = "never",
repos = private$repos,
timeout = private$timeout,
check_dir = private$check_dir
)
)
print(res)
if (length(res$errors) > 0) {
stopc("Errors found in rcmdcheck::rcmdcheck().")
}
if (private$error_on %in% c("warning", "note") && length(res$warnings) > 0) {
stopc(
"Warnings found in rcmdcheck::rcmdcheck(), ",
'and `errors_on = "warning"` is set.'
)
}
if (private$error_on == "note" && length(res$notes) > 0) {
stopc(
"Notes found in rcmdcheck::rcmdcheck(), ",
'and `errors_on = "note"` is set.'
)
}
},
prepare = function() {
verify_install("rcmdcheck")
super$prepare()
}
),
private = list(
args = NULL,
build_args = NULL,
error_on = NULL,
repos = NULL,
timeout = NULL,
check_dir = NULL
)
)
```
Here, a new instance of the defined `R6` class `RCMDcheck` is initiated with `RCMDcheck$new()`.
The arguments to `step_rcmdcheck()` are passed on to the `initialize()` function of the `R6` class.
Here, the arguments are assigned to the "private" members (e.g. `private$args`).
Next, these private members are used in the `run()` function which carries out the actual work.