blob: 13b29701ef6d7d76c34b3ed4666aec03ae384f5f [file] [log] [blame]
# File src/library/utils/R/packages.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/
available.packages <-
function(contriburl = contrib.url(repos, type), method,
fields = NULL, type = getOption("pkgType"),
filters = NULL, repos = getOption("repos"),
ignore_repo_cache = FALSE, max_repo_cache_age,
...)
{
requiredFields <-
c(tools:::.get_standard_repository_db_fields(), "File")
if (is.null(fields))
fields <- requiredFields
else {
stopifnot(is.character(fields))
fields <- unique(c(requiredFields, fields))
}
if(missing(max_repo_cache_age))
max_repo_cache_age <- as.numeric(Sys.getenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE", "3600"))
timestamp <- Sys.time()
res <- matrix(NA_character_, 0L, length(fields) + 1L,
dimnames = list(NULL, c(fields, "Repository")))
for(repos in contriburl) {
localcran <- startsWith(repos, "file:")
if(localcran) {
## see note in download.packages
if(startsWith(repos, "file:///")) {
tmpf <- paste0(substring(repos, 8L), "/PACKAGES")
if(.Platform$OS.type == "windows") {
if(length(grep("^/[A-Za-z]:", tmpf)))
tmpf <- substring(tmpf, 2L)
}
} else {
tmpf <- paste0(substring(repos, 6L), "/PACKAGES")
}
res0 <- if(file.exists(dest <- paste0(tmpf, ".rds")))
readRDS(dest)
else
read.dcf(file = tmpf)
if(length(res0))
rownames(res0) <- res0[, "Package"]
} else {
used_dest <- FALSE
if(ignore_repo_cache) {
dest <- tempfile()
} else {
dest <- file.path(tempdir(),
paste0("repos_", URLencode(repos, TRUE), ".rds"))
if(file.exists(dest)) {
age <- difftime(timestamp, file.mtime(dest), units = "secs")
if(isTRUE(age < max_repo_cache_age)) {
res0 <- readRDS(dest)
used_dest <- TRUE
## Be defensive ...
if(length(res0))
rownames(res0) <- res0[, "Package"]
}
else
unlink(dest) # Cache too old.
}
}
if(!used_dest) {
## Try .rds and readRDS(), and then .gz or plain DCF and
## read.dcf(), catching problems from both missing or
## invalid files.
need_dest <- FALSE
op <- options(warn = -1L)
z <- tryCatch({
download.file(url = paste0(repos, "/PACKAGES.rds"),
destfile = dest, method = method,
cacheOK = FALSE, quiet = TRUE, mode = "wb")
}, error = identity)
options(op)
if(!inherits(z, "error")) {
z <- res0 <- tryCatch(readRDS(dest), error = identity)
if(ignore_repo_cache) unlink(dest)
}
if(inherits(z, "error")) {
## Downloading or reading .rds failed, so try the
## DCF variants.
if(!ignore_repo_cache) need_dest <- TRUE
tmpf <- tempfile()
on.exit(unlink(tmpf))
op <- options(warn = -1L)
## FIXME: this should check the return value == 0L
z <- tryCatch({
## This is a binary file
download.file(url = paste0(repos, "/PACKAGES.gz"),
destfile = tmpf, method = method,
cacheOK = FALSE, quiet = TRUE, mode = "wb")
}, error = identity)
if(inherits(z, "error"))
z <- tryCatch({
## read.dcf is going to interpret CRLF as
## LF, so use binary mode to avoid CRLF.
download.file(url = paste0(repos, "/PACKAGES"),
destfile = tmpf, method = method,
cacheOK = FALSE, quiet = TRUE, mode = "wb")
}, error = identity)
options(op)
if (!inherits(z, "error"))
z <- res0 <- tryCatch(read.dcf(file = tmpf),
error = identity)
unlink(tmpf)
on.exit()
}
if(inherits(z, "error")) {
warning(gettextf("unable to access index for repository %s",
repos),
":\n ", conditionMessage(z),
call. = FALSE, immediate. = TRUE, domain = NA)
next
}
if(length(res0)) {
rownames(res0) <- res0[, "Package"]
if(need_dest)
saveRDS(res0, dest, compress = TRUE)
} else if(!need_dest) {
## download.file() gave an empty .rds
## Do not cache empty results.
unlink(dest)
}
} # end of download vs cached
} # end of localcran vs online
if (length(res0)) {
missingFields <- fields[!(fields %in% colnames(res0))]
if (length(missingFields)) {
toadd <- matrix(NA_character_, nrow = nrow(res0),
ncol = length(missingFields),
dimnames = list(NULL, missingFields))
res0 <- cbind(res0, toadd)
}
if ("Path" %in% colnames(res0)) {
rp <- rep.int(repos, nrow(res0))
path <- res0[, "Path"]
rp[!is.na(path)] <- paste(repos, path[!is.na(path)], sep = "/")
} else rp <- repos
res0 <- cbind(res0[, fields, drop = FALSE], Repository = rp)
res <- rbind(res, res0, deparse.level = 0L)
}
}
if(!length(res)) return(res)
if(is.null(filters)) {
filters <- getOption("available_packages_filters")
if(is.null(filters))
filters <- available_packages_filters_default
}
if(is.list(filters)) {
## If filters is a list with an add = TRUE element, add the
## given filters to the default ones.
if(isTRUE(filters$add)) {
filters$add <- NULL
filters <- c(available_packages_filters_default, filters)
}
}
for(f in filters) {
if(!length(res)) break
if(is.character(f)) {
## Look up the filters db.
## Could be nice and allow abbrevs or ignore case.
f <- available_packages_filters_db[[f[1L]]]
}
if(!is.function(f))
stop("invalid 'filters' argument.")
res <- f(res)
}
res
}
available_packages_filters_default <-
c("R_version", "OS_type", "subarch", "duplicates")
available_packages_filters_db <- new.env(hash = FALSE) # small
available_packages_filters_db$R_version <-
function(db)
{
## Ignore packages which don't fit our version of R.
depends <- db[, "Depends"]
depends[is.na(depends)] <- ""
## Collect the (versioned) R depends entries.
x <- lapply(strsplit(sub("^[[:space:]]*", "", depends),
"[[:space:]]*,[[:space:]]*"),
function(s) s[grepl("^R[[:space:]]*\\(", s)])
lens <- lengths(x)
pos <- which(lens > 0L)
if(!length(pos)) return(db)
lens <- lens[pos]
## Unlist.
x <- unlist(x)
pat <- "^R[[:space:]]*\\(([[<>=!]+)[[:space:]]+(.*)\\)[[:space:]]*"
## Extract ops.
ops <- sub(pat, "\\1", x)
## Split target versions accordings to ops.
v_t <- split(sub(pat, "\\2", x), ops)
## Current R version.
v_c <- getRversion()
## Compare current to target grouped by op.
res <- logical(length(x))
for(op in names(v_t))
res[ops == op] <- do.call(op, list(v_c, v_t[[op]]))
## And assemble test results according to the rows of db.
ind <- rep.int(TRUE, NROW(db))
ind[pos] <- sapply(split(res, rep.int(seq_along(lens), lens)), all)
db[ind, , drop = FALSE]
}
available_packages_filters_db$OS_type <-
function(db)
{
## Ignore packages that do not fit our OS.
OS_type <- db[, "OS_type"]
db[is.na(OS_type) | (OS_type == .Platform$OS.type), , drop = FALSE]
}
available_packages_filters_db$subarch <-
function(db)
{
## Ignore packages that do not fit our sub-architecture.
## Applies only to Mac and Windows binary repositories.
current <- .Platform$r_arch
if(!nzchar(current)) return(db)
archs <- db[, "Archs"]
if(all(is.na(archs))) return(db)
OK <- unlist(lapply(archs, function(x) {
if(is.na(x)) return(TRUE)
this <- strsplit(x, "[[:space:]]*,[[:space:]]*")[[1L]]
current %in% this
}))
db[OK, , drop = FALSE]
}
available_packages_filters_db$duplicates <-
function(db)
tools:::.remove_stale_dups(db)
filter_packages_by_depends_predicates <-
function(db, predicate, recursive = TRUE)
{
## Could also add a 'which' argument to specify which dependencies
## are taken.
## Drop all packages for which any (recursive) dependency does not
## satisfy the given predicate (implemented as a function computing
## TRUE or FALSE for each rows of the package db).
## Somewhat tricky because there may be depends missing from the db,
## which are taken not to satisfy the predicate unless they are
## standard packages.
## Determine all depends missing from the db.
db1 <- data.frame(Package = db[, "Package"],
stringsAsFactors = FALSE)
fields <- c("Depends", "Imports", "LinkingTo")
for(f in fields)
db1[[f]] <-
lapply(db[, f], tools:::.extract_dependency_package_names)
all_packages <- unique(unlist(db1[fields], use.names = FALSE))
bad_packages <-
all_packages[is.na(match(all_packages, db1$Package))]
## Drop the standard packages from these.
bad_packages <-
setdiff(bad_packages,
unlist(tools:::.get_standard_package_names()))
## Packages in the db which do not satisfy the predicate.
ind <- !predicate(db)
## Now find the recursive reverse dependencies of these and the
## non-standard packages missing from the db.
rdepends <-
tools::package_dependencies(db1$Package[ind], db = db1,
reverse = TRUE,
recursive = recursive)
rdepends <- unique(unlist(rdepends))
ind[match(rdepends, db1$Package, nomatch = 0L)] <- TRUE
## And drop these from the db.
db[!ind, , drop = FALSE]
}
available_packages_filters_db$`license/FOSS` <-
function(db) {
predicate <- function(db)
tools:::analyze_licenses(db[, "License"], db)$is_verified
filter_packages_by_depends_predicates(db, predicate)
}
available_packages_filters_db$`license/restricts_use` <-
function(db) {
predicate <- function(db) {
ru <- tools:::analyze_licenses(db[, "License"], db)$restricts_use
!is.na(ru) & !ru
}
filter_packages_by_depends_predicates(db, predicate)
}
available_packages_filters_db$CRAN <-
function(db)
{
packages <- db[, "Package"]
dups <- packages[duplicated(packages)]
drop <- integer()
CRAN <- getOption("repos")["CRAN"]
## do nothing if there is no CRAN repos on the list
if(is.na(CRAN)) return(db)
for(d in dups) {
pos <- which(packages == d)
ind <- !startsWith(db[pos, "Repository"], CRAN)
if(!all(ind)) drop <- c(drop, pos[ind])
}
if(length(drop)) db[-drop, , drop = FALSE] else db
}
## unexported helper function
simplifyRepos <- function(repos, type)
{
tail <- substring(contrib.url("---", type), 4L)
ind <- regexpr(tail, repos, fixed=TRUE)
ind <- ifelse(ind > 0L, ind-1L, nchar(repos, type="c"))
substr(repos, 1L, ind)
}
update.packages <- function(lib.loc = NULL, repos = getOption("repos"),
contriburl = contrib.url(repos, type),
method, instlib = NULL, ask = TRUE,
available = NULL, oldPkgs = NULL, ...,
checkBuilt = FALSE, type = getOption("pkgType"))
{
force(ask) # just a check that it is valid before we start work
text.select <- function(old)
{
update <- NULL
for(k in seq_len(nrow(old))) {
cat(old[k, "Package"], ":\n",
"Version", old[k, "Installed"],
"installed in", old[k, "LibPath"],
if(checkBuilt) paste("built under R", old[k, "Built"]),
"\n",
"Version", old[k, "ReposVer"], "available at",
simplifyRepos(old[k, "Repository"], type))
cat("\n")
answer <- askYesNo("Update?")
if(is.na(answer)) {
cat("cancelled by user\n")
return(invisible())
}
if(isTRUE(answer))
update <- rbind(update, old[k,])
}
update
}
if(is.null(lib.loc))
lib.loc <- .libPaths()
if(type == "both" && (!missing(contriburl) || !is.null(available))) {
stop("specifying 'contriburl' or 'available' requires a single type, not type = \"both\"")
}
if(is.null(available)) {
available <- available.packages(contriburl = contriburl,
method = method, ...)
if (missing(repos)) repos <- getOption("repos") # May have changed
}
if(!is.matrix(oldPkgs) && is.character(oldPkgs)) {
subset <- oldPkgs
oldPkgs <- NULL
} else
subset <- NULL
if(is.null(oldPkgs)) {
## since 'available' is supplied, 'contriburl' and 'method' are unused
oldPkgs <- old.packages(lib.loc = lib.loc,
contriburl = contriburl, method = method,
available = available, checkBuilt = checkBuilt)
if (missing(repos)) repos <- getOption("repos") # May have changed
## prune package versions which are invisible to require()
if(!is.null(oldPkgs)) {
pkg <- 0L
while(pkg < nrow(oldPkgs)) {
pkg <- pkg + 1L
if(find.package(oldPkgs[pkg], lib.loc = lib.loc) !=
find.package(oldPkgs[pkg], lib.loc = oldPkgs[pkg,2])) {
warning(sprintf("package '%s' in library '%s' will not be updated",
oldPkgs[pkg], oldPkgs[pkg, 2]),
call. = FALSE, immediate. = TRUE)
oldPkgs <- oldPkgs[-pkg, , drop = FALSE]
pkg <- pkg - 1L
}
}
}
if(is.null(oldPkgs))
return(invisible())
} else if (!(is.matrix(oldPkgs) && is.character(oldPkgs)))
stop("invalid 'oldPkgs'; must be a character vector or a result from old.packages()")
if(!is.null(subset)) {
oldPkgs <- oldPkgs[ rownames(oldPkgs) %in% subset, ,drop=FALSE]
if (nrow(oldPkgs) == 0)
return(invisible())
}
update <- if(is.character(ask) && ask == "graphics") {
if(.Platform$OS.type == "windows" || .Platform$GUI == "AQUA"
|| (capabilities("tcltk") && capabilities("X11"))) {
k <- select.list(oldPkgs[,1L], oldPkgs[,1L], multiple = TRUE,
title = "Packages to be updated", graphics = TRUE)
oldPkgs[match(k, oldPkgs[,1L]), , drop=FALSE]
} else text.select(oldPkgs)
} else if(isTRUE(ask)) text.select(oldPkgs)
else oldPkgs
if(length(update)) {
if(is.null(instlib)) instlib <- update[, "LibPath"]
## do this a library at a time, to handle dependencies correctly.
libs <- unique(instlib)
for(l in libs)
if (type == 'both')
install.packages(update[instlib == l , "Package"], l,
repos = repos, method = method,
..., type = type)
else
install.packages(update[instlib == l , "Package"], l,
contriburl = contriburl, method = method,
available = available, ..., type = type)
}
}
old.packages <- function(lib.loc = NULL, repos = getOption("repos"),
contriburl = contrib.url(repos, type),
instPkgs = installed.packages(lib.loc = lib.loc, ...),
method, available = NULL, checkBuilt = FALSE,
..., type = getOption("pkgType"))
{
if(is.null(lib.loc))
lib.loc <- .libPaths()
if(!missing(instPkgs)) {
## actually we need rather more than this
if(!is.matrix(instPkgs) || !is.character(instPkgs[, "Package"]))
stop("ill-formed 'instPkgs' matrix")
}
if(NROW(instPkgs) == 0L) return(NULL)
available <- if(is.null(available))
available.packages(contriburl = contriburl, method = method, ...)
else tools:::.remove_stale_dups(available)
update <- NULL
currentR <- minorR <- getRversion()
minorR[[c(1L, 3L)]] <- 0L # set patchlevel to 0
for(k in 1L:nrow(instPkgs)) {
if (instPkgs[k, "Priority"] %in% "base") next
z <- match(instPkgs[k, "Package"], available[, "Package"])
if(is.na(z)) next
onRepos <- available[z, ]
## works OK if Built: is missing (which it should not be)
if((!checkBuilt || package_version(instPkgs[k, "Built"]) >= minorR) &&
package_version(onRepos["Version"]) <=
package_version(instPkgs[k, "Version"])) next
deps <- onRepos["Depends"]
if(!is.na(deps)) {
Rdeps <- tools:::.split_dependencies(deps)[["R", exact=TRUE]]
if(length(Rdeps) > 1L) {
target <- Rdeps$version
res <- do.call(Rdeps$op, list(currentR, target))
## res <- eval(parse(text=paste("currentR", Rdeps$op, "target")))
if(!res) next
}
}
update <- rbind(update,
c(instPkgs[k, c("Package", "LibPath", "Version", "Built")],
onRepos["Version"], onRepos["Repository"]))
}
if(!is.null(update))
colnames(update) <- c("Package", "LibPath", "Installed", "Built",
"ReposVer", "Repository")
rownames(update) <- update[, "Package"]
## finally, remove any duplicate rows
update[!duplicated(update), , drop = FALSE]
}
new.packages <- function(lib.loc = NULL, repos = getOption("repos"),
contriburl = contrib.url(repos, type),
instPkgs = installed.packages(lib.loc = lib.loc, ...),
method, available = NULL, ask = FALSE,
..., type = getOption("pkgType"))
{
ask # just a check that it is valid before we start work
if(type == "both" && (!missing(contriburl) || !is.null(available))) {
stop("specifying 'contriburl' or 'available' requires a single type, not type = \"both\"")
}
if(is.null(lib.loc)) lib.loc <- .libPaths()
if(!is.matrix(instPkgs))
stop(gettextf("no installed packages for (invalid?) 'lib.loc=%s'",
lib.loc), domain = NA)
if(is.null(available))
available <- available.packages(contriburl = contriburl,
method = method, ...)
installed <- unique(instPkgs[, "Package"])
poss <- sort(unique(available[ ,"Package"])) # sort in local locale
res <- setdiff(poss, installed)
update <- character()
graphics <- FALSE
if(is.character(ask) && ask == "graphics") {
ask <- TRUE
if(.Platform$OS.type == "windows" || .Platform$GUI == "AQUA"
|| (capabilities("tcltk") && capabilities("X11")))
graphics <- TRUE
}
if(isTRUE(ask)) {
if(length(res))
update <- res[match(select.list(res, multiple = TRUE,
title = "New packages to be installed",
graphics = graphics)
, res)]
else message("no new packages are available")
}
if(length(update)) {
if(type == "both")
install.packages(update, lib = lib.loc[1L], method = method,
type = type, ...)
else
install.packages(update, lib = lib.loc[1L], contriburl = contriburl,
method = method, available = available,
type = type, ...)
# Now check if they were installed and update 'res'
dirs <- list.files(lib.loc[1L])
updated <- update[update %in% dirs]
res <- res[!res %in% updated]
}
res
}
.instPkgFields <- function(fields) {
## to be used in installed.packages() and similar
requiredFields <-
c(tools:::.get_standard_repository_db_fields(), "Built")
if (is.null(fields))
fields <- requiredFields
else {
stopifnot(is.character(fields))
fields <- unique(c(requiredFields, fields))
}
## Don't retain 'Package' and 'LibPath' fields as these are used to
## record name and path of installed packages.
fields[! fields %in% c("Package", "LibPath")]
}
## Read packages' Description and aggregate 'fields' into a character matrix
## NB: this does not handle encodings, so only suitable for ASCII-only fields.
.readPkgDesc <- function(lib, fields, pkgs = list.files(lib))
{
## to be used in installed.packages() and similar
## As from 2.13.0 only look at metadata.
ret <- matrix(NA_character_, length(pkgs), 2L+length(fields))
for(i in seq_along(pkgs)) {
pkgpath <- file.path(lib, pkgs[i])
if(file.access(pkgpath, 5L)) next
if (file.exists(file <- file.path(pkgpath, "Meta", "package.rds"))) {
## this is vulnerable to installs going on in parallel
md <- try(readRDS(file))
if(inherits(md, "try-error")) next
desc <- md$DESCRIPTION[fields]
if (!length(desc)) {
warning(gettextf("metadata of %s is corrupt", sQuote(pkgpath)),
domain = NA)
next
}
if("Built" %in% fields) {
## This should not be missing.
if(is.null(md$Built$R) || !("Built" %in% names(desc))) {
warning(gettextf("metadata of %s is corrupt",
sQuote(pkgpath)), domain = NA)
next
}
desc["Built"] <- as.character(md$Built$R)
}
ret[i, ] <- c(pkgs[i], lib, desc)
}
}
ret[!is.na(ret[, 1L]), ]
}
installed.packages <-
function(lib.loc = NULL, priority = NULL, noCache = FALSE,
fields = NULL, subarch = .Platform$r_arch, ...)
{
if(is.null(lib.loc))
lib.loc <- .libPaths()
if(!is.null(priority)) {
if(!is.character(priority))
stop("'priority' must be character or NULL")
if(any(b <- priority %in% "high"))
priority <- c(priority[!b], "recommended","base")
}
fields <- .instPkgFields(fields)
retval <- matrix(character(), 0L, 2L + length(fields))
for(lib in lib.loc) {
if(noCache) {
ret0 <- .readPkgDesc(lib, fields)
if(length(ret0)) retval <- rbind(retval, ret0, deparse.level = 0L)
} else {
## Previously used URLencode for e.g. Windows paths with drives
## This version works for very long file names.
base <- paste(c(lib, fields), collapse = ",")
## add length and 64-bit CRC in hex (in theory, seems
## it is actually 32-bit on some systems)
enc <- sprintf("%d_%s", nchar(base), .Call(C_crc64, base))
dest <- file.path(tempdir(), paste0("libloc_", enc, ".rds"))
test <- file.exists(dest) &&
file.mtime(dest) > file.mtime(lib) &&
(val <- readRDS(dest))$base == base
if(isTRUE(as.vector(test)))
## use the cache file
retval <- rbind(retval, val$value)
else {
ret0 <- .readPkgDesc(lib, fields)
if(length(ret0)) {
retval <- rbind(retval, ret0, deparse.level = 0L)
## save the cache file
saveRDS(list(base = base, value = ret0), dest)
} else unlink(dest)
}
}
}
.fixupPkgMat(retval, fields, priority, subarch)
}
.fixupPkgMat <- function(mat, fields, priority, subarch=NULL)
{
## to be used in installed.packages() and similar
colnames(mat) <- c("Package", "LibPath", fields)
if (length(mat) && !is.null(priority)) {
keep <- !is.na(pmatch(mat[,"Priority"], priority,
duplicates.ok = TRUE))
mat <- mat[keep, , drop = FALSE]
}
if (length(mat) && !is.null(subarch) && nzchar(subarch)) {
archs <- strsplit(mat[, "Archs"], ", ", fixed = TRUE)
keep <- unlist(lapply(archs,
function(x) is.na(x[1L]) || subarch %in% x))
mat <- mat[keep, , drop = FALSE]
}
if (length(mat)) mat <- mat[, colnames(mat) != "Archs", drop = FALSE]
if (length(mat)) rownames(mat) <- mat[, "Package"]
mat
}
remove.packages <- function(pkgs, lib)
{
updateIndices <- function(lib) {
## This matches what install.packages() does
if(lib == .Library && .Platform$OS.type == "unix") {
message("Updating HTML index of packages in '.Library'")
make.packages.html(.Library)
}
## FIXME: only needed for packages installed < 2.13.0,
## so remove eventually
## is this the lib now empty?
Rcss <- file.path(lib, "R.css")
if (file.exists(Rcss)) {
pkgs <- Sys.glob(file.path(lib, "*", "Meta", "package.rds"))
if (!length(pkgs)) unlink(Rcss)
}
}
if(!length(pkgs)) return(invisible())
if(missing(lib) || is.null(lib)) {
lib <- .libPaths()[1L]
message(sprintf(ngettext(length(pkgs),
"Removing package from %s\n(as %s is unspecified)",
"Removing packages from %s\n(as %s is unspecified)"),
sQuote(lib), sQuote("lib")), domain = NA)
}
paths <- find.package(pkgs, lib)
if(length(paths)) {
unlink(paths, TRUE)
for(lib in unique(dirname(paths))) updateIndices(lib)
}
invisible()
}
download.packages <- function(pkgs, destdir, available = NULL,
repos = getOption("repos"),
contriburl = contrib.url(repos, type),
method, type = getOption("pkgType"), ...)
{
nonlocalcran <- !all(startsWith(contriburl, "file:"))
if(nonlocalcran && !dir.exists(destdir))
stop("'destdir' is not a directory")
type <- resolvePkgType(type)
if(is.null(available))
available <-
available.packages(contriburl = contriburl, method = method, ...)
retval <- matrix(character(), 0L, 2L)
for(p in unique(pkgs))
{
ok <- (available[,"Package"] == p)
ok <- ok & !is.na(ok)
if(!any(ok))
warning(gettextf("no package %s at the repositories", sQuote(p)),
domain = NA, immediate. = TRUE)
else {
if(sum(ok) > 1L) { # have multiple copies
vers <- package_version(available[ok, "Version"])
keep <- vers == max(vers)
keep[duplicated(keep)] <- FALSE
ok[ok][!keep] <- FALSE
}
if (startsWith(type, "mac.binary")) type <- "mac.binary"
## in Oct 2009 we introduced file names in PACKAGES files
File <- available[ok, "File"]
fn <- paste0(p, "_", available[ok, "Version"],
switch(type,
"source" = ".tar.gz",
"mac.binary" = ".tgz",
"win.binary" = ".zip"))
have_fn <- !is.na(File)
fn[have_fn] <- File[have_fn]
repos <- available[ok, "Repository"]
if(startsWith(repos, "file:")) { # local repository
## This could be file: + file path or a file:/// URL.
if(startsWith(repos, "file:///")) {
## We need to derive the file name from the URL
## This is tricky as so many forms have been allowed,
## and indeed external methods may do even more.
fn <- paste(substring(repos, 8L), fn, sep = "/")
## This leaves a path beginning with /
if(.Platform$OS.type == "windows") {
if(length(grep("^/[A-Za-z]:", fn)))
fn <- substring(fn, 2L)
}
} else {
fn <- paste(substring(repos, 6L), fn, sep = "/")
}
if(file.exists(fn))
retval <- rbind(retval, c(p, fn))
else
warning(gettextf("package %s does not exist on the local repository", sQuote(p)),
domain = NA, immediate. = TRUE)
} else {
url <- paste(repos, fn, sep = "/")
destfile <- file.path(destdir, fn)
res <- try(download.file(url, destfile, method, mode = "wb",
...))
if(!inherits(res, "try-error") && res == 0L)
retval <- rbind(retval, c(p, destfile))
else
warning(gettextf("download of package %s failed", sQuote(p)),
domain = NA, immediate. = TRUE)
}
}
}
retval
}
resolvePkgType <- function(type) {
## Not entirely clear this is optimal
if(type == "both") type <- "source"
else if(type == "binary") type <- .Platform$pkgType
type
}
contrib.url <- function(repos, type = getOption("pkgType"))
{
type <- resolvePkgType(type)
if(is.null(repos)) return(NULL)
if("@CRAN@" %in% repos && interactive()) {
cat(gettext("--- Please select a CRAN mirror for use in this session ---"),
"\n", sep = "")
flush.console()
chooseCRANmirror()
m <- match("@CRAN@", repos)
nm <- names(repos)
repos[m] <- getOption("repos")["CRAN"]
if(is.null(nm)) nm <- rep.int("", length(repos))
nm[m] <- "CRAN"
names(repos) <- nm
}
if("@CRAN@" %in% repos) stop("trying to use CRAN without setting a mirror")
ver <- paste(R.version$major,
strsplit(R.version$minor, ".", fixed=TRUE)[[1L]][1L], sep = ".")
mac.path <- "macosx"
if (substr(type, 1L, 11L) == "mac.binary.") {
mac.path <- paste(mac.path, substring(type, 12L), sep = "/")
type <- "mac.binary"
}
res <- switch(type,
"source" = paste(gsub("/$", "", repos), "src", "contrib", sep = "/"),
"mac.binary" = paste(gsub("/$", "", repos), "bin", mac.path, "contrib", ver, sep = "/"),
"win.binary" = paste(gsub("/$", "", repos), "bin", "windows", "contrib", ver, sep = "/")
)
res
}
.getMirrors <- function(url, local.file, all, local.only)
{
m <- NULL
if(!local.only) {
## Try to handle explicitly failure to connect to CRAN.
f <- tempfile()
on.exit(unlink(f))
m <- tryCatch({
m <- download.file(url, destfile = f, quiet = TRUE)
if(m != 0L)
stop(gettextf("'download.file()' error code '%d'", m))
read.csv(f, as.is = TRUE, encoding = "UTF-8")
}, error=function(err) {
warning(gettextf("failed to download mirrors file (%s); using local file '%s'",
conditionMessage(err), local.file),
call.=FALSE, immediate.=TRUE)
NULL
})
}
if(is.null(m))
m <- read.csv(local.file, as.is = TRUE, encoding = "UTF-8")
if(!all) m <- m[as.logical(m$OK), ]
m
}
getCRANmirrors <- function(all = FALSE, local.only = FALSE)
{
.getMirrors("https://cran.r-project.org/CRAN_mirrors.csv",
file.path(R.home("doc"), "CRAN_mirrors.csv"),
all = all, local.only = local.only)
}
.chooseMirror <- function(m, label, graphics, ind)
{
if(is.null(ind) && !interactive())
stop("cannot choose a ", label, " mirror non-interactively")
if (length(ind))
res <- as.integer(ind)[1L]
else {
isHTTPS <- (startsWith(m[, "URL"], "https") &
grepl("secure_mirror_from_master",
m[, "Comment"],
fixed = TRUE))
mHTTPS <- m[isHTTPS,]
mHTTP <- m[!isHTTPS,]
httpsLabel <- paste("Secure", label, "mirrors")
httpLabel <- paste("Other", label, "mirrors")
m <- mHTTPS
res <- menu(c(m[, 1L], "(other mirrors)"), graphics, httpsLabel)
if (res > nrow(m)) {
m <- mHTTP
res <- menu(m[, 1L], graphics, httpLabel)
}
}
if (res > 0L) {
URL <- m[res, "URL"]
names(URL) <- m[res, "Name"]
sub("/$", "", URL[1L])
} else character()
}
chooseCRANmirror <- function(graphics = getOption("menu.graphics"), ind = NULL,
local.only = FALSE)
{
m <- getCRANmirrors(all = FALSE, local.only = local.only)
url <- .chooseMirror(m, "CRAN", graphics, ind)
if (length(url)) {
repos <- getOption("repos")
repos["CRAN"] <- url
options(repos = repos)
}
invisible()
}
chooseBioCmirror <- function(graphics = getOption("menu.graphics"), ind = NULL,
local.only = FALSE)
{
m <- .getMirrors("https://bioconductor.org/BioC_mirrors.csv",
file.path(R.home("doc"), "BioC_mirrors.csv"),
all = FALSE, local.only = local.only)
url <- .chooseMirror(m, "BioC", graphics, ind)
if (length(url))
options(BioC_mirror = url)
invisible()
}
setRepositories <-
function(graphics = getOption("menu.graphics"), ind = NULL,
addURLs = character())
{
if(is.null(ind) && !interactive())
stop("cannot set repositories non-interactively")
a <- tools:::.get_repositories()
pkgType <- getOption("pkgType")
if (pkgType == "both") pkgType <- "source" #.Platform$pkgType
if (pkgType == "binary") pkgType <- .Platform$pkgType
if(startsWith(pkgType, "mac.binary")) pkgType <- "mac.binary"
thisType <- a[[pkgType]]
a <- a[thisType, 1L:3L]
repos <- getOption("repos")
## Now look for CRAN and any others in getOptions("repos")
if("CRAN" %in% row.names(a) && !is.na(CRAN <- repos["CRAN"]))
a["CRAN", "URL"] <- CRAN
## Set as default any already in the option.
a[(a[["URL"]] %in% repos), "default"] <- TRUE
new <- !(repos %in% a[["URL"]])
if(any(new)) {
aa <- names(repos[new])
if(is.null(aa)) aa <- rep.int("", length(repos[new]))
aa[aa == ""] <- repos[new][aa == ""]
newa <- data.frame(menu_name=aa, URL=repos[new], default=TRUE)
row.names(newa) <- aa
a <- rbind(a, newa, deparse.level = 0L)
}
default <- a[["default"]]
res <- if(length(ind)) as.integer(ind)
else {
title <- if(graphics) "Repositories" else gettext("--- Please select repositories for use in this session ---\n")
match(select.list(a[, 1L], a[default, 1L], multiple = TRUE, title,
graphics = graphics), a[, 1L])
}
if(length(res) || length(addURLs)) {
repos <- a[["URL"]]
names(repos) <- row.names(a)
repos <- c(repos[res], addURLs)
options(repos = repos)
}
}
## used in some BioC packages and their support in tools.
compareVersion <- function(a, b)
{
if(is.na(a)) return(-1L)
if(is.na(b)) return(1L)
a <- as.integer(strsplit(a, "[.-]")[[1L]])
b <- as.integer(strsplit(b, "[.-]")[[1L]])
for(k in seq_along(a))
if(k <= length(b)) {
if(a[k] > b[k]) return(1) else if(a[k] < b[k]) return(-1L)
} else return(1L)
if(length(b) > length(a)) return(-1L) else return(0L)
}
## ------------- private functions --------------------
.clean_up_dependencies <- function(x, available = NULL)
{
## x is a character vector of Depends / Suggests / Imports entries
## returns a character vector of all the package dependencies mentioned
x <- x[!is.na(x)]
if(!length(x)) return(x)
x <- unlist(strsplit(x, ","))
unique(sub("^[[:space:]]*([[:alnum:].]+).*$", "\\1" , x))
}
.clean_up_dependencies2 <- function(x, installed, available)
{
## x is a character vector of Depends / Suggests / Imports entries.
## Returns a list of length 2, a character vector of the names of
## all the package dependencies mentioned that are not already
## satisfied and one of those which cannot be satisfied (possibly
## of the form "pkg (>= ver)')
.split_dependencies <- function(x) {
.split2 <- function(x) {
## some have had space before ,
x <- sub('[[:space:]]+$', '', x)
x <- unique(sub("^[[:space:]]*(.*)", "\\1" , x))
names(x) <- sub("^([[:alnum:].]+).*$", "\\1" , x)
x <- x[names(x) != "R"]
x <- x[nzchar(x)]
## FIXME: a better way to handle duplicates.
## However, there should not be any, and if there are
## Depends: should be the first.
x <- x[!duplicated(names(x))]
lapply(x, tools:::.split_op_version)
}
## given one of more concatenations of Depends/Imports/Suggests fields,
## return a named list of list(name, [op, version])
if(!any(nzchar(x))) return(list())
unlist(lapply(strsplit(x, ","), .split2), FALSE, FALSE)
}
x <- x[!is.na(x)]
if(!length(x)) return(list(character(), character()))
xx <- .split_dependencies(x)
if(!length(xx)) return(list(character(), character()))
## Then check for those we already have installed
pkgs <- installed[, "Package"]
have <- sapply(xx, function(x) {
if(length(x) == 3L) {
if (! x[[1L]] %in% pkgs ) return(FALSE)
if(x[[2L]] != ">=") return(TRUE)
## We may have the package installed more than once
## which we get will depend on the .libPaths() order,
## so for now just see if any installed version will do.
current <- as.package_version(installed[pkgs == x[[1L]], "Version"])
target <- as.package_version(x[[3L]])
any(do.call(x$op, list(current, target)))
## eval(parse(text = paste("any(current", x$op, "target)")))
} else x[[1L]] %in% pkgs
})
xx <- xx[!have]
if(!length(xx)) return(list(character(), character()))
## now check if we can satisfy the missing dependencies
pkgs <- row.names(available)
canget <- miss <- character()
for (i in seq_along(xx)) {
x <- xx[[i]]
if(length(x) == 3L) {
if (! x[[1L]] %in% pkgs ) { miss <- c(miss, x[[1L]]); next }
if(x[[2L]] != ">=") { canget <- c(canget, x[[1L]]); next }
## we may have the package available more than once
## install.packages() will find the highest version.
current <- as.package_version(available[pkgs == x[[1L]], "Version"])
target <- as.package_version(x[[3L]])
res <- any(do.call(x$op, list(current, target)))
## res <- eval(parse(text = paste("any(current", x$op, "target)")))
if(res) canget <- c(canget, x[[1L]])
else miss <- c(miss, paste0(x[[1L]], " (>= ", x[[3L]], ")"))
} else if(x[[1L]] %in% pkgs) canget <- c(canget, x[[1L]])
else miss <- c(miss, x[[1L]])
}
list(canget, miss)
}
.make_dependency_list <-
function(pkgs, available,
dependencies = c("Depends", "Imports", "LinkingTo"),
recursive = FALSE)
{
## given a character vector of packages,
## return a named list of character vectors of their dependencies.
## If recursive = TRUE, do this recursively.
if(!length(pkgs)) return(NULL)
if(is.null(available))
stop(gettextf("%s must be supplied", sQuote("available")), domain = NA)
info <- available[pkgs, dependencies, drop = FALSE]
x <- vector("list", length(pkgs)); names(x) <- pkgs
if(recursive) {
known <- row.names(available)
xx <- vector("list", length(known)); names(xx) <- known
info2 <- available[, dependencies, drop = FALSE]
for (i in seq_along(known))
xx[[i]] <- .clean_up_dependencies(info2[i, ])
for (i in pkgs) {
p <- xx[[i]]
p <- p[p %in% known]; p1 <- p
repeat {
extra <- unlist(xx[p1])
extra <- extra[extra != i]
extra <- extra[extra %in% known]
deps <- unique(c(p, extra))
if (length(deps) <= length(p)) break
p1 <- deps[!deps %in% p]
p <- deps
}
x[[i]] <- p
}
} else {
for (i in seq_along(pkgs)) x[[i]] <- .clean_up_dependencies(info[i, ])
}
x
}
.find_install_order <- function(pkgs, dependencyList)
{
## given a character vector of packages, find an install order
## which reflects their dependencies.
DL <- dependencyList[pkgs]
## some of the packages may be already installed, but the
## dependencies apply to those being got from CRAN.
DL <- lapply(DL, function(x) x[x %in% pkgs])
lens <- lengths(DL)
if(all(lens > 0L)) {
warning("every package depends on at least one other")
return(pkgs)
}
done <- names(DL[lens == 0L]); DL <- DL[lens > 0L]
while(length(DL)) {
OK <- vapply(DL, function(x) all(x %in% done), NA)
if(!any(OK)) {
warning(gettextf("packages %s are mutually dependent",
paste(sQuote(names(DL)), collapse = ", ")),
domain = NA)
return(c(done, names(DL)))
}
done <- c(done, names(DL[OK]))
DL <- DL[!OK]
}
done
}