Repository: conan-io/conan
Pull request #17566 (2025-01-13 10:57:33)

fix incubating CMakeDeps replace_requires
Changelog: Bugfix: Solve incubating ``CMakeDeps`` issues with transitive ``[replace_requires]``
Changelog: Bugfix: Solve ``PkgConfigDeps`` issues with transitive ``[replace_requires]``
Docs: Omit

For https://github.com/conan-io/conan/issues/17557 (doesn't fully solve it, there are other generators)



Patch:
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 091af879f..30fe82cc2 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -6,7 +6,6 @@ from conan.api.subapi.command import CommandAPI
 from conan.api.subapi.local import LocalAPI
 from conan.api.subapi.lockfile import LockfileAPI
 from conan.api.subapi.workspace import WorkspaceAPI
-from conan import conan_version
 from conan.api.subapi.config import ConfigAPI
 from conan.api.subapi.download import DownloadAPI
 from conan.api.subapi.export import ExportAPI
@@ -19,13 +18,18 @@ from conan.api.subapi.remotes import RemotesAPI
 from conan.api.subapi.remove import RemoveAPI
 from conan.api.subapi.search import SearchAPI
 from conan.api.subapi.upload import UploadAPI
-from conans.client.migrations import ClientMigrator
 from conan.errors import ConanException
 from conan.internal.paths import get_conan_user_home
+from conans.client.migrations import ClientMigrator
 from conan.internal.model.version_range import validate_conan_version
 
 
 class ConanAPI:
+    """
+    This is the main object to interact with the Conan API. It provides all the subapis to work with
+    recipes, packages, remotes, etc., which are exposed as attributes of this class, and should
+    not be created directly.
+    """
     def __init__(self, cache_folder=None):
 
         version = sys.version_info
@@ -36,12 +40,11 @@ class ConanAPI:
         self.workspace = WorkspaceAPI(self)
         self.cache_folder = self.workspace.home_folder() or cache_folder or get_conan_user_home()
         self.home_folder = self.cache_folder  # Lets call it home, deprecate "cache"
+        self.migrate()
 
-        # Migration system
-        migrator = ClientMigrator(self.cache_folder, conan_version)
-        migrator.migrate()
-
+        # This API is depended upon by the subsequent ones, it should be initialized first
         self.config = ConfigAPI(self)
+
         self.remotes = RemotesAPI(self)
         self.command = CommandAPI(self)
         # Search recipes by wildcard and packages filtering by configuration
@@ -60,6 +63,27 @@ class ConanAPI:
         self.lockfile = LockfileAPI(self)
         self.local = LocalAPI(self)
 
-        required_range_new = self.config.global_conf.get("core:required_conan_version")
-        if required_range_new:
-            validate_conan_version(required_range_new)
+        _check_conan_version(self)
+
+    def reinit(self):
+        """
+        Reinitialize the Conan API. This is useful when the configuration changes.
+        """
+        self.config.reinit()
+        self.remotes.reinit()
+        self.local.reinit()
+
+        _check_conan_version(self)
+
+    def migrate(self):
+        # Migration system
+        # TODO: A prettier refactoring of migrators would be nice
+        from conan import conan_version
+        migrator = ClientMigrator(self.cache_folder, conan_version)
+        migrator.migrate()
+
+
+def _check_conan_version(conan_api):
+    required_range_new = conan_api.config.global_conf.get("core:required_conan_version")
+    if required_range_new:
+        validate_conan_version(required_range_new)
diff --git a/conan/api/model/remote.py b/conan/api/model/remote.py
index 0f6120bd2..a66a28fb9 100644
--- a/conan/api/model/remote.py
+++ b/conan/api/model/remote.py
@@ -2,7 +2,10 @@ LOCAL_RECIPES_INDEX = "local-recipes-index"
 
 
 class Remote:
-
+    """
+    The ``Remote`` class represents a remote registry of packages. It's a read-only opaque object that
+    should not be created directly, but obtained from the relevant ``RemotesAPI`` subapi methods.
+    """
     def __init__(self, name, url, verify_ssl=True, disabled=False, allowed_packages=None,
                  remote_type=None):
         self.name = name  # Read only, is the key
@@ -11,7 +14,7 @@ class Remote:
         self.disabled = disabled
         self.allowed_packages = allowed_packages
         self.remote_type = remote_type
-        self.caching = {}
+        self._caching = {}
 
     def __eq__(self, other):
         if other is None:
@@ -33,5 +36,4 @@ class Remote:
         return str(self)
 
     def invalidate_cache(self):
-        # TODO: make it private
-        self.caching = {}
+        self._caching = {}
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
index 6070802e3..0267766a1 100644
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -16,11 +16,11 @@ from conans.client.graph.graph import CONTEXT_HOST, RECIPE_VIRTUAL, Node
 from conans.client.graph.graph_builder import DepsGraphBuilder
 from conans.client.graph.profile_node_definer import consumer_definer
 from conan.errors import ConanException
-from conan.internal.model.conf import ConfDefinition, BUILT_IN_CONFS
+from conan.internal.model.conf import ConfDefinition, BUILT_IN_CONFS, CORE_CONF_PATTERN
 from conan.internal.model.pkg_type import PackageType
 from conan.api.model import RecipeReference
 from conan.internal.model.settings import Settings
-from conans.util.files import load, save
+from conans.util.files import load, save, rmdir, remove
 
 
 class ConfigAPI:
@@ -28,6 +28,7 @@ class ConfigAPI:
     def __init__(self, conan_api):
         self.conan_api = conan_api
         self._new_config = None
+        self._cli_core_confs = None
 
     def home(self):
         return self.conan_api.cache_folder
@@ -40,6 +41,7 @@ class ConfigAPI:
         requester = self.conan_api.remotes.requester
         configuration_install(cache_folder, requester, path_or_url, verify_ssl, config_type=config_type, args=args,
                               source_folder=source_folder, target_folder=target_folder)
+        self.conan_api.reinit()
 
     def install_pkg(self, ref, lockfile=None, force=False, remotes=None, profile=None):
         ConanOutput().warning("The 'conan config install-pkg' is experimental",
@@ -100,6 +102,7 @@ class ConfigAPI:
         config_versions = {ref.split("/", 1)[0]: ref for ref in config_versions}
         config_versions[pkg.pref.ref.name] = pkg.pref.repr_notime()
         save(config_version_file, json.dumps({"config_version": list(config_versions.values())}))
+        self.conan_api.reinit()
         return pkg.pref
 
     def get(self, name, default=None, check_type=None):
@@ -114,11 +117,19 @@ class ConfigAPI:
         configuration defined with the new syntax as in profiles, this config will be composed
         to the profile ones and passed to the conanfiles.conf, which can be passed to collaborators
         """
+        # Lazy loading
         if self._new_config is None:
-            cache_folder = self.conan_api.cache_folder
-            self._new_config = self.load_config(cache_folder)
+            self._new_config = ConfDefinition()
+            self._populate_global_conf()
         return self._new_config
 
+    def _populate_global_conf(self):
+        cache_folder = self.conan_api.cache_folder
+        new_config = self.load_config(cache_folder)
+        self._new_config.update_conf_definition(new_config)
+        if self._cli_core_confs is not None:
+            self._new_config.update_conf_definition(self._cli_core_confs)
+
     @staticmethod
     def load_config(home_folder):
         # Do not document yet, keep it private
@@ -191,3 +202,35 @@ class ConfigAPI:
             appending_recursive_dict_update(settings, settings_user)
 
         return Settings(settings)
+
+    def clean(self):
+        contents = os.listdir(self.home())
+        packages_folder = self.global_conf.get("core.cache:storage_path") or os.path.join(self.home(), "p")
+        for content in contents:
+            content_path = os.path.join(self.home(), content)
+            if content_path == packages_folder or content == "version.txt":
+                continue
+            ConanOutput().debug(f"Removing {content_path}")
+            if os.path.isdir(content_path):
+                rmdir(content_path)
+            else:
+                remove(content_path)
+        self.conan_api.reinit()
+        # CHECK: This also generates a remotes.json that is not there after a conan profile show?
+        self.conan_api.migrate()
+
+    def set_core_confs(self, core_confs):
+        confs = ConfDefinition()
+        for c in core_confs:
+            if not CORE_CONF_PATTERN.match(c):
+                raise ConanException(f"Only core. values are allowed in --core-conf. Got {c}")
+        confs.loads("\n".join(core_confs))
+        confs.validate()
+        self._cli_core_confs = confs
+        # Last but not least, apply the new configuration
+        self.conan_api.reinit()
+
+    def reinit(self):
+        if self._new_config is not None:
+            self._new_config.clear()
+            self._populate_global_conf()
diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py
index da4f76578..55686c9bb 100644
--- a/conan/api/subapi/list.py
+++ b/conan/api/subapi/list.py
@@ -394,10 +394,12 @@ def _get_cache_packages_binary_info(cache, prefs) -> Dict[PkgReference, dict]:
         # Read conaninfo
         info_path = os.path.join(pkg_layout.package(), CONANINFO)
         if not os.path.exists(info_path):
-            raise ConanException(f"Corrupted package '{pkg_layout.reference}' "
-                                 f"without conaninfo.txt in: {info_path}")
-        conan_info_content = load(info_path)
-        info = load_binary_info(conan_info_content)
+            ConanOutput().error(f"Corrupted package '{pkg_layout.reference}' "
+                                f"without conaninfo.txt in: {info_path}")
+            info = {}
+        else:
+            conan_info_content = load(info_path)
+            info = load_binary_info(conan_info_content)
         pref = pkg_layout.reference
         # The key shoudln't have the latest package revision, we are asking for package configs
         pref.revision = None
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index 9a7ae9d9f..71dc48677 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -118,3 +118,6 @@ class LocalAPI:
         conanfile = app.loader.load_named(conanfile_path, name=name, version=version, user=user,
                                           channel=channel, remotes=remotes, graph_lock=lockfile)
         return conanfile
+
+    def reinit(self):
+        self.editable_packages = EditablePackages(self._conan_api.home_folder)
diff --git a/conan/api/subapi/new.py b/conan/api/subapi/new.py
index 8b3089a0c..a9a99ef14 100644
--- a/conan/api/subapi/new.py
+++ b/conan/api/subapi/new.py
@@ -32,6 +32,7 @@ class NewAPI:
         from conan.internal.api.new.autoools_exe import autotools_exe_files
         from conan.internal.api.new.local_recipes_index import local_recipes_index_files
         from conan.internal.api.new.qbs_lib import qbs_lib_files
+        from conan.internal.api.new.workspace import workspace_files
         new_templates = {"basic": basic_file,
                          "cmake_lib": cmake_lib_files,
                          "cmake_exe": cmake_exe_files,
@@ -48,7 +49,8 @@ class NewAPI:
                          "autotools_exe": autotools_exe_files,
                          "alias": alias_file,
                          "local_recipes_index": local_recipes_index_files,
-                         "qbs_lib": qbs_lib_files}
+                         "qbs_lib": qbs_lib_files,
+                         "workspace": workspace_files}
         template_files = new_templates.get(template_name)
         return template_files
 
diff --git a/conan/api/subapi/remotes.py b/conan/api/subapi/remotes.py
index 9e6ae90b1..1bb55664a 100644
--- a/conan/api/subapi/remotes.py
+++ b/conan/api/subapi/remotes.py
@@ -35,14 +35,17 @@ class RemotesAPI:
         # Wraps an http_requester to inject proxies, certs, etc
         self._requester = ConanRequester(self.conan_api.config.global_conf, self.conan_api.cache_folder)
 
+    def reinit(self):
+        self._requester = ConanRequester(self.conan_api.config.global_conf, self.conan_api.cache_folder)
+
     def list(self, pattern=None, only_enabled=True):
         """
-        Obtain a list of ``Remote`` objects matching the pattern.
+        Obtain a list of :ref:`Remote <conan.api.model.Remote>` objects matching the pattern.
 
         :param pattern: ``None``, single ``str`` or list of ``str``. If it is ``None``,
           all remotes will be returned (equivalent to ``pattern="*"``).
         :param only_enabled: boolean, by default return only enabled remotes
-        :return: A list of ``Remote`` objects
+        :return: A list of :ref:`Remote <conan.api.model.Remote>` objects
 
         """
         remotes = _load(self._remotes_file)
@@ -58,7 +61,7 @@ class RemotesAPI:
 
         :param pattern: single ``str`` or list of ``str``. If the pattern is an exact name without
           wildcards like "*" and no remote is found matching that exact name, it will raise an error.
-        :return: the list of disabled ``Remote`` objects  (even if they were already disabled)
+        :return: the list of disabled :ref:`Remote <conan.api.model.Remote>` objects  (even if they were already disabled)
         """
         remotes = _load(self._remotes_file)
         disabled = _filter(remotes, pattern, only_enabled=False)
@@ -76,7 +79,7 @@ class RemotesAPI:
 
         :param pattern: single ``str`` or list of ``str``. If the pattern is an exact name without
           wildcards like "*" and no remote is found matching that exact name, it will raise an error.
-        :return: the list of enabled ``Remote`` objects (even if they were already enabled)
+        :return: the list of enabled :ref:`Remote <conan.api.model.Remote>` objects (even if they were already enabled)
         """
         remotes = _load(self._remotes_file)
         enabled = _filter(remotes, pattern, only_enabled=False)
@@ -90,10 +93,10 @@ class RemotesAPI:
 
     def get(self, remote_name):
         """
-        Obtain a ``Remote`` object
+        Obtain a :ref:`Remote <conan.api.model.Remote>` object
 
         :param remote_name: the exact name of the remote to be returned
-        :return: the ``Remote`` object, or raise an Exception if the remote does not exist.
+        :return: the :ref:`Remote <conan.api.model.Remote>` object, or raise an Exception if the remote does not exist.
         """
         remotes = _load(self._remotes_file)
         try:
@@ -103,11 +106,11 @@ class RemotesAPI:
 
     def add(self, remote: Remote, force=False, index=None):
         """
-        Add a new ``Remote`` object to the existing ones
+        Add a new :ref:`Remote <conan.api.model.Remote>` object to the existing ones
 
 
-        :param remote: a ``Remote`` object to be added
-        :param force: do not fail if the remote already exist (but default it failes)
+        :param remote: a :ref:`Remote <conan.api.model.Remote>` object to be added
+        :param force: do not fail if the remote already exist (but default it fails)
         :param index: if not defined, the new remote will be last one. Pass an integer to insert
           the remote in that position instead of the last one
         """
@@ -140,7 +143,7 @@ class RemotesAPI:
 
         :param pattern: single ``str`` or list of ``str``. If the pattern is an exact name without
           wildcards like "*" and no remote is found matching that exact name, it will raise an error.
-        :return: The list of removed ``Remote`` objects
+        :return: The list of removed :ref:`Remote <conan.api.model.Remote>` objects
         """
         remotes = _load(self._remotes_file)
         removed = _filter(remotes, pattern, only_enabled=False)
@@ -217,7 +220,7 @@ class RemotesAPI:
         """
         Perform user authentication against the given remote with the provided username and password
 
-        :param remote: a ``Remote`` object
+        :param remote: a :ref:`Remote <conan.api.model.Remote>` object
         :param username: the user login as ``str``
         :param password: password ``str``
         """
@@ -226,9 +229,9 @@ class RemotesAPI:
 
     def user_logout(self, remote: Remote):
         """
-        Logout from the given ``Remote``
+        Logout from the given :ref:`Remote <conan.api.model.Remote>`
 
-        :param remote: The ``Remote`` object to logout
+        :param remote: The :ref:`Remote <conan.api.model.Remote>` object to logout
         """
         localdb = LocalDB(self._home_folder)
         # The localdb only stores url + username + token, not remote name, so use URL as key
diff --git a/conan/api/subapi/workspace.py b/conan/api/subapi/workspace.py
index 9485e769e..02e012314 100644
--- a/conan/api/subapi/workspace.py
+++ b/conan/api/subapi/workspace.py
@@ -19,9 +19,15 @@ class WorkspaceAPI:
         self._workspace = Workspace(conan_api)
 
     def home_folder(self):
+        """
+        @return: The custom defined Conan home/cache folder if defined, else None
+        """
         return self._workspace.home_folder()
 
     def folder(self):
+        """
+        @return: the current workspace folder where the conanws.yml or conanws.py is located
+        """
         return self._workspace.folder
 
     def config_folder(self):
@@ -31,6 +37,10 @@ class WorkspaceAPI:
     def editable_packages(self):
         return self._workspace.editables()
 
+    @property
+    def products(self):
+        return self._workspace.products
+
     def open(self, require, remotes, cwd=None):
         app = ConanApp(self._conan_api)
         ref = RecipeReference.loads(require)
@@ -63,21 +73,37 @@ class WorkspaceAPI:
         return dst_path
 
     def add(self, path, name=None, version=None, user=None, channel=None, cwd=None,
-            output_folder=None, remotes=None):
-        path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
+            output_folder=None, remotes=None, product=False):
+        """
+        Add a new editable package to the current workspace (the current workspace must exist)
+        @param path: The path to the folder containing the conanfile.py that defines the package
+        @param name: (optional) The name of the package to be added if not defined in recipe
+        @param version:
+        @param user:
+        @param channel:
+        @param cwd:
+        @param output_folder:
+        @param remotes:
+        @param product:
+        @return: The reference of the added package
+        """
+        full_path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
         app = ConanApp(self._conan_api)
