From a149f3678deb68edee2328b4bd905ca12d8c0a99 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 18 Sep 2019 17:14:09 -0500 Subject: [PATCH] Expose three code loading strategies Fixes #822 --- NAMESPACE | 3 ++ NEWS.md | 10 ++++++ R/load.R | 81 +++++++++++++++++++++++++++++++++++++++++++++++ R/options.R | 3 +- R/parse.R | 7 +--- R/roxygenize.R | 22 +++++++++---- man/load.Rd | 42 ++++++++++++++++++++++++ man/roxygenize.Rd | 27 ++++++---------- 8 files changed, 163 insertions(+), 32 deletions(-) create mode 100644 R/load.R create mode 100644 man/load.Rd diff --git a/NAMESPACE b/NAMESPACE index 18abeebe0..467da9e81 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -79,7 +79,10 @@ export(env_file) export(env_package) export(is_s3_generic) export(is_s3_method) +export(load_installed) export(load_options) +export(load_pkgload) +export(load_source) export(namespace_roclet) export(object) export(object_format) diff --git a/NEWS.md b/NEWS.md index d21b331a0..d2a45cdd8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,15 @@ # roxygen2 (development version) +* roxygen2 now provides three strategies for loading your code: + + * `load_pkgload()` uses pkgload (now compiling when needed). + * `load_installed()` assumes you have installed the package. + * `load_source()` attaches required packages and `source()`s code in `R/`. + + If the code loading strategy in roxygen2 6.1.0 and above has caused + you grief, you can revert to the old strategy by using + `Roxygen: list(load = "source")` (#822). + * New `@order n` tag controls the order in which blocks are processed. You can use it to override the usual ordering which proceeds in from the top of each file to the bottom. `@order 1` will be processed before `@order 2`, diff --git a/R/load.R b/R/load.R new file mode 100644 index 000000000..0b0e20ee8 --- /dev/null +++ b/R/load.R @@ -0,0 +1,81 @@ +#' Load package code +#' +#' @description +#' roxygen2 is a dynamic documentation system, which means it works with the +#' objects inside your package, not just the source code used to create them. +#' These functions offer various ways of loading your package to suit various +#' constraints: +#' +#' * `load_pkgload()` uses `pkgload::load_all()` to simulate package loading +#' as closely as we know how. It offers high fidelity handling of code that +#' uses S4, but requires that the package be compiled. +#' +#' * `load_source()` simulates package loading by attaching packages listed in +#' `Depends` and `Imports`, then sources all files in the `R/` directory. +#' This was the default strategy used in roxygen2 6.0.0 and earlier; +#' it's primary advantage is that it does not need compilation. +#' +#' * `load_installed()` uses the installed version of the package. Use this +#' strategy if you have installed a development version of the package +#' already. This is the highest fidelity strategy, but requires work +#' outside of roxygen2. +#' +#' You can change the default strategy for your function with roxygen2 `load` +#' option. Override the default off `pkgload` to use the `source` or +#' `installed` strategies: +#' +#' ``` +#' RoxygenNote: list(load = "source") +#' ``` +#' @name load +#' @param path Path to source package +NULL + +#' @rdname load +#' @export +load_pkgload <- function(path) { + pkgload::load_all(path, helpers = FALSE, attach_testthat = FALSE)$env +} + +#' @rdname load +#' @export +load_installed <- function(path) { + package <- desc::desc_get_field("Package", file = path) + asNamespace(package) +} + +#' @rdname load +#' @export +load_source <- function(path) { + r_path <- file.path(path, "R") + if (!file.exists(r_path)) { + abort("Can't find 'R/' directory") + } + + old_dir <- setwd(r_path) + on.exit(setwd(old_dir)) + + # Create environment + env <- new.env(parent = globalenv()) + methods::setPackageName("roxygen_devtest", env) + + # Attach dependencies + deps <- desc::desc_get_deps(path) + pkgs <- deps$package[ + deps$type %in% c("Depends", "Imports") & deps$package != "R" + ] + lapply(pkgs, require, character.only = TRUE) + + # Source files + lapply(package_files(path), sys_source, envir = env) + + env +} + +sys_source <- function(file, envir = baseenv()) { + exprs <- parse(text = read_lines(file)) + for (expr in exprs) { + eval(expr, envir = envir) + } + invisible() +} diff --git a/R/options.R b/R/options.R index e88ddcb2b..a908a8b99 100644 --- a/R/options.R +++ b/R/options.R @@ -18,7 +18,8 @@ load_options <- function(base_path = ".") { wrap = FALSE, roclets = c("collate", "namespace", "rd"), markdown = markdown_global_default, - old_usage = FALSE + old_usage = FALSE, + load = "pkgload" ) unknown_opts <- setdiff(names(opts), names(defaults)) diff --git a/R/parse.R b/R/parse.R index c90852959..5f3eeae8a 100644 --- a/R/parse.R +++ b/R/parse.R @@ -109,14 +109,9 @@ env_file <- function(file) { #' @export #' @rdname parse_package env_package <- function(path) { - pkgload::load_all(path, - compile = FALSE, - helpers = FALSE, - attach_testthat = FALSE - )$env + load_pkgload(path) } - # helpers ----------------------------------------------------------------- order_blocks <- function(blocks) { diff --git a/R/roxygenize.R b/R/roxygenize.R index 6c80ffcb0..8404c8d81 100644 --- a/R/roxygenize.R +++ b/R/roxygenize.R @@ -8,17 +8,18 @@ #' #' Note that roxygen2 is a dynamic documentation system: it works by #' inspecting loaded objects in the package. This means that you must -#' be able to load the package in order to document it. +#' be able to load the package in order to document it: see [load] for +#' details. #' #' @param package.dir Location of package top level directory. Default is #' working directory. #' @param roclets Character vector of roclet names to use with package. -#' This defaults to `NULL`, which will use the `roclets` fields in -#' the list provided in the `Roxygen` DESCRIPTION field. If none are -#' specified, defaults to `c("collate", "namespace", "rd")`. +#' The default, `NULL`, uses the roxygen `roclets` option, +#' which defaults to `c("collate", "namespace", "rd")`. #' @param load_code A function used to load all the R code in the package -#' directory. It is called with the path to the package, and it should return -#' an environment containing all the sourced code. +#' directory. The default, `NULL`, uses the strategy defined by +#' the `code` roxygen option, which defaults to [load_pkgload()]. +#' See [load] for more details. #' @param clean If `TRUE`, roxygen will delete all files previously #' created by roxygen before running each roclet. #' @return `NULL` @@ -26,7 +27,7 @@ #' @importFrom stats setNames roxygenize <- function(package.dir = ".", roclets = NULL, - load_code = env_package, + load_code = NULL, clean = FALSE) { base_path <- normalizePath(package.dir) @@ -72,6 +73,13 @@ roxygenize <- function(package.dir = ".", ) # Now load code + load_code <- load_code %||% switch(options$load, + pkgload = load_pkgload, + source = load_source, + installed = load_installed, + abort("Unknown value of `load` option") + ) + env <- load_code(base_path) blocks <- lapply(blocks, block_set_env, env = env, diff --git a/man/load.Rd b/man/load.Rd new file mode 100644 index 000000000..2dafbbc27 --- /dev/null +++ b/man/load.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/load.R +\name{load} +\alias{load} +\alias{load_pkgload} +\alias{load_installed} +\alias{load_source} +\title{Load package code} +\usage{ +load_pkgload(path) + +load_installed(path) + +load_source(path) +} +\arguments{ +\item{path}{Path to source package} +} +\description{ +roxygen2 is a dynamic documentation system, which means it works with the +objects inside your package, not just the source code used to create them. +These functions offer various ways of loading your package to suit various +constraints: +\itemize{ +\item \code{load_pkgload()} uses \code{pkgload::load_all()} to simulate package loading +as closely as we know how. It offers high fidelity handling of code that +uses S4, but requires that the package be compiled. +\item \code{load_source()} simulates package loading by attaching packages listed in +\code{Depends} and \code{Imports}, then sources all files in the \verb{R/} directory. +This was the default strategy used in roxygen2 6.0.0 and earlier; +it's primary advantage is that it does not need compilation. +\item \code{load_installed()} uses the installed version of the package. Use this +strategy if you have installed a development version of the package +already. This is the highest fidelity strategy, but requires work +outside of roxygen2. +} + +You can change the default strategy for your function with roxygen2 \code{load} +option. Override the default off \code{pkgload} to use the \code{source} or +\code{installed} strategies:\preformatted{RoxygenNote: list(load = "source") +} +} diff --git a/man/roxygenize.Rd b/man/roxygenize.Rd index e2cfceac6..d022378ff 100644 --- a/man/roxygenize.Rd +++ b/man/roxygenize.Rd @@ -5,32 +5,22 @@ \alias{roxygenise} \title{Process a package with the Rd, namespace and collate roclets.} \usage{ -roxygenize( - package.dir = ".", - roclets = NULL, - load_code = env_package, - clean = FALSE -) +roxygenize(package.dir = ".", roclets = NULL, load_code = NULL, clean = FALSE) -roxygenise( - package.dir = ".", - roclets = NULL, - load_code = env_package, - clean = FALSE -) +roxygenise(package.dir = ".", roclets = NULL, load_code = NULL, clean = FALSE) } \arguments{ \item{package.dir}{Location of package top level directory. Default is working directory.} \item{roclets}{Character vector of roclet names to use with package. -This defaults to \code{NULL}, which will use the \code{roclets} fields in -the list provided in the \code{Roxygen} DESCRIPTION field. If none are -specified, defaults to \code{c("collate", "namespace", "rd")}.} +The default, \code{NULL}, uses the roxygen \code{roclets} option, +which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package -directory. It is called with the path to the package, and it should return -an environment containing all the sourced code.} +directory. The default, \code{NULL}, uses the strategy defined by +the \code{code} roxygen option, which defaults to \code{\link[=load_pkgload]{load_pkgload()}}. +See \link{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously created by roxygen before running each roclet.} @@ -48,5 +38,6 @@ for more details. \details{ Note that roxygen2 is a dynamic documentation system: it works by inspecting loaded objects in the package. This means that you must -be able to load the package in order to document it. +be able to load the package in order to document it: see \link{load} for +details. }