Skip to content

Commit ee0184d

Browse files
committed
2/3 repair_wheel
1 parent 0cfd268 commit ee0184d

File tree

2 files changed

+97
-43
lines changed

2 files changed

+97
-43
lines changed

Diff for: src/auditwheel/pool.py

+58-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import functools
12
from concurrent.futures import Future, ThreadPoolExecutor
23
from pathlib import Path
4+
from time import sleep
35
from typing import Any, Callable, Optional
46

57

8+
def yield_thread() -> None:
9+
sleep(0)
10+
11+
612
class FileTaskExecutor:
713
"""A task executor that manages concurrent file operations with deduplication.
814
@@ -28,20 +34,56 @@ def __init__(self, concurrent: int = 0):
2834
)
2935
self.working_map: dict[Path, Future[Any]] = {}
3036

37+
def submit_chain(
38+
self, path: Path, fn: Callable[..., Any], /, *args: Any, **kwargs: Any
39+
) -> Future[Any]:
40+
return self._submit(path, fn, True, *args, **kwargs)
41+
3142
def submit(
3243
self, path: Path, fn: Callable[..., Any], /, *args: Any, **kwargs: Any
33-
) -> None:
44+
) -> Future[Any]:
45+
return self._submit(path, fn, False, *args, **kwargs)
46+
47+
def _submit(
48+
self,
49+
path: Path,
50+
fn: Callable[..., Any],
51+
chain: bool,
52+
/,
53+
*args: Any,
54+
**kwargs: Any,
55+
) -> Future[Any]:
3456
if not path.is_absolute():
3557
path = path.absolute()
3658

3759
future: Future[Any]
3860
if self.executor is None:
39-
fn(*args, **kwargs)
40-
return
61+
future = Future()
62+
future.set_result(fn(*args, **kwargs))
63+
elif not chain:
64+
assert path not in self.working_map, "path already in working_map"
65+
future = self.executor.submit(fn, *args, **kwargs)
66+
self.working_map[path] = future
67+
else:
68+
current = self.working_map[path]
69+
future = Future()
70+
71+
@functools.wraps(fn)
72+
def new_fn(_current: Future[Any]) -> None:
73+
nonlocal future, current
74+
75+
assert _current == current
76+
77+
self.working_map.pop(path)
78+
self.working_map[path] = future
79+
try:
80+
future.set_result(fn(*args, **kwargs))
81+
except Exception as e:
82+
future.set_exception(e)
83+
84+
current.add_done_callback(new_fn)
4185

42-
assert path not in self.working_map, "path already in working_map"
43-
future = self.executor.submit(fn, *args, **kwargs)
44-
self.working_map[path] = future
86+
return future
4587

4688
def wait(self, path: Optional[Path] = None) -> None:
4789
"""Wait for tasks to complete.
@@ -55,12 +97,16 @@ def wait(self, path: Optional[Path] = None) -> None:
5597
"""
5698
if self.executor is None:
5799
return
58-
if path is None:
59-
for future in self.working_map.values():
60-
future.result()
61-
self.working_map.clear()
62-
elif path in self.working_map:
63-
self.working_map.pop(path).result()
100+
if path is not None:
101+
while True:
102+
yield_thread()
103+
if path not in self.working_map:
104+
return
105+
self.working_map.pop(path).result()
106+
else:
107+
while self.working_map:
108+
path = next(iter(self.working_map))
109+
self.wait(path)
64110

65111
def __contains__(self, fn: Path) -> bool:
66112
return self.executor is not None and fn in self.working_map

Diff for: src/auditwheel/repair.py

+39-31
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
from subprocess import check_call
1515

1616
from auditwheel.patcher import ElfPatcher
17-
from auditwheel.pool import POOL
1817

1918
from .elfutils import elf_read_dt_needed, elf_read_rpaths, is_subdir
2019
from .hashfile import hashfile
2120
from .policy import WheelPolicies, get_replace_platforms
21+
from .pool import POOL
2222
from .wheel_abi import get_wheel_elfdata
2323
from .wheeltools import InWheelCtx, add_platforms
2424

@@ -83,25 +83,32 @@ def repair_wheel(
8383

8484
if not dest_dir.exists():
8585
dest_dir.mkdir()
86-
new_soname, new_path = copylib(src_path, dest_dir, patcher, dry=True)
87-
if new_path not in POOL:
88-
POOL.submit(new_path, copylib, src_path, dest_dir, patcher)
89-
soname_map[soname] = (new_soname, new_path)
90-
replacements.append((soname, new_soname))
86+
new_soname, new_path = get_new_soname(src_path, dest_dir)
87+
if soname not in soname_map:
88+
soname_map[soname] = (new_soname, new_path)
89+
replacements.append((soname, new_soname))
9190

92-
POOL.wait()
91+
POOL.submit(new_path, copylib, src_path, dest_dir, patcher)
9392

9493
if replacements:
95-
patcher.replace_needed(fn, *replacements)
94+
POOL.submit(fn, patcher.replace_needed, fn, *replacements)
9695

9796
if len(ext_libs) > 0:
98-
new_fn = fn
99-
if _path_is_script(fn):
100-
new_fn = _replace_elf_script_with_shim(match.group("name"), fn)
10197

102-
new_rpath = os.path.relpath(dest_dir, new_fn.parent)
103-
new_rpath = os.path.join("$ORIGIN", new_rpath)
104-
append_rpath_within_wheel(new_fn, new_rpath, ctx.name, patcher)
98+
def _patch_fn(fn: Path) -> None:
99+
new_fn = fn
100+
if _path_is_script(fn):
101+
POOL.wait(fn)
102+
new_fn = _replace_elf_script_with_shim(match.group("name"), fn)
103+
104+
new_rpath = os.path.relpath(dest_dir, new_fn.parent)
105+
new_rpath = os.path.join("$ORIGIN", new_rpath)
106+
107+
append_rpath_within_wheel(new_fn, new_rpath, ctx.name, patcher)
108+
109+
POOL.submit_chain(fn, _patch_fn)
110+
111+
POOL.wait()
105112

106113
# we grafted in a bunch of libraries and modified their sonames, but
107114
# they may have internal dependencies (DT_NEEDED) on one another, so
@@ -133,9 +140,21 @@ def strip_symbols(libraries: Iterable[Path]) -> None:
133140
check_call(["strip", "-s", lib])
134141

135142

136-
def copylib(
137-
src_path: Path, dest_dir: Path, patcher: ElfPatcher, dry: bool = False
138-
) -> tuple[str, Path]:
143+
def get_new_soname(src_path: Path, dest_dir: Path) -> tuple[str, Path]:
144+
with open(src_path, "rb") as f:
145+
shorthash = hashfile(f)[:8]
146+
src_name = src_path.name
147+
base, ext = src_name.split(".", 1)
148+
if not base.endswith(f"-{shorthash}"):
149+
new_soname = f"{base}-{shorthash}.{ext}"
150+
else:
151+
new_soname = src_name
152+
153+
dest_path = dest_dir / new_soname
154+
return new_soname, dest_path
155+
156+
157+
def copylib(src_path: Path, dest_dir: Path, patcher: ElfPatcher) -> None:
139158
"""Graft a shared library from the system into the wheel and update the
140159
relevant links.
141160
@@ -148,19 +167,10 @@ def copylib(
148167
# if the library has a RUNPATH/RPATH we clear it and set RPATH to point to
149168
# its new location.
150169

151-
with open(src_path, "rb") as f:
152-
shorthash = hashfile(f)[:8]
170+
new_soname, dest_path = get_new_soname(src_path, dest_dir)
153171

154-
src_name = src_path.name
155-
base, ext = src_name.split(".", 1)
156-
if not base.endswith(f"-{shorthash}"):
157-
new_soname = f"{base}-{shorthash}.{ext}"
158-
else:
159-
new_soname = src_name
160-
161-
dest_path = dest_dir / new_soname
162-
if dry or dest_path.exists():
163-
return new_soname, dest_path
172+
if dest_path.exists():
173+
return
164174

165175
logger.debug("Grafting: %s -> %s", src_path, dest_path)
166176
rpaths = elf_read_rpaths(src_path)
@@ -174,8 +184,6 @@ def copylib(
174184
if any(itertools.chain(rpaths["rpaths"], rpaths["runpaths"])):
175185
patcher.set_rpath(dest_path, "$ORIGIN")
176186

177-
return new_soname, dest_path
178-
179187

180188
def append_rpath_within_wheel(
181189
lib_name: Path, rpath: str, wheel_base_dir: Path, patcher: ElfPatcher

0 commit comments

Comments
 (0)