Repository: conan-io/conan
Pull request #17372 (2024-11-25 09:01:04)

pure refactor of ConanApp
Changelog: Omit
Docs: Omit


Pure internal refactor:

- To reduce the usage of ``ConanApp``, which is excessive in many situations
- To remove ConanOutput initialization logic from ``ConanApp``
- Decoupled ``HookManager`` from ``ConanApp``. It might end in its own API initialization, to load only once hooks, but that would be in a separate PR


Patch:
diff --git a/conan/api/subapi/cache.py b/conan/api/subapi/cache.py
index d347b2728..b3cf0b215 100644
--- a/conan/api/subapi/cache.py
+++ b/conan/api/subapi/cache.py
@@ -8,7 +8,6 @@ from conan.api.model import PackagesList
 from conan.api.output import ConanOutput
 from conan.internal.cache.cache import PkgCache
 from conan.internal.cache.home_paths import HomePaths
-from conan.internal.conan_app import ConanApp
 from conan.internal.cache.integrity_check import IntegrityChecker
 from conans.client.downloaders.download_cache import DownloadCache
 from conan.errors import ConanException
@@ -24,53 +23,53 @@ class CacheAPI:
         self.conan_api = conan_api
 
     def export_path(self, ref: RecipeReference):
-        app = ConanApp(self.conan_api)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
         ref.revision = None if ref.revision == "latest" else ref.revision
-        ref_layout = app.cache.recipe_layout(ref)
+        ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "export", ref_layout.export())
 
     def recipe_metadata_path(self, ref: RecipeReference):
-        app = ConanApp(self.conan_api)
-        ref = _resolve_latest_ref(app, ref)
-        ref_layout = app.cache.recipe_layout(ref)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        ref = _resolve_latest_ref(cache, ref)
+        ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "metadata", ref_layout.metadata())
 
     def export_source_path(self, ref: RecipeReference):
-        app = ConanApp(self.conan_api)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
         ref.revision = None if ref.revision == "latest" else ref.revision
-        ref_layout = app.cache.recipe_layout(ref)
+        ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "export_sources", ref_layout.export_sources())
 
     def source_path(self, ref: RecipeReference):
-        app = ConanApp(self.conan_api)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
         ref.revision = None if ref.revision == "latest" else ref.revision
-        ref_layout = app.cache.recipe_layout(ref)
+        ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "source", ref_layout.source())
 
     def build_path(self, pref: PkgReference):
-        app = ConanApp(self.conan_api)
-        pref = _resolve_latest_pref(app, pref)
-        ref_layout = app.cache.pkg_layout(pref)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        pref = _resolve_latest_pref(cache, pref)
+        ref_layout = cache.pkg_layout(pref)
         return _check_folder_existence(pref, "build", ref_layout.build())
 
     def package_metadata_path(self, pref: PkgReference):
-        app = ConanApp(self.conan_api)
-        pref = _resolve_latest_pref(app, pref)
-        ref_layout = app.cache.pkg_layout(pref)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        pref = _resolve_latest_pref(cache, pref)
+        ref_layout = cache.pkg_layout(pref)
         return _check_folder_existence(pref, "metadata", ref_layout.metadata())
 
     def package_path(self, pref: PkgReference):
-        app = ConanApp(self.conan_api)
-        pref = _resolve_latest_pref(app, pref)
-        ref_layout = app.cache.pkg_layout(pref)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        pref = _resolve_latest_pref(cache, pref)
+        ref_layout = cache.pkg_layout(pref)
         if os.path.exists(ref_layout.finalize()):
             return ref_layout.finalize()
         return _check_folder_existence(pref, "package", ref_layout.package())
 
     def check_integrity(self, package_list):
         """Check if the recipes and packages are corrupted (it will raise a ConanExcepcion)"""
