Repository: conan-io/conan
Pull request #17068 (2024-09-26 12:26:05)

fix --build=compatible for graph build-order
Changelog: Omit
Docs: Omit

Complements the ``--build=compatible`` for ``conan graph build-order``


Patch:
diff --git a/conan/cli/args.py b/conan/cli/args.py
index dfa4b9665..d25b2c292 100644
--- a/conan/cli/args.py
+++ b/conan/cli/args.py
@@ -48,9 +48,9 @@ def add_common_install_arguments(parser):
                        help='Do not use remote, resolve exclusively in the cache')
 
     update_help = ("Will install newer versions and/or revisions in the local cache "
-                   "for the given reference, or all in case no argument is supplied. "
+                   "for the given reference name, or all references in the graph if no argument is supplied. "
                    "When using version ranges, it will install the latest version that "
-                   "satisfies the range. Also, if using revisions, it will update to the "
+                   "satisfies the range. It will update to the "
                    "latest revision for the resolved version range.")
 
     parser.add_argument("-u", "--update", action="append", nargs="?", help=update_help, const="*")
@@ -96,9 +96,9 @@ def add_profiles_args(parser):
                             help="")
 
     create_config("pr", "profile")
-    create_config("o", "options", "-o pkg:with_qt=true")
-    create_config("s", "settings", "-s compiler=gcc")
-    create_config("c", "conf", "-c tools.cmake.cmaketoolchain:generator=Xcode")
+    create_config("o", "options", '-o="pkg/*:with_qt=True"')
+    create_config("s", "settings", '-s="compiler=gcc"')
+    create_config("c", "conf", '-c="tools.cmake.cmaketoolchain:generator=Xcode"')
 
 
 def add_reference_args(parser):
diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py
index 4cb213e88..916e13f42 100644
--- a/conan/tools/google/bazeldeps.py
+++ b/conan/tools/google/bazeldeps.py
@@ -10,8 +10,8 @@ from conan.internal import check_duplicated_generator
 from conans.model.dependencies import get_transitive_requires
 from conans.util.files import save
 
-_BazelTargetInfo = namedtuple("DepInfo", ['repository_name', 'name', 'requires', 'cpp_info'])
-_LibInfo = namedtuple("LibInfo", ['name', 'is_shared', 'lib_path', 'interface_lib_path'])
+_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,18 +72,19 @@ def _get_requirements(conanfile, build_context_activated):
         yield require, dep
 
 
-def _get_libs(dep, cpp_info=None) -> list:
+def _get_libs(dep, cpp_info=None, reference_name=None) -> list:
     """
     Get the static/shared library paths
 
     :param dep: normally a <ConanFileInterface obj>
     :param cpp_info: <CppInfo obj> of the component.
+    :param reference_name: <str> Package/Component's reference name. ``None`` by default.
     :return: list of tuples per static/shared library ->
-             [(lib_name, is_shared, library_path, interface_library_path)]
+             [(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 interface_library_path as LIB
-                        * static: library_path as LIB, and interface_library_path as None
+                        * 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():
         """
@@ -93,22 +94,52 @@ def _get_libs(dep, cpp_info=None) -> list:
         return {"shared-library": True,
                 "static-library": False}.get(str(dep.package_type), default_value)
 
-    def _save_lib_path(lib_, lib_path_):
+    def _save_lib_path(file_name, file_path):
         """Add each lib with its full library path"""
-        formatted_path = lib_path_.replace("\\", "/")
-        _, ext_ = os.path.splitext(formatted_path)
-        if is_shared and ext_ == ".lib":  # Windows interface library
-            interface_lib_paths[lib_] = formatted_path
-        else:
-            lib_paths[lib_] = formatted_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 ref_name in name and ref_name in lib 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
-    libs = cpp_info.libs[:]  # copying the values
+    ref_name = reference_name or dep.ref.name
+    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 = {}
-    interface_lib_paths = {}
+    shared_windows_libs = {}
     for libdir in set(libdirs + bindirs):
         if not os.path.exists(libdir):
             continue
@@ -117,35 +148,22 @@ def _get_libs(dep, cpp_info=None) -> list:
             full_path = os.path.join(libdir, f)
             if not os.path.isfile(full_path):  # Make sure that directories are excluded
                 continue
-            name, ext = os.path.splitext(f)
-            # 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 ext and f in libs:  # let's ensure that it has any extension
+            _, 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)
-                continue
-            if name not in libs and name.startswith("lib"):
-                name = name[3:]  # libpkg -> pkg
-            # FIXME: Should it read a conf variable to know unexpected extensions?
-            if (is_shared and ext in (".so", ".dylib", ".lib", ".dll")) or \
-               (not is_shared and ext in (".a", ".lib")):
-                if name in libs:
-                    _save_lib_path(name, full_path)
-                    continue
-                else:  # last chance: some cases the name could be pkg.if instead of pkg
-                    name = name.split(".", maxsplit=1)[0]
-                    if name in libs:
-                        _save_lib_path(name, full_path)
 
     libraries = []
