Repository: conan-io/conan
Pull request #16022 (2024-04-04 17:07:00)

New ``pkglist`` list manipuation command
Changelog: Feature: New ``conan pkglist merge`` command to merge multiple package lists
Changelog: Feature: New ``conan pkglist find-remote`` command to find matching in remotes for list of packages in the cache.
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/14145
Close https://github.com/conan-io/conan/issues/14144

This can be the missing piece for https://github.com/conan-io/conan/pull/15682




memsharded: Lets rename to ``conan pkglist merge/find-remote``

Patch:
diff --git a/conan/api/model.py b/conan/api/model.py
index 4a29bbea0..9e23cdfe1 100644
--- a/conan/api/model.py
+++ b/conan/api/model.py
@@ -47,6 +47,9 @@ class MultiPackagesList:
     def __init__(self):
         self.lists = {}
 
+    def setdefault(self, key, default):
+        return self.lists.setdefault(key, default)
+
     def __getitem__(self, name):
         try:
             return self.lists[name]
@@ -63,6 +66,10 @@ class MultiPackagesList:
         return {k: v.serialize() if isinstance(v, PackagesList) else v
                 for k, v in self.lists.items()}
 
+    def merge(self, other):
+        for k, v in other.lists.items():
+            self.lists.setdefault(k, PackagesList()).merge(v)
+
     @staticmethod
     def load(file):
         content = json.loads(load(file))
@@ -76,9 +83,18 @@ class MultiPackagesList:
         pkglist.lists = result
         return pkglist
 
+    @staticmethod
+    def from_graph(graph, graph_recipes=None, graph_binaries=None):
+        graph = {"graph": graph.serialize()}
+        return MultiPackagesList._define_graph(graph, graph_recipes, graph_binaries)
+
     @staticmethod
     def load_graph(graphfile, graph_recipes=None, graph_binaries=None):
         graph = json.loads(load(graphfile))
+        return MultiPackagesList._define_graph(graph, graph_recipes, graph_binaries)
+
+    @staticmethod
+    def _define_graph(graph, graph_recipes=None, graph_binaries=None):
         pkglist = MultiPackagesList()
         cache_list = PackagesList()
         if graph_recipes is None and graph_binaries is None:
@@ -143,6 +159,16 @@ class PackagesList:
     def __init__(self):
         self.recipes = {}
 
+    def merge(self, other):
+        def recursive_dict_update(d, u):  # TODO: repeated from conandata.py
+            for k, v in u.items():
+                if isinstance(v, dict):
+                    d[k] = recursive_dict_update(d.get(k, {}), v)
+                else:
+                    d[k] = v
+            return d
+        recursive_dict_update(self.recipes, other.recipes)
+
     def split(self):
         """
         Returns a list of PackageList, splitted one per reference.
diff --git a/conan/cli/commands/pkglist.py b/conan/cli/commands/pkglist.py
new file mode 100644
index 000000000..ee8d4aaaa
--- /dev/null
+++ b/conan/cli/commands/pkglist.py
@@ -0,0 +1,93 @@
+import copy
+
+from conan.api.conan_api import ConanAPI
+from conan.api.model import MultiPackagesList, PackagesList
+from conan.cli import make_abs_path
+from conan.cli.command import conan_command, conan_subcommand
+from conan.cli.commands.list import print_list_text, print_list_json
+from conan.cli.formatters.list import list_packages_html
+from conans.errors import NotFoundException
+
+
+@conan_command(group="Consumer")
+def pkglist(conan_api: ConanAPI, parser, *args):  # noqa
+    """
+    Several operations over package lists
+    """
+
+
+@conan_subcommand(formatters={"text": print_list_text,
+                              "json": print_list_json,
+                              "html": list_packages_html})
+def pkglist_find_remote(conan_api, parser, subparser, *args):
+    """
+    (Experimental) Find the remotes of a list of packages in the cache
+    """
+    subparser.add_argument('list', help="Input package list")
+    subparser.add_argument("-r", "--remote", default=None, action="append",
+                           help="Remote names. Accepts wildcards "
+                                "('*' means all the remotes available)")
+    args = parser.parse_args(*args)
+
+    listfile = make_abs_path(args.list)
+    multi_pkglist = MultiPackagesList.load(listfile)
+    package_list = multi_pkglist["Local Cache"]
+    selected_remotes = conan_api.remotes.list(args.remote)
+
+    result = MultiPackagesList()
+    for r in selected_remotes:
+        result_pkg_list = PackagesList()
+        for ref, recipe_bundle in package_list.refs().items():
+            ref_no_rev = copy.copy(ref)  # TODO: Improve ugly API
+            ref_no_rev.revision = None
+            try:
+                revs = conan_api.list.recipe_revisions(ref_no_rev, remote=r)
+            except NotFoundException:
+                continue
+            if ref not in revs:  # not found
+                continue
+            result_pkg_list.add_refs([ref])
+            for pref, pref_bundle in package_list.prefs(ref, recipe_bundle).items():
+                pref_no_rev = copy.copy(pref)  # TODO: Improve ugly API
+                pref_no_rev.revision = None
+                try:
+                    prevs = conan_api.list.package_revisions(pref_no_rev, remote=r)
+                except NotFoundException:
+                    continue
+                if pref in prevs:
+                    result_pkg_list.add_prefs(ref, [pref])
+                    info = recipe_bundle["packages"][pref.package_id]["info"]
+                    result_pkg_list.add_configurations({pref: info})
+        if result_pkg_list.recipes:
+            result.add(r.name, result_pkg_list)
+
+    return {
+        "results": result.serialize(),
+        "conan_api": conan_api,
+        "cli_args": " ".join([f"{arg}={getattr(args, arg)}"
+                              for arg in vars(args) if getattr(args, arg)])
+    }
+
+
+@conan_subcommand(formatters={"text": print_list_text,
+                              "json": print_list_json,
+                              "html": list_packages_html})
+def pkglist_merge(conan_api, parser, subparser, *args):
+    """
+    (Experimental) Merge several package lists into a single one
+    """
+    subparser.add_argument("-l", "--list", help="Package list file", action="append")
+    args = parser.parse_args(*args)
+
+    result = MultiPackagesList()
+    for pkglist in args.list:
+        listfile = make_abs_path(pkglist)
+        multi_pkglist = MultiPackagesList.load(listfile)
+        result.merge(multi_pkglist)
+
+    return {
+        "results": result.serialize(),
+        "conan_api": conan_api,
+        "cli_args": " ".join([f"{arg}={getattr(args, arg)}"
+                              for arg in vars(args) if getattr(args, arg)])
+    }
diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py
index 04964f18f..f8a9cca02 100644
--- a/conan/tools/cmake/cmakedeps/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps/cmakedeps.py
@@ -17,6 +17,7 @@ from conan.tools.cmake.cmakedeps.templates.targets import TargetsTemplate
 from conan.tools.files import save
 from conans.client.generators import relativize_generated_file
 from conan.errors import ConanException
+from conans.model.dependencies import get_transitive_requires
 
 
 class CMakeDeps(object):
@@ -220,3 +221,7 @@ class CMakeDeps(object):
                             undefined=jinja2.StrictUndefined)
         conandeps = template.render({"configs": configs})
         save(self._conanfile, "conandeps_legacy.cmake", conandeps)
+
+    def get_transitive_requires(self, conanfile):
+        # Prepared to filter transitive tool-requires with visible=True
+        return get_transitive_requires(self._conanfile, conanfile)
diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py
index 07b44a4a8..ea0184457 100644
--- a/conan/tools/cmake/cmakedeps/templates/config.py
+++ b/conan/tools/cmake/cmakedeps/templates/config.py
@@ -37,6 +37,9 @@ class ConfigTemplate(CMakeDepsFileTemplate):
     @property
     def template(self):
         return textwrap.dedent("""\