-        app = ConanApp(self.conan_api)
-        checker = IntegrityChecker(app)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        checker = IntegrityChecker(cache)
         checker.check(package_list)
 
     def clean(self, package_list, source=True, build=True, download=True, temp=True,
@@ -87,11 +86,11 @@ class CacheAPI:
         :return:
         """
 
-        app = ConanApp(self.conan_api)
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
         if temp:
-            rmdir(app.cache.temp_folder)
+            rmdir(cache.temp_folder)
             # Clean those build folders that didn't succeed to create a package and wont be in DB
-            builds_folder = app.cache.builds_folder
+            builds_folder = cache.builds_folder
             if os.path.isdir(builds_folder):
                 for subdir in os.listdir(builds_folder):
                     folder = os.path.join(builds_folder, subdir)
@@ -105,17 +104,17 @@ class CacheAPI:
                 remove(f)
 
         for ref, ref_bundle in package_list.refs().items():
-            ref_layout = app.cache.recipe_layout(ref)
+            ref_layout = cache.recipe_layout(ref)
             if source:
                 rmdir(ref_layout.source())
             if download:
                 rmdir(ref_layout.download_export())
             for pref, _ in package_list.prefs(ref, ref_bundle).items():
-                pref_layout = app.cache.pkg_layout(pref)
+                pref_layout = cache.pkg_layout(pref)
                 if build:
                     rmdir(pref_layout.build())
                     # It is important to remove the "build_id" identifier if build-folder is removed
-                    app.cache.remove_build_id(pref)
+                    cache.remove_build_id(pref)
                 if download:
                     rmdir(pref_layout.download_package())
 
@@ -244,21 +243,21 @@ class CacheAPI:
         return download_cache.get_backup_sources_files(excluded_urls, package_list, only_upload)
 
 
-def _resolve_latest_ref(app, ref):
+def _resolve_latest_ref(cache, ref):
     if ref.revision is None or ref.revision == "latest":
         ref.revision = None
-        result = app.cache.get_latest_recipe_reference(ref)
+        result = cache.get_latest_recipe_reference(ref)
         if result is None:
             raise ConanException(f"'{ref}' not found in cache")
         ref = result
     return ref
 
 
-def _resolve_latest_pref(app, pref):
-    pref.ref = _resolve_latest_ref(app, pref.ref)
+def _resolve_latest_pref(cache, pref):
+    pref.ref = _resolve_latest_ref(cache, pref.ref)
     if pref.revision is None or pref.revision == "latest":
         pref.revision = None
-        result = app.cache.get_latest_package_reference(pref)
+        result = cache.get_latest_package_reference(pref)
         if result is None:
             raise ConanException(f"'{pref.repr_notime()}' not found in cache")
         pref = result
diff --git a/conan/api/subapi/export.py b/conan/api/subapi/export.py
index e6e62c4eb..bfba33d91 100644
--- a/conan/api/subapi/export.py
+++ b/conan/api/subapi/export.py
@@ -1,8 +1,11 @@
 from conan.api.output import ConanOutput
+from conan.internal.cache.cache import PkgCache
+from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
 from conan.internal.api.export import cmd_export
 from conan.internal.methods import run_package_method
 from conans.client.graph.graph import BINARY_BUILD, RECIPE_INCACHE
+from conans.client.hook_manager import HookManager
 from conans.model.package_ref import PkgReference
 from conans.util.files import mkdir
 
@@ -15,12 +18,13 @@ class ExportAPI:
     def export(self, path, name, version, user, channel, lockfile=None, remotes=None):
         ConanOutput().title("Exporting recipe to the cache")
         app = ConanApp(self.conan_api)
-        return cmd_export(app, self.conan_api.config.global_conf, path, name, version, user, channel,
-                          graph_lock=lockfile, remotes=remotes)
+        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
+        return cmd_export(app, hook_manager, self.conan_api.config.global_conf, path, name, version,
+                          user, channel, graph_lock=lockfile, remotes=remotes)
 
     def export_pkg(self, deps_graph, source_folder, output_folder):
-        app = ConanApp(self.conan_api)
-        cache, hook_manager = app.cache, app.hook_manager
+        cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
+        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
 
         # The graph has to be loaded with build_mode=[ref.name], so that node is not tried
         # to be downloaded from remotes
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index cf3a9c4e7..34aa8374e 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -1,10 +1,12 @@
 import os
 
 from conan.internal.api.install.generators import write_generators
+from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
 from conan.internal.deploy import do_deploys
 
 from conans.client.graph.install_graph import InstallGraph
+from conans.client.hook_manager import HookManager
 from conans.client.installer import BinaryInstaller
 from conan.errors import ConanInvalidConfiguration
 
@@ -87,5 +89,6 @@ class InstallAPI:
             if gen not in final_generators:
                 final_generators.append(gen)
         conanfile.generators = final_generators
-        app = ConanApp(self.conan_api)
-        write_generators(conanfile, app, envs_generation=envs_generation)
+        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
+        write_generators(conanfile, hook_manager, self.conan_api.home_folder,
+                         envs_generation=envs_generation)
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index 5cff434a6..6145afc1d 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -1,6 +1,7 @@
 import os
 
 from conan.cli import make_abs_path
+from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
 from conan.internal.api.local.editable import EditablePackages
 from conan.internal.methods import run_build_method, run_source_method
@@ -8,6 +9,7 @@ from conans.client.graph.graph import CONTEXT_HOST
 from conans.client.graph.profile_node_definer import initialize_conanfile_profile
 from conan.internal.errors import conanfile_exception_formatter
 from conan.errors import ConanException
+from conans.client.hook_manager import HookManager
 from conans.model.recipe_ref import RecipeReference
 from conans.util.files import chdir
 
@@ -97,16 +99,16 @@ class LocalAPI:
         conanfile.folders.set_base_build(None)
         conanfile.folders.set_base_package(None)
 
-        app = ConanApp(self._conan_api)
-        run_source_method(conanfile, app.hook_manager)
+        hook_manager = HookManager(HomePaths(self._conan_api.home_folder).hooks_path)
+        run_source_method(conanfile, hook_manager)
 
     def build(self, conanfile):
         """ calls the 'build()' method of the current (user folder) conanfile.py
         """
-        app = ConanApp(self._conan_api)
+        hook_manager = HookManager(HomePaths(self._conan_api.home_folder).hooks_path)
         conanfile.folders.set_base_package(conanfile.folders.base_build)
         conanfile.folders.set_base_pkg_metadata(os.path.join(conanfile.build_folder, "metadata"))
-        run_build_method(conanfile, app.hook_manager)
+        run_build_method(conanfile, hook_manager)
 
     @staticmethod
     def test(conanfile):
diff --git a/conan/cli/command.py b/conan/cli/command.py
index cae95aa80..551fab40e 100644
--- a/conan/cli/command.py
+++ b/conan/cli/command.py
@@ -117,6 +117,7 @@ class ConanArgumentParser(argparse.ArgumentParser):
         ConanOutput.define_log_level(args.v)
         if getattr(args, "lockfile_packages", None):
             ConanOutput().error("The --lockfile-packages arg is private and shouldn't be used")
+        global_conf = self._conan_api.config.global_conf
         if args.core_conf:
             from conans.model.conf import ConfDefinition
             confs = ConfDefinition()
@@ -125,7 +126,14 @@ class ConanArgumentParser(argparse.ArgumentParser):
                     raise ConanException(f"Only core. values are allowed in --core-conf. Got {c}")
             confs.loads("\n".join(args.core_conf))
             confs.validate()
-            self._conan_api.config.global_conf.update_conf_definition(confs)
+            global_conf.update_conf_definition(confs)
+
+        # TODO: This might be even better moved to the ConanAPI so users without doing custom
+        #  commands can benefit from it
+        ConanOutput.set_warnings_as_errors(global_conf.get("core:warnings_as_errors",
+                                                           default=[], check_type=list))
+        ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings",
+                                                            default=[], check_type=list))
         return args
 
 
diff --git a/conan/internal/api/export.py b/conan/internal/api/export.py
index bc0d09c7a..72d77fa63 100644
--- a/conan/internal/api/export.py
+++ b/conan/internal/api/export.py
@@ -12,13 +12,13 @@ from conan.internal.paths import DATA_YML
 from conans.util.files import is_dirty, rmdir, set_dirty, mkdir, clean_dirty, chdir
 
 
-def cmd_export(app, global_conf, conanfile_path, name, version, user, channel, graph_lock=None,
-               remotes=None):
+def cmd_export(app, hook_manager, global_conf, conanfile_path, name, version, user, channel,
+               graph_lock=None, remotes=None):
     """ Export the recipe
     param conanfile_path: the original source directory of the user containing a
                        conanfile.py
     """
-    loader, cache, hook_manager = app.loader, app.cache, app.hook_manager
+    loader, cache = app.loader, app.cache
     conanfile = loader.load_export(conanfile_path, name, version, user, channel, graph_lock,
                                    remotes=remotes)
 
diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py
index 22e0c6db2..61c0d7b85 100644
--- a/conan/internal/api/install/generators.py
+++ b/conan/internal/api/install/generators.py
@@ -72,14 +72,13 @@ def load_cache_generators(path):
     return result
 
 
-def write_generators(conanfile, app, envs_generation=None):
+def write_generators(conanfile, hook_manager, home_folder, envs_generation=None):
     new_gen_folder = conanfile.generators_folder
     _receive_conf(conanfile)
     _receive_generators(conanfile)
 
-    hook_manager = app.hook_manager
     # TODO: Optimize this, so the global generators are not loaded every call to write_generators
-    global_generators = load_cache_generators(HomePaths(app.cache_folder).custom_generators_path)
+    global_generators = load_cache_generators(HomePaths(home_folder).custom_generators_path)
     hook_manager.execute("pre_generate", conanfile=conanfile)
 
     if conanfile.generators:
diff --git a/conan/internal/api/new/bazel_7_lib.py b/conan/internal/api/new/bazel_7_lib.py
index fdc51f6d6..2cc08df3c 100644
--- a/conan/internal/api/new/bazel_7_lib.py
+++ b/conan/internal/api/new/bazel_7_lib.py
@@ -49,7 +49,10 @@ class {{package_name}}Recipe(ConanFile):
         copy(self, "*.so", build, dest_lib, keep_path=False)
         copy(self, "*.dll", build, dest_bin, keep_path=False)
         copy(self, "*.dylib", build, dest_lib, keep_path=False)
-        copy(self, "*.a", build, dest_lib, keep_path=False)
+        if self.settings.os == "Linux" and self.options.get_safe("fPIC"):
+            copy(self, "*.pic.a", build, dest_lib, keep_path=False)
+        else:
+            copy(self, "*.a", build, dest_lib, keep_path=False)
         copy(self, "*.lib", build, dest_lib, keep_path=False)
         copy(self, "{{name}}.h", os.path.join(self.source_folder, "main"),
              os.path.join(self.package_folder, "include"), keep_path=False)
diff --git a/conan/internal/api/new/bazel_lib.py b/conan/internal/api/new/bazel_lib.py
index d888f7f7d..d4b375bb7 100644
--- a/conan/internal/api/new/bazel_lib.py
+++ b/conan/internal/api/new/bazel_lib.py
@@ -53,7 +53,10 @@ class {{package_name}}Recipe(ConanFile):
         copy(self, "*.so", build, dest_lib, keep_path=False)
         copy(self, "*.dll", build, dest_bin, keep_path=False)
         copy(self, "*.dylib", build, dest_lib, keep_path=False)
-        copy(self, "*.a", build, dest_lib, keep_path=False)
+        if self.settings.os == "Linux" and self.options.get_safe("fPIC"):
+            copy(self, "*.pic.a", build, dest_lib, keep_path=False)
+        else:
+            copy(self, "*.a", build, dest_lib, keep_path=False)
         copy(self, "*.lib", build, dest_lib, keep_path=False)
         copy(self, "{{name}}.h", os.path.join(self.source_folder, "main"),
              os.path.join(self.package_folder, "include"), keep_path=False)
diff --git a/conan/internal/cache/integrity_check.py b/conan/internal/cache/integrity_check.py
index e711701a5..742109721 100644
--- a/conan/internal/cache/integrity_check.py
+++ b/conan/internal/cache/integrity_check.py
@@ -17,8 +17,8 @@ class IntegrityChecker:
         This is to be done over the package contents, not the compressed conan_package.tgz
         artifacts
     """
-    def __init__(self, app):
-        self._app = app
+    def __init__(self, cache):
+        self._cache = cache
 
     def check(self, upload_data):
         corrupted = False
@@ -30,7 +30,7 @@ class IntegrityChecker:
             raise ConanException("There are corrupted artifacts, check the error logs")
 
     def _recipe_corrupted(self, ref: RecipeReference):
-        layout = self._app.cache.recipe_layout(ref)
+        layout = self._cache.recipe_layout(ref)
         output = ConanOutput()
         read_manifest, expected_manifest = layout.recipe_manifests()
         # Filter exports_sources from read manifest if there are no exports_sources locally
@@ -50,7 +50,7 @@ class IntegrityChecker:
         output.info(f"{ref}: Integrity checked: ok")
 
     def _package_corrupted(self, ref: PkgReference):
-        layout = self._app.cache.pkg_layout(ref)
+        layout = self._cache.pkg_layout(ref)
         output = ConanOutput()
         read_manifest, expected_manifest = layout.package_manifests()
 
diff --git a/conan/internal/conan_app.py b/conan/internal/conan_app.py
index cdb86f783..0cc07f107 100644
--- a/conan/internal/conan_app.py
+++ b/conan/internal/conan_app.py
@@ -1,6 +1,5 @@
 import os
 
-from conan.api.output import ConanOutput
 from conan.internal.cache.cache import PkgCache
 from conan.internal.cache.home_paths import HomePaths
 from conans.client.graph.proxy import ConanProxy
@@ -41,12 +40,10 @@ class ConanApp:
     def __init__(self, conan_api):
         global_conf = conan_api.config.global_conf
         cache_folder = conan_api.home_folder
-        self._configure(global_conf)
         self.cache_folder = cache_folder
         self.cache = PkgCache(self.cache_folder, global_conf)
 
         home_paths = HomePaths(self.cache_folder)
-        self.hook_manager = HookManager(home_paths.hooks_path)
 
         # Wraps an http_requester to inject proxies, certs, etc
         self.requester = ConanRequester(global_conf, cache_folder)
@@ -65,10 +62,3 @@ class ConanApp:
         conanfile_helpers = ConanFileHelpers(self.requester, cmd_wrap, global_conf, self.cache,
                                              self.cache_folder)
         self.loader = ConanFileLoader(self.pyreq_loader, conanfile_helpers)
-
-    @staticmethod
-    def _configure(global_conf):
-        ConanOutput.set_warnings_as_errors(global_conf.get("core:warnings_as_errors",
-                                                           default=[], check_type=list))
-        ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings",
-                                                            default=[], check_type=list))
diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py
index 3aac16e31..c9d6363ff 100644
--- a/conan/tools/google/bazeldeps.py
+++ b/conan/tools/google/bazeldeps.py
@@ -1,17 +1,17 @@
 import os
 import re
 import textwrap
