Skip to content

Commit eb70399

Browse files
authored
feat: import RBlas detection in macOS (#518)
1 parent 991944a commit eb70399

File tree

3 files changed

+113
-59
lines changed

3 files changed

+113
-59
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"rchitect>=0.4.8,<0.5.0",
1616
"prompt_toolkit>=3.0.41,<3.1",
1717
"pygments>=2.5.0",
18+
"lief>=0.16; sys_platform == 'darwin'",
1819
]
1920

2021
[project.urls]

radian/app.py

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ def main(cleanup=None):
77
import optparse
88
import os
99
import sys
10-
import subprocess
1110
from rchitect.utils import Rhome, rversion
1211
from radian import __version__
12+
from .dyld import should_set_ld_library_path, set_ld_library_path, reset_dyld_insert_blas_dylib
1313

1414
try:
1515
# failed to import jedi on demand in some edge cases.
@@ -167,66 +167,16 @@ def main(cleanup=None):
167167

168168
if not r_home:
169169
raise RuntimeError("Cannot find R binary. Expose it via the `PATH` variable.")
170+
170171

172+
if sys.platform == "darwin":
173+
# avoid libRBlas to propagate downstream
174+
reset_dyld_insert_blas_dylib()
175+
176+
# setup proper dynamic libraries
171177
if not sys.platform.startswith("win"):
172-
lib_path = os.path.join(r_home, "lib")
173-
ldpaths = os.path.join(r_home, "etc", "ldpaths")
174-
175-
if sys.platform == "darwin":
176-
libr_blas_dylib = os.path.join(lib_path, "libRblas.dylib")
177-
# avoid libRBlas to propagate downstream
178-
if "DYLD_INSERT_LIBRARIES" in os.environ:
179-
libs = [
180-
lib
181-
for lib in os.environ["DYLD_INSERT_LIBRARIES"].split(":")
182-
if lib != libr_blas_dylib
183-
]
184-
if libs:
185-
os.environ["DYLD_INSERT_LIBRARIES"] = ":".join(libs)
186-
else:
187-
del os.environ["DYLD_INSERT_LIBRARIES"]
188-
189-
if (
190-
"R_LD_LIBRARY_PATH" not in os.environ
191-
or lib_path not in os.environ["R_LD_LIBRARY_PATH"]
192-
):
193-
if os.path.exists(ldpaths):
194-
R_LD_LIBRARY_PATH = (
195-
subprocess.check_output(
196-
'. "{}"; echo $R_LD_LIBRARY_PATH'.format(ldpaths),
197-
shell=True,
198-
)
199-
.decode("utf-8")
200-
.strip()
201-
)
202-
elif "R_LD_LIBRARY_PATH" in os.environ:
203-
R_LD_LIBRARY_PATH = os.environ["R_LD_LIBRARY_PATH"]
204-
else:
205-
R_LD_LIBRARY_PATH = lib_path
206-
if lib_path not in R_LD_LIBRARY_PATH:
207-
R_LD_LIBRARY_PATH = "{}:{}".format(lib_path, R_LD_LIBRARY_PATH)
208-
os.environ["R_LD_LIBRARY_PATH"] = R_LD_LIBRARY_PATH
209-
# respect R_ARCH variable?
210-
if sys.platform == "darwin":
211-
ld_library_var = "DYLD_FALLBACK_LIBRARY_PATH"
212-
else:
213-
ld_library_var = "LD_LIBRARY_PATH"
214-
if ld_library_var in os.environ:
215-
LD_LIBRARY_PATH = "{}:{}".format(
216-
R_LD_LIBRARY_PATH, os.environ[ld_library_var]
217-
)
218-
else:
219-
LD_LIBRARY_PATH = R_LD_LIBRARY_PATH
220-
os.environ[ld_library_var] = LD_LIBRARY_PATH
221-
222-
if sys.platform == "darwin":
223-
if os.path.exists(libr_blas_dylib):
224-
if "DYLD_INSERT_LIBRARIES" not in os.environ:
225-
os.environ["DYLD_INSERT_LIBRARIES"] = libr_blas_dylib
226-
else:
227-
os.environ["DYLD_INSERT_LIBRARIES"] = "{}:{}".format(
228-
os.environ["DYLD_INSERT_LIBRARIES"], libr_blas_dylib
229-
)
178+
if should_set_ld_library_path(r_home):
179+
set_ld_library_path(r_home)
230180

231181
if sys.argv[0].endswith("radian"):
232182
os.execv(sys.argv[0], sys.argv)