+        {%- macro pkg_var(pkg_name, var, config_suffix) -%}
+             {{'${'+pkg_name+'_'+var+config_suffix+'}'}}
+        {%- endmacro -%}
         ########## MACROS ###########################################################################
         #############################################################################################
 
@@ -57,7 +60,7 @@ class ConfigTemplate(CMakeDepsFileTemplate):
 
         check_build_type_defined()
 
-        foreach(_DEPENDENCY {{ '${' + pkg_name + '_FIND_DEPENDENCY_NAMES' + '}' }} )
+        foreach(_DEPENDENCY {{ pkg_var(pkg_name, 'FIND_DEPENDENCY_NAMES', '') }} )
             # Check that we have not already called a find_package with the transitive dependency
             if(NOT {{ '${_DEPENDENCY}' }}_FOUND)
                 find_dependency({{ '${_DEPENDENCY}' }} REQUIRED ${${_DEPENDENCY}_FIND_MODE})
@@ -65,13 +68,13 @@ class ConfigTemplate(CMakeDepsFileTemplate):
         endforeach()
 
         set({{ file_name }}_VERSION_STRING "{{ version }}")
-        set({{ file_name }}_INCLUDE_DIRS {{ '${' + pkg_name + '_INCLUDE_DIRS' + config_suffix + '}' }} )
-        set({{ file_name }}_INCLUDE_DIR {{ '${' + pkg_name + '_INCLUDE_DIRS' + config_suffix + '}' }} )
-        set({{ file_name }}_LIBRARIES {{ '${' + pkg_name + '_LIBRARIES' + config_suffix + '}' }} )
-        set({{ file_name }}_DEFINITIONS {{ '${' + pkg_name + '_DEFINITIONS' + config_suffix + '}' }} )
+        set({{ file_name }}_INCLUDE_DIRS {{ pkg_var(pkg_name, 'INCLUDE_DIRS', config_suffix) }} )
+        set({{ file_name }}_INCLUDE_DIR {{ pkg_var(pkg_name, 'INCLUDE_DIRS', config_suffix) }} )
+        set({{ file_name }}_LIBRARIES {{ pkg_var(pkg_name, 'LIBRARIES', config_suffix) }} )
+        set({{ file_name }}_DEFINITIONS {{ pkg_var(pkg_name, 'DEFINITIONS', config_suffix) }} )
 
         # Only the first installed configuration is included to avoid the collision
-        foreach(_BUILD_MODULE {{ '${' + pkg_name + '_BUILD_MODULES_PATHS' + config_suffix + '}' }} )
+        foreach(_BUILD_MODULE {{ pkg_var(pkg_name, 'BUILD_MODULES_PATHS', config_suffix) }} )
             message({% raw %}${{% endraw %}{{ file_name }}_MESSAGE_MODE} "Conan: Including build module from '${_BUILD_MODULE}'")
             include({{ '${_BUILD_MODULE}' }})
         endforeach()
@@ -80,7 +83,7 @@ class ConfigTemplate(CMakeDepsFileTemplate):
         # Check that the specified components in the find_package(Foo COMPONENTS x y z) are there
         # This is the variable filled by CMake with the requested components in find_package
         if({{ file_name }}_FIND_COMPONENTS)
