R/pkgDep.R

Defines functions purgeAvailablePackages pkgDep2 getVersionOptionPkgEnv noHttp getArchiveDetailsInnerMemoise getAvailablePackagesCheckAdditRepos toPkgDepDT installedVersionOKPrecise depsImpsSugsLinksToWhich clearRequirePackageCache cacheClearPackages getAvailablePackagesIfNeeded prependSelf singularPlural hasHave isAre pkgDepDBFilename pkgDepTopoSortMemoise paddedFloatToChar masterMainToHead purgePkgDep dealWithCache purgeCache cachePurge purgeBasedOnTimeSinceCached checkCircular pkgDepIfDepRemoved .installed.pkgs whichToDILES DESCRIPTIONFile DESCRIPTIONFileDeps pkgDepTopoSort

Documented in cacheClearPackages cachePurge clearRequirePackageCache .installed.pkgs masterMainToHead paddedFloatToChar pkgDep2 pkgDepIfDepRemoved pkgDepTopoSort purgeCache

utils::globalVariables(
  c("Additional_repositories", "githubPkgName",
    "localBranch", "localFiles", "localRepo", "installedSha", "localUsername",
    "newPackageFullName", "..keepNames")
)


#' Reverse package depends
#'
#' This is a wrapper around `tools::dependsOnPkgs`,
#' but with the added option of `topoSort`, which
#' will sort them such that the packages at the top will have
#' the least number of dependencies that are in `packages`.
#' This is essentially a topological sort, but it is done
#' heuristically. This can be used to e.g., `detach` or
#' `unloadNamespace` packages in order so that they each
#' of their dependencies are detached or unloaded first.
#' @inheritParams Require
#' @param deps An optional named list of (reverse) dependencies.
#'   If not supplied, then `tools::dependsOnPkgs(..., recursive = TRUE)`
#'   will be used
#' @param topoSort Logical. If `TRUE`, the default, then
#'   the returned list of packages will be in order with the
#'   least number of dependencies listed in `packages` at
#'   the top of the list.
#' @param reverse Logical. If `TRUE`, then this will use `tools::pkgDependsOn`
#'   to determine which packages depend on the `packages`
#' @param useAllInSearch Logical. If `TRUE`, then all non-core
#' R packages in `search()` will be appended to `packages`
#' to allow those to also be identified
#' @param returnFull Logical. Primarily useful when `reverse = TRUE`.
#'   If `TRUE`, then then all installed packages will be searched.
#'   If `FALSE`, the default, only packages that are currently in
#'   the `search()` path and passed in `packages` will be included
#'   in the possible reverse dependencies.
#'
#' @inheritParams Require
#' @export
#' @rdname pkgDep
#' @return
#' A possibly ordered, named (with packages as names) list where list elements
#' are either full reverse depends.
#'
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDepTopoSort(c("Require", "data.table"), reverse = TRUE)
#'
#'   Require:::.cleanup(opts)
#' }
#' }
#'
pkgDepTopoSort <- function(packages,
                           deps,
                           reverse = FALSE,
                           topoSort = TRUE,
                           libPaths,
                           useAllInSearch = FALSE,
                           returnFull = TRUE,
                           recursive = TRUE,
                           purge = getOption("Require.purge", FALSE),
                           which = c("Depends", "Imports", "LinkingTo"),
                           type = getOption("pkgType"),
                           verbose = getOption("Require.verbose"), ...) {

  libPaths <- dealWithMissingLibPaths(libPaths, ...)
  packages <- parseMultiLinePackages(packages)

  if (isTRUE(useAllInSearch)) {
    if (missing(deps)) {
      a <- search()
      a <- setdiff(a, .defaultPackages)
      a <- gsub("package:", "", a)
      packages <- unique(c(packages, a))
    } else {
      messageVerbose(
        "deps is provided; useAllInSearch will be set to FALSE",
        verbose = verbose,
        verboseLevel = 2
      )
    }
  }

  names(packages) <- packages
  if (missing(deps)) {
    aa <- if (isTRUE(reverse)) {
      ip <- .installed.pkgs(lib.loc = libPaths, which = which, collapse = TRUE) # need all installed packages
      ip <- installed.packagesDeps(ip, libPaths = libPaths, which = which[1])
      deps <- depsWithCommasToVector(ip$Package, ip$deps)
      deps <- lapply(deps, extractPkgName)
      # names(deps) <- ip[, "Package"]
      # names(packages) <- packages
      deps <- deps[order(names(deps))]
      revDeps <-
        lapply(packages, function(p) {
          names(unlist(
            lapply(deps, function(d) {
              if (isTRUE(any(p %in% d))) {
                TRUE
              } else {
                NULL
              }
            })
          ))
        })
      if (recursive) {
        revDeps <- lapply(revDeps, function(p) {
          if (!is.null(p)) {
            used <- p
            repeat ({
              r <-
                unique(unlist(lapply(p, function(p1) {
                  names(unlist(
                    lapply(deps, function(d) {
                      if (isTRUE(any(
                        p1 %in% d
                      ))) {
                        TRUE
                      } else {
                        NULL
                      }
                    })
                  ))
                })))

              r <- setdiff(r, used) # addresses circularity

              used <- unique(c(r, used))
              if (length(r) == 0) {
                break
              }

              p <- r
            })
          } else {
            used <- NULL
          }
          sort(used)
        })
      }
    } else {
      pkgDep(
        packages,
        recursive = TRUE,
        purge = purge,
        libPaths = libPaths,
        which = which,
        verbose = verbose,
        includeSelf = FALSE
      )
    }
  } else {
    aa <- deps
  }
  bb <- list()

  aa <- checkCircular(aa)
  cc <- lapply(aa, function(x) {
    character()
  })
  dd <- lapply(cc, function(x) {
    0
  })


  if (length(aa) > 1) {
    lengths <- lengths(aa)
    aa <- aa[order(lengths)]
    cc <- cc[order(lengths)]
    dd <- lapply(cc, function(x) {
      0
    })

    ddIndex <- 0
    priorsBeingInstalled <- priorsAlreadyInstalled <- character()

    if (isTRUE(topoSort)) {
      notInOrder <- TRUE
      isCorrectOrder <- logical(length(aa))
      i <- 1
      newOrd <- numeric(0)
      for (i in seq_along(aa)) {
        dif <- setdiff(seq_along(aa), newOrd)
        pkgNameNames <- extractPkgName(names(aa))
        for (j in dif) {
          pkgName <- extractPkgName(aa[[j]])
          overlapFull <- pkgName %in% pkgNameNames[-i]
          overlap <- pkgName %in% pkgNameNames[dif]
          overlapPkgs <- pkgName[overlapFull]
          isCorrectOrder <- !any(overlap)
          if (isCorrectOrder) {
            cc[j] <- list(overlapPkgs)
            priorsBeingInstalled <-
              vapply(dd, function(x) {
                if (is.numeric(x)) {
                  x == ddIndex
                } else {
                  FALSE
                }
              }, logical(1))
            priorsBeingInstalled <-
              extractPkgName(names(priorsBeingInstalled)[priorsBeingInstalled])
            overlapPkgsAdditional <-
              intersect(overlapPkgs, priorsBeingInstalled)
            if (length(overlapPkgsAdditional)) {
              ddIndex <- ddIndex + 1
              priorsAlreadyInstalled <-
                vapply(dd, function(x) {
                  if (is.numeric(x)) {
                    x < ddIndex
                  } else {
                    FALSE
                  }
                }, logical(1))
              priorsAlreadyInstalled <-
                extractPkgName(names(priorsAlreadyInstalled)[priorsAlreadyInstalled])
            }
            dd[j] <- list(ddIndex)

            newOrd <- c(newOrd, j)
            # i <- i + 1
            break
          }
        }
      }
      aa <- aa[newOrd]
      cc <- cc[newOrd]
      dd <- dd[newOrd]
    }
  }

  out <- if (isTRUE(returnFull)) {
    aa
  } else {
    cc
  }
  attr(out, "installSafeGroups") <- dd

  return(out)
}

