|
| 1 | +# configure-database.R ---- |
| 2 | + |
| 3 | +#' Retrieve the Global Configuration Database |
| 4 | +#' |
| 5 | +#' Retrieve the global configuration database (as an \R environment). |
| 6 | +#' |
| 7 | +#' @family configure-db |
| 8 | +#' |
| 9 | +#' @export |
| 10 | +configure_database<- local({ |
| 11 | +database<- new.env(parent= emptyenv()) |
| 12 | +function() database |
| 13 | +}) |
| 14 | + |
| 15 | +#' Define Variables for the Configuration Database |
| 16 | +#' |
| 17 | +#' Define variables to be used as part of the default configuration database. |
| 18 | +#' These will be used by [configure_file()] when no configuration database |
| 19 | +#' is explicitly supplied. |
| 20 | +#' |
| 21 | +#' @param ... A set of named arguments, mapping configuration names to values. |
| 22 | +#' |
| 23 | +#' @family configure-db |
| 24 | +#' |
| 25 | +#' @export |
| 26 | +configure_define<-function(...){ |
| 27 | +envir<- configure_database() |
| 28 | + list2env(list(...), envir=envir) |
| 29 | +} |
| 30 | + |
| 31 | +#' @export |
| 32 | +define<-configure_define |
| 33 | + |
| 34 | + |
| 35 | +# utils.R ---- |
| 36 | + |
| 37 | +#' Configure a File |
| 38 | +#' |
| 39 | +#' Configure a file, replacing any instances of `@`-delimited variables, e.g. |
| 40 | +#' `@VAR@`, with the value of the variable called `VAR` in the associated |
| 41 | +#' `config` environment. |
| 42 | +#' |
| 43 | +#' @param source The file to be configured. |
| 44 | +#' @param target The file to be generated. |
| 45 | +#' @param config The configuration database. |
| 46 | +#' @param verbose Boolean; report files as they are configured? |
| 47 | +#' |
| 48 | +#' @family configure |
| 49 | +#' |
| 50 | +#' @export |
| 51 | +configure_file<-function( |
| 52 | +source, |
| 53 | +target= sub("[.]in$", "", source), |
| 54 | +config= configure_database(), |
| 55 | +verbose= configure_verbose()) |
| 56 | +{ |
| 57 | +contents<- readLines(source, warn=FALSE) |
| 58 | + enumerate(config, function(key, val){ |
| 59 | +needle<- paste("@", key, "@", sep="") |
| 60 | +replacement<-val |
| 61 | +contents<<- gsub(needle, replacement, contents) |
| 62 | + }) |
| 63 | + |
| 64 | + ensure_directory(dirname(target)) |
| 65 | + writeLines(contents, con=target) |
| 66 | + |
| 67 | +info<- file.info(source) |
| 68 | + Sys.chmod(target, mode=info$mode) |
| 69 | + |
| 70 | +if (isTRUE(verbose)){ |
| 71 | +fmt<-"** configured file: '%s' => '%s'" |
| 72 | + message(sprintf(fmt, source, target)) |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +#' Configure Files in a Directory |
| 77 | +#' |
| 78 | +#' This companion function to [configure_file()] can be used to |
| 79 | +#' configure all `.in` files within a directory. |
| 80 | +#' |
| 81 | +#' @param path The path to a directory in which files should be configured. |
| 82 | +#' @param config The configuration database to be used. |
| 83 | +#' @param verbose Boolean; report files as they are configured? |
| 84 | +#' |
| 85 | +#' @family configure |
| 86 | +#' |
| 87 | +#' @export |
| 88 | +configure_directory<-function( |
| 89 | +path=".", |
| 90 | +config= configure_database(), |
| 91 | +verbose= configure_verbose()) |
| 92 | +{ |
| 93 | +files<- list.files( |
| 94 | +path=path, |
| 95 | +pattern="[.]in$", |
| 96 | +full.names=TRUE) |
| 97 | + |
| 98 | + lapply(files, configure_file, config=config, verbose=verbose) |
| 99 | +} |
| 100 | + |
| 101 | +configure_auto<-function(type){ |
| 102 | + configure_common(type=type) |
| 103 | +} |
| 104 | + |
| 105 | +configure_common<-function(type){ |
| 106 | + |
| 107 | +if (!isTRUE(getOption("configure.common", default=TRUE))) |
| 108 | +return(invisible(FALSE)) |
| 109 | + |
| 110 | +sources<- list.files( |
| 111 | +path= c("R", "src"), |
| 112 | +pattern="[.]in$", |
| 113 | +full.names=TRUE |
| 114 | + ) |
| 115 | + |
| 116 | +sources<- sub("[.]/", "", sources) |
| 117 | + |
| 118 | +if (type=="configure"){ |
| 119 | + lapply(sources, configure_file) |
| 120 | + } elseif (type=="cleanup"){ |
| 121 | +targets<- sub("[.]in$", "", sources) |
| 122 | + lapply(targets, remove_file) |
| 123 | + } |
| 124 | + |
| 125 | +invisible(TRUE) |
| 126 | +} |
| 127 | + |
| 128 | +#' Read R Configuration for a Package |
| 129 | +#' |
| 130 | +#' Read the \R configuration, as through `R CMD config`. |
| 131 | +#' |
| 132 | +#' @param ... The \R configuration values to read (as a character vector). |
| 133 | +#' If empty, all values are read as through `R CMD config --all`). |
| 134 | +#' @param package The path to the \R package's sources. |
| 135 | +#' @param envir The environment in which the configuration information should |
| 136 | +#' be assigned. By default, the [configure_database()] is populated with the |
| 137 | +#' requested values. |
| 138 | +#' @param verbose Boolean; notify the user as \R configuration is read? |
| 139 | +#' |
| 140 | +#' @export |
| 141 | +read_r_config<-function( |
| 142 | +..., |
| 143 | +package= Sys.getenv("R_PACKAGE_DIR", unset="."), |
| 144 | +envir= configure_database(), |
| 145 | +verbose= configure_verbose()) |
| 146 | +{ |
| 147 | +# move to requested directory |
| 148 | +owd<- setwd(package) |
| 149 | + on.exit(setwd(owd), add=TRUE) |
| 150 | +R<- file.path(R.home("bin"), "R") |
| 151 | + |
| 152 | +values<- unlist(list(...), recursive=TRUE) |
| 153 | +if (length(values) ==0){ |
| 154 | +if (verbose) |
| 155 | + message("** executing 'R CMD config --all'") |
| 156 | +output<- system2(R, c("CMD", "config", "--all"), stdout=TRUE) |
| 157 | +equalsIndex<- regexpr("=", output, fixed=TRUE) |
| 158 | +keys<- trim_whitespace(substring(output, 1, equalsIndex-1)) |
| 159 | +config<- as.list(trim_whitespace(substring(output, equalsIndex+1))) |
| 160 | + names(config) <-keys |
| 161 | + |
| 162 | + } else{ |
| 163 | +if (verbose) |
| 164 | + message("** executing 'R CMD config'") |
| 165 | +config<- lapply(values, function(value){ |
| 166 | + system2(R, c("CMD", "config", value), stdout=TRUE) |
| 167 | + }) |
| 168 | + names(config) <-values |
| 169 | + } |
| 170 | + |
| 171 | + list2env(config, envir=envir) |
| 172 | +} |
| 173 | + |
| 174 | +#' Concatenate the Contents of a Set of Files |
| 175 | +#' |
| 176 | +#' Given a set of files, concatenate their contents into |
| 177 | +#' a single file. |
| 178 | +#' |
| 179 | +#' @param sources An \R list of files |
| 180 | +#' @param target The file to use for generation. |
| 181 | +#' @param headers Headers to be used for each file copied. |
| 182 | +#' @param preamble Text to be included at the beginning of the document. |
| 183 | +#' @param postamble Text to be included at the end of the document. |
| 184 | +#' @param verbose Boolean; inform the user when the requested file is created? |
| 185 | +#' |
| 186 | +#' @export |
| 187 | +concatenate_files<-function( |
| 188 | +sources, |
| 189 | +target, |
| 190 | +headers= sprintf("# %s ----", basename(sources)), |
| 191 | +preamble=NULL, |
| 192 | +postamble=NULL, |
| 193 | +verbose= configure_verbose()) |
| 194 | +{ |
| 195 | +pieces<- vapply(seq_along(sources), function(i){ |
| 196 | +source<-sources[[i]] |
| 197 | +header<-headers[[i]] |
| 198 | +contents<- trim_whitespace(read_file(source)) |
| 199 | + paste(header, contents, "", sep="\n\n") |
| 200 | + }, character(1)) |
| 201 | + |
| 202 | +all<- c(preamble, pieces, postamble) |
| 203 | + |
| 204 | + ensure_directory(dirname(target)) |
| 205 | + writeLines(all, con=target) |
| 206 | + |
| 207 | +if (verbose){ |
| 208 | +fmt<-"** created file '%s'" |
| 209 | + message(sprintf(fmt, target)) |
| 210 | + } |
| 211 | + |
| 212 | +TRUE |
| 213 | +} |
| 214 | + |
| 215 | +#' Add Configure Infrastructure to an R Package |
| 216 | +#' |
| 217 | +#' Add the infrastructure needed to configure an R package. |
| 218 | +#' |
| 219 | +#' @param package The path to the top-level directory of an \R package. |
| 220 | +#' @export |
| 221 | +use_configure<-function(package="."){ |
| 222 | + |
| 223 | +# preserve working directory |
| 224 | +owd<- getwd() |
| 225 | + on.exit(setwd(owd), add=TRUE) |
| 226 | + |
| 227 | +# find resources |
| 228 | +package<- normalizePath(package, winslash="/") |
| 229 | +resources<- system.file("resources", package="configure") |
| 230 | + |
| 231 | +# copy into temporary directory |
| 232 | +dir<- tempfile("configure-") |
| 233 | + on.exit(unlink(dir, recursive=TRUE), add=TRUE) |
| 234 | + |
| 235 | + dir.create(dir) |
| 236 | + file.copy(resources, dir, recursive=TRUE) |
| 237 | + |
| 238 | +# rename resources directory |
| 239 | + setwd(dir) |
| 240 | + file.rename(basename(resources), basename(package)) |
| 241 | + |
| 242 | +# now, copy these files back into the target directory |
| 243 | + file.copy(basename(package), dirname(package), recursive=TRUE) |
| 244 | + |
| 245 | +# ensure DESCRIPTION contains 'Biarch: TRUE' for Windows |
| 246 | + setwd(package) |
| 247 | +DESCRIPTION<- read_file("DESCRIPTION") |
| 248 | +if (!grepl("(?:^|\n)Biarch:", DESCRIPTION)){ |
| 249 | +DESCRIPTION<- paste(DESCRIPTION, "Biarch: TRUE", sep="\n") |
| 250 | +DESCRIPTION<- gsub("\n{2,}", "\n", DESCRIPTION) |
| 251 | + cat(DESCRIPTION, file="DESCRIPTION", sep="\n") |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +ensure_directory<-function(dir){ |
| 256 | +info<- file.info(dir) |
| 257 | + |
| 258 | +# no file exists at this location; try to make it |
| 259 | +if (is.na(info$isdir)){ |
| 260 | + dir.create(info$isdir, recursive=TRUE, showWarnings=FALSE) |
| 261 | +if (!file.exists(dir)) |
| 262 | + stop("failed to create directory '", dir, "'") |
| 263 | +return(TRUE) |
| 264 | + } |
| 265 | + |
| 266 | +# a directory already exists |
| 267 | +if (isTRUE(info$isdir)) |
| 268 | +return(TRUE) |
| 269 | + |
| 270 | +# a file exists, but it's not a directory |
| 271 | + stop("file already exists at path '", dir, "'") |
| 272 | +} |
| 273 | + |
| 274 | +enumerate<-function(x, f, ...){ |
| 275 | +nms<-if (is.environment(x)) ls(envir=x) else names(x) |
| 276 | + lapply(nms, function(nm){ |
| 277 | + f(nm, x[[nm]], ...) |
| 278 | + }) |
| 279 | +} |
| 280 | + |
| 281 | +read_file<-function(path){ |
| 282 | + paste(readLines(path, warn=FALSE), collapse="\n") |
| 283 | +} |
| 284 | + |
| 285 | +remove_file<-function( |
| 286 | +path, |
| 287 | +verbose= configure_verbose()) |
| 288 | +{ |
| 289 | +info<- file.info(path) |
| 290 | +if (!is.na(info$isdir)){ |
| 291 | + unlink(path, recursive= isTRUE(info$isdir)) |
| 292 | +if (verbose){ |
| 293 | +fmt<-"** removed file '%s'" |
| 294 | + message(sprintf(fmt, path)) |
| 295 | + } |
| 296 | + } |
| 297 | + |
| 298 | +TRUE |
| 299 | +} |
| 300 | + |
| 301 | +source_file<-function( |
| 302 | +path, |
| 303 | +envir= parent.frame()) |
| 304 | +{ |
| 305 | +contents<- read_file(path) |
| 306 | +invisible(eval(parse(text=contents), envir=envir)) |
| 307 | +} |
| 308 | + |
| 309 | +trim_whitespace<-function(x){ |
| 310 | + gsub("^[[:space:]]*|[[:space:]]*$", "", x) |
| 311 | +} |
| 312 | + |
| 313 | +configure_verbose<-function(){ |
| 314 | + getOption("configure.verbose", !interactive()) |
| 315 | +} |
| 316 | + |
| 317 | + |
| 318 | +# run.R ---- |
| 319 | + |
| 320 | +local({ |
| 321 | + |
| 322 | +# extract path to install script |
| 323 | +args<- commandArgs(TRUE) |
| 324 | +type<-args[[1]] |
| 325 | + |
| 326 | +# report start of execution |
| 327 | +package<- Sys.getenv("R_PACKAGE_NAME", unset="<unknown>") |
| 328 | +fmt<-"* preparing to %s package '%s' ..." |
| 329 | + message(sprintf(fmt, type, package)) |
| 330 | + |
| 331 | +# execute the requested script |
| 332 | +path<- sprintf("tools/config/%s.R", type) |
| 333 | +if (file.exists(path)) source_file(path) |
| 334 | + |
| 335 | +# perform automatic configuration |
| 336 | + configure_auto(type=type) |
| 337 | + |
| 338 | +# report end of execution |
| 339 | +fmt<-"* finished %s for package '%s'" |
| 340 | + message(sprintf(fmt, type, package)) |
| 341 | +}) |
| 342 | + |
| 343 | + |
0 commit comments