Luke, Leah
I’m sure that generates some office teasing (-:
The R framework(s) are available via
Thanks for the links, and the general background. Looking at those installer packages I see a structure like this for Apple silicon:
/Library/
Frameworks/
R.framework/
R -> Versions/Current/R
Versions/
Current -> 4.1-arm64
4.1-arm64/
R
and this for Intel:
/Library/
Frameworks/
R.framework/
R -> Versions/Current/R
Versions/
Current -> 4.1
4.1/
R
This is a very weird way to structure a framework. Most framework developers ship a single universal product, with both Intel and Apple silicon binaries merged together. Having said that, a lot of open source build systems have problems creating universal binaries, and so it’s understandable how things panned out this way.
Still, the real challenge is this:
Our focus is on reproducible research, so the rationale here is that
we want to allow users to govern the specific application (R, Stata,
etc.) versions for their project.
macOS supports two styles of dynamic linking:
-
Load-time dynamic linking — You can declare an import via a Mach-O load command. For example, here’s how the Pacifist app imports the Cocoa framework:
% otool -L /Applications/Pacifist.app/Contents/MacOS/Pacifist
…
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa …
…
-
Runtime dynamic linking — You can dynamically load libraries using various APIs (most notably, dlopen
and dlsym
).
The first option is by far the easiest, because the dynamic linker takes care of binding you to all your symbols. However, this option only works if the library exists at a known path. It doesn’t give you any control over the library resolution process.
In contrast, runtime dynamic linking gives you full control over the library resolution process but you have to manually bind yourself to all of your symbols.
If you want to give the user full control over which version of R they use then you have two challenges:
Ideally the user should be able to choose any framework, with any architecture, without having to reconfigure their system. I can, for example, imagine a user with an Apple silicon Mac who wants to use native R for their new projects but requires Intel R for their existing projects. That pretty much torpedoes the idea of importing the R framework via a Mach-O import; you really have to do this all using runtime dynamic linking.
Beyond that, if you want to use the Intel framework when your app is running natively on Apple silicon, you’ll need a second process for that. Using an XPC Service will help with the IPC and the process lifecycle management, but this is still not easy. The main challenge is performance. Looking at RCocoa it seems that you export low-level primitives from the framework. That’s problematic for any IPC setup because IPC takes a cheap operation (a function call) and turns it into an expensive one (an IPC request/response).
My general advice when doing this is to move as far up the abstraction ladder as you can. For example, instead of exposing an increment primitive and calling it 1000 times, you’d implement an add X primitive that you call once. It’s hard for me to say how feasible this is because of my limited understanding of R and how your product interacts with it.
If you’d like to dig into this in more detail, I’m going to recommend that you open a DTS tech support incident so that I can allocate more time to it. It might even involve *gasp* a phone call (-:
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"