top <- file.path(normalizePath("~"), "tmp", "autocheck.d")
## FIXME: how can we be notified about problems?
file_age <- function(paths) {
as.numeric(Sys.Date() - as.Date(file.mtime(paths)))
}
file_age_in_hours <- function(paths) {
as.numeric(difftime(Sys.time(), file.mtime(paths),
units = "hours"))
}
summarize <- function(dir, reverse = FALSE, process = FALSE) {
log <- file.path(dir, "package", "00check.log")
results <- tools:::check_packages_in_dir_results(logs = log)
status <- results$package$status
out <- sprintf("Package check result: %s\n", status)
if(status != "OK") {
details <- tools:::check_packages_in_dir_details(logs = log)
out <- c(out,
sprintf("Check: %s, Result: %s\n %s\n",
details$Check,
details$Status,
gsub("\n", "\n ", details$Output, perl = TRUE)))
}
if(reverse) {
changes <- readLines(file.path(dir, "changes.txt"))
out <- c(out,
if(length(changes))
c("Changes to worse in reverse depends:\n",
changes)
else
"No changes to worse in reverse depends.")
}
if(process) {
process <- readLines(file.path(dir, "process.txt"))
if(length(process))
out <- c(out,
"Post-processing issues found:\n",
process)
}
out
}
format_process_outputs <- function(x) {
if(!length(x)) return(character())
fun <- function(u, v) {
paste(c(sprintf("File: %s", u), v), collapse = "\n")
}
paste(unlist(Map(fun, names(x), x), use.names = FALSE),
collapse = "\n\n")
}
## A very simple post-processor for valgrind checks:
process_valg <- function(dir) {
files <-
Sys.glob(file.path(dir,
c("*.Rout",
"tests/*.Rout",
"tests/*.Rout.fail",
"*.[RSrs]nw.log",
"*.[RSrs]tex.log",
"*.Rmd.log")))
lines <- lapply(files,
function(f) {
s <- readLines(f, warn = FALSE, encoding = "bytes")
h <- gsub("(==[0-9]+==) .*", "\\1", s[1L])
s <- s[startsWith(s, h)]
if(any(grepl(dir, s)))
s
else
NULL
})
## Cf. tools:::file_path_relative_to().
names(lines) <- substring(files, nchar(dir) + 2L)
Filter(length, lines)
}
## A very simple post-processor for sanitizer checks:
process_xtra <- function(dir) {
files <-
Sys.glob(file.path(dir,
c("*.Rout",
"tests/*.Rout",
"tests/*.Rout.fail",
"*.[RSrs]nw.log",
"*.[RSrs]tex.log",
"build_vignettes.log",
"00check.log",
"00install.out")))
lines <- lapply(files,
function(f) {
s <- readLines(f, warn = FALSE, encoding = "bytes")
grepv("ASan|AddressSanitizer|runtime error:", s)
})
## Cf. tools:::file_path_relative_to().
names(lines) <- substring(files, nchar(dir) + 2L)
Filter(length, lines)
}
run <- function(service = "pretest") {
reverse <- (service == "recheck")
process <- (service %in% c("special/clang-san",
"special/gcc-san",
"special/valgrind"))
wrk <- file.path(normalizePath("~"), "tmp",
paste0("CRAN_", sub("/", "_", service)))
if(dir.exists(wrk)) {
if(file_age_in_hours(wrk) < 6)
return(0)
else
unlink(wrk, recursive = TRUE)
}
if(!dir.exists(top))
dir.create(top, recursive = TRUE)
## Allow to disable from "outside":
if(file.exists(file.path(top, "disable")))
return(0)
top <- file.path(top, service)
if(!dir.exists(top))
dir.create(top, recursive = TRUE)
if(file.exists(lck <- file.path(top, ".lock"))) {
if(file_age_in_hours(lck) < 6)
return(0)
else
file.remove(lck)
}
file.create(lck)
on.exit(file.remove(lck))
## From now on we have a lock in place.
## General idea is the following.
## Check results for package tarball
## sources/PACKAGE_VERSION.tar.gz
## are put into directory
## results/PACKAGE_VERSION_DATE_TIME
## Determine the oldest tarball without corresponding results dir,
## and run the check.
sources.d <- file.path(top, "sources")
results.d <- file.path(top, "results")
outputs.d <- file.path(top, "outputs")
if(!dir.exists(sources.d))
dir.create(sources.d)
if(!dir.exists(results.d))
dir.create(results.d)
if(!dir.exists(outputs.d))
dir.create(outputs.d)
## Clean up results.
results <- list.dirs(results.d,
full.names = TRUE, recursive = FALSE)
old <- results[file_age(results) > 14]
if(length(old))
unlink(old, recursive = TRUE)
## Populate sources: this could also be done by someone else.
system2("rsync",
c("-aqzv --recursive --delete",
sprintf("cran.wu.ac.at::CRAN-incoming/%s/",
service),
sources.d),
stdout = FALSE, stderr = FALSE)
sources <- Sys.glob(file.path(sources.d, "*.tar.gz"))
##
## There currently is no mechanism for *stoplisting* packages (or
## maintainers). For the former, we could use something like
## sources <-
## sources[!startsWith("MiscMetabar_", basename(sources))]
##
outputs <- list.dirs(outputs.d,
full.names = FALSE, recursive = FALSE)
if(!length(sources)) {
if(length(outputs)) {
old <- file.path(outputs.d, outputs)
if(!reverse)
old <- old[file_age(old) > 7]
if(length(old))
unlink(old, recursive = TRUE)
}
return(0)
}
results <- list.dirs(results.d,
full.names = FALSE, recursive = FALSE)
dts <- format(file.mtime(sources), "%Y%m%d_%H%M%S")
pos <- order(dts)
ids <- sprintf("%s_%s",
sub("[.]tar[.]gz$", "", basename(sources)[pos]),
dts[pos])
old <- file.path(outputs.d, outputs[is.na(match(outputs, ids))])
if(!reverse)
old <- old[file_age(old) > 7]
if(length(old))
unlink(old, recursive = TRUE)
new <- ids[is.na(match(ids, results))]
if(!length(new)) {
return(0)
}
new <- new[1L]
writeLines(new, lck)
dir.create(wrk, recursive = TRUE)
file.copy(file.path(sources.d,
paste0(sub("^([^_]+_[^_]+)_.*", "\\1", new),
".tar.gz")),
wrk)
## Avoid 'WARNING: ignoring environment value of R_HOME' ...
on.exit(Sys.setenv(R_HOME = Sys.getenv("R_HOME")), add = TRUE)
Sys.unsetenv("R_HOME")
exe <- list()
##
## We currently hard-wire pretest to use LLVM: could make this
## settable via an additional command line option ...
##
arg <- list("pretest" =
"-c -fc",
"recheck" =
"-r=most",
"special/LTO" =
"-fg/LTO",
"special/clang19" =
"-fc/lcxx",
"special/clang-san" =
"-fc/xtra",
"special/donttest" =
"-a=\"--run-donttest\"",
"special/gcc" =
"-fg",
"special/gcc-san" =
"-fg/xtra",
"special/noLD" =
"-fg/noLD",
"special/valgrind" =
"-fg/valg -a=\"--use-valgrind\""
)
env <- list("special/clang-san" =
c("R_LIBS_USER=NULL",
"_R_CHECK_CRAN_INCOMING_=false",
"_R_CHECK_RD_MATH_RENDERING_=false",
sprintf("ASAN_OPTIONS=detect_leaks=0:detect_odr_violation=0:suppressions=%s",
file.path(normalizePath("~"),
".R", "asan.supp")),
sprintf("UBSAN_OPTIONS=print_stacktrace=1:suppressions=%s",
file.path(normalizePath("~"),
".R", "ubsan.supp")),
##
## remove eventually ...
"_R_CXX_USE_NO_REMAP_=false",
"_R_USE_STRICT_R_HEADERS_=false"
##
),
"special/donttest" =
"_R_CHECK_EXAMPLE_TIMING_THRESHOLD_=600",
"special/gcc-san" =
c("R_LIBS_USER=NULL",
"_R_CHECK_CRAN_INCOMING_=false",
"_R_CHECK_RD_MATH_RENDERING_=false",
sprintf("ASAN_OPTIONS=detect_leaks=0:detect_odr_violation=0:suppressions=%s",
file.path(normalizePath("~"),
".R", "asan.supp")),
sprintf("UBSAN_OPTIONS=print_stacktrace=1:suppressions=%s",
file.path(normalizePath("~"),
".R", "ubsan.supp")),
##
## remove eventually ...
"_R_CXX_USE_NO_REMAP_=false",
"_R_USE_STRICT_R_HEADERS_=false"
##
),
"special/noSuggests" =
"_R_CHECK_DEPENDS_ONLY_=true",
"special/valgrind" =
c("R_LIBS_USER=NULL",
"_R_CHECK_CRAN_INCOMING_=false",
"_R_CHECK_RD_MATH_RENDERING_=false",
##
## remove eventually ...
"_R_CXX_USE_NO_REMAP_=false",
"_R_USE_STRICT_R_HEADERS_=false",
##
"VALGRIND_OPTS=\"--fullpath-after=\"")
)
exe <- exe[[service]]
arg <- arg[[service]]
env <- env[[service]]
cmd <- file.path(normalizePath("~"), "bin", "check-CRAN-incoming")
val <- system2(cmd,
paste(c("-n -s",
paste0("-d=", wrk),
arg,
if(!is.null(exe))
paste("--exe", exe)),
collapse = " "),
env = c("_R_CHECK_CRAN_STATUS_SUMMARY_=false",
env),
stdout = file.path(wrk, "outputs.txt"),
stderr = file.path(wrk, "outputs.txt"))
## Should we check the value returned?
if(reverse) {
## Create a summary of the changes in reverse depends.
cmd <- file.path(normalizePath("~"),
"bin", "summarize-check-CRAN-incoming-changes")
system2(cmd, c("-m -w -o", wrk),
stdout = file.path(wrk, "changes.txt"))
}
if(process) {
tmp <- file.path(wrk, paste0(sub("_.*", "", new), ".Rcheck"))
bad <- if(service == "special/valgrind")
process_valg(tmp)
else
process_xtra(tmp)
writeLines(format_process_outputs(bad),
file.path(wrk, "process.txt"))
}
if(dir.exists(file.path(results.d, new)))
unlink(file.path(results.d, new), recursive = TRUE)
file.rename(wrk, file.path(results.d, new))
## Populate outputs for rsync from cran master.
if(dir.exists(file.path(outputs.d, new)))
unlink(file.path(outputs.d, new), recursive = TRUE)
dir.create(file.path(outputs.d, new))
file.copy(file.path(results.d, new, "outputs.txt"),
file.path(outputs.d, new))
if(reverse)
file.copy(file.path(results.d, new, "changes.txt"),
file.path(outputs.d, new))
if(process)
file.copy(file.path(results.d, new, "process.txt"),
file.path(outputs.d, new))
package <- sub("_.*", "", new)
if(dir.exists(from <- file.path(results.d, new,
paste0(package, ".Rcheck")))) {
dir.create(to <- file.path(outputs.d, new, "package"))
file.copy(file.path(from, "00check.log"), to)
if(file.exists(fp <- file.path(from, "00install.out")))
file.copy(fp, to)
writeLines(summarize(file.path(outputs.d, new),
reverse, process),
file.path(outputs.d, new, "summary.txt"))
}
return(0)
}
if(!interactive()) {
service <- "pretest"
args <- commandArgs(trailingOnly = TRUE)
if(any(ind <- startsWith(args, "-r"))) {
service <- "recheck"
args <- args[!ind]
}
if(any(ind <- startsWith(args, "-s="))) {
service <- paste0("special/", substring(args[ind][1L], 4L))
args <- args[!ind]
}
if(any(ind <- startsWith(args, "-t="))) {
top <- substring(args[ind][1L], 4L)
args <- args[!ind]
}
val <- run(service)
}
if(FALSE) {
while(TRUE) {
run()
Sys.sleep(1)
}
}