-from collections import namedtuple
+from collections import namedtuple, defaultdict
 
 from jinja2 import Template, StrictUndefined
 
 from conan.errors import ConanException
 from conan.internal import check_duplicated_generator
 from conans.model.dependencies import get_transitive_requires
+from conans.model.pkg_type import PackageType
 from conans.util.files import save
 
 _BazelTargetInfo = namedtuple("DepInfo", ['repository_name', 'name', 'ref_name', 'requires', 'cpp_info'])
-_LibInfo = namedtuple("LibInfo", ['name', 'is_shared', 'lib_path', 'import_lib_path'])
 
 
 def _get_name_with_namespace(namespace, name):
@@ -72,100 +72,6 @@ def _get_requirements(conanfile, build_context_activated):
         yield require, dep
 
 
-def _get_libs(dep, cpp_info=None) -> list:
-    """
-    Get the static/shared library paths
-
-    :param dep: normally a <ConanFileInterface obj>
-    :param cpp_info: <CppInfo obj> of the component.
-    :return: list of tuples per static/shared library ->
-             [(name, is_shared, lib_path, import_lib_path)]
-             Note: ``library_path`` could be both static and shared ones in case of UNIX systems.
-                    Windows would have:
-                        * shared: library_path as DLL, and import_library_path as LIB
-                        * static: library_path as LIB, and import_library_path as None
-    """
-    def _is_shared():
-        """
-        Checking traits and shared option
-        """
-        default_value = dep.options.get_safe("shared") if dep.options else False
-        return {"shared-library": True,
-                "static-library": False}.get(str(dep.package_type), default_value)
-
-    def _save_lib_path(file_name, file_path):
-        """Add each lib with its full library path"""
-        name, ext = file_name.split('.', maxsplit=1)  # ext could be .if.lib
-        formatted_path = file_path.replace("\\", "/")
-        lib_name = None
-        # Users may not name their libraries in a conventional way. For example, directly
-        # use the basename of the lib file as lib name, e.g., cpp_info.libs = ["liblib1.a"]
-        # Issue related: https://github.com/conan-io/conan/issues/11331
-        if file_name in libs:  # let's ensure that it has any extension
-            lib_name = file_name
-        elif name in libs:
-            lib_name = name
-        elif name.startswith("lib"):
-            short_name = name[3:]  # libpkg -> pkg
-            if short_name in libs:
-                lib_name = short_name
-        # FIXME: CPS will be in charge of defining correctly this part. At the moment, we can
-        #        only guess the name of the DLL binary as it does not follow any Conan pattern.
-        if ext == "dll" or ext.endswith(".dll"):
-            if lib_name:
-                shared_windows_libs[lib_name] = formatted_path
-            elif total_libs_number == 1 and libs[0] not in shared_windows_libs:
-                shared_windows_libs[libs[0]] = formatted_path
-            else:  # let's cross the fingers... This is the last chance.
-                for lib in libs:
-                    if lib in name and lib not in shared_windows_libs:
-                        shared_windows_libs[lib] = formatted_path
-                        break
-        elif lib_name is not None:
-            lib_paths[lib_name] = formatted_path
-
-    cpp_info = cpp_info or dep.cpp_info
-    libs = cpp_info.libs[:]  # copying the values
-    if not libs:  # no libraries declared
-        return []
-    is_shared = _is_shared()
-    libdirs = cpp_info.libdirs
-    bindirs = cpp_info.bindirs if is_shared else []  # just want to get shared libraries
-    if hasattr(cpp_info, "aggregated_components"):
-        # Global cpp_info
-        total_libs_number = len(cpp_info.aggregated_components().libs)
-    else:
-        total_libs_number = len(libs)
-    lib_paths = {}
-    shared_windows_libs = {}
-    for libdir in set(libdirs + bindirs):
-        if not os.path.exists(libdir):
-            continue
-        files = os.listdir(libdir)
-        for f in files:
-            full_path = os.path.join(libdir, f)
-            if not os.path.isfile(full_path):  # Make sure that directories are excluded
-                continue
-            _, ext = os.path.splitext(f)
-            if is_shared and ext in (".so", ".dylib", ".lib", ".dll"):
-                _save_lib_path(f, full_path)
-            elif not is_shared and ext in (".a", ".lib"):
-                _save_lib_path(f, full_path)
-
-    libraries = []
-    for name, lib_path in lib_paths.items():
-        import_lib_path = None
-        if is_shared and os.path.splitext(lib_path)[1] == ".lib":
-            if name not in shared_windows_libs:
-                raise ConanException(f"Windows needs a .lib for link-time and .dll for runtime."
-                                     f" Only found {lib_path}")
-            import_lib_path = lib_path  # .lib
-            lib_path = shared_windows_libs.pop(name)  # .dll
-        libraries.append((name, is_shared, lib_path, import_lib_path))
-    # TODO: Would we want to manage the cases where DLLs are provided by the system?
-    return libraries
-
-
 def _get_headers(cpp_info, package_folder_path):
     return ['"{}/**"'.format(_relativize_path(path, package_folder_path))
             for path in cpp_info.includedirs]