-        conanfile = app.loader.load_named(path, name, version, user, channel, remotes=remotes)
+        conanfile = app.loader.load_named(full_path, name, version, user, channel, remotes=remotes)
         if conanfile.name is None or conanfile.version is None:
             raise ConanException("Editable package recipe should declare its name and version")
         ref = RecipeReference(conanfile.name, conanfile.version, conanfile.user, conanfile.channel)
         ref.validate_ref()
         output_folder = make_abs_path(output_folder) if output_folder else None
         # Check the conanfile is there, and name/version matches
-        self._workspace.add(ref, path, output_folder=output_folder)
+        self._workspace.add(ref, full_path, output_folder=output_folder,  product=product)
         return ref
 
-    def remove(self, path):
+    def remove(self, path, cwd=None):
         return self._workspace.remove(path)
 
     def info(self):
         return self._workspace.serialize()
+
+    def editable_from_path(self, path):
+        return self._workspace.editable_from_path(path)
diff --git a/conan/cli/args.py b/conan/cli/args.py
index d25b2c292..95b889c45 100644
--- a/conan/cli/args.py
+++ b/conan/cli/args.py
@@ -7,18 +7,22 @@ _help_build_policies = '''Optional, specify which packages to build from source.
     '--build' options on one command line is allowed.
     Possible values:
 
-    --build="*"        Force build from source for all packages.
     --build=never      Disallow build for all packages, use binary packages or fail if a binary
                        package is not found, it cannot be combined with other '--build' options.
     --build=missing    Build packages from source whose binary package is not found.
     --build=cascade    Build packages from source that have at least one dependency being built from
                        source.
     --build=[pattern]  Build packages from source whose package reference matches the pattern. The
-                       pattern uses 'fnmatch' style wildcards.
+                       pattern uses 'fnmatch' style wildcards, so '--build="*"' will build everything
+                       from source.
     --build=~[pattern] Excluded packages, which will not be built from the source, whose package
                        reference matches the pattern. The pattern uses 'fnmatch' style wildcards.
     --build=missing:[pattern] Build from source if a compatible binary does not exist, only for
                               packages matching pattern.
+    --build=compatible:[pattern] (Experimental) Build from source if a compatible binary does not
+                                 exist, and the requested package is invalid, the closest package
+                                 binary following the defined compatibility policies (method and
+                                 compatibility.py)
 '''
 
 
diff --git a/conan/cli/command.py b/conan/cli/command.py
index 5447203b3..4a7e80971 100644
--- a/conan/cli/command.py
+++ b/conan/cli/command.py
@@ -4,7 +4,6 @@ from contextlib import redirect_stdout
 
 from conan.api.output import ConanOutput
 from conan.errors import ConanException
-from conan.internal.model.conf import CORE_CONF_PATTERN
 
 
 class OnceArgument(argparse.Action):
@@ -126,14 +125,7 @@ class ConanArgumentParser(argparse.ArgumentParser):
             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 conan.internal.model.conf import ConfDefinition
-            confs = ConfDefinition()
-            for c in args.core_conf:
-                if not CORE_CONF_PATTERN.match(c):
-                    raise ConanException(f"Only core. values are allowed in --core-conf. Got {c}")
-            confs.loads("\n".join(args.core_conf))
-            confs.validate()
-            global_conf.update_conf_definition(confs)
+            self._conan_api.config.set_core_confs(args.core_conf)
 
         # TODO: This might be even better moved to the ConanAPI so users without doing custom
         #  commands can benefit from it
diff --git a/conan/cli/commands/config.py b/conan/cli/commands/config.py
index 9aeaa2e5f..8454d1401 100644
--- a/conan/cli/commands/config.py
+++ b/conan/cli/commands/config.py
@@ -1,3 +1,4 @@
+from conan.api.input import UserInput
 from conan.api.model import Remote
 from conan.api.output import cli_out_write
 from conan.cli.command import conan_command, conan_subcommand, OnceArgument
@@ -132,3 +133,12 @@ def config_show(conan_api, parser, subparser, *args):
     args = parser.parse_args(*args)
 
     return conan_api.config.show(args.pattern)
+
+
+@conan_subcommand()
+def config_clean(conan_api, parser, subparser, *args):
+    """
+    Clean the configuration files in the Conan home folder. (Keeping installed packages)
+    """
+    parser.parse_args(*args)
+    conan_api.config.clean()
diff --git a/conan/cli/commands/new.py b/conan/cli/commands/new.py
index ecc961589..374f9548e 100644
--- a/conan/cli/commands/new.py
+++ b/conan/cli/commands/new.py
@@ -16,7 +16,7 @@ def new(conan_api, parser, *args):
                         "either a predefined built-in or a user-provided one. "
                         "Available built-in templates: basic, cmake_lib, cmake_exe, "
                         "meson_lib, meson_exe, msbuild_lib, msbuild_exe, bazel_lib, bazel_exe, "
-                        "autotools_lib, autotools_exe, local_recipes_index. "
+                        "autotools_lib, autotools_exe, local_recipes_index, workspace. "
                         "E.g. 'conan new cmake_lib -d name=hello -d version=0.1'. "
                         "You can define your own templates too by inputting an absolute path "
                         "as your template, or a path relative to your conan home folder."
diff --git a/conan/cli/commands/workspace.py b/conan/cli/commands/workspace.py
index bbf19b607..3f3bbdcc3 100644
--- a/conan/cli/commands/workspace.py
+++ b/conan/cli/commands/workspace.py
@@ -4,9 +4,11 @@ import os
 from conan.api.conan_api import ConanAPI
 from conan.api.output import ConanOutput, cli_out_write
 from conan.cli import make_abs_path
-from conan.cli.args import add_reference_args
+from conan.cli.args import add_reference_args, add_common_install_arguments, add_lockfile_args
 from conan.cli.command import conan_command, conan_subcommand
 from conan.cli.commands.list import print_serial
+from conan.cli.printers import print_profiles
+from conan.cli.printers.graph import print_graph_packages, print_graph_basic
 from conan.errors import ConanException
 
 
@@ -56,6 +58,7 @@ def workspace_add(conan_api: ConanAPI, parser, subparser, *args):
                        help='Look in the specified remote or remotes server')
     group.add_argument("-nr", "--no-remote", action="store_true",
                        help='Do not use remote, resolve exclusively in the cache')
+    subparser.add_argument("--product", action="store_true", help="Add the package as a product")
     args = parser.parse_args(*args)
     if args.path and args.ref:
         raise ConanException("Do not use both 'path' and '--ref' argument")
@@ -67,7 +70,7 @@ def workspace_add(conan_api: ConanAPI, parser, subparser, *args):
         path = conan_api.workspace.open(args.ref, remotes, cwd=cwd)
     ref = conan_api.workspace.add(path,
                                   args.name, args.version, args.user, args.channel,
-                                  cwd, args.output_folder, remotes=remotes)
+                                  cwd, args.output_folder, remotes=remotes, product=args.product)
     ConanOutput().success("Reference '{}' added to workspace".format(ref))
 
 
@@ -101,6 +104,66 @@ def workspace_info(conan_api: ConanAPI, parser, subparser, *args):
     return {"info": conan_api.workspace.info()}
 
 