.defaultPackages <-
  c(
    ".GlobalEnv",
    "tools:rstudio",
    "package:stats",
    "package:graphics",
    "package:grDevices",
    "package:utils",
    "package:datasets",
    "package:methods",
    "Autoloads",
    "package:base",
    "devtools_shims"
  )


# @inheritParams Require
# pkgDepCRANInner <-
#   function(ap,
#            which,
#            pkgDT,
#            repos,
#            purge,
#            type,
#            # pkgsNoVersion,
#            # keepVersionNumber,
#            # keepSeparate = FALSE,
#            verbose = getOption("Require.verbose")) {
#
#     if (is.null(pkgDT$Depends)) {
#       if (missing(ap))
#         ap <- getAvailablePackagesIfNeeded(pkgDT$Package, repos = repos, purge = purge, verbose = verbose, type = type)
#       keepNames <- c("Package", setdiff(colnames(pkgDT), colnames(ap)))
#       pkgDT <- ap[pkgDT[, ..keepNames], on = "Package"]
#     }
#
#     pkgDT[is.na(versionSpec), availableVersionOK := TRUE]
#     pkgDT[!is.na(versionSpec), availableVersionOK := compareVersion2(Version, versionSpec, inequality)]
#     for (co in which)
#       set(pkgDT, which(is.na(pkgDT[[co]])), co, "")
#
#     pkgDT[, deps := do.call(paste, append(.SD, list(sep = comma))), .SDcols=which]
#
#     # remove trailing and initial commas
#     set(pkgDT, NULL, "deps", gsub("(, )+$", "", pkgDT$deps))
#     set(pkgDT, NULL, "deps", gsub("^(, )+", "", pkgDT$deps))
#
#     deps <- Map(pkgFN = pkgDT$packageFullName, x = pkgDT$deps, function(pkgFN, x) {
#       out <- strsplit(x, split = "(, {0,1})|(,\n)")[[1]]
#       out <- out[!is.na(out)]
#       out <-
#         grep(.grepR, out, value = TRUE, invert = TRUE)
#       out
#     })
#     depsAll <- Map(toPkgDepDT, deps, verbose = verbose)
#     depsAll
#   }

DESCRIPTIONFileDeps <-
  function(desc_path,
           which = c("Depends", "Imports", "LinkingTo"),
           keepVersionNumber = TRUE,
           purge = getOption("Require.purge", FALSE),
           keepSeparate = FALSE) {
    if (is.null(pkgDepEnv())) {
      envPkgDepCreate()
    }
    if (is.null(envPkgDepDESCFile())) {
      envPkgDepDESCFileCreate()
    }
    objName <- if (length(desc_path) == 1) {
      paste0(
        desc_path,
        paste0(collapse = "_", which, "_", keepVersionNumber)
      )
    } else {
      grepPackage <- "Package *: *"
      grepVersion <- "Version *: *"
      suppressWarnings({
        pkg <-
          gsub(
            grepPackage,
            "",
            grep(grepPackage, desc_path, value = TRUE)
          )
      })
      suppressWarnings({
        vers <-
          gsub(
            grepVersion,
            "",
            grep(grepVersion, desc_path, value = TRUE)
          )
      })
      paste(pkg,
        vers,
        sep = "_",
        paste0(which, "_", keepVersionNumber, collapse = "_")
      )
    }
    if (isTRUE(length(objName) > 1) ||
      isTRUE(any(!exists(objName, envir = envPkgDepDESCFile()))) ||
      isTRUE(purge)) {
      if (is.null(desc_path)) {
        needed <- NULL
      } else {

        lines <- if (length(desc_path) == 1) {
          # linesAll <- lapply(desc_path, read.dcf)
          try(readLines(desc_path), silent = TRUE)
        } else {
          lines <- desc_path
        }
        if (is(lines, "try-error")) {
          stop("Cannot read the file: ", desc_path)
        }
        Sys.setlocale(locale = "C") # required to deal with non English characters in Author names
        on.exit(Sys.setlocale(locale = ""))
        sl <- list()
        sl[["Depends"]] <- grep("^Depends: *", lines) # nolint
        sl[["Suggests"]] <- grep("^Suggests: *", lines) # nolint
        sl[["Imports"]] <- grep("^Imports: *", lines) # nolint
        sl[["Linkingto"]] <- grep("^LinkingTo: *", lines) # nolint
        sl[["Remotes"]] <- grep("^Remotes: *", lines) # nolint
        sl[["colon"]] <- grep(": *", lines) # nolint
        # sl[["Additional_repositories"]] <- grep("^Additional_repositories: *", lines) # nolint

        which <-
          paste0(toupper(substr(which, 1, 1)), substr(which, 2, 1e4))
        which <- unique(which)
        whichLower <- tolower(which)
        needed <-
          Map(#whLower = whichLower,
              # wh = which, function(whLower, wh) {
                wh = which, function(wh) {
            if (length(sl[[wh]])) {
              whEnd <- which(sl[["colon"]] %in% sl[[wh]]) + 1
              whEnd <- if (length(sl[["colon"]]) < whEnd) {
                length(lines)
              } else {
                (sl[["colon"]][whEnd] - 1)
              }
              allLines <- sl[[wh]]:whEnd # nolint
              needs <- paste(lines[allLines], collapse = "")
              needs <- gsub(paste0(wh, ": *"), "", needs)
              needs <- strsplit(needs, split = ", *")
              needs <-
                gsub(" *$", "", needs[[1]]) # remove trailing spaces
              if (isFALSE(keepVersionNumber)) {
                needs <- trimVersionNumber(needs)
              }
              needs
            }
          })
        if (length(objName) == 1) {
          assign(objName, needed, envir = envPkgDepDESCFile())
        }
      }
    } else {
      needed <-
        get(objName, envir = envPkgDepDESCFile())
    }
    if (!keepSeparate) {
      needed <- unname(unlist(needed))
      needed <- grep("^R[\\( ]", needed, value = TRUE, invert = TRUE)
    }
    needed
  }

DESCRIPTIONFile <- function(desc_path) {
  lines <- readLines(desc_path)
}

