Repository: conan-io/conan
Pull request: Skip subdirectories in fix_apple_shared_install_name
Changelog: Bugfix: Fix issue in `fix_apple_shared_install_name` when libdirs or bindirs have subfolders.
Docs: Omit

Close: https://github.com/conan-io/conan/issues/12727

Co-authored-by: Rubén Rincón Blanco



Problem statement:
[bug] `fix_apple_shared_install_name()` fails if there is a subfolder in bin folder
### Environment details

* Operating System+version: macOS Monterey
* Compiler+version: apple-clang 14
* Conan version: 1.55.0
* Python version: 3.10.9


### Steps to reproduce

build a recipe creating a subfolder in bin folder & calling fix_apple_shared_install_name() in package()

### Logs

```
ERROR: xapian-core/1.4.19: Error in package() method, line 134
	fix_apple_shared_install_name(self)
	CalledProcessErrorWithStderr: Command 'otool -hv /Users/spaceim/.conan/data/xapian-core/1.4.19/_/_/package/fecdf3b9eb7fa1cc93253eb65f840989ddd93cd3/bin/share' returned non-zero exit status 1.
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool-classic: can't map file: /Users/spaceim/.conan/data/xapian-core/1.4.19/_/_/package/fecdf3b9eb7fa1cc93253eb65f840989ddd93cd3/bin/share (Invalid argument)
```

Hints from issue discussion:
Hi! Nice catch! We've now identified the issue and have submitted a fix in the linked PR. Thanks for taking the time to open the report :)

Commit message: Only check files directly inside declared directories

Patch to review:
diff --git a/conan/tools/apple/apple.py b/conan/tools/apple/apple.py
index 8bffb654ec9..409fe7a0088 100644
--- a/conan/tools/apple/apple.py
+++ b/conan/tools/apple/apple.py
@@ -167,9 +167,14 @@ def _get_install_name(path_to_dylib):
         install_name = check_output_runner(command).strip().split(":")[1].strip()
         return install_name
 
-    def _darwin_collect_dylibs(lib_folder):
-        return [os.path.join(lib_folder, f) for f in os.listdir(lib_folder) if f.endswith(".dylib")
-                and not os.path.islink(os.path.join(lib_folder, f))]
+    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}"
+        return True if binary_type in check_output_runner(check_file) else False
+
+    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}"
@@ -179,17 +184,6 @@ def _fix_dep_name(dylib_path, old_name, new_name):
         command = f"install_name_tool {dylib_path} -change {old_name} {new_name}"
         conanfile.run(command)
 
-    def _darwin_collect_executables(bin_folder):
-        ret = []
-        for f in os.listdir(bin_folder):
-            full_path = os.path.join(bin_folder, f)
-
-            # Run "otool -hv" to verify it is an executable
-            check_bin = "otool -hv {}".format(full_path)
-            if "EXECUTE" in check_output_runner(check_bin):
-                ret.append(full_path)
-        return ret
-
     def _get_rpath_entries(binary_file):
         entries = []
         command = "otool -l {}".format(binary_file)
@@ -215,7 +209,7 @@ def _fix_dylib_files(conanfile):
             if not os.path.exists(full_folder):
                 raise ConanException(f"Trying to locate shared libraries, but `{libdir}` "
                                      f" not found inside package folder {conanfile.package_folder}")
-            shared_libs = _darwin_collect_dylibs(full_folder)
+            shared_libs = _darwin_collect_binaries(full_folder, "DYLIB")
             # fix LC_ID_DYLIB in first pass
             for shared_lib in shared_libs:
                 install_name = _get_install_name(shared_lib)
@@ -242,7 +236,7 @@ def _fix_executables(conanfile, substitutions):
                 # Skip if the folder does not exist inside the package
                 # (e.g. package does not contain executables but bindirs is defined)
                 continue
-            executables = _darwin_collect_executables(full_folder)
+            executables = _darwin_collect_binaries(full_folder, "EXECUTE")
             for executable in executables:
 
                 # Fix install names of libraries from within the same package
diff --git a/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py b/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
index 240f3213b01..23006f59fce 100644
--- a/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
+++ b/conans/test/functional/toolchains/gnu/test_v2_autotools_template.py
@@ -216,6 +216,7 @@ def test_autotools_fix_shared_libs():
         import os
 
         from conan import ConanFile
+        from conan.tools.files import save
         from conan.tools.gnu import AutotoolsToolchain, Autotools
         from conan.tools.layout import basic_layout
         from conan.tools.apple import fix_apple_shared_install_name
@@ -254,6 +255,10 @@ def package(self):
                                                                                                          "lib", "libbye.0.dylib")))
                 self.run("install_name_tool {} -id /lib/libhello.dylib".format(os.path.join(self.package_folder,
                                                                                             "lib","libhello.0.dylib")))
+                # https://github.com/conan-io/conan/issues/12727
+                save(self, os.path.join(self.package_folder, "bin", "subfolder", "testfile"), "foobar")
+                save(self, os.path.join(self.package_folder, "lib", "subfolder", "randomfile"), "foobar")
+
                 fix_apple_shared_install_name(self)
 
             def package_info(self):


Code review comments:
diff_hunk: @@ -167,9 +167,14 @@ def _get_install_name(path_to_dylib):
         install_name = check_output_runner(command).strip().split(":")[1].strip()
         return install_name
 
-    def _darwin_collect_dylibs(lib_folder):
-        return [os.path.join(lib_folder, f) for f in os.listdir(lib_folder) if f.endswith(".dylib")
-                and not os.path.islink(os.path.join(lib_folder, f))]
+    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}"
+        return True if binary_type in check_output_runner(check_file) else False
path: conan/tools/apple/apple.py
text: @user1:
```suggestion
        return binary_type in check_output_runner(check_file)
```