diff --git a/DESCRIPTION b/DESCRIPTION index 7db0adf59..edbefdf47 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,6 +25,7 @@ Imports: lifecycle (>= 1.0.4), memoise (>= 2.0.1), miniUI (>= 0.1.2), + pak, pkgbuild (>= 1.4.8), pkgdown (>= 2.1.3), pkgload (>= 1.4.1), diff --git a/NEWS.md b/NEWS.md index 3d9740ba0..3ea436fca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * `build_manual()` reports more details on failure (#2586). * New `check_mac_devel()` function to check a package using the macOS builder at https://mac.r-project.org/macbuilder/submit.html (@nfrerebeau, #2507) * `is_loading()` is now re-exported from pkgload (#2556). +* `install()` now uses `pak::local_install()` instead of `remotes::install_deps()`. This gives a lot of improved behaviours, particularly around upgrading dependencies that you already have installed (#2486). * `load_all()` now errors if called recursively, i.e. if you accidentally include a `load_all()` call in one of your R source files (#2617). * `show_news()` now looks for NEWS files in the same locations as `utils::news()`: `inst/NEWS.Rd`, `NEWS.md`, `NEWS`, and `inst/NEWS` (@arcresu, #2499). diff --git a/R/build-site.R b/R/build-site.R index 9935963c3..7ff383577 100644 --- a/R/build-site.R +++ b/R/build-site.R @@ -19,7 +19,7 @@ build_site <- function(path = ".", quiet = TRUE, ...) { check_dots_used(action = getOption("devtools.ellipsis_action", rlang::warn)) withr::with_temp_libpaths(action = "prefix", code = { - install(pkg = pkg$path, upgrade = "never", reload = FALSE, quiet = quiet) + install(pkg = pkg$path, upgrade = FALSE, reload = FALSE, quiet = quiet) if (isTRUE(quiet)) { withr::with_output_sink( file_temp(), diff --git a/R/install.R b/R/install.R index b56402cbb..28d487106 100644 --- a/R/install.R +++ b/R/install.R @@ -1,26 +1,23 @@ -#' Install a local development package. +#' Install a local development package #' -#' Uses `R CMD INSTALL` to install the package. Will also try to install -#' dependencies of the package from CRAN, if they're not already installed. -#' -#' If `quick = TRUE`, installation takes place using the current package -#' directory. If you have compiled code, this means that artefacts of -#' compilation will be created in the `src/` directory. If you want to avoid -#' this, you can use `build = TRUE` to first build a package bundle and then -#' install it from a temporary directory. This is slower, but keeps the source -#' directory pristine. -#' -#' If the package is loaded, it will be reloaded after installation. This is -#' not always completely possible, see [reload()] for caveats. +#' @description +#' Uses `R CMD INSTALL` to install the package, after installing needed +#' dependencies with [pak::local_install_deps()]. #' #' To install a package in a non-default library, use [withr::with_libpaths()]. #' #' @template devtools -#' @inheritParams remotes::install_local -#' @param reload if `TRUE` (the default), will automatically reload the -#' package after installing. +#' @param reload if `TRUE` (the default), will automatically attempt reload the +#' package after installing. Reloading is always completely possible so see +#' [pkgload::unregister()] for caveats. #' @param quick if `TRUE` skips docs, multiple-architectures, #' demos, and vignettes, to make installation as fast as possible. +#' If `quick = TRUE`, installation takes place using the current package +#' directory. If you have compiled code, this means that artefacts of +#' compilation will be created in the `src/` directory. If you want to avoid +#' this, you can use `build = TRUE` to first build a package bundle and then +#' install it from a temporary directory. This is slower, but keeps the source +#' directory pristine. #' @param build if `TRUE` [pkgbuild::build()]s the package first: #' this ensures that the installation is completely clean, and prevents any #' binary artefacts (like \file{.o}, `.so`) from appearing in your local @@ -43,118 +40,124 @@ #' @param keep_source If `TRUE` will keep the srcrefs from an installed #' package. This is useful for debugging (especially inside of RStudio). #' It defaults to the option `"keep.source.pkgs"`. -#' @param ... additional arguments passed to [remotes::install_deps()] -#' when installing dependencies. +#' @param quiet If `TRUE`, suppress output. +#' @param force `r lifecycle::badge("deprecated")` No longer used. #' @family package installation -#' @seealso [update_packages()] to update installed packages from the -#' source location and [with_debug()] to install packages with -#' debugging flags set. +#' @inheritParams pak::local_install_deps +#' @seealso [with_debug()] to install packages with debugging flags set. #' @export -install <- - function( - pkg = ".", - reload = TRUE, - quick = FALSE, - build = !quick, - args = getOption("devtools.install.args"), - quiet = FALSE, - dependencies = NA, - upgrade = "default", - build_vignettes = FALSE, - keep_source = getOption("keep.source.pkgs"), - force = FALSE, - ... - ) { - pkg <- as.package(pkg) - - # Forcing all of the promises for the current namespace now will avoid lazy-load - # errors when the new package is installed overtop the old one. - # https://stat.ethz.ch/pipermail/r-devel/2015-December/072150.html - if (reload && is_loaded(pkg)) { - eapply(pkgload::ns_env(pkg$package), force, all.names = TRUE) - } +install <- function( + pkg = ".", + reload = TRUE, + quick = FALSE, + build = !quick, + args = getOption("devtools.install.args"), + quiet = FALSE, + dependencies = NA, + upgrade = FALSE, + build_vignettes = FALSE, + keep_source = getOption("keep.source.pkgs"), + force = deprecated() +) { + if (!is.logical(upgrade) || length(upgrade) != 1) { + cli::cli_abort("{.arg upgrade} must be a single TRUE, FALSE, or NA") + } + if (lifecycle::is_present(force)) { + lifecycle::deprecate_warn("2.5.0", "intall(force)") + } + + pkg <- as.package(pkg) + + # Forcing all of the promises for the current namespace now will avoid lazy-load + # errors when the new package is installed overtop the old one. + # https://stat.ethz.ch/pipermail/r-devel/2015-December/072150.html + if (reload && is_loaded(pkg)) { + eapply(pkgload::ns_env(pkg$package), base::force, all.names = TRUE) + } - if (isTRUE(build_vignettes)) { - # we likely need all Suggested dependencies if building vignettes - dependencies <- TRUE - build_opts <- c("--no-resave-data", "--no-manual") + # Install dependencies + local({ + if (quiet) { + withr::local_output_sink(withr::local_tempfile()) + withr::local_message_sink(withr::local_tempfile()) } else { - build_opts <- c("--no-resave-data", "--no-manual", "--no-build-vignettes") + cli::cat_rule("Installing dependencies", col = "cyan") } - - opts <- c( - if (keep_source) "--with-keep.source", - "--install-tests" + pak::local_install_deps( + pkg$path, + upgrade = upgrade, + dependencies = dependencies || isTRUE(build_vignettes) ) - if (quick) { - opts <- c(opts, "--no-docs", "--no-multiarch", "--no-demo") - } - opts <- c(opts, args) + }) - check_dots_used(action = getOption("devtools.ellipsis_action", rlang::warn)) + if (isTRUE(build_vignettes)) { + build_opts <- c("--no-resave-data", "--no-manual") + } else { + build_opts <- c("--no-resave-data", "--no-manual", "--no-build-vignettes") + } + opts <- c( + if (keep_source) "--with-keep.source", + "--install-tests" + ) + if (quick) { + opts <- c(opts, "--no-docs", "--no-multiarch", "--no-demo") + } + opts <- c(opts, args) - remotes::install_deps( + if (build) { + install_path <- pkgbuild::build( pkg$path, - build = build, - build_opts = build_opts, - INSTALL_opts = opts, - dependencies = dependencies, - quiet = quiet, - force = force, - upgrade = upgrade, - ... + dest_path = tempdir(), + args = build_opts, + quiet = quiet ) + on.exit(file_delete(install_path), add = TRUE) + } else { + install_path <- pkg$path + } - if (build) { - install_path <- pkgbuild::build( - pkg$path, - dest_path = tempdir(), - args = build_opts, - quiet = quiet - ) - on.exit(file_delete(install_path), add = TRUE) - } else { - install_path <- pkg$path - } - - was_loaded <- is_loaded(pkg) - was_attached <- is_attached(pkg) + was_loaded <- is_loaded(pkg) + was_attached <- is_attached(pkg) - if (reload && was_loaded) { - pkgload::unregister(pkg$package) - } + if (reload && was_loaded) { + pkgload::unregister(pkg$package) + } - pkgbuild::with_build_tools( - required = FALSE, - callr::rcmd( - "INSTALL", - c(install_path, opts), - echo = !quiet, - show = !quiet, - spinner = FALSE, - stderr = "2>&1", - fail_on_status = TRUE - ) + if (!quiet) { + cli::cat_rule(paste0("R CMD INSTALL"), col = "cyan") + } + pkgbuild::with_build_tools( + required = FALSE, + callr::rcmd( + "INSTALL", + c(install_path, opts), + echo = !quiet, + show = !quiet, + spinner = FALSE, + stderr = "2>&1", + fail_on_status = TRUE ) + ) - if (reload && was_loaded) { - if (was_attached) { - require(pkg$package, quietly = TRUE, character.only = TRUE) - } else { - requireNamespace(pkg$package, quietly = TRUE) - } + if (reload && was_loaded) { + if (was_attached) { + require(pkg$package, quietly = TRUE, character.only = TRUE) + } else { + requireNamespace(pkg$package, quietly = TRUE) } - - invisible(TRUE) } + invisible(TRUE) +} + #' Install package dependencies if needed. #' #' `install_deps()` will install the #' user dependencies needed to run the package, `install_dev_deps()` will also #' install the development dependencies needed to test and build the package. -#' @inheritParams install #' @inherit remotes::install_deps +#' @inheritParams install +#' @param ... Additional arguments passed to [remotes::install_deps()]. #' @export install_deps <- function( pkg = ".", @@ -221,5 +224,5 @@ local_install <- function(pkg = ".", quiet = TRUE, env = parent.frame()) { cli::cli_inform(c(i = "Installing {.pkg {pkg$package}} in temporary library")) withr::local_temp_libpaths(.local_envir = env) - install(pkg, upgrade = "never", reload = FALSE, quick = TRUE, quiet = quiet) + install(pkg, upgrade = FALSE, reload = FALSE, quick = TRUE, quiet = quiet) } diff --git a/inst/WORDLIST b/inst/WORDLIST index 365d8944e..3adcb801b 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -117,6 +117,7 @@ miniUI mnel nchar objs +pak pandoc param params diff --git a/man/install.Rd b/man/install.Rd index b00094edd..8a55e7f15 100644 --- a/man/install.Rd +++ b/man/install.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/install.R \name{install} \alias{install} -\title{Install a local development package.} +\title{Install a local development package} \usage{ install( pkg = ".", @@ -12,22 +12,28 @@ install( args = getOption("devtools.install.args"), quiet = FALSE, dependencies = NA, - upgrade = "default", + upgrade = FALSE, build_vignettes = FALSE, keep_source = getOption("keep.source.pkgs"), - force = FALSE, - ... + force = deprecated() ) } \arguments{ \item{pkg}{The package to use, can be a file path to the package or a package object. See \code{\link[=as.package]{as.package()}} for more information.} -\item{reload}{if \code{TRUE} (the default), will automatically reload the -package after installing.} +\item{reload}{if \code{TRUE} (the default), will automatically attempt reload the +package after installing. Reloading is always completely possible so see +\code{\link[pkgload:unload]{pkgload::unregister()}} for caveats.} \item{quick}{if \code{TRUE} skips docs, multiple-architectures, -demos, and vignettes, to make installation as fast as possible.} +demos, and vignettes, to make installation as fast as possible. +If \code{quick = TRUE}, installation takes place using the current package +directory. If you have compiled code, this means that artefacts of +compilation will be created in the \verb{src/} directory. If you want to avoid +this, you can use \code{build = TRUE} to first build a package bundle and then +install it from a temporary directory. This is slower, but keeps the source +directory pristine.} \item{build}{if \code{TRUE} \code{\link[pkgbuild:build]{pkgbuild::build()}}s the package first: this ensures that the installation is completely clean, and prevents any @@ -48,30 +54,27 @@ value of the option \code{"devtools.install.args"}.} \item{quiet}{If \code{TRUE}, suppress output.} -\item{dependencies}{Which dependencies do you want to check? -Can be a character vector (selecting from "Depends", "Imports", -"LinkingTo", "Suggests", or "Enhances"), or a logical vector. - -\code{TRUE} is shorthand for "Depends", "Imports", "LinkingTo" and -"Suggests". \code{NA} is shorthand for "Depends", "Imports" and "LinkingTo" -and is the default. \code{FALSE} is shorthand for no dependencies (i.e. -just check this package, not its dependencies). - -The value "soft" means the same as \code{TRUE}, "hard" means the same as \code{NA}. - -You can also specify dependencies from one or more additional fields, -common ones include: +\item{dependencies}{What kinds of dependencies to install. Most commonly +one of the following values: \itemize{ -\item Config/Needs/website - for dependencies used in building the pkgdown site. -\item Config/Needs/coverage for dependencies used in calculating test coverage. +\item \code{NA}: only required (hard) dependencies, +\item \code{TRUE}: required dependencies plus optional and development +dependencies, +\item \code{FALSE}: do not install any dependencies. (You might end up with a +non-working package, and/or the installation might fail.) +See \link[pak]{Package dependency types} for other possible values and more +information about package dependencies. }} -\item{upgrade}{Should package dependencies be upgraded? One of "default", "ask", "always", or "never". "default" -respects the value of the \code{R_REMOTES_UPGRADE} environment variable if set, -and falls back to "ask" if unset. "ask" prompts the user for which out of -date packages to upgrade. For non-interactive sessions "ask" is equivalent -to "always". \code{TRUE} and \code{FALSE} are also accepted and correspond to -"always" and "never" respectively.} +\item{upgrade}{When \code{FALSE}, the default, pak does the minimum amount +of work to give you the latest version(s) of \code{pkg}. It will only upgrade +dependent packages if \code{pkg}, or one of their dependencies explicitly +require a higher version than what you currently have. It will also +prefer a binary package over to source package, even it the binary +package is older. + +When \code{upgrade = TRUE}, pak will ensure that you have the latest +version(s) of \code{pkg} and all their dependencies.} \item{build_vignettes}{if \code{TRUE}, will build vignettes. Normally it is \code{build} that's responsible for creating vignettes; this argument makes @@ -82,33 +85,16 @@ sure vignettes are built even if a build never happens (i.e. because package. This is useful for debugging (especially inside of RStudio). It defaults to the option \code{"keep.source.pkgs"}.} -\item{force}{Force installation, even if the remote state has not changed -since the previous install.} - -\item{...}{additional arguments passed to \code{\link[remotes:install_deps]{remotes::install_deps()}} -when installing dependencies.} +\item{force}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} No longer used.} } \description{ -Uses \verb{R CMD INSTALL} to install the package. Will also try to install -dependencies of the package from CRAN, if they're not already installed. -} -\details{ -If \code{quick = TRUE}, installation takes place using the current package -directory. If you have compiled code, this means that artefacts of -compilation will be created in the \verb{src/} directory. If you want to avoid -this, you can use \code{build = TRUE} to first build a package bundle and then -install it from a temporary directory. This is slower, but keeps the source -directory pristine. - -If the package is loaded, it will be reloaded after installation. This is -not always completely possible, see \code{\link[=reload]{reload()}} for caveats. +Uses \verb{R CMD INSTALL} to install the package, after installing needed +dependencies with \code{\link[pak:local_install_deps]{pak::local_install_deps()}}. To install a package in a non-default library, use \code{\link[withr:with_libpaths]{withr::with_libpaths()}}. } \seealso{ -\code{\link[=update_packages]{update_packages()}} to update installed packages from the -source location and \code{\link[=with_debug]{with_debug()}} to install packages with -debugging flags set. +\code{\link[=with_debug]{with_debug()}} to install packages with debugging flags set. Other package installation: \code{\link{uninstall}()} diff --git a/man/install_deps.Rd b/man/install_deps.Rd index cb253983b..2b1a40bf7 100644 --- a/man/install_deps.Rd +++ b/man/install_deps.Rd @@ -64,23 +64,11 @@ to "always". \code{TRUE} and \code{FALSE} are also accepted and correspond to \item{quiet}{If \code{TRUE}, suppress output.} -\item{build}{if \code{TRUE} \code{\link[pkgbuild:build]{pkgbuild::build()}}s the package first: -this ensures that the installation is completely clean, and prevents any -binary artefacts (like \file{.o}, \code{.so}) from appearing in your local -package directory, but is considerably slower, because every compile has -to start from scratch. - -One downside of installing from a built tarball is that the package is -installed from a temporary location. This means that any source references, -at R level or C/C++ level, will point to dangling locations. The debuggers -will not be able to find the sources for step-debugging. If you're -installing the package for development, consider setting \code{build} to -\code{FALSE}.} +\item{build}{If \code{TRUE} build the package before installing.} \item{build_opts}{Options to pass to \verb{R CMD build}, only used when \code{build} is \code{TRUE}.} -\item{...}{additional arguments passed to \code{\link[remotes:install_deps]{remotes::install_deps()}} -when installing dependencies.} +\item{...}{Additional arguments passed to \code{\link[remotes:install_deps]{remotes::install_deps()}}.} } \description{ \code{install_deps()} will install the diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 000000000..b9e1cb7bd --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,5 @@ +# Ensure that we don't affect the user's cache when testing +withr::local_envvar( + R_USER_CACHE_DIR = tempfile(), + .local_envir = teardown_env() +)