I'm running a somewhat tight ship in regard to available libraries in my docker based R setup. Thus, package updates are performed conservatively and consciously. New versions are more or less hand picked. In this context I realised that incrementing a package's version will not complain in regard to outdated dependencies if the dependency is already installed in a smaller version than is actually required.
An example is shown below to demonstrate what I mean. The example was created on a "clean" R 4.1.2
without any packages installed except for the ones that come directly with R.
I'm aware that the paramter settings dependencies = FALSE
and upgrade = "never"
prevent installing/upgrading dependencies (I need these settings for stringent control), however, I would have expected at least a warning telling me that e.g. askpass 1.2.0
requires at least sys 2.1
or equivalently that highr 0.10
requires at least xfun 0.18
.
Presumably problems will only start showing up on runtime of certain features, which I would like to prevent.
Is there a clever way handle this?
install.packages("remotes", dependencies = FALSE) # 2.4.2.1
remotes::install_version("sys",
version = "2.0",
dependencies = FALSE,
upgrade = "never")
remotes::install_version("askpass",
version = "1.2.0",
dependencies = FALSE,
upgrade = "never")
remotes::install_version("xfun",
version = "0.17",
dependencies = FALSE,
upgrade = "never")
remotes::install_version("highr",
version = "0.10",
dependencies = FALSE,
upgrade = "never")
Not sure if this qualifies as "clever" but at least it returns the required information to remedy outdated dependencies problem.
# these packages do not interfere with the
# example but are required to run the below code
#install.packages("dplyr")
#install.packages("purrr")
#install.packages("stringr")
library(dplyr) # 1.1.3
library(purrr) # 1.0.2
library(stringr) # 1.5.0
inst_packs <- as.data.frame(installed.packages())
inst_packs_name_version <- inst_packs %>%
select(Package, Version)
# single vector
dependency_info <- unlist(strsplit(c(inst_packs$Depends,
inst_packs$Imports,
inst_packs$LinkingTo),
split = ",",
fixed = TRUE)) %>%
str_squish() %>%
unique() %>%
discard(is.na)
splits <- strsplit(dependency_info, split = "\\(")
package <- str_squish(as.character(map(splits, 1)))
version <- gsub(x = as.character(map(splits, 2)),
pattern = "[^0-9.-]",
replacement = "")
deps <- data.frame(Package = package, Version = version) %>%
# highest Version is up top
arrange(Package, desc(Version)) %>%
# dependencies without version do not need to be considered
# also R is not a relevant dependency here
filter(Version != "" & Package != "R")
deps_uniq <- deps[!duplicated(deps$Package), ] %>%
arrange(Package)
row.names(deps_uniq) <- NULL
relevant_installed <- inst_packs_name_version %>%
filter(Package %in% deps_uniq$Package) %>%
arrange(Package, desc(Version))
# base R packages that are installed by the user too appear more than once
# e.g. "Matrix", "survival"
relevant_installed_uniq <- relevant_installed[!duplicated(relevant_installed$Package),]
row.names(relevant_installed_uniq) <- NULL
# sanity check
if(! all(deps_uniq$Package == relevant_installed_uniq$Package)) {
stop("Different set of packages. Assumptions are not met.")
}
reference_df <- data.frame(Package = deps_uniq$Package,
Version_req = deps_uniq$Version,
Version_inst = relevant_installed_uniq$Version)
# show only packages where a later version than the installed one is required
reference_df %>%
rowwise() %>%
mutate(req_larger_inst = compareVersion(Version_req, Version_inst)) %>%
filter(req_larger_inst > 0)
Returns:
# A tibble: 2 × 4
# Rowwise:
Package Version_req Version_inst req_larger_inst
<chr> <chr> <chr> <dbl>
1 sys 2.1 2.0 1
2 xfun 0.18 0.17 1