R/init.R

Defines functions renv_init_settings renv_init_validate_project renv_init_action_conflict_library renv_init_action_conflict_lockfile renv_init_action renv_init_fini init

Documented in init

#' Initialize a Project
#'
#' Discover packages used within the current project, and then initialize a
#' project-local private \R library with those packages. The currently-installed
#' versions of any packages in use (as detected within the default R libraries)
#' are then installed to the project's private library.
#'
#' The primary steps taken when initializing a new project are:
#'
#' 1. \R package dependencies are discovered within the \R files used within
#'    the project with [dependencies()];
#'
#' 2. Discovered packages are copied into the `renv` global package cache, so
#'    these packages can be re-used across future projects as necessary;
#'
#' 3. Any missing \R package dependencies discovered are then installed into
#'    the project's private library;
#'
#' 4. A lockfile capturing the state of the project's library is created
#'    with [snapshot()];
#'
#' 5. The project is activated with [activate()].
#'
#' This mimics the workflow provided by `packrat::init()`, but with a few
#' differences -- in particular, `renv` does not attempt to download and store
#' package sources, and `renv` will re-use packages that have already been
#' installed whenever possible.
#'
#' If `renv` sees that the associated project has already been initialized and
#' has a lockfile, then it will attempt to infer the appropriate action to take
#' based on the presence of a private library. If no library is available,
#' `renv` will restore the private library from the lockfile; if one is
#' available, `renv` will ask if you want to perform a 'standard' init,
#' restore from the lockfile, or activate the project without taking any
#' further action.
#'
#' @section Infrastructure:
#'
#' `renv` will write or amend the following files in the project:
#'
#' - `.Rprofile`: An auto-loader will be installed, so that new R sessions
#'   launched within the project are automatically loaded.
#'
#' - `renv/activate.R`: This script is run by the previously-mentioned
#'   `.Rprofile` to load the project.
#'
#' - `renv/.gitignore`: This is used to instruct Git to ignore the project's
#'   private library, as it should normally not be committed to a version
#'   control repository.
#'
#' - `.Rbuildignore`: to ensure that the `renv` directory is ignored during
#'   package development; e.g. when attempting to build or install a package
#'   using `renv`.
#'
#' @inherit renv-params
#'
#' @param project The project directory. The \R working directory will be
#'   changed to match the requested project directory.
#'
#' @param settings A list of [settings] to be used with the newly-initialized
#'   project.
#'
#' @param bare Boolean; initialize the project without attempting to discover
#'   and install R package dependencies?
#'
#' @param force Boolean; force initialization? By default, `renv` will refuse
#'   to initialize the home directory as a project, to defend against accidental
#'   mis-usages of `init()`.
#'
#' @param restart Boolean; attempt to restart the \R session after initializing
#'   the project? A session restart will be attempted if the `"restart"` \R
#'   option is set by the frontend embedding \R.
#'
#' @export
#'
#' @example examples/examples-init.R
init <- function(project = NULL,
                 ...,
                 profile  = NULL,
                 settings = NULL,
                 bare     = FALSE,
                 force    = FALSE,
                 restart  = interactive())
{
  renv_consent_check()
  renv_scope_error_handler()
  renv_dots_check(...)

  project <- renv_path_normalize(project %||% getwd())
  renv_scope_lock(project = project)

  # prepare and move into project directory
  renv_init_validate_project(project, force)
  renv_init_settings(project, settings)
  setwd(project)

  # collect dependencies
  renv_dependencies_scope(project, action = "init")

  # be quiet in RStudio projects (as we will normally restart automatically)
  quiet <- !is.null(getOption("restart"))

  # for bare inits, just activate the project
  if (bare)
    return(renv_init_fini(project, profile, version, restart, quiet))

  # form path to lockfile, library
  library  <- renv_paths_library(project = project)
  lockfile <- renv_lockfile_path(project)

  # determine appropriate action
  action <- renv_init_action(project, library, lockfile)
  if (empty(action) || identical(action, "cancel")) {
    message("* Operation aborted.")
    return(invisible(FALSE))
  }

  # activate library paths for this project
  libpaths <- renv_libpaths_activate(project = project)

  # perform the action
  if (action == "init") {
    vwritef("* Initializing project ...")
    renv_imbue_impl(project)
    hydrate(project = project, library = library)
    snapshot(project = project, library = libpaths, prompt = FALSE)
  } else if (action == "restore") {
    vwritef("* Restoring project ... ")
    ensure_directory(library)
    restore(project = project, library = libpaths, prompt = FALSE)
  }

  # activate the newly-hydrated project
  renv_init_fini(project, profile, version, restart, quiet)

}

renv_init_fini <- function(project, profile, version, restart, quiet) {

  version <- renv_package_version("renv")

  renv_activate_impl(
    project = project,
    profile = profile,
    version = version,
    restart = restart,
    quiet   = quiet
  )

  invisible(project)

}

renv_init_action <- function(project, library, lockfile) {

  # figure out appropriate action
  case(

    # if both the library and lockfile exist, ask user for intended action
    file.exists(lockfile) && file.exists(library)
      ~ renv_init_action_conflict_lockfile(project, library, lockfile),

    # if a private library exists but no lockfile, ask whether we should use it
    !file.exists(lockfile) && file.exists(library)
      ~ renv_init_action_conflict_library(project, library, lockfile),

    # if a lockfile exists but not a library, we just want to restore
    file.exists(lockfile) && !file.exists(library)
      ~ "restore",

    # otherwise, we juse want to initialize the project
    ~ "init"

  )

}

renv_init_action_conflict_lockfile <- function(project, library, lockfile) {

  if (!interactive())
    return("nothing")

  title <- "This project already has a lockfile. What would you like to do?"
  choices <- c(
    restore = "Restore the project from the lockfile.",
    init    = "Discard the lockfile and re-initialize the project.",
    nothing = "Activate the project without snapshotting or installing any packages.",
    cancel  = "Abort project initialization."
  )

  selection <- tryCatch(
    utils::select.list(choices, title = title, graphics = FALSE),
    interrupt = identity
  )

  if (inherits(selection, "interrupt"))
    return(NULL)

  names(selection)

}

renv_init_action_conflict_library <- function(project, library, lockfile) {

  if (!interactive())
    return("nothing")

  title <- "This project already has a private library. What would you like to do?"
  choices <- c(
    nothing = "Activate the project and use the existing library.",
    init    = "Re-initialize the project with a new library.",
    cancel  = "Abort project initialization."
  )

  selection <- tryCatch(
    utils::select.list(choices, title = title, graphics = FALSE),
    interrupt = identity
  )

  if (inherits(selection, "interrupt"))
    return(NULL)

  names(selection)

}

renv_init_validate_project <- function(project, force) {

  # allow all project directories when force = TRUE
  if (force)
    return(TRUE)

  # disallow attempts to initialize renv in the home directory
  home <- path.expand("~/")
  msg <- if (renv_file_same(project, home))
    "refusing to initialize project in home directory"
  else if (renv_path_within(home, project))
    sprintf("refusing to initialize project in directory '%s'", project)

  if (!is.null(msg)) {
    msg <- paste(msg, "-- use renv::init(force = TRUE) to override")
    stopf(msg)
  }

}

renv_init_settings <- function(project, settings) {

  ensure_directory(file.path(project, "renv"))
  defaults <- renv_settings_get(project)
  merged <- renv_settings_merge(defaults, settings)
  renv_settings_persist(project, merged)
  invisible(merged)

}
wnattt/renv documentation built on Dec. 23, 2021, 5:19 p.m.