Skip to content

Commit 03ed693

Browse files
committed
fix: --exclude libfoo.so shall ignore dependencies of libfoo.so
When using `--exclude libfoo.so`, dependencies of `libfoo.so` are still being analyzed & grafted. This commit moves the exclusion analysis to `lddtree` and filters `libfoo.so` `DT_NEEDED` entries thus excluding its dependencies from the tree.
1 parent 45a8c00 commit 03ed693

File tree

7 files changed

+47
-41
lines changed

7 files changed

+47
-41
lines changed

src/auditwheel/lddtree.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def lddtree(
300300
prefix: str = "",
301301
ldpaths: dict[str, list[str]] | None = None,
302302
display: str | None = None,
303+
exclude: frozenset[str] = frozenset(),
303304
_first: bool = True,
304305
_all_libs: dict = {},
305306
) -> dict:
@@ -320,6 +321,8 @@ def lddtree(
320321
will be called.
321322
display
322323
The path to show rather than ``path``
324+
exclude
325+
List of soname (DT_NEEDED) to exclude from the tree
323326
_first
324327
Recursive use only; is this the first ELF?
325328
_all_libs
@@ -402,7 +405,10 @@ def lddtree(
402405
elif t.entry.d_tag == "DT_RUNPATH":
403406
runpaths = parse_ld_paths(t.runpath, path=path, root=root)
404407
elif t.entry.d_tag == "DT_NEEDED":
405-
libs.append(t.needed)
408+
if t.needed in exclude:
409+
log.info(f"Excluding {t.needed}")
410+
else:
411+
libs.append(t.needed)
406412
if runpaths:
407413
# If both RPATH and RUNPATH are set, only the latter is used.
408414
rpaths = []
@@ -449,6 +455,7 @@ def lddtree(
449455
prefix,
450456
ldpaths,
451457
display=fullpath,
458+
exclude=exclude,
452459
_first=False,
453460
_all_libs=_all_libs,
454461
)

src/auditwheel/main_repair.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ def execute(args, p):
109109
from .repair import repair_wheel
110110
from .wheel_abi import NonPlatformWheel, analyze_wheel_abi
111111

112+
exclude = frozenset(args.EXCLUDE)
113+
patcher = Patchelf()
112114
wheel_policy = WheelPolicies()
113115

114116
for wheel_file in args.WHEEL_FILE:
@@ -121,7 +123,7 @@ def execute(args, p):
121123
os.makedirs(args.WHEEL_DIR)
122124

123125
try:
124-
wheel_abi = analyze_wheel_abi(wheel_policy, wheel_file)
126+
wheel_abi = analyze_wheel_abi(wheel_policy, wheel_file, exclude)
125127
except NonPlatformWheel:
126128
logger.info(NonPlatformWheel.LOG_MESSAGE)
127129
return 1
@@ -168,7 +170,6 @@ def execute(args, p):
168170
higher_policy = wheel_policy.get_policy_by_name(wheel_abi.overall_tag)
169171
abis = [higher_policy["name"]] + higher_policy["aliases"] + abis
170172

171-
patcher = Patchelf()
172173
out_wheel = repair_wheel(
173174
wheel_policy,
174175
wheel_file,
@@ -177,7 +178,7 @@ def execute(args, p):
177178
out_dir=args.WHEEL_DIR,
178179
update_tags=args.UPDATE_TAGS,
179180
patcher=patcher,
180-
exclude=args.EXCLUDE,
181+
exclude=exclude,
181182
strip=args.STRIP,
182183
)
183184

src/auditwheel/repair.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ def repair_wheel(
3939
out_dir: str,
4040
update_tags: bool,
4141
patcher: ElfPatcher,
42-
exclude: list[str],
42+
exclude: frozenset[str],
4343
strip: bool = False,
4444
) -> str | None:
45-
external_refs_by_fn = get_wheel_elfdata(wheel_policy, wheel_path)[1]
45+
external_refs_by_fn = get_wheel_elfdata(wheel_policy, wheel_path, exclude)[1]
4646

4747
# Do not repair a pure wheel, i.e. has no external refs
4848
if not external_refs_by_fn:
@@ -72,9 +72,7 @@ def repair_wheel(
7272
ext_libs: dict[str, str] = v[abis[0]]["libs"]
7373
replacements: list[tuple[str, str]] = []
7474
for soname, src_path in ext_libs.items():
75-
if soname in exclude:
76-
logger.info(f"Excluding {soname}")
77-
continue
75+
assert soname not in exclude
7876

7977
if src_path is None:
8078
raise ValueError(

src/auditwheel/wheel_abi.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ class NonPlatformWheel(WheelAbiError):
5252

5353

5454
@functools.lru_cache
55-
def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
55+
def get_wheel_elfdata(
56+
wheel_policy: WheelPolicies, wheel_fn: str, exclude: frozenset[str]
57+
):
5658
full_elftree = {}
5759
nonpy_elftree = {}
5860
full_external_refs = {}
@@ -80,7 +82,7 @@ def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
8082
# to fail and there's no need to do further checks
8183
if not shared_libraries_in_purelib:
8284
log.debug("processing: %s", fn)
83-
elftree = lddtree(fn)
85+
elftree = lddtree(fn, exclude=exclude)
8486

8587
for key, value in elf_find_versioned_symbols(elf):
8688
log.debug("key %s, value %s", key, value)
@@ -227,7 +229,9 @@ def get_symbol_policies(
227229
return result
228230

229231

230-
def analyze_wheel_abi(wheel_policy: WheelPolicies, wheel_fn: str) -> WheelAbIInfo:
232+
def analyze_wheel_abi(
233+
wheel_policy: WheelPolicies, wheel_fn: str, exclude: frozenset[str]
234+
) -> WheelAbIInfo:
231235
external_refs = {
232236
p["name"]: {"libs": {}, "blacklist": {}, "priority": p["priority"]}
233237
for p in wheel_policy.policies
@@ -239,7 +243,7 @@ def analyze_wheel_abi(wheel_policy: WheelPolicies, wheel_fn: str) -> WheelAbIInf
239243
versioned_symbols,
240244
has_ucs2,
241245
uses_PyFPE_jbuf,
242-
) = get_wheel_elfdata(wheel_policy, wheel_fn)
246+
) = get_wheel_elfdata(wheel_policy, wheel_fn, exclude)
243247

244248
for fn in elftree_by_fn.keys():
245249
update(external_refs, external_refs_by_fn[fn])

tests/integration/test_bundled_wheels.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@
2929
)
3030
def test_analyze_wheel_abi(file, external_libs):
3131
wheel_policies = WheelPolicies(libc=Libc.GLIBC, arch="x86_64")
32-
winfo = analyze_wheel_abi(wheel_policies, str(HERE / file))
32+
winfo = analyze_wheel_abi(wheel_policies, str(HERE / file), frozenset())
3333
assert set(winfo.external_refs["manylinux_2_5_x86_64"]["libs"]) == external_libs
3434

3535

3636
def test_analyze_wheel_abi_pyfpe():
3737
wheel_policies = WheelPolicies(libc=Libc.GLIBC, arch="x86_64")
3838
winfo = analyze_wheel_abi(
39-
wheel_policies, str(HERE / "fpewheel-0.0.0-cp35-cp35m-linux_x86_64.whl")
39+
wheel_policies,
40+
str(HERE / "fpewheel-0.0.0-cp35-cp35m-linux_x86_64.whl"),
41+
frozenset(),
4042
)
4143
assert (
4244
winfo.sym_tag == "manylinux_2_5_x86_64"

tests/integration/test_manylinux.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -313,51 +313,45 @@ def test_build_repair_numpy(
313313
# at once in the same Python program:
314314
docker_exec(docker_python, ["python", "-c", "'import numpy; import foo'"])
315315

316-
@pytest.mark.skipif(
317-
PLATFORM != "x86_64", reason="Only needs checking on one platform"
318-
)
319316
def test_repair_exclude(self, any_manylinux_container, io_folder):
320317
"""Test the --exclude argument to avoid grafting certain libraries."""
321318

322319
policy, tag, manylinux_ctr = any_manylinux_container
323320

324-
orig_wheel = build_numpy(manylinux_ctr, policy, io_folder)
325-
assert orig_wheel == ORIGINAL_NUMPY_WHEEL
321+
test_path = "/auditwheel_src/tests/integration/testrpath"
322+
build_cmd = (
323+
f"cd {test_path} && "
324+
"if [ -d ./build ]; then rm -rf ./build ./*.egg-info; fi && "
325+
"python setup.py bdist_wheel -d /io"
326+
)
327+
docker_exec(manylinux_ctr, ["bash", "-c", build_cmd])
328+
filenames = os.listdir(io_folder)
329+
assert filenames == [f"testrpath-0.0.1-{PYTHON_ABI}-linux_{PLATFORM}.whl"]
330+
orig_wheel = filenames[0]
326331
assert "manylinux" not in orig_wheel
327332

328-
# Exclude libgfortran from grafting into the wheel
329-
excludes = {
330-
"manylinux_2_5_x86_64": ["libgfortran.so.1", "libgfortran.so.3"],
331-
"manylinux_2_12_x86_64": ["libgfortran.so.3", "libgfortran.so.5"],
332-
"manylinux_2_17_x86_64": ["libgfortran.so.3", "libgfortran.so.5"],
333-
"manylinux_2_28_x86_64": ["libgfortran.so.5"],
334-
"musllinux_1_1_x86_64": ["libgfortran.so.5"],
335-
}[policy]
336-
337333
repair_command = [
334+
"env",
335+
f"LD_LIBRARY_PATH={test_path}/a:$LD_LIBRARY_PATH",
338336
"auditwheel",
339337
"repair",
340-
"--plat",
341-
policy,
338+
f"--plat={policy}",
342339
"--only-plat",
343340
"-w",
344341
"/io",
342+
"--exclude=liba.so",
343+
f"/io/{orig_wheel}",
345344
]
346-
for exclude in excludes:
347-
repair_command.extend(["--exclude", exclude])
348-
repair_command.append(f"/io/{orig_wheel}")
349345
output = docker_exec(manylinux_ctr, repair_command)
350-
351-
for exclude in excludes:
352-
assert f"Excluding {exclude}" in output
346+
assert "Excluding liba.so" in output
353347
filenames = os.listdir(io_folder)
354348
assert len(filenames) == 2
355-
repaired_wheel = f"numpy-{NUMPY_VERSION}-{PYTHON_ABI}-{tag}.whl"
349+
repaired_wheel = f"testrpath-0.0.1-{PYTHON_ABI}-{tag}.whl"
356350
assert repaired_wheel in filenames
357351

358-
# Make sure we don't have libgfortran in the result
352+
# Make sure we don't have liba.so & libb.so in the result
359353
contents = zipfile.ZipFile(os.path.join(io_folder, repaired_wheel)).namelist()
360-
assert not any(x for x in contents if "/libgfortran" in x)
354+
assert not any(x for x in contents if "/liba" in x or "/libb" in x)
361355

362356
def test_build_wheel_with_binary_executable(
363357
self, any_manylinux_container, docker_python, io_folder

tests/unit/test_wheel_abi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ def test_finds_shared_library_in_purelib(self, filenames, message, monkeypatch):
4848
wheel_policy = WheelPolicies()
4949

5050
with pytest.raises(RuntimeError) as exec_info:
51-
wheel_abi.get_wheel_elfdata(wheel_policy, "/fakepath")
51+
wheel_abi.get_wheel_elfdata(wheel_policy, "/fakepath", frozenset())
5252

5353
assert exec_info.value.args == (message,)

0 commit comments

Comments
 (0)