From beadb4b415d44782a8098605bdf6e03b47bcb02b Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 22 Jan 2026 09:31:45 -0600 Subject: [PATCH 01/12] Use `pak::local_install_deps()` in `install()` Includes some general updates to the docs of `install()`, and mild improvements to non-quiet output. Fixes #2486. Fixes #2572. --- DESCRIPTION | 1 + NEWS.md | 1 + R/build-site.R | 2 +- R/install.R | 213 +++++++++++++++++++++-------------------- man/install.Rd | 84 +++++++--------- man/install_deps.Rd | 16 +--- tests/testthat/setup.R | 5 + 7 files changed, 153 insertions(+), 169 deletions(-) create mode 100644 tests/testthat/setup.R 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..2db1613ea 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 INTSTALL"), 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/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() +) From b411470b0bab0e6e07553c94a50253cb57608447 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 22 Jan 2026 09:34:59 -0600 Subject: [PATCH 02/12] Fix typo --- R/install.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/install.R b/R/install.R index 2db1613ea..28d487106 100644 --- a/R/install.R +++ b/R/install.R @@ -124,7 +124,7 @@ install <- function( } if (!quiet) { - cli::cat_rule(paste0("R CMD INTSTALL"), col = "cyan") + cli::cat_rule(paste0("R CMD INSTALL"), col = "cyan") } pkgbuild::with_build_tools( required = FALSE, From dcec15a5f53d473f34fc0a74877c21b717ad0d7b Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 22 Jan 2026 09:37:30 -0600 Subject: [PATCH 03/12] Add pak to WORDLIST --- inst/WORDLIST | 1 + 1 file changed, 1 insertion(+) 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 From 46be427c07026e51abdd340d75a64dfa1ece6c8f Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 10 Feb 2026 19:34:04 -0800 Subject: [PATCH 04/12] Fix some wordos --- R/install.R | 6 +++--- man/install.Rd | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/install.R b/R/install.R index 422d58dc1..4a1ebbf30 100644 --- a/R/install.R +++ b/R/install.R @@ -7,9 +7,9 @@ #' To install a package in a non-default library, use [withr::with_libpaths()]. #' #' @template devtools -#' @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 reload if `TRUE` (the default), will automatically attempt to reload +#' the package after installing. Reloading is not 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 diff --git a/man/install.Rd b/man/install.Rd index 8a55e7f15..304b307ca 100644 --- a/man/install.Rd +++ b/man/install.Rd @@ -22,9 +22,9 @@ install( \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 attempt reload the -package after installing. Reloading is always completely possible so see -\code{\link[pkgload:unload]{pkgload::unregister()}} for caveats.} +\item{reload}{if \code{TRUE} (the default), will automatically attempt to reload +the package after installing. Reloading is not 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. From 531df95fb01904572e0f5f128ada8c257716738e Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 10 Feb 2026 19:41:50 -0800 Subject: [PATCH 05/12] Typo --- R/install.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/install.R b/R/install.R index 4a1ebbf30..09f2cc639 100644 --- a/R/install.R +++ b/R/install.R @@ -63,7 +63,7 @@ install <- function( 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)") + lifecycle::deprecate_warn("2.5.0", "install(force)") } pkg <- as.package(pkg) From e23189b217597c655c69201fe199ad56f8e55189 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 11 Feb 2026 08:13:03 -0800 Subject: [PATCH 06/12] AFAICT R_PKG_CACHE_DIR is the highest priority env var for pak https://github.com/r-lib/pak/blob/da1069e3cf6401be4cde33468beed67c8c248e9c/src/library/pkgcache/R/cache-dirs.R#L57-L71 --- tests/testthat/setup.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index b9e1cb7bd..e436580d4 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -1,5 +1,5 @@ # Ensure that we don't affect the user's cache when testing withr::local_envvar( - R_USER_CACHE_DIR = tempfile(), + R_PKG_CACHE_DIR = tempfile(), .local_envir = teardown_env() ) From 2ace12834704f702a55ff38e399557d35cfe2f88 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 11 Feb 2026 13:15:49 -0800 Subject: [PATCH 07/12] Work on the headers --- R/install.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/R/install.R b/R/install.R index 09f2cc639..4a191b5d0 100644 --- a/R/install.R +++ b/R/install.R @@ -81,7 +81,10 @@ install <- function( withr::local_output_sink(withr::local_tempfile()) withr::local_message_sink(withr::local_tempfile()) } else { - cli::cat_rule("Installing dependencies", col = "cyan") + cli::cat_rule( + paste0("Installing dependencies of ", pkg$package), + col = "cyan" + ) } pak::local_install_deps( pkg$path, @@ -105,6 +108,9 @@ install <- function( opts <- c(opts, args) if (build) { + if (!quiet) { + cli::cat_rule(paste0("Building ", pkg$package), col = "cyan") + } install_path <- pkgbuild::build( pkg$path, dest_path = tempdir(), @@ -124,7 +130,7 @@ install <- function( } if (!quiet) { - cli::cat_rule(paste0("R CMD INSTALL"), col = "cyan") + cli::cat_rule(paste0("Installing ", pkg$package), col = "cyan") } pkgbuild::with_build_tools( required = FALSE, From 37702d8669d410adb12e7aafa6f953a896f1a1ee Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 11 Feb 2026 15:42:16 -0800 Subject: [PATCH 08/12] Update `keep_source` default and work on the docs --- R/install.R | 44 ++++++++++++++++++++++---------------------- man/install.Rd | 44 ++++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/R/install.R b/R/install.R index 4a191b5d0..23348c4ef 100644 --- a/R/install.R +++ b/R/install.R @@ -4,32 +4,30 @@ #' 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()]. +#' To install to a non-default library, use [withr::with_libpaths()]. #' #' @template devtools #' @param reload if `TRUE` (the default), will automatically attempt to reload #' the package after installing. Reloading is not 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 -#' package directory, but is considerably slower, because every compile has -#' to start from scratch. +#' @param quick if `TRUE`, skips some optional steps (e.g. help +#' pre-rendering and multi-arch builds) to make installation as fast +#' as possible. +#' @param build if `TRUE` (the default), [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 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 `build` to -#' `FALSE`. +#' installed from a temporary location. This means that any source references +#' will point to dangling locations and debuggers won't have direct access to +#' the source for step-debugging. For development purposes, `build = FALSE` is +#' often the better choice. +#' +#' If `FALSE`, the package is installed directly from its source +#' directory. This is faster and can be favorable for preserving source +#' references for debugging (see `keep_source`). #' @param args An optional character vector of additional command line #' arguments to be passed to `R CMD INSTALL`. This defaults to the #' value of the option `"devtools.install.args"`. @@ -38,8 +36,10 @@ #' sure vignettes are built even if a build never happens (i.e. because #' `build = FALSE`). #' @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"`. +#' package. This is useful for debugging (especially inside of RStudio or +#' Positron). Defaults to `getOption("keep.source.pkgs") || !build`, +#' since srcrefs are most useful when the package is installed from its +#' source directory, i.e. when `build = FALSE`. #' @param quiet If `TRUE`, suppress output. #' @param force `r lifecycle::badge("deprecated")` No longer used. #' @family package installation @@ -56,7 +56,7 @@ install <- function( dependencies = NA, upgrade = FALSE, build_vignettes = FALSE, - keep_source = getOption("keep.source.pkgs"), + keep_source = getOption("keep.source.pkgs") || !build, force = deprecated() ) { if (!is.logical(upgrade) || length(upgrade) != 1) { diff --git a/man/install.Rd b/man/install.Rd index 304b307ca..4a1cbc184 100644 --- a/man/install.Rd +++ b/man/install.Rd @@ -14,7 +14,7 @@ install( dependencies = NA, upgrade = FALSE, build_vignettes = FALSE, - keep_source = getOption("keep.source.pkgs"), + keep_source = getOption("keep.source.pkgs") || !build, force = deprecated() ) } @@ -26,27 +26,25 @@ package object. See \code{\link[=as.package]{as.package()}} for more informatio the package after installing. Reloading is not 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. -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{quick}{if \code{TRUE}, skips some optional steps (e.g. help +pre-rendering and multi-arch builds) to make installation as fast +as possible.} -\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. +\item{build}{if \code{TRUE} (the default), \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}.} +installed from a temporary location. This means that any source references +will point to dangling locations and debuggers won't have direct access to +the source for step-debugging. For development purposes, \code{build = FALSE} is +often the better choice. + +If \code{FALSE}, the package is installed directly from its source +directory. This is faster and can be favorable for preserving source +references for debugging (see \code{keep_source}).} \item{args}{An optional character vector of additional command line arguments to be passed to \verb{R CMD INSTALL}. This defaults to the @@ -82,8 +80,10 @@ sure vignettes are built even if a build never happens (i.e. because \code{build = FALSE}).} \item{keep_source}{If \code{TRUE} will keep the srcrefs from an installed -package. This is useful for debugging (especially inside of RStudio). -It defaults to the option \code{"keep.source.pkgs"}.} +package. This is useful for debugging (especially inside of RStudio or +Positron). Defaults to \code{getOption("keep.source.pkgs") || !build}, +since srcrefs are most useful when the package is installed from its +source directory, i.e. when \code{build = FALSE}.} \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.} } @@ -91,7 +91,7 @@ It defaults to the option \code{"keep.source.pkgs"}.} 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()}}. +To install to a non-default library, use \code{\link[withr:with_libpaths]{withr::with_libpaths()}}. } \seealso{ \code{\link[=with_debug]{with_debug()}} to install packages with debugging flags set. From 2fe560b729d099d48f8b754fd4d52ba30cde0e85 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 11 Feb 2026 16:30:44 -0800 Subject: [PATCH 09/12] Polish NEWS bullet --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7196c26f2..16cb6e9e7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ * `dev_sitrep()` now uses cli for user-facing messages instead of deprecated usethis UI functions. * `dev_sitrep()` now works correctly in Positron (#2618). * `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). +* `install()` now installs dependencies with `pak::local_install_deps()` instead of `remotes::install_deps()`. This lets us default to `upgrade = FALSE`, so that existing dependencies are only upgraded when a newer version is actually required (#2486). `keep_source` now defaults to `TRUE` when `build = FALSE`, so that source references are automatically preserved during development installs. * Package installation functions are now deprecated: `install_bioc()`, `install_bitbucket()`, `install_cran()`, `install_deps()`, `install_dev()`, `install_dev_deps()`, `install_git()`, `install_github()`, `install_gitlab()`, `install_local()`, `install_svn()`, `install_url()`, `install_version()`, `update_packages()`, `dev_package_deps()`, `github_pull()`, and `github_release()`. We now recommend pak () for general package installation. See `?install-deprecated` for migration guidance. * `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). * `release()` is deprecated in favour of `usethis::use_release_issue()`. From c73c275e4c5ed11ff4eb586269ce007169a852b1 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 12 Feb 2026 07:40:24 -0600 Subject: [PATCH 10/12] Remove extra build rule --- R/install.R | 3 --- tests/testthat/_snaps/install.md | 11 +++++++++++ tests/testthat/test-install.R | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 tests/testthat/_snaps/install.md diff --git a/R/install.R b/R/install.R index 23348c4ef..ea21bcd0f 100644 --- a/R/install.R +++ b/R/install.R @@ -108,9 +108,6 @@ install <- function( opts <- c(opts, args) if (build) { - if (!quiet) { - cli::cat_rule(paste0("Building ", pkg$package), col = "cyan") - } install_path <- pkgbuild::build( pkg$path, dest_path = tempdir(), diff --git a/tests/testthat/_snaps/install.md b/tests/testthat/_snaps/install.md new file mode 100644 index 000000000..282e3b572 --- /dev/null +++ b/tests/testthat/_snaps/install.md @@ -0,0 +1,11 @@ +# install reports stages + + Code + install(pkg, reload = FALSE, build = TRUE) + Output + -- Installing dependencies of testTest --------------------- + Message + Output + -- R CMD build --------------------------------------------- + -- Installing testTest ------------------------------------- + diff --git a/tests/testthat/test-install.R b/tests/testthat/test-install.R index 6e14eb33a..1dddee640 100644 --- a/tests/testthat/test-install.R +++ b/tests/testthat/test-install.R @@ -1,3 +1,22 @@ +show_headers <- function(lines) { + lines <- cli::ansi_strip(lines) + lines <- lines[grepl("^--", lines)] + lines +} + +test_that("install reports stages", { + skip_on_cran() + local_reproducible_output(width = 60) + + pkg <- local_package_copy(test_path("testTest")) + withr::local_temp_libpaths() + + expect_snapshot( + install(pkg, reload = FALSE, build = TRUE), + transform = show_headers + ) +}) + test_that("vignettes built on install", { skip_on_cran() From 33849fa39cdb2ba95d1b4b585b07f878e8871eee Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 12 Feb 2026 07:44:41 -0600 Subject: [PATCH 11/12] Tweak messaging some more --- R/install.R | 7 ++----- tests/testthat/_snaps/install.md | 14 ++++++++++++-- tests/testthat/test-install.R | 4 ++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/R/install.R b/R/install.R index ea21bcd0f..47edaab04 100644 --- a/R/install.R +++ b/R/install.R @@ -81,10 +81,7 @@ install <- function( withr::local_output_sink(withr::local_tempfile()) withr::local_message_sink(withr::local_tempfile()) } else { - cli::cat_rule( - paste0("Installing dependencies of ", pkg$package), - col = "cyan" - ) + cli::cat_rule("pak::local_install_deps()", col = "cyan") } pak::local_install_deps( pkg$path, @@ -127,7 +124,7 @@ install <- function( } if (!quiet) { - cli::cat_rule(paste0("Installing ", pkg$package), col = "cyan") + cli::cat_rule("R CMD INSTALL", col = "cyan") } pkgbuild::with_build_tools( required = FALSE, diff --git a/tests/testthat/_snaps/install.md b/tests/testthat/_snaps/install.md index 282e3b572..e0b84ceab 100644 --- a/tests/testthat/_snaps/install.md +++ b/tests/testthat/_snaps/install.md @@ -1,11 +1,21 @@ # install reports stages + Code + install(pkg, reload = FALSE, build = FALSE) + Output + -- pak::local_install_deps() ------------------------------- + Message + Output + -- R CMD INSTALL ------------------------------------------- + +--- + Code install(pkg, reload = FALSE, build = TRUE) Output - -- Installing dependencies of testTest --------------------- + -- pak::local_install_deps() ------------------------------- Message Output -- R CMD build --------------------------------------------- - -- Installing testTest ------------------------------------- + -- R CMD INSTALL ------------------------------------------- diff --git a/tests/testthat/test-install.R b/tests/testthat/test-install.R index 1dddee640..75b702665 100644 --- a/tests/testthat/test-install.R +++ b/tests/testthat/test-install.R @@ -11,6 +11,10 @@ test_that("install reports stages", { pkg <- local_package_copy(test_path("testTest")) withr::local_temp_libpaths() + expect_snapshot( + install(pkg, reload = FALSE, build = FALSE), + transform = show_headers + ) expect_snapshot( install(pkg, reload = FALSE, build = TRUE), transform = show_headers From 95e25adc9677de2b530e4e42c0a39f3b73c7afb3 Mon Sep 17 00:00:00 2001 From: "Jennifer (Jenny) Bryan" Date: Thu, 12 Feb 2026 07:43:23 -0800 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Hadley Wickham --- R/install.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/install.R b/R/install.R index 47edaab04..7225fbec0 100644 --- a/R/install.R +++ b/R/install.R @@ -13,8 +13,8 @@ #' @param quick if `TRUE`, skips some optional steps (e.g. help #' pre-rendering and multi-arch builds) to make installation as fast #' as possible. -#' @param build if `TRUE` (the default), [pkgbuild::build()]s the package -#' first. This ensures that the installation is completely clean, and +#' @param build If `TRUE` (the default), first [pkgbuild::build()]s the +#' package. This ensures that the installation is completely clean, and #' prevents any binary artefacts (like \file{.o}, `.so`) from appearing #' in your local package directory, but is considerably slower, because #' every compile has to start from scratch.