-    for lib, lib_path in lib_paths.items():
-        interface_lib_path = None
-        if lib_path.endswith(".dll"):
-            if lib not in interface_lib_paths:
+    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}")
-            interface_lib_path = interface_lib_paths.pop(lib)
-        libraries.append((lib, is_shared, lib_path, interface_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
 
@@ -345,8 +363,8 @@ class _BazelBUILDGenerator:
         {% else %}
         static_library = "{{ lib_info.lib_path }}",
         {% endif %}
-        {% if lib_info.interface_lib_path %}
-        interface_library = "{{ lib_info.interface_lib_path }}",
+        {% if lib_info.import_lib_path %}
+        interface_library = "{{ lib_info.import_lib_path }}",
         {% endif %}
     )
     {% endfor %}
@@ -498,12 +516,12 @@ class _BazelBUILDGenerator:
                 libs_info = []
                 bindirs = [_relativize_path(bindir, package_folder_path)
                            for bindir in cpp_info.bindirs]
-                for (lib, is_shared, lib_path, interface_lib_path) in libs:
+                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(interface_lib_path, package_folder_path))
+                                 _relativize_path(import_lib_path, package_folder_path))
                     )
                 ret.update({
                     "libs": libs_info,
@@ -599,7 +617,8 @@ class _InfoGenerator:
             comp_requires_names = self._get_cpp_info_requires_names(cpp_info)
             comp_name = _get_component_name(self._dep, comp_ref_name)
             # Save each component information
-            components_info.append(_BazelTargetInfo(None, comp_name, comp_requires_names, cpp_info))
+            components_info.append(_BazelTargetInfo(None, comp_name, comp_ref_name,
+                                                    comp_requires_names, cpp_info))
         return components_info
 
     @property
@@ -622,7 +641,8 @@ class _InfoGenerator:
                 for req in self._transitive_reqs.values()
             ]
         cpp_info = self._dep.cpp_info
-        return _BazelTargetInfo(repository_name, pkg_name, requires, cpp_info)
+        return _BazelTargetInfo(repository_name, pkg_name, _get_package_reference_name(self._dep),
+                                requires, cpp_info)
 
 
 class BazelDeps:
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index 129fe221d..4961c28c0 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -35,7 +35,6 @@ def cppstd_compat(conanfile):
     cppstd = conanfile.settings.get_safe("compiler.cppstd")
     if not compiler or not compiler_version:
         return []
-    base = dict(conanfile.settings.values_list)
     factors = []  # List of list, each sublist is a potential combination
     if cppstd is not None and extension_properties.get("compatibility_cppstd") is not False:
         cppstd_possible_values = supported_cppstd(conanfile)
@@ -72,9 +71,7 @@ def cppstd_compat(conanfile):
 
     ret = []
     for comb in combinations:
-        configuration = base.copy()
-        configuration.update(comb)
-        ret.append({"settings": [(k, v) for k, v in configuration.items()]})
+        ret.append({"settings": [(k, v) for k, v in comb.items()]})
     return ret
 """
 
@@ -144,6 +141,7 @@ class BinaryCompatibility:
         if compatibles:
             for elem in compatibles:
                 compat_info = conanfile.original_info.clone()
+                compat_info.compatibility_delta = elem
                 settings = elem.get("settings")
                 if settings:
                     compat_info.settings.update_values(settings, raise_undefined=False)
diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py
index 284a05b20..d79cf16ba 100644
--- a/conans/client/graph/graph.py
+++ b/conans/client/graph/graph.py
@@ -67,6 +67,7 @@ class Node(object):
         self.should_build = False  # If the --build or policy wants to build this binary
         self.build_allowed = False
         self.is_conf = False
+        self.replaced_requires = {}  # To track the replaced requires for self.dependencies[old-ref]
 
     def __lt__(self, other):
         """
@@ -135,7 +136,7 @@ class Node(object):
             if require.build and (self.context == CONTEXT_HOST or  # switch context
                                   require.ref.version != self.ref.version):  # or different version
                 pass
-            elif require.visible is False and require.ref.version != self.ref.version:
+            elif require.visible is False: #  and require.ref.version != self.ref.version:
                 # Experimental, to support repackaging of openssl previous versions FIPS plugins
                 pass  # An invisible require doesn't conflict with itself
             else:
@@ -177,15 +178,17 @@ class Node(object):
         source_node = dependant.src
         return source_node.check_downstream_exists(down_require) or result
 
-    def check_loops(self, new_node):
+    def check_loops(self, new_node, count=0):
         if self.ref == new_node.ref and self.context == new_node.context:
-            return self
+            if count >= 1:
+                return self
+            count += 1
         if not self.dependants:
             return
         assert len(self.dependants) == 1
         dependant = self.dependants[0]
         source_node = dependant.src
-        return source_node.check_loops(new_node)
+        return source_node.check_loops(new_node, count)
 
     @property
     def package_id(self):
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 41793dee9..a442fa4a1 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -312,6 +312,7 @@ class DepsGraphBuilder(object):
                 node.conanfile.requires.reindex(require, alternative_ref.name)
             require.ref.name = alternative_ref.name
             graph.replaced_requires[original_require] = repr(require.ref)
+            node.replaced_requires[original_require] = require
             break  # First match executes the alternative and finishes checking others
 
     def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock):
diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py
index 82e6e1700..63221cc32 100644
--- a/conans/client/graph/install_graph.py
+++ b/conans/client/graph/install_graph.py
@@ -32,6 +32,7 @@ class _InstallPackageReference:
         self.depends = []  # List of package_ids of dependencies to other binaries of the same ref
         self.overrides = Overrides()
         self.ref = None
+        self.info = None
 
     @property
     def pref(self):
@@ -53,6 +54,7 @@ class _InstallPackageReference:
         result.options = node.conanfile.self_options.dumps().splitlines()
         result.nodes.append(node)
         result.overrides = node.overrides()
+        result.info = node.conanfile.info.serialize()  # ConanInfo doesn't have deserialize
         return result
 
     def add(self, node):
@@ -67,7 +69,8 @@ class _InstallPackageReference:
         if self.binary != BINARY_BUILD:
             return None
         cmd = f"--requires={self.ref}" if self.context == "host" else f"--tool-requires={self.ref}"
-        cmd += f" --build={self.ref}"
+        compatible = "compatible:" if self.info and self.info.get("compatibility_delta") else ""
+        cmd += f" --build={compatible}{self.ref}"
         if self.options:
             cmd += " " + " ".join(f"-o {o}" for o in self.options)
         if self.overrides:
@@ -83,7 +86,8 @@ class _InstallPackageReference:
                 "filenames": self.filenames,
                 "depends": self.depends,
                 "overrides": self.overrides.serialize(),
-                "build_args": self._build_args()}
+                "build_args": self._build_args(),
+                "info": self.info}
 
     @staticmethod
     def deserialize(data, filename, ref):
@@ -97,6 +101,7 @@ class _InstallPackageReference:
         result.filenames = data["filenames"] or [filename]
         result.depends = data["depends"]
         result.overrides = Overrides.deserialize(data["overrides"])
+        result.info = data.get("info")
         return result
 
 
@@ -221,6 +226,7 @@ class _InstallConfiguration:
         self.filenames = []  # The build_order.json filenames e.g. "windows_build_order"
         self.depends = []  # List of full prefs
         self.overrides = Overrides()
+        self.info = None
 
     def __str__(self):
         return f"{self.ref}:{self.package_id} ({self.binary}) -> {[str(d) for d in self.depends]}"
@@ -248,6 +254,7 @@ class _InstallConfiguration:
         # self_options are the minimum to reproduce state
         result.options = node.conanfile.self_options.dumps().splitlines()
         result.overrides = node.overrides()