@@ -342,12 +248,19 @@ class _BazelDependenciesBZLGenerator:
         save("BUILD.bazel", "# This is an empty BUILD file.")
 
 
+class _LibInfo:
+    def __init__(self, lib_name, cpp_info_, package_folder_path):
+        self.name = lib_name
+        self.is_shared = cpp_info_.type == PackageType.SHARED
+        self.lib_path = _relativize_path(cpp_info_.location, package_folder_path)
+        self.import_lib_path = _relativize_path(cpp_info_.link_location, package_folder_path)
+
+
 class _BazelBUILDGenerator:
     """
     This class creates the BUILD.bazel for each dependency where it's declared all the
     necessary information to load the libraries
     """
-
     # If both files exist, BUILD.bazel takes precedence over BUILD
     # https://bazel.build/concepts/build-files
     filename = "BUILD.bazel"
@@ -490,8 +403,38 @@ class _BazelBUILDGenerator:
         """
         return self._root_package_info.repository_name
 
+    def get_full_libs_info(self):
+        full_libs_info = defaultdict(list)
+        cpp_info = self._dep.cpp_info
+        deduced_cpp_info = cpp_info.deduce_full_cpp_info(self._dep)
+        package_folder_path = self.package_folder
+        # Root
+        if len(cpp_info.libs) > 1:
+            for lib_name in cpp_info.libs:
+                virtual_component = deduced_cpp_info.components.pop(f"_{lib_name}")  # removing it!
+                full_libs_info["root"].append(
+                    _LibInfo(lib_name, virtual_component, package_folder_path)
+                )
+        elif cpp_info.libs:
+            full_libs_info["root"].append(
+                _LibInfo(cpp_info.libs[0], deduced_cpp_info, package_folder_path)
+            )
+        # components
+        for cmp_name, cmp_cpp_info in deduced_cpp_info.components.items():
+            if cmp_name == "_common":  # FIXME: placeholder?
+                continue
+            if len(cmp_cpp_info.libs) > 1:
+                raise ConanException(f"BazelDeps only allows 1 lib per component:\n"
+                                     f"{self._dep}: {cmp_cpp_info.libs}")
+            elif cmp_cpp_info.libs:
+                # Bazel needs to relativize each path
+                full_libs_info[_get_component_name(self._dep, cmp_name)].append(
+                    _LibInfo(cmp_cpp_info.libs[0], cmp_cpp_info, package_folder_path)
+                )
+        return full_libs_info
+
     def _get_context(self):
-        def fill_info(info):
+        def fill_info(info, libs_info):
             ret = {
                 "name": info.name,  # package name and components name
                 "libs": {},
@@ -511,17 +454,8 @@ class _BazelBUILDGenerator:
                 defines = _get_defines(cpp_info)
                 os_build = self._dep.settings_build.get_safe("os")
                 linkopts = _get_linkopts(cpp_info, os_build)
-                libs = _get_libs(self._dep, cpp_info)
-                libs_info = []
                 bindirs = [_relativize_path(bindir, package_folder_path)
                            for bindir in cpp_info.bindirs]
-                for (lib, is_shared, lib_path, import_lib_path) in libs:
-                    # Bazel needs to relativize each path
-                    libs_info.append(
-                        _LibInfo(lib, is_shared,
-                                 _relativize_path(lib_path, package_folder_path),
-                                 _relativize_path(import_lib_path, package_folder_path))
-                    )
                 ret.update({
                     "libs": libs_info,
                     "bindirs": bindirs,
@@ -535,12 +469,14 @@ class _BazelBUILDGenerator:
 
         package_folder_path = self.package_folder
         context = dict()
-        context["root"] = fill_info(self._root_package_info)
+        full_libs_info = self.get_full_libs_info()
+        context["root"] = fill_info(self._root_package_info, full_libs_info.get("root", []))
         context["components"] = []
         for component in self._components_info:
-            component_context = fill_info(component)
+            component_context = fill_info(component, full_libs_info.get(component.name, []))
             context["components"].append(component_context)
             context["root"]["component_names"].append(component_context["name"])
+
         return context
 
     def generate(self):
diff --git a/conans/client/installer.py b/conans/client/installer.py
index b9bb03d7f..bcd3ffa82 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -3,11 +3,13 @@ import shutil
 from multiprocessing.pool import ThreadPool
 
 from conan.api.output import ConanOutput
+from conan.internal.cache.home_paths import HomePaths
 from conan.internal.methods import run_build_method, run_package_method
 from conan.internal.api.install.generators import write_generators
 from conans.client.graph.graph import BINARY_BUILD, BINARY_CACHE, BINARY_DOWNLOAD, BINARY_EDITABLE, \
     BINARY_UPDATE, BINARY_EDITABLE_BUILD, BINARY_SKIP
 from conans.client.graph.install_graph import InstallGraph
+from conans.client.hook_manager import HookManager
 from conans.client.source import retrieve_exports_sources, config_source
 from conan.internal.errors import conanfile_remove_attr, conanfile_exception_formatter
 from conan.errors import ConanException
@@ -30,13 +32,13 @@ def build_id(conan_file):
     return None
 
 
-class _PackageBuilder(object):
+class _PackageBuilder:
 
-    def __init__(self, app):
-        self._app = app
+    def __init__(self, app, hook_manager):
         self._cache = app.cache
-        self._hook_manager = app.hook_manager
+        self._hook_manager = hook_manager
         self._remote_manager = app.remote_manager
+        self._home_folder = app.cache_folder
 
     def _get_build_folder(self, conanfile, package_layout):
         # Build folder can use a different package_ID if build_id() is defined.
@@ -90,7 +92,7 @@ class _PackageBuilder(object):
                 raise ConanException("%s\nError copying sources to build folder" % msg)
 
     def _build(self, conanfile, pref):
-        write_generators(conanfile, self._app)
+        write_generators(conanfile, self._hook_manager, self._home_folder)
 
         try:
             run_build_method(conanfile, self._hook_manager)
@@ -173,8 +175,9 @@ class BinaryInstaller:
         self._editable_packages = editable_packages
         self._cache = app.cache
         self._remote_manager = app.remote_manager
-        self._hook_manager = app.hook_manager
+        self._hook_manager = HookManager(HomePaths(app.cache_folder).hooks_path)
         self._global_conf = global_conf
+        self._home_folder = app.cache_folder
 
     def _install_source(self, node, remotes, need_conf=False):
         conanfile = node.conanfile
@@ -357,7 +360,7 @@ class BinaryInstaller:
         output = conanfile.output
         output.info("Rewriting files of editable package "
                     "'{}' at '{}'".format(conanfile.name, conanfile.generators_folder))
-        write_generators(conanfile, self._app)
+        write_generators(conanfile, self._hook_manager, self._home_folder)
 
         if node.binary == BINARY_EDITABLE_BUILD:
             run_build_method(conanfile, self._hook_manager)
@@ -389,7 +392,7 @@ class BinaryInstaller:
         with pkg_layout.package_lock():
             pkg_layout.package_remove()
             with pkg_layout.set_dirty_context_manager():
-                builder = _PackageBuilder(self._app)
+                builder = _PackageBuilder(self._app, self._hook_manager)
                 pref = builder.build_package(node, pkg_layout)
             assert node.prev, "Node PREV shouldn't be empty"
             assert node.pref.revision, "Node PREF revision shouldn't be empty"
@@ -457,14 +460,16 @@ class BinaryInstaller:
 
         conanfile.cpp_info.check_component_requires(conanfile)
 
-    def _call_finalize_method(self, conanfile, finalize_folder):
+    @staticmethod
+    def _call_finalize_method(conanfile, finalize_folder):
         if hasattr(conanfile, "finalize"):
             conanfile.folders.set_finalize_folder(finalize_folder)
             if not os.path.exists(finalize_folder):
                 mkdir(finalize_folder)
                 conanfile.output.highlight("Calling finalize()")
                 with conanfile_exception_formatter(conanfile, "finalize"):
-                    with conanfile_remove_attr(conanfile, ['cpp_info', 'settings', 'options'], 'finalize'):
+                    with conanfile_remove_attr(conanfile, ['cpp_info', 'settings', 'options'],
+                                               'finalize'):
                         conanfile.finalize()
 
             conanfile.output.success(f"Finalized folder {finalize_folder}")
diff --git a/conans/client/rest_client_local_recipe_index.py b/conans/client/rest_client_local_recipe_index.py
index 2514b091d..9423fb247 100644
--- a/conans/client/rest_client_local_recipe_index.py
+++ b/conans/client/rest_client_local_recipe_index.py
@@ -10,6 +10,7 @@ from conan.api.model import LOCAL_RECIPES_INDEX
 from conan.api.output import ConanOutput
 from conan.internal.cache.home_paths import HomePaths
 from conan.internal.api.export import cmd_export
+from conans.client.hook_manager import HookManager
 from conans.client.loader import ConanFileLoader
 from conan.internal.errors import ConanReferenceDoesNotExistInDB, NotFoundException, RecipeNotFoundException, \
     PackageNotFoundException
@@ -68,6 +69,7 @@ class RestApiClientLocalRecipesIndex:
         from conan.api.conan_api import ConanAPI
         conan_api = ConanAPI(local_recipes_index_path)
         self._app = ConanApp(conan_api)
+        self._hook_manager = HookManager(HomePaths(conan_api.home_folder).hooks_path)
         self._layout = _LocalRecipesIndexLayout(repo_folder)
 
     def call_method(self, method_name, *args, **kwargs):
@@ -150,8 +152,8 @@ class RestApiClientLocalRecipesIndex:
         sys.stderr = StringIO()
         try:
             global_conf = ConfDefinition()
-            new_ref, _ = cmd_export(self._app, global_conf, conanfile_path, ref.name,
-                                    str(ref.version), None, None, remotes=[self._remote])
+            new_ref, _ = cmd_export(self._app, self._hook_manager, global_conf, conanfile_path,
+                                    ref.name, str(ref.version), None, None, remotes=[self._remote])
         except Exception as e:
             raise ConanException(f"Error while exporting recipe from remote: {self._remote.name}\n"
                                  f"{str(e)}")
diff --git a/conans/model/build_info.py b/conans/model/build_info.py
index e721fab1b..dc8e79a6c 100644
--- a/conans/model/build_info.py
+++ b/conans/model/build_info.py
@@ -2,6 +2,7 @@ import copy
 import glob
 import json
 import os
+import re
 from collections import OrderedDict, defaultdict
 
 from conan.api.output import ConanOutput
@@ -486,46 +487,78 @@ class _Component:
     def parsed_requires(self):
         return [r.split("::", 1) if "::" in r else (None, r) for r in self.requires]
 
-    def deduce_locations(self, conanfile):
-        pkg_type = conanfile.package_type
-        if self._exe:   # exe is a new field, it should have the correct location
-            return
-        if self._location or self._link_location:
-            if self._type is None or self._type is PackageType.HEADER:
-                raise ConanException("Incorrect cpp_info defining location without type or header")
-            return
-        if self._type not in [None, PackageType.SHARED, PackageType.STATIC, PackageType.APP]:
-            return
-
-        if len(self.libs) == 0:
-            return
-
-        if len(self.libs) != 1:
-            raise ConanException("More than 1 library defined in cpp_info.libs, cannot deduce CPS")
-
-        # Recipe didn't specify things, need to auto deduce
-        libdirs = [x.replace("\\", "/") for x in self.libdirs]
-        bindirs = [x.replace("\\", "/") for x in self.bindirs]
+    def _auto_deduce_locations(self, conanfile, component_name):
+
+        def _lib_match_by_glob(dir_, filename):
+            # Run a glob.glob function to find the file given by the filename
+            matches = glob.glob(f"{dir_}/{filename}")
+            if matches:
+                return matches
+
+
+        def _lib_match_by_regex(dir_, pattern):
+            ret = set()
+            # pattern is a regex compiled pattern, so let's iterate each file to find the library
+            files = os.listdir(dir_)
+            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)
+                        if pattern.match(os.path.basename(real_path)):
+                            ret.add(real_path)
+                    else:
+                        ret.add(full_path)
+            return list(ret)
+
+
+        def _find_matching(dirs, pattern):
+            for d in dirs:
+                if not os.path.exists(d):
+                    continue
+                # If pattern is an exact match
+                if isinstance(pattern, str):
+                    # pattern == filename
+                    lib_found = _lib_match_by_glob(d, pattern)
+                else:
+                    lib_found = _lib_match_by_regex(d, pattern)
+                if lib_found:
+                    if len(lib_found) > 1:
+                        lib_found.sort()
+                        out.warning(f"There were several matches for Lib {libname}: {lib_found}")
+                    return lib_found[0].replace("\\", "/")
 
-        # TODO: Do a better handling of pre-defined type
-        libname = self.libs[0]
-        static_patterns = [f"{libname}.lib", f"{libname}.a", f"lib{libname}.a"]
-        shared_patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib",
-                           f"lib{libname}.*dylib"]
-        dll_patterns = [f"{libname}.dll"]
-
-        def _find_matching(patterns, dirs):
-            matches = set()
-            for pattern in patterns:
-                for d in dirs:
-                    matches.update(glob.glob(f"{d}/{pattern}"))
-            if len(matches) == 1:
-                return next(iter(matches))
-
-        static_location = _find_matching(static_patterns, libdirs)
-        shared_location = _find_matching(shared_patterns, libdirs)
-        dll_location = _find_matching(dll_patterns, bindirs)
         out = ConanOutput(scope=str(conanfile))
+        pkg_type = conanfile.package_type
+        libdirs = self.libdirs
+        bindirs = self.bindirs
+        libname = self.libs[0]
+        static_location = None
+        shared_location = None
+        dll_location = None
+        # libname is exactly the pattern, e.g., ["mylib.a"] instead of ["mylib"]
+        _, ext = os.path.splitext(libname)
+        if ext in (".lib", ".a", ".dll", ".so", ".dylib"):
+            if ext in (".lib", ".a"):
+                static_location = _find_matching(libdirs, libname)
+            elif ext in (".so", ".dylib"):
+                shared_location = _find_matching(libdirs, libname)
+            elif ext == ".dll":
+                dll_location = _find_matching(bindirs, libname)
+        else:
+            regex_static = re.compile(rf"(?:lib)?{libname}(?:[._-].+)?\.(?:a|lib)")
+            regex_shared = re.compile(rf"(?:lib)?{libname}(?:[._-].+)?\.(?:so|dylib)")
+            regex_dll = re.compile(rf"(?:.+)?({libname}|{component_name})(?:.+)?\.dll")
+            static_location = _find_matching(libdirs, regex_static)
+            shared_location = _find_matching(libdirs, regex_shared)
+            if static_location or not shared_location:
+                dll_location = _find_matching(bindirs, regex_dll)
+
         if static_location:
             if shared_location:
                 out.warning(f"Lib {libname} has both static {static_location} and "
@@ -558,6 +591,25 @@ class _Component:
         if self._type != pkg_type:
             out.warning(f"Lib {libname} deduced as '{self._type}, but 'package_type={pkg_type}'")
 
+    def deduce_locations(self, conanfile, component_name=""):
+        if self._exe:   # exe is a new field, it should have the correct location
+            return
+        if self._location or self._link_location:
+            if self._type is None or self._type is PackageType.HEADER:
+                raise ConanException("Incorrect cpp_info defining location without type or header")
+            return
+        if self._type not in [None, PackageType.SHARED, PackageType.STATIC, PackageType.APP]:
+            return
+        num_libs = len(self.libs)
+        if num_libs == 0:
+            return
+        elif num_libs > 1:
+            raise ConanException(
+                f"More than 1 library defined in cpp_info.libs, cannot deduce CPS ({num_libs} libraries found)")
+        else:
+            # If no location is defined, it's time to guess the location
+            self._auto_deduce_locations(conanfile, component_name=component_name)
+
 
 class CppInfo:
 
@@ -739,6 +791,7 @@ class CppInfo:
                 c.type = self.type  # This should be a string
                 c.includedirs = self.includedirs
                 c.libdirs = self.libdirs
+                c.bindirs = self.bindirs
                 c.libs = [lib]
                 result.components[f"_{lib}"] = c
 
@@ -746,15 +799,14 @@ class CppInfo:
             common.libs = []
             common.type = str(PackageType.HEADER)  # the type of components is a string!
             common.requires = list(result.components.keys()) + (self.requires or [])
-
             result.components["_common"] = common
         else:
             result._package = self._package.clone()
             result.default_components = self.default_components
             result.components = {k: v.clone() for k, v in self.components.items()}
 
-        result._package.deduce_locations(conanfile)
-        for comp in result.components.values():
-            comp.deduce_locations(conanfile)
+        result._package.deduce_locations(conanfile, component_name=conanfile.ref.name)
+        for comp_name, comp in result.components.items():
+            comp.deduce_locations(conanfile, component_name=comp_name)
 
         return result


Test patch:
diff --git a/conan/test/utils/mocks.py b/conan/test/utils/mocks.py
index acdb379c0..3e616b70a 100644
--- a/conan/test/utils/mocks.py
+++ b/conan/test/utils/mocks.py
@@ -2,8 +2,8 @@ from collections import defaultdict
 from io import StringIO
 
 from conan import ConanFile
-from conan.internal.conan_app import ConanFileHelpers
 from conan.errors import ConanException
+from conan.internal.conan_app import ConanFileHelpers
 from conans.model.conf import Conf
 from conans.model.layout import Folders, Infos
 from conans.model.options import Options
diff --git a/test/integration/toolchains/google/test_bazeldeps.py b/test/integration/toolchains/google/test_bazeldeps.py
index a9a0b3d78..de31863da 100644
--- a/test/integration/toolchains/google/test_bazeldeps.py
+++ b/test/integration/toolchains/google/test_bazeldeps.py
@@ -679,9 +679,15 @@ def test_tool_requires():
     client = TestClient()
     conanfile = textwrap.dedent("""
         from conan import ConanFile
