Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
3-manifolds
GitHub Repository: 3-manifolds/Sage_macOS
Path: blob/main/Sage_framework/fix_paths.py
170 views
1
import sys
2
import os
3
import subprocess
4
import re
5
LOCAL_LIB = '/private/var/tmp/sage-X.X-current/local/lib'
6
get_info = re.compile(b'Filetype: (?P<filetype>.*)| *LC_LOAD_DYLIB: (?P<dylib>.*)| *LC_RPATH: (?P<rpath>.*)')
7
get_version = re.compile(r'SageMath version ([0-9]*\.[0-9]*)')
8
9
# Bigendian encoding of the magic numbers for mach-O binaries
10
feedface_big = b'\xfe\xed\xfa\xce'
11
cafebabe_big = b'\xca\xfe\xba\xbe'
12
feedfacf_big = b'\xfe\xed\xfa\xcf'
13
cafebabf_big = b'\xca\xfe\xba\xbf'
14
# Smallendian encodings of the magic number for mach-O binaries
15
feedface = b'\xce\xfa\xed\xfe'
16
cafebabe = b'\xbe\xba\xfe\xca'
17
feedfacf = b'\xcf\xfa\xed\xfe'
18
cafebabf = b'\xbf\xba\xfe\xca'
19
20
magics = (cafebabf, feedfacf, cafebabe_big, feedface_big, cafebabe, feedface, cafebabf_big, feedfacf_big)
21
22
def unique(some_list):
23
return list(dict((x, None) for x in some_list).keys())
24
25
class MachFile:
26
def __init__(self, path):
27
self.path = path
28
nodes = self.path.split(os.path.sep)
29
self.nodes = nodes[nodes.index('local'):]
30
self.local_path = os.path.join(*nodes)
31
self.depth = len(nodes)
32
info = subprocess.run(['macher', 'info', path], capture_output=True).stdout
33
self.filetype = None
34
self.dylibs = []
35
self.rpaths = []
36
for line in info.split(b'\n'):
37
m = get_info.match(line)
38
if m is None:
39
continue
40
filetype = m['filetype']
41
if filetype:
42
self.filetype = filetype.decode('ascii')
43
continue
44
dylib = m['dylib']
45
if dylib:
46
if (not dylib.startswith(b'/usr') and
47
not dylib.startswith(b'/lib') and
48
not dylib.startswith(b'/System')):
49
self.dylibs.append(dylib.decode('ascii'))
50
continue
51
rpath = m['rpath']
52
if rpath:
53
self.rpaths.append(rpath.decode('ascii'))
54
55
def relative_path(self, path):
56
"""
57
Return a relative path from the directory containing this file to
58
the directory containing the file with the given path.
59
"""
60
nodes = path.split(os.path.sep)
61
try:
62
local_nodes = nodes[nodes.index('local'):]
63
except ValueError:
64
return
65
prefix = []
66
index = 0
67
for local_node, node in zip(local_nodes, self.nodes):
68
if local_node == node:
69
index += 1
70
steps = len(self.nodes) - index - 1
71
try:
72
return os.path.join(*(['..']*steps + local_nodes[index:-1]))
73
except TypeError:
74
return ''
75
76
def fixed_rpaths(self):
77
"""
78
Return the list of rpaths to be installed in this file. These
79
should all be relative paths, prefixed with @loader_path or
80
@executable_path, depending on the file type.
81
"""
82
rpaths = unique(self.rpaths)
83
relpaths = []
84
def build_rpath(relpath):
85
if self.filetype == "MH_EXECUTE":
86
prefix = '@executable_path'
87
elif self.filetype in ("MH_DYLIB", "MH_BUNDLE"):
88
prefix = '@loader_path'
89
else:
90
prefix = ''
91
if relpath:
92
return os.path.join(prefix, relpath)
93
else:
94
return prefix
95
for dylib in self.dylibs:
96
if dylib.startswith('/opt'):
97
# Special case for libgfortan on arm64. Simulate the library being
98
# installed in our bundle.
99
installed_path = os.path.join(LOCAL_LIB, os.path.basename(dylib))
100
relpaths.append(self.relative_path(installed_path))
101
elif dylib.startswith('/'):
102
relpaths.append(self.relative_path(dylib))
103
elif dylib.startswith('@rpath'):
104
for rpath in rpaths:
105
if rpath.startswith('@loader_path'):
106
# Already fixed, e.g. _tkinter
107
continue
108
expanded_dylib = dylib.replace('@rpath', rpath)
109
relpaths.append(self.relative_path(expanded_dylib))
110
elif dylib.startswith('@'):
111
continue
112
else:
113
raise RuntimeError('Unrecognized load path %s'%dylib)
114
fixed = [rpath for rpath in self.rpaths if rpath.startswith('@loader_path')]
115
return fixed + unique([build_rpath(relpath) for relpath in relpaths])
116
117
def fix(self):
118
rpaths = self.fixed_rpaths()
119
subprocess.run(['macher', 'clear_rpaths', self.path], capture_output=True)
120
for rpath in rpaths:
121
subprocess.run(['macher', 'add_rpath', rpath, self.path], capture_output=True)
122
for dylib in self.dylibs:
123
if (dylib.startswith('/usr') or dylib.startswith('/lib') or
124
dylib.startswith('@rpath')):
125
continue
126
new_dylib = os.path.join('@rpath', os.path.basename(dylib))
127
subprocess.run(['macher', 'edit_libpath', dylib, new_dylib, self.path])
128
# Stripping more than this breaks the gcc stub library, but probably most executables
129
# and libraries could be stripped to -u -r without causing problems.
130
subprocess.run(['strip', '-x', self.path], capture_output=True)
131
132
class ScriptFile:
133
def __init__(self, repo, symlink, path):
134
self.path = path
135
nodes = self.path.split(os.path.sep)
136
self.sage_dir = os.path.join(repo, 'sage').encode('utf-8')
137
self.symlink = symlink
138
139
def fix(self):
140
try:
141
with open(self.path, 'rb') as infile:
142
shebang = infile.readline()
143
rest = infile.read()
144
tail = shebang.split(b'/local/')[1]
145
new_shebang = b'#!' + os.path.join(self.symlink, b'local', tail)
146
except:
147
new_shebang = shebang
148
with open(self.path, 'wb') as outfile:
149
outfile.write(new_shebang + b'\n')
150
outfile.write(rest.replace(self.sage_dir, self.symlink))
151
152
class ConfigFile:
153
def __init__(self, repo, symlink, path):
154
self.path = path
155
nodes = self.path.split(os.path.sep)
156
self.sage_dir = os.path.join(repo, 'sage').encode('utf-8')
157
self.symlink = symlink
158
159
def fix(self):
160
with open(self.path, 'rb') as infile:
161
contents = infile.read()
162
with open(self.path, 'wb') as outfile:
163
outfile.write(contents.replace(self.sage_dir, self.symlink))
164
165
def mach_check(path):
166
if os.path.islink(path):
167
return False
168
with open(path, 'rb') as inputfile:
169
magic = inputfile.read(4)
170
return magic in magics
171
172
def shebang_check(path):
173
if os.path.islink(path):
174
return False
175
with open(path, 'rb') as inputfile:
176
first = inputfile.read(2)
177
return first == b'#!'
178
179
# These files need to be fixed as ConfigFiles as well.
180
MAKEFILE = 'python3.9/config-3.9-darwin/Makefile'
181
DARWIN_DATA = 'python3.9/_sysconfigdata__darwin_darwin.py'
182
SAGE_CONFIG = 'python3.9/site-packages/sage_conf.py'
183
184
delocated_pkgs = ['PIL', 'zmq']
185
def is_delocated(path):
186
for pkg in delocated_pkgs:
187
if path.find(pkg) > 0:
188
return True
189
return False
190
191
def fix_files(directory):
192
for dirpath, dirnames, filenames in os.walk(directory):
193
for filename in filenames:
194
fullpath = os.path.join(dirpath, filename)
195
if mach_check(fullpath):
196
if not is_delocated(fullpath):
197
MF = MachFile(fullpath)
198
MF.fix()
199
print(fullpath)
200
201
if __name__ == '__main__':
202
try:
203
repo, directory = sys.argv[1], sys.argv[2]
204
except IndexError:
205
print('Usage python3 fixpaths.py repo <directory>')
206
with open(os.path.join(repo, 'sage', 'VERSION.txt')) as input_file:
207
m = get_version.match(input_file.readline())
208
sage_version = m.groups()[0]
209
LOCAL_LIB = LOCAL_LIB.replace('X.X', sage_version)
210
repo = os.path.abspath(repo)
211
fix_files(directory)
212
213