devtools::load_all() 副作用:C++-class 构造函数调用 base::system.file 失败/ returns 空字符串

devtools::load_all() side effects: C++-class constructor calling base::system.file fails/ returns empty string

上下文

我正在(第一次)使用 Rcpp 开发一个 R 包,它实现了另一个程序的接口 (maxima)。此包定义了一个 C++ class,其构造函数需要检索与包一起安装的初始化脚本的路径(包内路径为 inst/extdata/maxima-init.mac。此脚本的路径随后用作生成运行该程序的子进程的参数。

为了检索 installed 初始化脚本的路径,我从 C++ class 构造函数定义中调用 R 函数 base::system.file

...
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

我的 R/zzz.R 创建了一个 class 的对象,当包被附加时:

loadModule("Maxima", TRUE)

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

问题

我可以 install.packages(rmaxima)library(rmaxima) 就好了,包按预期工作。 我现在想通过使用 devtools::load_all() 来提高我的开发效率,以避免每次我想测试更改时都必须 R CMD build rmaximainstall.packages(rmaxima)library(rmaxima)。但是,当调用 devtools::load_all()(或类似地 devtools::test()(工作目录设置为包根目录)时,我的实现冻结了,因为变量 utilsDir 为空,因此进程启动不会 return (我猜它一直在等待有效路径)。我最终需要手动终止进程。同样的事情发生在没有设置 .onAttach()

显然 devtools::load_all() 与 R 在重新启动时的默认搜索路径不同。我能做什么?这是问题所在还是我遗漏了什么?

更新

我刚刚在 devtools::load_all() R 文档文件中遇到以下概念,这可能是正确方向的提示

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.

我还意识到,devtools::load_all() 只是暂时将我的包安装到,但我的 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

事实证明 devtools 提供了完全 这个问题的解决方案。

简而言之:调用 system.file(即从全局环境并附加 devtools 包)解决了问题。具体修改:

// 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();

说明

base::system.file(..., mustWork = FALSE) returns 如果未找到匹配项,则为空字符串。 devtools::load_all() 临时安装 /tmp/ 内的包(在我的 linux 机器上)。临时安装的目录结构 不同于常规安装的目录结构 ,即由 install.packages() 创建的目录结构。在我的例子中,最值得注意的是,devtools::load_all() 不会复制包含初始化文件的 inst/ 目录。

现在,调用 base::system.file("maxima-init.mac", package="rmaxima", mustWork=FALSE) 自然会失败,因为它会在临时安装中搜索。将 devtools 附加掩码 system.file()devtools::system.file(),正如上面提到的那样“......意味着拦截对 base::sysem.file() 的调用”并且行为不同于 base::system.file()。实际上,我认为这意味着它将搜索包的源目录而不是临时安装。

这样,只需从全局环境调用 system.file() 即可自动从 devtoolsbase 调用正确的函数,用于包的开发版本或用户版本。

尽管如此,另外使用 ccache(感谢@dirk)大大加快了我的开发工作流程。