+@conan_subcommand()
+def workspace_build(conan_api: ConanAPI, parser, subparser, *args):
+    """
+    Build the current workspace, starting from the "products"
+    """
+    subparser.add_argument("path", nargs="?",
+                           help='Path to a package folder in the user workspace')
+    add_common_install_arguments(subparser)
+    add_lockfile_args(subparser)
+    args = parser.parse_args(*args)
+    # Basic collaborators: remotes, lockfile, profiles
+    remotes = conan_api.remotes.list(args.remote) if not args.no_remote else []
+    overrides = eval(args.lockfile_overrides) if args.lockfile_overrides else None
+    # The lockfile by default if not defined will be read from the root workspace folder
+    ws_folder = conan_api.workspace.folder()
+    lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile, conanfile_path=ws_folder,
+                                               cwd=None,
+                                               partial=args.lockfile_partial, overrides=overrides)
+    profile_host, profile_build = conan_api.profiles.get_profiles_from_args(args)
+    print_profiles(profile_host, profile_build)
+
+    build_mode = args.build or []
+    if "editable" not in build_mode:
+        ConanOutput().info("Adding '--build=editable' as build mode")
+        build_mode.append("editable")
+
+    if args.path:
+        products = [args.path]
+    else:  # all products
+        products = conan_api.workspace.products
+        if products is None:
+            raise ConanException("There are no products defined in the workspace, can't build\n"
+                                 "You can use 'conan build <path> --build=editable' to build")
+        ConanOutput().title(f"Building workspace products {products}")
+
+    editables = conan_api.workspace.editable_packages
+    # TODO: This has to be improved to avoid repetition when there are multiple products
+    for product in products:
+        ConanOutput().subtitle(f"Building workspace product: {product}")
+        product_ref = conan_api.workspace.editable_from_path(product)
+        if product_ref is None:
+            raise ConanException(f"Product '{product}' not defined in the workspace as editable")
+        editable = editables[product_ref]
+        editable_path = editable["path"]
+        deps_graph = conan_api.graph.load_graph_consumer(editable_path, None, None, None, None,
+                                                         profile_host, profile_build, lockfile,
+                                                         remotes, args.update)
+        deps_graph.report_graph_error()
+        print_graph_basic(deps_graph)
+        conan_api.graph.analyze_binaries(deps_graph, build_mode, remotes=remotes, update=args.update,
+                                         lockfile=lockfile)
+        print_graph_packages(deps_graph)
+        conan_api.install.install_binaries(deps_graph=deps_graph, remotes=remotes)
+        conan_api.install.install_consumer(deps_graph, None, os.path.dirname(editable_path),
+                                           editable.get("output_folder"))
+        ConanOutput().title(f"Calling build() for the product {product_ref}")
+        conanfile = deps_graph.root.conanfile
+        conan_api.local.build(conanfile)
+
+
 @conan_command(group="Consumer")
 def workspace(conan_api, parser, *args):
     """
diff --git a/conan/internal/api/new/workspace.py b/conan/internal/api/new/workspace.py
new file mode 100644
index 000000000..7a56617eb
--- /dev/null
+++ b/conan/internal/api/new/workspace.py
@@ -0,0 +1,33 @@
+from conan.api.subapi.new import NewAPI
+from conan.internal.api.new.cmake_exe import cmake_exe_files
+from conan.internal.api.new.cmake_lib import cmake_lib_files
+
+
+conanws_yml = """\
+editables:
+  liba/0.1:
+    path: liba
+  libb/0.1:
+    path: libb
+  app1/0.1:
+    path: app1
+products:
+- app1
+"""
+
+workspace_files = {"conanws.yml": conanws_yml}
+
+for lib in ("liba", "libb"):
+    definitions = {"name": lib}
+    if lib == "libb":
+        definitions["requires"] = ["liba/0.1"]
+    elif lib == "libc":
+        definitions["requires"] = ["libb/0.1"]
+    files = NewAPI.render(cmake_lib_files, definitions)
+    files = {f"{lib}/{k}": v for k, v in files.items()}
+    workspace_files.update(files)
+
+
+files = NewAPI.render(cmake_exe_files, definitions={"name": "app1", "requires": ["libb/0.1"]})
+files = {f"app1/{k}": v for k, v in files.items()}
+workspace_files.update(files)
diff --git a/conan/internal/cache/db/table.py b/conan/internal/cache/db/table.py
index 5ad675ad6..e4e150d26 100644
--- a/conan/internal/cache/db/table.py
+++ b/conan/internal/cache/db/table.py
@@ -23,8 +23,8 @@ class BaseDbTable:
 
     @contextmanager
     def db_connection(self):
-        assert self._lock.acquire(timeout=10), "Conan failed to acquire database lock"
-        connection = sqlite3.connect(self.filename, isolation_level=None, timeout=10)
+        assert self._lock.acquire(timeout=20), "Conan failed to acquire database lock"
+        connection = sqlite3.connect(self.filename, isolation_level=None, timeout=20)
         try:
             yield connection
         finally:
diff --git a/conan/internal/cache/integrity_check.py b/conan/internal/cache/integrity_check.py
index 7e48b3a22..58dec88ea 100644
--- a/conan/internal/cache/integrity_check.py
+++ b/conan/internal/cache/integrity_check.py
@@ -32,7 +32,11 @@ class IntegrityChecker:
     def _recipe_corrupted(self, ref: RecipeReference):
         layout = self._cache.recipe_layout(ref)
         output = ConanOutput()
-        read_manifest, expected_manifest = layout.recipe_manifests()
+        try:
+            read_manifest, expected_manifest = layout.recipe_manifests()
+        except FileNotFoundError:
+            output.error(f"{ref.repr_notime()}: Manifest missing", error_type="exception")
+            return True
         # Filter exports_sources from read manifest if there are no exports_sources locally
         # This happens when recipe is downloaded without sources (not built from source)
         export_sources_folder = layout.export_sources()
@@ -52,7 +56,11 @@ class IntegrityChecker:
     def _package_corrupted(self, ref: PkgReference):
         layout = self._cache.pkg_layout(ref)
         output = ConanOutput()
-        read_manifest, expected_manifest = layout.package_manifests()
+        try:
+            read_manifest, expected_manifest = layout.package_manifests()
+        except FileNotFoundError:
+            output.error(f"{ref.repr_notime()}: Manifest missing", error_type="exception")
+            return True
 
         if read_manifest != expected_manifest:
             output.error(f"{ref}: Manifest mismatch", error_type="exception")
diff --git a/conan/internal/model/conan_file.py b/conan/internal/model/conan_file.py
index 3af81e440..58fb90754 100644
--- a/conan/internal/model/conan_file.py
+++ b/conan/internal/model/conan_file.py
@@ -189,6 +189,10 @@ class ConanFile:
     def context(self):
         return self._conan_node.context
 
+    @property
+    def subgraph(self):
+        return self._conan_node.subgraph()
+
     @property
     def dependencies(self):
         # Caching it, this object is requested many times
diff --git a/conan/internal/model/conf.py b/conan/internal/model/conf.py
index 065af3098..f7b9bcc32 100644
--- a/conan/internal/model/conf.py
+++ b/conan/internal/model/conf.py
@@ -33,6 +33,7 @@ BUILT_IN_CONFS = {
     "core.sources:download_urls": "List of URLs to download backup sources from",
     "core.sources:upload_url": "Remote URL to upload backup sources to",
     "core.sources:exclude_urls": "URLs which will not be backed up",
+    "core.sources.patch:extra_path": "Extra path to search for patch files for conan create",
     # Package ID
     "core.package_id:default_unknown_mode": "By default, 'semver_mode'",
     "core.package_id:default_non_embed_mode": "By default, 'minor_mode'",
@@ -76,6 +77,7 @@ BUILT_IN_CONFS = {
     "tools.cmake.cmaketoolchain:presets_environment": "String to define wether to add or not the environment section to the CMake presets. Empty by default, will generate the environment section in CMakePresets. Can take values: 'disabled'.",
     "tools.cmake.cmaketoolchain:extra_variables": "Dictionary with variables to be injected in CMakeToolchain (potential override of CMakeToolchain defined variables)",
     "tools.cmake.cmaketoolchain:enabled_blocks": "Select the specific blocks to use in the conan_toolchain.cmake",
+    "tools.cmake.cmaketoolchain:user_presets": "(Experimental) Select a different name instead of CMakeUserPresets.json, empty to disable",
     "tools.cmake.cmake_layout:build_folder_vars": "Settings and Options that will produce a different build folder and different CMake presets names",
     "tools.cmake.cmake_layout:build_folder": "(Experimental) Allow configuring the base folder of the build for local builds",
     "tools.cmake.cmake_layout:test_folder": "(Experimental) Allow configuring the base folder of the build for test_package",
@@ -705,3 +707,6 @@ class ConfDefinition:
     def validate(self):
         for conf in self._pattern_confs.values():
             conf.validate()
+
+    def clear(self):
+        self._pattern_confs.clear()
diff --git a/conan/internal/model/cpp_info.py b/conan/internal/model/cpp_info.py
index af66f87e6..05f9af4c2 100644
--- a/conan/internal/model/cpp_info.py
+++ b/conan/internal/model/cpp_info.py
@@ -822,7 +822,7 @@ class CppInfo:
 
             for lib in self.libs:
                 c = _Component()  # Do not do a full clone, we don't need the properties
-                c.type = self.type  # This should be a string
+                c.type = self.type
                 c.includedirs = self.includedirs
                 c.libdirs = self.libdirs
                 c.bindirs = self.bindirs
diff --git a/conan/internal/model/dependencies.py b/conan/internal/model/dependencies.py
index a1b0d3882..22353419e 100644
--- a/conan/internal/model/dependencies.py
+++ b/conan/internal/model/dependencies.py
@@ -130,7 +130,7 @@ class ConanFileDependencies(UserRequirementsDict):
         for k, v in self._data.items():
             for otherk, otherv in other._data.items():
                 if v == otherv:
-                    data[k] = v
+                    data[otherk] = v  # Use otherk to respect original replace_requires
         return ConanFileDependencies(data)
 
     @property
@@ -182,6 +182,8 @@ def get_transitive_requires(consumer, dependency):
     # The build dependencies cannot be transitive in generators like CMakeDeps,
     # even if users make them visible
     pkg_deps = dependency.dependencies.filter({"direct": True, "build": False})
-    result = consumer.dependencies.transitive_requires(pkg_deps)
-    result = result.filter({"skip": False})
+    # First we filter the skipped dependencies
+    result = consumer.dependencies.filter({"skip": False})
+    # and we keep those that are really dependencies of the current package
+    result = result.transitive_requires(pkg_deps)
     return result
diff --git a/conan/internal/model/requires.py b/conan/internal/model/requires.py
index 2c3d03451..f5e5aae6f 100644
--- a/conan/internal/model/requires.py
+++ b/conan/internal/model/requires.py
@@ -15,6 +15,7 @@ class Requirement:
         # * prevents the usage of more positional parameters, always ref + **kwargs
         # By default this is a generic library requirement
         self.ref = ref
+        self._required_ref = ref  # Store the original reference
         self._headers = headers  # This dependent node has headers that must be -I<headers-path>
         self._libs = libs
         self._build = build  # This dependent node is a build tool that runs at build time only
@@ -152,7 +153,8 @@ class Requirement:
         return "{}, Traits: {}".format(self.ref, traits)
 
     def serialize(self):
-        result = {"ref": str(self.ref)}
+        result = {"ref": str(self.ref),
+                  "require": str(self._required_ref)}
         serializable = ("run", "libs", "skip", "test", "force", "direct", "build",
                         "transitive_headers", "transitive_libs", "headers",
                         "package_id_mode", "visible")
diff --git a/conan/internal/workspace.py b/conan/internal/workspace.py
index 1abd54ebe..ae076e6db 100644
--- a/conan/internal/workspace.py
+++ b/conan/internal/workspace.py
@@ -72,6 +72,10 @@ class Workspace:
     def name(self):
         return self._attr("name") or os.path.basename(self._folder)
 
+    @property
+    def products(self):
+        return self._attr("products")
+
     @property
     def folder(self):
         return self._folder
@@ -102,17 +106,21 @@ class Workspace:
             raise ConanException("Workspace not defined, please create a "
                                  "'conanws.py' or 'conanws.yml' file")
 
-    def add(self, ref, path, output_folder):
+    def add(self, ref, path, output_folder, product=False):
         """
         Add a new editable to the current workspace 'conanws.yml' file.
         If existing, the 'conanws.py' must use this via 'conanws_data' attribute
         """
         self._check_ws()
         self._yml = self._yml or {}
-        editable = {"path": self._rel_path(path)}
+        assert os.path.isfile(path)
+        path = self._rel_path(os.path.dirname(path))
+        editable = {"path": path}
         if output_folder:
             editable["output_folder"] = self._rel_path(output_folder)
         self._yml.setdefault("editables", {})[str(ref)] = editable
+        if product:
+            self._yml.setdefault("products", []).append(path)
         save(self._yml_file, yaml.dump(self._yml))
 
     def _rel_path(self, path):
@@ -126,29 +134,40 @@ class Workspace:
                                  f"{self._folder}")
         return path.replace("\\", "/")  # Normalize to unix path
 
+    def editable_from_path(self, path):
+        editables = self._attr("editables")
+        for ref, info in editables.items():
+            if info["path"].replace("\\", "/") == path:
+                return RecipeReference.loads(ref)
+
     def remove(self, path):
         self._check_ws()
         self._yml = self._yml or {}
         found_ref = None
         path = self._rel_path(path)
         for ref, info in self._yml.get("editables", {}).items():
-            if os.path.dirname(info["path"]).replace("\\", "/") == path:
+            if info["path"].replace("\\", "/") == path:
                 found_ref = ref
                 break
         if not found_ref:
             raise ConanException(f"No editable package to remove from this path: {path}")
         self._yml["editables"].pop(found_ref)
+        if path in self._yml.get("products", []):
+            self._yml["products"].remove(path)
         save(self._yml_file, yaml.dump(self._yml))
         return found_ref
 
     def editables(self):
+        """
+        @return: Returns {RecipeReference: {"path": full abs-path, "output_folder": abs-path}}
+        """
         if not self._folder:
             return
         editables = self._attr("editables")
         if editables:
             editables = {RecipeReference.loads(r): v.copy() for r, v in editables.items()}
             for v in editables.values():
-                v["path"] = os.path.normpath(os.path.join(self._folder, v["path"]))
+                v["path"] = os.path.normpath(os.path.join(self._folder, v["path"], "conanfile.py"))
                 if v.get("output_folder"):
                     v["output_folder"] = os.path.normpath(os.path.join(self._folder,
                                                                        v["output_folder"]))
@@ -158,4 +177,5 @@ class Workspace:
         self._check_ws()
         return {"name": self.name,
                 "folder": self._folder,
+                "products": self._attr("products"),
                 "editables": self._attr("editables")}
diff --git a/conan/tools/cmake/__init__.py b/conan/tools/cmake/__init__.py
index 73c8a835b..e57f14a65 100644
--- a/conan/tools/cmake/__init__.py
+++ b/conan/tools/cmake/__init__.py
@@ -7,7 +7,7 @@ def CMakeDeps(conanfile):  # noqa
     if conanfile.conf.get("tools.cmake.cmakedeps:new", choices=["will_break_next"]):
         from conan.tools.cmake.cmakedeps2.cmakedeps import CMakeDeps2
         conanfile.output.warning("Using the new CMakeDeps generator, behind the "
-                                 "'tools.cmake.cmakedeps:new' gate conf. This conf will change"
+                                 "'tools.cmake.cmakedeps:new' gate conf. This conf will change "
                                  "next release, breaking, so use it only for testing and dev")
         return CMakeDeps2(conanfile)
     from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps as _CMakeDeps
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index 0cb24590d..be3967ffd 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -67,10 +67,12 @@ class TargetConfigurationTemplate2:
                         # It must be the interface pkgname::pkgname target
                         assert required_pkg == required_comp
                         comp = None
+                        default_target = f"{dep.ref.name}::{dep.ref.name}"  # replace_requires
                     else:
                         comp = required_comp
+                        default_target = f"{required_pkg}::{required_comp}"
                     dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp)
-                    dep_target = dep_target or f"{required_pkg}::{required_comp}"
+                    dep_target = dep_target or default_target
                     result.append(dep_target)
         return result
 
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index b7e616150..cd7e3db27 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -261,6 +261,7 @@ class _IncludingPresets:
         if not (conanfile.source_folder and conanfile.source_folder != conanfile.generators_folder):
             return
 
+        is_default = user_presets_path == "CMakeUserPresets.json"
         user_presets_path = os.path.join(conanfile.source_folder, user_presets_path)
         if os.path.isdir(user_presets_path):  # Allows user to specify only the folder
             output_dir = user_presets_path
@@ -268,7 +269,7 @@ class _IncludingPresets:
         else:
             output_dir = os.path.dirname(user_presets_path)
 
-        if not os.path.exists(os.path.join(output_dir, "CMakeLists.txt")):
+        if is_default and not os.path.exists(os.path.join(output_dir, "CMakeLists.txt")):
             return
 
         # It uses schema version 4 unless it is forced to 2
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 36ad0e925..6abeb5609 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -22,6 +22,7 @@ from conan.tools.microsoft import VCVars
 from conan.tools.microsoft.visual import vs_ide_version
 from conan.errors import ConanException
 from conan.internal.model.options import _PackageOption
+from conans.client.graph.graph import RECIPE_CONSUMER, RECIPE_EDITABLE
 from conans.util.files import save
 
 
@@ -210,8 +211,17 @@ class CMakeToolchain:
 
             cmake_executable = self._conanfile.conf.get("tools.cmake:cmake_program", None) or self._find_cmake_exe()
 
+        user_presets = self.user_presets_path
+        try:  # TODO: Refactor this repeated pattern to deduce "is-consumer"
+            # The user conf user_presets ONLY applies to dev space, not in the cache
+            if self._conanfile._conan_node.recipe in (RECIPE_CONSUMER, RECIPE_EDITABLE):
+                user_presets = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_presets",
+                                                        default=self.user_presets_path)
+        except AttributeError:
+            pass
+
         write_cmake_presets(self._conanfile, toolchain_file, self.generator, cache_variables,
-                            self.user_presets_path, self.presets_prefix, buildenv, runenv,
+                            user_presets, self.presets_prefix, buildenv, runenv,
                             cmake_executable, self.absolute_paths)
 
     def _get_generator(self, recipe_generator):
diff --git a/conan/tools/files/conandata.py b/conan/tools/files/conandata.py
index 3b1c47600..3fa41d59f 100644
--- a/conan/tools/files/conandata.py
+++ b/conan/tools/files/conandata.py
@@ -69,5 +69,8 @@ def trim_conandata(conanfile, raise_if_missing=True):
         if version_data is not None:
             result[k] = {version: version_data}
 
+    # Update the internal conanfile data too
+    conanfile.conan_data = result
+
     new_conandata_yml = yaml.safe_dump(result, default_flow_style=False)
     save(path, new_conandata_yml)
diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py
index 6cbb550cd..433c30e2d 100644
--- a/conan/tools/files/files.py
+++ b/conan/tools/files/files.py
@@ -224,7 +224,8 @@ def rename(conanfile, src, dst):
     :param dst: Path to be renamed to.
     """
 
-    # FIXME: This function has been copied from legacy. Needs to fix: which() call and wrap subprocess call.
+    # FIXME: This function has been copied from legacy. Needs to fix: which()
+    # call and wrap subprocess call.
     if os.path.exists(dst):
         raise ConanException("rename {} to {} failed, dst exists.".format(src, dst))
 
