Skip to content

Commit e331c1c

Browse files
committed
Use delvewheel to bundle dlls for Windows wheels
1 parent ce89b3e commit e331c1c

File tree

1 file changed

+19
-145
lines changed

1 file changed

+19
-145
lines changed

scripts/wheelbuilder/build_wheels.py

Lines changed: 19 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def build_wheels(pip):
134134
env["VIRTUAL_ENV"] = abspath(dirname(dirname(pip)))
135135
print("Building", name, version, "with", script, flush=True)
136136
if sys.platform == "win32":
137-
cmd = [script, version] # Python's subprocess.py does the quoting we need
137+
cmd = [script, version] # Python's subprocess.py does the quoting we need
138138
else:
139139
cmd = f"{shlex.quote(script)} {version}"
140140
subprocess.check_call(cmd, shell=True, env=env)
@@ -146,152 +146,26 @@ def build_wheels(pip):
146146
subprocess.check_call([pip, "wheel", spec])
147147

148148

149-
_warned_dlls = []
150-
151-
152-
def repair_wheels_windows(output_dir, wheels):
153-
import pefile
154-
from machomachomangler.pe import redll
155-
156-
def resolve_dll_src(dll):
157-
# search for dependencies in system directories
158-
dll_search_paths = [
159-
os.environ["WINDIR"],
160-
join(os.environ["WINDIR"], "System32"),
161-
*os.environ["PATH"].split(";"),
162-
]
163-
ignored_dlls = [
164-
# These DLLs are just provided by Windows.
165-
# This list is probably incomplete.
166-
r"advapi32\.dll",
167-
r"advapires32\.dll",
168-
r"atl.*\.dll",
169-
r"comctl32\.dll",
170-
r"comdlg32\.dll",
171-
r"crtdll\.dll",
172-
r"gdi32\.dll",
173-
r"hal.*\.dll",
174-
r"imm32\.dll",
175-
r"iphlpapi\.dll",
176-
r"kernel32\.dll",
177-
r"kernelbase\.dll",
178-
r"msvbvm60\.dll",
179-
r"msvcirt\.dll",
180-
r"msvcrt?.*\.dll",
181-
r"netapi32\.dll",
182-
r"ntdll\.dll",
183-
r"ole32\.dll",
184-
r"pdh\.dll",
185-
r"powrprof\.dll",
186-
r"psapi\.dll",
187-
r"rpcrt4\.dll",
188-
r"sechost\.dll",
189-
r"shell32\.dll",
190-
r"shlwapi\.dll",
191-
r"shscrap\.dll",
192-
r"ucrtbase\.dll",
193-
r"user32\.dll",
194-
r"version\.dll",
195-
r"winmm\.dll",
196-
r"ws2_32\.dll",
197-
# These match DLLs that provide API sets.
198-
# See https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets
199-
r"api-ms-win-.*\.dll",
200-
r"ext-ms-win-.*\.dll",
201-
# These match DLLs that we provide in GraalPy
202-
r"python.*\.dll",
203-
# These are the DLLs typically linked when building with MSVC. See
204-
# https://learn.microsoft.com/en-us/cpp/windows/determining-which-dlls-to-redistribute
205-
# When these are included, the user should install the latest
206-
# redist package from
207-
# https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
208-
# However, https://aka.ms/vs/17/redist.txt lists the libraries
209-
# which can be included in application distributions.
210-
r"concrt.*\.dll",
211-
r"mfc.*\.dll",
212-
r"msvcp.*\.dll",
213-
r"vcamp.*\.dll",
214-
r"vccorlib.*\.dll",
215-
r"vcomp.*\.dll",
216-
r"vcruntime.*\.dll",
217-
]
218-
if not dll:
219-
return
220-
if any(re.match(pat, basename(dll), re.IGNORECASE) for pat in ignored_dlls):
221-
if dll not in _warned_dlls:
222-
print("Not including", dll, flush=True)
223-
_warned_dlls.append(dll)
224-
return
225-
if isabs(dll):
226-
return dll
227-
for search_path in dll_search_paths:
228-
if exists(src := join(search_path, dll)):
229-
return src
230-
231-
def resolve_dll_target(dll, dependent, checksum):
232-
return join(dirname(dependent), f"{checksum}.{basename(dll)}")
233-
234-
def filehash(files):
235-
sha1 = hashlib.sha1()
236-
for file in files:
237-
with open(file, mode="rb") as f:
238-
sha1.update(f.read())
239-
return sha1.hexdigest()[:8]
240-
241-
for whl in wheels:
242-
with TemporaryDirectory() as name:
243-
with zipfile.ZipFile(whl) as f:
244-
f.extractall(name)
245-
246-
# find all pyd files and recursively copy dependencies
247-
dlls = glob(f"{name}/**/*.pyd", recursive=True)
248-
checksum = filehash(dlls)
249-
dependents_to_dependencies = {}
250-
while dlls:
251-
dll = dlls.pop()
252-
with pefile.PE(dll) as pe:
253-
pe_info = pe.dump_dict()
254-
for syms in pe_info.get("Imported symbols", []):
255-
for sym in syms:
256-
if dep_src := resolve_dll_src(sym.get("DLL", b"").decode("utf-8")):
257-
if not exists(dep_tgt := resolve_dll_target(dep_src, dll, checksum)):
258-
print("Including", dep_src, "as", dep_tgt, flush=True)
259-
shutil.copy(dep_src, dep_tgt)
260-
dlls.append(dep_tgt)
261-
dependents_to_dependencies.setdefault(dll, []).append(dep_src)
262-
263-
for dll, dependencies in dependents_to_dependencies.items():
264-
mapping = {}
265-
for dep_src in dependencies:
266-
mapping[basename(dep_src).encode("utf-8")] = basename(
267-
resolve_dll_target(dep_src, dll, checksum)
268-
).encode("utf-8")
269-
with open(dll, mode="rb") as f:
270-
data = f.read()
271-
print(
272-
"Rewriting\n\t",
273-
"\n\t".join([k.decode("utf-8") for k in mapping.keys()]),
274-
"\n\t->\n\t",
275-
"\n\t".join([v.decode("utf-8") for v in mapping.values()]),
276-
"\nin",
277-
dll,
278-
)
279-
data = redll(data, mapping)
280-
with open(dll, mode="wb") as f:
281-
f.write(data)
282-
283-
os.makedirs(output_dir, exist_ok=True)
284-
if exists(whl_tgt := join(output_dir, whl)):
285-
os.unlink(whl_tgt)
286-
shutil.make_archive(whl_tgt, "zip", name)
287-
os.rename(f"{whl_tgt}.zip", whl_tgt)
288-
289-
290149
def repair_wheels():
291150
if sys.platform == "win32":
292-
ensure_installed("machomachomangler")
293-
ensure_installed("pefile")
294-
repair_wheels_windows("wheelhouse", glob("*.whl"))
151+
ensure_installed("delvewheel")
152+
env = os.environ.copy()
153+
env["PYTHONUTF8"] = "1"
154+
subprocess.check_call(
155+
[
156+
sys.executable,
157+
"-m",
158+
"delvewheel",
159+
"repair",
160+
"-v",
161+
"--exclude",
162+
"python-native.dll",
163+
"-w",
164+
"wheelhouse",
165+
*glob("*.whl"),
166+
],
167+
env=env,
168+
)
295169
elif sys.platform == "linux":
296170
ensure_installed("auditwheel")
297171
subprocess.check_call(

0 commit comments

Comments
 (0)