Skip to content

Commit a5c339b

Browse files
committed
feat: import RBlas detection in macOS
1 parent 991944a commit a5c339b

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: 4 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
1313

1414
try:
1515
# failed to import jedi on demand in some edge cases.
@@ -168,65 +168,10 @@ def main(cleanup=None):
168168
if not r_home:
169169
raise RuntimeError("Cannot find R binary. Expose it via the `PATH` variable.")
170170

171+
# setup proper dynamic libraries
171172
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-
)
173+
if should_set_ld_library_path(r_home):
174+
set_ld_library_path(r_home)
230175

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

radian/dyld.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 sys.platform == "darwin":
21+
libr_blas_dylib = get_blas_dylib_path(r_home)
22+
# avoid libRBlas to propagate downstream
23+
dyld_remove_library(libr_blas_dylib)
24+
25+
if os.path.exists(ldpaths):
26+
R_LD_LIBRARY_PATH = (
27+
subprocess.check_output(
28+
'. "{}"; echo $R_LD_LIBRARY_PATH'.format(ldpaths),
29+
shell=True,
30+
)
31+
.decode("utf-8")
32+
.strip()
33+
)
34+
elif "R_LD_LIBRARY_PATH" in os.environ:
35+
R_LD_LIBRARY_PATH = os.environ["R_LD_LIBRARY_PATH"]
36+
else:
37+
R_LD_LIBRARY_PATH = lib_path
38+
if lib_path not in R_LD_LIBRARY_PATH:
39+
R_LD_LIBRARY_PATH = "{}:{}".format(lib_path, R_LD_LIBRARY_PATH)
40+
os.environ["R_LD_LIBRARY_PATH"] = R_LD_LIBRARY_PATH
41+
if sys.platform == "darwin":
42+
ld_library_var = "DYLD_FALLBACK_LIBRARY_PATH"
43+
else:
44+
ld_library_var = "LD_LIBRARY_PATH"
45+
if ld_library_var in os.environ:
46+
LD_LIBRARY_PATH = "{}:{}".format(R_LD_LIBRARY_PATH, os.environ[ld_library_var])
47+
else:
48+
LD_LIBRARY_PATH = R_LD_LIBRARY_PATH
49+
os.environ[ld_library_var] = LD_LIBRARY_PATH
50+
51+
if sys.platform == "darwin":
52+
# pythons load a version of Blas, we need to inject RBlas directly
53+
dyld_insert_library(libr_blas_dylib)
54+
55+
56+
def get_blas_dylib_path(r_home):
57+
if not sys.platform == "darwin":
58+
return None
59+
60+
import lief
61+
62+
lib_path = os.path.join(r_home, "lib")
63+
libr_path = os.path.join(lib_path, "libR.dylib")
64+
if not os.path.exists(libr_path):
65+
return None
66+
67+
# return os.path.join(os.path.dirname(os.path.realpath(libr_path)), "libRBlas.dylib")
68+
lief_res = lief.parse(os.path.realpath(libr_path))
69+
for cmd in lief_res.commands:
70+
if cmd.command == lief.MachO.LoadCommand.TYPE.LOAD_DYLIB and cmd.name.endswith(
71+
"libRblas.dylib"
72+
):
73+
return cmd.name
74+
75+
# best effort
76+
return os.path.join(lib_path, "libRBlas.dylib")
77+
78+
79+
def dyld_insert_library(lib):
80+
if not sys.platform == "darwin":
81+
return
82+
if not os.path.exists(lib):
83+
return
84+
if "R_DYLD_INSERT_LIBRARIES" in os.environ:
85+
del os.environ["R_DYLD_INSERT_LIBRARIES"]
86+
return
87+
if "DYLD_INSERT_LIBRARIES" not in os.environ:
88+
os.environ["DYLD_INSERT_LIBRARIES"] = lib
89+
else:
90+
os.environ["DYLD_INSERT_LIBRARIES"] = "{}:{}".format(
91+
os.environ["DYLD_INSERT_LIBRARIES"], lib
92+
)
93+
94+
95+
def dyld_remove_library(lib):
96+
if not sys.platform == "darwin":
97+
return
98+
if "DYLD_INSERT_LIBRARIES" not in os.environ:
99+
return
100+
libs = [
101+
path for path in os.environ["DYLD_INSERT_LIBRARIES"].split(":") if lib != path
102+
]
103+
if libs:
104+
os.environ["DYLD_INSERT_LIBRARIES"] = ":".join(libs)
105+
else:
106+
del os.environ["DYLD_INSERT_LIBRARIES"]
107+
108+
os.environ["R_DYLD_INSERT_LIBRARIES"] = "false"

0 commit comments

Comments
 (0)