@@ -310,7 +311,7 @@ def unzip(conanfile, filename, destination=".", keep_permissions=False, pattern=
         if pattern:
             zip_info = [zi for zi in zip_info if fnmatch(zi.filename, pattern)]
         if strip_root:
-            names = [n.replace("\\", "/") for n in z.namelist()]
+            names = [zi.filename.replace("\\", "/") for zi in zip_info]
             common_folder = os.path.commonprefix(names).split("/", 1)[0]
             if not common_folder and len(names) > 1:
                 raise ConanException("The zip file contains more than 1 folder in the root")
@@ -363,8 +364,11 @@ def untargz(filename, destination=".", pattern=None, strip_root=False, extract_f
         else:
             members = tarredgzippedFile.getmembers()
 
+            if pattern:
+                members = list(filter(lambda m: fnmatch(m.name, pattern),
+                                      tarredgzippedFile.getmembers()))
             if strip_root:
-                names = [n.replace("\\", "/") for n in tarredgzippedFile.getnames()]
+                names = [member.name.replace("\\", "/") for member in members]
                 common_folder = os.path.commonprefix(names).split("/", 1)[0]
                 if not common_folder and len(names) > 1:
                     raise ConanException("The tgz file contains more than 1 folder in the root")
@@ -381,9 +385,6 @@ def untargz(filename, destination=".", pattern=None, strip_root=False, extract_f
                         linkpath = member.linkpath.replace("\\", "/")
                         member.linkpath = linkpath.split("/", 1)[1]
                         member.linkname = member.linkpath
-            if pattern:
-                members = list(filter(lambda m: fnmatch(m.name, pattern),
-                                      tarredgzippedFile.getmembers()))
             tarredgzippedFile.extractall(destination, members=members)
 
 
diff --git a/conan/tools/files/patches.py b/conan/tools/files/patches.py
index 5c35641d7..834a61b68 100644
--- a/conan/tools/files/patches.py
+++ b/conan/tools/files/patches.py
@@ -3,9 +3,11 @@ import os
 import shutil
 
 import patch_ng
+import yaml
 
 from conan.errors import ConanException
-from conans.util.files import mkdir
+from conan.internal.paths import DATA_YML
+from conans.util.files import mkdir, load, save
 
 
 class PatchLogHandler(logging.Handler):
@@ -122,27 +124,60 @@ def export_conandata_patches(conanfile):
     if conanfile.conan_data is None:
         raise ConanException("conandata.yml not defined")
 
-    patches = conanfile.conan_data.get('patches')
-    if patches is None:
-        conanfile.output.info("export_conandata_patches(): No patches defined in conandata")
-        return
+    conanfile_patches = conanfile.conan_data.get('patches')
 
-    if isinstance(patches, dict):
-        assert conanfile.version, "Can only be exported if conanfile.version is already defined"
-        entries = patches.get(conanfile.version, [])
-        if entries is None:
-            conanfile.output.warning(f"export_conandata_patches(): No patches defined for version {conanfile.version} in conandata.yml")
+    def _handle_patches(patches, patches_folder):
+        if patches is None:
+            conanfile.output.info("export_conandata_patches(): No patches defined in conandata")
             return
-    elif isinstance(patches, list):
-        entries = patches
-    else:
-        raise ConanException("conandata.yml 'patches' should be a list or a dict {version: list}")
-    for it in entries:
-        patch_file = it.get("patch_file")
-        if patch_file:
-            src = os.path.join(conanfile.recipe_folder, patch_file)
-            dst = os.path.join(conanfile.export_sources_folder, patch_file)
-            if not os.path.exists(src):
-                raise ConanException(f"Patch file does not exist: '{src}'")
-            mkdir(os.path.dirname(dst))
-            shutil.copy2(src, dst)
+
+        if isinstance(patches, dict):
+            assert conanfile.version, "Can only be exported if conanfile.version is already defined"
+            entries = patches.get(conanfile.version, [])
+            if entries is None:
+                conanfile.output.warning("export_conandata_patches(): No patches defined for "
+                                         f"version {conanfile.version} in conandata.yml")
+                return
+        elif isinstance(patches, list):
+            entries = patches
+        else:
+            raise ConanException("conandata.yml 'patches' should be a list or a dict "
+                                 "{version: list}")
+        for it in entries:
+            patch_file = it.get("patch_file")
+            if patch_file:
+                src = os.path.join(patches_folder, patch_file)
+                dst = os.path.join(conanfile.export_sources_folder, patch_file)
+                if not os.path.exists(src):
+                    raise ConanException(f"Patch file does not exist: '{src}'")
+                mkdir(os.path.dirname(dst))
+                shutil.copy2(src, dst)
+        return entries
+
+    _handle_patches(conanfile_patches, conanfile.recipe_folder)
+
+    extra_path = conanfile.conf.get("core.sources.patch:extra_path")
+    if extra_path:
+        if not os.path.isdir(extra_path):
+            raise ConanException(f"Patches extra path '{extra_path}' does not exist")
+        pkg_path = os.path.join(extra_path, conanfile.name)
+        if not os.path.isdir(pkg_path):
+            return
+        data_path = os.path.join(pkg_path, DATA_YML)
+        try:
+            data = yaml.safe_load(load(data_path))
+        except Exception as e:
+            raise ConanException("Invalid yml format at {}: {}".format(data_path, e))
+        data = data or {}
+        conanfile.output.info(f"Applying extra patches 'core.sources.patch:extra_path': {data_path}")
+        new_patches = _handle_patches(data.get('patches'), pkg_path)
+
+        # Update the CONANDATA.YML
+        conanfile_patches = conanfile_patches or {}
+        conanfile_patches.setdefault(conanfile.version, []).extend(new_patches)
+
+        conanfile.conan_data['patches'] = conanfile_patches
+        # Saving in the EXPORT folder
+        conanfile_data_path = os.path.join(conanfile.export_folder, DATA_YML)
+        new_conandata_yml = yaml.safe_dump(conanfile.conan_data, default_flow_style=False)
+        save(conanfile_data_path, new_conandata_yml)
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index e2daf568f..fd5421cc8 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -257,7 +257,7 @@ class AutotoolsToolchain:
         env.append("LDFLAGS", self.ldflags)
         env.prepend_path("PKG_CONFIG_PATH", self._conanfile.generators_folder)
         # Issue related: https://github.com/conan-io/conan/issues/15486
-        if self._is_cross_building and self._conanfile.conf_build:
+        if self._is_cross_building and getattr(self._conanfile, 'conf_build', False):
             compilers_build_mapping = (
                 self._conanfile.conf_build.get("tools.build:compiler_executables", default={},
                                                check_type=dict)
diff --git a/conan/tools/gnu/gnutoolchain.py b/conan/tools/gnu/gnutoolchain.py
index 1b22240e8..a23f4bf0b 100644
--- a/conan/tools/gnu/gnutoolchain.py
+++ b/conan/tools/gnu/gnutoolchain.py
@@ -186,7 +186,7 @@ class GnuToolchain:
                     compiler = unix_path(self._conanfile, compiler)
                     ret[env_var] = compiler  # User/tools ones have precedence
         # Issue related: https://github.com/conan-io/conan/issues/15486
-        if self._is_cross_building and self._conanfile.conf_build:
+        if self._is_cross_building and getattr(self._conanfile, 'conf_build', False):
             compilers_build_mapping = (
                 self._conanfile.conf_build.get("tools.build:compiler_executables", default={},
                                                check_type=dict)
diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py
index 5ef67d55d..205b2d872 100644
--- a/conan/tools/gnu/pkgconfigdeps.py
+++ b/conan/tools/gnu/pkgconfigdeps.py
@@ -182,7 +182,7 @@ class _PCGenerator:
                     continue  # If the dependency is not in the transitive, might be skipped
             else:  # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello
                 req_conanfile = self._dep
-            comp_name = self._get_component_name(req_conanfile, comp_ref_name)
+            comp_name = self._get_component_name(req_conanfile, pkg_ref_name, comp_ref_name)
             if not comp_name:
                 pkg_name = self._get_package_name(req_conanfile)
                 # Creating a component name with namespace, e.g., dep-comp1
@@ -203,13 +203,13 @@ class _PCGenerator:
         for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items():
             # At first, let's check if we have defined some components requires, e.g., "dep::cmp1"
             comp_requires_names = self._get_cpp_info_requires_names(cpp_info)
-            comp_name = self._get_component_name(self._dep, comp_ref_name)
+            comp_name = self._get_component_name(self._dep, pkg_name, comp_ref_name)
             if not comp_name:
                 comp_name = self._get_name_with_namespace(pkg_name, comp_ref_name)
                 comp_description = f"Conan component: {comp_name}"
             else:
                 comp_description = f"Conan component: {pkg_name}-{comp_name}"
-            comp_aliases = self._get_component_aliases(self._dep, comp_ref_name)
+            comp_aliases = self._get_component_aliases(self._dep, pkg_name, comp_ref_name)
             comp_version = (self.get_property("component_version", self._dep, comp_ref_name) or
                             self.get_property("system_package_version", self._dep, comp_ref_name) or
                             self._dep.ref.version)
@@ -314,10 +314,14 @@ class _PCGenerator:
         pkg_aliases = self.get_property("pkg_config_aliases", dep, check_type=list)
         return pkg_aliases or []
 
-    def _get_component_aliases(self, dep, comp_name):
+    def _get_component_aliases(self, dep, pkg_name, comp_name):
+        # TODO: LET'S DEPRECATE ALL THE ALIASES MECHANISM!!
         if comp_name not in dep.cpp_info.components:
-            # foo::foo might be referencing the root cppinfo
-            if dep.ref.name == comp_name:
+            # Either foo::foo might be referencing the root cpp_info
+            if (dep.ref.name == comp_name or
+                # Or a "replace_require" is used and cpp_info.requires is the root one, e.g.,
+                # zlib/*: zlib-ng/*, and self.cpp_info.requires = ["zlib::zlib"]
+                (dep.ref.name != pkg_name and pkg_name == comp_name)):
                 return self._get_package_aliases(dep)
             raise ConanException("Component '{name}::{cname}' not found in '{name}' "
                                  "package requirement".format(name=dep.ref.name,
@@ -329,10 +333,13 @@ class _PCGenerator:
         pkg_name = self.get_property("pkg_config_name", dep) or dep.ref.name
         return f"{pkg_name}{self._suffix}"
 
-    def _get_component_name(self, dep, comp_name):
+    def _get_component_name(self, dep, pkg_name, comp_name):
         if comp_name not in dep.cpp_info.components:
-            # foo::foo might be referencing the root cppinfo
-            if dep.ref.name == comp_name:
+            # Either foo::foo might be referencing the root cpp_info
+            if (dep.ref.name == comp_name or
+                # Or a "replace_require" is used and cpp_info.requires is the root one, e.g.,
+                # zlib/*: zlib-ng/*, and self.cpp_info.requires = ["zlib::zlib"]
+                (dep.ref.name != pkg_name and pkg_name == comp_name)):
                 return self._get_package_name(dep)
             raise ConanException("Component '{name}::{cname}' not found in '{name}' "
                                  "package requirement".format(name=dep.ref.name,
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index a3cccea28..62b9213dd 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -9,7 +9,7 @@ from conan.internal.internal_tools import raise_on_universal_arch
 from conan.tools.apple.apple import is_apple_os, apple_min_version_flag, \
     resolve_apple_flags
 from conan.tools.build.cross_building import cross_building
-from conan.tools.build.flags import libcxx_flags
+from conan.tools.build.flags import libcxx_flags, architecture_flag
 from conan.tools.env import VirtualBuildEnv
 from conan.tools.meson.helpers import *
 from conan.tools.microsoft import VCVars, msvc_runtime_flag
@@ -210,6 +210,8 @@ class MesonToolchain:
         #: List of extra preprocessor definitions. Added to ``c_args`` and ``cpp_args`` with the
         #: format ``-D[FLAG_N]``.
         self.extra_defines = []
+        #: Architecture flag deduced by Conan and added to ``c_args``, ``cpp_args``, ``c_link_args`` and ``cpp_link_args``
+        self.arch_flag = architecture_flag(self._conanfile)  # https://github.com/conan-io/conan/issues/17624
         #: Dict-like object that defines Meson ``properties`` with ``key=value`` format
         self.properties = {}
         #: Dict-like object that defines Meson ``project options`` with ``key=value`` format
@@ -429,9 +431,9 @@ class MesonToolchain:
         sys_root = [f"--sysroot={self._sys_root}"] if self._sys_root else [""]
         ld = sharedlinkflags + exelinkflags + linker_script_flags + sys_root + self.extra_ldflags
         return {
-            "cxxflags": cxxflags + sys_root + self.extra_cxxflags,
-            "cflags": cflags + sys_root + self.extra_cflags,
-            "ldflags": ld,
+            "cxxflags": [self.arch_flag] + cxxflags + sys_root + self.extra_cxxflags,
+            "cflags": [self.arch_flag] + cflags + sys_root + self.extra_cflags,
+            "ldflags": [self.arch_flag] + ld,
             "defines": [f"-D{d}" for d in (defines + self.extra_defines)]
         }
 
diff --git a/conan/tools/sbom/__init__.py b/conan/tools/sbom/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py
new file mode 100644
index 000000000..a875833df
--- /dev/null
+++ b/conan/tools/sbom/cyclonedx.py
@@ -0,0 +1,76 @@
+
+def cyclonedx_1_4(graph, **kwargs):
+    """
+    (Experimental) Generate cyclone 1.4 sbom with json format
+    """
+    import uuid
+    import time
+    from datetime import datetime, timezone
+
+    has_special_root_node = not (getattr(graph.root.ref, "name", False) and getattr(graph.root.ref, "version", False) and getattr(graph.root.ref, "revision", False))
+    special_id = str(uuid.uuid4())
+
+    components = [node for node in graph.nodes]
+    if has_special_root_node:
+        components = components[1:]
+
+    dependencies = []
+    if has_special_root_node:
+        deps = {"ref": special_id,
+                "dependsOn": [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}"
+                              for d in graph.root.dependencies]}
+        dependencies.append(deps)
+    for c in components:
+        deps = {"ref": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}"}
+        depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in c.dependencies]
+        if depends_on:
+            deps["dependsOn"] = depends_on
+        dependencies.append(deps)
+
+    def _calculate_licenses(component):
+        if isinstance(component.conanfile.license, str): # Just one license
+            return [{"license": {
+                        "id": component.conanfile.license
+                    }}]
+        return [{"license": {
+                    "id": l
+                }} for l in c.conanfile.license]
+
+    sbom_cyclonedx_1_4 = {
+        **({"components": [{
+            "author": "Conan",
+            "bom-ref": special_id if has_special_root_node else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}",
+            "description": c.conanfile.description,
+            **({"externalReferences": [{
+                "type": "website",
+                "url": c.conanfile.homepage
+            }]} if c.conanfile.homepage else {}),
+            **({"licenses": _calculate_licenses(c)} if c.conanfile.license else {}),
+            "name": c.name,
+            "purl": f"pkg:conan/{c.name}@{c.ref.version}",
+            "type": "library",
+            "version": str(c.ref.version),
+        } for c in components]} if components else {}),
+        **({"dependencies": dependencies} if dependencies else {}),
+        "metadata": {
+            "component": {
+                "author": "Conan",
+                "bom-ref": special_id if has_special_root_node else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}",
+                "name": graph.root.conanfile.display_name,
+                "type": "library"
+            },
+            "timestamp": f"{datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}",
+            "tools": [{
+                "externalReferences": [{
+                    "type": "website",
+                    "url": "https://github.com/conan-io/conan"
+                }],
+                "name": "Conan-io"
+            }],
+        },
+        "serialNumber": f"urn:uuid:{uuid.uuid4()}",
+        "bomFormat": "CycloneDX",
+        "specVersion": "1.4",
+        "version": 1,
+    }
+    return sbom_cyclonedx_1_4
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index 85e615502..d69e64eff 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -15,9 +15,40 @@ from cppstd_compat import cppstd_compat
 
 
 def compatibility(conanfile):
-    configs = cppstd_compat(conanfile)
-    # TODO: Append more configurations for your custom compatibility rules
-    return configs
+    # By default, different compiler.cppstd are compatible
+    # factors is a list of lists
+    factors = cppstd_compat(conanfile)
+
+    # MSVC 194->193 fallback compatibility
+    compiler = conanfile.settings.get_safe("compiler")
+    compiler_version = conanfile.settings.get_safe("compiler.version")
+    if compiler == "msvc":
+        msvc_fallback = {"194": "193"}.get(compiler_version)
+        if msvc_fallback:
+            factors.append([{"compiler.version": msvc_fallback}])
+
+    # Append more factors for your custom compatibility rules here
+
+    # Combine factors to compute all possible configurations
+    combinations = _factors_combinations(factors)
+    # Final compatibility settings combinations to check
+    return [{"settings": [(k, v) for k, v in comb.items()]} for comb in combinations]
+
+
+def _factors_combinations(factors):
+    combinations = []
+    for factor in factors:
+        if not combinations:
+            combinations = factor
+            continue
+        new_combinations = []
+        for comb in combinations:
+            for f in factor:
+                new_comb = comb.copy()
+                new_comb.update(f)
+                new_combinations.append(new_comb)
+        combinations.extend(new_combinations)
+    return combinations
 """
 
 
@@ -51,29 +82,7 @@ def cppstd_compat(conanfile):
             conanfile.output.warning(f'No cstd compatibility defined for compiler "{compiler}"')
         else:
             factors.append([{"compiler.cstd": v} for v in cstd_possible_values if v != cstd])
-
-    if compiler == "msvc":
-        msvc_fallback = {"194": "193"}.get(compiler_version)
-        if msvc_fallback:
-            factors.append([{"compiler.version": msvc_fallback}])
-
-    combinations = []
-    for factor in factors:
-        if not combinations:
-            combinations = factor
-            continue
-        new_combinations = []
-        for comb in combinations:
-            for f in factor:
-                new_comb = comb.copy()
-                new_comb.update(f)
-                new_combinations.append(new_comb)
-        combinations.extend(new_combinations)
-
-    ret = []
-    for comb in combinations:
-        ret.append({"settings": [(k, v) for k, v in comb.items()]})
-    return ret
+    return factors
 """
 
 
diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py
index 2b2ef7bec..b5037c271 100644
--- a/conans/client/graph/graph.py
+++ b/conans/client/graph/graph.py
@@ -70,6 +70,23 @@ class Node(object):
         self.replaced_requires = {}  # To track the replaced requires for self.dependencies[old-ref]
         self.skipped_build_requires = False
 
+    def subgraph(self):
+        nodes = [self]
+        opened = [self]
+        while opened:
+            new_opened = []
+            for o in opened:
+                for n in o.neighbors():
+                    if n not in nodes:
+                        nodes.append(n)
+                    if n not in opened:
+                        new_opened.append(n)
+            opened = new_opened
+
+        graph = DepsGraph()
+        graph.nodes = nodes
+        return graph
+
     def __lt__(self, other):
         """
         @type other: Node
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 970d95f95..51ac37afa 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -318,6 +318,7 @@ class DepsGraphBuilder(object):
                 continue
             if pattern.channel != "*" and pattern.channel != require.ref.channel:
                 continue
+            require._required_ref = require.ref.copy()  # Save the original ref before replacing
             original_require = repr(require.ref)
             if alternative_ref.version != "*":
                 require.ref.version = alternative_ref.version
diff --git a/conans/client/migrations.py b/conans/client/migrations.py
index 82997ba4e..026ef8b81 100644
--- a/conans/client/migrations.py
+++ b/conans/client/migrations.py
@@ -3,7 +3,6 @@ import sqlite3
 import textwrap
 
 from conan.api.output import ConanOutput
-from conan.api.subapi.config import ConfigAPI
 from conan.internal.default_settings import migrate_settings_file
 from conans.migrations import Migrator
 from conans.util.dates import timestamp_now
@@ -76,6 +75,7 @@ def _migrate_default_compatibility(cache_folder):
 
 
 def _migrate_pkg_db_lru(cache_folder, old_version):
+    from conan.api.subapi.config import ConfigAPI
     config = ConfigAPI.load_config(cache_folder)
     storage = config.get("core.cache:storage_path") or os.path.join(cache_folder, "p")
     db_filename = os.path.join(storage, 'cache.sqlite3')
diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py
index ae1c860de..4f5fff28e 100644
--- a/conans/client/remote_manager.py
+++ b/conans/client/remote_manager.py
@@ -187,7 +187,7 @@ class RemoteManager:
             raise
 
     def search_recipes(self, remote, pattern):
-        cached_method = remote.caching.setdefault("search_recipes", {})
+        cached_method = remote._caching.setdefault("search_recipes", {})
         try:
             return cached_method[pattern]
         except KeyError:
@@ -241,7 +241,7 @@ class RemoteManager:
             if options:
                 headers['Conan-PkgID-Options'] = ';'.join(options)
 
-        cached_method = remote.caching.setdefault("get_latest_package_reference", {})
+        cached_method = remote._caching.setdefault("get_latest_package_reference", {})
         try:
             return cached_method[pref]
         except KeyError:


Test patch:
diff --git a/test/functional/sbom/test_cyclonedx.py b/test/functional/sbom/test_cyclonedx.py
new file mode 100644
index 000000000..8aafd1945
--- /dev/null
+++ b/test/functional/sbom/test_cyclonedx.py
@@ -0,0 +1,138 @@
+import textwrap
+
+import pytest
+
+from conan.test.assets.genconanfile import GenConanfile
+from conan.test.utils.tools import TestClient
+from conans.util.files import save
+import os
+
+# Using the sbom tool with "conan create"
+sbom_hook_post_package = """
+import json
+import os
+from conan.errors import ConanException
+from conan.api.output import ConanOutput
+from conan.tools.sbom.cyclonedx import cyclonedx_1_4
+
+def post_package(conanfile):
+    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph)
+    metadata_folder = conanfile.package_metadata_folder
+    file_name = "sbom.cdx.json"
+    with open(os.path.join(metadata_folder, file_name), 'w') as f:
+        json.dump(sbom_cyclonedx_1_4, f, indent=4)
+    ConanOutput().success(f"CYCLONEDX CREATED - {conanfile.package_metadata_folder}")
+"""
+
+@pytest.fixture()
+def hook_setup_post_package():
+    tc = TestClient()
+    hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
+    save(hook_path, sbom_hook_post_package)
+    return tc
+
+@pytest.fixture()
+def hook_setup_post_package_tl(transitive_libraries):
+    tc = transitive_libraries
+    hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
+    save(hook_path, sbom_hook_post_package)
+    return tc
+
+
+
+def test_sbom_generation_create(hook_setup_post_package_tl):
+    tc = hook_setup_post_package_tl
+    tc.run("new cmake_lib -d name=bar -d version=1.0 -d requires=engine/1.0 -f")
+    # bar -> engine/1.0 -> matrix/1.0
+    tc.run("create . --build=missing")
+    bar_layout = tc.created_layout()
+    assert os.path.exists(os.path.join(bar_layout.metadata(), "sbom.cdx.json"))
+
+def test_sbom_generation_skipped_dependencies(hook_setup_post_package):
+    tc = hook_setup_post_package
+    tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+             "app/conanfile.py": GenConanfile("app", "1.0")
+                                .with_package_type("application")
+                                .with_requires("dep/1.0"),
+             "conanfile.py": GenConanfile("foo", "1.0").with_tool_requires("app/1.0")})
+    tc.run("create dep")
+    tc.run("create app")
+    tc.run("create .")
+    create_layout = tc.created_layout()
+
+    cyclone_path = os.path.join(create_layout.metadata(), "sbom.cdx.json")
+    content = tc.load(cyclone_path)
+    # A skipped dependency also shows up in the sbom
+    assert "pkg:conan/dep@1.0?rref=6a99f55e933fb6feeb96df134c33af44" in content
+
+
+# Using the sbom tool with "conan install"
+sbom_hook_post_generate = """
+import json
+import os
+from conan.errors import ConanException
+from conan.api.output import ConanOutput
+from conan.tools.sbom.cyclonedx import cyclonedx_1_4
+
+def post_generate(conanfile):
+    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph)
+    generators_folder = conanfile.generators_folder
+    file_name = "sbom.cdx.json"
+    os.mkdir(os.path.join(generators_folder, "sbom"))
+    with open(os.path.join(generators_folder, "sbom", file_name), 'w') as f:
+        json.dump(sbom_cyclonedx_1_4, f, indent=4)
+    ConanOutput().success(f"CYCLONEDX CREATED - {conanfile.generators_folder}")
+"""
+
+@pytest.fixture()
+def hook_setup_post_generate():
+    tc = TestClient()
+    hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py")
+    save(hook_path, sbom_hook_post_generate)
+    return tc
+
+def test_sbom_generation_install_requires(hook_setup_post_generate):
+    tc = hook_setup_post_generate
+    tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+             "conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")})
+    tc.run("export dep")
+    tc.run("create . --build=missing")
+
+    #cli -> foo -> dep
+    tc.run("install --requires=foo/1.0")
+    assert os.path.exists(os.path.join(tc.current_folder, "sbom", "sbom.cdx.json"))
+
+def test_sbom_generation_install_path(hook_setup_post_generate):
+    tc = hook_setup_post_generate
+    tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+             "conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")})
+    tc.run("create dep")
+
+    #foo -> dep
+    tc.run("install .")
+    assert os.path.exists(os.path.join(tc.current_folder, "sbom", "sbom.cdx.json"))
+
+def test_sbom_generation_install_path_consumer(hook_setup_post_generate):
+    tc = hook_setup_post_generate
+    tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+             "conanfile.py": GenConanfile().with_requires("dep/1.0")})
+    tc.run("create dep")
+
+    #conanfile.py -> dep
+    tc.run("install .")
+    assert os.path.exists(os.path.join(tc.current_folder, "sbom", "sbom.cdx.json"))
+
+def test_sbom_generation_install_path_txt(hook_setup_post_generate):
+    tc = hook_setup_post_generate
+    tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+             "conanfile.txt": textwrap.dedent(
+                 """
+                 [requires]
+                 dep/1.0
+                 """
+             )})
+    tc.run("create dep")
+
+    #foo -> dep
+    tc.run("install .")
+    assert os.path.exists(os.path.join(tc.current_folder, "sbom", "sbom.cdx.json"))
diff --git a/test/functional/tools/test_files.py b/test/functional/tools/test_files.py
index 44b0c2748..9abc601e4 100644
--- a/test/functional/tools/test_files.py
+++ b/test/functional/tools/test_files.py
@@ -6,8 +6,9 @@ import pytest
 
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.file_server import TestFileServer
+from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
-from conans.util.files import save
+from conans.util.files import save, load
 
 
 class MockPatchset:
@@ -365,7 +366,7 @@ def test_relate_base_path_all_versions(mock_patch_ng):
     assert mock_patch_ng.apply_args[1:] == (0, False)
 
 
-def test_export_conandata_patches(mock_patch_ng):
+def test_export_conandata_patches():
     conanfile = textwrap.dedent("""
         import os
         from conan import ConanFile
@@ -402,7 +403,7 @@ def test_export_conandata_patches(mock_patch_ng):
     # wrong patches
     client.save({"conandata.yml": "patches: 123"})
     client.run("create .", assert_error=True)
-    assert "conandata.yml 'patches' should be a list or a dict"  in client.out
+    assert "conandata.yml 'patches' should be a list or a dict" in client.out
 
     # No patch found
     client.save({"conandata.yml": conandata_yml})
@@ -447,3 +448,60 @@ def test_export_conandata_patches_no_patches():
     client.save({"conandata.yml": conandata_yml, "conanfile.py": conanfile})
     client.run("create .")
     assert "No patches defined for version 1.0 in conandata.yml" in client.out
+
+
+@pytest.mark.parametrize("trim", [True, False])
+def test_export_conandata_patches_extra_origin(trim):
+    conanfile = textwrap.dedent(f"""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import export_conandata_patches, load, trim_conandata
+
+        class Pkg(ConanFile):
+            name = "mypkg"
+            version = "1.0"
+
+            def export(self):
+                if {trim}:
+                    trim_conandata(self)
+
+            def layout(self):
+                self.folders.source = "source_subfolder"
+
+            def export_sources(self):
+                export_conandata_patches(self)
+
+            def source(self):
+                self.output.info(load(self, os.path.join(self.export_sources_folder,
+                                                         "patches/mypatch.patch")))
+        """)
+    client = TestClient(light=True)
+    patches_folder = temp_folder()
+    conandata_yml = textwrap.dedent("""
+        patches:
+            "1.0":
+                - patch_file: "patches/mypatch.patch"
+        """)
+    save(os.path.join(patches_folder, "mypkg", "conandata.yml"), conandata_yml)
+    save(os.path.join(patches_folder, "mypkg", "patches", "mypatch.patch"), "mypatch!!!")
+
+    pkg_conandata = textwrap.dedent("""\
+        patches:
+            "1.1":
+                - patch_file: "patches/mypatch2.patch"
+    """)
+    client.save({"conanfile.py": conanfile,
+                 "conandata.yml": pkg_conandata,
+                 "patches/mypatch2.patch": ""})
+    client.run(f'create . -cc core.sources.patch:extra_path="{patches_folder}"')
+    assert "mypkg/1.0: Applying extra patches" in client.out
+    assert "mypkg/1.0: mypatch!!!" in client.out
+
+    conandata = load(client.exported_layout().conandata())
+    assert "1.0" in conandata
+    assert "patch_file: patches/mypatch.patch" in conandata
+
+    if trim:
+        assert "1.1" not in conandata
+    else:
+        assert "1.1" in conandata
diff --git a/test/integration/command/config_test.py b/test/integration/command/config_test.py
index 17c552ec4..c84ea3e36 100644
--- a/test/integration/command/config_test.py
+++ b/test/integration/command/config_test.py
@@ -2,7 +2,10 @@ import json
 import os
 import textwrap
 
+import pytest
+
 from conan.api.conan_api import ConanAPI
+from conan.test.assets.genconanfile import GenConanfile
 from conan.internal.model.conf import BUILT_IN_CONFS
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
@@ -201,3 +204,67 @@ def test_config_show():
     tc.run("config show zlib/*:foo")
     assert "zlib/*:user.mycategory:foo" in tc.out
     assert "zlib/*:user.myothercategory:foo" in tc.out
+
+
+@pytest.mark.parametrize("storage_path", [None, "p", "../foo"])
+def test_config_clean(storage_path):
+    tc = TestClient(light=True)
+    absolut_storage_path = os.path.abspath(os.path.join(tc.current_folder, storage_path)) if storage_path else os.path.join(tc.cache_folder, "p")
+
+    storage = f"core.cache:storage_path={storage_path}" if storage_path else ""
+    tc.save_home({"global.conf": f"core.upload:retry=7\n{storage}",
+                  "extensions/compatibility/mycomp.py": "",
+                  "extensions/commands/cmd_foo.py": "",
+                  })
+
+    tc.run("profile detect --name=foo")
+    tc.run("remote add bar http://fakeurl")
+
+    tc.save({"conanfile.py": GenConanfile("pkg", "0.1")})
+    tc.run("create .")
+
+    assert os.path.exists(absolut_storage_path)
+
+    tc.run("config clean")
+    tc.run("profile list")
+    assert "foo" not in tc.out
+    tc.run("remote list")
+    assert "bar" not in tc.out
+    tc.run("config show core.upload:retry")
+    assert "7" not in tc.out
+    assert not os.path.exists(os.path.join(tc.cache_folder, "extensions"))
+    assert os.path.exists(absolut_storage_path)
+
+
+def test_config_reinit():
+    custom_global_conf = "core.upload:retry=7"
+    global_conf_folder = temp_folder()
+    with open(os.path.join(global_conf_folder, "global.conf"), "w") as f:
+        f.write(custom_global_conf)
+
+    cache_folder = temp_folder()
+    conan_api = ConanAPI(cache_folder=cache_folder)
+    # Ensure reinitialization does not invalidate references
+    config_api = conan_api.config
+    assert config_api.global_conf.get("core.upload:retry", check_type=int) != 7
+
+    conan_api.config.install(global_conf_folder, verify_ssl=False)
+    # Already has an effect, the config installation reinitializes the config
+    assert config_api.global_conf.get("core.upload:retry", check_type=int) == 7
+
+
+def test_config_reinit_core_conf():
+    tc = TestClient(light=True)
+    tc.save_home({"extensions/commands/cmd_foo.py": textwrap.dedent("""
+        import json
+        from conan.cli.command import conan_command
+        from conan.api.output import ConanOutput
+
+        @conan_command()
+        def foo(conan_api, parser, *args, **kwargs):
+            ''' Foo '''
+            parser.parse_args(*args)
+            ConanOutput().info(f"Retry: {conan_api.config.global_conf.get('core.upload:retry', check_type=int)}")
+    """)})
+    tc.run("foo -cc core.upload:retry=7")
+    assert "Retry: 7" in tc.out
diff --git a/test/integration/command/create_test.py b/test/integration/command/create_test.py
index e68f52946..620d11f51 100644
--- a/test/integration/command/create_test.py
+++ b/test/integration/command/create_test.py
@@ -488,11 +488,11 @@ def test_create_format_json():
         '1': {'ref': 'hello/0.1', 'run': False, 'libs': True, 'skip': False,
               'test': False, 'force': False, 'direct': True, 'build': False,
               'transitive_headers': None, 'transitive_libs': None, 'headers': True,
-              'package_id_mode': None, 'visible': True},
+              'package_id_mode': None, 'visible': True, 'require': 'hello/0.1'},
         '2': {'ref': 'pkg/0.2', 'run': False, 'libs': True, 'skip': False, 'test': False,
               'force': False, 'direct': False, 'build': False, 'transitive_headers': None,
               'transitive_libs': None, 'headers': True, 'package_id_mode': None,
-              'visible': True}
+              'visible': True, 'require': 'pkg/0.2'}
     }
     assert consumer_info["dependencies"] == consumer_deps
     # hello/0.1 pkg information
