R/setLibPaths.R

Defines functions checkTRUERprofile resetRprofileMessage checkMissingLibPaths setLibPathsUpdateRprofile setLibPaths

Documented in setLibPaths

#' Set `.libPaths`
#'
#' This will set the `.libPaths()` by either adding a new path to it if
#' `standAlone = FALSE`, or will concatenate `c(libPath, tail(.libPaths(), 1))`
#' if `standAlone = TRUE`. Currently, the default is to make this new
#' `.libPaths()` "sticky", meaning it becomes associated with the current
#' directory even through a restart of R. It does this by adding and/updating
#' the \file{.Rprofile} file in the current directory. If this current directory
#' is a project, then the project will have the new `.libPaths()` associated
#' with it, even through an R restart.
#'
#' @details This details of this code were modified from
#'   <https://github.com/milesmcbain>. A different, likely non-approved by CRAN
#'   approach that also works is here:
#'   <https://stackoverflow.com/a/36873741/3890027>.
#'
#' @param libPaths A new path to append to, or replace all existing user
#'   components of `.libPath()`
#' @param updateRprofile Logical or Character string. If `TRUE`, then this
#'   function will put several lines of code in the current directory's
#'   `.Rprofile` file setting up the package libraries for this and future
#'   sessions. If a character string, then this should be the path to an
#'   `.Rprofile` file. To reset back to normal, run `setLibPaths()` without a
#'   `libPath`. Default: `getOption("Require.updateRprofile", FALSE)`, meaning
#'   `FALSE`, but it can be set with an option or within a single call.
#' @param exact Logical. This function will automatically append the R version
#'   number to the `libPaths` to maintain separate R package libraries for each
#'   R version on the system. There are some cases where this behaviour is not
#'   desirable. Set `exact` to `TRUE` to override this automatic appending and
#'   use the exact, unaltered `libPaths`. Default is `FALSE`
#' @inheritParams Require
#' @return The main point of this function is to set `.libPaths()`, which will
#'   be changed as a side effect of this function. As when setting `options`,
#'   this will return the previous state of `.libPaths()` allowing the user to
#'   reset easily.
#'
#' @export
#' @importFrom utils compareVersion
#' @inheritParams Require
#' @examples
#' \dontrun{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'   origDir <- setwd(tempdir())
#'   td <- tempdir()
#'   setLibPaths(td) # set a new R package library locally
#'   setLibPaths() # reset it to original
#'   setwd(origDir)
#'   # Using standAlone = FALSE means that newly installed packages
#'   #   will be installed
#'   #   in the new package library, but loading packages can come
#'   #   from any of the ones listed in .libPaths()
#'
#'   # will have 2 or more paths
#'   otherLib <- file.path(td, "newProjectLib")
#'   setLibPaths(otherLib, standAlone = FALSE)
#'   # Can restart R, and changes will stay
#'
#'   # remove the custom .libPaths()
#'   setLibPaths() # reset to previous; remove from .Rprofile
#'   # because libPath arg is empty
#'
#'   Require:::.cleanup(opts)
#'   unlink(otherLib, recursive = TRUE)
#' }
#' }
#'
setLibPaths <- function(libPaths, standAlone = TRUE,
                        updateRprofile = getOption("Require.updateRprofile", FALSE),
                        exact = FALSE, verbose = getOption("Require.verbose")) {
  oldLibPaths <- .libPaths()
  if (missing(libPaths)) {
    return(checkMissingLibPaths(libPaths, updateRprofile))
  } ## End missing
  libPaths <- checkLibPaths(libPaths, exact = exact)

  if (!is.null(updateRprofile)) {
    setLibPathsUpdateRprofile(libPaths, standAlone, updateRprofile)
  }
  gte4.1 <- isTRUE(getRversion() >= "4.1")
  if (gte4.1) { # now correct behaviour; remaining parts unnecessary
    ## to avoid triggering warning on R < 4.1
    do.call(.libPaths, list(
      new = libPaths,
      if (gte4.1) include.site <- !standAlone
    ))
    return(oldLibPaths)
  }

  # libPaths <- checkPath(normPath(libPaths), create = TRUE)#, mustWork = TRUE)
  shim_fun <- .libPaths
  shim_env <- new.env(parent = environment(shim_fun))
  if (isTRUE(standAlone)) {
    shim_env$.Library <- tail(.libPaths(), 1)
  } else {
    shim_env$.Library <- .libPaths()
  }
  shim_env$.Library.site <- character()

  environment(shim_fun) <- shim_env
  shim_fun(unique(libPaths))
  message(".libPaths() is now: ", paste(.libPaths(), collapse = ", "))
  return(invisible(oldLibPaths))
}