+        result.info = node.conanfile.info.serialize()
 
         result.nodes.append(node)
         for dep in node.dependencies:
@@ -273,7 +280,8 @@ class _InstallConfiguration:
         if self.binary != BINARY_BUILD:
             return None
         cmd = f"--requires={self.ref}" if self.context == "host" else f"--tool-requires={self.ref}"
-        cmd += f" --build={self.ref}"
+        compatible = "compatible:" if self.info and self.info.get("compatibility_delta") else ""
+        cmd += f" --build={compatible}{self.ref}"
         if self.options:
             cmd += " " + " ".join(f"-o {o}" for o in self.options)
         if self.overrides:
@@ -291,7 +299,8 @@ class _InstallConfiguration:
                 "filenames": self.filenames,
                 "depends": [d.repr_notime() for d in self.depends],
                 "overrides": self.overrides.serialize(),
-                "build_args": self._build_args()
+                "build_args": self._build_args(),
+                "info": self.info
                 }
 
     @staticmethod
@@ -306,6 +315,7 @@ class _InstallConfiguration:
         result.filenames = data["filenames"] or [filename]
         result.depends = [PkgReference.loads(p) for p in data["depends"]]
         result.overrides = Overrides.deserialize(data["overrides"])
+        result.info = data.get("info")
         return result
 
     def merge(self, other):
@@ -456,6 +466,7 @@ class InstallGraph:
 
     def _get_missing_invalid_packages(self):
         missing, invalid = [], []
+
         def analyze_package(package):
             if package.binary == BINARY_MISSING:
                 missing.append(package)
@@ -505,7 +516,6 @@ class InstallGraph:
             msg.append("{}: {}: {}".format(node.conanfile, binary, reason))
         raise ConanInvalidConfiguration("\n".join(msg))
 
-
     def _raise_missing(self, missing):
         # TODO: Remove out argument
         # TODO: A bit dirty access to .pref
diff --git a/conans/model/build_info.py b/conans/model/build_info.py
index 4aa0aee2d..b392c7f55 100644
--- a/conans/model/build_info.py
+++ b/conans/model/build_info.py
@@ -564,8 +564,10 @@ class CppInfo:
         if not external:
             return
         # Only direct host (not test) dependencies can define required components
-        direct_dependencies = [d.ref.name for d in conanfile.requires.values()
-                               if not d.build and not d.is_test and d.visible and not d.override]
+        # We use conanfile.dependencies to use the already replaced ones by "replace_requires"
+        # So consumers can keep their ``self.cpp_info.requires = ["pkg_name::comp"]``
+        direct_dependencies = [r.ref.name for r, d in conanfile.dependencies.items() if r.direct
+                               and not r.build and not r.is_test and r.visible and not r.override]
 
         for e in external:
             if e not in direct_dependencies:
diff --git a/conans/model/dependencies.py b/conans/model/dependencies.py
index 0f8640fc8..a2734b5e8 100644
--- a/conans/model/dependencies.py
+++ b/conans/model/dependencies.py
@@ -91,6 +91,12 @@ class ConanFileDependencies(UserRequirementsDict):
     def from_node(node):
         d = OrderedDict((require, ConanFileInterface(transitive.node.conanfile))
                         for require, transitive in node.transitive_deps.items())
+        for old_req, new_req in node.replaced_requires.items():
+            existing = d.pop(new_req, None)
+            if existing is not None:
+                added_req = new_req.copy_requirement()
+                added_req.ref = RecipeReference.loads(old_req)
+                d[added_req] = existing
         return ConanFileDependencies(d)
 
     def filter(self, require_filter, remove_system=True):
diff --git a/conans/model/info.py b/conans/model/info.py
index ad1a7b249..630f13104 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -338,6 +338,7 @@ class ConanInfo:
         self.python_requires = python_requires
         self.conf = conf
         self.config_version = config_version
