Repository: conan-io/conan

Issue:
tools.collect_libs() doesn't respect self.cpp_info.libdirs
Fedora is putting 64bit libraries in `lib64/` not in `lib/`.

The collect_libs tool should look out for an existing `lib64` directory to collect all libs.



Discussion:
I'm starting to realize that this is an issue in several places.

`self.cpp_info.libdirs` might be better to default to `lib, lib64` instead of only `lib`.

Otherwise every single recipe needs this special treatment.
Yet another thought: Why is tools.collect_libs not simply using self.cpp_info.libdirs?
I think this is a duplicate of #3369. Also assigned for take a look for the next release.
Seems mostly a duplicate, yes.

But maybe it should be also an issue that tools.collect_libs() doesn't seem to respect self.cpp_info.libdirs?
as mentioned on the other issue, this isn't a duplicate. At least, it solves the same problem but I believe there are subtle differences in the consequences of these two different ways of solving it.
> But maybe it should be also an issue that tools.collect_libs() doesn't seem to respect self.cpp_info.libdirs?

Sure, this is a different issue, rename the title then ;)

Resolving patch:
diff --git a/conans/client/tools/files.py b/conans/client/tools/files.py
--- a/conans/client/tools/files.py
+++ b/conans/client/tools/files.py
@@ -251,21 +251,31 @@ def _path_equals(path1, path2):
     return path1 == path2
 
 
-def collect_libs(conanfile, folder="lib"):
+def collect_libs(conanfile, folder=None):
     if not conanfile.package_folder:
         return []
-    lib_folder = os.path.join(conanfile.package_folder, folder)
-    if not os.path.exists(lib_folder):
-        conanfile.output.warn("Lib folder doesn't exist, can't collect libraries: {0}".format(lib_folder))
-        return []
-    files = os.listdir(lib_folder)
+    if folder:
+        lib_folders = [os.path.join(conanfile.package_folder, folder)]
+    else:
+        lib_folders = [os.path.join(conanfile.package_folder, folder)
+                       for folder in conanfile.cpp_info.libdirs]
     result = []
-    for f in files:
-        name, ext = os.path.splitext(f)
-        if ext in (".so", ".lib", ".a", ".dylib"):
-            if ext != ".lib" and name.startswith("lib"):
-                name = name[3:]
-            result.append(name)
+    for lib_folder in lib_folders:
+        if not os.path.exists(lib_folder):
+            conanfile.output.warn("Lib folder doesn't exist, can't collect libraries: "
+                                  "{0}".format(lib_folder))
+            continue
+        files = os.listdir(lib_folder)
+        for f in files:
+            name, ext = os.path.splitext(f)
+            if ext in (".so", ".lib", ".a", ".dylib"):
+                if ext != ".lib" and name.startswith("lib"):
+                    name = name[3:]
+                if name in result:
+                    conanfile.output.warn("Library '%s' already found in a previous "
+                                          "'conanfile.cpp_info.libdirs' folder" % name)
+                else:
+                    result.append(name)
     return result
 
 


Test patch:
diff --git a/conans/test/util/tools_test.py b/conans/test/util/tools_test.py
--- a/conans/test/util/tools_test.py
+++ b/conans/test/util/tools_test.py
@@ -21,7 +21,9 @@
 from conans.client.tools.scm import Git
 
 from conans.errors import ConanException, NotFoundException
+from conans.model.build_info import CppInfo
 from conans.model.settings import Settings
+from conans.test.build_helpers.cmake_test import ConanFileMock
 
 from conans.test.utils.runner import TestRunner
 from conans.test.utils.test_files import temp_folder
@@ -31,7 +33,7 @@
 from conans.tools import which
 from conans.tools import OSInfo, SystemPackageTool, replace_in_file, AptTool, ChocolateyTool,\
     set_global_instances
-from conans.util.files import save, load, md5
+from conans.util.files import save, load, md5, mkdir
 import requests
 
 from nose.plugins.attrib import attr
@@ -1383,3 +1385,66 @@ def build(self):
 """
         client.save({"conanfile.py": conanfile, "file.txt": "hello\r\n"})
         client.run("create . user/channel")
+
+    def collect_libs_test(self):
+        conanfile = ConanFileMock()
+        # Without package_folder
+        conanfile.package_folder = None
+        result = tools.collect_libs(conanfile)
+        self.assertEqual([], result)
+
+        # Default behavior
+        conanfile.package_folder = temp_folder()
+        mylib_path = os.path.join(conanfile.package_folder, "lib", "mylib.lib")
+        save(mylib_path, "")
+        conanfile.cpp_info = CppInfo("")
+        result = tools.collect_libs(conanfile)
+        self.assertEqual(["mylib"], result)
+
+        # Custom folder
+        customlib_path = os.path.join(conanfile.package_folder, "custom_folder", "customlib.lib")
+        save(customlib_path, "")
+        result = tools.collect_libs(conanfile, folder="custom_folder")
+        self.assertEqual(["customlib"], result)
+
+        # Custom folder doesn't exist
+        result = tools.collect_libs(conanfile, folder="fake_folder")
+        self.assertEqual([], result)
+        self.assertIn("Lib folder doesn't exist, can't collect libraries:", conanfile.output)
+
+        # Use cpp_info.libdirs
+        conanfile.cpp_info.libdirs = ["lib", "custom_folder"]
+        result = tools.collect_libs(conanfile)
+        self.assertEqual(["mylib", "customlib"], result)
+
+        # Custom folder with multiple libdirs should only collect from custom folder
+        self.assertEqual(["lib", "custom_folder"], conanfile.cpp_info.libdirs)
+        result = tools.collect_libs(conanfile, folder="custom_folder")
+        self.assertEqual(["customlib"], result)
+
+        # Warn same lib different folders
+        conanfile = ConanFileMock()
+        conanfile.package_folder = temp_folder()
+        conanfile.cpp_info = CppInfo("")
+        custom_mylib_path = os.path.join(conanfile.package_folder, "custom_folder", "mylib.lib")
+        lib_mylib_path = os.path.join(conanfile.package_folder, "lib", "mylib.lib")
+        save(custom_mylib_path, "")
+        save(lib_mylib_path, "")
+        conanfile.cpp_info.libdirs = ["lib", "custom_folder"]
+        result = tools.collect_libs(conanfile)
+        self.assertEqual(["mylib"], result)
+        self.assertIn("Library 'mylib' already found in a previous 'conanfile.cpp_info.libdirs' "
+                      "folder", conanfile.output)
+
+        # Warn lib folder does not exist with correct result
+        conanfile = ConanFileMock()
+        conanfile.package_folder = temp_folder()
+        conanfile.cpp_info = CppInfo("")
+        lib_mylib_path = os.path.join(conanfile.package_folder, "lib", "mylib.lib")
+        save(lib_mylib_path, "")
+        no_folder_path = os.path.join(conanfile.package_folder, "no_folder")
+        conanfile.cpp_info.libdirs = ["no_folder", "lib"]  # 'no_folder' does NOT exist
+        result = tools.collect_libs(conanfile)
+        self.assertEqual(["mylib"], result)
+        self.assertIn("WARN: Lib folder doesn't exist, can't collect libraries: %s"
+                      % no_folder_path, conanfile.output)