Nothing
copy_package_tree <- function(
path = ".",
dest,
pkgname = desc::desc_get("Package", path)) {
if (!file.exists(dest)) mkdirp(dest)
pkgdir <- file.path(dest, pkgname)
if (file.exists(pkgdir)) {
stop(cli::format_error(c(
"Cannot copy package tree to {.path {dest}}",
i = "Directory {.path {pkgdir}} already exists, and did not want
to overwrite."
)))
}
mkdirp(pkgdir)
paths <- build_files(path, pkgname)
method <- get_copy_method(path)
for (i in seq_len(nrow(paths))) {
# excluded, skip
if (paths$exclude[i]) next
if (paths$isdir[i] && paths$trimmed[i]) {
# trimmed directory, only create directory
# we do not try to update the mode of the directory, as it is not
# very important for R packages, and it might fail on some file
# systems.
mkdirp(file.path(pkgdir, paths$path[i]))
} else {
# not a directory, or a non-trimmed directory, recurse
src <- paths$realpath[i]
tgt <- file.path(pkgdir, paths$path[i])
if (method == "link") {
file.symlink(src, tgt)
} else if (paths$isdir[i]) {
cp(src, dirname(tgt), recursive = TRUE)
} else {
cp(src, tgt)
}
}
}
# TODO: should we return a trimming summary?
invisible()
}
get_copy_method <- function(path = ".") {
method <- getOption("pkg.build_copy_method", NA_character_)
values <- c("none", "copy", "link")
check_method <- function(method) {
# no symlinks on Windows
if (method == "link" && is_windows()) method <- "copy"
if (method %in% values) return(method)
stop(cli::format_error(c(
"Invalid {.code pkg.build_copy_method} value: {.val {method}}.",
i = "It must be one of {.str {values}}."
)))
}
if (!is_string(method) && !is_na(method)) {
stop(cli::format_error(c(
"Invalid {.code pkg.build_copy_method} value.",
i = "It must be a string, but it is {.type {method}}."
)))
}
if (!is.na(method)) return(check_method(method))
method <- desc::desc_get("Config/build/copy-method", path)
if (!is.na(method)) return(check_method(method))
method <- Sys.getenv("PKG_BUILD_COPY_METHOD", "none")
check_method(method)
}
build_files <- function(
path = ".",
pkgname = desc::desc_get("Package", path)) {
path <- normalizePath(path)
# patterns in .Rbuildignore
ign_file <- file.path(path, ".Rbuildignore")
ign <- if (file.exists(ign_file)) {
readLines(ign_file, warn = FALSE)
} else {
character()
}
ign <- ign[nzchar(ign)]
# make it case insensitive, that's how R matches them
if (length(ign)) ign <- paste0("(?i)", ign)
ptrn <- c(ign, re_exclude(pkgname))
ptrn_dir <- re_exclude_dir(pkgname)
# filter at the top level first, so we don't need to enumerate these
top <- dir(path, include.dirs = TRUE, all.files=TRUE, no.. = TRUE)
# now filter top
realtop <- file.path(path, top)
topfls <- data.frame(
stringsAsFactors = FALSE,
path = top,
realpath = realtop,
exclude = logical(length(top)),
isdir = file.info(realtop)$isdir
)
topfls <- exclude(path, topfls, ptrn, ptrn_dir)
# now create the rest of the files
sub <- unlist(lapply(
topfls$path[topfls$isdir & !topfls$exclude],
function(t) {
tf <- dir(file.path(path, t), include.dirs = TRUE, all.files = TRUE,
recursive = TRUE)
tf <- file.path(t, tf)
}
))
realsub <- file.path(path, sub)
subfls <- data.frame(
stringsAsFactors = FALSE,
path = sub,
realpath = realsub,
exclude = logical(length(sub)),
isdir = file.info(realsub)$isdir
)
subfls <- exclude(path, subfls, ptrn, ptrn_dir)
allfls <- rbind(topfls, subfls)
# Always keep this, so base R can do its own filtering, just in case
# it changes compared to ours
allfls$exclude[allfls$path == ".Rbuildignore"] <- FALSE
allfls <- exclude_downstream(allfls)
allfls
}
re_exclude <- function(pkg) {
c(
paste0(
"(?i)", # these are case insensitive
c(
"(^|/)\\.DS_Store$", # by macOS finder
"^\\.RData$", # .RData at /
"~$", "\\.bak$", "\\.swp$", # backup files
"(^|/)\\.#[^/]*$", "(^|/)#[^/]*#$", # more backup files (Emacs)
"^config\\.(cache|log|status)$", # leftover by autoconf
"(^|/)autom4te\\.cache$",
"^src/.*\\.d$", "^src/Makedeps$", # INSTALL leftover on Windows
"^inst/doc/Rplots\\.(ps|pdf)$" # Sweave leftover
)
),
"(^|/)\\._[^/]*$", # macOS resource forks
paste0( # hidden files
"(^|/)\\.",
c("Renviron",
"Rprofile",
"Rproj.user",
"Rhistory",
"Rapp.history",
"tex",
"log",
"aux",
"pdf",
"png",
"backups",
"cvsignore",
"cproject",
"directory",
"dropbox",
"exrc",
"gdb.history",
"gitattributes",
"github",
"gitignore",
"gitmodules",
"hgignore",
"hgtags",
"htaccess",
"latex2html-init",
"project",
"seed",
"settings",
"tm_properties"
),
"$"
),
paste0(
"(^|/)",
pkg,
"_[0-9.-]+",
"\\.(tar\\.gz|tar|tar\\.bz2|tar\\.xz|tgz|zip)",
"$"
)
)
}
re_exclude_dir <- function(pkg) {
c(
"^revdep$", # revdepcheck
paste0( # VC
"(^|/)",
c("CVS",
".svn",
".arch-ids",
".bzr",
".git",
".hg",
"_darcs",
".metadata"
),
"$"
),
"(^|/)[^/]*[Oo]ld$",
"(^|/)[^/]*\\.Rcheck",
"^src.*/\\.deps$"
)
}
exclude <- function(root, paths, patterns, patterns_dir) {
ex <- logical(nrow(paths))
for (p in patterns) {
ex <- ex | grepl(p, paths$path, perl = TRUE)
}
# additional regexes for directories, we don't need to check 'ex'
wdirs <- !ex & paths$isdir
paths_dirs <- paths$path[wdirs]
dex <- logical(length(paths_dirs))
for (p in patterns_dir) {
dex <- dex | grepl(p, paths_dirs, perl = TRUE)
}
ex[wdirs][dex] <- TRUE
paths$exclude <- ex
paths
}
exclude_downstream <- function(paths) {
# We don't actually need this now, but we could use it to optimize,
# because we could trim only the subsequent elements after a directory.
paths <- paths[order(paths$path), ]
# We need to take each excluded directory, and remove all paths in them
exdirs <- paste0(paths$path[paths$isdir & paths$exclude], "/")
del <- logical(nrow(paths))
for (ed in exdirs) {
del <- del | startsWith(paths$path, ed)
}
paths <- paths[!del, ]
# Now we mark the subdirectories that can be copied as is, i.e. none of
# their downstream contents are excluded
indirs <- which(paths$isdir & !paths$exclude)
trm <- logical(nrow(paths))
for (id in indirs) {
trm[id] <- any(
startsWith(paths$path, paste0(paths$path[id], "/")) & paths$exclude
)
}
paths$trimmed <- trm
# We can remove the subdirectories of non-trimmed directories as well
fulldirs <- paste0(paths$path[paths$isdir & !trm], "/")
del2 <- logical(nrow(paths))
for (fd in fulldirs) {
del2 <- del2 | startsWith(paths$path, fd)
}
paths <- paths[!del2, ]
rownames(paths) <- NULL
paths
}
cp <- local({
wind <- NULL
cpargs <- NULL
function(src, tgt, recursive = FALSE) {
if (is.null(wind)) wind <<- is_windows()
if (wind) {
if (!file.copy(src, tgt, recursive = recursive, copy.date = TRUE)) {
stop(cli::format_error(c(
"Could not copy package files.",
i = "Failed to copy {.path {src}} to {.path {tgt}}."
)))
}
} else {
if (is.null(cpargs)) cpargs <<- detect_cp_args()
ret <- processx::run(
"cp", c(cpargs, src, tgt),
stderr = "2>&1",
error_on_status = FALSE
)
if (ret$status != 0) {
stop(cli::format_error(c(
"Could not copy package files.",
i = "Failed to copy {.path {src}} to {.path {tgt}}.",
i = "{.code cp} output:",
" " = verb_for_cli(ret$stdout)
)))
}
}
}
})
detect_cp_args <- function() {
# we do this in a tempdir, because it might create a file a called
# `--preserve=timestamps`
dir.create(tmp <- tempfile())
old <- getwd()
on.exit({ setwd(old); unlink(tmp, recursive = TRUE) }, add = TRUE)
setwd(tmp)
f1 <- basename(tempfile())
f2 <- basename(tempfile())
file.create(f1)
tryCatch(
suppressWarnings(processx::run("cp", c("--preserve=timestamps", f1, f2))),
error = function(e) e
)
if (file.exists(f2)) {
c("-LR", "--preserve=timestamps")
} else {
"-pLR"
}
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.