whichToDILES <- function(which) {
  if (identical("all", which) || is.null(which)) {
    which <-
      list(c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances"))
  } else if (identical("most", which)) {
    which <- list(c("Depends", "Imports", "LinkingTo", "Suggests"))
  } else if (isTRUE(which)) {
    which <- list(
      c("Depends", "Imports", "LinkingTo", "Suggests"),
      c("Depends", "Imports", "LinkingTo")
    )
  } else if (any(is.na(which))) {
    which <- list(c("Depends", "Imports", "LinkingTo"))
  } else if (is.character(which)) {
    which <- list(which)
  } else if (isFALSE(which)) {
    which <- character()
  }
  which
}

#' Partial alternative (faster) to `installed.packages`
#'
#' This reads the DESCRIPTION files only, so can only access fields that are
#' available in the DESCRIPTION file. This is different than `installed.packages`
#' which has many other fields, like "Built", "NeedsCompilation" etc. If those
#' fields are needed, then this function will return an empty column in the returned
#' character matrix.
#' @inheritParams utils::installed.packages
#' @inheritParams pkgDep
#' @param other Can supply other fields; the only benefit here is that a user
#'   can specify `"github"` (lower case) and it will automatically add
#'   c("GithubRepo", "GithubUsername", "GithubRef", "GithubSHA1",
#'   "GithubSubFolder") fields
#' @param collapse Logical. If `TRUE` then the dependency fields will be collapsed;
#'   if `FALSE` (default) then the `which` fields will be kept separate.
#' @param packages Character vector. If `NULL` (default), then all installed packages
#'   are searched for. If a character vector is supplied, then it will only return
#'   information about those packages (and is thus faster to execute).
.installed.pkgs <-
  function(lib.loc = .libPaths(),
           which = c("Depends", "Imports", "LinkingTo"),
           other = NULL,
           purge = getOption("Require.purge", FALSE),
           packages = NULL,
           collapse = FALSE) {
    purge <- dealWithCache(purge)
    if (!is.null(other)) {
      hasGit <- grepl("github", tolower(other))
      if (any(hasGit)) {
        other <- c(
          c("GithubRepo", "GithubUsername", "GithubRef", "GithubSHA1",
            "GithubSubFolder"),
          other[!hasGit])
      }
    }

    out <- lapply(lib.loc, function(path) {
      dirs <- dir(path, full.names = TRUE)
      # from pak -- makes a _cache which isn't relevant here
      dirs <- dirs[!endsWith(dirs, suffix = "_cache")]
      mat <- NULL
      if (length(dirs)) {
        if (!is.null(packages)) {
          keeps <- basename(dirs) %in% packages
          dirs <- dirs[keeps]
        }
        areDirs <- dir.exists(dirs)
        dirs <- dirs[areDirs]
        files <- file.path(dirs, "DESCRIPTION")
        filesExist <- file.exists(files)
        files <- files[filesExist]
        names(files) <- basename(dirs[filesExist])

        desc_lines <-
          lapply(files, function(file) {
            DESCRIPTIONFile(file)
          })
        versions <- DESCRIPTIONFileVersionV(desc_lines, purge = FALSE)
        deps <- if (length(which)) {
          names(which) <- which
          deps <- lapply(desc_lines, function(lines) {
            if (isTRUE(collapse))
              Map(a = "Depends", function(a) paste(DESCRIPTIONFileDeps(lines, which = which, purge = purge), collapse = comma))
            else
              lapply(which, function(wh)
                paste(DESCRIPTIONFileDeps(lines, which = wh, purge = purge), collapse = comma)
              )
          })
          if (length(deps))
            deps <- invertList(deps)
          deps
        } else {
          NULL
        }
        if (!is.null(other)) {
          names(other) <- other
          others <-
            lapply(other, function(oth) {
              DESCRIPTIONFileOtherV(desc_lines, other = oth)
            })
          # shas <- DESCRIPTIONFileOtherV(desc_lines)
        }
        mat <-
          cbind(
            "Package" = dirs[filesExist],
            "Version" = versions
          )
        if (!is.null(unlist(deps))) {
          cn <- c(colnames(mat), names(deps))
          mat <- cbind(mat, matrix(unlist(deps), ncol = length(deps)))
          colnames(mat) <- cn
        }
        if (!is.null(other)) {
          others <- lapply(others, function(co) {
            if (!is(co, "character")) {
              as.character(co)
            } else {
              co
            }
          })
          othersDF <- as.data.frame(others, stringsAsFactors = FALSE)
          mat <- cbind(mat, othersDF, stringsAsFactors = FALSE)
        }
      }
      mat
    })

    lengths <- unlist(lapply(out, function(x) {
      NROW(x)
    }))

    out <- do.call(rbind, out)
    colNames <- c("Package", "Version", "LibPath", which, other)
    if (is.null(out)) {
      out <- matrix(data = character(), ncol = 3 + length(which) + length(other))
      colnames(out) <- colNames
    } else {
      out[, "Package"] <- basename(out[, "Package"])
      out <- cbind(out, "LibPath" = rep(lib.loc, lengths), stringsAsFactors = FALSE)

      dups <- duplicated(out[, "Package"]) # means installed in >1 .libPaths()
      out <- out[!dups, , drop = FALSE]
    }
    colNames <- intersect(colNames, colnames(out))
    out <- out[, colNames, drop = FALSE]
    # ret <-
    #   cbind(
    #     "Package" = basename(unlist(out[, "Package"])),
    #     "LibPath" = rep(lib.loc, lengths),
    #     "Version" = out[, "Version"]
    #   )
    # if (length(which)) {
    #   ret <-
    #     cbind(ret,
    #       "Dependencies" = out[, "Depends"],
    #       stringsAsFactors = FALSE
    #     )
    # }
    # if (NROW(ret)) {
    #   if (!is.null(other)) {
    #     ncolBefore <- NCOL(ret)
    #     ret <- cbind(ret, out[, other], stringsAsFactors = FALSE)
    #   }
    # }
    out
  }

.basePkgs <-
  c(
    "base",
    "boot",
    "class",
    "cluster",
    "codetools",
    "compiler",
    "datasets",
    "foreign",
    "graphics",
    "grDevices",
    "grid",
    "KernSmooth",
    "lattice",
    "MASS",
    "Matrix",
    "methods",
    "mgcv",
    "nlme",
    "nnet",
    "parallel",
    "rpart",
    "spatial",
    "splines",
    "stats",
    "stats4",
    "survival",
    "tcltk",
    "tools",
    "translations",
    "utils"
  )

DESCRIPTIONFileDepsV <-
  Vectorize(DESCRIPTIONFileDeps,
    vectorize.args = "desc_path",
    SIMPLIFY = FALSE
  )

#' Package dependencies when one or more packages removed
#'
#' This is primarily for package developers. It allows the testing of what the
#' recursive dependencies would be if a package was removed from the immediate
#' dependencies.
#' @param pkg A package name to be testing the dependencies
#' @param depsRemoved A vector of package names who are to be "removed" from the
#'   `pkg` immediate dependencies
#' @export
#' @return A list with 3 named lists `Direct`, `Recursive` and `IfRemoved`.
#' `Direct` will show the top level direct dependencies, either `Remaining` or
#' `Removed`. `Recursive` will show the full recursive dependencies, either
#' `Remaining` or `Removed`. `IfRemoved` returns all package dependencies that
#' are removed for each top level dependency. If a top level dependency is not
#' listed in this final list, then it means that it is also a recursive
#' dependency elsewhere, so its removal has no effect.
#' @inheritParams Require
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDepIfDepRemoved("reproducible", "data.table")
#'
#'   Require:::.cleanup(opts)
#' }
#' }
#'
pkgDepIfDepRemoved <-
  function(pkg = character(),
           depsRemoved = character(),
           verbose = getOption()) {
    if (length(pkg)) {
      p2 <- pkgDep2(pkg, recursive = TRUE)
      p2 <-
        Map(p = p2, nam = names(p2), function(p, nam) {
          setdiff(p, nam)
        })
      names(p2) <- extractPkgName(names(p2))
      p1 <- names(p2) # just the immediate
      p3 <- p2
      p2All <-
        unique(extractPkgName(c(names(p2), unname(unlist(
          p2
        )))))
      p3[depsRemoved] <- NULL
      p3All <-
        unique(extractPkgName(c(names(p3), unname(unlist(
          p3
        )))))
      removed <- sort(setdiff(p2All, p3All))
      left <- sort(p3All)

      removedDirect <- intersect(p1, depsRemoved)
      allInRecursive <- unique(extractPkgName(unname(unlist(p2))))
      direct <- p1
      bestToRemove <- setdiff(direct, allInRecursive)
      bestToRemove <- p2[bestToRemove]
      bestToRemove <-
        Map(
          btr = bestToRemove, top = names(bestToRemove),
          function(btr, top) {
            p4 <- p2
            p4[top] <- NULL
            setdiff(
              extractPkgName(unique(c(
                names(p4), unname(unlist(p4))
              ))),
              extractPkgName(c(top, btr))
            )
          }
        )
      leftDirect <- names(p3)
      pkg <-
        list(
          Direct = list(
            Remaining = leftDirect,
            Removed = removedDirect,
            NRemaining = length(leftDirect),
            NRemoved = length(removedDirect)
          ),
          Recursive = list(
            "Remaining" = left,
            "Removed" = removed,
            NRemaining = length(left),
            NRemoved = length(removed)
          ),
          IfRemoved = bestToRemove
        )
    }
    return(pkg)
  }

checkCircular <- function(aa) {
  circular <- lapply(names(aa), function(outer) {
    circularWh <- extractPkgName(aa[[outer]]) %in% extractPkgName(outer)
    circ <- if (any(circularWh)) {
      aa[[outer]][circularWh]
    } else {
      NULL
    }
  })

  if (length(unlist(circular))) {
    for (i in which(!unlist(lapply(circular, is.null)))) {
      aa[[i]] <- setdiff(aa[[i]], circular[[i]])
    }
  }
  aa
}

# @inheritParams Require
# getGitHubDeps <-
#   function(pkg,
#            pkgDT,
#            which,
#            purge,
#            verbose = getOption("Require.verbose"),
#            includeBase = FALSE) {
#     pkg <- masterMainToHead(pkgDT$packageFullName)
#
#     localVersionOK <- pkgDT$installedVersionOK
#     pkgDepDTOuter <- data.table(packageFullName = character())
#
#     if (any(!localVersionOK %in% TRUE)) {
#       pkgDTNotLocal <- dlGitHubDESCRIPTION(pkgDT[!localVersionOK %in% TRUE], purge = purge)
#     }
#     if (any(localVersionOK %in% TRUE)) {
#       pkgDT[localVersionOK %in% TRUE, DESCFile := base::system.file("DESCRIPTION", package = Package), by = "Package"]
#       if (exists("pkgDTNotLocal", inherits = FALSE))
#         pkgDT <- rbindlist(list(pkgDT[localVersionOK %in% TRUE], pkgDTNotLocal), fill = TRUE, use.names = TRUE)
#     } else {
#       pkgDT <- pkgDTNotLocal
#     }
#
#     hasVersionNum <- pkgDT$hasVers # grep(grepExtractPkgs, pkgDT$packageFullName)
#     set(pkgDT, NULL, "availableVersionOK", NA)
#     if (any(hasVersionNum)) {
#       if (!isTRUE(getOption("Require.offlineMode"))) {
#         pkgDT[hasVersionNum, VersionOnRepos := DESCRIPTIONFileVersionV(DESCFile, purge = purge)]
#         if (is.null(pkgDT$versionSpec)) {
#           pkgDT[hasVersionNum, versionSpec := extractVersionNumber(packageFullName)]
#           pkgDT[hasVersionNum, inequality := extractInequality(packageFullName)]
#         }
#         pkgDT[hasVersionNum, availableVersionOK := compareVersion2(VersionOnRepos, versionSpec, inequality)]
#       }
#     }
#
#
#     if (any(!pkgDT$availableVersionOK %in% FALSE)) {
#       neededV <-
#         try(DESCRIPTIONFileDepsV(pkgDT$DESCFile, which = which, purge = purge))
#       if (is(neededV, "try-error")) {
#         unlink(pkgDT$DESCFile)
#         unlink(pkgDT$destFile)
#         set(pkgDT, NULL, c("DESCFile", "destFile"), NULL)
#         browserDeveloper(paste0("A problem occurred installing ", pkgDT$packageFullName, ". Does it exist?",
#                                 "\nTo confirm whether it exists, try browsing to ",
#                                 file.path("https://github.com", pkgDT$Account, pkgDT$Package, "tree", pkgDT$Branch),
#                                 "\nIf it does exist, try rerunning with `purge = TRUE`",
#                                 "\nIf this error is inaccurate, and the problem persists, ",
#                                 "please contact developers with error code 949"))
#       }
#
#       neededAdditionalReposV <- DESCRIPTIONFileOtherV(pkgDT$DESCFile, other = "Additional_repositories")
#
#       neededRemotesV <-
#         DESCRIPTIONFileDepsV(pkgDT$DESCFile, which = "Remotes", purge = purge)
#       names(neededV) <- pkgDT$packageFullName
#
#       pkgDepDTOuter <- updateWithRemotesNamespaceAddRepos(neededV, neededRemotesV, pkgDT, neededAdditionalReposV, includeBase, verbose)
#     }
#
#     pkgDepDTOuter
#   }

purgeBasedOnTimeSinceCached <- function(savedTime) {
  purgeDiff <-
    as.numeric(Sys.getenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE"))
  if (is.null(savedTime)) {
    purge <- TRUE
  } else {
    purgeDiff <-
      if (identical(purgeDiff, "") ||
          is.na(purgeDiff)) {
        defaultCacheAgeForPurge
      } else {
        purgeDiff
      }
    purge <-
      purgeDiff < as.numeric(difftime(Sys.time(), savedTime, units = "sec"))
  }
  purge
}

defaultCacheAgeForPurge <- 3600

#' Purge everything in the Require cache
#'
#' Clears Require's own bookkeeping caches: the `available.packages()`
#' snapshot, the GitHub SHA database, the `DESCRIPTION` cache, and the
#' pkgDep memoisation database. These live under [cacheDir()] and are
#' distinct from the package binary tarball cache.
#'
#' With `packages = TRUE`, the package binary cache is also cleared by
#' calling [cacheClearPackages()] -- which under `usePak = TRUE` (the
#' default) delegates to `pak::cache_clean()`, and under the legacy path
#' walks Require's bookkeeping dir directly.
#'
#' `purgeCache()` is a deprecated alias.
#'
#' @inheritParams Require
#' @return Run for its side effect, namely, all cached objects are removed.
#'
#' @export
cachePurge <- function(packages = FALSE,
                       repos = getOption("repos")) {
  if (isTRUE(packages))
    Require::cacheClearPackages(ask = FALSE)
  dealWithCache(TRUE, repos = repos)
}

#' @rdname cachePurge
#' @export
purgeCache <- function(packages = FALSE, repos = getOption("repos")) {
  .Deprecated("cachePurge", package = "Require",
              msg = "purgeCache() is deprecated; use cachePurge() instead.")
  cachePurge(packages = packages, repos = repos)
}

dealWithCache <- function(purge = TRUE,
                          checkAge = TRUE,
                          repos = getOption("repos"),
                          force = FALSE) {
  if (isTRUE(getOption("Require.offlineMode"))) {
    purge <- FALSE
    checkAge <- FALSE
  }

  if (isTRUE(force)) {
    purge <- TRUE
    checkAge <- TRUE
  }

  purgeBasedOnTime <- FALSE
  if (!isTRUE(purge) && isTRUE(checkAge)) {
    ee <- pkgEnvStartTime()
    purgeBasedOnTime <- if (is.null(ee)) FALSE else purgeBasedOnTimeSinceCached(ee)
  }
  purge <- purge || purgeBasedOnTime

  if (purge) {
    unlink(availablePackagesCachedPath(repos, type = c("binary", "source")))
    Sys.setenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE" = 0)
    pkgEnvStartTimeCreate()
    unlink(dir(RequireGitHubCacheDir(), full.names = TRUE))
    # getSHAFromGItHubMemoise
    SHAfile <- getSHAFromGitHubDBFilename()
    if (isTRUE(file.exists(SHAfile)))
      unlink(SHAfile)

    purgeAvailablePackages(repos, purge)

    # This is for pkgDep
    fn <- pkgDepDBFilename()
    unlink(fn)
  }

  if (is.null(pkgDepEnv()))
    envPkgDepCreate()

  if (is.null(envPkgDepGitHubSHA()))
    envPkgDepGitHubSHACreate()

  if (is.null(envPkgDepDeps()))
    envPkgDepDepsCreate()

  if (is.null(envPkgDepDESCFile()))
    envPkgDepDESCFileCreate()

  if (is.null(envPkgDepArchiveDetailsInner()))
    envPkgDepArchiveDetailsInnerCreate()

  purgePkgDep(purge)

  purge
}

.grepTooManySpaces <- " {2,}"
.grepTabCR <- "\n|\t"



purgePkgDep <- function(purge) {
  if (isTRUE(purge)) {
    envPkgDepCreate()
    envPkgDepGitHubSHACreate()
    envPkgDepDepsCreate()
    envPkgDepDESCFileCreate()
    envPkgDepArchiveDetailsInnerCreate()
  }
  purge
}

#' This converts master or main to HEAD for a git repo
#'
#' This will also convert a git repo with nothing after the @ to @HEAD
#' @param gitRepo A git repository of the form account/repo with optional @branch
#'    or @sha or @tag
#' @return The git repository with @HEAD if it had @master, @main or no @.
masterMainToHead <- function(gitRepo) {
  masterOrMain <- "@(master|main) *"
  areMasterOrMain <- grepl(masterOrMain, gitRepo)
  if (any(areMasterOrMain)) {
    masterOrMainNoSpace <- "@(master|main)"
    gitRepo[areMasterOrMain] <-
      gsub(paste0("(", masterOrMainNoSpace, ")"), "@HEAD", gitRepo[areMasterOrMain])
  }

  noAt <- !grepl("@", gitRepo)
  if (any(noAt)) {
    gitRepo[noAt] <- paste0(gitRepo[noAt], "@HEAD")
  }
  return(gitRepo)
}

#' Convert numeric to character with padding
#'
#' This will pad floating point numbers, right or left. For integers, either class
#' integer or functionally integer (e.g., 1.0), it will not pad right of the decimal.
#' For more specific control or to get exact padding right and left of decimal,
#' try the \code{stringi} package. It will also not do any rounding. See examples.
#'
#' @param x numeric. Number to be converted to character with padding
#'
#' @param padL numeric. Desired number of digits on left side of decimal.
#'              If not enough, \code{pad} will be used to pad.
#'
#' @param padR numeric. Desired number of digits on right side of decimal.
#'              If not enough, \code{pad} will be used to pad.
#'
#' @param pad character to use as padding (\code{nchar(pad) == 1} must be \code{TRUE}).
#'   Currently, can be only `"0"` or `" "` (i.e., space).
#'
#' @return Character string representing the filename.
#'
#' @author Eliot McIntire and Alex Chubaty
#' @export
#' @rdname paddedFloatToChar
#'
#' @examples
#' paddedFloatToChar(1.25)
#' paddedFloatToChar(1.25, padL = 3, padR = 5)
#' paddedFloatToChar(1.25, padL = 3, padR = 1) # no rounding, so keeps 2 right of decimal
paddedFloatToChar <-
  function(x,
           padL = ceiling(log10(x + 1)),
           padR = 3,
           pad = "0") {
    if (!pad %in% c("0", " ")) {
      stop("pad must be either '0' or ' '")
    }
    xf <- x %% 1
    numDecimals <- nchar(gsub("(.*)(\\.)|([0]*$)", "", xf))

    newPadR <-
      ifelse(abs(xf - 0) < sqrt(.Machine$double.eps),
        0,
        pmax(numDecimals, padR)
      )
    string <-
      paste0(
        paste0("%", pad),
        padL + newPadR + 1 * (newPadR > 0),
        ".",
        newPadR,
        "f"
      )
    xFCEnd <- sprintf(string, x)
    return(xFCEnd)
  }



pkgDepTopoSortMemoise <- function(...) {
  if (getOption("Require.useMemoise", TRUE)) {
    dots <- list(...)
    fnName <- "pkgDepTopoSort"
    fn <- eval(parse(text = fnName))
    pe <- pkgEnv()
    if (!exists(fnName, envir = pe, inherits = FALSE)) {
      pe[[fnName]] <- new.env()
    }
    ret <- NULL
    ss <- match.call(definition = fn)
    pkg <- eval(ss$pkg, envir = parent.frame())
    hash <-
      sum(as.integer(serialize(
        object = pkg, ascii = TRUE, NULL
      )))
    hash <- as.character(hash)
    if (!exists(hash, envir = pe[[fnName]], inherits = FALSE)) {
      pe[[fnName]][[hash]] <- list()
    } else {
      whIdent <-
        unlist(lapply(pe[[fnName]][[hash]], function(x) {
          identical(x$input, dots)
        }))
      if (any(whIdent)) {
        ret <- pe[[fnName]][[hash]][[which(whIdent)]]$output
      }
    }
    if (is.null(ret)) {
      inputs <- data.table::copy(dots)
      ret <- fn(...)
      pe[[fnName]][[hash]] <-
        list(pe[[fnName]][[hash]], list(input = inputs, output = ret))
    }
  } else {
    ret <- fn(...)
  }

  return(ret)
}

pkgDepDBFilename <- function() {
  ## Require's pkgDep cache file -- bookkeeping, kept next to the legacy
  ## SHA DB so first-run-after-upgrade doesn't lose existing state.
  d <- .requirePkgInfoDir()
  if (!is.null(d) && nzchar(d))
    file.path(d, "pkgDepDB.rds")
  ## else NULL (matches the prior "no Cache used" branch)
}

isAre <- function(l, v) {
  singularPlural(c("is", "are"), l, v)
}

hasHave <- function(l, v) {
  singularPlural(c("has", "have"), l, v)
}

singularPlural <- function(singPlur, l, v) {
  if (!missing(l)) {
    out <- singPlur[(length(l) > 1) + 1]
  }
  if (!missing(v)) {
    out <- singPlur[(v > 1) + 1]
  }
  out
}


prependSelf <- function(deps, includeSelf) {
  if (isTRUE(includeSelf)) {
    deps <- Map(pkgDepDT = deps, nam = names(deps), function(pkgDepDT, nam) {
      depsInner <- pkgDepDT
      alreadyHasSelf <- identical(extractPkgName(pkgDepDT$packageFullName[1]), extractPkgName(nam))
      # alreadyHasSelf <- startsWith(pkgDepDT[1], trimVersionNumber(nam))
      if (!isTRUE(alreadyHasSelf)) {
        depsInner <- rbindlist(list(toPkgDepDT(nam), pkgDepDT), fill = TRUE, use.names = TRUE)
      }

      return(depsInner)
    })
  }
  deps
}

getAvailablePackagesIfNeeded <-
  function(packages, repos, purge, verbose, type) {
    if (is.data.table(packages)) {
      pkgDT <- packages
    } else {
      pkgDT <- parseGitHub(packages)
    }
    isCRAN <- pkgDT[["repoLocation"]] %in% "CRAN"

    if (any(isCRAN %in% TRUE)) {
      ap <-
        available.packagesCached(
          repos = repos,
          purge = purge,
          verbose = verbose,
          type = type
        )
    } else {
      ap <- NULL
    }
    ap
  }

#' Clear cached package tarballs
#'
#' Removes downloaded package archives from the cache that [cachePkgDir()]
#' returns. With `usePak = TRUE` (the default) this delegates to
#' `pak::cache_clean()` (no `packages` arg) or
#' `pak::cache_delete(package = ...)` (selective). With `usePak = FALSE`
#' it walks Require's legacy bookkeeping dir and unlinks tarballs there.
#'
#' Require's own bookkeeping (SHA DB, mirrors.csv, available.packages
#' snapshots) is preserved by this function; use [cachePurge()] to clear
#' those as well.
#'
#' `clearRequirePackageCache()` is a deprecated alias.
#'
#' @section Migration note:
#'
#' The `Rversion` parameter is honoured only on the legacy path -- pak's
#' cache is not partitioned by R version the way Require's cache was, so
#' `pak::cache_clean()`/`pak::cache_delete()` operate on the whole cache
#' regardless of `Rversion`. The function emits a verbose note when
#' `Rversion != versionMajorMinor()` under `usePak = TRUE`.
#'
#' @param packages Either missing or a character vector of package names
#'   (currently cannot specify version number) to remove from the cache.
#' @param ask Logical. If `TRUE`, asks the user to confirm before deleting.
#' @param Rversion An R version (major.minor, e.g., `"4.2"`). Defaults to
#'   the current R version. **Ignored under `usePak = TRUE`** -- see
#'   "Migration note".
#' @param clearCranCache Logical. If `TRUE`, also clears the local
#'   `crancache` cache, which is only relevant if
#'   `options(Require.useCranCache = TRUE)`.
#' @export
#' @inheritParams Require
#' @rdname clearRequire
cacheClearPackages <- function(packages,
                                     ask = interactive(),
                                     Rversion = versionMajorMinor(),
                                     clearCranCache = FALSE,
                                     verbose = getOption("Require.verbose")) {
  ## In pak mode the package tarballs live in pak's cache, not in
  ## Require's bookkeeping dir. Delegate to pak's native API so the
  ## right files actually get removed.
  if (isTRUE(getOption("Require.usePak", TRUE)) &&
      requireNamespace("pak", quietly = TRUE)) {
    if (!identical(Rversion, versionMajorMinor())) {
      messageVerbose(
        "cacheClearPackages: `Rversion` arg ignored under usePak = TRUE -- ",
        "pak's cache is not partitioned by R version the way Require's ",
        "legacy cache was; pak::cache_delete() / pak::cache_clean() ",
        "operate on the full cache.",
        verbose = verbose, verboseLevel = 1
      )
    }
    proceed <- TRUE
    if (isTRUE(ask)) {
      message(if (missing(packages))
                "Are you sure you would like to remove ALL packages from pak's cache?"
              else
                paste0("Are you sure you would like to remove\n",
                       paste(packages, collapse = ", "),
                       "\nfrom pak's cache?"))
      askResult <- readline("(n or anything else for yes) ")
      if (startsWith(tolower(askResult), "n")) proceed <- FALSE
    }
    if (isTRUE(proceed)) {
      if (missing(packages)) {
        messageVerbose("Clearing entire pak download cache via pak::cache_clean()",
                       verbose = verbose, verboseLevel = 1)
        tryCatch(pak::cache_clean(),
                 error = function(e) {
                   warning("pak::cache_clean() failed: ", conditionMessage(e),
                           call. = FALSE)
                 })
      } else {
        messageVerbose("Removing from pak download cache: ",
                       paste(packages, collapse = ", "),
                       verbose = verbose, verboseLevel = 1)
        tryCatch(pak::cache_delete(package = packages),
                 error = function(e) {
                   warning("pak::cache_delete() failed: ", conditionMessage(e),
                           call. = FALSE)
                 })
      }
      ## Also drop Require's own SHA DB so HEAD-pin resolution
      ## reflects the fresh state (mirrors the legacy branch's behavior).
      SHAfile1 <- getSHAFromGitHubDBFilename()
      if (length(SHAfile1) && nzchar(SHAfile1) && isTRUE(file.exists(SHAfile1)))
        unlink(SHAfile1)
    } else {
      message("Aborting")
    }
    return(invisible(NULL))
  }

  ## Legacy non-pak path: walk Require's bookkeeping dir directly.
  out <- .requirePkgInfoDir(create = FALSE)
  if (!identical(Rversion, versionMajorMinor())) {
    out <- file.path(dirname(out), Rversion)
  }
  if (getOption("Require.useCranCache")) {
    crancache <- crancacheFolder()
    toDelete <- dir(crancache, recursive = TRUE, full.names = TRUE)
    if (length(toDelete) && isFALSE(clearCranCache)) {
      messageVerbose(blue(
        "crancache is being used because options(Require.useCranCache = TRUE); ",
        "however, clearCranCache is FALSE. This means that packages from ",
        "crancache will continue to re-populate the Require Cache. ",
        "To remove all local packages, set clearCranCache in this  function to TRUE."
      ))
    }
    if (isTRUE(clearCranCache)) {
      if (!missing(packages)) {
        pkgNamesInFiles <- extractPkgName(filenames = basename(toDelete))
        present <- pkgNamesInFiles %in% packages
        toDelete <- toDelete[present]
      }
      if (length(toDelete)) {
        unlink(toDelete)
      }
    }
  }
  proceed <- TRUE
  # Scan flat dir AND repos-specific subdirs, excluding metadata (.rds) files
  indivFiles <- dir(out, full.names = TRUE, recursive = TRUE)
  isFile <- !dir.exists(indivFiles)
  indivFiles <- indivFiles[isFile & !grepl("\\.rds$", indivFiles)]
  if (missing(packages)) {
    toDelete <-
      indivFiles # don't delete whole dir because has available.packages too; not to delete
    forMess <-
      paste0("all ", length(indivFiles), " cached packages in ", out)
  } else {
    if (length(indivFiles)) {
      pkgNamesInFiles <- extractPkgName(filenames = basename(indivFiles))
      # toDelete <- indivFiles[pkgNamesInFiles %in% packages]
      toDelete <- unlist(grepV(packages, indivFiles, value = TRUE))
      forMess <- paste(sort(basename(toDelete)), collapse = ",\n")
    } else {
      toDelete <- character()
    }
  }
  if (length(toDelete)) {
    if (isTRUE(ask)) {
      message("Are you sure you would like to remove\n", forMess, "\n?")
      askResult <- readline("(n or anything else for yes) ")
      if (startsWith(tolower(askResult), "n")) {
        proceed <- FALSE
      }
    }

    if (isTRUE(proceed)) {
      messageVerbose("Clearing: \n",
                     forMess,
                     verbose = verbose,
                     verboseLevel = 1
      )
      unlink(toDelete, recursive = TRUE)

      # This purges all the pkgDep stuff
      SHAfile1 <- getSHAFromGitHubDBFilename()
      if (isTRUE(file.exists(SHAfile1))) # can be character() ifgetOptionRPackageCache is NULL/FALSE
        unlink(SHAfile1)

    } else {
      message("Aborting")
    }
  } else {
    messageVerbose("Nothing to clear in Cache", verbose = verbose, verboseLevel = 1)
  }
}


#' @export
#' @rdname clearRequire
clearRequirePackageCache <- function(packages,
                                     ask = interactive(),
                                     Rversion = versionMajorMinor(),
                                     clearCranCache = FALSE,
                                     verbose = getOption("Require.verbose")) {
  .Deprecated("cacheClearPackages", package = "Require",
              msg = paste0(
                "clearRequirePackageCache() is deprecated; ",
                "use cacheClearPackages() instead."
              ))
  if (missing(packages)) {
    cacheClearPackages(ask = ask, Rversion = Rversion,
                       clearCranCache = clearCranCache, verbose = verbose)
  } else {
    cacheClearPackages(packages = packages, ask = ask, Rversion = Rversion,
                       clearCranCache = clearCranCache, verbose = verbose)
  }
}

depsImpsSugsLinksToWhich <- function(depends, imports, suggests, linkingTo, which) {
  if (!missing(depends)) {
    wh <-
      "Depends"
    which <- if (isTRUE(depends)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(imports)) {
    wh <-
      "Imports"
    which <- if (isTRUE(imports)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(suggests)) {
    wh <-
      "Suggests"
    which <- if (isTRUE(suggests)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(linkingTo)) {
    wh <-
      "LinkingTo"
    which <- if (isTRUE(linkingTo)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  whichToDILES(which)
}


installedVersionOKPrecise <- function(pkgDT, libPaths) {
  # pkgload steals system.file but fails under some conditions, not sure what...
  withCallingHandlers(
    pkgDT[, localFiles := base::system.file("DESCRIPTION", package = Package), by = "Package"],
    warning = function(w) {
      if (isTRUE(any(grepl("cannot open compressed file", w$message))))
        invokeRestart("muffleWarning")
    })
  fe <- nzchar(pkgDT$localFiles)
  if (any(fe)) {
    pkgDT[fe, localRepo := DESCRIPTIONFileOtherV(localFiles, "RemoteRepo")]
    pkgDT[fe, localUsername := DESCRIPTIONFileOtherV(localFiles, "RemoteUsername")]
    pkgDT[fe, localBranch := DESCRIPTIONFileOtherV(localFiles, "RemoteRef")]
    pkgDT[fe, installedSha := DESCRIPTIONFileOtherV(localFiles, "RemoteSha")]
  }
  if (any(!fe)) {
    pkgDT[!fe, localRepo := NA]
    pkgDT[!fe, localUsername := NA]
    pkgDT[!fe, localBranch := NA]
    pkgDT[!fe, installedSha := NA]
  }

  pkgDT <- installedVers(pkgDT, libPaths = libPaths)

  if (isTRUE(any(pkgDT$installed %in% TRUE))) {
    brOrSha <- pkgDT$Branch == pkgDT$localBranch |
      pkgDT$Branch == pkgDT$installedSha

    installedNotOK <- pkgDT$Account != pkgDT$localUsername |
      pkgDT$Repo != pkgDT$localRepo |
      brOrSha %in% FALSE

  } else {
    installedNotOK <- NA
  }
  set(pkgDT, NULL, "installedVersionOK", installedNotOK %in% FALSE)
  pkgDT
}


toPkgDepDT <- function(packageFullName, neededFromDESCRIPTION, pkg, verbose) {
  if (missing(pkg))
    pkg <- packageFullName

  pkgDepDT <- toPkgDT(packageFullName)
  if (missing(neededFromDESCRIPTION)) {
    neededFromDESCRIPTION <- pkgDepDT$packageFullName
  }
  if (NROW(pkgDepDT)) {
    pkgDepDT <- parsePackageFullname(pkgDepDT, sorted = FALSE)
    pkgDepDT <- parseGitHub(pkgDepDT)
    set(pkgDepDT, NULL, "isGitPkg", !is.na(pkgDepDT$githubPkgName))
    # pkgDepDT[, isGitPkg := !is.na(pkgDepDT$githubPkgName)]
    # pkgDepDT <- trimRedundancies(pkgDepDT, repos = repos, purge = FALSE)
    setorderv(pkgDepDT, "isGitPkg", order = -1)
    # pkgDepDT[, Package := extractPkgName(packageFullName)]
    # set(pkgDepDT, NULL, "versionSpec", extractVersionNumber(packageFullName))

    # Here, GitHub package specification in a DESCRIPTION file Remotes section
    #   won't have version numbering --> Need to merge the two fields



    # This shouldn't be "Version" but "versionSpec" from here
    if (any(!is.na(pkgDepDT[["versionSpec"]]))) {
      whHasVersion <- which(!is.na(pkgDepDT[["versionSpec"]]))
      set(pkgDepDT, whHasVersion, "inequality",
          extractInequality(pkgDepDT$packageFullName[whHasVersion]))
      set(pkgDepDT, NULL, "github", extractPkgGitHub(pkgDepDT$packageFullName))
      if (any(pkgDepDT$isGitPkg == TRUE &
              !is.na(pkgDepDT[["versionSpec"]]))) {
        pkgDepDT[isGitPkg == TRUE & !is.na(versionSpec), newPackageFullName :=
                   ifelse(
                     is.na(extractVersionNumber(packageFullName)),
                     paste0(packageFullName, " (", inequality, versionSpec, ")"),
                     NA
                   )]
        whGitNeedVersion <- !is.na(pkgDepDT$newPackageFullName)
        if (any(whGitNeedVersion)) {
          pkgDepDT[whGitNeedVersion == TRUE, packageFullName := newPackageFullName]
        }
      }
    }
    dup <- duplicated(pkgDepDT, by = c("Package", "versionSpec"))
    pkgDepDT <- pkgDepDT[dup == FALSE]
    differences <-
      setdiff(setdiff(pkgDepDT$Package, .basePkgs), extractPkgName(neededFromDESCRIPTION))
    if (length(differences)) {
      messageVerbose(
        " (-- The DESCRIPTION file for ",
        pkg,
        " is incomplete; there are missing imports: ",
        paste(differences, collapse = comma),
        " --) ",
        verbose = verbose,
        verboseLevel = 1
      )
    }
  }
  pkgDepDT
}


getAvailablePackagesCheckAdditRepos <- function(pkgDepDTList2, pkgDepDT, repos, verbose, type, ap = NULL) {
  #
  anyNewAdditionalRepositories <-
    unlist(lapply(pkgDepDTList2, function(dt)
      if (is.null(dt$Additional_repositories)) NULL else unique(dt$Additional_repositories)))
  if (!is.null(anyNewAdditionalRepositories) || is.null(ap)) {


    isDT <- vapply(pkgDepDTList2, is.data.table, FUN.VALUE = logical(1))
    if (any(isDT)) {
      if (any(!isDT))
        pkgDepDTList2 <- pkgDepDTList2[isDT]
      if (missing(pkgDepDT))
        pkgDepDT <- rbindlist(pkgDepDTList2, fill = TRUE, use.names = TRUE)
      additionalRepos <- lapply(pkgDepDTList2, function(DT)
        if (!is.null(DT$Additional_repositories)) unique(DT$Additional_repositories) else NULL)
      uniqueAdditionalRepos <- vapply(additionalRepos, function(ar) paste(sort(ar), collapse = "__"), FUN.VALUE = character(1))
      uniques <- as.integer(factor(paste0(seq(uniqueAdditionalRepos), uniqueAdditionalRepos)))
      hasAdditionalRepos <- vapply(additionalRepos, function(ar) NROW(ar) > 0, FUN.VALUE = logical(1))
      if (sum(hasAdditionalRepos)) {
        ap <- lapply(uniques, function(un) {
          DT <- rbindlist(pkgDepDTList2[un], fill = TRUE, use.names = TRUE)
          repos <- c(repos, na.omit(unique(DT$Additional_repositories)))
          needPurge <- FALSE
          for (i in 1:2) {
            ap <-
              getAvailablePackagesIfNeeded(DT, repos, purge = needPurge, verbose, type)
            if (!is.null(ap)) {
              repo1 <- noHttp(repos)
              haveRepos <- unlist(lapply(repo1, function(re) any(startsWith(noHttp(unique(ap$Repository)), re))))
              needPurge <- !all(haveRepos)
            }
            if (needPurge %in% FALSE)
              break
          }
          ap
        })
        ap <- unique(rbindlist(ap, use.names = TRUE, fill = TRUE))
      } else {
        if (is.null(ap))
          ap <-
            getAvailablePackagesIfNeeded(pkgDepDT, repos, purge = FALSE, verbose, type)
      }
    }
  }
  ap
}



getArchiveDetailsInnerMemoise <- function(...) {
  if (getOption("Require.useMemoise", TRUE)) {
    dots <- list(...)
    if (is.null(envPkgDepArchiveDetailsInner())) {
      envPkgDepArchiveDetailsInnerCreate()
    }
    ret <- NULL
    ss <- match.call(definition = getArchiveDetailsInner)
    Package <- eval(ss$Package, envir = parent.frame())
    if (!exists(Package, envir = envPkgDepArchiveDetailsInner(), inherits = FALSE)) {
      assign(Package, list(), envir = envPkgDepArchiveDetailsInner())
      # envPkgDepArchiveDetailsInner()[[Package]] <- list()
    } else {
      if (length(envPkgDepArchiveDetailsInner()[[Package]]) > 0) {
        # This is trigger
        prevInOuts <- envPkgDepArchiveDetailsInner()[[Package]][[2]]
        whIdent <- identical(prevInOuts$input, dots[[2]])
        if (any(whIdent)) {
          ret <- prevInOuts$output
        }
      }

    }
    if (is.null(ret)) {
      # inputs <- dots[[2]]
      inputs <- data.table::copy(dots[[2]])  # just take ava argument -- it has everything that is relevant
      ret <- getArchiveDetailsInner(...)
      assign(Package,
             list(envPkgDepArchiveDetailsInner()[[Package]], list(input = inputs, output = ret)),
             envir = envPkgDepArchiveDetailsInner())
      # envPkgDepArchiveDetailsInner()[[Package]] <-
      #   list(envPkgDepArchiveDetailsInner()[[Package]], list(input = inputs, output = ret))
    }
  } else {
    ret <- getArchiveDetailsInner(...)
  }

  return(ret)
}

noHttp <- function(url) {
  gsub("^http.*://", "", url)
}


getVersionOptionPkgEnv <- function(psnNoVersion, verNum, inequ) {
  pat <- paste0(psnNoVersion)
  pat <- gsub("[[:punct:]]| ", "_", pat)
  nams <- names(as.list(envPkgDepDeps()))
  if (!is.null(nams)) {
    versions <- Map(pa = pat, ver = verNum, ineq = inequ, #MoreArgs = list(nam = nams),
                function(pa, ver, ineq) {
                  sw <- startsWith(prefix = pa, nams)
                  if (any(sw)) {
                    # if (all(sw %in% FALSE))
                    #   sw <- grep(pa, nams)
                    nams <- nams[sw]
                    # nams <- nams[have]

                    poss <- get0(nams, envir = envPkgDepDeps())
                    versions <- extractVersionNumber(names(poss))
                    # have <- ls(envPkgDepDeps(), pattern = pat) # TOO SLOW APPARENTLY
                    # versions <- gsub(paste0("^", pa, "\\_\\_*([[:alnum:]]{40})|[(.+)\\_\\_+].+$"), "\\1", nams)
                    if (!is.null(ineq)) {
                      if (!is.na(ineq)) {
                        if (length(versions) > 0) {
                          okVers <- compareVersion2(gsub("_", "\\.", versions),
                                                    versionSpec = ver, inequality = ineq)
                          if (any(!okVers)) {
                            versions[!okVers] <- ""
                          }
                        }
                      }
                    }
                    noSavedOption <- nchar(versions) == 0
                    if (!is.null(ver))
                      if (any(noSavedOption))
                        versions[noSavedOption] <- verNum[noSavedOption]
                    if (length(versions) > 1)
                      versions <- tail(versions, 1)
                    versions
                  } else {
                    NULL
                  }
                })
  } else {
    versions <- NULL
  }

  versions

}




#' @description
#' `pkgDep2` is a convenience wrapper of `pkgDep` that "goes one level in",
#' i.e., the first order dependencies, and runs the `pkgDep` on those.
#' @rdname pkgDep
#' @export
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDep2("reproducible")
#'   # much bigger one
#'   pkgDep2("tidyverse")
#'
#'   Require:::.cleanup(opts)
#' }
#' }
pkgDep2 <- function(...) {
  dots <- list(...)
  dots$recursive <- FALSE
  forms <- formals(pkgDep)
  if (is.null(dots$simplify)) dots$simplify <- forms$simplify
  # dots$simplify <- FALSE
  deps <- do.call(pkgDep, dots)
  dots[[1]] <- NULL
  dots$recursive <- TRUE
  if (isTRUE(dots$simplify %in% FALSE)) {
    deps1 <- lapply(deps$deps, function(dep) do.call(pkgDep, append(list(dep$packageFullName), dots)))
  } else {
    deps1 <- lapply(deps, function(dep) do.call(pkgDep, append(list(dep), dots)))
  }
  deps1
}




purgeAvailablePackages <- function(repos, purge = FALSE) {
  if (isTRUE(purge)) {
    fn <- availablePackagesCachedPath(repos = repos, type = c("source", "binary"))
    fExists <- file.exists(fn)
    if (any(fExists)) {
      unlink(fn[fExists])
    }
  }
  purge
}

Try the Require package in your browser

Any scripts or data that you put into this service are public.

Require documentation built on May 16, 2026, 1:06 a.m.