blob: 7814e39a20883bd84bd6e927026de6f3ba772b42 [file] [log] [blame]
# File src/library/tools/R/build.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2018 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
#### R based engine for R CMD build
## R developers can use this to debug the function by running it
## directly as tools:::.build_packages(args), where the args should
## be what commandArgs(TRUE) would return, that is a character vector
## of (space-delimited) terms that would be passed to R CMD build.
writeDefaultNamespace <-
function(filename, desc = file.path(dirname(filename), "DESCRIPTION"))
{
pkgInfo <- .split_description(.read_description(desc))
pkgs <- unique(c(names(pkgInfo$Imports), names(pkgInfo$Depends)))
pkgs <- pkgs[pkgs != "base"]
writeLines(c("# Default NAMESPACE created by R",
"# Remove the previous line if you edit this file",
"",
"# Export all names",
"exportPattern(\".\")",
if (length(pkgs))
c("",
"# Import all packages listed as Imports or Depends",
"import(",
paste0(" ", pkgs, collapse = ",\n"),
")")),
filename)
}
### formerly Perl R::Utils::get_exclude_patterns
## Return list of file patterns excluded by R CMD build and check.
## Kept here so that we ensure that the lists are in sync, but not exported.
## Has Unix-style '/' path separators hard-coded, but that is what dir() uses.
get_exclude_patterns <- function()
c("^\\.Rbuildignore$",
"(^|/)\\.DS_Store$",
"^\\.(RData|Rhistory)$",
"~$", "\\.bak$", "\\.swp$",
"(^|/)\\.#[^/]*$", "(^|/)#[^/]*#$",
## Outdated ...
"^TITLE$", "^data/00Index$",
"^inst/doc/00Index\\.dcf$",
## Autoconf
"^config\\.(cache|log|status)$",
"(^|/)autom4te\\.cache$", # ncdf4 had this in subdirectory 'tools'
## Windows dependency files
"^src/.*\\.d$", "^src/Makedeps$",
## IRIX, of some vintage
"^src/so_locations$",
## Sweave detrius
"^inst/doc/Rplots\\.(ps|pdf)$"
)
## Check for files listed in .Rbuildignore or get_exclude_patterns()
inRbuildignore <- function(files, pkgdir) {
exclude <- rep.int(FALSE, length(files))
ignore <- get_exclude_patterns()
## handle .Rbuildignore:
## 'These patterns should be Perl regexps, one per line,
## to be matched against the file names relative to
## the top-level source directory.'
ignore_file <- file.path(pkgdir, ".Rbuildignore")
if (file.exists(ignore_file))
ignore <- c(ignore, readLines(ignore_file, warn = FALSE))
for(e in ignore[nzchar(ignore)])
exclude <- exclude | grepl(e, files, perl = TRUE,
ignore.case = TRUE)
exclude
}
### based on Perl build script
.build_packages <- function(args = NULL, no.q = interactive())
{
## on Windows, this requires sh make
WINDOWS <- .Platform$OS.type == "windows"
Sys.umask("022") # Perl version did not have this.
writeLinesNL <- function(text, file)
{
## a version that uses NL line endings everywhere
con <- file(file, "wb")
on.exit(close(con))
writeLines(text, con)
}
## This version of system_with_capture merges stdout and stderr
## Used to run R to install package and build vignettes.
system_with_capture <- function (command, args) {
outfile <- tempfile("xshell")
on.exit(unlink(outfile))
status <- system2(command, args, stdout=outfile, stderr=outfile)
list(status = status, stdout = readLines(outfile, warn = FALSE))
}
## Run silently
Ssystem <- function(command, args = character(), ...)
system2(command, args, stdout = NULL, stderr = NULL, ...)
do_exit <-
if(no.q)
function(status) (if(status) stop else message)(
".build_packages() exit status ", status)
else
function(status) q("no", status = status, runLast = FALSE)
## Used for BuildVignettes, BuildManual, BuildKeepEmpty,
## and (character not logical) BuildResaveData
parse_description_field <-
function(desc, field, default = TRUE, logical = TRUE)
str_parse(desc[field], default=default, logical=logical)
Usage <- function() {
cat("Usage: R CMD build [options] pkgdirs",
"",
"Build R packages from package sources in the directories specified by",
sQuote("pkgdirs"),
"",
"Options:",
" -h, --help print short help message and exit",
" -v, --version print version info and exit",
"",
" --force force removal of INDEX file",
" --keep-empty-dirs do not remove empty dirs",
" --no-build-vignettes do not (re)build package vignettes",
" --no-manual do not build the PDF manual even if \\Sexprs are present",
" --resave-data= re-save data files as compactly as possible:",
' "no", "best", "gzip" (default)',
" --resave-data same as --resave-data=best",
" --no-resave-data same as --resave-data=no",
" --compact-vignettes= try to compact PDF files under inst/doc:",
' "no" (default), "qpdf", "gs", "gs+qpdf", "both"',
" --compact-vignettes same as --compact-vignettes=qpdf",
" --compression= type of compression to be used on tarball:",
' "gzip" (default), "none", "bzip2", "xz"',
" --md5 add MD5 sums",
" --log log to file 'pkg-00build.log' when processing ",
" the pkgdir with basename 'pkg'",
"",
"Report bugs at <https://bugs.R-project.org>.", sep = "\n")
}
add_build_stamp_to_description_file <- function(ldpath, pkgdir)
{
db <- .read_description(ldpath)
if(dir.exists(file.path(pkgdir, "src")))
db["NeedsCompilation"] <- "yes"
else if(is.na(db["NeedsCompilation"]))
db["NeedsCompilation"] <- "no"
## this is an optional function, so could fail
user <- Sys.info()["user"]
if(user == "unknown") user <- Sys.getenv("LOGNAME")
db["Packaged"] <-
sprintf("%s; %s",
format(Sys.time(), '', tz = 'UTC', usetz = TRUE),
user)
.write_description(db, ldpath)
}
## <FIXME>
## This should really be combined with
## add_build_stamp_to_description_file().
## Also, the build code reads DESCRIPTION files too often ...
add_expanded_R_fields_to_description_file <- function(ldpath) {
db <- .read_description(ldpath)
fields <- .expand_package_description_db_R_fields(db)
if(length(fields))
.write_description(c(db, fields), ldpath)
}
## </FIXME>
temp_install_pkg <- function(pkgdir, libdir) {
dir.create(libdir, mode = "0755", showWarnings = FALSE)
install_missing_dependencies <-
config_val_to_logical(Sys.getenv("_R_BUILD_INSTALL_MISSING_DEPENDENCIES_",
"no"))
if(install_missing_dependencies &&
all((repos <- getOption("repos")) != "@CRAN@")) {
## try installing missing dependencies too
available <- utils::available.packages(repos = repos)
db <- .read_description(file.path(pkgdir, "DESCRIPTION"))
package <- db["Package"]
available <-
rbind(available[available[, "Package"] != package, ,
drop = FALSE],
db[colnames(available)])
depends <- package_dependencies(package, available,
which = "most")
depends <- setdiff(unlist(depends),
utils::installed.packages())
if(length(depends)) {
message(paste(strwrap(sprintf("installing dependencies %s",
paste(sQuote(sort(depends)),
collapse = ", ")),
exdent = 2L),
collapse = "\n"), domain = NA)
utils::install.packages(depends,
libdir,
available =
available[-nrow(available), ,
drop = FALSE],
dependencies = NA)
}
}
## assume vignettes only need one arch
if (WINDOWS) {
cmd <- file.path(R.home("bin"), "Rcmd.exe")
args <- c("INSTALL -l", shQuote(libdir),
"--no-multiarch", shQuote(pkgdir))
} else {
cmd <- file.path(R.home("bin"), "R")
args <- c("CMD", "INSTALL -l", shQuote(libdir),
"--no-multiarch", shQuote(pkgdir))
}
res <- system_with_capture(cmd, args)
if (res$status) {
printLog(Log, " -----------------------------------\n")
printLog0(Log, paste(c(res$stdout, ""), collapse = "\n"))
printLog(Log, " -----------------------------------\n")
unlink(libdir, recursive = TRUE)
printLog(Log, "ERROR: package installation failed\n")
do_exit(1L)
}
Sys.setenv("R_BUILD_TEMPLIB" = libdir)
TRUE
} ## {temp_install_pkg}
prepare_pkg <- function(pkgdir, desc, Log)
{
owd <- setwd(pkgdir); on.exit(setwd(owd))
## pkgname <- basename(pkgdir)
checkingLog(Log, "DESCRIPTION meta-information")
res <- try(.check_package_description("DESCRIPTION"))
if (inherits(res, "try-error")) {
resultLog(Log, "ERROR")
messageLog(Log, "running '.check_package_description' failed")
} else {
if (any(lengths(res))) {
resultLog(Log, "ERROR")
print(res) # FIXME print to Log?
do_exit(1L)
} else resultLog(Log, "OK")
}
cleanup_pkg(pkgdir, Log)
libdir <- tempfile("Rinst")
ensure_installed <- function()
if (!pkgInstalled) {
messageLog(Log,
"installing the package to build vignettes")
pkgInstalled <<- temp_install_pkg(pkgdir, libdir)
}
pkgInstalled <- build_Rd_db(pkgdir, libdir, desc)
if (file.exists("INDEX")) update_Rd_index("INDEX", "man", Log)
doc_dir <- file.path("inst", "doc")
if ("makefile" %in% dir(doc_dir)) { # avoid case-insensitive match
messageLog(Log, "renaming 'inst/doc/makefile' to 'inst/doc/Makefile'")
file.rename(file.path(doc_dir, "makefile"),
file.path(doc_dir, "Makefile"))
}
if (vignettes &&
parse_description_field(desc, "BuildVignettes", TRUE)) {
vignette_index_path <- file.path("build", "vignette.rds")
if(file.exists(vignette_index_path))
unlink(vignette_index_path)
## this is not a logical field
## if (nchar(parse_description_field(desc, "VignetteBuilder", "")))
## ensure_installed()
## PR#15775: check VignetteBuilder packages are installed
## This is a bit wasteful: we do not need them in this process
loadVignetteBuilder(pkgdir, TRUE)
## Look for vignette sources
vigns <- pkgVignettes(dir = '.', check = TRUE)
if (!is.null(vigns) && length(vigns$docs)) {
ensure_installed()
## Good to do this in a separate process: it might die
creatingLog(Log, "vignettes")
R_LIBS <- Sys.getenv("R_LIBS", NA_character_)
if (!is.na(R_LIBS)) {
on.exit(Sys.setenv(R_LIBS = R_LIBS), add = TRUE)
Sys.setenv(R_LIBS = path_and_libPath(libdir, R_LIBS))
} else { # no .libPaths() here (speed; ok ?)
on.exit(Sys.unsetenv("R_LIBS"), add = TRUE)
Sys.setenv(R_LIBS = libdir)
}
## Tangle (and weave) all vignettes now.
cmd <- file.path(R.home("bin"), "Rscript")
args <- c("--vanilla",
"--default-packages=", # some vignettes assume methods
"-e", shQuote("tools::buildVignettes(dir = '.', tangle = TRUE)"))
## since so many people use 'R CMD' in Makefiles,
oPATH <- Sys.getenv("PATH")
Sys.setenv(PATH = paste(R.home("bin"), oPATH,
sep = .Platform$path.sep))
res <- system_with_capture(cmd, args)
Sys.setenv(PATH = oPATH)
if (res$status) {
resultLog(Log, "ERROR")
printLog0(Log, paste(c(res$stdout, ""), collapse = "\n"))
do_exit(1L)
} else {
# Rescan for weave and tangle output files
vigns <- pkgVignettes(dir = '.', output = TRUE, source = TRUE)
stopifnot(!is.null(vigns))
resultLog(Log, "OK")
}
## We may need to install them.
if (basename(vigns$dir) == "vignettes") {
## inst may not yet exist
dir.create(doc_dir, recursive = TRUE, showWarnings = FALSE)
tocopy <- unique(c(vigns$docs, vigns$outputs,
unlist(vigns$sources)))
copied <- file.copy(tocopy, doc_dir, copy.date = TRUE)
if (!all(copied)) {
warning(sprintf(ngettext(sum(!copied),
"%s file\n", "%s files\n"),
sQuote("inst/doc")),
strwrap(paste(sQuote(basename(tocopy[!copied])), collapse=", "),
indent = 4, exdent = 2),
"\n ignored as vignettes have been rebuilt.",
"\n Run R CMD build with --no-build-vignettes to prevent rebuilding.",
call. = FALSE)
file.copy(tocopy[!copied], doc_dir, overwrite = TRUE, copy.date = TRUE)
}
unlink(c(vigns$outputs, unlist(vigns$sources)))
extras_file <- file.path("vignettes", ".install_extras")
if (file.exists(extras_file)) {
extras <- readLines(extras_file, warn = FALSE)
if(length(extras)) {
allfiles <- dir("vignettes", all.files = TRUE,
full.names = TRUE, recursive = TRUE,
include.dirs = TRUE)
inst <- rep.int(FALSE, length(allfiles))
for (e in extras)
inst <- inst | grepl(e, allfiles, perl = TRUE,
ignore.case = TRUE)
file.copy(allfiles[inst], doc_dir, recursive = TRUE, copy.date = TRUE)
}
}
}
vignetteIndex <- .build_vignette_index(vigns)
if(NROW(vignetteIndex) > 0L) {
## remove any files with no R code (they will have header comments).
## if not correctly declared they might not be in the current encoding
sources <- vignetteIndex$R
for(i in seq_along(sources)) {
file <- file.path(doc_dir, sources[i])
if (!file_test("-f", file)) next
bfr <- readLines(file, warn = FALSE)
if(all(grepl("(^###|^[[:space:]]*$)", bfr, useBytes = TRUE))) {
unlink(file)
vignetteIndex$R[i] <- ""
}
}
}
## Save the list
dir.create("build", showWarnings = FALSE)
## version = 2L for maximal back-compatibility
saveRDS(vignetteIndex,
file = vignette_index_path,
version = 2L)
}
} else {
fv <- file.path("build", "vignette.rds")
if(file.exists(fv)) {
checkingLog(Log, "vignette meta-information")
db <- readRDS(fv)
pdfs <- file.path("inst", "doc", db[nzchar(db$PDF), ]$PDF)
missing <- !file.exists(pdfs)
if(any(missing)) {
msg <- c("Output(s) listed in 'build/vignette.rds' but not in package:",
strwrap(sQuote(pdfs[missing]), indent = 2L, exdent = 2L),
"Run R CMD build without --no-build-vignettes to re-create")
errorLog(Log, paste(msg, collapse = "\n"))
do_exit(1L)
} else resultLog(Log, "OK")
}
}
if (compact_vignettes != "no" &&
length(pdfs <- dir(doc_dir, pattern = "[.]pdf", recursive = TRUE,
full.names = TRUE))) {
messageLog(Log, "compacting vignettes and other PDF files")
if(compact_vignettes %in% c("gs", "gs+qpdf", "both")) {
gs_cmd <- find_gs_cmd()
gs_quality <- "ebook"
} else {
gs_cmd <- ""
gs_quality <- "none"
}
qpdf <-
if(compact_vignettes %in% c("qpdf", "gs+qpdf", "both"))
Sys.which(Sys.getenv("R_QPDF", "qpdf")) else ""
res <- compactPDF(pdfs, qpdf = qpdf,
gs_cmd = gs_cmd, gs_quality = gs_quality)
res <- format(res, diff = 1e5)
if(length(res))
printLog0(Log, paste0(" ", format(res), collapse = "\n"), "\n")
}
if (pkgInstalled) {
unlink(libdir, recursive = TRUE)
## And finally, clean up again.
cleanup_pkg(pkgdir, Log)
}
} ## {prepare_pkg}
cleanup_pkg <- function(pkgdir, Log)
{
owd <- setwd(pkgdir); on.exit(setwd(owd))
pkgname <- basename(pkgdir)
if (dir.exists("src")) {
setwd("src")
messageLog(Log, "cleaning src")
if (WINDOWS) {
have_make <- nzchar(Sys.which(Sys.getenv("MAKE", "make")))
if (file.exists("Makefile.win")) {
if (have_make)
Ssystem(Sys.getenv("MAKE", "make"), "-f Makefile.win clean")
else warning("unable to run 'make clean' in 'src'",
domain = NA)
} else {
if (file.exists("Makevars.win")) {
if (have_make) {
makefiles <- paste("-f",
shQuote(file.path(R.home("share"), "make", "clean.mk")),
"-f Makevars.win")
Ssystem(Sys.getenv("MAKE", "make"),
c(makefiles, "clean"))
} else warning("unable to run 'make clean' in 'src'",
domain = NA)
}
## Also cleanup possible leftovers ...
unlink(c(Sys.glob(c("*.o", "*.so", "*.dylib", "*.mod")),
paste0(pkgname, c(".a", ".dll", ".def")),
"symbols.rds"))
if (dir.exists(".libs")) unlink(".libs", recursive = TRUE)
if (dir.exists("_libs")) unlink("_libs", recursive = TRUE)
}
} else {
makefiles <- paste("-f",
shQuote(file.path(R.home("etc"),
Sys.getenv("R_ARCH"),
"Makeconf")))
if (file.exists("Makefile")) {
makefiles <- paste(makefiles, "-f", "Makefile")
Ssystem(Sys.getenv("MAKE", "make"), c(makefiles, "clean"))
} else {
if (file.exists("Makevars")) {
## ensure we do have a 'clean' target.
makefiles <- paste(makefiles, "-f",
shQuote(file.path(R.home("share"), "make", "clean.mk")),
"-f Makevars")
Ssystem(Sys.getenv("MAKE", "make"),
c(makefiles, "clean"))
}
## Also cleanup possible leftovers ...
unlink(c(Sys.glob(c("*.o", "*.so", "*.dylib", "*.mod")),
paste0(pkgname, c(".a", ".dll", ".def")),
"symbols.rds"))
if (dir.exists(".libs")) unlink(".libs", recursive = TRUE)
if (dir.exists("_libs")) unlink("_libs", recursive = TRUE)
}
}
}
setwd(owd)
## It is not clear that we want to do this: INSTALL should do so.
## Also, certain environment variables should be set according
## to 'Writing R Extensions', but were not in Perl version (nor
## was cleanup.win used).
if (WINDOWS) {
if (file.exists("cleanup.win")) {
## check we have sh.exe first
if (nzchar(Sys.which("sh.exe"))) {
Sys.setenv(R_PACKAGE_NAME = pkgname)
Sys.setenv(R_PACKAGE_DIR = pkgdir)
Sys.setenv(R_LIBRARY_DIR = dirname(pkgdir))
messageLog(Log, "running 'cleanup.win'")
Ssystem("sh", "./cleanup.win")
}
}
} else if (file_test("-x", "cleanup")) {
Sys.setenv(R_PACKAGE_NAME = pkgname)
Sys.setenv(R_PACKAGE_DIR = pkgdir)
Sys.setenv(R_LIBRARY_DIR = dirname(pkgdir))
messageLog(Log, "running 'cleanup'")
Ssystem("./cleanup")
}
}
update_Rd_index <- function(oldindex, Rd_files, Log)
{
newindex <- tempfile()
res <- tryCatch(
Rdindex(Rd_files, newindex),
error = function(e) {
errorLog(Log, "computing Rd index failed:",
conditionMessage(e))
do_exit(1L)
})
checkingLog(Log, "whether ", sQuote(oldindex), " is up-to-date")
if (file.exists(oldindex)) {
ol <- readLines(oldindex, warn = FALSE) # e.g. BaM had missing final NL
nl <- readLines(newindex)
if (!identical(ol, nl)) {
resultLog(Log, "NO")
if (force) {
messageLog(Log, "removing ", sQuote(oldindex),
" as '--force' was given")
unlink(oldindex)
} else {
messageLog(Log, "use '--force' to remove ",
"the existing ", sQuote(oldindex))
unlink(newindex)
}
} else {
resultLog(Log, "OK")
unlink(newindex)
}
} else {
resultLog(Log, "NO")
messageLog(Log, "creating new ", sQuote(oldindex))
file.rename(newindex, oldindex)
}
}
build_Rd_db <- function(pkgdir, libdir, desc) {
build_partial_Rd_db_path <-
file.path("build", "partial.rdb")
if(file.exists(build_partial_Rd_db_path))
unlink(build_partial_Rd_db_path)
## Use a full path as this could be passed to ..Rd2pdf().
build_refman_path <-
file.path(pkgdir, "build", paste0(basename(pkgdir), ".pdf"))
if(file.exists(build_refman_path))
unlink(build_refman_path)
db <- .build_Rd_db(pkgdir, stages = NULL,
os = c("unix", "windows"), step = 1)
if (!length(db)) return(FALSE)
# Strip the pkgdir off the names
names(db) <- substring(names(db),
nchar(file.path(pkgdir, "man")) + 2L)
containsSexprs <-
which(sapply(db, function(Rd) getDynamicFlags(Rd)["\\Sexpr"]))
if (!length(containsSexprs)) return(FALSE)
messageLog(Log, "installing the package to process help pages")
dir.create(libdir, mode = "0755", showWarnings = FALSE)
savelib <- .libPaths()
.libPaths(c(libdir, savelib))
on.exit(.libPaths(savelib), add = TRUE)
temp_install_pkg(pkgdir, libdir)
containsBuildSexprs <-
which(sapply(db, function(Rd) getDynamicFlags(Rd)["build"]))
if (length(containsBuildSexprs)) {
for (i in containsBuildSexprs)
db[[i]] <- prepare_Rd(db[[i]], stages = "build",
stage2 = FALSE, stage3 = FALSE)
messageLog(Log, "saving partial Rd database")
partial <- db[containsBuildSexprs]
dir.create("build", showWarnings = FALSE)
## version = 2L for maximal back-compatibility
saveRDS(partial, build_partial_Rd_db_path, version = 2L)
}
needRefman <- manual &&
parse_description_field(desc, "BuildManual", TRUE) &&
any(vapply(db,
function(Rd)
any(getDynamicFlags(Rd)[c("install", "render")]),
NA))
if (needRefman) {
messageLog(Log, "building the PDF package manual")
dir.create("build", showWarnings = FALSE)
..Rd2pdf(c("--force", "--no-preview",
paste0("--output=", build_refman_path),
pkgdir), quit = FALSE)
}
return(TRUE)
} ## {build_Rd_db}
## also fixes up missing final NL
fix_nonLF_in_files <- function(pkgname, dirPattern, Log)
{
sDir <- file.path(pkgname, c("src", "inst/include"))
files <- dir(sDir, pattern = dirPattern,
full.names = TRUE, recursive = TRUE)
for (ff in files) {
old_time <- file.mtime(ff)
lines <- readLines(ff, warn = FALSE)
writeLinesNL(lines, ff)
Sys.setFileTime(ff, old_time)
}
}
fix_nonLF_in_source_files <- function(pkgname, Log) {
fix_nonLF_in_files(pkgname, dirPattern = "\\.([cfh]|cc|cpp|hpp)$", Log)
}
fix_nonLF_in_make_files <- function(pkgname, Log) {
fix_nonLF_in_files(pkgname,
paste0("^(",
paste(c("Makefile", "Makefile.in", "Makefile.win",
"Makevars", "Makevars.in", "Makevars.win"),
collapse = "|"), ")$"), Log)
## Other Makefiles
makes <- dir(pkgname, pattern = "^Makefile$",
full.names = TRUE, recursive = TRUE)
for (ff in makes) {
lines <- readLines(ff, warn = FALSE)
writeLinesNL(lines, ff)
}
}
fix_nonLF_in_config_files <- function(pkgname, Log) {
files <- dir(pkgname, pattern = "^(configure|cleanup)$",
full.names = TRUE, recursive = TRUE)
## FIXME: This "destroys" all timestamps
for (ff in files) {
lines <- readLines(ff, warn = FALSE)
writeLinesNL(lines, ff)
}
}
find_empty_dirs <- function(d)
{
## dir(recursive = TRUE) did not include directories, so
## we needed to do this recursively
files <- dir(d, all.files = TRUE, full.names = TRUE)
for (dd in files[dir.exists(files)]) {
if (grepl("/\\.+$", dd)) next
find_empty_dirs(dd)
}
## allow per-package override
keep_empty1 <- parse_description_field(desc, "BuildKeepEmpty",
keep_empty)
if (!keep_empty1) # might have removed a dir
files <- dir(d, all.files = TRUE, full.names = TRUE)
if (length(files) <= 2L) { # always has ., ..
if (keep_empty1) {
printLog(Log, "WARNING: directory ", sQuote(d), " is empty\n")
} else {
unlink(d, recursive = TRUE)
printLog(Log, "Removed empty directory ", sQuote(d), "\n")
}
}
}
fixup_R_dep <- function(pkgname, ver = "2.10")
{
desc <- .read_description(file.path(pkgname, "DESCRIPTION"))
Rdeps <- .split_description(desc)$Rdepends2
for(dep in Rdeps) {
if(dep$op != '>=') next
if(dep$version >= package_version(ver)) return()
}
on.exit(Sys.setlocale("LC_CTYPE", Sys.getlocale("LC_CTYPE")))
Sys.setlocale("LC_CTYPE", "C")
flatten <- function(x) {
if(length(x) == 3L)
paste0(x$name, " (", x$op, " ", x$version, ")")
else x[[1L]]
}
deps <- desc["Depends"]
desc["Depends"] <- if(!is.na(deps)) {
deps <- .split_dependencies(deps)
deps <- deps[names(deps) != "R"] # could be more than one
paste(c(sprintf("R (>= %s)", ver), sapply(deps, flatten)),
collapse = ", ")
} else sprintf("R (>= %s)", ver)
.write_description(desc, file.path(pkgname, "DESCRIPTION"))
printLog(Log,
" NB: this package now depends on R (>= ", ver, ")\n")
}
resave_data_rda <- function(pkgname, resave_data)
{
if (resave_data == "no") return()
ddir <- file.path(pkgname, "data")
if(resave_data == "best") {
files <- Sys.glob(c(file.path(ddir, "*.rda"),
file.path(ddir, "*.RData"),
file.path(pkgname, "R", "sysdata.rda")))
messageLog(Log, "re-saving image files")
resaveRdaFiles(files)
rdas <- checkRdaFiles(files)
if(any(rdas$compress %in% c("bzip2", "xz")))
fixup_R_dep(pkgname, "2.10")
} else {
## ddir need not exist if just R/sysdata.rda
rdas <- checkRdaFiles(Sys.glob(c(file.path(ddir, "*.rda"),
file.path(ddir, "*.RData"))))
if(nrow(rdas)) {
update <- with(rdas, ASCII | compress == "none" | version < 2)
if(any(update)) {
messageLog(Log, "re-saving image files")
resaveRdaFiles(row.names(rdas)[update], "gzip")
}
}
if(file.exists(f <- file.path(pkgname, "R", "sysdata.rda"))) {
rdas <- checkRdaFiles(f)
update <- with(rdas, ASCII | compress == "none" | version < 2)
if(any(update)) {
messageLog(Log, "re-saving sysdata.rda")
resaveRdaFiles(f, "gzip")
}
}
}
}
resave_data_others <- function(pkgname, resave_data)
{
if (resave_data == "no") return()
if(!dir.exists(ddir <- file.path(pkgname, "data")))
return()
ddir <- normalizePath(ddir)
dataFiles <- filtergrep("\\.(rda|RData)$",
list_files_with_type(ddir, "data"))
if (!length(dataFiles)) return()
resaved <- character()
on.exit(unlink(resaved))
Rs <- grep("\\.[Rr]$", dataFiles, value = TRUE)
if (length(Rs)) { # these might use .txt etc
messageLog(Log, "re-saving .R files as .rda")
## ensure utils is visible
## library("utils")
lapply(Rs, function(x){
envir <- new.env(hash = TRUE)
sys.source(x, chdir = TRUE, envir = envir)
## version = 2L for maximal back-compatibility
save(list = ls(envir, all.names = TRUE),
file = sub("\\.[Rr]$", ".rda", x),
compress = TRUE, compression_level = 9,
envir = envir,
version = 2L)
resaved <<- c(resaved, x)
})
printLog(Log,
" NB: *.R converted to .rda: other files may need to be removed\n")
}
tabs <- grep("\\.(CSV|csv|TXT|tab|txt)$", dataFiles, value = TRUE)
if (length(tabs)) {
messageLog(Log, "re-saving tabular files")
if (resave_data == "gzip") {
lapply(tabs, function(nm) {
## DiceDesign/data/greenwood.table.txt is missing NL
x <- readLines(nm, warn = FALSE)
con <- gzfile(paste0(nm, ".gz"), "wb")
writeLines(x, con)
close(con)
resaved <<- c(resaved, nm)
})
} else {
OK <- TRUE
lapply(tabs, function(nm) {
x <- readLines(nm, warn = FALSE)
nm3 <- paste(nm, c("gz", "bz2", "xz"), sep = ".")
con <- gzfile(nm3[1L], "wb", compression = 9L); writeLines(x, con); close(con)
con <- bzfile(nm3[2L], "wb", compression = 9L); writeLines(x, con); close(con)
con <- xzfile(nm3[3L], "wb", compression = 9L); writeLines(x, con); close(con)
sizes <- file.size(nm3) * c(0.9, 1, 1)
ind <- which.min(sizes)
if(ind > 1) OK <<- FALSE
resaved <<- c(resaved, nm, nm3[-ind])
})
if (!OK) fixup_R_dep(pkgname, "2.10")
}
}
} ## {resave_data_others}
force <- FALSE
vignettes <- TRUE
manual <- TRUE # Install the manual if Rds contain \Sexprs
with_md5 <- FALSE
with_log <- FALSE
## INSTALL_opts <- character()
pkgs <- character()
options(showErrorCalls = FALSE, warn = 1)
## Read in build environment file.
Renv <- Sys.getenv("R_BUILD_ENVIRON", unset = NA_character_)
if(!is.na(Renv)) {
## Do not read any build environment file if R_BUILD_ENVIRON is
## set to empty of something non-existent.
if(nzchar(Renv) && file.exists(Renv)) readRenviron(Renv)
} else {
## Read in ~/.R/build.Renviron[.rarch] (if existent).
rarch <- .Platform$r_arch
if (nzchar(rarch) &&
file.exists(Renv <- paste0("~/.R/build.Renviron.", rarch)))
readRenviron(Renv)
else if (file.exists(Renv <- "~/.R/build.Renviron"))
readRenviron(Renv)
}
## Configurable variables.
compact_vignettes <- Sys.getenv("_R_BUILD_COMPACT_VIGNETTES_", "no")
resave_data <- Sys.getenv("_R_BUILD_RESAVE_DATA_", "gzip")
keep_empty <-
config_val_to_logical(Sys.getenv("_R_BUILD_KEEP_EMPTY_DIRS_", "FALSE"))
if (is.null(args)) {
args <- commandArgs(TRUE)
## it seems that splits on spaces, so try harder.
args <- paste(args, collapse = " ")
args <- strsplit(args,'nextArg', fixed = TRUE)[[1L]][-1L]
}
compression <- "gzip"
while(length(args)) {
a <- args[1L]
if (a %in% c("-h", "--help")) {
Usage()
do_exit(0L)
}
else if (a %in% c("-v", "--version")) {
cat("R add-on package builder: ",
R.version[["major"]], ".", R.version[["minor"]],
" (r", R.version[["svn rev"]], ")\n", sep = "")
cat("",
.R_copyright_msg(1997),
"This is free software; see the GNU General Public License version 2",
"or later for copying conditions. There is NO warranty.",
sep = "\n")
do_exit(0L)
} else if (a == "--force") {
force <- TRUE
} else if (a == "--keep-empty-dirs") {
keep_empty <- TRUE
} else if (a == "--no-build-vignettes") {
vignettes <- FALSE
} else if (a == "--no-vignettes") { # pre-3.0.0 version
stop("'--no-vignettes' is defunct:\n use '--no-build-vignettes' instead",
call. = FALSE, domain = NA)
} else if (a == "--resave-data") {
resave_data <- "best"
} else if (a == "--no-resave-data") {
resave_data <- "no"
} else if (substr(a, 1, 14) == "--resave-data=") {
resave_data <- substr(a, 15, 1000)
} else if (a == "--no-manual") {
manual <- FALSE
} else if (substr(a, 1, 20) == "--compact-vignettes=") {
compact_vignettes <- substr(a, 21, 1000)
} else if (a == "--compact-vignettes") {
compact_vignettes <- "qpdf"
} else if (a == "--md5") {
with_md5 <- TRUE
} else if (a == "--log") {
with_log <- TRUE
} else if (substr(a, 1, 14) == "--compression=") {
compression <- match.arg(substr(a, 15, 1000),
c("none", "gzip", "bzip2", "xz"))
} else if (startsWith(a, "-")) {
message("Warning: unknown option ", sQuote(a))
} else pkgs <- c(pkgs, a)
args <- args[-1L]
}
if(compact_vignettes %notin% c("no", "qpdf", "gs", "gs+qpdf", "both")) {
warning(gettextf("invalid value for '--compact-vignettes', assuming %s",
"\"qpdf\""),
domain = NA)
compact_vignettes <-"qpdf"
}
Sys.unsetenv("R_DEFAULT_PACKAGES")
startdir <- getwd()
if (is.null(startdir))
stop("current working directory cannot be ascertained")
## R_platform <- Sys.getenv("R_PLATFORM", "unknown-binary")
## libdir <- tempfile("Rinst")
if (WINDOWS) {
## Some people have *assumed* that R_HOME uses / in Makefiles
## Spaces in paths might still cause trouble.
rhome <- chartr("\\", "/", R.home())
Sys.setenv(R_HOME = rhome)
}
for(pkg in pkgs) {
## remove any trailing /, for Windows' sake
pkg <- sub("/$", "", pkg)
## Argh. For logging we should really know the actual name of
## the package being built, but this needs first establishing
## the actual pkgdir (see below) and then getting the package
## name from the DESCRIPTION file ... and problems in these
## steps (currently) already get logged. So for now try using
## the basename of pkg (one could try renaming at the end, but
## that will only work in case of success ...)
Log <- if(with_log)
newLog(paste0(file.path(startdir, basename(pkg)),
"-00build.log"))
else
newLog()
## 'Older versions used $pkg as absolute or relative to $startdir.
## This does not easily work if $pkg is a symbolic link.
## Hence, we now convert to absolute paths.'
setwd(startdir)
res <- tryCatch(setwd(pkg), error = function(e) {
errorLog(Log, "cannot change to directory ", sQuote(pkg))
do_exit(1L)
})
pkgdir <- getwd()
pkgname <- basename(pkgdir)
checkingLog(Log, "for file ", sQuote(file.path(pkg, "DESCRIPTION")))
f <- file.path(pkgdir, "DESCRIPTION")
if (file.exists(f)) {
desc <- try(.read_description(f))
if (inherits(desc, "try-error") || !length(desc)) {
resultLog(Log, "EXISTS but not correct format")
do_exit(1L)
}
resultLog(Log, "OK")
} else {
resultLog(Log, "NO")
do_exit(1L)
}
if(is.na(intname <- desc["Package"]) || !length(intname) ||
!nchar(intname)) {
errorLog(Log, "invalid 'Package' field"); do_exit(1L)
}
## make a copy, cd to parent of copy
setwd(dirname(pkgdir))
filename <- paste0(intname, "_", desc["Version"], ".tar")
filepath <- file.path(startdir, filename)
Tdir <- tempfile("Rbuild")
dir.create(Tdir, mode = "0755")
if (WINDOWS) {
## This preserves read-only for files, and dates
if (!file.copy(pkgname, Tdir, recursive = TRUE, copy.date = TRUE)) {
errorLog(Log, "copying to build directory failed")
do_exit(1L)
}
} else {
## This should preserve dates and permissions (subject to
## umask, if that is consulted which it seems it usually is not).
## Permissions are increased later.
## -L is to follow (de-reference) symlinks
## --preserve is GNU only: at least macOS, FreeBSD and Solaris
## have non-GNU cp's as it seems do some Linuxen.
ver <- suppressWarnings(system2("cp", "--version", stdout = TRUE,
stderr = FALSE))
GNU_cp <- any(grepl("GNU coreutils", ver))
cp_sw <- if(GNU_cp) "-LR --preserve=timestamps" else "-pLR"
if (system2("cp", c(cp_sw, shQuote(pkgname), shQuote(Tdir)))) {
errorLog(Log, "copying to build directory failed")
do_exit(1L)
}
}
setwd(Tdir)
## Now correct the package name (PR#9266)
if (pkgname != intname) {
if (!file.rename(pkgname, intname)) {
message(gettextf("Error: cannot rename directory to %s",
sQuote(intname)), domain = NA)
do_exit(1L)
}
pkgname <- intname
}
## prepare the copy
messageLog(Log, "preparing ", sQuote(pkgname), ":")
prepare_pkg(normalizePath(pkgname, "/"), desc, Log);
owd <- setwd(pkgname)
## remove exclude files
allfiles <- dir(".", all.files = TRUE, recursive = TRUE,
full.names = TRUE, include.dirs = TRUE)
allfiles <- substring(allfiles, 3L) # drop './'
bases <- basename(allfiles)
exclude <- inRbuildignore(allfiles, pkgdir)
isdir <- dir.exists(allfiles)
## old (pre-2.10.0) dirnames
exclude <- exclude | (isdir & (bases %in%
c("check", "chm", .vc_dir_names)))
exclude <- exclude | (isdir & grepl("([Oo]ld|\\.Rcheck)$", bases))
## FIXME: GNU make uses GNUmakefile (note capitalization)
exclude <- exclude | bases %in% c("Read-and-delete-me", "GNUMakefile")
## Mac resource forks
exclude <- exclude | startsWith(bases, "._")
exclude <- exclude | (isdir & grepl("^src.*/[.]deps$", allfiles))
## Windows DLL resource file
exclude <- exclude | (allfiles == paste0("src/", pkgname, "_res.rc"))
## inst/doc/.Rinstignore is a mistake
exclude <- exclude | endsWith(allfiles, "inst/doc/.Rinstignore") |
endsWith(allfiles, "inst/doc/.build.timestamp") |
endsWith(allfiles, "vignettes/.Rinstignore")
## leftovers
exclude <- exclude | grepl("^.Rbuildindex[.]", allfiles)
## or simply? exclude <- exclude | startsWith(allfiles, ".Rbuildindex.")
exclude <- exclude | (bases %in% .hidden_file_exclusions)
unlink(allfiles[exclude], recursive = TRUE, force = TRUE)
setwd(owd)
## Fix up man, R, demo inst/doc directories
res <- .check_package_subdirs(pkgname, TRUE)
if (any(lengths(res))) {
messageLog(Log, "excluding invalid files")
print(res) # FIXME print to Log?
}
setwd(Tdir)
## Fix permissions for all files to be at least 644, and dirs 755
## Not restricted by umask.
if (!WINDOWS) .Call(C_dirchmod, pkgname, group.writable=FALSE)
## Add build stamp to the DESCRIPTION file.
add_build_stamp_to_description_file(file.path(pkgname, "DESCRIPTION"),
pkgdir)
## Add expanded R fields to the DESCRIPTION file.
add_expanded_R_fields_to_description_file(file.path(pkgname,
"DESCRIPTION"))
messageLog(Log,
"checking for LF line-endings in source and make files and shell scripts")
fix_nonLF_in_source_files(pkgname, Log)
fix_nonLF_in_make_files(pkgname, Log)
fix_nonLF_in_config_files(pkgname, Log)
messageLog(Log, "checking for empty or unneeded directories");
find_empty_dirs(pkgname)
for(dir in c("Meta", "R-ex", "chtml", "help", "html", "latex")) {
d <- file.path(pkgname, dir)
if (dir.exists(d)) {
msg <- paste("WARNING: Removing directory",
sQuote(d),
"which should only occur",
"in an installed package")
printLog(Log, paste(strwrap(msg, indent = 0L, exdent = 2L),
collapse = "\n"), "\n")
unlink(d, recursive = TRUE)
}
}
## remove subarch build directories
unlink(file.path(pkgname,
c("src-i386", "src-x64", "src-x86_64", "src-ppc")),
recursive = TRUE)
## work on 'data' directory if present
if(dir.exists(file.path(pkgname, "data")) ||
file_test("-f", file.path(pkgname, "R", "sysdata.rda"))) {
messageLog(Log, "looking to see if a 'data/datalist' file should be added")
## in some cases data() needs the package installed as
## there are links to the package's namespace
tryCatch(add_datalist(pkgname),
error = function(e)
printLog(Log, " unable to create a 'datalist' file: may need the package to be installed\n"))
## allow per-package override
resave_data1 <- parse_description_field(desc, "BuildResaveData",
resave_data, logical=FALSE)
resave_data_others(pkgname, resave_data1)
resave_data_rda(pkgname, resave_data1)
}
## add dependency on R >= 3.5.0 to DESCRIPTION if there are files in
## serialization version 3
desc <- .read_description(file.path(pkgname, "DESCRIPTION"))
Rdeps <- .split_description(desc)$Rdepends2
hasDep350 <- FALSE
for(dep in Rdeps) {
if(dep$op != '>=') next
if(dep$version >= "3.5.0") hasDep350 <- TRUE
}
if (!hasDep350) {
## re-read files after exclusions have been applied
allfiles <- dir(".", all.files = TRUE, recursive = TRUE,
full.names = TRUE)
allfiles <- substring(allfiles, 3L) # drop './'
vers <- get_serialization_version(allfiles)
toonew <- names(vers[vers >= 3L])
if (length(toonew)) {
fixup_R_dep(pkgname, "3.5.0")
msg <- paste("WARNING: Added dependency on R >= 3.5.0 because",
"serialized objects in serialize/load version 3",
"cannot be read in older versions of R. File(s)",
"containing such objects:",
.pretty_format(sort(toonew)),
"\n")
printLog(Log, strwrap(msg, indent = 2L, exdent = 2L), "\n")
}
}
## add NAMESPACE if the author didn't write one
if(!file.exists(namespace <- file.path(pkgname, "NAMESPACE")) ) {
messageLog(Log, "creating default NAMESPACE file")
writeDefaultNamespace(namespace)
}
if(with_md5) {
messageLog(Log, "adding MD5 file")
.installMD5sums(pkgname)
} else {
## remove any stale file
unlink(file.path(pkgname, "MD5"))
}
## Finalize
ext <- switch(compression,
"none"="", "gzip"= ".gz", "bzip2" = ".bz2", "xz" = ".xz")
filename <- paste0(pkgname, "_", desc["Version"], ".tar", ext)
filepath <- file.path(startdir, filename)
## NB: ../../../../tests/reg-packages.R relies on this exact format!
messageLog(Log, "building ", sQuote(filename))
res <- utils::tar(filepath, pkgname, compression = compression,
compression_level = 9L,
tar = Sys.getenv("R_BUILD_TAR"),
extra_flags = NULL) # use trapdoor
if (res) {
errorLog(Log, "packaging into tarball failed")
do_exit(1L)
}
message("") # blank line
setwd(startdir)
unlink(Tdir, recursive = TRUE)
closeLog(Log)
}
do_exit(0L)
}