radian/dyld.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import os
2+
import sys
3+
4+
import subprocess
5+
6+
7+
def should_set_ld_library_path(r_home):
8+
lib_path = os.path.join(r_home, "lib")
9+
return (
10+
"R_LD_LIBRARY_PATH" not in os.environ
11+
or lib_path not in os.environ["R_LD_LIBRARY_PATH"]
12+
)
13+
14+
15+
def set_ld_library_path(r_home):
16+
# respect R_ARCH variable?
17+
lib_path = os.path.join(r_home, "lib")
18+
ldpaths = os.path.join(r_home, "etc", "ldpaths")
19+
20+
if os.path.exists(ldpaths):
21+
R_LD_LIBRARY_PATH = (
22+
subprocess.check_output(
23+
'. "{}"; echo $R_LD_LIBRARY_PATH'.format(ldpaths),
24+
shell=True,
25+
)
26+
.decode("utf-8")
27+
.strip()
28+
)
29+
elif "R_LD_LIBRARY_PATH" in os.environ:
30+
R_LD_LIBRARY_PATH = os.environ["R_LD_LIBRARY_PATH"]
31+
else:
32+
R_LD_LIBRARY_PATH = lib_path
33+
if lib_path not in R_LD_LIBRARY_PATH:
34+
R_LD_LIBRARY_PATH = "{}:{}".format(lib_path, R_LD_LIBRARY_PATH)
35+
os.environ["R_LD_LIBRARY_PATH"] = R_LD_LIBRARY_PATH
36+
if sys.platform == "darwin":
37+
ld_library_var = "DYLD_FALLBACK_LIBRARY_PATH"
38+
else:
39+
ld_library_var = "LD_LIBRARY_PATH"
40+
if ld_library_var in os.environ:
41+
LD_LIBRARY_PATH = "{}:{}".format(R_LD_LIBRARY_PATH, os.environ[ld_library_var])
42+
else:
43+
LD_LIBRARY_PATH = R_LD_LIBRARY_PATH
44+
os.environ[ld_library_var] = LD_LIBRARY_PATH
45+
46+
if sys.platform == "darwin":
47+
# pythons load a version of Blas, we need to inject RBlas directly
48+
set_dyld_insert_blas_dylib(r_home)
49+
50+
51+
def get_blas_dylib_path(r_home):
52+
if not sys.platform == "darwin":
53+
return None
54+
55+
import lief
56+
57+
lib_path = os.path.join(r_home, "lib")
58+
libr_path = os.path.join(lib_path, "libR.dylib")
59+
if not os.path.exists(libr_path):
60+
return None
61+
62+
try:
63+
lief_res = lief.parse(os.path.realpath(libr_path))
64+
for cmd in lief_res.commands:
65+
if cmd.command == lief.MachO.LoadCommand.TYPE.LOAD_DYLIB and cmd.name.endswith(
66+
"libRblas.dylib"
67+
):
68+
return cmd.name
69+
except Exception:
70+
pass
71+
72+
# best effort
73+
return os.path.join(lib_path, "libRBlas.dylib")
74+
75+
76+
def set_dyld_insert_blas_dylib(r_home):
77+
if not sys.platform == "darwin":
78+
return
79+
libr_blas_dylib = get_blas_dylib_path(r_home)
80+
if not os.path.exists(libr_blas_dylib):
81+
return
82+
83+
if "DYLD_INSERT_LIBRARIES" not in os.environ:
84+
os.environ["DYLD_INSERT_LIBRARIES"] = libr_blas_dylib
85+
else:
86+
os.environ["DYLD_INSERT_LIBRARIES"] = "{}:{}".format(
87+
os.environ["DYLD_INSERT_LIBRARIES"], libr_blas_dylib
88+
)
89+
os.environ["R_DYLD_INSERT_LIBRARIES"] = libr_blas_dylib
90+
91+
92+
def reset_dyld_insert_blas_dylib():
93+
if not sys.platform == "darwin":
94+
return
95+
if "DYLD_INSERT_LIBRARIES" not in os.environ or "R_DYLD_INSERT_LIBRARIES" not in os.environ:
96+
return
97+
98+
lib = os.environ["DYLD_INSERT_LIBRARIES"]
99+
lib = lib.replace(os.environ["R_DYLD_INSERT_LIBRARIES"], "")
100+
if lib == "":
101+
del os.environ["DYLD_INSERT_LIBRARIES"]
102+
103+
del os.environ["R_DYLD_INSERT_LIBRARIES"]

0 commit comments

Comments
 (0)