+        from conan.tools.files import save
 
         class PkgBazelConan(ConanFile):
 
+            def package(self):
+                import os
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(libdirs, "libtool.lib"), "")
+
             def package_info(self):
                 self.cpp_info.libs = ["libtool"]
         """)
@@ -690,9 +696,16 @@ def test_tool_requires():
 
     conanfile = textwrap.dedent("""
         from conan import ConanFile
+        from conan.tools.files import save
 
         class PkgBazelConan(ConanFile):
 
+            def package(self):
+                import os
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(libdirs, "other_cmp1.lib"), "")
+                save(self, os.path.join(libdirs, "other_cmp2.lib"), "")
+
             def package_info(self):
                 self.cpp_info.set_property("bazel_target_name", "libother")
                 self.cpp_info.set_property("bazel_repository_name", "other-repo")
@@ -726,6 +739,20 @@ def test_tool_requires():
     client.run("install . -pr:h default -pr:b default")
     assert 'name = "tool"' in client.load("build-tool/BUILD.bazel")
     assert textwrap.dedent("""\
+    # Components precompiled libs
+    cc_import(
+        name = "other_cmp1_precompiled",
+        static_library = "lib/other_cmp1.lib",
+    )
+
+    cc_import(
+        name = "other_cmp2_precompiled",
+        static_library = "lib/other_cmp2.lib",
+    )
+
+
+    # Root package precompiled libs
+
     # Components libraries declaration
     cc_library(
         name = "component1",
@@ -736,6 +763,10 @@ def test_tool_requires():
             "include",
         ],
         visibility = ["//visibility:public"],
+        deps = [
+            # do not sort
+            ":other_cmp1_precompiled",
+        ],
     )
 
     cc_library(
@@ -747,6 +778,10 @@ def test_tool_requires():
             "include",
         ],
         visibility = ["//visibility:public"],
+        deps = [
+            # do not sort
+            ":other_cmp2_precompiled",
+        ],
     )
 
     cc_library(
@@ -800,9 +835,14 @@ def test_tool_requires_not_created_if_no_activated():
     client = TestClient()
     conanfile = textwrap.dedent("""
         from conan import ConanFile
+        from conan.tools.files import save
 
         class PkgBazelConan(ConanFile):
 
+            def package(self):
+                import os
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(libdirs, "libtool.lib"), "")
             def package_info(self):
                 self.cpp_info.libs = ["libtool"]
         """)
@@ -833,9 +873,14 @@ def test_tool_requires_raise_exception_if_exist_both_require_and_build_one():
     client = TestClient()
     conanfile = textwrap.dedent("""
         from conan import ConanFile