-            foreach(_FIND_COMPONENT {{ '${'+file_name+'_FIND_COMPONENTS}' }})
+            foreach(_FIND_COMPONENT {{ pkg_var(file_name, 'FIND_COMPONENTS', '') }})
                 if (TARGET ${_FIND_COMPONENT})
                     message({% raw %}${{% endraw %}{{ file_name }}_MESSAGE_MODE} "Conan: Component '${_FIND_COMPONENT}' found in package '{{ pkg_name }}'")
                 else()
diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
index 8a8508d37..932f39c95 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
@@ -1,7 +1,6 @@
 import textwrap
 
 from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate
-from conans.model.dependencies import get_transitive_requires
 
 """
 
@@ -45,14 +44,17 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
         # Avoid multiple calls to find_package to append duplicated properties to the targets
         include_guard()
 
-        {%- macro tvalue(pkg_name, comp_name, var, config_suffix) -%}
+        {%- macro comp_var(pkg_name, comp_name, var, config_suffix) -%}
             {{'${'+pkg_name+'_'+comp_name+'_'+var+config_suffix+'}'}}
         {%- endmacro -%}
+        {%- macro pkg_var(pkg_name, var, config_suffix) -%}
+            {{'${'+pkg_name+'_'+var+config_suffix+'}'}}
+        {%- endmacro -%}
 
         ########### VARIABLES #######################################################################
         #############################################################################################
         set({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "") # Will be filled later
-        conan_find_apple_frameworks({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${' }}{{ pkg_name }}_FRAMEWORKS{{ config_suffix }}}" "{{ '${' }}{{ pkg_name }}_FRAMEWORK_DIRS{{ config_suffix }}}")
+        conan_find_apple_frameworks({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ pkg_var(pkg_name, 'FRAMEWORKS', config_suffix) }}" "{{ pkg_var(pkg_name, 'FRAMEWORK_DIRS', config_suffix) }}")
 
         set({{ pkg_name }}_LIBRARIES_TARGETS "") # Will be filled later
 
@@ -64,35 +66,35 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
 
         set_property(TARGET {{ pkg_name + '_DEPS_TARGET'}}
                      APPEND PROPERTY INTERFACE_LINK_LIBRARIES
-                     $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_FRAMEWORKS_FOUND'+config_suffix+'}' }}>
-                     $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_SYSTEM_LIBS'+config_suffix+'}' }}>
+                     $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'FRAMEWORKS_FOUND', config_suffix) }}>
+                     $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'SYSTEM_LIBS', config_suffix) }}>
                      $<$<CONFIG:{{configuration}}>:{{ deps_targets_names }}>)
 
         ####### Find the libraries declared in cpp_info.libs, create an IMPORTED target for each one and link the
         ####### {{pkg_name}}_DEPS_TARGET to all of them
-        conan_package_library_targets("{{ '${' }}{{ pkg_name }}_LIBS{{ config_suffix }}}"    # libraries
-                                      "{{ '${' }}{{ pkg_name }}_LIB_DIRS{{ config_suffix }}}" # package_libdir
-                                      "{{ '${' }}{{ pkg_name }}_BIN_DIRS{{ config_suffix }}}" # package_bindir
-                                      "{{ '${' }}{{ pkg_name }}_LIBRARY_TYPE{{ config_suffix }}}"
-                                      "{{ '${' }}{{ pkg_name }}_IS_HOST_WINDOWS{{ config_suffix }}}"
+        conan_package_library_targets("{{ pkg_var(pkg_name, 'LIBS', config_suffix) }}"    # libraries
+                                      "{{ pkg_var(pkg_name, 'LIB_DIRS', config_suffix) }}" # package_libdir
+                                      "{{ pkg_var(pkg_name, 'BIN_DIRS', config_suffix) }}" # package_bindir
+                                      "{{ pkg_var(pkg_name, 'LIBRARY_TYPE', config_suffix) }}"
+                                      "{{ pkg_var(pkg_name, 'IS_HOST_WINDOWS', config_suffix) }}"
                                       {{ pkg_name + '_DEPS_TARGET'}}
                                       {{ pkg_name }}_LIBRARIES_TARGETS  # out_libraries_targets
                                       "{{ config_suffix }}"
                                       "{{ pkg_name }}"    # package_name
-                                      "{{ '${' }}{{ pkg_name }}_NO_SONAME_MODE{{ config_suffix }}}")  # soname
+                                      "{{ pkg_var(pkg_name, 'NO_SONAME_MODE', config_suffix) }}")  # soname
 
         # FIXME: What is the result of this for multi-config? All configs adding themselves to path?
-        set(CMAKE_MODULE_PATH {{ '${' }}{{ pkg_name }}_BUILD_DIRS{{ config_suffix }}} {{ '${' }}CMAKE_MODULE_PATH})
+        set(CMAKE_MODULE_PATH {{ pkg_var(pkg_name, 'BUILD_DIRS', config_suffix) }} {{ '${' }}CMAKE_MODULE_PATH})
         {% if not components_names %}
 
         ########## GLOBAL TARGET PROPERTIES {{ configuration }} ########################################
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_LINK_LIBRARIES
-                         $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_OBJECTS'+config_suffix+'}' }}>
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS}>
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'OBJECTS', config_suffix) }}>
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'LIBRARIES_TARGETS', '') }}>
                          )
 
-            if("{{ '${' }}{{ pkg_name }}_LIBS{{ config_suffix }}}" STREQUAL "")
+            if("{{ pkg_var(pkg_name, 'LIBS', config_suffix) }}" STREQUAL "")
                 # If the package is not declaring any "cpp_info.libs" the package deps, system libs,
                 # frameworks etc are not linked to the imported targets and we need to do it to the
                 # global target
@@ -103,27 +105,27 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
 
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_LINK_OPTIONS
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'LINKER_FLAGS', config_suffix) }}>)
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_INCLUDE_DIRS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'INCLUDE_DIRS', config_suffix) }}>)
             # Necessary to find LINK shared libraries in Linux
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIB_DIRS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'LIB_DIRS', config_suffix) }}>)
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_COMPILE_DEFINITIONS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'COMPILE_DEFINITIONS', config_suffix) }}>)
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_COMPILE_OPTIONS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'COMPILE_OPTIONS', config_suffix) }}>)
 
             {%- if set_interface_link_directories %}
 
             # This is only used for '#pragma comment(lib, "foo")' (automatic link)
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
-                         $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIB_DIRS{{config_suffix}}}>)
+                         $<$<CONFIG:{{configuration}}>:{{ pkg_var(pkg_name, 'LIB_DIRS', config_suffix) }}>)
             {%- endif %}
 
 
@@ -137,7 +139,7 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
             ########## COMPONENT {{ comp_target_name }} #############
 
                 set({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "")
-                conan_find_apple_frameworks({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS'+config_suffix+'}' }}" "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORK_DIRS'+config_suffix+'}' }}")
+                conan_find_apple_frameworks({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ comp_var(pkg_name, comp_variable_name, 'FRAMEWORKS', config_suffix) }}" "{{ comp_var(pkg_name, comp_variable_name, 'FRAMEWORK_DIRS', config_suffix) }}")
 
                 set({{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS "")
 
@@ -148,33 +150,33 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
 
                 set_property(TARGET {{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}}
                              APPEND PROPERTY INTERFACE_LINK_LIBRARIES
-                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS_FOUND'+config_suffix+'}' }}>
-                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_SYSTEM_LIBS'+config_suffix+'}' }}>
-                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_DEPENDENCIES'+config_suffix+'}' }}>
+                             $<$<CONFIG:{{configuration}}>:{{ comp_var(pkg_name, comp_variable_name, 'FRAMEWORKS_FOUND', config_suffix) }}>
+                             $<$<CONFIG:{{configuration}}>:{{ comp_var(pkg_name, comp_variable_name, 'SYSTEM_LIBS', config_suffix) }}>
+                             $<$<CONFIG:{{configuration}}>:{{ comp_var(pkg_name, comp_variable_name, 'DEPENDENCIES', config_suffix) }}>
                              )
 
                 ####### Find the libraries declared in cpp_info.component["xxx"].libs,
                 ####### create an IMPORTED target for each one and link the '{{pkg_name}}_{{comp_variable_name}}_DEPS_TARGET' to all of them
-                conan_package_library_targets("{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS'+config_suffix+'}' }}"
-                                      "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIB_DIRS'+config_suffix+'}' }}"
-                                      "{{ '${'+pkg_name+'_'+comp_variable_name+'_BIN_DIRS'+config_suffix+'}' }}" # package_bindir
-                                      "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBRARY_TYPE'+config_suffix+'}' }}"
-                                      "{{ '${'+pkg_name+'_'+comp_variable_name+'_IS_HOST_WINDOWS'+config_suffix+'}' }}"
+                conan_package_library_targets("{{ comp_var(pkg_name, comp_variable_name, 'LIBS', config_suffix) }}"
+                                      "{{ comp_var(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix) }}"
+                                      "{{ comp_var(pkg_name, comp_variable_name, 'BIN_DIRS', config_suffix) }}" # package_bindir
+                                      "{{ comp_var(pkg_name, comp_variable_name, 'LIBRARY_TYPE', config_suffix) }}"
+                                      "{{ comp_var(pkg_name, comp_variable_name, 'IS_HOST_WINDOWS', config_suffix) }}"
                                       {{ pkg_name + '_' + comp_variable_name + '_DEPS_TARGET'}}
-                                      {{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS
+                                      {{ pkg_name + '_' + comp_variable_name + '_LIBRARIES_TARGETS'}}
                                       "{{ config_suffix }}"
                                       "{{ pkg_name }}_{{ comp_variable_name }}"
-                                      "{{ '${'+pkg_name+'_'+comp_variable_name+'_NO_SONAME_MODE'+config_suffix+'}' }}")
+                                      "{{ comp_var(pkg_name, comp_variable_name, 'NO_SONAME_MODE', config_suffix) }}")
 
 
                 ########## TARGET PROPERTIES #####################################
                 set_property(TARGET {{comp_target_name}}
                              APPEND PROPERTY INTERFACE_LINK_LIBRARIES
-                             $<$<CONFIG:{{configuration}}>:{{ '${'+pkg_name+'_'+comp_variable_name+'_OBJECTS'+config_suffix+'}' }}>
-                             $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_LIBRARIES_TARGETS}>
+                             $<$<CONFIG:{{configuration}}>:{{ comp_var(pkg_name, comp_variable_name, 'OBJECTS', config_suffix) }}>
+                             $<$<CONFIG:{{configuration}}>:{{ comp_var(pkg_name, comp_variable_name, 'LIBRARIES_TARGETS', '') }}>
                              )
 
-                if("{{ '${' }}{{ pkg_name }}_{{comp_variable_name}}_LIBS{{ config_suffix }}}" STREQUAL "")
+                if("{{ comp_var(pkg_name, comp_variable_name, 'LIBS', config_suffix) }}" STREQUAL "")
                     # If the component is not declaring any "cpp_info.components['foo'].libs" the system, frameworks etc are not
                     # linked to the imported targets and we need to do it to the global target
                     set_property(TARGET {{comp_target_name}}
@@ -183,20 +185,20 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
                 endif()
 
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_LINK_OPTIONS
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'LINKER_FLAGS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'LINKER_FLAGS', config_suffix) }}>)
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'INCLUDE_DIRS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'INCLUDE_DIRS', config_suffix) }}>)
                 set_property(TARGET {{comp_target_name }} APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix) }}>)
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'COMPILE_DEFINITIONS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'COMPILE_DEFINITIONS', config_suffix) }}>)
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'COMPILE_OPTIONS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'COMPILE_OPTIONS', config_suffix) }}>)
 
                 {%- if set_interface_link_directories %}
                 # This is only used for '#pragma comment(lib, "foo")' (automatic link)
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
-                             $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix)}}>)
+                             $<$<CONFIG:{{ configuration }}>:{{ comp_var(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix) }}>)
 
                 {%- endif %}
             {%endfor %}
@@ -235,7 +237,7 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
 
         # Get a list of dependencies target names
         # Declared cppinfo.requires or .components[].requires
-        transitive_reqs = get_transitive_requires(self.cmakedeps._conanfile, self.conanfile)
+        transitive_reqs = self.cmakedeps.get_transitive_requires(self.conanfile)
         if self.conanfile.cpp_info.required_components:
             for dep_name, component_name in self.conanfile.cpp_info.required_components:
                 try:
diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py
index 1d4c8b96d..e86d89be4 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_data.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_data.py
@@ -5,7 +5,6 @@ from conan.tools.cmake.cmakedeps import FIND_MODE_NONE, FIND_MODE_CONFIG, FIND_M
     FIND_MODE_BOTH
 from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate
 from conan.errors import ConanException
-from conans.model.dependencies import get_transitive_requires
 
 
 """
