Repository: conan-io/conan
Pull request #16065 (2024-04-11 09:45:15)

improve settings.yml error message
Changelog: Fix: Improve error message UX when incorrect ``settings.yml`` or ``settings_user.yml``
Docs: Omit

Also a small refactor of SettingsItem avoiding duplicate creation of objects and processing.

Close https://github.com/conan-io/conan/issues/15720



Patch:
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/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/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