Search code examples
rbuilddevtools

Exclude R files for R folder with .Rbuildignore


There are some files containing work under progress that I'd like to exclude from the entire build process (checking, installing, even documenting if possible), but not loading (devtools::load_all()).

I always name them dev-{feature}.R so I was about to use a wildcard, but it happened that I was not even able to exclude a single file from the build.

For debugging, I created an R file as "R/dev-myfeature.R" containing only the line stop("TEST"). I then ran usethis::use_build_ignore("R/dev-myfeature.R"), which added ^R/dev-myfeature\.R$ in the .Rbuildignore file.

Whether I run devtools::load_all("."), devtools::check() or even devtools::document(), I get the TEST error, so .Rbuildignore was ignored. Since it works fine with all other files, I guess it has no power inside the R folder.

Is there any way around this limitation?

Otherwise, what would be a good workflow for work-in-progress files that should not be part of the build yet?


Solution

  • I think the .Rbuildignore file is useful for bundling of your package, and that's about it. If you want certain functionality in R/*.R files but only on your console when testing, then you may consider structuring the test files with a wrapping if expression.

    For example, I'll the existence and true-ness of a variable named MYDEV to declare that I want to load the contents of the file.

    if (exists("MYDEV") && MYDEV) {
      stop("TEST")
    }
    

    In a default environment (MYDEV not defined), load_all, check, and document do not see the error. The use of exists(...) helps ensure that this will not error (object not found) in a different R instance, either on your computer or elsewhere.

    If you set MYDEV <- TRUE, then all the devtools functions will see the stop and error accordingly. I believe there is no (reasonable) way to get the code sourced in load_all and not with document.

    One problem you have with not erring with devtools::document is that it calls devtools::load_all internally, as such simple tests do not work. One could adapt a function from https://stackoverflow.com/a/62747050/3358272, with:

    search_calling_pkg <- function(pkgs, funcs) {
      # <borrowed from="rlang::trace_back">
      frames <- sys.frames()
      idx <- rlang:::trace_find_bottom(NULL, frames)
      frames <- frames[idx]
      parents <- sys.parents()[idx]
      calls <- as.list(sys.calls()[idx])
      calls <- purrr::map(calls, rlang:::call_fix_car)
      #==# calls <- lapply(calls, rlang:::call_fix_car)
      calls <- rlang:::add_pipe_pointer(calls, frames)
      calls <- purrr::map2(calls, seq_along(calls), rlang:::maybe_add_namespace)
      #==# calls <- Map(rlang:::maybe_add_namespace, calls, seq_along(calls))
      # </borrowed>
      calls_chr <- vapply(calls, function(cl) as.character(cl)[1], character(1))
      ptn <- paste0("^(", paste(pkgs, collapse = "|"), ")::")
      pkgres <- any(grepl(ptn, calls_chr))
      funcres <- !missing(funcs) && length(calls_chr) &&
        any(mapply(grepl, paste0("(^|::)", funcs, "$"), list(calls_chr)))
      return(pkgres && funcres)
    }
    

    This function returns true if any of the package/function pairs are found, and this combination will "see" (error on) stop("TEST") with devtools::load_all but not with devtools::document:

    if (search_calling_pkg("pkgload", "load_all") &&
          !search_calling_pkg("devtools", "document")) {
      stop("TEST")
    }
    

    Yes, this means you need search_calling_pkg somewhere in your environment, whether part of this package or not. Recall that if the .Rbuildignore file includes R/dev-myfeature.R, then package bundles and all down-stream users will never see this code. If you wanted to be even more defensive, you could use

    if (exists("search_calling_pkg") &&
          search_calling_pkg("pkgload", "load_all") &&
          !search_calling_pkg("devtools", "document")) {
      stop("TEST")
    }