Repository: conan-io/conan

Issue:
[bug] z3 and fmt so's are not working correctly in bazel
### Describe the bug

conan 2.12, linux, bazel 8

We are seeing issues with both z3 (4.12.4.0) and fmt (8.0.1). I'm not sure if the issue is with the respective builds, or with the way that conan is invoking the builds. The problem is in the version numbers and symlinks between so's in the conan packages.

Taking z3 as example: It packages these so's:
```
% ls -l external/+conan_extension+z3/lib/
total 31104
lrwxrwxrwx 1        13 Feb  5 13:54 libz3.so -> libz3.so.4.12
lrwxrwxrwx 1        17 Feb  5 13:54 libz3.so.4.12 -> libz3.so.4.12.4.0
-rwxr-xr-x 1  31850360 Nov 22 06:12 libz3.so.4.12.4.0
```
Conan's generated BUILD.bazel correctly references the leaf in this symlink chain:
```
% cat external/+conan_extension+z3/BUILD.bazel
# Components precompiled libs
cc_import(
    name = "z3_precompiled",
    shared_library = "lib/libz3.so.4.12.4.0",
)
```
However, the resulting binary built against this so thinks that it needs a different version. The link line specifies libz3.so.4.12.4.0, so it is not an issue with command line parameters passed to link:
```
% ldd mybinary | grep z3
       libz3.so.4.12 => not found
       libz3.so.4.12 => not found
       libz3.so.4.12 => not found
       libz3.so.4.12 => not found
       libz3.so.4.12 => not found
```

It appears the root cause may be because libz3.so.4.12.4.0 reports itself as being `libz3.so.4.12`:
```
% strings external/+conan_extension+z3/lib/libz3.so.4.12.4.0 | grep "libz3"
libz3.so.4.12
```

As a workaround, I can patch conan's generated BUILD.bazel and add an extra cc_import statement for 4.12, but this is not scalable for us especially as this is occurring with multiple targets.



### How to reproduce it

_No response_


Discussion:
Hi @peakschris 

So sorry for the delay in replying to your issue 🙏 

I've been looking into this, and I get the problem. Yeah, as you said, we're resolving that symlink, and we get the `libz3.so.4.12.4.0` lib. Running the readelf command, we can see this:

```shell
$ readelf -d /root/.conan2/p/b/z309fb606d30e0c/p/lib/libz3.so.4.12.4.0

Dynamic section at offset 0x1a9f888 contains 31 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [ld-linux-aarch64.so.1]
 0x000000000000000e (SONAME)             Library soname: [libz3.so.4.12]
```

The soname says `libz3.so.4.12`, so it fails because that one is not there.
As a workaround, you could add the `VirtualRunEnv` generator to your recipe (in build context) and try it again. For instance:

```python

def generate(self):
     # ......
     if self.options.shared:
         VirtualRunEnv(self).generate(scope="build")
```

I think that including it could solve the problem until we can provide a better solution from BazelDeps. I'd want to study the case of likely reverting that behavior of resolving the symlink real path.

Let me know if adding that workaround could work for you while I look into those symlinks...
Hi @peakschris 

I'm proposing this: https://github.com/conan-io/conan/pull/17964. I tried it locally, and it worked.
Could you check to see if it solves the issue for fmt and z3?



Patch:
diff --git a/conan/internal/model/cpp_info.py b/conan/internal/model/cpp_info.py
index 1a702ba6e3a..94986a4bae0 100644
--- a/conan/internal/model/cpp_info.py
+++ b/conan/internal/model/cpp_info.py
@@ -511,16 +511,8 @@ def _lib_match_by_regex(dir_, pattern):
             for file_name in files:
                 full_path = os.path.join(dir_, file_name)
                 if os.path.isfile(full_path) and pattern.match(file_name):
-                    # File name could be a symlink, e.g., mylib.1.0.0.so -> mylib.so
-                    if os.path.islink(full_path):
-                        # Important! os.path.realpath returns the final path of the symlink even if it points
-                        # to another symlink, i.e., libmylib.dylib -> libmylib.1.dylib -> libmylib.1.0.0.dylib
-                        # then os.path.realpath("libmylib.1.0.0.dylib") == "libmylib.dylib"
-                        # Note: os.readlink() returns the path which the symbolic link points to.
-                        real_path = os.path.realpath(full_path)
-                        ret.add(real_path)
-                    else:
-                        ret.add(full_path)
+                    # Issue: https://github.com/conan-io/conan/issues/17721 (stop resolving symlinks)
+                    ret.add(full_path)
             return list(ret)
 
         def _find_matching(dirs, pattern):


Test patch:
diff --git a/test/unittests/model/build_info/test_deduce_locations.py b/test/unittests/model/build_info/test_deduce_locations.py
index 1fd6349b7e4..3a8169e6a8c 100644
--- a/test/unittests/model/build_info/test_deduce_locations.py
+++ b/test/unittests/model/build_info/test_deduce_locations.py
@@ -169,21 +169,31 @@ def test_windows_several_shared_link_locations(lib_info, conanfile):
 @pytest.mark.skipif(platform.system() == "Windows", reason="Can't apply symlink on Windows")
 def test_shared_link_locations_symlinks(lib, symlinks, conanfile):
     """
-    Tests auto deduce location is able to find the real path of
-    any symlink created in the libs folder
+    Tests auto deduce location is not resolving symlinks by default. For instance:
+        .
+        ├── libpng.so -> libpng16.so  (exact match)
+        ├── libpng16.so -> libpng16.so.16
+        ├── libpng16.so.16 -> libpng16.so.16.44.0
+        └── libpng16.so.16.44.0  (real one)
+
+
+    Issues related:
+        - https://github.com/conan-io/conan/issues/17417
+        - https://github.com/conan-io/conan/issues/17721
     """
     folder = temp_folder()
     all_files = symlinks.split(" <- ")  # [real_one, sym1, sym2, ...]
-    # forcing a folder that it's not going to be analysed by the deduce_location() function
-    real_location = os.path.join(folder, "other", all_files.pop(0))
+    # Real one (first item from list)
+    real_location = os.path.join(folder, "libdir", all_files.pop(0))
     save(real_location, "")
     # Symlinks
-    os.makedirs(os.path.join(folder, "libdir"))
     prev_path = real_location
     for file in all_files:
         sym = os.path.join(folder, "libdir", file)
         os.symlink(prev_path, sym)
         prev_path = sym
+    # Exact match and symlink (latest item from list)
+    exact_match = os.path.join(folder, "libdir", all_files[-1])
 
     cppinfo = CppInfo()
     cppinfo.libdirs = ["libdir"]
@@ -191,7 +201,7 @@ def test_shared_link_locations_symlinks(lib, symlinks, conanfile):
     cppinfo.set_relative_base_folder(folder)
 
     result = cppinfo.deduce_full_cpp_info(conanfile)
-    assert result.location == real_location
+    assert result.location == exact_match
     assert result.type == "shared-library"