+        from conan.tools.files import save
 
         class PkgBazelConan(ConanFile):
 
+            def package(self):
+                import os
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(libdirs, "libtool.lib"), "")
             def package_info(self):
                 self.cpp_info.libs = ["libtool"]
         """)
@@ -1137,7 +1182,7 @@ def test_shared_windows_find_libraries():
 
     cc_import(
         name = "libssl_precompiled",
-        shared_library = "bin/libcrypto-3-x64.dll",
+        shared_library = "bin/libssl-3-x64.dll",
         interface_library = "lib/libssl.lib",
     )
     """)
diff --git a/test/unittests/model/build_info/test_deduce_locations.py b/test/unittests/model/build_info/test_deduce_locations.py
index 610cd2a87..5aeeff77f 100644
--- a/test/unittests/model/build_info/test_deduce_locations.py
+++ b/test/unittests/model/build_info/test_deduce_locations.py
@@ -1,28 +1,52 @@
 import os
+import platform
+from unittest.mock import MagicMock
 
+import pytest
+
+from conan.errors import ConanException
 from conan.test.utils.mocks import ConanFileMock
 from conan.test.utils.test_files import temp_folder
+from conan.tools.files import save
 from conans.model.build_info import CppInfo
+from conans.model.pkg_type import PackageType
+from conans.model.recipe_ref import RecipeReference
 from conans.util.files import save
 
 
