1#' Build package vignettes.
2#'
3#' Builds package vignettes using the same algorithm that `R CMD build`
4#' does. This means including non-Sweave vignettes, using makefiles (if
5#' present), and copying over extra files. The files are copied in the 'doc'
6#' directory and an vignette index is created in 'Meta/vignette.rds', as they
7#' would be in a built package. 'doc' and 'Meta' are added to
8#' `.Rbuildignore`, so will not be included in the built package. These
9#' files can be checked into version control, so they can be viewed with
10#' `browseVignettes()` and `vignette()` if the package has been
11#' loaded with `load_all()` without needing to re-build them locally.
12#'
13#' @template devtools
14#' @param quiet If `TRUE`, suppresses most output. Set to `FALSE`
15#'   if you need to debug.
16#' @param install If `TRUE`, install the package before building
17#'   vignettes.
18#' @param keep_md If `TRUE`, move md intermediates as well as rendered
19#'   outputs. Most useful when using the `keep_md` YAML option for Rmarkdown
20#'   outputs. See
21#'   <https://bookdown.org/yihui/rmarkdown/html-document.html#keeping-markdown>.
22#' @inheritParams tools::buildVignettes
23#' @inheritParams remotes::install_deps
24#' @importFrom stats update
25#' @keywords programming
26#' @seealso [clean_vignettes()] to remove the pdfs in
27#'   \file{doc} created from vignettes
28#' @export
29#' @seealso [clean_vignettes()] to remove build tex/pdf files.
30build_vignettes <- function(pkg = ".",
31                            dependencies = "VignetteBuilder",
32                            clean = TRUE,
33                            upgrade = "never",
34                            quiet = TRUE,
35                            install = TRUE,
36                            keep_md = TRUE) {
37  pkg <- as.package(pkg)
38
39  deps <- remotes::dev_package_deps(pkg$path, dependencies)
40  update(deps, upgrade = upgrade)
41
42  vigns <- tools::pkgVignettes(dir = pkg$path)
43  if (length(vigns$docs) == 0) return()
44
45  cli::cli_alert_info("Building {.pkg {pkg$package}} vignettes")
46
47  if (isTRUE(install)) {
48    build <- function(pkg_path, clean, quiet, upgrade) {
49      withr::with_temp_libpaths(action = "prefix", {
50        devtools::install(pkg_path, upgrade = upgrade, reload = FALSE, quiet = quiet)
51        tools::buildVignettes(dir = pkg_path, clean = clean, tangle = TRUE, quiet = quiet)
52      })
53    }
54  } else {
55    build <- function(pkg_path, clean, quiet, upgrade) {
56      tools::buildVignettes(dir = pkg_path, clean = clean, tangle = TRUE, quiet = quiet)
57    }
58  }
59
60  callr::r(
61    build,
62    args = list(pkg_path = pkg$path, clean = clean, upgrade = upgrade, quiet = quiet),
63    show = !quiet,
64    spinner = FALSE,
65    stderr = "2>&1"
66  )
67
68  # We need to re-run pkgVignettes now that they are built to get the output
69  # files as well
70  vigns <- tools::pkgVignettes(dir = pkg$path, source = TRUE, output = TRUE)
71
72  copy_vignettes(pkg, keep_md)
73
74  create_vignette_index(pkg, vigns)
75
76  invisible(TRUE)
77}
78
79create_vignette_index <- function(pkg, vigns) {
80  usethis_use_directory(pkg, "Meta", ignore = TRUE)
81  usethis_use_git_ignore(pkg, "/Meta/")
82
83  cli::cli_alert_info("Building vignette index")
84
85  vignette_index <- ("tools" %:::% ".build_vignette_index")(vigns)
86
87  vignette_index_path <- path(pkg$path, "Meta", "vignette.rds")
88
89  saveRDS(vignette_index, vignette_index_path, version = 2L)
90}
91
92#' Clean built vignettes.
93#'
94#' This uses a fairly rudimentary algorithm where any files in \file{doc}
95#' with a name that exists in \file{vignettes} are removed.
96#'
97#' @template devtools
98#' @export
99clean_vignettes <- function(pkg = ".") {
100  pkg <- as.package(pkg)
101  vigns <- tools::pkgVignettes(dir = pkg$path)
102  if (path_file(vigns$dir) != "vignettes") return()
103
104  cli::cli_alert_info("Cleaning built vignettes and index from {.pkg {pkg$package}}")
105
106  doc_path <- path(pkg$path, "doc")
107
108  vig_candidates <- if (dir_exists(doc_path)) dir_ls(doc_path) else character()
109  vig_rm <- vig_candidates[file_name(vig_candidates) %in% file_name(vigns$docs)]
110
111  extra_candidates <- path(doc_path, path_file(find_vignette_extras(pkg)))
112  extra_rm <- extra_candidates[file_exists(extra_candidates)]
113
114  meta_path <- path(pkg$path, "Meta")
115  vig_index_path <- path(meta_path, "vignette.rds")
116  vig_index_rm <- if (file_exists(vig_index_path)) vig_index_path
117
118  to_remove <- c(vig_rm, extra_rm, vig_index_rm)
119  if (length(to_remove) > 0) {
120    cli::cli_alert_warning("Removing {.file {path_file(to_remove)}}")
121    file_delete(to_remove)
122  }
123
124  lapply(c(doc_path, meta_path), dir_delete_if_empty)
125
126  invisible(TRUE)
127}
128
129dir_delete_if_empty <- function(x) {
130  if (dir_exists(x) && rlang::is_empty(dir_ls(x))) {
131    dir_delete(x)
132    cli::cli_alert_warning("Removing {.file {path_file(x)}}")
133  }
134}
135
136file_name <- function(x) {
137  if (length(x) == 0) return(NULL)
138  path_ext_remove(path_file(x))
139}
140