@@ -505,7 +505,7 @@ def test_create_format_json():
             "ref": "pkg/0.2", "run": False, "libs": True, "skip": False, "test": False,
             "force": False, "direct": True, "build": False, "transitive_headers": None,
             "transitive_libs": None, "headers": True, "package_id_mode": "semver_mode",
-            "visible": True
+            "visible": True, 'require': 'pkg/0.2'
         }
     }
     assert hello_pkg_info["dependencies"] == hello_pkg_info_deps
diff --git a/test/integration/command/info/info_test.py b/test/integration/command/info/info_test.py
index 8f254820a..59765ee41 100644
--- a/test/integration/command/info/info_test.py
+++ b/test/integration/command/info/info_test.py
@@ -176,6 +176,15 @@ class TestJsonOutput:
         graph = json.loads(client.stdout)
         assert graph["graph"]["nodes"]["0"]["settings"]["build_type"] == "Debug"
 
+    def test_json_info_requirements(self):
+        c = TestClient()
+        c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1"),
+                "app/conanfile.py": GenConanfile().with_require("pkg/[>=0.0 <1.0]")})
+        c.run("create pkg")
+        c.run("graph info app --format=json")
+        graph = json.loads(c.stdout)
+        assert graph["graph"]["nodes"]["0"]["dependencies"]["1"]["require"] == "pkg/[>=0.0 <1.0]"
+
 
 class TestAdvancedCliOutput:
     """ Testing more advanced fields output, like SCM or PYTHON-REQUIRES
diff --git a/test/integration/command_v2/test_cache_integrity.py b/test/integration/command_v2/test_cache_integrity.py
index 324f9050f..b40c74565 100644
--- a/test/integration/command_v2/test_cache_integrity.py
+++ b/test/integration/command_v2/test_cache_integrity.py
@@ -17,12 +17,92 @@ def test_cache_integrity():
     layout = t.created_layout()
     conaninfo = os.path.join(layout.package(), "conaninfo.txt")
     save(conaninfo, "[settings]")
+    t.run("create . --name pkg4 --version=4.0")
+    layout = t.created_layout()
+    conaninfo = os.path.join(layout.package(), "conaninfo.txt")
+    save(conaninfo, "[settings]")
 
     t.run("cache check-integrity *", assert_error=True)
     assert "pkg1/1.0: Integrity checked: ok" in t.out
     assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
     assert "ERROR: pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
     assert "ERROR: pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
+    assert "ERROR: pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
+
+    t.run("remove pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
+    t.run("remove pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
+    t.run("remove pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
+    t.run("cache check-integrity *")
+    assert "pkg1/1.0: Integrity checked: ok" in t.out
+    assert "pkg2/2.0: Integrity checked: ok" in t.out
+    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "pkg4/4.0: Integrity checked: ok" in t.out
+
+
+def test_cache_integrity_missing_recipe_manifest():
+    t = TestClient()
+    t.save({"conanfile.py": GenConanfile()})
+    t.run("create . --name pkg1 --version 1.0")
+    t.run("create . --name pkg2 --version=2.0")
+    layout = t.exported_layout()
+    manifest = os.path.join(layout.export(), "conanmanifest.txt")
+    os.remove(manifest)
+    t.run("create . --name pkg3 --version=3.0")
+
+    t.run("cache check-integrity *", assert_error=True)
+    assert "pkg1/1.0: Integrity checked: ok" in t.out
+    assert "ERROR: pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: Manifest missing" in t.out
+    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "ERROR: There are corrupted artifacts, check the error logs" in t.out
+
+    t.run("remove pkg2* -c")
+    t.run("cache check-integrity *")
+    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
+    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "Integrity check: ok" in t.out
+
+
+def test_cache_integrity_missing_package_manifest():
+    t = TestClient()
+    t.save({"conanfile.py": GenConanfile()})
+    t.run("create . --name pkg1 --version 1.0")
+    t.run("create . --name pkg2 --version=2.0")
+    layout = t.created_layout()
+    manifest = os.path.join(layout.package(), "conanmanifest.txt")
+    os.remove(manifest)
+    t.run("create . --name pkg3 --version=3.0")
+
+    t.run("cache check-integrity *", assert_error=True)
+    assert "pkg1/1.0: Integrity checked: ok" in t.out
+    assert "ERROR: pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd" \
+           ":da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Manifest missing" in t.out
+    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "ERROR: There are corrupted artifacts, check the error logs" in t.out
+
+    t.run("remove pkg2* -c")
+    t.run("cache check-integrity *")
+    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
+    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "Integrity check: ok" in t.out
+
+
+def test_cache_integrity_missing_package_conaninfo():
+    t = TestClient()
+    t.save({"conanfile.py": GenConanfile()})
+    t.run("create . --name pkg1 --version 1.0")
+    t.run("create . --name pkg2 --version=2.0")
+    layout = t.created_layout()
+    conaninfo = os.path.join(layout.package(), "conaninfo.txt")
+    os.remove(conaninfo)
+
+    t.run("cache check-integrity *", assert_error=True)
+    assert "pkg1/1.0: Integrity checked: ok" in t.out
+    assert "ERROR: pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
+
+    t.run("remove pkg2* -c")
+    t.run("cache check-integrity *")
+    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
 
 
 def test_cache_integrity_export_sources():
diff --git a/test/integration/command_v2/test_inspect.py b/test/integration/command_v2/test_inspect.py
index cd7445800..e23ee392a 100644
--- a/test/integration/command_v2/test_inspect.py
+++ b/test/integration/command_v2/test_inspect.py
@@ -136,8 +136,8 @@ def test_requiremens_inspect():
             'options:',
             'options_definitions:',
             'package_type: None',
-            "requires: [{'ref': 'zlib/1.2.13', 'run': False, 'libs': True, 'skip': "
-            "False, 'test': False, 'force': False, 'direct': True, 'build': "
+            "requires: [{'ref': 'zlib/1.2.13', 'require': 'zlib/1.2.13', 'run': False, "
+            "'libs': True, 'skip': False, 'test': False, 'force': False, 'direct': True, 'build': "
             "False, 'transitive_headers': None, 'transitive_libs': None, 'headers': "
             "True, 'package_id_mode': None, 'visible': True}]",
             'revision_mode: hash',
diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py
index 4d6373c82..dbde54b94 100644
--- a/test/integration/graph/test_replace_requires.py
+++ b/test/integration/graph/test_replace_requires.py
@@ -150,6 +150,8 @@ def test_replace_requires_json_format():
     assert "pkg/0.1: pkg/0.2" in c.out  # The replacement happens
     graph = json.loads(c.stdout)
     assert graph["graph"]["replaced_requires"] == {"pkg/0.1": "pkg/0.2"}
+    assert graph["graph"]["nodes"]["0"]["dependencies"]["1"]["ref"] == "pkg/0.2"
+    assert graph["graph"]["nodes"]["0"]["dependencies"]["1"]["require"] == "pkg/0.1"
 
 
 def test_replace_requires_test_requires():
@@ -373,3 +375,315 @@ def test_replace_requires_multiple():
     # There are actually 2 dependencies, pointing to the same node
     assert "libepoxy/0.1: DEP: opengl: libgl" in c.out
     assert "libepoxy/0.1: DEP: egl: libgl" in c.out
+
+
+class TestReplaceRequiresTransitiveGenerators:
+    """ Generators are incorrectly managing replace_requires
+    # https://github.com/conan-io/conan/issues/17557
+    """
+
+    @pytest.mark.parametrize("diamond", [True, False])
+    def test_no_components(self, diamond):
+        c = TestClient()
+        zlib_ng = textwrap.dedent("""
+            from conan import ConanFile
+            class ZlibNG(ConanFile):
+                name = "zlib-ng"
+                version = "0.1"
+                package_type = "static-library"
+                def package_info(self):
+                    self.cpp_info.libs = ["zlib"]
+                    self.cpp_info.type = "static-library"
+                    self.cpp_info.location = "lib/zlib.lib"
+                    self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                    self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
+                    self.cpp_info.set_property("pkg_config_name", "ZLIB")
+            """)
+        openssl = textwrap.dedent("""
+            from conan import ConanFile
+            class openssl(ConanFile):
+                name = "openssl"
+                version = "0.1"
+                package_type = "static-library"
+                requires = "zlib/0.1"
+                def package_info(self):
+                    self.cpp_info.libs = ["crypto"]
+                    self.cpp_info.type = "static-library"
+                    self.cpp_info.location = "lib/crypto.lib"
+                    self.cpp_info.requires = ["zlib::zlib"]
+            """)
+        zlib = '"zlib/0.1"' if diamond else ""
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            class App(ConanFile):
+                name = "app"
+                version = "0.1"
+                settings = "build_type"
+                requires = "openssl/0.1", {zlib}
+                package_type = "application"
+                generators = "CMakeDeps", "PkgConfigDeps"
+            """)
+        profile = textwrap.dedent("""
+            [settings]
+            build_type = Release
+
+            [replace_requires]
+            zlib/0.1: zlib-ng/0.1
+            """)
+        c.save({"zlibng/conanfile.py": zlib_ng,
+                "openssl/conanfile.py": openssl,
+                "app/conanfile.py": conanfile,
+                "profile": profile})
+
+        c.run("create zlibng")
+        c.run("create openssl -pr=profile")
+        c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next")
+        assert "zlib/0.1: zlib-ng/0.1" in c.out
+
+        pc_content = c.load("app/ZLIB.pc")
+        assert 'Libs: -L"${libdir}" -lzlib' in pc_content
+        pc_content = c.load("app/openssl.pc")
+        assert 'Requires: ZLIB' in pc_content
+
+        cmake = c.load("app/ZLIB-Targets-release.cmake")
+        assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake
+
+        cmake = c.load("app/openssl-Targets-release.cmake")
+        assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
+        assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake
+        assert "set_target_properties(openssl::openssl PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
+               '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
+
+    @pytest.mark.parametrize("diamond", [True, False])
+    def test_openssl_components(self, diamond):
+        c = TestClient()
+        zlib_ng = textwrap.dedent("""
+            from conan import ConanFile
+            class ZlibNG(ConanFile):
+                name = "zlib-ng"
+                version = "0.1"
+                package_type = "static-library"
+                def package_info(self):
+                    self.cpp_info.libs = ["zlib"]
+                    self.cpp_info.type = "static-library"
+                    self.cpp_info.location = "lib/zlib.lib"
+                    self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                    self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
+                    self.cpp_info.set_property("pkg_config_name", "ZLIB")
+            """)
+        openssl = textwrap.dedent("""
+            from conan import ConanFile
+            class openssl(ConanFile):
+                name = "openssl"
+                version = "0.1"
+                package_type = "static-library"
+                requires = "zlib/0.1"
+                def package_info(self):
+                    self.cpp_info.components["crypto"].libs = ["crypto"]
+                    self.cpp_info.components["crypto"].type = "static-library"
+                    self.cpp_info.components["crypto"].location = "lib/crypto.lib"
+                    self.cpp_info.components["crypto"].requires = ["zlib::zlib"]
+            """)
+        zlib = '"zlib/0.1"' if diamond else ""
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            class App(ConanFile):
+                name = "app"
+                version = "0.1"
+                settings = "build_type"
+                requires = "openssl/0.1", {zlib}
+                package_type = "application"
+                generators = "CMakeDeps", "PkgConfigDeps"
+            """)
+        profile = textwrap.dedent("""
+            [settings]
+            build_type = Release
+
+            [replace_requires]
+            zlib/0.1: zlib-ng/0.1
+            """)
+        c.save({"zlibng/conanfile.py": zlib_ng,
+                "openssl/conanfile.py": openssl,
+                "app/conanfile.py": conanfile,
+                "profile": profile})
+
+        c.run("create zlibng")
+        c.run("create openssl -pr=profile")
+        c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next")
+        assert "zlib/0.1: zlib-ng/0.1" in c.out
+
+        pc_content = c.load("app/ZLIB.pc")
+        assert 'Libs: -L"${libdir}" -lzlib' in pc_content
+        pc_content = c.load("app/openssl-crypto.pc")
+        assert 'Requires: ZLIB' in pc_content
+
+        cmake = c.load("app/ZLIB-Targets-release.cmake")
+        assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake
+
+        cmake = c.load("app/openssl-Targets-release.cmake")
+        assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
+        assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake
+        assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
+               '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
+
+    @pytest.mark.parametrize("diamond", [True, False])
+    @pytest.mark.parametrize("explicit_requires", [True, False])
+    def test_zlib_components(self, diamond, explicit_requires):
+        c = TestClient()
+        zlib_ng = textwrap.dedent("""
+            from conan import ConanFile
+            class ZlibNG(ConanFile):
+                name = "zlib-ng"
+                version = "0.1"
+                package_type = "static-library"
+                def package_info(self):
+                    self.cpp_info.components["myzlib"].libs = ["zlib"]
+                    self.cpp_info.components["myzlib"].type = "static-library"
+                    self.cpp_info.components["myzlib"].location = "lib/zlib.lib"
+                    self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                    self.cpp_info.components["myzlib"].set_property("pkg_config_name", "ZLIB")
+                    self.cpp_info.components["myzlib"].set_property("cmake_target_name",
+                                                                    "ZLIB::ZLIB")
+            """)
+        openssl = textwrap.dedent(f"""
+            from conan import ConanFile
+            class openssl(ConanFile):
+                name = "openssl"
+                version = "0.1"
+                package_type = "static-library"
+                requires = "zlib/0.1"
+                def package_info(self):
+                    self.cpp_info.libs = ["crypto"]
+                    self.cpp_info.type = "static-library"
+                    self.cpp_info.location = "lib/crypto.lib"
+                    if {explicit_requires}:
+                        self.cpp_info.requires = ["zlib::zlib"]
+            """)
+        zlib = '"zlib/0.1"' if diamond else ""
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            class App(ConanFile):
+                name = "app"
+                version = "0.1"
+                settings = "build_type"
+                requires = "openssl/0.1", {zlib}
+                package_type = "application"
+                generators = "CMakeDeps", "PkgConfigDeps"
+            """)
+        profile = textwrap.dedent("""
+            [settings]
+            build_type = Release
+
+            [replace_requires]
+            zlib/0.1: zlib-ng/0.1
+            """)
+        c.save({"zlibng/conanfile.py": zlib_ng,
+                "openssl/conanfile.py": openssl,
+                "app/conanfile.py": conanfile,
+                "profile": profile})
+
+        c.run("create zlibng")
+        c.run("create openssl -pr=profile")
+        c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next")
+        assert "zlib/0.1: zlib-ng/0.1" in c.out
+
+        pc_content = c.load("app/zlib-ng.pc")
+        assert 'Requires: ZLIB' in pc_content
+        pc_content = c.load("app/ZLIB.pc")
+        assert 'Libs: -L"${libdir}" -lzlib' in pc_content
+        pc_content = c.load("app/openssl.pc")
+        assert 'Requires: zlib-ng' in pc_content
+
+        cmake = c.load("app/ZLIB-Targets-release.cmake")
+        assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake
+
+        cmake = c.load("app/openssl-Targets-release.cmake")
+        assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
+        assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake
+        # It should access the generic zlib-ng target
+        assert "set_target_properties(openssl::openssl PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
+               '                      "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
+
+    @pytest.mark.parametrize("diamond", [True, False])
+    @pytest.mark.parametrize("package_requires", [False, True])
+    def test_both_components(self, diamond, package_requires):
+        c = TestClient()
+        zlib_ng = textwrap.dedent("""
+            from conan import ConanFile
+            class ZlibNG(ConanFile):
+                name = "zlib-ng"
+                version = "0.1"
+                package_type = "static-library"
+                def package_info(self):
+                    self.cpp_info.components["myzlib"].libs = ["zlib"]
+                    self.cpp_info.components["myzlib"].type = "static-library"
+                    self.cpp_info.components["myzlib"].location = "lib/zlib.lib"
+                    self.cpp_info.set_property("cmake_file_name", "ZLIB")
+                    self.cpp_info.components["myzlib"].set_property("pkg_config_name", "ZLIB")
+                    self.cpp_info.components["myzlib"].set_property("cmake_target_name",
+                                                                    "ZLIB::ZLIB")
+            """)
+        openssl = textwrap.dedent(f"""
+            from conan import ConanFile
+            class openssl(ConanFile):
+                name = "openssl"
+                version = "0.1"
+                package_type = "static-library"
+                requires = "zlib/0.1"
+                def package_info(self):
+                    self.cpp_info.components["crypto"].libs = ["crypto"]
+                    self.cpp_info.components["crypto"].type = "static-library"
+                    self.cpp_info.components["crypto"].location = "lib/crypto.lib"
+                    if {package_requires}:
+                        self.cpp_info.components["crypto"].requires = ["zlib::zlib"]
+                    else:
+                        self.cpp_info.components["crypto"].requires = ["zlib::myzlib"]
+            """)
+        zlib = '"zlib/0.1"' if diamond else ""
+        conanfile = textwrap.dedent(f"""
+            from conan import ConanFile
+            class App(ConanFile):
+                name = "app"
+                version = "0.1"
+                settings = "build_type"
+                requires = "openssl/0.1", {zlib}
+                package_type = "application"
+                generators = "CMakeDeps", "PkgConfigDeps"
+            """)
+        profile = textwrap.dedent("""
+            [settings]
+            build_type = Release
+
+            [replace_requires]
+            zlib/0.1: zlib-ng/0.1
+            """)
+        c.save({"zlibng/conanfile.py": zlib_ng,
+                "openssl/conanfile.py": openssl,
+                "app/conanfile.py": conanfile,
+                "profile": profile})
+
+        c.run("create zlibng")
+        c.run("create openssl -pr=profile")
+        c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next")
+        assert "zlib/0.1: zlib-ng/0.1" in c.out
+
+        pc_content = c.load("app/zlib-ng.pc")
+        assert 'Requires: ZLIB' in pc_content
+        pc_content = c.load("app/ZLIB.pc")
+        assert 'Libs: -L"${libdir}" -lzlib' in pc_content
+        pc_content = c.load("app/openssl-crypto.pc")
+        assert f'Requires: {"zlib-ng" if package_requires else "ZLIB"}' in pc_content
+
+        cmake = c.load("app/ZLIB-Targets-release.cmake")
+        assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake
+
+        cmake = c.load("app/openssl-Targets-release.cmake")
+        assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
+        assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake
+        if package_requires:
+            # The generic package requirement uses the package name zlib-ng
+            assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
+                   '                      "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
+        else:
+            assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
+                   '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
diff --git a/test/integration/graph/test_subgraph_reports.py b/test/integration/graph/test_subgraph_reports.py
new file mode 100644
index 000000000..597fb6386
--- /dev/null
+++ b/test/integration/graph/test_subgraph_reports.py
@@ -0,0 +1,48 @@
+import json
+import os
+import textwrap
+
+from conan.test.assets.genconanfile import GenConanfile
+from conan.test.utils.tools import TestClient
+from conans.model.recipe_ref import RecipeReference
+from conans.util.files import load
+
+
+def _metadata(c, ref):
+    pref = c.get_latest_package_reference(RecipeReference.loads(ref))
+    return c.get_latest_pkg_layout(pref).metadata()
+from conan.internal.model.lockfile import Lockfile
+def test_subgraph_reports():
+    c = TestClient()
+    subgraph_hook = textwrap.dedent("""\
+        import os, json
+        from conan.tools.files import save
+        from conan.internal.model.lockfile import Lockfile
+        def post_package(conanfile):
+            subgraph = conanfile.subgraph
+            save(conanfile, os.path.join(conanfile.package_metadata_folder, f"conangraph.json"),
+                 json.dumps(subgraph.serialize(), indent=2))
+            save(conanfile, os.path.join(conanfile.package_metadata_folder, f"conan.lock"),
+                 Lockfile(subgraph).dumps())
+        """)
+
+    c.save_home({"extensions/hooks/subgraph_hook/hook_subgraph.py": subgraph_hook})
+    c.save({"dep/conanfile.py": GenConanfile("dep", "0.1"),
+            "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_requirement("dep/0.1"),
+            "app/conanfile.py": GenConanfile("app", "0.1").with_requirement("pkg/0.1")})
+    c.run("export dep")
+    c.run("export pkg")
+    # app -> pkg -> dep
+    c.run("create app --build=missing --format=json")
+
+    app_graph = json.loads(load(os.path.join(_metadata(c, "app/0.1"), "conangraph.json")))
+    pkg_graph = json.loads(load(os.path.join(_metadata(c, "pkg/0.1"), "conangraph.json")))
+    dep_graph = json.loads(load(os.path.join(_metadata(c, "dep/0.1"), "conangraph.json")))
+
+    app_lock = json.loads(load(os.path.join(_metadata(c, "app/0.1"), "conan.lock")))
+    pkg_lock = json.loads(load(os.path.join(_metadata(c, "pkg/0.1"), "conan.lock")))
+    dep_lock = json.loads(load(os.path.join(_metadata(c, "dep/0.1"), "conan.lock")))
+
+    assert len(app_graph["nodes"]) == len(app_lock["requires"])
+    assert len(pkg_graph["nodes"]) == len(pkg_lock["requires"])
+    assert len(dep_graph["nodes"]) == len(dep_lock["requires"])
diff --git a/test/integration/lockfile/test_graph_overrides.py b/test/integration/lockfile/test_graph_overrides.py
index 17698db54..2212e2989 100644
--- a/test/integration/lockfile/test_graph_overrides.py
+++ b/test/integration/lockfile/test_graph_overrides.py
@@ -35,8 +35,8 @@ def test_overrides_half_diamond(override, force):
     # apply the lockfile to pkgb, should it lock to pkga/0.2
     c.run("graph info pkgb --lockfile=pkgc/conan.lock --format=json")
     dependencies = json.loads(c.stdout)["graph"]["nodes"]["0"]["dependencies"]
