-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdllutils.py
231 lines (195 loc) · 8.82 KB
/
dllutils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# -*- coding: utf-8 -*-
# Copyright (C) 2012 Almar Klein
# This module is distributed under the terms of the (new) BSD License.
""" Various utilities to modify Dynamic Link libraries.
Needed to build the Pyzo distro, and it's possible that this
functionality is needed to fix extension modules after installation in
a Pyzo distro.
This is a mix of utilities for Windows, Mac and Linux.
"""
import os
import stat
import sys
import subprocess
import time
import re
_COMMAND_TO_SEARCH_PATH = []
def get_command_to_set_search_path():
""" Get the command to change the RPATH of executables and dynamic
libraries. Returns None if there is no such command or if it
cannot be found.
"""
# Check if already computed
if _COMMAND_TO_SEARCH_PATH:
return _COMMAND_TO_SEARCH_PATH[0]
# Get name of the utility
# In Pyzo it should be present in 'shared'.
utilCommand = None
if sys.platform.startswith('win'):
return
if sys.platform.startswith('linux'):
utilname = 'patchelf'
if sys.platform.startswith('darwin'):
utilname = 'install_name_tool'
if True:
# Try old Pyzo
utilCommand = os.path.join(sys.prefix, 'shared', utilname)
if not os.path.isfile(utilCommand):
utilCommand = utilname
# Try new Pyzo / anaconda
utilCommand = os.path.join(sys.prefix, 'bin', utilname)
if not os.path.isfile(utilCommand):
utilCommand = utilname
# Test whether it exists
try:
subprocess.check_output(['which', utilCommand])
except Exception:
raise RuntimeError('Could not get command (%s) to set search path.' % utilCommand)
# Store and return
_COMMAND_TO_SEARCH_PATH.append(utilCommand)
return utilCommand
def set_search_path(fname, *args):
""" set_search_path(fname, *args)
For the given library/executable, set the search path to the
relative paths specified in args.
For Linux: The RPATH is the path to search for its dependencies.
http://enchildfone.wordpress.com/2010/03/23/a-description-of-rpath-origin-ld_library_path-and-portable-linux-binaries/
For Mac: We use the @rpath identifier to get similar behavior to
Linux. But each dependency must be specified. To realize this, we
need to check for each dependency whether it is on one of te given
search paths.
For Windows: not supported in any way. Windows searches next to the
library and then in system paths and PATH.
"""
# Prepare
args = [arg.lstrip('/') for arg in args if arg]
args = [arg for arg in args if arg != '.'] # Because we add empty dir anyway
args.append('') # make libs search next to themselves
command = get_command_to_set_search_path()
if sys.platform.startswith('linux'):
# Create search path value
rpath = ':'.join( ['$ORIGIN/'+arg for arg in args] )
# Modify rpath using a call to patchelf utility
cmd = [command, '--set-rpath', rpath, fname]
subprocess.check_call(cmd)
print('Set RPATH for %r' % os.path.basename(fname))
#print('Set RPATH for %r: %r' % (os.path.basename(fname), rpath))
elif sys.platform.startswith('darwin'):
# ensure write permissions
mode = os.stat(fname).st_mode
if not (mode & stat.S_IWUSR):
os.chmod(fname, mode | stat.S_IWUSR)
# let the file itself know its place (simpyl on rpath)
name = os.path.basename(fname)
subprocess.call(('install_name_tool', '-id', '@rpath/'+name, fname))
# find the references: call otool -L on the file
otool = subprocess.Popen(('otool', '-L', fname),
stdout = subprocess.PIPE)
references = otool.stdout.readlines()[1:]
# Replace each reference
rereferencedlibs = []
for reference in references:
# find the actual referenced file name
referencedFile = reference.decode().strip().split()[0]
if referencedFile.startswith('@'):
continue # the referencedFile is already a relative path
# Get lib name
_, name = os.path.split(referencedFile)
if name.lower() == 'python':
name = 'libpython' # Rename Python lib on Mac
# see if we provided the referenced file
potentiallibs = [os.path.join(os.path.dirname(fname), arg, name)
for arg in args]
# if so, change the reference and rpath
if any([os.path.isfile(p) for p in potentiallibs]):
subprocess.call(('install_name_tool', '-change',
referencedFile, '@rpath/'+name, fname))
for arg in args:
mac_add_rpath(fname, '@loader_path/' + arg)
mac_add_rpath(fname, '@executable_path/') # use libpython next to exe
rereferencedlibs.append(name)
if rereferencedlibs:
print('Replaced refs for "%s": %s' %
(os.path.basename(fname), ', '.join(rereferencedlibs)) )
elif sys.platform.startswith('win'):
raise RuntimeError('Windows has no way of setting the search path on a library or exe.')
else:
raise RuntimeError('Do not know how to set search path of library or exe on %s' % sys.platform)
def mac_add_rpath(fname, rpath):
""" mac_add_rpath(fname, rpath)
Set the rpath for a Mac library or executble. If the rpath is already
registered, it is ignored.
"""
cmd = ['install_name_tool', '-add_rpath', rpath, fname]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None:
time.sleep(0.01)
if p.returncode:
msg = p.stdout.read().decode('utf-8')
if 'would duplicate path' in msg:
pass # Ignore t
else:
raise RuntimeError('Could not set rpath: ' + msg)
def remove_CRT_dependencies(dirname, recurse=True):
""" remove_CRT_dependencies(path, recurse=True)
Check all .dll and .pyd files in the given directory (and its
subdirectories if recurse is True), removing the dependency on the
Windows C runtime from the embedded manifest.
"""
dllExt = ['.dll', '.pyd']
for entry in os.listdir(dirname):
p = os.path.join(dirname, entry)
if recurse and os.path.isdir(p):
remove_CRT_dependencies(p, recurse)
elif os.path.isfile(p) and os.path.splitext(p)[1].lower() in dllExt:
remove_CRT_dependency(p)
def remove_CRT_dependency(filename):
""" remove_CRT_dependency(filename)
Modify the embedded manifest of a Windows dll (or pyd file),
such that it no longer depends on the Windows C runtime.
In effect, the dll will fall back to using the C runtime that
the executable depends on (and has loaded in memory).
This function is not necessary for dll's and pyd's that come with
Python, because these are build without the CRT dependencies for a
while. However, some third party packages (e.g. PySide) do have
these dependencies, and they need to be removed in order to work
on a system that does not have the C-runtime installed.
Based on this diff by C. Gohlke:
http://bugs.python.org/file15113/msvc9compiler_stripruntimes_regexp2.diff
See discussion at: http://bugs.python.org/issue4120
"""
if 'QtCore' in filename:
1/0
# Read the whole file
with open(filename, 'rb') as f:
try:
bb = f.read()
except IOError:
#raise IOError('Could not read %s'%filename)
print('Warning: could not read %s'%filename)
return
# Remove assemblyIdentity tag
# This code is different from that in python's distutils/msvc9compiler.py
# by removing re.DOTALL and replaceing the second DOT with "(.|\n|\r)",
# which means that the first DOT cannot contain newlines. Would we not do
# this, the match is too greedy (and causes tk85.dll to break).
pattern = r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
r"""VC\d{2}\.CRT("|')(.|\n|\r)*?(/>|</assemblyIdentity>)"""
pattern = re.compile(pattern.encode('ascii'))
bb, hasMatch = _replacePatternWithSpaces(pattern, bb)
if hasMatch:
# Remove dependentAssembly tag if it's empty
pattern = "<dependentAssembly>\s*</dependentAssembly>".encode('ascii')
bb, hasMatch = _replacePatternWithSpaces(pattern, bb)
# Write back
with open(filename, "wb") as f:
f.write(bb)
print('Removed embedded MSVCR dependency for: %s' % filename)
def _replacePatternWithSpaces(pattern, bb):
match = re.search(pattern, bb)
if match is not None:
L = match.end() - match.start()
bb = re.sub(pattern, b" "*L, bb)
return bb, True
else:
return bb, False