+        self.compatibility_delta = None
 
     def clone(self):
         """ Useful for build_id implementation and for compatibility()
@@ -381,6 +382,8 @@ class ConanInfo:
         config_version_dumps = self.config_version.serialize() if self.config_version else None
         if config_version_dumps:
             result["config_version"] = config_version_dumps
+        if self.compatibility_delta:
+            result["compatibility_delta"] = self.compatibility_delta
         return result
 
     def dumps(self):


Test patch:
diff --git a/test/integration/build_requires/test_install_test_build_require.py b/test/integration/build_requires/test_install_test_build_require.py
index 148dfd2a3..8892dc032 100644
--- a/test/integration/build_requires/test_install_test_build_require.py
+++ b/test/integration/build_requires/test_install_test_build_require.py
@@ -222,3 +222,37 @@ def test_bootstrap_other_architecture():
     assert package1["depends"] == []
     assert package2["package_id"] == linux_pkg_id
     assert package2["depends"] == [win_pkg_id]
+
+
+def test_bootstrap_cc():
+    # https://github.com/conan-io/conan/issues/16758
+    cc = textwrap.dedent("""
+        from conan import ConanFile
+
+        class CcConanfile(ConanFile):
+            name = "cc"
+            version = "1.0"
+            options = {"bootstrap": [True, False]}
+            default_options = {"bootstrap": True}
+
+            def build_requirements(self):
+                if self.options.bootstrap:
+                    self.tool_requires("cc/1.0", options={"*:bootstrap": False})
+        """)
+    foo = GenConanfile("foo", "1.0").with_tool_requirement("cc/1.0")
+    c = TestClient()
+    c.save({"cc/conanfile.py": cc,
+            "foo/conanfile.py": foo})
+    c.run("create cc --build-require")
+    # Both are build-requires
+    c.assert_listed_binary({"cc/1.0": ("826727aac60b5956d1df5121a3921f26a6984f15", "Build")},
+                           build=True)
+    c.assert_listed_binary({"cc/1.0": ("96ae1d965ce6f2e2256ac0cfc3a35dcd4860c389", "Build")},
+                           build=True)
+    # This works fine, being able to build the 2 different binaries both bootstrapped and not
+    c.run("list cc/1.0:*")
+    assert "bootstrap: False" in c.out
+    assert "bootstrap: True" in c.out
+    c.run("create foo")
+    c.assert_listed_binary({"cc/1.0": ("96ae1d965ce6f2e2256ac0cfc3a35dcd4860c389", "Cache")},
+                           build=True)
diff --git a/test/integration/command_v2/test_info_build_order.py b/test/integration/command_v2/test_info_build_order.py
index f71d51ba9..21f6b86a4 100644
--- a/test/integration/command_v2/test_info_build_order.py
+++ b/test/integration/command_v2/test_info_build_order.py
@@ -28,6 +28,7 @@ def test_info_build_order():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': [],
+                        'info': {},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -49,6 +50,7 @@ def test_info_build_order():
                         "package_id": "59205ba5b14b8f4ebc216a6c51a89553021e82c1",
                         'prev': None,
                         'filenames': [],
+                        'info': {'requires': ['dep/0.1']},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -102,6 +104,7 @@ def test_info_build_order_configuration():
                 "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                 'prev': None,
                 'filenames': [],
+                'info': {},
                 "context": "host",
                 "binary": "Build",
                 'build_args': '--requires=dep/0.1 --build=dep/0.1',
@@ -119,6 +122,7 @@ def test_info_build_order_configuration():
                 "package_id": "59205ba5b14b8f4ebc216a6c51a89553021e82c1",
                 'prev': None,
                 'filenames': [],
+                'info': {'requires': ['dep/0.1']},
                 "context": "host",
                 "binary": "Build",
                 'build_args': '--requires=pkg/0.1 --build=pkg/0.1',
@@ -170,6 +174,7 @@ def test_info_build_order_build_require():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': [],
+                        'info': {},
                         "context": "build",
                         'depends': [],
                         "binary": "Build",
@@ -191,6 +196,7 @@ def test_info_build_order_build_require():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': [],
+                        'info': {},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -230,9 +236,11 @@ def test_info_build_order_options():
                  {'package_id': '1124b99dc8cd3c8bbf79121c7bf86ce40c725a40', 'prev': None,
                   'context': 'build', 'depends': [], "overrides": {},
                   'binary': 'Build', 'options': ['tool/0.1:myopt=2'], 'filenames': [],
+                  'info': {'options': {'myopt': '2'}},
                   'build_args': '--tool-requires=tool/0.1 --build=tool/0.1 -o tool/0.1:myopt=2'},
                  {'package_id': 'a9035d84c5880b26c4b44acf70078c9a7dd37412', 'prev': None,
                   'context': 'build', 'depends': [], "overrides": {},
+                  'info': {'options': {'myopt': '1'}},
                   'binary': 'Build', 'options': ['tool/0.1:myopt=1'],
                   'filenames': [],
                   'build_args': '--tool-requires=tool/0.1 --build=tool/0.1 -o tool/0.1:myopt=1'}
@@ -244,7 +252,7 @@ def test_info_build_order_options():
              'packages': [[
                  {'package_id': 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 'prev': None,
                   'context': 'host', 'depends': [], 'binary': 'Build', 'options': [],
-                  'filenames': [], "overrides": {},
+                  'filenames': [], 'info': {}, "overrides": {},
                   'build_args': '--requires=dep1/0.1 --build=dep1/0.1'}
              ]]},
             {'ref': 'dep2/0.1#23c789d2b36f0461e52cd6f139f97f5e',
@@ -252,7 +260,7 @@ def test_info_build_order_options():
              'packages': [[
                  {'package_id': 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 'prev': None,
                   'context': 'host', 'depends': [], 'binary': 'Build', 'options': [],
-                  'filenames': [], "overrides": {},
+                  'filenames': [], 'info': {}, "overrides": {},
                   'build_args': '--requires=dep2/0.1 --build=dep2/0.1'}
              ]]}
         ]
@@ -286,6 +294,7 @@ def test_info_build_order_merge_multi_product():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': ["bo1", "bo2"],
+                        'info': {},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -307,6 +316,7 @@ def test_info_build_order_merge_multi_product():
                         "package_id": "59205ba5b14b8f4ebc216a6c51a89553021e82c1",
                         'prev': None,
                         'filenames': ["bo1"],
+                        'info': {'requires': ['dep/0.1']},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -326,6 +336,7 @@ def test_info_build_order_merge_multi_product():
                         "package_id": "59205ba5b14b8f4ebc216a6c51a89553021e82c1",
                         'prev': None,
                         'filenames': ["bo2"],
+                        'info': {'requires': ['dep/0.1']},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -345,7 +356,6 @@ def test_info_build_order_merge_multi_product():
     assert "<body>" in c.stdout
 
 
-
 def test_info_build_order_merge_multi_product_configurations():
     c = TestClient()
     c.save({"dep/conanfile.py": GenConanfile(),
@@ -378,6 +388,7 @@ def test_info_build_order_merge_multi_product_configurations():
                     "bo1",
                     "bo2"
                 ],
+                'info': {},
                 "depends": [],
                 "overrides": {},
                 "build_args": "--requires=dep/0.1 --build=dep/0.1"
@@ -395,6 +406,7 @@ def test_info_build_order_merge_multi_product_configurations():
                 "filenames": [
                     "bo1"
                 ],
+                'info': {'requires': ['dep/0.1']},
                 "depends": [
                     "dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709"
                 ],
@@ -412,6 +424,7 @@ def test_info_build_order_merge_multi_product_configurations():
                 "filenames": [
                     "bo2"
                 ],
+                'info': {'requires': ['dep/0.1']},
                 "depends": [
                     "dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709"
                 ],
@@ -461,6 +474,7 @@ def test_info_build_order_merge_conditionals():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': ["bo_win"],
+                        'info': {},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -478,6 +492,7 @@ def test_info_build_order_merge_conditionals():
                         "package_id": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                         'prev': None,
                         'filenames': ["bo_nix"],
+                        'info': {},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -500,6 +515,8 @@ def test_info_build_order_merge_conditionals():
                         "package_id": "b23846b9b10455081d89a9dfacd01f7712d04b95",
                         'prev': None,
                         'filenames': ["bo_win"],
+                        'info': {'requires': ['depwin/0.1'],
+                                 'settings': {'os': 'Windows'}},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -511,6 +528,8 @@ def test_info_build_order_merge_conditionals():
                         "package_id": "dc29fa55ec82fab6bd820398c7a152ae5f7d4e28",
                         'prev': None,
                         'filenames': ["bo_nix"],
+                        'info': {'requires': ['depnix/0.1'],
+                                 'settings': {'os': 'Linux'}},
                         "context": "host",
                         'depends': [],
                         "binary": "Build",
@@ -565,6 +584,7 @@ def test_build_order_missing_package_check_error():
                 "binary": "Missing",
                 "options": [],
                 "filenames": [],
+                'info': {},
                 "depends": [],
                 "overrides": {},
                 "build_args": None,
@@ -580,6 +600,7 @@ def test_build_order_missing_package_check_error():
                 "binary": "Build",
                 "options": [],
                 "filenames": [],
+                'info': {'requires': ['dep/0.1']},
                 "depends": [
                     "dep/0.1#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709"
                 ],
diff --git a/test/integration/configuration/conf/test_conf.py b/test/integration/configuration/conf/test_conf.py
index d8329e09e..52e2bf858 100644
--- a/test/integration/configuration/conf/test_conf.py
+++ b/test/integration/configuration/conf/test_conf.py
@@ -414,3 +414,30 @@ def test_especial_strings_fail():
     assert "user.mycompany:myother: fnmatch" in c.out
     assert "user.mycompany:myfunct: re.search" in c.out
     assert "user.mycompany:mydict: {1: 're', 2: 'fnmatch'}" in c.out
+
+
+def test_conf_test_package():
+    c = TestClient()
+    dep = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            name = "dep"
+            version = "0.1"
+            def generate(self):
+                self.output.info(f'user.myteam:myconf: {self.conf.get("user.myteam:myconf")}')
+        """)
+    pkg = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            def requirements(self):
+                self.requires(self.tested_reference_str)
+            def generate(self):
+                self.output.info(f'user.myteam:myconf: {self.conf.get("user.myteam:myconf")}')
+            def test(self):
+                pass
+        """)
+    c.save({"conanfile.py": dep,
+            "test_package/conanfile.py": pkg})
+    c.run("create . -c &:user.myteam:myconf=myvalue")
+    assert "dep/0.1: user.myteam:myconf: myvalue" in c.out
+    assert "dep/0.1 (test package): user.myteam:myconf: myvalue" in c.out
diff --git a/test/integration/graph/core/test_build_requires.py b/test/integration/graph/core/test_build_requires.py
index 7e396044b..a3945956a 100644
--- a/test/integration/graph/core/test_build_requires.py
+++ b/test/integration/graph/core/test_build_requires.py
@@ -883,12 +883,15 @@ class TestLoops(GraphManagerTest):
         assert type(deps_graph.error) == GraphLoopError
 
         # Build requires always apply to the consumer
-        self.assertEqual(2, len(deps_graph.nodes))
+        self.assertEqual(4, len(deps_graph.nodes))
         app = deps_graph.root
         tool = app.dependencies[0].dst
+        tool2 = tool.dependencies[0].dst
+        tool3 = tool2.dependencies[0].dst
 
         self._check_node(app, "app/0.1", deps=[tool], dependents=[])
-        self._check_node(tool, "cmake/0.1#123", deps=[], dependents=[app])
+        self._check_node(tool, "cmake/0.1#123", deps=[tool2], dependents=[app])
+        self._check_node(tool2, "cmake/0.1#123", deps=[tool3], dependents=[tool])
 
     def test_indirect_loop_error(self):
         # app -(br)-> gtest/0.1 -(br)-> cmake/0.1 -(br)->gtest/0.1 ....
@@ -902,14 +905,16 @@ class TestLoops(GraphManagerTest):
         assert type(deps_graph.error) == GraphLoopError
 
         # Build requires always apply to the consumer
-        self.assertEqual(4, len(deps_graph.nodes))
+        self.assertEqual(6, len(deps_graph.nodes))
         app = deps_graph.root
         cmake = app.dependencies[0].dst
         gtest = cmake.dependencies[0].dst
         cmake2 = gtest.dependencies[0].dst
+        gtest2 = cmake2.dependencies[0].dst
+        cmake3 = gtest2.dependencies[0].dst
 
         assert deps_graph.error.ancestor == cmake
-        assert deps_graph.error.node == cmake2
+        assert deps_graph.error.node == cmake3
 
     def test_infinite_recursion_test(self):
         """Ensure that direct conflicts in a node are properly reported and Conan does not loop"""
diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py
index 31edd82f2..909854d0b 100644
--- a/test/integration/graph/test_replace_requires.py
+++ b/test/integration/graph/test_replace_requires.py
@@ -1,4 +1,5 @@
 import json
+import textwrap
 
 import pytest
 
@@ -157,3 +158,120 @@ def test_replace_requires_test_requires():
     c.run("create gtest")
     c.run("install app -pr=profile")
     assert "gtest/0.1: gtest/0.2" in c.out  # The replacement happens
+
+
+def test_replace_requires_consumer_references():
+    c = TestClient()
+    # IMPORTANT: The replacement package must be target-compatible
+    zlib_ng = textwrap.dedent("""
+        from conan import ConanFile
+        class ZlibNG(ConanFile):
+            name = "zlib-ng"
+            version = "0.1"
+            def package_info(self):
+                self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
+        """)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class App(ConanFile):
+            name = "app"
+            version = "0.1"
+            settings = "build_type"
+            requires = "zlib/0.1"
+            generators = "CMakeDeps"
+
+            def generate(self):
+                self.output.info(f"DEP ZLIB generate: {self.dependencies['zlib'].ref.name}!")
+            def build(self):
+                self.output.info(f"DEP ZLIB build: {self.dependencies['zlib'].ref.name}!")
+            def package_info(self):
+                self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
+                self.cpp_info.requires = ["zlib::zlib"]
+        """)
+    c.save({"zlibng/conanfile.py": zlib_ng,
+            "app/conanfile.py": conanfile,
+            "profile": "[replace_requires]\nzlib/0.1: zlib-ng/0.1"})
+    c.run("create zlibng")
+    c.run("build app -pr=profile")
+    assert "zlib/0.1: zlib-ng/0.1" in c.out
+    assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib-ng!" in c.out
+    assert "conanfile.py (app/0.1): DEP ZLIB build: zlib-ng!" in c.out
+    # Check generated CMake code. If the targets are NOT compatible, then the replacement
+    # Cannot happen
+    assert "find_package(ZLIB)" in c.out
+    assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
+    cmake = c.load("app/ZLIBTargets.cmake")
+    assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
+    c.run("create app -pr=profile")
+    assert "zlib/0.1: zlib-ng/0.1" in c.out
+    assert "app/0.1: DEP ZLIB generate: zlib-ng!" in c.out
+    assert "app/0.1: DEP ZLIB build: zlib-ng!" in c.out
+
+
+def test_replace_requires_consumer_components_options():
+    c = TestClient()
+    # IMPORTANT: The replacement package must be target-compatible
+    zlib_ng = textwrap.dedent("""
+        from conan import ConanFile
+        class ZlibNG(ConanFile):
+            name = "zlib-ng"
+            version = "0.1"
+            options = {"compat": [False, True]}
+            default_options = {"compat": False}
+            def package_info(self):
+                self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
+                if self.options.compat:
+                    self.cpp_info.components["myzlib"].set_property("cmake_target_name",
+                                                                    "ZLIB::zmylib")
+        """)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class App(ConanFile):
+            name = "app"
+            version = "0.1"
+            settings = "build_type"
+            requires = "zlib/0.1"
+            generators = "CMakeDeps"
+
+            def generate(self):
+                self.output.info(f"DEP ZLIB generate: {self.dependencies['zlib'].ref.name}!")
+            def build(self):
+                self.output.info(f"DEP ZLIB build: {self.dependencies['zlib'].ref.name}!")
+            def package_info(self):
+                self.output.info(f"DEP ZLIB package_info: {self.dependencies['zlib'].ref.name}!")
+                self.cpp_info.requires = ["zlib::myzlib"]
+        """)
+    profile = textwrap.dedent("""
+        [options]
+        zlib-ng/*:compat=True
+
+        [replace_requires]
+        zlib/0.1: zlib-ng/0.1
+        """)
+    c.save({"zlibng/conanfile.py": zlib_ng,
+            "app/conanfile.py": conanfile,
+            "profile": profile})
+
+    c.run("create zlibng -o *:compat=True")
+    c.run("build app -pr=profile")
+    assert "zlib/0.1: zlib-ng/0.1" in c.out
+    assert "conanfile.py (app/0.1): DEP ZLIB generate: zlib-ng!" in c.out
+    assert "conanfile.py (app/0.1): DEP ZLIB build: zlib-ng!" in c.out
+    # Check generated CMake code. If the targets are NOT compatible, then the replacement
+    # Cannot happen
+    assert "find_package(ZLIB)" in c.out
+    assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
+    cmake = c.load("app/ZLIBTargets.cmake")
+    assert "add_library(ZLIB::ZLIB INTERFACE IMPORTED)" in cmake
+    cmake = c.load("app/ZLIB-Target-none.cmake")
+    assert "set_property(TARGET ZLIB::ZLIB APPEND PROPERTY INTERFACE_LINK_LIBRARIES ZLIB::zmylib)" \
+           in cmake
+
+    c.run("create app -pr=profile")
+    assert "zlib/0.1: zlib-ng/0.1" in c.out
+    assert "app/0.1: DEP ZLIB generate: zlib-ng!" in c.out
+    assert "app/0.1: DEP ZLIB build: zlib-ng!" in c.out
+    assert "find_package(ZLIB)" in c.out
+    assert "target_link_libraries(... ZLIB::ZLIB)" in c.out
diff --git a/test/integration/package_id/compatible_test.py b/test/integration/package_id/compatible_test.py
index b7719e417..eb390b27b 100644
--- a/test/integration/package_id/compatible_test.py
+++ b/test/integration/package_id/compatible_test.py
@@ -1,3 +1,4 @@
+import json
 import textwrap
 import unittest
 
@@ -618,3 +619,90 @@ class TestCompatibleBuild:
         assert "compiler.cppstd: 14" in c.out
         c.run("list libb:*")
         assert "compiler.cppstd: 17" in c.out
+
+    def test_multi_level_build_compatible_build_order(self):
+        c = TestClient()
+        conanfile = textwrap.dedent("""
+           from conan import ConanFile
+           from conan.tools.build import check_min_cppstd
+
+           class Pkg(ConanFile):
+               name = "{name}"
+               version = "0.1"
+               settings = "os", "compiler"
+               {requires}
+
+               def validate_build(self):
+                   check_min_cppstd(self, {cppstd})
+            """)
+        c.save({"liba/conanfile.py": conanfile.format(name="liba", cppstd=14, requires=""),
+                "libb/conanfile.py": conanfile.format(name="libb", cppstd=17,
+                                                      requires='requires="liba/0.1"')})
+        c.run("export liba")
+        c.run("export libb")
+        settings = "-s os=Windows -s compiler=gcc -s compiler.version=11 " \
+                   "-s compiler.libcxx=libstdc++11 -s compiler.cppstd=11"
+        c.run(f"graph build-order --requires=libb/0.1 {settings} --format=json", assert_error=True,
+              redirect_stdout="build_order.json")
+        c.assert_listed_binary({"liba/0.1": ("bb33db23c961978d08dc0cdd6bc786b45b3e5943", "Missing"),
+                                "libb/0.1": ("144910d65b27bcbf7d544201f5578555bbd0376e", "Missing")})
+
+        c.run(f"graph build-order --requires=libb/0.1 {settings} --build=compatible "
+              "--order-by=configuration --format=json", redirect_stdout="build_order.json")
+        bo = json.loads(c.load("build_order.json"))
+        liba = bo["order"][0][0]
+        assert liba["ref"] == "liba/0.1#c1459d256a9c2d3c49d149fd7c43310c"
+        assert liba["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "14"]]}
+        assert liba["build_args"] == "--requires=liba/0.1 --build=compatible:liba/0.1"
+        # Lets make sure the build works too
+        c.run(f"install {settings} {liba['build_args']}")
+        libb = bo["order"][1][0]
+        assert libb["ref"] == "libb/0.1#62bb167aaa5306d1ac757bb817797f9e"
+        assert libb["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "17"]]}
+        assert libb["build_args"] == "--requires=libb/0.1 --build=compatible:libb/0.1"
+        # Lets make sure the build works too
+        c.run(f"install {settings} {libb['build_args']}")
+
+        # Now lets make sure that build-order-merge works too
+        c.run(f"graph build-order --requires=libb/0.1 {settings} -s compiler.version=12 "
+              "--build=compatible --order-by=configuration --format=json",
+              redirect_stdout="build_order2.json")
+
+        c.run(f"graph build-order-merge --file=build_order.json --file=build_order2.json -f=json",
+              redirect_stdout="build_order_merged.json")
+        bo = json.loads(c.load("build_order_merged.json"))
+        for pkg_index in (0, 1):
+            liba = bo["order"][0][pkg_index]
+            assert liba["ref"] == "liba/0.1#c1459d256a9c2d3c49d149fd7c43310c"
+            assert liba["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "14"]]}
+            assert liba["build_args"] == "--requires=liba/0.1 --build=compatible:liba/0.1"
+
+        # By recipe also works
+        c.run("remove *:* -c")
+        c.run(f"graph build-order --requires=libb/0.1 {settings} --build=compatible "
+              "--order-by=recipe --format=json", redirect_stdout="build_order.json")
+        bo = json.loads(c.load("build_order.json"))
+        liba = bo["order"][0][0]
+        assert liba["ref"] == "liba/0.1#c1459d256a9c2d3c49d149fd7c43310c"
+        pkga = liba["packages"][0][0]
+        assert pkga["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "14"]]}
+        assert pkga["build_args"] == "--requires=liba/0.1 --build=compatible:liba/0.1"
+        libb = bo["order"][1][0]
+        assert libb["ref"] == "libb/0.1#62bb167aaa5306d1ac757bb817797f9e"
+        pkgb = libb["packages"][0][0]
+        assert pkgb["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "17"]]}
+        assert pkgb["build_args"] == "--requires=libb/0.1 --build=compatible:libb/0.1"
+
+        # Now lets make sure that build-order-merge works too
+        c.run(f"graph build-order --requires=libb/0.1 {settings} -s compiler.version=12 "
+              "--order-by=recipe --build=compatible --format=json",
+              redirect_stdout="build_order2.json")
+        c.run(f"graph build-order-merge --file=build_order.json --file=build_order2.json -f=json",
+              redirect_stdout="build_order_merged.json")
+        bo = json.loads(c.load("build_order_merged.json"))
+        liba = bo["order"][0][0]
+        assert liba["ref"] == "liba/0.1#c1459d256a9c2d3c49d149fd7c43310c"
+        for pkg_index in (0, 1):
+            pkga = liba["packages"][0][pkg_index]
+            assert pkga["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "14"]]}
+            assert pkga["build_args"] == "--requires=liba/0.1 --build=compatible:liba/0.1"
diff --git a/test/integration/toolchains/google/test_bazeldeps.py b/test/integration/toolchains/google/test_bazeldeps.py
index d4b99059a..62c4a45a2 100644
--- a/test/integration/toolchains/google/test_bazeldeps.py
+++ b/test/integration/toolchains/google/test_bazeldeps.py
@@ -4,6 +4,7 @@ import textwrap
 
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
+from conan.tools.files import load
 
 
 def test_bazel():
@@ -983,3 +984,132 @@ class TestBazelGenerationBuildContext:
         # Now make sure we can actually build with build!=host context
         c.run("install app -s:h build_type=Debug --build=missing")
         assert "Install finished successfully" in c.out  # the asserts in build() didn't fail
+
+
+
+def test_shared_windows_find_libraries():
+    """
+    Testing the ``_get_libs`` mechanism in Windows, the shared libraries and their
+    import ones are correctly found.
+
+    Note: simulating dependencies with openssl, libcurl, and zlib packages:
+
+    zlib:
+        - (zlib package) bin/zlib1.dll AND lib/zdll.lib
+    libcurl:
+        - (curl component) bin/libcurl.dll AND lib/libcurl_imp.lib
+    openssl:
+        - (crypto component) bin/libcrypto-3-x64.dll AND lib/libcrypto.lib
+        - (ssl component) bin/libssl-3-x64.dll AND lib/libssl.lib
+
+
+    Issue: https://github.com/conan-io/conan/issues/16691
+    """
+    c = TestClient()
+    zlib = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save
+        class Example(ConanFile):
+            name = "zlib"
+            version = "1.0"
+            options = {"shared": [True, False]}
+            default_options = {"shared": False}
+            def package(self):
+                bindirs = os.path.join(self.package_folder, "bin")
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(bindirs, "zlib1.dll"), "")
+                save(self, os.path.join(libdirs, "zdll.lib"), "")
+            def package_info(self):
+                self.cpp_info.libs = ["zdll"]
+            """)
+    openssl = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save
+        class Pkg(ConanFile):
+            name = "openssl"
+            version = "1.0"
+            options = {"shared": [True, False]}
+            default_options = {"shared": False}
+            def package(self):
+                bindirs = os.path.join(self.package_folder, "bin")
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(bindirs, "libcrypto-3-x64.dll"), "")
+                save(self, os.path.join(bindirs, "libssl-3-x64.dll"), "")
+                save(self, os.path.join(libdirs, "libcrypto.lib"), "")
+                save(self, os.path.join(libdirs, "libssl.lib"), "")
+            def package_info(self):
+                self.cpp_info.components["crypto"].libs = ["libcrypto"]
+                self.cpp_info.components["ssl"].libs = ["libssl"]
+        """)
+    libcurl = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save
+        class Example(ConanFile):
+            name = "libcurl"
+            version = "1.0"
+            options = {"shared": [True, False]}
+            default_options = {"shared": False}
+            def package(self):
+                bindirs = os.path.join(self.package_folder, "bin")
+                libdirs = os.path.join(self.package_folder, "lib")
+                save(self, os.path.join(bindirs, "libcurl.dll"), "")
+                save(self, os.path.join(libdirs, "libcurl_imp.lib"), "")
+            def package_info(self):
+                self.cpp_info.components["curl"].libs = ["libcurl_imp"]
+            """)
+    consumer = textwrap.dedent("""
+        [requires]
+        zlib/1.0
+        openssl/1.0
+        libcurl/1.0
+        [options]
+        *:shared=True
+    """)
+    c.save({"conanfile.txt": consumer,
+            "zlib/conanfile.py": zlib,
+            "openssl/conanfile.py": openssl,
+            "libcurl/conanfile.py": libcurl})
+    c.run("export-pkg zlib -o:a shared=True")
+    c.run("export-pkg openssl -o:a shared=True")
+    c.run("export-pkg libcurl -o:a shared=True")
+    c.run("install . -g BazelDeps")
+    libcurl_bazel_build = load(None, os.path.join(c.current_folder, "libcurl", "BUILD.bazel"))
+    zlib_bazel_build = load(None, os.path.join(c.current_folder, "zlib", "BUILD.bazel"))
+    openssl_bazel_build = load(None, os.path.join(c.current_folder, "openssl", "BUILD.bazel"))
+    libcurl_expected = textwrap.dedent("""\
+    # Components precompiled libs
+    cc_import(
+        name = "libcurl_imp_precompiled",
+        shared_library = "bin/libcurl.dll",
+        interface_library = "lib/libcurl_imp.lib",
+    )
+    """)
+    openssl_expected = textwrap.dedent("""\
+    # Components precompiled libs
+    cc_import(
+        name = "libcrypto_precompiled",
+        shared_library = "bin/libcrypto-3-x64.dll",
+        interface_library = "lib/libcrypto.lib",
+    )
+
+    cc_import(
+        name = "libssl_precompiled",
+        shared_library = "bin/libcrypto-3-x64.dll",
+        interface_library = "lib/libssl.lib",
+    )
+    """)
+    zlib_expected = textwrap.dedent("""\
+    # Components precompiled libs
+    # Root package precompiled libs
+    cc_import(
+        name = "zdll_precompiled",
+        shared_library = "bin/zlib1.dll",
+        interface_library = "lib/zdll.lib",
+    )
+    """)
+    assert libcurl_expected in libcurl_bazel_build
+    assert zlib_expected in zlib_bazel_build
+    assert openssl_expected in openssl_bazel_build
diff --git a/test/unittests/tools/cmake/test_cmaketoolchain.py b/test/unittests/tools/cmake/test_cmaketoolchain.py
index 25ffb1104..09ba6d0fd 100644
--- a/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -32,6 +32,7 @@ def conanfile():
     c.folders.set_base_generators("/some/abs/path")  # non-existing to not relativize
     c._conan_node = Mock()
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -229,6 +230,8 @@ def conanfile_apple():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    # FIXME: Repeated fix pattern
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -258,6 +261,7 @@ def conanfile_msvc():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -332,6 +336,7 @@ def test_older_msvc_toolset():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     toolchain = CMakeToolchain(c)
     content = toolchain.content
     assert 'CMAKE_GENERATOR_TOOLSET "v110"' in content
@@ -360,6 +365,7 @@ def test_older_msvc_toolset_update():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     toolchain = CMakeToolchain(c)
     content = toolchain.content
     assert 'CMAKE_GENERATOR_TOOLSET "v142,version=14.29"' in content
@@ -385,6 +391,7 @@ def test_msvc_xp_toolsets():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     toolchain = CMakeToolchain(c)
     content = toolchain.content
     assert 'CMAKE_GENERATOR_TOOLSET "v110_xp"' in content
@@ -411,6 +418,7 @@ def conanfile_linux():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -442,6 +450,7 @@ def conanfile_linux_shared():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -482,6 +491,7 @@ def conanfile_windows_fpic():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -513,6 +523,7 @@ def conanfile_linux_fpic():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
     return c
 
 
@@ -547,6 +558,7 @@ def test_libcxx_abi_flag():
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
 
     toolchain = CMakeToolchain(c)
     content = toolchain.content
@@ -597,6 +609,7 @@ def test_apple_cmake_osx_sysroot(os, os_sdk, arch, expected_sdk):
     c._conan_node = Mock()
     c._conan_node.dependencies = []
     c._conan_node.transitive_deps = {}
+    c._conan_node.replaced_requires = {}
 
     toolchain = CMakeToolchain(c)
     content = toolchain.content
@@ -680,6 +693,7 @@ class TestCrossBuild:
         c._conan_node = Mock()
         c._conan_node.dependencies = []
         c._conan_node.transitive_deps = {}
+        c._conan_node.replaced_requires = {}
         return c
 
     def test_cmake_system_name(self, conanfile_cross):
diff --git a/test/unittests/tools/google/test_bazel.py b/test/unittests/tools/google/test_bazel.py
index dae3611bf..5c5aa5836 100644
--- a/test/unittests/tools/google/test_bazel.py
+++ b/test/unittests/tools/google/test_bazel.py
@@ -4,12 +4,11 @@ from unittest.mock import MagicMock
 
 import pytest
 
+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 conans.model.options import Options
-from conan.test.utils.mocks import ConanFileMock
-from conan.test.utils.test_files import temp_folder
 
 
 @pytest.fixture(scope="module")
@@ -28,10 +27,12 @@ def cpp_info():
     save(ConanFileMock(), os.path.join(libdirs, "mylibwin2.if.lib"), "")
     save(ConanFileMock(), os.path.join(libdirs, "libmylib.so"), "")
     save(ConanFileMock(), os.path.join(libdirs, "subfolder", "libmylib.a"), "")  # recursive
-    cpp_info_mock = MagicMock(_base_folder=None, libdirs=None, bindirs=None, libs=None)
+    cpp_info_mock = MagicMock(_base_folder=None, libdirs=[], bindirs=[], libs=[],
+                              aggregated_components=MagicMock())
     cpp_info_mock._base_folder = folder.replace("\\", "/")
     cpp_info_mock.libdirs = [libdirs]
     cpp_info_mock.bindirs = [bindirs]
+    cpp_info_mock.aggregated_components.return_value = cpp_info_mock
     return cpp_info_mock
 
 
@@ -82,15 +83,11 @@ def test_bazeldeps_relativize_path(path, pattern, expected):
     # 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')]),
-    # Win + Mac + shared
-    (["mylibwin", "mylibmac"], True, [('mylibmac', True, '{base_folder}/bin/mylibmac.dylib', None),
-                                      ('mylibwin', True, '{base_folder}/bin/mylibwin.dll',
-                                       '{base_folder}/lib/mylibwin.lib')]),
-    # Linux + Mac + static
-    (["myliblin", "mylibmac"], False, [('mylibmac', False, '{base_folder}/lib/mylibmac.a', None),
-                                       ('myliblin', False, '{base_folder}/lib/myliblin.a', None)]),
+    (["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
@@ -113,8 +110,10 @@ def test_bazeldeps_get_libs(cpp_info, libs, is_shared, expected):
         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))
-    found_libs = _get_libs(ConanFileMock(options=Options(options_values={"shared": is_shared})),
-                           cpp_info)
+    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