-    assert "pkga/0.2" in str(dependencies)
-    assert "pkga/0.1" not in str(dependencies)
+    assert "pkga/0.2" == dependencies["1"]["ref"]
+    assert "pkga/0.1" == dependencies["1"]["require"]
 
 
 @pytest.mark.parametrize("override, force", [(True, False), (False, True)])
@@ -140,9 +140,8 @@ def test_overrides_diamond(override, force):
     c.run("graph info pkgb --lockfile=pkgd/conan.lock --format=json")
     json_graph = json.loads(c.stdout)
     deps = json_graph["graph"]["nodes"]["0"]["dependencies"]
-    assert "pkga/0.3" in str(deps)
-    assert "pkga/0.2" not in str(deps)
-    assert "pkga/0.1" not in str(deps)
+    assert "pkga/0.3" == deps["1"]["ref"]
+    assert "pkga/0.1" == deps["1"]["require"]
     # Redundant assert, but checking "overrides" summary
     overrides = json_graph['graph']["overrides"]
     assert len(overrides) == 1
diff --git a/test/integration/package_id/compatible_test.py b/test/integration/package_id/compatible_test.py
index 5d384b7df..8758d8629 100644
--- a/test/integration/package_id/compatible_test.py
+++ b/test/integration/package_id/compatible_test.py
@@ -443,13 +443,18 @@ class TestNewCompatibility:
                    compiler.runtime=dynamic
                    """)
         tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_setting("compiler"),
-                 "conanfile.py": GenConanfile("app", "1.0").with_require("dep/1.0").with_setting("compiler"),
                  "profile": profile})
 
         tc.run("create dep -pr=profile -s compiler.cppstd=20")
-        tc.run("create . -pr=profile -s compiler.cppstd=17")
+        tc.run("install --requires=dep/1.0 -pr=profile -s compiler.cppstd=17")
         tc.assert_listed_binary({"dep/1.0": ("b6d26a6bc439b25b434113982791edf9cab4d004", "Cache")})
 
+        tc.run("remove * -c")
+        tc.run("create dep -pr=profile -s compiler.version=193 -s compiler.cppstd=20")
+        tc.run("install --requires=dep/1.0 -pr=profile -s compiler.cppstd=17")
+        assert "compiler.cppstd=20, compiler.version=193" in tc.out
+        tc.assert_listed_binary({"dep/1.0": ("535899bb58c3ca7d80a380313d31f4729e735d1c", "Cache")})
+
 
 class TestCompatibleBuild:
     def test_build_compatible(self):
@@ -706,3 +711,58 @@ class TestCompatibleBuild:
             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"
+
+
+def test_compatibility_new_setting_forwards_compat():
+    """ This test tries to reflect the following scenario:
+    - User adds a new setting (libc.version in this case)
+    - This setting is forward compatible
+    How is it solved with compatibility.py? Like this:
+    """
+    tc = TestClient()
+    tc.save_home({"settings_user.yml": "libc_version: [1, 2, 3]"})
+    tc.save({"conanfile.py": GenConanfile("dep", "1.0").with_settings("libc_version", "compiler")})
+    # The extra cppstd and compiler versions are for later demonstrations of combinations of settings
+    # The cppstd=17 and compiler.version=193 are used thought until the last 2 install calls
+    tc.run("create . -s=libc_version=2 -s=compiler.cppstd=17")
+    dep_package_id = tc.created_package_id("dep/1.0")
+    tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=17", assert_error=True)
+    # We can't compile, because the dep is not compatible
+    assert "Missing prebuilt package for 'dep/1.0'" in tc.out
+
+    # Let's create a compatibility extensions
+    libc_compat = textwrap.dedent("""
+        from conan.tools.scm import Version
+
+        def libc_compat(conanfile):
+            # Do we have the setting?
+            libc_version = conanfile.settings.get_safe("libc_version")
+            if libc_version is None:
+                return []
+            available_libc_versions = conanfile.settings.libc_version.possible_values()
+            ret = []
+            for possible_libc_version in available_libc_versions:
+                if Version(possible_libc_version) < Version(libc_version):
+                    ret.append({"libc_version": possible_libc_version})
+            return ret
+        """)
+    compat = tc.load_home("extensions/plugins/compatibility/compatibility.py")
+    compat = "from libc_compat import libc_compat\n" + compat
+    compat = compat.replace("# Append more factors for your custom compatibility rules here",
+                            "factors.append(libc_compat(conanfile))")
+    tc.save_home({"extensions/plugins/compatibility/libc_compat.py": libc_compat,
+                  "extensions/plugins/compatibility/compatibility.py": compat})
+
+    # Now we try again, this time app will find the compatible dep with libc_version 2
+    tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=17")
+    assert f"dep/1.0: Found compatible package '{dep_package_id}'" in tc.out
+
+    # And now we try to create the app with libc_version 1, which is still not compatible
+    tc.run("install --requires=dep/1.0 -s=libc_version=1 -s=compiler.cppstd=17", assert_error=True)
+    assert "Missing prebuilt package for 'dep/1.0'" in tc.out
+
+    # Now we try again, this time app will find the compatible dep with libc_version 2
+    # And see how we're also compatible over a different cppstd
+    tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=14")
+    assert f"dep/1.0: Found compatible package '{dep_package_id}': compiler.cppstd=17, " \
+           f"libc_version=2" in tc.out
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index c9629d565..318f43d76 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -383,6 +383,7 @@ def lib_dir_setup():
     client.save({"conanfile.py": conanfile})
     return client
 
+
 def test_runtime_lib_dirs_single_conf(lib_dir_setup):
     client = lib_dir_setup
     generator = ""
@@ -1537,6 +1538,28 @@ def test_toolchain_keep_absolute_paths():
     assert os.path.isabs(presets["configurePresets"][0]["toolchainFile"])
 
 
+def test_customize_cmakeuserpresets():
+    # https://github.com/conan-io/conan/issues/15639
+    c = TestClient()
+    c.save({"conanfile.py": GenConanfile(),
+            "CMakeLists.txt": ""})
+    c.run("install . -g CMakeToolchain -of=build")
+    old = c.load("CMakeUserPresets.json")
+    os.remove(os.path.join(c.current_folder, "CMakeUserPresets.json"))
+
+    c.run("install . -g CMakeToolchain -of=build -c tools.cmake.cmaketoolchain:user_presets=my.json")
+    new = c.load("my.json")
+    assert old == new
+    # also in subfolder
+    c.run("install . -g CMakeToolchain -of=build "
+          "-c tools.cmake.cmaketoolchain:user_presets=mysub/my.json")
+    assert os.path.exists(os.path.join(c.current_folder, "mysub", "my.json"))
+
+    assert not os.path.exists(os.path.join(c.current_folder, "CMakeUserPresets.json"))
+    c.run("install . -g CMakeToolchain -of=build -c tools.cmake.cmaketoolchain:user_presets=")
+    assert not os.path.exists(os.path.join(c.current_folder, "CMakeUserPresets.json"))
+
+
 def test_output_dirs_gnudirs_local_default():
     # https://github.com/conan-io/conan/issues/14733
     c = TestClient()
@@ -1634,7 +1657,7 @@ def test_toolchain_extra_variables():
     # Test input from command line passing dict between doble quotes
     client.run(textwrap.dedent(r"""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'CMAKE_GENERATOR_INSTANCE': '${GENERATOR_INSTANCE}/buildTools/', 'FOO': 42.2, 'DICT': {'value': 1}, 'CACHE_VAR': {'value': 'hello world', 'cache': True, 'type': 'BOOL', 'docstring': 'test variable'}}"
