rdependenciesremotes

For a set of (installed) R packages how can I find out if all dependencies are at least minimum required versions for the given setup?


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")

Solution

  • 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