Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Suggests:
httr2 (>= 1.0.0),
knitr (>= 1.39),
lintr (>= 3.0.0),
quarto,
quarto (>= 1.5.1),
remotes (>= 2.5.0),
rmarkdown (>= 2.14),
rstudioapi (>= 0.13),
Expand Down
4 changes: 2 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Deprecations

Other improvements

* `build_readme()` gains support for `README.qmd` and renders using Quarto (#2620).
* `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.
* `build_manual()` reports more details on failure (#2586).
* `build_site()` now just calls `pkgdown::build_site()`, meaning that you will get more (informative) output by default (#2578).
Expand All @@ -28,8 +29,7 @@ Other improvements

# devtools 2.4.6

* Functions that use httr now explicitly check that it is installed
(@catalamarti, #2573).
* Functions that use httr now explicitly check that it is installed (@catalamarti, #2573).

* `test_coverage()` now works if the package has not been installed.

Expand Down
76 changes: 57 additions & 19 deletions R/build-readme.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#' `r lifecycle::badge("deprecated")`
#'
#' `build_rmd()` is deprecated, as it is a low-level helper for internal use. To
#' render your package's `README.Rmd` or `README.qmd`, use [build_readme()]. To
#' render your package's `README.qmd` or `README.Rmd`, use [build_readme()]. To
#' preview a vignette or article, use functions like [pkgdown::build_site()] or
#' [pkgdown::build_article()].
#'
Expand Down Expand Up @@ -64,9 +64,6 @@ build_rmd_impl <- function(
output_options$html_preview <- FALSE

for (path in paths) {
if (!quiet) {
cli::cli_inform(c(i = "Building {.path {path}}"))
}
callr::r_safe(
function(...) rmarkdown::render(...),
args = list(
Expand All @@ -86,36 +83,77 @@ build_rmd_impl <- function(

#' Build README
#'
#' Renders an executable README, such as `README.Rmd`, to `README.md`.
#' Specifically, `build_readme()`:
#' Renders an executable README, i.e. `README.qmd` or `README.Rmd`, to
#' `README.md`. Specifically, `build_readme()`:
#' * Installs a copy of the package's current source to a temporary library
#' * Renders the README in a clean R session
#'
#' @param path Path to the package to build the README.
#' @param path Path to the top-level directory of the source package.
#' @param quiet If `TRUE`, suppresses most output. Set to `FALSE`
#' if you need to debug.
#' @param ... Additional arguments passed to [rmarkdown::render()].
#' @param ... Additional arguments passed to [rmarkdown::render()], in the
#' case of `README.Rmd`. Not used for `README.qmd`
#' @export
build_readme <- function(path = ".", quiet = TRUE, ...) {
pkg <- as.package(path)

regexp <- paste0(path_file(pkg$path), "/(inst/)?readme[.]rmd$")
readme_path <- path_abs(dir_ls(
pkg$path,
ignore.case = TRUE,
regexp = regexp,
recurse = 1,
type = "file"
))
readme_candidates <- c(
path(pkg$path, "README.qmd"),
path(pkg$path, "README.Rmd"),
path(pkg$path, "inst", "README.qmd"),
path(pkg$path, "inst", "README.Rmd")
)
readme_path <- readme_candidates[file_exists(readme_candidates)]

if (length(readme_path) == 0) {
cli::cli_abort("Can't find {.file README.Rmd} or {.file inst/README.Rmd}.")
cli::cli_abort(
"Can't find {.file README.qmd} or {.file README.Rmd}, at the top-level or
below {.file inst/}."
)
}
if (length(readme_path) > 1) {
rel_paths <- path_rel(readme_path, pkg$path)
cli::cli_abort(
"Can't have both {.file README.Rmd} and {.file inst/README.Rmd}."
"Found multiple executable READMEs: {.file {rel_paths}}. There can only be
one."
)
}

build_rmd_impl(readme_path, path = path, quiet = quiet, ...)
if (!quiet) {
cli::cli_inform(c(i = "Building {.path {readme_path}}"))
}

if (path_ext(readme_path) == "qmd") {
build_qmd_readme(readme_path, path = path, quiet = quiet)
} else {
build_rmd_impl(readme_path, path = path, quiet = quiet, ...)
}
}

build_qmd_readme <- function(readme_path, path = ".", quiet = TRUE) {
pkg <- as.package(path)

check_installed("quarto")
save_all()

local_install(pkg, quiet = TRUE)

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

# Pass library paths via R_LIBS_USER so the quarto subprocess finds the
# temporarily installed package first, ahead of any user-installed version.
lib_paths <- paste(.libPaths(), collapse = .Platform$path.sep)

callr::r_safe(
function(input, quiet) {
quarto::quarto_render(input = input, quiet = quiet)
},
args = list(input = readme_path, quiet = quiet),
env = c(callr::rcmd_safe_env(), R_LIBS_USER = lib_paths),
show = TRUE,
spinner = FALSE,
stderr = "2>&1"
)

invisible(TRUE)
}
9 changes: 5 additions & 4 deletions man/build_readme.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion man/build_rmd.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions tests/testthat/_snaps/build-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
build_readme(pkg)
Condition
Error in `build_readme()`:
! Can't find 'README.Rmd' or 'inst/README.Rmd'.
! Can't find 'README.qmd' or 'README.Rmd', at the top-level or below 'inst/'.

---

Code
build_readme(pkg)
Condition
Error in `build_readme()`:
! Can't have both 'README.Rmd' and 'inst/README.Rmd'.
! Found multiple executable READMEs: 'README.Rmd' and 'inst/README.Rmd'. There can only be one.

# errors if both README.qmd and README.Rmd exist

Code
build_readme(pkg)
Condition
Error in `build_readme()`:
! Found multiple executable READMEs: 'README.qmd' and 'README.Rmd'. There can only be one.

# build_rmd() is deprecated

Expand Down
64 changes: 62 additions & 2 deletions tests/testthat/test-build-readme.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test_that("can build README in root directory", {
test_that("can build README.Rmd in root directory", {
skip_on_cran()

pkg <- local_package_create()
Expand All @@ -14,7 +14,7 @@ test_that("can build README in root directory", {
expect_false(file_exists(path(pkg, "README.html")))
})

test_that("can build README in inst/", {
test_that("can build README.Rmd in inst/", {
skip_on_cran()

pkg <- local_package_create()
Expand All @@ -37,6 +37,59 @@ test_that("can build README in inst/", {
expect_false(file_exists(path(pkg, "inst", "README.html")))
})

test_that("can build README.qmd in root directory", {
skip_on_cran()
skip_if_not_installed("quarto")
skip_if_not(quarto::quarto_available(), "quarto cli not available")

pkg <- local_package_create()
# TODO: use usethis::use_readme_qmd() once it's in a usethis release
# https://github.com/r-lib/usethis/pull/2219
writeLines(
c(
"---",
"format: gfm",
"---",
"",
"# testpkg",
"",
"This is a test package."
),
path(pkg, "README.qmd")
)

build_readme(pkg, quiet = TRUE)
expect_true(file_exists(path(pkg, "README.md")))
})

test_that("can build README.qmd in inst/", {
skip_on_cran()
skip_if_not_installed("quarto")
skip_if_not(quarto::quarto_available(), "quarto cli not available")

pkg <- local_package_create()
# TODO: use usethis::use_readme_qmd() once it's in a usethis release
# https://github.com/r-lib/usethis/pull/2219
dir_create(pkg, "inst")
writeLines(
c(
"---",
"format: gfm",
"---",
"",
"# testpkg",
"",
"This is a test package."
),
path(pkg, "inst", "README.qmd")
)

build_readme(pkg, quiet = TRUE)
expect_true(file_exists(path(pkg, "inst", "README.md")))
expect_false(file_exists(path(pkg, "README.qmd")))
expect_false(file_exists(path(pkg, "README.md")))
})

test_that("useful errors if too few or too many", {
pkg <- local_package_create()
expect_snapshot(build_readme(pkg), error = TRUE)
Expand All @@ -52,6 +105,13 @@ test_that("useful errors if too few or too many", {
expect_snapshot(build_readme(pkg), error = TRUE)
})

test_that("errors if both README.qmd and README.Rmd exist", {
pkg <- local_package_create()
file_create(path(pkg, "README.Rmd"))
file_create(path(pkg, "README.qmd"))
expect_snapshot(build_readme(pkg), error = TRUE)
})

test_that("don't error for README in another directory", {
skip_on_cran()

Expand Down