-    """)
+        """)
     )
 
     toolchain = client.load("conan_toolchain.cmake")
@@ -1643,32 +1666,31 @@ def test_toolchain_extra_variables():
     assert 'set(DICT 1)' in toolchain
     assert 'set(CACHE_VAR "hello world" CACHE BOOL "test variable")' in toolchain
 
-
     client.run(textwrap.dedent("""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'myVar': {'value': 'hello world', 'cache': 'true'}}"
-    """) , assert_error=True)
+    """), assert_error=True)
     assert 'tools.cmake.cmaketoolchain:extra_variables "myVar" "cache" must be a boolean' in client.out
 
     # Test invalid force
     client.run(textwrap.dedent("""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'myVar': {'value': 'hello world', 'force': True}}"
-    """) , assert_error=True)
+    """), assert_error=True)
     assert 'tools.cmake.cmaketoolchain:extra_variables "myVar" "force" is only allowed for cache variables' in client.out
 
     client.run(textwrap.dedent("""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'myVar': {'value': 'hello world', 'cache': True, 'force': 'true'}}"
-    """) , assert_error=True)
+    """), assert_error=True)
     assert 'tools.cmake.cmaketoolchain:extra_variables "myVar" "force" must be a boolean' in client.out
 
     # Test invalid cache variable
     client.run(textwrap.dedent("""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'myVar': {'value': 'hello world', 'cache': True}}"
-    """) , assert_error=True)
+    """), assert_error=True)
     assert 'tools.cmake.cmaketoolchain:extra_variables "myVar" needs "type" defined for cache variable' in client.out
 
     client.run(textwrap.dedent("""
         install . -c tools.cmake.cmaketoolchain:extra_variables="{'myVar': {'value': 'hello world', 'cache': True, 'type': 'INVALID_TYPE'}}"
-    """) , assert_error=True)
+    """), assert_error=True)
     assert 'tools.cmake.cmaketoolchain:extra_variables "myVar" invalid type "INVALID_TYPE" for cache variable. Possible types: BOOL, FILEPATH, PATH, STRING, INTERNAL' in client.out
 
     client.run(textwrap.dedent("""
diff --git a/test/integration/toolchains/gnu/test_autotoolstoolchain.py b/test/integration/toolchains/gnu/test_autotoolstoolchain.py
index e7fdbfc05..c15908f98 100644
--- a/test/integration/toolchains/gnu/test_autotoolstoolchain.py
+++ b/test/integration/toolchains/gnu/test_autotoolstoolchain.py
@@ -341,3 +341,36 @@ def test_toolchain_crossbuild_to_android():
         "conanfile.py": consumer
     })
     client.run("create . -pr:h host -pr:b build")
+
+
+def test_conf_build_does_not_exist():
+    host = textwrap.dedent("""
+    [settings]
+    arch=x86_64
+    build_type=Release
+    compiler=gcc
+    compiler.cppstd=gnu17
+    compiler.libcxx=libstdc++11
+    compiler.version=13
+    os=Linux
+    [conf]
+    tools.build:compiler_executables={'c': 'x86_64-linux-gnu-gcc', 'cpp': 'x86_64-linux-gnu-g++'}
+    """)
+    build = textwrap.dedent("""
+    [settings]
+    arch=armv8
+    build_type=Release
+    compiler=gcc
+    compiler.cppstd=gnu17
+    compiler.libcxx=libstdc++11
+    compiler.version=13
+    os=Linux
+    """)
+    c = TestClient()
+    c.save({
+        "conanfile.py": GenConanfile("pkg", "0.1"),
+        "host": host,
+        "build": build
+    })
+    c.run("export .")
+    c.run("install --requires=pkg/0.1 --build=pkg/0.1 -g AutotoolsToolchain -pr:h host -pr:b build")
diff --git a/test/integration/toolchains/gnu/test_gnutoolchain.py b/test/integration/toolchains/gnu/test_gnutoolchain.py
index 0982ef010..a5fe7f3f6 100644
--- a/test/integration/toolchains/gnu/test_gnutoolchain.py
+++ b/test/integration/toolchains/gnu/test_gnutoolchain.py
@@ -430,3 +430,36 @@ def test_msvc_profile_defaults():
         "consumer/conanfile.py": consumer
     })
     client.run("create consumer -pr:a profile --build=missing")
