-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathbuild.R
More file actions
348 lines (325 loc) · 10.8 KB
/
Copy pathbuild.R
File metadata and controls
348 lines (325 loc) · 10.8 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#' Build package
#'
#' Building converts a package source directory into a single bundled file.
#' If `binary = FALSE` this creates a `tar.gz` package that can
#' be installed on any platform, provided they have a full development
#' environment (although packages without source code can typically be
#' installed out of the box). If `binary = TRUE`, the package will have
#' a platform specific extension (e.g. `.zip` for windows), and will
#' only be installable on the current platform, but no development
#' environment is needed.
#'
#' ## Configuration
#'
#' ### `DESCRIPTION` entries
#'
#' * `Config/build/clean-inst-doc` can be set to `FALSE` to avoid cleaning up
#' `inst/doc` when building a source package. Set it to `TRUE` to force a
#' cleanup. See the `clean_doc` argument.
#'
#' * `Config/build/copy-method` can be used to avoid copying large
#' directories in `R CMD build`. It works by copying (or linking) the
#' files of the package to a temporary directory, leaving out the
#' (possibly large) files that are not part of the package. Possible
#' values:
#'
#' - `none`: pkgbuild does not copy the package tree. This is the default.
#' - `copy`: the package files are copied to a temporary directory before
#' ` R CMD build`.
#' - `link`: the package files are symbolic linked to a temporary
#' directory before `R CMD build`. Windows does not have symbolic
#' links, so on Windows this is equivalent to `copy`.
#'
#' You can also use the `pkg.build_copy_method` option or the
#' `PKG_BUILD_COPY_METHOD` environment variable to set the copy method.
#' The option is consulted first, then the `DESCRIPTION` entry, then the
#' environment variable.
#'
#' * `Config/build/extra-sources` can be used to define extra source files
#' for pkgbuild to decide whether a package DLL needs to be recompiled in
#' `needs_compile()`. The syntax is a comma separated list of file names,
#' or globs. (See [utils::glob2rx()].) E.g. `src/rust/src/*.rs` or
#' `configure*`.
#'
#' * `Config/build/bootstrap` can be set to `TRUE` to run
#' `Rscript bootstrap.R` in the source directory prior to running subsequent
#' build steps.
#'
#' * `Config/build/never-clean` can be set to `TRUE` to never add `--preclean`
#' to `R CMD INSTALL`, e.g., when header files have changed.
#' This helps avoiding rebuilds that can take long for very large C/C++ codebases
#' and can lead to build failures if object files are out of sync with header files.
#' Control the dependencies between object files and header files
#' by adding `include file.d` to `Makevars` for each `file.c` or `file.cpp` source file.
#'
#' ### Options
#'
#' * `pkg.build_copy_method`: use this option to avoid copying large
#' directories when building a package. See possible values above, at the
#' `Config/build/copy-method` `DESCRIPTION` entry.
#'
#' * `pkg.build_stop_for_warnings`: if it is set to `TRUE`, then pkgbuild
#' will stop for `R CMD build` errors. It takes precedence over the
#' `PKG_BUILD_STOP_FOR_WARNINGS` environment variable.
#'
#' ### Environment variables
#'
#' * `PKG_BUILD_COLOR_DIAGNOSTICS`: set it to `false` to opt out of colored
#' compiler diagnostics. Set it to `true` to force colored compiler
#' diagnostics.
#'
#' * `PKG_BUILD_COPY_METHOD`: use this environment variable to avoid copying
#' large directories when building a package. See possible values above,
#' at the `Config/build/copy-method` `DESCRIPTION` entry.
#'
#' will stop for `R CMD build` errors. The `pkg.build_stop_for_warnings`
#' option takes precedence over this environment variable.
#'
#' @param path Path to a package, or within a package.
#' @param dest_path path in which to produce package. If it is an existing
#' directory, then the output file is placed in `dest_path` and named
#' according to the current R conversions (e.g. `.zip` for Windows binary
#' packages, `.tgz` for macOS binary packages, etc).
#' If it is an existing file, then it will be overwritten.
#' If `dest_path` does not exist, then it is used as a file name.
#' If `NULL`, it defaults to the parent directory of the package.
#' @param binary Produce a binary (`--binary`) or source (
#' `--no-manual --no-resave-data`) version of the package.
#' @param vignettes,manual For source packages: if `FALSE`, don't build PDF
#' vignettes (`--no-build-vignettes`) or manual (`--no-manual`).
#' @param args An optional character vector of additional command
#' line arguments to be passed to `R CMD build` if `binary = FALSE`,
#' or `R CMD install` if `binary = TRUE`.
#' @param quiet if `TRUE` suppresses output from this function.
#' @param needs_compilation Usually only needed if the packages has
#' C/C++/Fortran code. By default this is autodetected.
#' @param compile_attributes if `TRUE` and the package uses Rcpp, call
#' [Rcpp::compileAttributes()] before building the package. It is ignored
#' if package does not need compilation.
#' @param register_routines if `TRUE` and the package does not use Rcpp, call
#' register routines with
#' `tools::package_native_routine_registration_skeleton()` before building
#' the package. It is ignored if package does not need compilation.
#' @param clean_doc If `TRUE`, clean the files in `inst/doc` before building
#' the package. If `NULL` and the `Config/build/clean-inst-doc` entry is
#' present in `DESCRIPTION`, then that is used. Otherwise, if `NULL`,
#' and interactive, ask to remove the files prior to cleaning. In most
#' cases cleaning the files is the correct behavior to avoid stale
#' vignette outputs in the built package.
#' @export
#' @return a string giving the location (including file name) of the built
#' package
build <- function(
path = ".",
dest_path = NULL,
binary = FALSE,
vignettes = TRUE,
manual = FALSE,
clean_doc = NULL,
args = NULL,
quiet = FALSE,
needs_compilation = pkg_has_src(path),
compile_attributes = FALSE,
register_routines = FALSE
) {
options <- build_setup(
path,
dest_path,
binary,
vignettes,
manual,
clean_doc,
args,
needs_compilation,
compile_attributes,
register_routines,
quiet
)
on.exit(unlink(options$out_dir, recursive = TRUE), add = TRUE)
withr_with_makevars(
compiler_flags(debug = FALSE),
{
output <- withr_with_temp_libpaths(
rcmd_build_tools(
options$cmd,
c(options$path, options$args),
wd = options$out_dir,
fail_on_status = TRUE,
required = FALSE, # already checked in setup
quiet = quiet
)
)
if (
should_stop_for_warnings() &&
grepl("\n\\s*warning:", output$stdout, ignore.case = TRUE)
) {
cli::cli_alert_warning(
"Stopping as requested for a warning during {.code R CMD build}."
)
if (quiet) {
cli::cli_alert_warning("The full output is printed below.")
cli::cli_verbatim(output$stdout)
}
stop("converted from `R CMD build` warning.")
}
out_file <- dir(options$out_dir)
file.copy(
file.path(options$out_dir, out_file),
options$dest_path,
overwrite = TRUE
)
if (is_dir(options$dest_path)) {
file.path(options$dest_path, out_file)
} else {
options$dest_path
}
}
)
}
build_setup <- function(
path,
dest_path,
binary,
vignettes,
manual,
clean_doc,
args,
needs_compilation,
compile_attributes,
register_routines,
quiet
) {
if (!file.exists(path)) {
stop("`path` must exist", call. = FALSE)
}
if (!is_dir(path)) {
if (!binary) stop("`binary` must be TRUE for package files", call. = FALSE)
if (compile_attributes) {
stop(
"`compile_attributes` must be FALSE for package files",
call. = FALSE
)
}
if (register_routines) {
stop("`register_routines` must be FALSE for package files", call. = FALSE)
}
} else {
path <- pkg_path(path)
}
if (is.null(dest_path)) {
dest_path <- dirname(path)
}
if (binary) {
build_setup_binary(path, dest_path, args, needs_compilation)
} else {
build_setup_source(
path,
dest_path,
vignettes,
manual,
clean_doc,
args,
needs_compilation,
compile_attributes,
register_routines,
quiet
)
}
}
build_setup_binary <- function(path, dest_path, args, needs_compilation) {
if (needs_compilation) {
check_build_tools(quiet = TRUE)
}
# Build in temporary directory and then copy to final location
out_dir <- tempfile()
dir.create(out_dir)
list(
cmd = "INSTALL",
path = normalizePath(path),
args = c("--build", args),
out_dir = out_dir,
dest_path = dest_path
)
}
build_setup_source <- function(
path,
dest_path,
vignettes,
manual,
clean_doc,
args,
needs_compilation,
compile_attributes,
register_routines,
quiet
) {
bootstrap_file <- file.path(path, "bootstrap.R")
run_bootstrap <- isTRUE(get_desc_config_flag(path, "bootstrap"))
if (file.exists(bootstrap_file) && run_bootstrap) {
if (!quiet) message("Running bootstrap.R...")
callr::rscript(
bootstrap_file,
wd = path,
stderr = "2>&1",
show = !quiet
)
}
if (needs_compilation) {
update_registration(path, compile_attributes, register_routines, quiet)
}
if (!("--resave-data" %in% args)) {
args <- c(args, "--no-resave-data")
}
if (!manual) {
args <- unique(c(args, "--no-manual"))
}
if (!vignettes) {
args <- unique(c(args, "--no-build-vignettes"))
}
no_manual <- "--no-manual" %in% args
if (!no_manual && !has_latex()) {
message("pdflatex not found! Not building PDF manual.")
manual <- FALSE
}
if (needs_compilation && (vignettes || manual)) {
check_build_tools(quiet = TRUE)
}
build_vignettes <- !("--no-build-vignettes" %in% args)
if (is.null(clean_doc)) {
clean_doc <- get_desc_config_flag(path, "clean-inst-doc")
}
if (build_vignettes && (is.null(clean_doc) || isTRUE(clean_doc))) {
doc_dir <- file.path(path, "inst", "doc")
if (dir.exists(doc_dir)) {
if (is.null(clean_doc) && interactive()) {
message(
"Building the package will delete...\n '",
doc_dir,
"'\nAre you sure?"
)
res <- utils::menu(c("Yes", "No"))
if (res == 2) {
return()
}
}
unlink(doc_dir, recursive = TRUE)
}
}
# Build in temporary directory and then copy to final location
out_dir <- tempfile()
dir.create(out_dir)
copy_method <- get_copy_method(path)
if (copy_method != "none") {
pkgname <- desc::desc_get("Package", path)
tmppath <- tempfile("build-")
copy_package_tree(path, tmppath, pkgname)
path <- file.path(tmppath, pkgname)
}
list(
cmd = "build",
path = normalizePath(path),
args = args,
out_dir = out_dir,
dest_path = dest_path
)
}