diff --git a/src/auditwheel/elfutils.py b/src/auditwheel/elfutils.py index f375a20c..677fe169 100644 --- a/src/auditwheel/elfutils.py +++ b/src/auditwheel/elfutils.py @@ -117,9 +117,19 @@ def elf_read_rpaths(fn: Path) -> dict[str, list[str]]: for t in section.iter_tags(): if t.entry.d_tag == "DT_RPATH": - result["rpaths"] = parse_ld_paths(t.rpath, root="/", path=str(fn)) + result["rpaths"] = parse_ld_paths( + t.rpath, + root="/", + path=str(fn), + keep_non_exist=True, + ) elif t.entry.d_tag == "DT_RUNPATH": - result["runpaths"] = parse_ld_paths(t.runpath, root="/", path=str(fn)) + result["runpaths"] = parse_ld_paths( + t.runpath, + root="/", + path=str(fn), + keep_non_exist=True, + ) return result diff --git a/src/auditwheel/lddtree.py b/src/auditwheel/lddtree.py index 76fe6836..421bd35b 100644 --- a/src/auditwheel/lddtree.py +++ b/src/auditwheel/lddtree.py @@ -213,7 +213,12 @@ def dedupe(items: list[str]) -> list[str]: return [seen.setdefault(x, x) for x in items if x not in seen] -def parse_ld_paths(str_ldpaths: str, path: str, root: str = "") -> list[str]: +def parse_ld_paths( + str_ldpaths: str, + path: str, + root: str = "", + keep_non_exist: bool = False, # noqa: FBT001, FBT002 +) -> list[str]: """Parse the colon-delimited list of paths and apply ldso rules to each Note the special handling as dictated by the ldso: @@ -229,6 +234,8 @@ def parse_ld_paths(str_ldpaths: str, path: str, root: str = "") -> list[str]: The path to prepend to all paths found path The object actively being parsed (used for $ORIGIN) + keep_non_exist + Do not eliminate non-exist rpath from result Returns ------- @@ -245,7 +252,7 @@ def parse_ld_paths(str_ldpaths: str, path: str, root: str = "") -> list[str]: else: ldpath_ = root + ldpath ldpaths.append(normpath(ldpath_)) - return [p for p in dedupe(ldpaths) if os.path.isdir(p)] + return [p for p in dedupe(ldpaths) if keep_non_exist or os.path.isdir(p)] @functools.lru_cache diff --git a/tests/unit/test_repair.py b/tests/unit/test_repair.py index 2a0df9d4..bfef1112 100644 --- a/tests/unit/test_repair.py +++ b/tests/unit/test_repair.py @@ -4,7 +4,7 @@ from unittest.mock import call, patch from auditwheel.patcher import Patchelf -from auditwheel.repair import append_rpath_within_wheel +from auditwheel.repair import append_rpath_within_wheel, copylib @patch("auditwheel.patcher._verify_patchelf") @@ -115,3 +115,27 @@ def test_append_rpath_ignore_relative(self, check_call, check_output, _): # noq assert check_output.call_args_list == check_output_expected_args assert check_call.call_args_list == check_call_expected_args + + +@patch("auditwheel.patcher._verify_patchelf") +@patch("auditwheel.patcher.check_output") +@patch("auditwheel.patcher.check_call") +class TestCopylib: + @patch("auditwheel.repair.elf_read_rpaths") + def test_nonexistent_rpath(self, elf_read_rpaths, check_call, _0, _1, tmp_path): # noqa: PT019 + # When a library has a non-existent runpath, copylib should still + # call set_rpath to replace it with $ORIGIN + patcher = Patchelf() + src_path = tmp_path / "b.so" + src_path.write_bytes(b"\x00") + dest_dir = tmp_path / "dest" + dest_dir.mkdir() + elf_read_rpaths.return_value = {"rpaths": [], "runpaths": ["/nonexistent/path"]} + + copylib(src_path, dest_dir, patcher) + + elf_read_rpaths.assert_called_once_with(src_path) + dest_path = next(dest_dir.iterdir()) + check_call.assert_any_call( + ["patchelf", "--force-rpath", "--set-rpath", "$ORIGIN", dest_path], + )