1
votes

Context

I'm developing (for the first time) an R package using Rcpp which implements an interface to another program (maxima). This package defines a C++ class, whose constructor needs to retrieve the path to an initialization script that gets installed with the package (the in-package path is inst/extdata/maxima-init.mac. The path to this script is then used as a parameter to spawn a child process that runs the program.

In order to retrieve the path to the installed initialization script I'm calling the R function base::system.file from within the C++ class constructor definition:

...
Environment env("package:base");
Function f = env["system.file"];
fs::path p(Rcpp::as<std::string>(f("extdata", "maxima-init.mac", Named("package") = "rmaxima")));

std::string utilsDir = p.parent_path().string();

...

# spawn child process using path in utilsDir

My R/zzz.R creates an object of that class when the packages gets attached:

loadModule("Maxima", TRUE)

 .onAttach <- function(libname, pkgname) {
    "package:base" %in% search()
    maxima <<- new(RMaxima)
}

The Problem

I can install.packages(rmaxima) and library(rmaxima) just fine and the package works as expected. I now want to increase my development efficiency by using devtools::load_all() to avoid having to R CMD build rmaxima, install.packages(rmaxima) and library(rmaxima) each time I want to test changes. However, when calling devtools::load_all() (or similarily devtools::test() (working directory set to package root) my implementation freezes, because the variable utilsDir is empty and therefore the process launching does not return (I guess it keeps waiting for a valid path). I eventually need to manually kill the process. The same thing happens without setting .onAttach()

Apparently devtools::load_all() does not resemble R's default search path on restart. What can I do? Is this the problem or am I missing something else?

Update

I just came across the following notion of in the devtools::load_all() R documentation file which could be a tip in the right direction

Shim files:

‘load_all’ also inserts shim functions into the imports environment of the loaded package. It presently adds a replacement version of ‘system.file’ which returns different paths from ‘base::system.file’. This is needed because installed and uninstalled package sources have different directory structures. Note that this is not a perfect replacement for base::system.file.

Also I realized, that devtools::load_all() only temporarily installs my package into, but somehow doesn't the files from my inst/

rcst@Velveeta:~$ ls -1R /tmp/RtmpdnvOQg/devtools_install_ee1e82c780/rmaxima/
/tmp/RtmpdnvOQg/devtools_install_ee1e82c780/rmaxima/:
DESCRIPTION
libs
Meta
NAMESPACE

/tmp/RtmpdnvOQg/devtools_install_ee1e82c780/rmaxima/libs:
rmaxima.so

/tmp/RtmpdnvOQg/devtools_install_ee1e82c780/rmaxima/Meta:
features.rds
package.rds
1
devtools::load_all() is not something documented, referenced or suggested by the Rcpp documentation. We have been providing documentation for over a dozen years, and it seems to have help spawn over 2200 CRAN packages. Maybe you can find something suitable in our documentation as e.g. the ten vignettes or the Rcpp Gallery. I generally find that decomposing aggregation helpers into base commands such as R CMD INSTALL ... helps me. As an aside, I think we either have or had a package interfacing Maxima on CRAN, and of course have hundreds interfacing other libraries. - Dirk Eddelbuettel
I do recall another project but I am not finding it right now. An older one is here: github.com/skranz/RMaxima - Dirk Eddelbuettel
Thank you @DirkEddelbuettel, you are right, the Rcpp tag is misleading in this post, I'm sorry. While working on this package, I have been learned from other packages, I'm aware of Rcpp Gallery and have studies the Rcpp-vignettes, but the latter in this regard, as you said, hasn't helped me with this particular problem. I'd like to understand why my implementation does not comply with load_all(). I thought that it may be due to the way packages are attached during load_all(). - rcst
@DirkEddelbuettel the reason for this package is that I couldn't find an R interface to maxima. In fact I exchanged with Sebastian Kranz (RMaxima) about his approach, but it's been discontinued and didn't use process pipe interfacing, but reading/ writing files to mange the exchange. My motivation for doing this is actually to leverage maxima's latex/ mathml displaying capabilities for setting up a knitr engine to directly perform symbolic calculation in RMarkdown code chunks and nicely print it's results. I'm not aware of anything similar to that (besides sympy, less capable and not R). - rcst
I am with you on " I now want to increase my development efficiency" but I deal with that differently. One tool is ccache to accelerate compilation (a few blog posts or answers here), another is options to R CMD INSTALL (add `--help). Your mileage, as they say, may vary. - Dirk Eddelbuettel

1 Answers

0
votes

As it turns out devtools provides a solution to exactly this problem.

In short: calling system.file (i.e. from the global environitment and having the devtools package attached) solves the issue. Specifically the modification:

// Environment env("package:base");
// Function f = env["system.file"];
Function f("system.file");
fs::path p(Rcpp::as<std::string>(f("extdata", "maxima-init.mac", Named("package") = "rmaxima")));

std::string utilsDir = p.parent_path().string();

Explanation

base::system.file(..., mustWork = FALSE) returns an empty string if no match is found. devtools::load_all() temporarily installs the packages inside /tmp/ (on my linux machine). The directory structure of the temporary installation differs from the one of the regular installation, i.e. the one created by install.packages(). In my case, most notably, devtools::load_all() does not copy the inst/ directory, which contains the initialization file.

Now, calling base::system.file("maxima-init.mac", package="rmaxima", mustWork=FALSE) naturally fails, since it searches inside the temporary installation. Having devtools attached masks system.file() with devtools::system.file(), which as mentioned above is "... meant to intercept calls to base::sysem.file() " and behaves differently from base::system.file(). Practically, I think this means, that it will search for the package's source directory instead of the temporary installation.

This way, simply calling system.file() from the global environment calls the right function, either from devtools or base, for either the development or user version of the package automatically.

Nonetheless, using ccache additionally (thanks @dirk) substantially speeds up my development workflow.