Skip to content

Commit 8733b93

Browse files
authored
Better reporting around dependencies needed for task (#2687)
* Better reporting around dependencies needed for task Motivated by build_readme() but also made some improvements in stuff shared with dev_sitrep() * Add NEWS bullets
1 parent 07292ed commit 8733b93

8 files changed

Lines changed: 322 additions & 85 deletions

File tree

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# devtools (development version)
22

3+
* `build_readme()` no longer installs dependencies into the temporary library
4+
(a regression introduced in 2.5.0). It now exits early if a required
5+
dependency is missing and reports any that are out of date or at a dev
6+
version (#2683).
7+
* `dev_sitrep()` reports if devtools itself is out of date (#2687).
8+
39
# devtools 2.5.0
410

511
Deprecations

R/build-readme.R

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ build_rmd_impl <- function(
4040
path = ".",
4141
output_options = list(),
4242
...,
43-
quiet = TRUE
43+
quiet = TRUE,
44+
call = parent.frame()
4445
) {
4546
check_dots_used(action = getOption("devtools.ellipsis_action", warn))
4647

@@ -58,7 +59,7 @@ build_rmd_impl <- function(
5859
cli::cli_abort("Can't find file{?s}: {.path {files[!ok]}}.")
5960
}
6061

61-
local_install(pkg, quiet = TRUE)
62+
local_install(pkg, quiet = quiet, call = call)
6263

6364
# Ensure rendering github_document() doesn't generate HTML file
6465
output_options$html_preview <- FALSE
@@ -130,13 +131,18 @@ build_readme <- function(path = ".", quiet = TRUE, ...) {
130131
}
131132
}
132133

133-
build_qmd_readme <- function(readme_path, path = ".", quiet = TRUE) {
134+
build_qmd_readme <- function(
135+
readme_path,
136+
path = ".",
137+
quiet = TRUE,
138+
call = parent.frame()
139+
) {
134140
pkg <- as.package(path)
135141

136142
check_installed("quarto")
137143
save_all()
138144

139-
local_install(pkg, quiet = TRUE)
145+
local_install(pkg, quiet = quiet, call = call)
140146

141147
# Quarto spawns its own R process for knitr, which won't inherit .libPaths().
142148

R/install.R

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,39 @@ install_dev_deps <- function(
245245
)
246246
}
247247

248-
local_install <- function(pkg = ".", quiet = TRUE, env = parent.frame()) {
248+
abort_for_missing_deps <- function(dep_status, pkg, call = parent.frame()) {
249+
missing <- dep_status[dep_status$status == "missing", ]
250+
if (nrow(missing) == 0) {
251+
return(invisible())
252+
}
253+
254+
n <- nrow(missing)
255+
cli::cli_abort(
256+
c(
257+
"{n} {.pkg {pkg$package}} {cli::qty(n)}{?dependency is/dependencies are} not installed.",
258+
"i" = "Install {cli::qty(n)}{?it/them} with {.run pak::local_install_dev_deps()}.",
259+
dep_labels(missing)
260+
),
261+
call = call
262+
)
263+
}
264+
265+
local_install <- function(
266+
pkg = ".",
267+
quiet = TRUE,
268+
env = parent.frame(),
269+
call = parent.frame()
270+
) {
249271
pkg <- as.package(pkg)
250272

273+
dep_status <- pkg_dep_status(pkg, dependencies = NA)
274+
abort_for_missing_deps(dep_status, pkg, call = call)
275+
report_deps_ahead_behind(
276+
dep_status,
277+
pkg_name = pkg$package,
278+
update_code = "pak::local_install_dev_deps()"
279+
)
280+
251281
if (!quiet) {
252282
cli::cli_inform(c(
253283
i = "Installing {.pkg {pkg$package}} in temporary library"

R/sitrep.R

Lines changed: 97 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ dev_sitrep <- function(pkg = ".", debug = FALSE) {
109109
has_build_tools = has_build_tools,
110110
rtools_path = if (has_build_tools) pkgbuild::rtools_path(),
111111
devtools_version = utils::packageVersion("devtools"),
112-
devtools_deps = compare_deps(pak::pkg_deps("devtools", dependencies = NA)),
113-
pkg_deps = if (!is.null(pkg)) {
114-
compare_deps(pak::local_dev_deps(pkg$path, dependencies = TRUE))
115-
},
112+
devtools_cran_version = pak::pkg_deps(
113+
"devtools",
114+
dependencies = FALSE
115+
)$version,
116+
devtools_deps = pkg_dep_status("devtools", dependencies = NA),
117+
pkg_deps = if (!is.null(pkg)) pkg_dep_status(pkg, dependencies = TRUE),
116118
rstudio_version = if (is_rstudio_running()) rstudioapi::getVersion(),
117119
rstudio_msg = if (!is_positron()) check_for_rstudio_updates()
118120
)
@@ -127,6 +129,7 @@ new_dev_sitrep <- function(
127129
has_build_tools = TRUE,
128130
rtools_path = NULL,
129131
devtools_version = utils::packageVersion("devtools"),
132+
devtools_cran_version = devtools_version,
130133
devtools_deps = NULL,
131134
pkg_deps = NULL,
132135
rstudio_version = NULL,
@@ -142,6 +145,7 @@ new_dev_sitrep <- function(
142145
has_build_tools = has_build_tools,
143146
rtools_path = rtools_path,
144147
devtools_version = devtools_version,
148+
devtools_cran_version = devtools_cran_version,
145149
devtools_deps = devtools_deps,
146150
pkg_deps = pkg_deps,
147151
rstudio_version = rstudio_version,
@@ -191,26 +195,28 @@ print.dev_sitrep <- function(x, ...) {
191195
cli::cli_rule("devtools")
192196
kv_line("version", x$devtools_version)
193197

198+
devtools_version <- package_version(x$devtools_version)
199+
devtools_cran_version <- package_version(x$devtools_cran_version)
200+
if (devtools_version < devtools_cran_version) {
201+
all_ok <- FALSE
202+
cli::cli_bullets(c(
203+
"!" = "{.field devtools} is out of date ({.val {devtools_version}} vs {.val {devtools_cran_version}}).",
204+
" " = "Update it with {.run pak::pak(\"devtools\")}."
205+
))
206+
} else if (devtools_version > devtools_cran_version) {
207+
cli::cli_bullets(c(
208+
"i" = "{.field devtools} is ahead of CRAN ({.val {devtools_version}} vs {.val {devtools_cran_version}})."
209+
))
210+
}
211+
194212
devtools_not_ok <- any(x$devtools_deps$status != "ok")
195213
if (devtools_not_ok) {
196214
all_ok <- FALSE
197-
198-
behind <- x$devtools_deps[x$devtools_deps$status == "behind", ]
199-
if (nrow(behind) > 0) {
200-
cli::cli_bullets(c(
201-
"!" = "{.field devtools} or its dependencies are out of date.",
202-
" " = "Update them with {.code pak::pak(\"devtools\").}"
203-
))
204-
cli::cli_verbatim(paste(" ", dep_labels(behind)))
205-
}
206-
207-
ahead <- x$devtools_deps[x$devtools_deps$status == "ahead", ]
208-
if (nrow(ahead) > 0) {
209-
cli::cli_bullets(c(
210-
"i" = "{.field devtools} or its dependencies are installed from a dev version, FYI:"
211-
))
212-
cli::cli_verbatim(paste(" ", dep_labels(ahead)))
213-
}
215+
report_deps_ahead_behind(
216+
x$devtools_deps,
217+
pkg_name = "devtools",
218+
update_code = 'pak::pak("devtools")'
219+
)
214220
}
215221

216222
cli::cli_rule("dev package")
@@ -220,23 +226,11 @@ print.dev_sitrep <- function(x, ...) {
220226
dev_pkg_not_ok <- any(x$pkg_deps$status != "ok")
221227
if (dev_pkg_not_ok) {
222228
all_ok <- FALSE
223-
224-
behind <- x$pkg_deps[x$pkg_deps$status == "behind", ]
225-
if (nrow(behind) > 0) {
226-
cli::cli_bullets(c(
227-
"!" = "{.field {x$pkg$package}} dependencies are out of date.",
228-
" " = "Update them with {.code pak::local_install_dev_deps()}."
229-
))
230-
cli::cli_verbatim(paste(" ", dep_labels(behind)))
231-
}
232-
233-
ahead <- x$pkg_deps[x$pkg_deps$status == "ahead", ]
234-
if (nrow(ahead) > 0) {
235-
cli::cli_bullets(c(
236-
"i" = "{.field {x$pkg$package}} dependencies are installed from a dev version, FYI:"
237-
))
238-
cli::cli_verbatim(paste(" ", dep_labels(ahead)))
239-
}
229+
report_deps_ahead_behind(
230+
x$pkg_deps,
231+
pkg_name = x$pkg$package,
232+
update_code = "pak::local_install_dev_deps()"
233+
)
240234
}
241235

242236
if (all_ok) {
@@ -248,7 +242,29 @@ print.dev_sitrep <- function(x, ...) {
248242

249243
# Helpers -----------------------------------------------------------------
250244

251-
compare_deps <- function(deps) {
245+
#' Get dependency status for a package, excluding the package itself
246+
#'
247+
#' @param pkg A package name or a package object, as returned by `as.package()`.
248+
#' @param dependencies Which dependency types to include. Passed along to
249+
#' `pak::pkg_deps()` or `pak::local_dev_deps()`.
250+
#' @returns A data frame with one row per dependency and columns:
251+
#' * package (name)
252+
#' * latest (version)
253+
#' * installed (version)
254+
#' * status (one of: missing, behind, ok, ahead)
255+
#' @noRd
256+
pkg_dep_status <- function(pkg, dependencies = NA) {
257+
if (is_string(pkg)) {
258+
deps <- pak::pkg_deps(pkg, dependencies = dependencies)
259+
} else if (inherits(pkg, "package")) {
260+
deps <- pak::local_dev_deps(pkg$path, dependencies = dependencies)
261+
deps <- deps[deps$package != pkg$package, ]
262+
} else {
263+
cli::cli_abort(
264+
"{.arg pkg} must be a string package name or a package object."
265+
)
266+
}
267+
252268
installed <- map_chr(deps$package, function(p) {
253269
tryCatch(
254270
as.character(utils::packageVersion(p)),
@@ -257,7 +273,7 @@ compare_deps <- function(deps) {
257273
})
258274
status <- map2_chr(installed, deps$version, function(inst, latest) {
259275
if (is.na(inst)) {
260-
return("behind")
276+
return("missing")
261277
}
262278
switch(
263279
as.character(utils::compareVersion(inst, latest)),
@@ -274,6 +290,46 @@ compare_deps <- function(deps) {
274290
)
275291
}
276292

293+
#' Emit cli messages about ahead/behind dependencies
294+
#'
295+
#' @param dep_status A data frame as returned by `compare_deps()`, with
296+
#' columns `package`, `latest`, `installed`, `status`.
297+
#' @param pkg_name Package name to mention in the message.
298+
#' @param update_code Code suggestion for updating behind deps.
299+
#' @return Called for its side effects.
300+
#' @noRd
301+
report_deps_ahead_behind <- function(dep_status, pkg_name, update_code) {
302+
missing <- dep_status[dep_status$status == "missing", ]
303+
if (nrow(missing) > 0) {
304+
n <- nrow(missing)
305+
cli::cli_bullets(c(
306+
"!" = "{n} {.field {pkg_name}} {cli::qty(n)}{?dependency is/dependencies are} not installed.",
307+
" " = "Install {cli::qty(n)}{?it/them} with {.run {update_code}}."
308+
))
309+
cli::cli_verbatim(paste(" ", dep_labels(missing)))
310+
}
311+
312+
behind <- dep_status[dep_status$status == "behind", ]
313+
if (nrow(behind) > 0) {
314+
n <- nrow(behind)
315+
cli::cli_bullets(c(
316+
"!" = "{n} {.field {pkg_name}} {cli::qty(n)}{?dependency is/dependencies are} out of date.",
317+
" " = "Update {cli::qty(n)}{?it/them} with {.run {update_code}}."
318+
))
319+
cli::cli_verbatim(paste(" ", dep_labels(behind)))
320+
}
321+
322+
ahead <- dep_status[dep_status$status == "ahead", ]
323+
if (nrow(ahead) > 0) {
324+
n <- nrow(ahead)
325+
cli::cli_bullets(c(
326+
"i" = "{n} {.field {pkg_name}} {cli::qty(n)}{?dependency is/dependencies are} ahead of CRAN."
327+
))
328+
cli::cli_verbatim(paste(" ", dep_labels(ahead)))
329+
}
330+
}
331+
332+
277333
dep_labels <- function(deps) {
278334
# helps with readability
279335
deps$package <- format(deps$package, justify = "left")
@@ -283,7 +339,7 @@ dep_labels <- function(deps) {
283339

284340
format_dep_line <- function(package, installed, latest, status) {
285341
if (is.na(installed)) {
286-
paste0(package, " (not installed)")
342+
paste0(package, " (", cli::col_red("missing"), ")")
287343
} else {
288344
status_styled <- if (status == "behind") {
289345
cli::col_red(status)

tests/testthat/_snaps/build-readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
Error in `build_readme()`:
2323
! Found multiple executable READMEs: 'README.qmd' and 'README.Rmd'. There can only be one.
2424

25+
# build_readme() aborts when a dep is missing
26+
27+
Code
28+
build_readme(pkg)
29+
Condition
30+
Error in `build_readme()`:
31+
! 1 {PACKAGE} dependency is not installed.
32+
i Install it with `pak::local_install_dev_deps()`.
33+
missingpkg (missing)
34+
2535
# build_rmd() is deprecated
2636

2737
Code

0 commit comments

Comments
 (0)