+
+
+def test_conf_build_does_not_exist():
+    host = textwrap.dedent("""
+    [settings]
+    arch=x86_64
+    build_type=Release
+    compiler=gcc
+    compiler.cppstd=gnu17
+    compiler.libcxx=libstdc++11
+    compiler.version=13
+    os=Linux
+    [conf]
+    tools.build:compiler_executables={'c': 'x86_64-linux-gnu-gcc', 'cpp': 'x86_64-linux-gnu-g++'}
+    """)
+    build = textwrap.dedent("""
+    [settings]
+    arch=armv8
+    build_type=Release
+    compiler=gcc
+    compiler.cppstd=gnu17
+    compiler.libcxx=libstdc++11
+    compiler.version=13
+    os=Linux
+    """)
+    c = TestClient()
+    c.save({
+        "conanfile.py": GenConanfile("pkg", "0.1"),
+        "host": host,
+        "build": build
+    })
+    c.run("export .")
+    c.run("install --requires=pkg/0.1 --build=pkg/0.1 -g GnuToolchain -pr:h host -pr:b build")
diff --git a/test/integration/toolchains/meson/test_mesontoolchain.py b/test/integration/toolchains/meson/test_mesontoolchain.py
index a9075c667..e5cb72953 100644
--- a/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -98,10 +98,10 @@ def test_extra_flags_via_conf():
 
     t.run("install . -pr:h=profile -pr:b=profile")
     content = t.load(MesonToolchain.native_filename)
-    assert "cpp_args = ['-flag0', '-other=val', '-flag1', '-flag2', '-Ddefine1=0', '-D_GLIBCXX_USE_CXX11_ABI=0']" in content
-    assert "c_args = ['-flag0', '-other=val', '-flag3', '-flag4', '-Ddefine1=0']" in content
-    assert "c_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
-    assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
+    assert "cpp_args = ['-flag0', '-other=val', '-m64', '-flag1', '-flag2', '-Ddefine1=0', '-D_GLIBCXX_USE_CXX11_ABI=0']" in content
+    assert "c_args = ['-flag0', '-other=val', '-m64', '-flag3', '-flag4', '-Ddefine1=0']" in content
+    assert "c_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6']" in content
+    assert "cpp_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6']" in content
 
 
 def test_extra_flags_via_toolchain():
@@ -138,10 +138,32 @@ def test_extra_flags_via_toolchain():
             "profile": profile})
     t.run("install . -pr:h=profile -pr:b=profile")
     content = t.load(MesonToolchain.native_filename)
-    assert "cpp_args = ['-flag0', '-other=val', '-flag1', '-flag2', '-Ddefine1=0', '-D_GLIBCXX_USE_CXX11_ABI=0']" in content
-    assert "c_args = ['-flag0', '-other=val', '-flag3', '-flag4', '-Ddefine1=0']" in content
-    assert "c_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
-    assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
+    assert "cpp_args = ['-flag0', '-other=val', '-m64', '-flag1', '-flag2', '-Ddefine1=0', '-D_GLIBCXX_USE_CXX11_ABI=0']" in content
+    assert "c_args = ['-flag0', '-other=val', '-m64', '-flag3', '-flag4', '-Ddefine1=0']" in content
+    assert "c_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6']" in content
+    assert "cpp_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6']" in content
+
+
+def test_custom_arch_flag_via_toolchain():
+    t = TestClient()
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+    from conan.tools.meson import MesonToolchain
+    class Pkg(ConanFile):
+        settings = "os", "compiler", "arch", "build_type"
+        def generate(self):
+            tc = MesonToolchain(self)
+            tc.arch_flag = "-mmy-flag"
+            tc.generate()
+    """)
+    t.save({"conanfile.py": conanfile})
+    t.run("install .")
+    content = t.load(MesonToolchain.native_filename)
+    assert re.search(r"c_args =.+-mmy-flag.+", content)
+    assert re.search(r"c_link_args =.+-mmy-flag.+", content)
+    assert re.search(r"cpp_args =.+-mmy-flag.+", content)
+    assert re.search(r"cpp_link_args =.+-mmy-flag.+", content)
+
 
 
 def test_linker_scripts_via_conf():
@@ -169,8 +191,8 @@ def test_linker_scripts_via_conf():
 
     t.run("install . -pr:b=profile -pr=profile")
     content = t.load(MesonToolchain.native_filename)
-    assert "c_link_args = ['-flag0', '-other=val', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
-    assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
+    assert "c_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
+    assert "cpp_link_args = ['-flag0', '-other=val', '-m64', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
 
 
 def test_correct_quotes():
diff --git a/test/integration/workspace/test_workspace.py b/test/integration/workspace/test_workspace.py
index 2dbb50917..18b25b256 100644
--- a/test/integration/workspace/test_workspace.py
+++ b/test/integration/workspace/test_workspace.py
@@ -153,8 +153,7 @@ class TestAddRemove:
                             name = open(os.path.join(workspace_folder, f, "name.txt")).read().strip()
                             version = open(os.path.join(workspace_folder, f,
                                                         "version.txt")).read().strip()
-                            p = os.path.join(f, "conanfile.py").replace("\\\\", "/")
-                            result[f"{name}/{version}"] = {"path": p}
+                            result[f"{name}/{version}"] = {"path": f}
                     return result
                 """)
         else:
@@ -166,8 +165,7 @@ class TestAddRemove:
                    result = {}
                    for f in os.listdir(workspace_api.folder):
                        if os.path.isdir(os.path.join(workspace_api.folder, f)):
-                           f = os.path.join(f, "conanfile.py").replace("\\\\", "/")
-                           conanfile = workspace_api.load(f)
+                           conanfile = workspace_api.load(os.path.join(f, "conanfile.py"))
                            result[f"{conanfile.name}/{conanfile.version}"] = {"path": f}
                    return result
                """)
@@ -178,12 +176,12 @@ class TestAddRemove:
                 "dep1/version.txt": "2.1"})
         c.run("workspace info --format=json")
         info = json.loads(c.stdout)
-        assert info["editables"] == {"pkg/2.1": {"path": "dep1/conanfile.py"}}
+        assert info["editables"] == {"pkg/2.1": {"path": "dep1"}}
         c.save({"dep1/name.txt": "other",
                 "dep1/version.txt": "14.5"})
         c.run("workspace info --format=json")
         info = json.loads(c.stdout)
-        assert info["editables"] == {"other/14.5": {"path": "dep1/conanfile.py"}}
+        assert info["editables"] == {"other/14.5": {"path": "dep1"}}
         c.run("install --requires=other/14.5")
         # Doesn't fail
         assert "other/14.5 - Editable" in c.out
@@ -239,6 +237,15 @@ class TestAddRemove:
         c.run("workspace open dep/0.1", assert_error=True)
         assert "ERROR: Can't open a dependency that is already an editable: dep/0.1" in c.out
 
+    def test_remove_product(self):
+        c = TestClient(light=True)
+        c.save({"conanws.py": "name='myws'",
+                "mydeppkg/conanfile.py": GenConanfile("mydeppkg", "0.1")})
+        c.run("workspace add mydeppkg --product")
+        c.run("workspace remove mydeppkg")
+        c.run("workspace info")
+        assert "mydeppkg" not in c.out
+
 
 class TestOpenAdd:
     def test_without_git(self):
@@ -319,3 +326,80 @@ class TestOpenAdd:
                                              "EditableBuild")})
         assert "pkga/0.1: WARN: BUILD PKGA!" in c.out
         assert "pkgb/0.1: WARN: BUILD PKGB!" in c.out
+
+
+class TestWorkspaceBuild:
+    def test_dynamic_products(self):
+        c = TestClient(light=True)
+
+        workspace = textwrap.dedent("""\
+            import os
+            name = "myws"
+
+            workspace_folder = os.path.dirname(os.path.abspath(__file__))
+
+            def products():
+                result = []
+                for f in os.listdir(workspace_folder):
+                    if os.path.isdir(os.path.join(workspace_folder, f)):
+                        if f.startswith("product"):
+                            result.append(f)
+                return result
+                    """)
+
+        c.save({"conanws.py": workspace,
+                "lib1/conanfile.py": GenConanfile("lib1", "0.1"),
+                "product_app1/conanfile.py": GenConanfile("app1", "0.1")})
+        c.run("workspace add lib1")
+        c.run("workspace add product_app1")
+        c.run("workspace info --format=json")
+        info = json.loads(c.stdout)
+        assert info["products"] == ["product_app1"]
+
+    def test_build(self):
+        c = TestClient(light=True)
+        c.save({"conanws.yml": ""})
+
+        c.save({"pkga/conanfile.py": GenConanfile("pkga", "0.1").with_build_msg("BUILD PKGA!"),
+                "pkgb/conanfile.py": GenConanfile("pkgb", "0.1").with_build_msg("BUILD PKGB!")
+               .with_requires("pkga/0.1")})
+        c.run("workspace add pkga")
+        c.run("workspace add pkgb --product")
+        c.run("workspace info --format=json")
+        assert json.loads(c.stdout)["products"] == ["pkgb"]
+        c.run("workspace build")
+        c.assert_listed_binary({"pkga/0.1": ("da39a3ee5e6b4b0d3255bfef95601890afd80709",
+                                             "EditableBuild")})
+        assert "pkga/0.1: WARN: BUILD PKGA!" in c.out
+        assert "conanfile.py (pkgb/0.1): WARN: BUILD PKGB!" in c.out
+
+        # It is also possible to build a specific package by path
+        # equivalent to ``conan build <path> --build=editable``
+        # This can be done even if it is not a product
+        c.run("workspace build pkgc", assert_error=True)
+        assert "ERROR: Product 'pkgc' not defined in the workspace as editable" in c.out
+        c.run("workspace build pkgb")
+        c.assert_listed_binary({"pkga/0.1": ("da39a3ee5e6b4b0d3255bfef95601890afd80709",
+                                             "EditableBuild")})
+        assert "pkga/0.1: WARN: BUILD PKGA!" in c.out
+        assert "conanfile.py (pkgb/0.1): WARN: BUILD PKGB!" in c.out
+
+        c.run("workspace build pkga")
+        assert "conanfile.py (pkga/0.1): Calling build()" in c.out
+        assert "conanfile.py (pkga/0.1): WARN: BUILD PKGA!" in c.out
+
+    def test_error_if_no_products(self):
+        c = TestClient(light=True)
+        c.save({"conanws.yml": ""})
+        c.run("workspace build", assert_error=True)
+        assert "There are no products defined in the workspace, can't build" in c.out
+
+
+def test_new():
+    # Very basic workspace for testing
+    c = TestClient()
+    c.run("new workspace")
+    c.run("workspace info")
+    assert "liba/0.1" in c.out
+    assert "libb/0.1" in c.out
+    assert "app1/0.1" in c.out
diff --git a/test/unittests/tools/files/test_zipping.py b/test/unittests/tools/files/test_zipping.py
index 42e89bcee..0c41a494d 100644
--- a/test/unittests/tools/files/test_zipping.py
+++ b/test/unittests/tools/files/test_zipping.py
@@ -1,6 +1,6 @@
-import os
 import zipfile
-from os.path import basename
+import tarfile
+from os.path import join, exists
 
 import pytest
 
@@ -8,6 +8,7 @@ from conan.tools.files import unzip
 from conan.test.utils.mocks import ConanFileMock
 from conan.test.utils.test_files import temp_folder
 from conans.util.files import save
+from conan.errors import ConanException
 
 
 def test_impossible_to_import_untargz():
@@ -15,17 +16,136 @@ def test_impossible_to_import_untargz():
         from conan.tools.files import untargz
 
 
-def test_unzip():
+def create_example_zip(root_file=True, subfolder=False):
     tmp_dir = temp_folder()
-    file_path = os.path.join(tmp_dir, "foo.txt")
-    save(os.path.join(tmp_dir, "foo.txt"), "bar")
-    zf = zipfile.ZipFile(os.path.join(tmp_dir, 'zipfile.zip'), mode='w')
-    zf.write(file_path, basename(file_path))
+    archive = join(tmp_dir, "zipfile.zip")
+    zf = zipfile.ZipFile(archive, mode="w")
+    if root_file:
+        foo_txt = join(tmp_dir, "foo.txt")
+        save(foo_txt, "foo-content")
+        zf.write(foo_txt, "foo.txt")
+    if subfolder:
+        src_bar_txt = join(tmp_dir, "src", "bar.txt")
+        save(src_bar_txt, "bar-content")
+        zf.write(src_bar_txt, join("src", "bar.txt"))
     zf.close()
+    return archive
+
+
+def test_unzip():
+    archive = create_example_zip(subfolder=True)
+    conanfile = ConanFileMock({})
+
+    # Unzip and check permissions are kept
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir)
+    assert exists(join(dest_dir, "foo.txt"))
+    assert exists(join(dest_dir, "src", "bar.txt"))
+
+
+def test_unzip_with_pattern():
+    archive = create_example_zip(subfolder=True)
+    conanfile = ConanFileMock({})
+
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir, pattern="foo.txt")
+    assert exists(join(dest_dir, "foo.txt"))
+    assert not exists(join(dest_dir, "src", "bar.txt"))
+
+
+def test_unzip_with_strip_root():
+    archive = create_example_zip(root_file=False, subfolder=True)
+    conanfile = ConanFileMock({})
+
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir, strip_root=True)
+    assert exists(join(dest_dir, "bar.txt"))
+
+
+def test_unzip_with_strip_root_fails():
+    archive = create_example_zip(root_file=True, subfolder=True)
+    conanfile = ConanFileMock({})
+
+    dest_dir = temp_folder()
+    with pytest.raises(ConanException) as error:
+        unzip(conanfile, archive, dest_dir, strip_root=True)
+    assert "The zip file contains more than 1 folder in the root" in str(error.value)
+
+
+def test_unzip_with_strip_root_and_pattern():
+    archive = create_example_zip(root_file=True, subfolder=True)
+    conanfile = ConanFileMock({})
+
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir, pattern="src/*", strip_root=True)
+    assert exists(join(dest_dir, "bar.txt"))
+    assert not exists(join(dest_dir, "foo.txt"))
+
+
+def create_example_tar(root_file=True, subfolder=False):
+    tmp_dir = temp_folder()
+    tar_path = join(tmp_dir, "file.tgz")
+    tar = tarfile.open(tar_path, "w:gz")
+    if root_file:
+        foo_txt = join(tmp_dir, "foo.txt")
+        save(foo_txt, "foo-content")
+        tar.add(foo_txt, "foo.txt")
+    if subfolder:
+        src_bar_txt = join(tmp_dir, "src", "bar.txt")
+        save(src_bar_txt, "bar-content")
+        tar.add(src_bar_txt, join("src", "bar.txt"))
+    tar.close()
+    return tar_path
+
+
+def test_untargz():
+    archive = create_example_tar(subfolder=True)
+    conanfile = ConanFileMock({})
+
+    # Unzip and check permissions are kept
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir)
+    assert exists(join(dest_dir, "foo.txt"))
+    assert exists(join(dest_dir, "src", "bar.txt"))
+
+
+def test_untargz_with_pattern():
+    archive = create_example_tar(subfolder=True)
+    conanfile = ConanFileMock({})
+
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir, pattern="foo.txt")
+    assert exists(join(dest_dir, "foo.txt"))
+    assert not exists(join(dest_dir, "src", "bar.txt"))
+
+
+def test_untargz_with_strip_root():
+    archive = create_example_tar(root_file=False, subfolder=True)
+    conanfile = ConanFileMock({})
+
+    # Unzip and check permissions are kept
+    dest_dir = temp_folder()
+    unzip(conanfile, archive, dest_dir, strip_root=True)
+    assert exists(join(dest_dir, "bar.txt"))
+
+
+def test_untargz_with_strip_root_fails():
+    archive = create_example_tar(root_file=True, subfolder=True)
+    conanfile = ConanFileMock({})
+
+    # Unzip and check permissions are kept
+    dest_dir = temp_folder()
+    with pytest.raises(ConanException) as error:
+        unzip(conanfile, archive, dest_dir, strip_root=True)
+    assert "The tgz file contains more than 1 folder in the root" in str(error.value)
+
 
+def test_untargz_with_strip_root_and_pattern():
+    archive = create_example_tar(root_file=True, subfolder=True)
     conanfile = ConanFileMock({})
 
     # Unzip and check permissions are kept
     dest_dir = temp_folder()
-    unzip(conanfile, os.path.join(tmp_dir, 'zipfile.zip'), dest_dir)
-    assert os.path.exists(os.path.join(dest_dir, "foo.txt"))
+    unzip(conanfile, archive, dest_dir, pattern="src/*", strip_root=True)
+    assert exists(join(dest_dir, "bar.txt"))
+    assert not exists(join(dest_dir, "foo.txt"))