-def test_deduce_locations():
+@pytest.fixture
+def conanfile():
+    c = ConanFileMock()
+    c._conan_node = MagicMock(ref=RecipeReference(""))
+    return c
+
+
+@pytest.mark.parametrize("lib_name, libs", [
+    ("myliblin.a", ["myliblin"]),
+    ("libmyliblin.a", ["myliblin"]),
+    ("mylibmac.a", ["mylibmac"]),
+    ("mylibwin.lib", ["mylibwin"]),
+    ("libmylibwin.lib", ["libmylibwin"]),
+    ("mylibwin2.if.lib", ["mylibwin2.if.lib"]),
+    ("mylibwin2.if.lib", ["mylibwin2"])
+])
+def test_simple_deduce_locations_static(lib_name, libs, conanfile):
     folder = temp_folder()
-    location = os.path.join(folder, "libdir", "mylib.lib")
+    location = os.path.join(folder, "libdir", lib_name)
     save(location, "")
 
     cppinfo = CppInfo()
     cppinfo.libdirs = ["libdir"]
-    cppinfo.libs = ["mylib"]
+    cppinfo.libs = libs
     cppinfo.set_relative_base_folder(folder)
 
-    result = cppinfo.deduce_full_cpp_info(ConanFileMock())
+    result = cppinfo.deduce_full_cpp_info(conanfile)
     assert result.location == location.replace("\\", "/")
     assert result.link_location is None
     assert result.type == "static-library"
 
 
-def test_deduce_shared_link_locations():
+def test_deduce_shared_link_locations(conanfile):
     folder = temp_folder()
     imp_location = os.path.join(folder, "libdir", "mylib.lib")
     save(imp_location, "")
@@ -35,7 +59,165 @@ def test_deduce_shared_link_locations():
     cppinfo.libs = ["mylib"]
     cppinfo.set_relative_base_folder(folder)
 
-    result = cppinfo.deduce_full_cpp_info(ConanFileMock())
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    assert result.location == location.replace("\\", "/")
+    assert result.link_location == imp_location.replace("\\", "/")
+    assert result.type == "shared-library"
+
+
+@pytest.mark.parametrize("lib_name, libs", [
+    ("liblog4cxx.so.15.2.0", ["log4cxx"]),
+    ("libapr-1.0.dylib", ["apr-1"]),
+    ("libapr-1.so.0.7.4", ["apr-1"])
+])
+def test_complex_deduce_locations_shared(lib_name, libs, conanfile):
+    """
+    Tests real examples of shared library names in Linux/MacOS,
+    e.g., log4cxx, apr-1, etc.
+
+    Related issue: https://github.com/conan-io/conan/issues/16990
+    """
+    folder = temp_folder()
+    location = os.path.join(folder, "libdir", lib_name)
+    save(location, "")
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.libs = libs
+    cppinfo.set_relative_base_folder(folder)
+
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    assert result.location == location.replace("\\", "/")
+    assert result.link_location is None
+    assert result.type == "shared-library"
+
+
+@pytest.mark.parametrize("lib_name, dll_name, libs, pkg_name", [
+    ("libcurl_imp.lib", "libcurl.dll", ["libcurl_imp"], "libcurl"),
+    ("libcrypto.lib", "libcrypto-3-x64.dll", ["libcrypto"], "crypto"),
+    ("libssl.lib", "libssl-3-x64.dll", ["libssl"], "ssl"),
+    ("zdll.lib", "zlib1.dll", ["zdll"], "zlib")
+])
+def test_windows_shared_link_locations(lib_name, dll_name, libs, pkg_name, conanfile):
+    """
+    Tests real examples of shared library names in Windows,
+    e.g., openssl, zlib, libcurlb, etc.
+    """
+    folder = temp_folder()
+    imp_location = os.path.join(folder, "libdir", lib_name)
+    save(imp_location, "")
+    location = os.path.join(folder, "bindir", dll_name)
+    save(location, "")
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.bindirs = ["bindir"]
+    cppinfo.libs = libs
+    cppinfo.set_relative_base_folder(folder)
+
+    conanfile._conan_node.ref = RecipeReference(name=pkg_name)
+    result = cppinfo.deduce_full_cpp_info(conanfile)
     assert result.location == location.replace("\\", "/")
     assert result.link_location == imp_location.replace("\\", "/")
     assert result.type == "shared-library"
