Skip to content

Commit

Permalink
fix: --exclude libfoo.so shall ignore dependencies of libfoo.so (#…
Browse files Browse the repository at this point in the history
…474)

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.
  • Loading branch information
mayeut authored Jan 8, 2024
1 parent 45a8c00 commit 24000c6
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 46 deletions.
9 changes: 8 additions & 1 deletion src/auditwheel/lddtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def lddtree(
prefix: str = "",
ldpaths: dict[str, list[str]] | None = None,
display: str | None = None,
exclude: frozenset[str] = frozenset(),
_first: bool = True,
_all_libs: dict = {},
) -> dict:
Expand All @@ -320,6 +321,8 @@ def lddtree(
will be called.
display
The path to show rather than ``path``
exclude
List of soname (DT_NEEDED) to exclude from the tree
_first
Recursive use only; is this the first ELF?
_all_libs
Expand Down Expand Up @@ -402,7 +405,10 @@ def lddtree(
elif t.entry.d_tag == "DT_RUNPATH":
runpaths = parse_ld_paths(t.runpath, path=path, root=root)
elif t.entry.d_tag == "DT_NEEDED":
libs.append(t.needed)
if t.needed in exclude:
log.info(f"Excluding {t.needed}")
else:
libs.append(t.needed)
if runpaths:
# If both RPATH and RUNPATH are set, only the latter is used.
rpaths = []
Expand Down Expand Up @@ -449,6 +455,7 @@ def lddtree(
prefix,
ldpaths,
display=fullpath,
exclude=exclude,
_first=False,
_all_libs=_all_libs,
)
Expand Down
5 changes: 3 additions & 2 deletions src/auditwheel/main_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def execute(args, p):
from .repair import repair_wheel
from .wheel_abi import NonPlatformWheel, analyze_wheel_abi

exclude = frozenset(args.EXCLUDE)
wheel_policy = WheelPolicies()

for wheel_file in args.WHEEL_FILE:
Expand All @@ -121,7 +122,7 @@ def execute(args, p):
os.makedirs(args.WHEEL_DIR)

try:
wheel_abi = analyze_wheel_abi(wheel_policy, wheel_file)
wheel_abi = analyze_wheel_abi(wheel_policy, wheel_file, exclude)
except NonPlatformWheel:
logger.info(NonPlatformWheel.LOG_MESSAGE)
return 1
Expand Down Expand Up @@ -177,7 +178,7 @@ def execute(args, p):
out_dir=args.WHEEL_DIR,
update_tags=args.UPDATE_TAGS,
patcher=patcher,
exclude=args.EXCLUDE,
exclude=exclude,
strip=args.STRIP,
)

Expand Down
2 changes: 1 addition & 1 deletion src/auditwheel/main_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def execute(args, p):
p.error("cannot access %s. No such file" % args.WHEEL_FILE)

try:
winfo = analyze_wheel_abi(wheel_policy, args.WHEEL_FILE)
winfo = analyze_wheel_abi(wheel_policy, args.WHEEL_FILE, frozenset())
except NonPlatformWheel:
logger.info(NonPlatformWheel.LOG_MESSAGE)
return 1
Expand Down
8 changes: 3 additions & 5 deletions src/auditwheel/repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ def repair_wheel(
out_dir: str,
update_tags: bool,
patcher: ElfPatcher,
exclude: list[str],
exclude: frozenset[str],
strip: bool = False,
) -> str | None:
external_refs_by_fn = get_wheel_elfdata(wheel_policy, wheel_path)[1]
external_refs_by_fn = get_wheel_elfdata(wheel_policy, wheel_path, exclude)[1]

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

if src_path is None:
raise ValueError(
Expand Down
12 changes: 8 additions & 4 deletions src/auditwheel/wheel_abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class NonPlatformWheel(WheelAbiError):


@functools.lru_cache
def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
def get_wheel_elfdata(
wheel_policy: WheelPolicies, wheel_fn: str, exclude: frozenset[str]
):
full_elftree = {}
nonpy_elftree = {}
full_external_refs = {}
Expand Down Expand Up @@ -80,7 +82,7 @@ def get_wheel_elfdata(wheel_policy: WheelPolicies, wheel_fn: str):
# to fail and there's no need to do further checks
if not shared_libraries_in_purelib:
log.debug("processing: %s", fn)
elftree = lddtree(fn)
elftree = lddtree(fn, exclude=exclude)

for key, value in elf_find_versioned_symbols(elf):
log.debug("key %s, value %s", key, value)
Expand Down Expand Up @@ -227,7 +229,9 @@ def get_symbol_policies(
return result


def analyze_wheel_abi(wheel_policy: WheelPolicies, wheel_fn: str) -> WheelAbIInfo:
def analyze_wheel_abi(
wheel_policy: WheelPolicies, wheel_fn: str, exclude: frozenset[str]
) -> WheelAbIInfo:
external_refs = {
p["name"]: {"libs": {}, "blacklist": {}, "priority": p["priority"]}
for p in wheel_policy.policies
Expand All @@ -239,7 +243,7 @@ def analyze_wheel_abi(wheel_policy: WheelPolicies, wheel_fn: str) -> WheelAbIInf
versioned_symbols,
has_ucs2,
uses_PyFPE_jbuf,
) = get_wheel_elfdata(wheel_policy, wheel_fn)
) = get_wheel_elfdata(wheel_policy, wheel_fn, exclude)

for fn in elftree_by_fn.keys():
update(external_refs, external_refs_by_fn[fn])
Expand Down
19 changes: 13 additions & 6 deletions tests/integration/test_bundled_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,29 @@


@pytest.mark.parametrize(
"file, external_libs",
"file, external_libs, exclude",
[
("cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5"}),
("python_snappy-0.5.2-pp260-pypy_41-linux_x86_64.whl", {"libsnappy.so.1"}),
("cffi-1.5.0-cp27-none-linux_x86_64.whl", {"libffi.so.5"}, frozenset()),
("cffi-1.5.0-cp27-none-linux_x86_64.whl", set(), frozenset(["libffi.so.5"])),
(
"python_snappy-0.5.2-pp260-pypy_41-linux_x86_64.whl",
{"libsnappy.so.1"},
frozenset(),
),
],
)
def test_analyze_wheel_abi(file, external_libs):
def test_analyze_wheel_abi(file, external_libs, exclude):
wheel_policies = WheelPolicies(libc=Libc.GLIBC, arch="x86_64")
winfo = analyze_wheel_abi(wheel_policies, str(HERE / file))
winfo = analyze_wheel_abi(wheel_policies, str(HERE / file), exclude)
assert set(winfo.external_refs["manylinux_2_5_x86_64"]["libs"]) == external_libs


def test_analyze_wheel_abi_pyfpe():
wheel_policies = WheelPolicies(libc=Libc.GLIBC, arch="x86_64")
winfo = analyze_wheel_abi(
wheel_policies, str(HERE / "fpewheel-0.0.0-cp35-cp35m-linux_x86_64.whl")
wheel_policies,
str(HERE / "fpewheel-0.0.0-cp35-cp35m-linux_x86_64.whl"),
frozenset(),
)
assert (
winfo.sym_tag == "manylinux_2_5_x86_64"
Expand Down
45 changes: 19 additions & 26 deletions tests/integration/test_manylinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,51 +313,44 @@ def test_build_repair_numpy(
# at once in the same Python program:
docker_exec(docker_python, ["python", "-c", "'import numpy; import foo'"])

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

policy, tag, manylinux_ctr = any_manylinux_container

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

# Exclude libgfortran from grafting into the wheel
excludes = {
"manylinux_2_5_x86_64": ["libgfortran.so.1", "libgfortran.so.3"],
"manylinux_2_12_x86_64": ["libgfortran.so.3", "libgfortran.so.5"],
"manylinux_2_17_x86_64": ["libgfortran.so.3", "libgfortran.so.5"],
"manylinux_2_28_x86_64": ["libgfortran.so.5"],
"musllinux_1_1_x86_64": ["libgfortran.so.5"],
}[policy]

repair_command = [
f"LD_LIBRARY_PATH={test_path}/a:$LD_LIBRARY_PATH",
"auditwheel",
"repair",
"--plat",
policy,
f"--plat={policy}",
"--only-plat",
"-w",
"/io",
"--exclude=liba.so",
f"/io/{orig_wheel}",
]
for exclude in excludes:
repair_command.extend(["--exclude", exclude])
repair_command.append(f"/io/{orig_wheel}")
output = docker_exec(manylinux_ctr, repair_command)

for exclude in excludes:
assert f"Excluding {exclude}" in output
output = docker_exec(manylinux_ctr, ["bash", "-c", " ".join(repair_command)])
assert "Excluding liba.so" in output
filenames = os.listdir(io_folder)
assert len(filenames) == 2
repaired_wheel = f"numpy-{NUMPY_VERSION}-{PYTHON_ABI}-{tag}.whl"
repaired_wheel = f"testrpath-0.0.1-{PYTHON_ABI}-{tag}.whl"
assert repaired_wheel in filenames

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

def test_build_wheel_with_binary_executable(
self, any_manylinux_container, docker_python, io_folder
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_wheel_abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ def test_finds_shared_library_in_purelib(self, filenames, message, monkeypatch):
wheel_policy = WheelPolicies()

with pytest.raises(RuntimeError) as exec_info:
wheel_abi.get_wheel_elfdata(wheel_policy, "/fakepath")
wheel_abi.get_wheel_elfdata(wheel_policy, "/fakepath", frozenset())

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

0 comments on commit 24000c6

Please sign in to comment.