Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locate otool and install_name_tool with xcrun #14195

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions conan/tools/apple/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,19 @@ def libtool(self):
"""path to libtool"""
return self.find('libtool')

@property
def otool(self):
"""path to otool"""
return self.find('otool')

@property
def install_name_tool(self):
"""path to install_name_tool"""
return self.find('install_name_tool')

def _get_dylib_install_name(path_to_dylib):
command = "otool -D {}".format(path_to_dylib)

def _get_dylib_install_name(otool, path_to_dylib):
command = f"{otool} -D {path_to_dylib}"
output = iter(check_output_runner(command).splitlines())
# Note: if otool return multiple entries for different architectures
# assume they are the same and pick the first one.
Expand All @@ -194,26 +204,33 @@ def fix_apple_shared_install_name(conanfile):
*install_name_tool* utility available in macOS to set ``@rpath``.
"""

if not is_apple_os(conanfile):
return

xcrun = XCRun(conanfile)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious (and possibly ignorance about how it works). How this XCRun connects to the specific osxcross environment? Via os.sdk setting? I guess that you have tested against your osxcross, and it works, but as it is not covered by full functional tests, I'd like to understand how it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the xcrun has that relative SDK path hardcoded as the default search location when it was compiled, but I'm not totally sure.

On a semi-related note, I do need to explicitly set tools.apple:sdk_path for some CMake logic to work properly - would it be worth it to have the CMakeToolchain tool query XCRun for the SDK path if it's not explicitly configured?

otool = xcrun.otool
install_name_tool = xcrun.install_name_tool
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could just prefix all of the commands with xcrun, e.g. xcrun otool -hv {file}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is good as is, thanks!


def _darwin_is_binary(file, binary_type):
if binary_type not in ("DYLIB", "EXECUTE") or os.path.islink(file) or os.path.isdir(file):
return False
check_file = f"otool -hv {file}"
check_file = f"{otool} -hv {file}"
return binary_type in check_output_runner(check_file)

def _darwin_collect_binaries(folder, binary_type):
return [os.path.join(folder, f) for f in os.listdir(folder) if _darwin_is_binary(os.path.join(folder, f), binary_type)]

def _fix_install_name(dylib_path, new_name):
command = f"install_name_tool {dylib_path} -id {new_name}"
command = f"{install_name_tool} {dylib_path} -id {new_name}"
conanfile.run(command)

def _fix_dep_name(dylib_path, old_name, new_name):
command = f"install_name_tool {dylib_path} -change {old_name} {new_name}"
command = f"{install_name_tool} {dylib_path} -change {old_name} {new_name}"
conanfile.run(command)

def _get_rpath_entries(binary_file):
entries = []
command = "otool -l {}".format(binary_file)
command = f"{otool} -l {binary_file}"
otool_output = check_output_runner(command).splitlines()
for count, text in enumerate(otool_output):
pass
Expand All @@ -223,7 +240,7 @@ def _get_rpath_entries(binary_file):
return entries

def _get_shared_dependencies(binary_file):
command = "otool -L {}".format(binary_file)
command = f"{otool} -L {binary_file}"
all_shared = check_output_runner(command).strip().split(":")[1].strip()
ret = [s.split("(")[0].strip() for s in all_shared.splitlines()]
return ret
Expand All @@ -239,7 +256,7 @@ def _fix_dylib_files(conanfile):
shared_libs = _darwin_collect_binaries(full_folder, "DYLIB")
# fix LC_ID_DYLIB in first pass
for shared_lib in shared_libs:
install_name = _get_dylib_install_name(shared_lib)
install_name = _get_dylib_install_name(otool, shared_lib)
#TODO: we probably only want to fix the install the name if
# it starts with `/`.
rpath_name = f"@rpath/{os.path.basename(install_name)}"
Expand Down Expand Up @@ -282,13 +299,12 @@ def _fix_executables(conanfile, substitutions):
existing_rpaths = _get_rpath_entries(executable)
rpaths_to_add = list(set(rel_paths) - set(existing_rpaths))
for entry in rpaths_to_add:
command = f"install_name_tool {executable} -add_rpath {entry}"
command = f"{install_name_tool} {executable} -add_rpath {entry}"
conanfile.run(command)

if is_apple_os(conanfile):
substitutions = _fix_dylib_files(conanfile)
substitutions = _fix_dylib_files(conanfile)

# Only "fix" executables if dylib files were patched, otherwise
# there is nothing to do.
if substitutions:
_fix_executables(conanfile, substitutions)
# Only "fix" executables if dylib files were patched, otherwise
# there is nothing to do.
if substitutions:
_fix_executables(conanfile, substitutions)
2 changes: 1 addition & 1 deletion conans/test/unittests/tools/apple/test_apple_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ def test_get_dylib_install_name():
for mock_output in (single_arch, universal_binary):
with mock.patch("conan.tools.apple.apple.check_output_runner") as mock_output_runner:
mock_output_runner.return_value = mock_output
install_name = _get_dylib_install_name("/path/to/libwebp.7.dylib")
install_name = _get_dylib_install_name("otool", "/path/to/libwebp.7.dylib")
assert "/absolute/path/lib/libwebp.7.dylib" == install_name