@@ -84,6 +83,12 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
     def template(self):
         # This will be at: XXX-release-data.cmake
         ret = textwrap.dedent("""\
+              {%- macro comp_var(pkg_name, comp_name, var, config_suffix) -%}
+                 {{'${'+pkg_name+'_'+comp_name+'_'+var+config_suffix+'}'}}
+              {%- endmacro -%}
+              {%- macro pkg_var(pkg_name, var, config_suffix) -%}
+                 {{'${'+pkg_name+'_'+var+config_suffix+'}'}}
+              {%- endmacro -%}
               ########### AGGREGATED COMPONENTS AND DEPENDENCIES FOR THE MULTI CONFIG #####################
               #############################################################################################
 
@@ -132,12 +137,12 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
 
               # COMPOUND VARIABLES
               set({{ pkg_name }}_COMPILE_OPTIONS{{ config_suffix }}
-                  "$<$<COMPILE_LANGUAGE:CXX>{{ ':${' }}{{ pkg_name }}_COMPILE_OPTIONS_CXX{{ config_suffix }}}>"
-                  "$<$<COMPILE_LANGUAGE:C>{{ ':${' }}{{ pkg_name }}_COMPILE_OPTIONS_C{{ config_suffix }}}>")
+                  "$<$<COMPILE_LANGUAGE:CXX>:{{ pkg_var(pkg_name, 'COMPILE_OPTIONS_CXX', config_suffix) }}>"
+                  "$<$<COMPILE_LANGUAGE:C>:{{ pkg_var(pkg_name, 'COMPILE_OPTIONS_C', config_suffix) }}>")
               set({{ pkg_name }}_LINKER_FLAGS{{ config_suffix }}
-                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>{{ ':${' }}{{ pkg_name }}_SHARED_LINK_FLAGS{{ config_suffix }}}>"
-                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>{{ ':${' }}{{ pkg_name }}_SHARED_LINK_FLAGS{{ config_suffix }}}>"
-                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,EXECUTABLE>{{ ':${' }}{{ pkg_name }}_EXE_LINK_FLAGS{{ config_suffix }}}>")
+                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:{{ pkg_var(pkg_name, 'SHARED_LINK_FLAGS', config_suffix) }}>"
+                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:{{ pkg_var(pkg_name, 'SHARED_LINK_FLAGS', config_suffix) }}>"
+                  "$<$<STREQUAL{{ ':$' }}<TARGET_PROPERTY:TYPE>,EXECUTABLE>:{{ pkg_var(pkg_name, 'EXE_LINK_FLAGS', config_suffix) }}>")
 
 
               set({{ pkg_name }}_COMPONENTS{{ config_suffix }} {{ components_names }})
@@ -167,13 +172,13 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
 
               # COMPOUND VARIABLES
               set({{ pkg_name }}_{{ comp_variable_name }}_LINKER_FLAGS{{ config_suffix }}
-                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_SHARED_LINK_FLAGS{{ config_suffix }}}>
-                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_SHARED_LINK_FLAGS{{ config_suffix }}}>
-                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_EXE_LINK_FLAGS{{ config_suffix }}}>
+                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:{{ comp_var(pkg_name, comp_variable_name, 'SHARED_LINK_FLAGS', config_suffix) }}>
+                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:{{ comp_var(pkg_name, comp_variable_name, 'SHARED_LINK_FLAGS', config_suffix) }}>
+                      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:{{ comp_var(pkg_name, comp_variable_name, 'EXE_LINK_FLAGS', config_suffix) }}>
               )
               set({{ pkg_name }}_{{ comp_variable_name }}_COMPILE_OPTIONS{{ config_suffix }}
-                  "$<$<COMPILE_LANGUAGE:CXX>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_COMPILE_OPTIONS_CXX{{ config_suffix }}}>"
-                  "$<$<COMPILE_LANGUAGE:C>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_COMPILE_OPTIONS_C{{ config_suffix }}}>")
+                  "$<$<COMPILE_LANGUAGE:CXX>:{{ comp_var(pkg_name, comp_variable_name, 'COMPILE_OPTIONS_CXX', config_suffix) }}>"
+                  "$<$<COMPILE_LANGUAGE:C>:{{ comp_var(pkg_name, comp_variable_name, 'COMPILE_OPTIONS_C', config_suffix) }}>")
 
               {%- endfor %}
           """)