+
+
+@pytest.mark.parametrize("lib_info", [
+    {"charset": ["charset.lib", "charset-1.dll"],
+     "iconv": ["iconv.lib", "iconv-2.dll"]},
+    {"charset": ["libcharset.so.1.0.0"],
+     "iconv": ["libiconv.so.2.6.1"]},
+])
+def test_windows_several_shared_link_locations(lib_info, conanfile):
+    """
+    Tests a real model as LIBICONV with several libs defined in the root component
+    """
+    folder = temp_folder()
+    locations = {}
+    is_windows = False
+    for lib_name, lib_files in lib_info.items():
+        imp_location = os.path.join(folder, "libdir", lib_files[0])
+        save(imp_location, "")
+        if len(lib_files) > 1:
+            is_windows = True
+            location = os.path.join(folder, "bindir", lib_files[1])
+            save(location, "")
+        else:
+            location = imp_location  # Linux
+        locations[lib_name] = (location, imp_location)
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.bindirs = ["bindir"]
+    cppinfo.libs = list(lib_info.keys())
+    cppinfo.set_relative_base_folder(folder)
+
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    for lib_name in lib_info:
+        assert result.components[f"_{lib_name}"].location == locations[lib_name][0].replace("\\", "/")
+        if is_windows:
+            assert result.components[f"_{lib_name}"].link_location == locations[lib_name][1].replace("\\", "/")
+        assert result.components[f"_{lib_name}"].type == "shared-library"
+
+
+@pytest.mark.skipif(platform.system() == "Windows", reason="Can't apply symlink on Windows")
+def test_shared_link_locations_symlinks(conanfile):
+    """
+    Tests auto deduce location is able to find the real path of
+    any symlink created in the libs folder
+    """
+    folder = temp_folder()
+    # forcing a folder that it's not going to be analysed by the deduce_location() function
+    real_location = os.path.join(folder, "other", "mylib.so")
+    save(real_location, "")
+    # Symlinks
+    os.makedirs(os.path.join(folder, "libdir"))
+    sym_1 = os.path.join(folder, "libdir", "mylib.1.0.0.so")
+    sym_2 = os.path.join(folder, "libdir", "mylib.2.0.0.so")
+    os.symlink(real_location, sym_1)
+    os.symlink(sym_1, sym_2)
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.libs = ["mylib"]
+    cppinfo.set_relative_base_folder(folder)
+
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    assert result.location == real_location
+    assert result.type == "shared-library"
+
+
+@pytest.mark.parametrize("static", [True, False])
+def test_error_if_shared_and_static_found(static, conanfile):
+    folder = temp_folder()
+    save(os.path.join(folder, "libdir", "libmylib.a"), "")
+    save(os.path.join(folder, "libdir", "libmylib.so"), "")
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.libs = ["mylib"]
+    cppinfo.set_relative_base_folder(folder)
+    folder = folder.replace("\\", "/")
+    if static:
+        conanfile.package_type = PackageType.STATIC
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    ext = "a" if static else "so"
+    assert result.location == f"{folder}/libdir/libmylib.{ext}"
+    assert result.type == (PackageType.STATIC if static else PackageType.SHARED)
+
+
+def test_warning_windows_if_more_than_one_dll(conanfile):
+    folder = temp_folder()
+    save(os.path.join(folder, "libdir", "mylib.a"), "")
+    save(os.path.join(folder, "bindir", "libx.dll"), "")
+    save(os.path.join(folder, "bindir", "liby.dll"), "")
+
+    cppinfo = CppInfo()
+    cppinfo.libdirs = ["libdir"]
+    cppinfo.bindirs = ["bindir"]
+    cppinfo.libs = ["mylib"]
+    cppinfo.set_relative_base_folder(folder)
+    folder = folder.replace("\\", "/")
+    result = cppinfo.deduce_full_cpp_info(conanfile)
+    assert result.location == f"{folder}/bindir/libx.dll"
+    assert result.type == "shared-library"
diff --git a/test/unittests/tools/google/test_bazel.py b/test/unittests/tools/google/test_bazel.py
index 3aa21d364..b6250f6ee 100644
--- a/test/unittests/tools/google/test_bazel.py
+++ b/test/unittests/tools/google/test_bazel.py
@@ -8,7 +8,7 @@ from conan.test.utils.mocks import ConanFileMock
 from conan.test.utils.test_files import temp_folder
 from conan.tools.files import save
 from conan.tools.google import Bazel
-from conan.tools.google.bazeldeps import _relativize_path, _get_libs
+from conan.tools.google.bazeldeps import _relativize_path
 
 
 @pytest.fixture(scope="module")
@@ -77,45 +77,3 @@ def test_bazel_command_with_config_values():
 ])
 def test_bazeldeps_relativize_path(path, pattern, expected):
     assert _relativize_path(path, pattern) == expected
-
-
-@pytest.mark.parametrize("libs, is_shared, expected", [
-    # expected == (lib_name, is_shared, library_path, interface_library_path)
-    (["mylibwin"], False, [('mylibwin', False, '{base_folder}/lib/mylibwin.lib', None)]),
-    # Win + shared
-    (["mylibwin"], True, [('mylibwin', True, '{base_folder}/bin/mylibwin.dll', '{base_folder}/lib/mylibwin.lib')]),
-    # Win + shared (interface with another ext)
-    (["mylibwin2"], True, [('mylibwin2', True, '{base_folder}/bin/mylibwin2.dll', '{base_folder}/lib/mylibwin2.if.lib')]),
-    # Mac + shared
-    (["mylibmac"], True, [('mylibmac', True, '{base_folder}/bin/mylibmac.dylib', None)]),
-    # Mac + static
-    (["mylibmac"], False, [('mylibmac', False, '{base_folder}/lib/mylibmac.a', None)]),
-    # mylib + shared (saved as libmylib.so) -> removing the leading "lib" if it matches
-    (["mylib"], True, [('mylib', True, '{base_folder}/lib/libmylib.so', None)]),
-    # mylib + static (saved in a subfolder subfolder/libmylib.a) -> non-recursive at this moment
-    (["mylib"], False, []),
-    # no lib matching
-    (["noexist"], False, []),
-    # no lib matching + Win + static
-    (["noexist", "mylibwin"], False, [('mylibwin', False, '{base_folder}/lib/mylibwin.lib', None)]),
-    # protobuf (Issue related https://github.com/conan-io/conan/issues/15390)
-    (["protoc"], True, []),
-    # non-conventional library name (Issue related https://github.com/conan-io/conan/pull/11343)
-    (["libmylib.so"], True, [('libmylib.so', True, '{base_folder}/lib/libmylib.so', None)]),
-])
-def test_bazeldeps_get_libs(cpp_info, libs, is_shared, expected):
-    cpp_info.libs = libs
-    ret = []
-    for (lib, is_shared, lib_path, interface_lib_path) in expected:
-        if lib_path:
-            lib_path = lib_path.format(base_folder=cpp_info._base_folder)
-        if interface_lib_path:
-            interface_lib_path = interface_lib_path.format(base_folder=cpp_info._base_folder)
-        ret.append((lib, is_shared, lib_path, interface_lib_path))
-    dep = MagicMock()
-    dep.options.get_safe.return_value = is_shared
-    dep.ref.name = "my_pkg"
-    found_libs = _get_libs(dep, cpp_info)
-    found_libs.sort()
-    ret.sort()
-    assert found_libs == ret