diff --git a/com.oracle.truffle.r.test.packages/r/install.cache.R b/com.oracle.truffle.r.test.packages/r/install.cache.R new file mode 100644 index 0000000000000000000000000000000000000000..9c854d4c6c2063c05e0fad093abd8e1a97cc7151 --- /dev/null +++ b/com.oracle.truffle.r.test.packages/r/install.cache.R @@ -0,0 +1,245 @@ +# +# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code 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 +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +pkg.cache.install <- function(pkg.cache.env, pkgname, lib.install, install.cmd) { + is.cached <- pkg.cache.get(pkg.cache.env, pkgname, lib.install) + if (!is.cached) { + res <- install.cmd() + + # 0L stands for success + if (res == 0L) { + pkg.cache.insert(pkg.cache.env, pkgname, lib.install) + } + } +} + +pkg.cache.get <- function(pkg.cache.env, pkgname, lib) { + version.dir <- pkg.cache.check(pkg.cache.env) + if(is.null(version.dir)) { + return (FALSE) + } + + log.message("using package cache directory ", version.dir, level=1) + cache.entry.name <- paste0(pkgname, ".gz") + + # lookup package dir + pkg.dirs <- list.files(version.dir, full.names=FALSE, recursive=FALSE) + if (!is.na(match(cache.entry.name, pkg.dirs))) { + # cache hit + fromPath <- file.path(version.dir, cache.entry.name) + toPath <- lib + + # extract cached package to library directory + tryCatch({ + unzip(fromPath, exdir=toPath, unzip = getOption("unzip")) + log.message("package cache hit, using package from ", fromPath) + return (TRUE) + }, error = function(e) { + log.message("could not extract cached package from ", fromPath , " to ", toPath, level=1) + return (FALSE) + }) + } + log.message("cache miss for package ", pkgname, level=1) + + FALSE +} + +pkg.cache.insert <- function(pkg.cache.env, pkgname, lib) { + version.dir <- pkg.cache.check(pkg.cache.env) + if(is.null(version.dir)) { + return (FALSE) + } + + tryCatch({ + # Create version directory if inexisting + if (!dir.exists(version.dir)) { + log.message("creating version directory ", version.dir, level=1) + dir.create(version.dir) + } + + fromPath <- file.path(lib, pkgname) + toPath <- file.path(version.dir, paste0(pkgname, ".gz")) + + # to produce a TAR with relative paths, we need to change the working dir + prev.wd <- getwd() + setwd(lib) + if(zip(toPath, pkgname) != 0L) { + log.message("could not compress package dir ", fromPath , " and store it to ", toPath, level=1) + return (FALSE) + } + setwd(prev.wd) + + log.message("successfully inserted package ", pkgname , " to package cache (", toPath, ")") + return (TRUE) + }, error = function(e) { + log.message("could not insert package '", pkgname, "' because: ", e$message) + }) + FALSE +} + +pkg.cache.check <- function(pkg.cache.env) { + # check if caching is enabled + if (!pkg.cache.env$enabled) { + return (NULL) + } + + # check if package cache directory can be accessed + if (dir.exists(pkg.cache.env$dir) && any(file.access(pkg.cache.env$dir, mode = 6) == -1)) { + log.message("cannot access package cache dir ", pkg.cache.env$dir, level=1) + return (NULL) + } + + # check cache directory has valid structure + if (!is.valid.cache.dir(pkg.cache.env$dir, pkg.cache.env$table.file.name)) { + pkg.cache.init(pkg.cache.env$dir, as.character(pkg.cache.env$version), pkg.cache.env$table.file.name, pkg.cache.env$size) + } + + # get version sub-directory + version.dir <- pkg.cache.get.version(pkg.cache.env$dir, as.character(pkg.cache.env$version), pkg.cache.env$table.file.name, pkg.cache.env$size) + if (is.null(version.dir)) { + log.message("cannot access or create version subdir for ", as.character(pkg.cache.env$version), level=1) + } + + version.dir +} + +is.valid.cache.dir <- function(cache.dir, table.file.name) { + if (!dir.exists(cache.dir)) { + return (FALSE) + } + + # look for the version table + version.table.name <- file.path(cache.dir, table.file.name) + if (any(file.access(version.table.name, mode = 6) == -1)) { + return (FALSE) + } + + tryCatch({ + version.table <- read.csv(version.table.name) + TRUE + }, error = function(e) { + log.message("could not read package cache's version table: ", e$message, level=1) + FALSE + }) +} + +# Generates a package cache API version directory using the first 20 characters (if available) from the version. +pkg.cache.gen.version.dir.name <- function(version) { + paste0("library", substr(version, 1, max(20,length(version)))) +} + +pkg.cache.init <- function(cache.dir, version, table.file.name, cache.size) { + if (is.null(version)) { + # This has been logged during argument parsing. + return (NULL) + } + + if (!dir.exists(cache.dir)) { + log.message("creating cache directory ", cache.dir, level=1) + + tryCatch({ + dir.create(cache.dir) + }, error = function(e) { + log.message("could create package cache dir '", cache.dir, "' because: ", e$message) + }) + } + + version.table.name <- file.path(cache.dir, table.file.name) + + # create package lib dir for this version (if not existing) + version.table <- pkg.cache.create.version(cache.dir, version, table.file.name, cache.size, data.frame(version=character(0),dir=character(0),ctime=double(0))) + tryCatch({ + write.csv(version.table, version.table.name, row.names=FALSE) + }, error = function(e) { + log.message("could not write version table to file ", version.table.name, " because: ", e$message) + }) + NULL +} + +# creates package lib dir for this version (if not existing) +pkg.cache.create.version <- function(cache.dir, version, table.file.name, cache.size, version.table) { + version.table.name <- file.path(cache.dir, table.file.name) + version.subdir <- pkg.cache.gen.version.dir.name(version) + version.dir <- file.path(cache.dir, version.subdir) + + # We do not create the version directory here because we cannot guarantee that this will stay in sync + # with the version table anyway. + + # Do cleanup if cache dir size exceeds + while (cache.size > 0 && nrow(version.table) >= cache.size) { + # Remove oldest version (if any) + order <- order(version.table$ctime) + if (length(order) > 0) { + oldest.entry <- version.table[order[[1]],] + oldest.dir <- file.path(cache.dir, as.character(oldest.entry$dir)) + log.message("removing oldest version ", as.character(oldest.entry$version), " with dir ", oldest.dir, level=1) + version.table <- version.table[-order[[1]],] + + # delete directory + tryCatch({ + unlink(oldest.dir, recursive=TRUE) + }, error = function(e) { + log.message("could not remove directory ", oldest.dir, " from cache", level=1) + }) + } else { + # just to be sure + break () + } + } + + # Add entry to version table + log.message("adding entry for ", version, level=1) + rbind(version.table, data.frame(version=version,dir=version.subdir,ctime=as.double(Sys.time()))) +} + +pkg.cache.get.version <- function(cache.dir, cache.version, table.file.name, cache.size) { + if (is.null(cache.version)) { + return (NULL) + } + + # look for 'version.table' + version.table.name <- file.path(cache.dir, table.file.name) + if (any(file.access(version.table.name, mode = 6) == -1)) { + return (NULL) + } + + tryCatch({ + version.table <- read.csv(version.table.name) + version.subdir <- as.character(version.table[version.table$version == cache.version, "dir"]) + updated.version.table <- NULL + if (length(version.subdir) == 0L) { + updated.version.table <- pkg.cache.create.version(cache.dir, cache.version, table.file.name, cache.size, version.table) + } + if (!is.null(updated.version.table)) { + version.subdir <- as.character(updated.version.table[updated.version.table$version == cache.version, "dir"]) + write.csv(updated.version.table, version.table.name, row.names=FALSE) + } + + # return the version directory + file.path(cache.dir, version.subdir) + }, error = function(e) { + log.message("error reading/writing 'version.table': ", e$message, level=1) + NULL + }) +} + diff --git a/com.oracle.truffle.r.test.packages/r/install.packages.R b/com.oracle.truffle.r.test.packages/r/install.packages.R index f7d8f6b080b7f4244a109860c7b385025e5f94d7..d254206c339167b3b948ff082681e97820b685ba 100644 --- a/com.oracle.truffle.r.test.packages/r/install.packages.R +++ b/com.oracle.truffle.r.test.packages/r/install.packages.R @@ -737,7 +737,7 @@ install.pkg <- function(pkgname) { return (e$status) }) } - pkg.cache.install(pkgname, system.install.wrapper) + pkg.cache.install(pkg.cache, pkgname, lib.install, system.install.wrapper) } else if (run.mode == "internal") { internal.install.wrapper <- function() { tryCatch( @@ -747,7 +747,7 @@ install.pkg <- function(pkgname) { return (1) }) } - pkg.cache.install(pkgname, internal.install.wrapper) + pkg.cache.install(pkg.cache, pkgname, lib.install, internal.install.wrapper) } else if (run.mode == "context") { stop("context run-mode not implemented\n") } @@ -757,228 +757,6 @@ install.pkg <- function(pkgname) { return(rc) } -pkg.cache.install <- function(pkgname, install.cmd) { - is.cached <- pkg.cache.get(pkgname, lib=lib.install) - if (!is.cached) { - res <- install.cmd() - - # 0L stands for success - if (res == 0L) { - pkg.cache.insert(pkgname, lib.install) - } - } -} - -pkg.cache.get <- function(pkgname, lib) { - version.dir <- pkg.cache.check() - if(is.null(version.dir)) { - return (FALSE) - } - - log.message("using package cache directory ", version.dir, level=1) - cache.entry.name <- paste0(pkgname, ".gz") - - # lookup package dir - pkg.dirs <- list.files(version.dir, full.names=FALSE, recursive=FALSE) - if (!is.na(match(cache.entry.name, pkg.dirs))) { - # cache hit - fromPath <- file.path(version.dir, cache.entry.name) - toPath <- lib - - # extract cached package to library directory - tryCatch({ - unzip(fromPath, exdir=toPath, unzip = getOption("unzip")) - log.message("package cache hit, using package from ", fromPath) - return (TRUE) - }, error = function(e) { - log.message("could not extract cached package from ", fromPath , " to ", toPath, level=1) - return (FALSE) - }) - } - log.message("cache miss for package ", pkgname, level=1) - - FALSE -} - -pkg.cache.insert <- function(pkgname, lib) { - version.dir <- pkg.cache.check() - if(is.null(version.dir)) { - return (FALSE) - } - - tryCatch({ - # Create version directory if inexisting - if (!dir.exists(version.dir)) { - log.message("creating version directory ", version.dir, level=1) - dir.create(version.dir) - } - - fromPath <- file.path(lib, pkgname) - toPath <- file.path(version.dir, paste0(pkgname, ".gz")) - - # to produce a TAR with relative paths, we need to change the working dir - prev.wd <- getwd() - setwd(lib) - if(zip(toPath, pkgname) != 0L) { - log.message("could not compress package dir ", fromPath , " and store it to ", toPath, level=1) - return (FALSE) - } - setwd(prev.wd) - - log.message("successfully inserted package ", pkgname , " to package cache (", toPath, ")") - return (TRUE) - }, error = function(e) { - log.message("could not insert package '", pkgname, "' because: ", e$message) - }) - FALSE -} - -pkg.cache.check <- function() { - # check if caching is enabled - if (!pkg.cache$enabled) { - return (NULL) - } - - # check if package cache directory can be accessed - if (dir.exists(pkg.cache$dir) && any(file.access(pkg.cache$dir, mode = 6) == -1)) { - log.message("cannot access package cache dir ", pkg.cache$dir, level=1) - return (NULL) - } - - # check cache directory has valid structure - if (!is.valid.cache.dir(pkg.cache$dir)) { - pkg.cache.init(pkg.cache$dir, as.character(pkg.cache$version)) - } - - # get version sub-directory - version.dir <- pkg.cache.get.version(pkg.cache$dir, as.character(pkg.cache$version)) - if (is.null(version.dir)) { - log.message("cannot access or create version subdir for ", as.character(pkg.cache$version), level=1) - } - - version.dir -} - -is.valid.cache.dir <- function(cache.dir) { - if (!dir.exists(cache.dir)) { - return (FALSE) - } - - # look for the version table - version.table.name <- file.path(cache.dir, pkg.cache$table.file.name) - if (any(file.access(version.table.name, mode = 6) == -1)) { - return (FALSE) - } - - tryCatch({ - version.table <- read.csv(version.table.name) - TRUE - }, error = function(e) { - log.message("could not read package cache's version table: ", e$message, level=1) - FALSE - }) -} - -# Generates a package cache API version directory using the first 20 characters (if available) from the version. -pkg.cache.gen.version.dir.name <- function(version) { - paste0("library", substr(version, 1, max(20,length(version)))) -} - -pkg.cache.init <- function(cache.dir, version) { - if (is.null(version)) { - # This has been logged during argument parsing. - return (NULL) - } - - if (!dir.exists(cache.dir)) { - log.message("creating cache directory ", cache.dir, level=1) - - tryCatch({ - dir.create(cache.dir) - }, error = function(e) { - log.message("could create package cache dir '", cache.dir, "' because: ", e$message) - }) - } - - version.table.name <- file.path(cache.dir, pkg.cache$table.file.name) - - # create package lib dir for this version (if not existing) - version.table <- pkg.cache.create.version(cache.dir, version, data.frame(version=character(0),dir=character(0),ctime=double(0))) - tryCatch({ - write.csv(version.table, version.table.name, row.names=FALSE) - }, error = function(e) { - log.message("could not write version table to file ", version.table.name, " because: ", e$message) - }) - NULL -} - -# creates package lib dir for this version (if not existing) -pkg.cache.create.version <- function(cache.dir, version, version.table) { - version.table.name <- file.path(cache.dir, pkg.cache$table.file.name) - version.subdir <- pkg.cache.gen.version.dir.name(version) - version.dir <- file.path(cache.dir, version.subdir) - - # We do not create the version directory here because we cannot guarantee that this will stay in sync - # with the version table anyway. - - # Do cleanup if cache dir size exceeds - while (pkg.cache$size > 0 && nrow(version.table) >= pkg.cache$size) { - # Remove oldest version (if any) - order <- order(version.table$ctime) - if (length(order) > 0) { - oldest.entry <- version.table[order[[1]],] - oldest.dir <- file.path(cache.dir, as.character(oldest.entry$dir)) - log.message("removing oldest version ", as.character(oldest.entry$version), " with dir ", oldest.dir, level=1) - version.table <- version.table[-order[[1]],] - - # delete directory - tryCatch({ - unlink(oldest.dir, recursive=TRUE) - }, error = function(e) { - log.message("could not remove directory ", oldest.dir, " from cache", level=1) - }) - } else { - # just to be sure - break () - } - } - - # Add entry to version table - log.message("adding entry for ", version, level=1) - rbind(version.table, data.frame(version=version,dir=version.subdir,ctime=as.double(Sys.time()))) -} - -pkg.cache.get.version <- function(cache.dir, cache.version) { - if (is.null(cache.version)) { - return (NULL) - } - - # look for 'version.table' - version.table.name <- file.path(cache.dir, pkg.cache$table.file.name) - if (any(file.access(version.table.name, mode = 6) == -1)) { - return (NULL) - } - - tryCatch({ - version.table <- read.csv(version.table.name) - version.subdir <- as.character(version.table[version.table$version == cache.version, "dir"]) - updated.version.table <- NULL - if (length(version.subdir) == 0L) { - updated.version.table <- pkg.cache.create.version(cache.dir, cache.version, version.table) - } - if (!is.null(updated.version.table)) { - version.subdir <- as.character(updated.version.table[updated.version.table$version == cache.version, "dir"]) - write.csv(updated.version.table, version.table.name, row.names=FALSE) - } - - # return the version directory - file.path(cache.dir, version.subdir) - }, error = function(e) { - log.message("error reading/writing 'version.table': ", e$message, level=1) - NULL - }) -} - # when testing under graalvm, fastr is not built so we must use the (assumed) sibling gnur repo check_graalvm <- function() { if (!is.na(Sys.getenv('FASTR_GRAALVM', unset=NA)) || !is.na(Sys.getenv('GRAALVM_FASTR', unset=NA))) { @@ -1262,20 +1040,43 @@ run.setup <- function() { cat.args() } +# Determines the directory of the script assuming that there is a "--file=" argument on the command line. +getCurrentScriptDir <- function() { + cmdArgs <- commandArgs() + res <- startsWith(cmdArgs, '--file=') + fileArg <- cmdArgs[res] + if (length(fileArg) > 0L) { + p <- strsplit(fileArg, "=")[[1]][[2]] + dirname(p) + } else { + NULL + } +} + run <- function() { - parse.args() - if (find.top100) { - set.repos() - do.find.top100() - } else { - run.setup() + parse.args() + + if (pkg.cache$enabled) { + curScriptDir <- getCurrentScriptDir() + if (!is.null(curScriptDir)) { + source(file.path(curScriptDir, "install.cache.R")) + } else { + log.message("Cannot use package cache since script directory cannot be determined") + } + } + + if (find.top100) { + set.repos() + do.find.top100() + } else { + run.setup() do.it() } } quiet <- F repo.list <- c("CRAN") -pkg.cache <- as.environment(list(enabled=FALSE, table.file.name="version.table", size=2L,link=FALSE)) +pkg.cache <- as.environment(list(enabled=FALSE, table.file.name="version.table", size=2L)) cran.mirror <- NA blacklist.file <- NA initial.blacklist.file <- NA