setLibPathsUpdateRprofile <- function(libPaths, standAlone = TRUE, updateRprofile = NULL,
                                      verbose = getOption("Require.verbose")) {
  updateRprofile <- checkTRUERprofile(updateRprofile)
  if (is.character(updateRprofile)) {
    newFile <- FALSE
    if (!file.exists(updateRprofile)) {
      newFile <- TRUE
      file.create(updateRprofile)
      if (file.exists(".gitignore")) {
        if (!isTRUE(any(grepl(".Rprofile", readLines(".gitignore"))))) {
          cat(".Rprofile\n", file = ".gitignore", append = TRUE)
        }
      }
    }
    if (any(grepl(setLibPathsStartText, readLines(".Rprofile")))) {
      messageVerbose(alreadyInRprofileMessage, verbose = verbose, verboseLevel = 1)
    } else {
      bodyFn <- format(body(Require::setLibPaths))
      lineWCheckPath <- grepl("checkPath.normPath|checkLibPaths", bodyFn)
      bodyFn[lineWCheckPath] <- "    if (!dir.exists(libPaths[1])) dir.create(libPaths[1], recursive = TRUE)"
      lineWReturn <- grepl("return.*oldLibPaths", bodyFn)
      bodyFn <- bodyFn[!lineWReturn]
      bodyFn <- gsub("tail", "utils::tail", bodyFn)
      bodyFn <- gsub("shim_env", ".shim_env", bodyFn)
      bodyFn <- gsub("shim_fun", ".shim_fun", bodyFn)
      bodyFn <- gsub("oldLibPaths", ".oldLibPaths", bodyFn)
      lineWMissing <- which(grepl("missing.libPaths", bodyFn))
      bodyFn <- bodyFn[-(lineWMissing:(lineWMissing + 2))]
      lineWRprofileToUpdate <- which(grepl("is\\.null\\(updateRprofile\\)", bodyFn))
      bodyFn <- bodyFn[-(lineWRprofileToUpdate:(lineWRprofileToUpdate + 2))]
      bodyFn <- gsub("\\<standAlone\\>", "._standAlone", bodyFn)
      bodyFn <- gsub("\\.libPaths", "origDotlibPaths", bodyFn)
      bodyFn <- gsub("\\<libPaths\\>", "._libPaths", bodyFn)
      bodyFn <- gsub("origDotlibPaths", ".libPaths", bodyFn)
      bodyFn <- c(
        paste0(
          "\n", setLibPathsStartText, " #### ", newFileTrigger, newFile,
          " # DO NOT EDIT BETWEEN THESE LINES"
        ),
        "### DELETE THESE LINES BELOW TO RESTORE STANDARD R Package LIBRARY",
        paste0("### ", prevLibPathsText, paste(.libPaths(), collapse = ", ")),
        paste0("._libPaths <- c('", paste(libPaths, collapse = "', '"), "')"),
        paste0("._standAlone <- ", standAlone),
        bodyFn,
        if (verbose >= 1) {
          resetRprofileMessage(updateRprofile)
        },
        paste0(setLibPathsEndText)
      )
      messageVerbose("Updating ", normPath(updateRprofile),
        "; this will set new libPaths for R packages even after restarting R",
        verbose = verbose, verboseLevel = 1
      )
      cat(bodyFn, file = updateRprofile, append = TRUE, sep = "\n")
    }
  }
}

checkMissingLibPaths <- function(libPaths, updateRprofile = NULL, verbose = getOption("Require.verbose")) {
  if (!is.null(updateRprofile)) {
    if (updateRprofile == FALSE && missing(libPaths)) {
      updateRprofile <- TRUE
    }
    updateRprofile <- checkTRUERprofile(updateRprofile)
    noChange <- FALSE
    if (is.character(updateRprofile)) {
      if (file.exists(updateRprofile)) {
        ll <- readLines(updateRprofile)
        bounds <- which(grepl("#### setLibPaths", ll))
        if (length(bounds)) {
          messageVerbose("removing custom libPaths in .Rprofile",
            verbose = verbose, verboseLevel = 1
          )
          if (identical("", ll[bounds[1] - 1])) {
            bounds[1] <- bounds[1] - 1
          }
          newFileLine <- grepl(newFileTrigger, ll)
          newFile <- gsub(paste0(".*", newFileTrigger, "([[:alpha:]]+) .*"), "\\1", ll[newFileLine])
          wasNew <- as.logical(newFile)
          prevLines <- grepl(prevLibPathsText, ll)
          prevLibPaths <- strsplit(gsub(paste0(".*", prevLibPathsText), "", ll[prevLines]), split = ", ")[[1]]
          .libPaths(prevLibPaths)

          # needs to be NEW and starts on 2nd line
          if (isTRUE(wasNew) && which(newFileLine) == 2) {
            file.remove(updateRprofile)
          } else {
            ll <- ll[-(bounds[1]:bounds[2])]
            writeLines(ll, con = updateRprofile)
          }
        } else {
          noChange <- TRUE
        }
      } else {
        noChange <- TRUE
      }
      if (isTRUE(noChange)) {
        messageVerbose("There was no custom libPaths setting in .Rprofile; nothing changed",
          verbose = verbose, verboseLevel = 0
        )
      }

      return(invisible())
    }
  }
  stop("libPaths cannot be missing; please supply a folder to install R packages to")
  return(invisible())
}

resetRprofileMessage <- function(updateRprofile = ".Rprofile") {
  paste0("message(\"To reset libPaths to previous state, run: Require::setupOff() (or delete section in .Rprofile file)\") ")
}

checkTRUERprofile <- function(updateRprofile) {
  if (isTRUE(updateRprofile)) updateRprofile <- ".Rprofile"
  updateRprofile
}

prevLibPathsText <- "Previous .libPaths: "
commentCharsForSetLibPaths <- "#### setLibPaths "
setLibPathsStartText <- paste0(commentCharsForSetLibPaths, "start")
setLibPathsEndText <- paste0(commentCharsForSetLibPaths, "end")
newFileTrigger <- "New File:"
alreadyInRprofileMessage <- "There is already a setLibPaths in the .Rprofile, skipping"
PredictiveEcology/Require documentation built on Nov. 5, 2024, 12:57 p.m.