@@ -196,7 +201,7 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         ret = []
         sorted_comps = self.conanfile.cpp_info.get_sorted_components()
         pfolder_var_name = "{}_PACKAGE_FOLDER{}".format(self.pkg_name, self.config_suffix)
-        transitive_requires = get_transitive_requires(self.cmakedeps._conanfile, self.conanfile)
+        transitive_requires = self.cmakedeps.get_transitive_requires(self.conanfile)
         for comp_name, comp in sorted_comps.items():
             deps_cpp_cmake = _TargetDataContext(comp, pfolder_var_name, self._root_folder,
                                                 self.require, self.cmake_package_type,
@@ -230,7 +235,7 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         if self.require.build:
             return []
 
-        transitive_reqs = get_transitive_requires(self.cmakedeps._conanfile, self.conanfile)
+        transitive_reqs = self.cmakedeps.get_transitive_requires(self.conanfile)
         # Previously it was filtering here components, but not clear why the file dependency
         # should be skipped if components are not being required, why would it declare a
         # dependency to it?
@@ -242,7 +247,7 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         ret = {}
         if self.require.build:
             return ret
-        deps = get_transitive_requires(self.cmakedeps._conanfile, self.conanfile)
+        deps = self.cmakedeps.get_transitive_requires(self.conanfile)
         for dep in deps.values():
             dep_file_name = self.cmakedeps.get_cmake_package_name(dep, self.generating_module)
             find_mode = self.cmakedeps.get_find_mode(dep)
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index 2e019d88d..3cd4ce73b 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -6,8 +6,8 @@ from jinja2 import Template
 from conan.errors import ConanException
 from conan.internal import check_duplicated_generator
 from conan.internal.internal_tools import raise_on_universal_arch
-from conan.tools.apple.apple import to_apple_arch, is_apple_os, apple_min_version_flag, \
-    apple_sdk_path, resolve_apple_flags
+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.env import VirtualBuildEnv
diff --git a/conans/model/settings.py b/conans/model/settings.py
index d9cf3e8ed..8ebe924b4 100644
--- a/conans/model/settings.py
+++ b/conans/model/settings.py
@@ -18,25 +18,32 @@ def undefined_field(name, field, fields=None, value=None):
     return ConanException("\n".join(result))
 
 
-class SettingsItem(object):
+class SettingsItem:
     """ represents a setting value and its child info, which could be:
     - A range of valid values: [Debug, Release] (for settings.compiler.runtime of VS)
     - List [None, "ANY"] to accept None or any value
     - A dict {subsetting: definition}, e.g. {version: [], runtime: []} for VS
     """
-    def __init__(self, definition, name):
+    def __init__(self, definition, name, value):
+        self._definition = definition  # range of possible values
         self._name = name  # settings.compiler
-        self._value = None  # gcc
+        self._value = value  # gcc
+
+    @staticmethod
+    def new(definition, name):
+        if definition is None:
+            raise ConanException(f"Definition of settings.yml '{name}' cannot be null")
         if isinstance(definition, dict):
-            self._definition = {}
+            parsed_definitions = {}
             # recursive
             for k, v in definition.items():
                 # None string from yaml definition maps to python None, means not-defined value
                 k = str(k) if k is not None else None
-                self._definition[k] = Settings(v, name, k)
+                parsed_definitions[k] = Settings(v, name, k)
         else:
             # list or tuple of possible values, it can include "ANY"
-            self._definition = [str(v) if v is not None else None for v in definition]
+            parsed_definitions = [str(v) if v is not None else None for v in definition]
+        return SettingsItem(parsed_definitions, name, None)
 
     def __contains__(self, value):
         return value in (self._value or "")
@@ -44,13 +51,11 @@ class SettingsItem(object):
     def copy(self):
         """ deepcopy, recursive
         """
-        result = SettingsItem({}, name=self._name)
-        result._value = self._value
         if not isinstance(self._definition, dict):
-            result._definition = self._definition  # Not necessary to copy this, not mutable
+            definition = self._definition  # Not necessary to copy this, not mutable
         else:
-            result._definition = {k: v.copy() for k, v in self._definition.items()}
-        return result
+            definition = {k: v.copy() for k, v in self._definition.items()}
+        return SettingsItem(definition, self._name, self._value)
 
     def copy_conaninfo_settings(self):
         """ deepcopy, recursive
@@ -66,15 +71,12 @@ class SettingsItem(object):
         - Settings that are "final" (lists), like build_type, or arch or compiler.version they
         can get any value without issues.
         """
-        result = SettingsItem({}, name=self._name)
-        result._value = self._value
         if not isinstance(self._definition, dict):
-            result._definition = self._definition[:] + ["ANY"]
+            definition = self._definition[:] + ["ANY"]
         else:
-            result._definition = {k: v.copy_conaninfo_settings()
-                                  for k, v in self._definition.items()}
-            result._definition["ANY"] = Settings()
-        return result
+            definition = {k: v.copy_conaninfo_settings() for k, v in self._definition.items()}
+            definition["ANY"] = Settings()
+        return SettingsItem(definition, self._name, self._value)
 
     def __bool__(self):
         if not self._value:
@@ -188,8 +190,7 @@ class Settings(object):
             raise ConanException(f"Invalid settings.yml format: '{name}{val}' is not a dictionary")
         self._name = name  # settings, settings.compiler
         self._parent_value = parent_value  # gcc, x86
-        self._data = {k: SettingsItem(v, "%s.%s" % (name, k))
-                      for k, v in definition.items()}
+        self._data = {k: SettingsItem.new(v, f"{name}.{k}") for k, v in definition.items()}
         self._frozen = False
 
     def serialize(self):


Test patch:
diff --git a/conans/test/integration/command_v2/test_combined_pkglist_flows.py b/conans/test/integration/command_v2/test_combined_pkglist_flows.py
index 115f9d1ac..fbd88317d 100644
--- a/conans/test/integration/command_v2/test_combined_pkglist_flows.py
+++ b/conans/test/integration/command_v2/test_combined_pkglist_flows.py
@@ -1,9 +1,10 @@
 import json
+from collections import OrderedDict
 
 import pytest
 
 from conans.test.assets.genconanfile import GenConanfile
-from conans.test.utils.tools import TestClient
+from conans.test.utils.tools import TestClient, TestServer
 
 
 class TestListUpload:
@@ -168,6 +169,92 @@ class TestGraphInfoToPkgList:
         assert len(pkglist["default"]) == 2
 
 
+class TestPkgListFindRemote:
+    """ we can recover a list of remotes for an already installed graph, for metadata download
+    """
+    def test_graph_2_pkg_list_remotes(self):
+        servers = OrderedDict([("default", TestServer()), ("remote2", TestServer())])
+        c = TestClient(servers=servers, inputs=2 * ["admin", "password"], light=True)
+        c.save({"zlib/conanfile.py": GenConanfile("zlib", "1.0"),
+                "app/conanfile.py": GenConanfile("app", "1.0").with_requires("zlib/1.0")})
+        c.run("create zlib")
+        c.run("create app ")
+        c.run("upload zlib* -c -r=default")
+        c.run("upload zlib* -c -r=remote2")
+        c.run("upload app* -c -r=remote2")
+
+        # This install, packages will be in the cache
+        c.run("install --requires=app/1.0 --format=json", redirect_stdout="graph.json")
+        # So list, will not have remote at all
+        c.run("list --graph=graph.json --format=json", redirect_stdout="pkglist.json")
+
+        pkglist = json.loads(c.load("pkglist.json"))
+        assert len(pkglist["Local Cache"]) == 2
+        assert "default" not in pkglist  # The remote doesn't even exist
+
+        # Lets now compute a list finding in the remotes
+        c.run("pkglist find-remote pkglist.json --format=json", redirect_stdout="remotepkg.json")
+        pkglist = json.loads(c.stdout)
+        assert "Local Cache" not in pkglist
+        assert len(pkglist["default"]) == 1
+        assert "zlib/1.0" in pkglist["default"]
+        assert len(pkglist["remote2"]) == 2
+        assert "app/1.0" in pkglist["remote2"]
+        assert "zlib/1.0" in pkglist["remote2"]
+
+        c.run("download --list=remotepkg.json -r=default --metadata=*")
+        assert "zlib/1.0: Retrieving recipe metadata from remote 'default'" in c.out
+        assert "zlib/1.0: Retrieving package metadata" in c.out
+        c.run("download --list=remotepkg.json -r=remote2 --metadata=*")
+        assert "app/1.0: Retrieving recipe metadata from remote 'remote2'" in c.out
+        assert "app/1.0: Retrieving package metadata" in c.out
+
+
+class TestPkgListMerge:
+    """ deep merge lists
+    """
+    def test_graph_2_pkg_list_remotes(self):
+        servers = OrderedDict([("default", TestServer()), ("remote2", TestServer())])
+        c = TestClient(servers=servers, inputs=2 * ["admin", "password"])
+        c.save({"zlib/conanfile.py": GenConanfile("zlib", "1.0").with_settings("build_type"),
+                "bzip2/conanfile.py": GenConanfile("bzip2", "1.0").with_settings("build_type"),
+                "app/conanfile.py": GenConanfile("app", "1.0").with_requires("zlib/1.0", "bzip2/1.0")
+                                                              .with_settings("build_type")})
+        c.run("create zlib")
+        c.run("create bzip2")
+        c.run("create app ")
+
+        c.run("list zlib:* --format=json", redirect_stdout="list1.json")
+        c.run("list bzip2:* --format=json", redirect_stdout="list2.json")
+        c.run("list app:* --format=json", redirect_stdout="list3.json")
+        c.run("pkglist merge --list=list1.json --list=list2.json --list=list3.json --format=json",
+              redirect_stdout="release.json")
+        final = json.loads(c.stdout)
+        assert "app/1.0" in final["Local Cache"]
+        assert "zlib/1.0" in final["Local Cache"]
+        assert "bzip2/1.0" in final["Local Cache"]
+
+        c.run("create zlib -s build_type=Debug")
+        c.run("create bzip2 -s build_type=Debug")
+        c.run("create app -s build_type=Debug")
+        c.run("list *:* -fs build_type=Debug --format=json", redirect_stdout="debug.json")
+        c.run("pkglist merge --list=release.json --list=debug.json --format=json",
+              redirect_stdout="release.json")
+        final = json.loads(c.stdout)
+        rev = final["Local Cache"]["zlib/1.0"]["revisions"]["11f74ff5f006943c6945117511ac8b64"]
+        assert len(rev["packages"]) == 2  # Debug and Release
+        settings = rev["packages"]["efa83b160a55b033c4ea706ddb980cd708e3ba1b"]["info"]["settings"]
+        assert settings == {"build_type": "Release"}
+        settings = rev["packages"]["9e186f6d94c008b544af1569d1a6368d8339efc5"]["info"]["settings"]
+        assert settings == {"build_type": "Debug"}
+        rev = final["Local Cache"]["bzip2/1.0"]["revisions"]["9e0352b3eb99ba4ac79bc7eeae2102c5"]
+        assert len(rev["packages"]) == 2  # Debug and Release
+        settings = rev["packages"]["efa83b160a55b033c4ea706ddb980cd708e3ba1b"]["info"]["settings"]
+        assert settings == {"build_type": "Release"}
+        settings = rev["packages"]["9e186f6d94c008b544af1569d1a6368d8339efc5"]["info"]["settings"]
+        assert settings == {"build_type": "Debug"}
+
+
 class TestDownloadUpload:
     @pytest.fixture()
     def client(self):
diff --git a/conans/test/integration/settings/test_settings_user.py b/conans/test/integration/settings/test_settings_user.py
index 984f8137f..eb24e909f 100644
--- a/conans/test/integration/settings/test_settings_user.py
+++ b/conans/test/integration/settings/test_settings_user.py
@@ -81,3 +81,15 @@ def test_settings_user_convert_list_dict():
     c.run("install . -s arch=x86_64 -s arch.subarch=2 ")
     assert "arch=x86_64" in c.out
     assert "arch.subarch=2" in c.out
+
+
+def test_settings_user_error():
+    c = TestClient()
+    settings_user = textwrap.dedent("""\
+        os:
+            Windows:
+                libc: null
+        """)
+    save(os.path.join(c.cache_folder, "settings_user.yml"), settings_user)
+    c.run("profile show", assert_error=True)
+    assert "ERROR: Definition of settings.yml 'settings.os.libc' cannot be null" in c.out
diff --git a/conans/test/integration/toolchains/meson/test_mesontoolchain.py b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
index 6ce4b163f..a79b9bed1 100644
--- a/conans/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -311,70 +311,6 @@ def test_check_pkg_config_paths():
     assert f"build.pkg_config_path = '{os.path.join(base_folder, 'build')}'" in content
 
 
-
-def test_toolchain_and_compilers_build_context():
-    """
-    Tests how MesonToolchain manages the build context profile if the build profile is
-    specifying another compiler path (using conf).
-
-    It should create both native and cross files.
-
-    Issue related: https://github.com/conan-io/conan/issues/15878
-    """
-    host = textwrap.dedent("""
-    [settings]
-    arch=armv8
-    build_type=Release
-    compiler=gcc
-    compiler.cppstd=gnu17
-    compiler.libcxx=libstdc++11
-    compiler.version=11
-    os=Linux
-    """)
-    build = textwrap.dedent("""
-    [settings]
-    os=Linux
-    arch=x86_64
-    compiler=clang
-    compiler.version=12
-    compiler.libcxx=libc++
-    compiler.cppstd=11
-
-    [conf]
-    tools.build:compiler_executables={"asm": "clang", "c": "clang", "cpp": "clang++"}
-    """)
-    conanfile = textwrap.dedent("""
-    import os
-    from conan import ConanFile
-    from conan.tools.files import replace_in_file
-    class helloRecipe(ConanFile):
-        name = "hello"
-        version = "1.0.0"
-        package_type = "application"
-        # Binary configuration
-        settings = "os", "compiler", "build_type", "arch"
-        generators = "MesonToolchain"
-
-        def build(self):
-            native_path = os.path.join(self.generators_folder, "conan_meson_native.ini")
-            cross_path = os.path.join(self.generators_folder, "conan_meson_cross.ini")
-            assert os.path.exists(cross_path)  # sanity check
-            assert os.path.exists(native_path)  # sanity check
-            # This should not raise anything!! Notice the strict=True
-            replace_in_file(self, cross_path, 'c = gcc', "#Hey", strict=True)
-            replace_in_file(self, cross_path, 'cpp = g++', "#Hey", strict=True)
-            replace_in_file(self, native_path, 'c = clang', "#Hey", strict=True)
-            replace_in_file(self, native_path, 'cpp = clang++', "#Hey", strict=True)
-    """)
-    client = TestClient()
-    client.save({
-        "host": host,
-        "build": build,
-        "conanfile.py": conanfile
-    })
-    client.run("build . -pr:h host -pr:b build")
-
-
 def test_toolchain_and_compilers_build_context():
     """
     Tests how MesonToolchain manages the build context profile if the build profile is