Fix relocating MachO binary, when store projection changes (#46840)

* Remove "modify_object_macholib"

According to documentation, this function is used when installing
Mach-O binaries on linux. The implementation seems questionable at
least, and the code seems to be never hit (Spack currently doesn't
support installing Mach-O binaries on linux).

* Fix relocation on macOS, when store projection changes

---------

Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
This commit is contained in:
Massimiliano Culpo 2024-10-08 13:32:28 +02:00 committed by GitHub
parent d7643d4f88
commit d70e9e131d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 49 deletions

View File

@ -205,23 +205,33 @@ def macho_find_paths(orig_rpaths, deps, idpath, old_layout_root, prefix_to_prefi
paths_to_paths dictionary which maps all of the old paths to new paths
"""
paths_to_paths = dict()
# Sort from longest path to shortest, to ensure we try /foo/bar/baz before /foo/bar
prefix_iteration_order = sorted(prefix_to_prefix, key=len, reverse=True)
for orig_rpath in orig_rpaths:
if orig_rpath.startswith(old_layout_root):
for old_prefix, new_prefix in prefix_to_prefix.items():
for old_prefix in prefix_iteration_order:
new_prefix = prefix_to_prefix[old_prefix]
if orig_rpath.startswith(old_prefix):
new_rpath = re.sub(re.escape(old_prefix), new_prefix, orig_rpath)
paths_to_paths[orig_rpath] = new_rpath
break
else:
paths_to_paths[orig_rpath] = orig_rpath
if idpath:
for old_prefix, new_prefix in prefix_to_prefix.items():
for old_prefix in prefix_iteration_order:
new_prefix = prefix_to_prefix[old_prefix]
if idpath.startswith(old_prefix):
paths_to_paths[idpath] = re.sub(re.escape(old_prefix), new_prefix, idpath)
break
for dep in deps:
for old_prefix, new_prefix in prefix_to_prefix.items():
for old_prefix in prefix_iteration_order:
new_prefix = prefix_to_prefix[old_prefix]
if dep.startswith(old_prefix):
paths_to_paths[dep] = re.sub(re.escape(old_prefix), new_prefix, dep)
break
if dep.startswith("@"):
paths_to_paths[dep] = dep
@ -270,36 +280,6 @@ def modify_macho_object(cur_path, rpaths, deps, idpath, paths_to_paths):
install_name_tool = executable.Executable("install_name_tool")
install_name_tool(*args)
return
def modify_object_macholib(cur_path, paths_to_paths):
"""
This function is used when install machO buildcaches on linux by
rewriting mach-o loader commands for dependency library paths of
mach-o binaries and the id path for mach-o libraries.
Rewritting of rpaths is handled by replace_prefix_bin.
Inputs
mach-o binary to be modified
dictionary mapping paths in old install layout to new install layout
"""
dll = macholib.MachO.MachO(cur_path)
dll.rewriteLoadCommands(paths_to_paths.get)
try:
f = open(dll.filename, "rb+")
for header in dll.headers:
f.seek(0)
dll.write(f)
f.seek(0, 2)
f.flush()
f.close()
except Exception:
pass
return
def macholib_get_paths(cur_path):
"""Get rpaths, dependent libraries, and library id of mach-o objects."""
@ -415,10 +395,7 @@ def relocate_macho_binaries(
# normalized paths
rel_to_orig = macho_make_paths_normal(orig_path_name, rpaths, deps, idpath)
# replace the relativized paths with normalized paths
if sys.platform == "darwin":
modify_macho_object(path_name, rpaths, deps, idpath, rel_to_orig)
else:
modify_object_macholib(path_name, rel_to_orig)
# get the normalized paths in the mach-o binary
rpaths, deps, idpath = macholib_get_paths(path_name)
# get the mapping of paths in old prefix to path in new prefix
@ -426,10 +403,7 @@ def relocate_macho_binaries(
rpaths, deps, idpath, old_layout_root, prefix_to_prefix
)
# replace the old paths with new paths
if sys.platform == "darwin":
modify_macho_object(path_name, rpaths, deps, idpath, paths_to_paths)
else:
modify_object_macholib(path_name, paths_to_paths)
# get the new normalized path in the mach-o binary
rpaths, deps, idpath = macholib_get_paths(path_name)
# get the mapping of paths to relative paths in the new prefix
@ -437,10 +411,7 @@ def relocate_macho_binaries(
path_name, new_layout_root, rpaths, deps, idpath
)
# replace the new paths with relativized paths in the new prefix
if sys.platform == "darwin":
modify_macho_object(path_name, rpaths, deps, idpath, paths_to_paths)
else:
modify_object_macholib(path_name, paths_to_paths)
else:
# get the paths in the old prefix
rpaths, deps, idpath = macholib_get_paths(path_name)
@ -449,10 +420,7 @@ def relocate_macho_binaries(
rpaths, deps, idpath, old_layout_root, prefix_to_prefix
)
# replace the old paths with new paths
if sys.platform == "darwin":
modify_macho_object(path_name, rpaths, deps, idpath, paths_to_paths)
else:
modify_object_macholib(path_name, paths_to_paths)
def _transform_rpaths(orig_rpaths, orig_root, new_prefixes):

View File

@ -549,3 +549,35 @@ def test_fetch_external_package_is_noop(default_mock_concretization, fetching_no
spec.external_path = "/some/where"
assert spec.external
spec.package.do_fetch()
@pytest.mark.parametrize(
"relocation_dict",
[
{"/foo/bar/baz": "/a/b/c", "/foo/bar": "/a/b"},
# Ensure correctness does not depend on the ordering of the dict
{"/foo/bar": "/a/b", "/foo/bar/baz": "/a/b/c"},
],
)
def test_macho_relocation_with_changing_projection(relocation_dict):
"""Tests that prefix relocation is computed correctly when the prefixes to be relocated
contain a directory and its subdirectories.
This happens when relocating to a new place AND changing the store projection. In that case we
might have a relocation dict like:
/foo/bar/baz/ -> /a/b/c
/foo/bar -> /a/b
What we need to check is that we don't end up in situations where we relocate to a mixture of
the two schemes, like /a/b/baz.
"""
original_rpath = "/foo/bar/baz/abcdef"
result = macho_find_paths(
[original_rpath],
deps=[],
idpath=None,
old_layout_root="/foo",
prefix_to_prefix=relocation_dict,
)
assert result[original_rpath] == "/a/b/c/abcdef"