Repository: conan-io/conan
Pull request #16854 (2024-08-21 14:50:37)

`get_property` with `check_type` for CMakeDeps
Changelog: Feature: `get_property` with `check_type` for CMakeDeps
Docs: TODO

This is the second part of this #16848



Patch:
diff --git a/conan/cli/printers/graph.py b/conan/cli/printers/graph.py
index 5790f8e84..ddbd95326 100644
--- a/conan/cli/printers/graph.py
+++ b/conan/cli/printers/graph.py
@@ -1,6 +1,7 @@
 from conan.api.output import ConanOutput, Color, LEVEL_VERBOSE
-from conans.client.graph.graph import BINARY_INVALID, BINARY_MISSING, RECIPE_CONSUMER, RECIPE_VIRTUAL, CONTEXT_BUILD, BINARY_SKIP,\
-    BINARY_PLATFORM
+from conans.client.graph.graph import BINARY_INVALID, BINARY_MISSING, RECIPE_CONSUMER, \
+    RECIPE_VIRTUAL, CONTEXT_BUILD, BINARY_SKIP, \
+    BINARY_PLATFORM, BINARY_BUILD
 
 
 def print_graph_basic(graph):
@@ -129,6 +130,9 @@ def print_graph_packages(graph):
             elif status == BINARY_MISSING or status == BINARY_INVALID:
                 output.write(msg, Color.BRIGHT_CYAN)
                 output.writeln(status, Color.BRIGHT_RED)
+            elif status == BINARY_BUILD:
+                output.write(msg, Color.BRIGHT_CYAN)
+                output.writeln(status, Color.BRIGHT_YELLOW)
             else:
                 # Support python36
                 msg += status
diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py
index e982c9ae5..f4b49bd1e 100644
--- a/conan/tools/cmake/cmakedeps/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps/cmakedeps.py
@@ -149,7 +149,7 @@ class CMakeDeps(object):
         build_suffix = "&build" if build_context else ""
         self._properties.setdefault(f"{dep}{build_suffix}", {}).update({prop: value})
 
-    def get_property(self, prop, dep, comp_name=None):
+    def get_property(self, prop, dep, comp_name=None, check_type=None):
         dep_name = dep.ref.name
         build_suffix = "&build" if str(
             dep_name) in self.build_context_activated and dep.context == "build" else ""
@@ -157,8 +157,8 @@ class CMakeDeps(object):
         try:
             return self._properties[f"{dep_comp}{build_suffix}"][prop]
         except KeyError:
-            return dep.cpp_info.get_property(prop) if not comp_name else dep.cpp_info.components[
-                comp_name].get_property(prop)
+            return dep.cpp_info.get_property(prop, check_type=check_type) if not comp_name \
+                else dep.cpp_info.components[comp_name].get_property(prop, check_type=check_type)
 
     def get_cmake_package_name(self, dep, module_mode=None):
         """Get the name of the file for the find_package(XXX)"""
diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py
index 68c456398..1fc1d61d1 100644
--- a/conan/tools/cmake/cmakedeps/templates/config.py
+++ b/conan/tools/cmake/cmakedeps/templates/config.py
@@ -25,7 +25,7 @@ class ConfigTemplate(CMakeDepsFileTemplate):
     @property
     def additional_variables_prefixes(self):
         prefix_list = (
-            self.cmakedeps.get_property("cmake_additional_variables_prefixes", self.conanfile) or [])
+            self.cmakedeps.get_property("cmake_additional_variables_prefixes", self.conanfile, check_type=list) or [])
         return list(set([self.file_name] + prefix_list))
 
     @property
diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
index 932f39c95..747b2bf01 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
@@ -28,7 +28,7 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
 
         is_win = self.conanfile.settings.get_safe("os") == "Windows"
         auto_link = self.cmakedeps.get_property("cmake_set_interface_link_directories",
-                                                self.conanfile)
+                                                self.conanfile, check_type=bool)
         return {"pkg_name": self.pkg_name,
                 "root_target_name": self.root_target_name,
                 "config_suffix": self.config_suffix,
diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py
index 2aa6bf514..35330ee09 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_data.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_data.py
@@ -356,8 +356,8 @@ class _TargetDataContext(object):
         if require and not require.run:
             self.bin_paths = ""
 
-        build_modules = cmakedeps.get_property("cmake_build_modules", conanfile) or []
+        build_modules = cmakedeps.get_property("cmake_build_modules", conanfile, check_type=list) or []
         self.build_modules_paths = join_paths(build_modules)
         # SONAME flag only makes sense for SHARED libraries
-        nosoname = cmakedeps.get_property("nosoname", conanfile, comp_name)
+        nosoname = cmakedeps.get_property("nosoname", conanfile, comp_name, check_type=bool)
         self.no_soname = str((nosoname if self.library_type == "SHARED" else False) or False).upper()
diff --git a/conans/client/graph/graph_binaries.py b/conans/client/graph/graph_binaries.py
index b589a3c2f..8fdfd4f23 100644
--- a/conans/client/graph/graph_binaries.py
+++ b/conans/client/graph/graph_binaries.py
@@ -122,16 +122,15 @@ class GraphBinariesAnalyzer(object):
         compatibles = self._compatibility.compatibles(conanfile)
         existing = compatibles.pop(original_package_id, None)   # Skip main package_id
         if existing:  # Skip the check if same package_id
-            conanfile.output.info(f"Compatible package ID {original_package_id} equal to "
-                                  "the default package ID: Skipping it.")
+            conanfile.output.debug(f"Compatible package ID {original_package_id} equal to "
+                                   "the default package ID: Skipping it.")
 
         if not compatibles:
             return
 
         def _compatible_found(pkg_id, compatible_pkg):
             diff = conanfile.info.dump_diff(compatible_pkg)
-            conanfile.output.info(f"Main binary package '{original_package_id}' missing. Using "
-                                  f"compatible package '{pkg_id}': {diff}")
+            conanfile.output.success(f"Found compatible package '{pkg_id}': {diff}")
             # So they are available in package_info() method
             conanfile.info = compatible_pkg  # Redefine current
 
@@ -144,6 +143,7 @@ class GraphBinariesAnalyzer(object):
             conanfile.options = conanfile.options.copy_conaninfo_options()
             conanfile.options.update_options(compatible_pkg.options)
 
+        conanfile.output.info(f"Main binary package '{original_package_id}' missing")
         conanfile.output.info(f"Checking {len(compatibles)} compatible configurations")
         for package_id, compatible_package in compatibles.items():
             if should_update_reference(node.ref, update):


Test patch:
diff --git a/conan/test/assets/visual_project_files.py b/conan/test/assets/visual_project_files.py
index 32edd4c99..5db7b7cec 100644
--- a/conan/test/assets/visual_project_files.py
+++ b/conan/test/assets/visual_project_files.py
@@ -1,6 +1,6 @@
 # All harcoded (names, paths etc), refactor it if needed
 
-sln_file = '''
+sln_file = r'''
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2013
 VisualStudioVersion = 12.0.31101.0
@@ -38,7 +38,7 @@ EndGlobal
 
 '''
 
-vcxproj_file = '''<?xml version="1.0" encoding="utf-8"?>
+vcxproj_file = r'''<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
     <ProjectConfiguration Include="Debug|ARM">
@@ -282,6 +282,8 @@ def get_vs_project_files(std=False):
         main = main_file_17_2015
     elif std == "cpp17_2017":
         main = main_file_17_2017
+    else:
+        raise Exception("Incorrect std")
 
     return {"MyProject.sln": sln_file,
             "MyProject/MyProject.vcxproj": vcxproj_file,
diff --git a/test/functional/toolchains/microsoft/test_msbuilddeps.py b/test/functional/toolchains/microsoft/test_msbuilddeps.py
index 5c08c36c4..c69139d1a 100644
--- a/test/functional/toolchains/microsoft/test_msbuilddeps.py
+++ b/test/functional/toolchains/microsoft/test_msbuilddeps.py
@@ -900,7 +900,7 @@ def test_build_requires():
                 msbuild = MSBuild(self)
                 msbuild.build("hello.sln")
             """)
-    hello_vcxproj = textwrap.dedent( r"""<?xml version="1.0" encoding="utf-8"?>
+    hello_vcxproj = textwrap.dedent(r"""<?xml version="1.0" encoding="utf-8"?>
         <Project DefaultTargets="Build" ToolsVersion="15.0"
                xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
           <ItemGroup Label="ProjectConfigurations">
diff --git a/test/integration/lockfile/test_compatibility.py b/test/integration/lockfile/test_compatibility.py
index 4c52ca72d..5a1e35e4c 100644
--- a/test/integration/lockfile/test_compatibility.py
+++ b/test/integration/lockfile/test_compatibility.py
@@ -21,16 +21,16 @@ def test_lockfile_compatibility():
     c.run("create . -pr=profile")
     c.save({"conanfile.py": GenConanfile().with_requires("pkg/1.0")})
     c.run("install .")
-    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing. " \
-           "Using compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
+    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing" in c.out
+    assert "Found compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
 
     c.run("lock create conanfile.py")
-    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing. " \
-           "Using compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
+    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing" in c.out
+    assert "Found compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
 
     c.run("lock create conanfile.py --lockfile=conan.lock")
-    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing. " \
-           "Using compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
+    assert "pkg/1.0: Main binary package 'efa83b160a55b033c4ea706ddb980cd708e3ba1b' missing" in c.out
+    assert "Found compatible package 'da39a3ee5e6b4b0d3255bfef95601890afd80709'" in c.out
 
     c.run("install . --lockfile=conan.lock")
     c.assert_listed_binary({"pkg/1.0": ("da39a3ee5e6b4b0d3255bfef95601890afd80709", "Cache")})
diff --git a/test/integration/package_id/compatible_test.py b/test/integration/package_id/compatible_test.py
index 4f2865f7b..5e27f7816 100644
--- a/test/integration/package_id/compatible_test.py
+++ b/test/integration/package_id/compatible_test.py
@@ -137,16 +137,15 @@ class CompatibleIDsTest(unittest.TestCase):
         self.assertIn(f"pkg/0.1@user/stable: Package '{package_id}' created", client.out)
 
         client.save({"conanfile.py": GenConanfile().with_require("pkg/0.1@user/stable")})
-        client.run("install . -o pkg/*:optimized=2")
+        client.run("install . -o pkg/*:optimized=2 -vv")
         # Information messages
         missing_id = "0a8157f8083f5ece34828d27fb2bf5373ba26366"
         self.assertIn("pkg/0.1@user/stable: PackageInfo!: Option optimized 1!", client.out)
         self.assertIn("pkg/0.1@user/stable: Compatible package ID "
                       f"{missing_id} equal to the default package ID",
                       client.out)
-        self.assertIn("pkg/0.1@user/stable: Main binary package "
-                      f"'{missing_id}' missing. Using compatible package"
-                      f" '{package_id}'", client.out)
+        self.assertIn(f"pkg/0.1@user/stable: Main binary package '{missing_id}' missing", client.out)
+        self.assertIn(f"Found compatible package '{package_id}'", client.out)
         # checking the resulting dependencies
         client.assert_listed_binary({"pkg/0.1@user/stable": (package_id, "Cache")})
         self.assertIn("pkg/0.1@user/stable: Already installed!", client.out)
@@ -399,7 +398,7 @@ class TestNewCompatibility:
         c.run("list pdfium/2020.9:*")
 
         c.run("install --requires=pdfium/2020.9 -pr=myprofile -s build_type=Debug")
-        assert "missing. Using compatible package" in c.out
+        assert "Found compatible package" in c.out
 
     def test_compatibility_erase_package_id(self):
         c = TestClient()
diff --git a/test/integration/package_id/test_cache_compatibles.py b/test/integration/package_id/test_cache_compatibles.py
index 74f1c364c..077e25203 100644
--- a/test/integration/package_id/test_cache_compatibles.py
+++ b/test/integration/package_id/test_cache_compatibles.py
@@ -39,8 +39,9 @@ class TestCacheCompatibles:
         package_id = client.created_package_id("dep/0.1")
 
         client.run("install consumer -s build_type=Debug")
-        assert "dep/0.1: Main binary package '9e186f6d94c008b544af1569d1a6368d8339efc5' missing. "\
-               f"Using compatible package '{package_id}'" in client.out
+        assert "dep/0.1: Main binary package '9e186f6d94c008b544af1569d1a6368d8339efc5' missing"\
+               in client.out
+        assert f"Found compatible package '{package_id}'" in client.out
 
     def test_compatible_recipe_reference(self, client):
         """ check that the recipe name can be used to filter
@@ -86,12 +87,14 @@ def test_cppstd():
 
     client.run(f"install consumer {base_settings} -s compiler.cppstd=17")
     assert "dep/0.1: Checking 3 compatible configurations" in client.out
-    assert "dep/0.1: Main binary package 'ec174bec4a5ee2d44d3e33d9f4fdacd9b65a6772' missing. "\
-           f"Using compatible package '{package_id}'" in client.out
+    assert "dep/0.1: Main binary package 'ec174bec4a5ee2d44d3e33d9f4fdacd9b65a6772' missing" \
+           in client.out
+    assert f"Found compatible package '{package_id}'" in client.out
 
     client.run(f"install consumer {base_settings} -s build_type=Debug -s compiler.cppstd=17")
-    assert "dep/0.1: Main binary package '94758b7bbcb365aaf355913b35431c0da6ed6da5' missing. " \
-           f"Using compatible package '{package_id}'" in client.out
+    assert "dep/0.1: Main binary package '94758b7bbcb365aaf355913b35431c0da6ed6da5' missing" \
+           in client.out
+    assert f"Found compatible package '{package_id}'" in client.out
 
 
 def test_cppstd_validated():
@@ -151,8 +154,9 @@ def test_cppstd_server():
     c.run(f"install consumer {base_settings} -s compiler.cppstd=17")
     assert "dep/0.1: Checking 3 compatible configurations" in c.out
     assert "dep/0.1: Compatible configurations not found in cache, checking servers" in c.out
-    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing. " \
-           "Using compatible package '326c500588d969f55133fdda29506ef61ef03eee': " \
+    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing" \
+           in c.out
+    assert "Found compatible package '326c500588d969f55133fdda29506ef61ef03eee': " \
            "compiler.cppstd=20" in c.out
     c.assert_listed_binary({"dep/0.1": ("326c500588d969f55133fdda29506ef61ef03eee",
                                         "Download (default)")})
@@ -160,8 +164,8 @@ def test_cppstd_server():
     c.run(f"install consumer {base_settings} -s compiler.cppstd=17")
     assert "dep/0.1: Checking 3 compatible configurations" in c.out
     assert "dep/0.1: Compatible configurations not found in cache, checking servers" not in c.out
-    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing. " \
-           "Using compatible package '326c500588d969f55133fdda29506ef61ef03eee': " \
+    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing" in c.out
+    assert "Found compatible package '326c500588d969f55133fdda29506ef61ef03eee': " \
            "compiler.cppstd=20" in c.out
     c.assert_listed_binary({"dep/0.1": ("326c500588d969f55133fdda29506ef61ef03eee", "Cache")})
 
@@ -174,8 +178,8 @@ def test_cppstd_server():
     c.run(f"install consumer {base_settings} -s compiler.cppstd=17 --update")
     assert "dep/0.1: Checking 3 compatible configurations" in c.out
     assert "dep/0.1: Compatible configurations not found in cache, checking servers" not in c.out
-    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing. " \
-           "Using compatible package 'ce92fac7c26ace631e30875ddbb3a58a190eb601': " \
+    assert "dep/0.1: Main binary package '6179018ccb6b15e6443829bf3640e25f2718b931' missing" in c.out
+    assert "Found compatible package 'ce92fac7c26ace631e30875ddbb3a58a190eb601': " \
            "compiler.cppstd=14" in c.out
     c.assert_listed_binary({"dep/0.1": ("ce92fac7c26ace631e30875ddbb3a58a190eb601",
                                         "Download (default)")})
@@ -247,7 +251,7 @@ class TestDefaultCompat:
 
         c.run("create . --build=missing -s compiler.cppstd=17")
         assert "mylib/1.0: Main binary package" in c.out
-        assert " missing. Using compatible package" in c.out
+        assert "Found compatible package" in c.out
         assert "Possible options are ['shared', 'header_only']" not in c.out
         assert "mylib/1.0: WARN: WITH_FMT_ALIAS=False" in c.out
 
diff --git a/test/integration/package_id/test_validate.py b/test/integration/package_id/test_validate.py
index 8cdb2cf87..b603e216c 100644
--- a/test/integration/package_id/test_validate.py
+++ b/test/integration/package_id/test_validate.py
@@ -139,22 +139,19 @@ class TestValidate(unittest.TestCase):
         self.assertIn("Windows not supported", client.out)
 
         client.run("install --requires=pkg/0.1@ -s os=Windows")
-        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' "
-                      f"missing. Using compatible package '{package_id}'",
-                      client.out)
+        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' missing", client.out)
+        self.assertIn(f"Found compatible package '{package_id}'", client.out)
         client.assert_listed_binary({"pkg/0.1": (package_id, "Cache")})
 
         # --build=missing means "use existing binary if possible", and compatibles are valid binaries
         client.run("install --requires=pkg/0.1@ -s os=Windows --build=missing")
-        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' "
-                      f"missing. Using compatible package '{package_id}'",
-                      client.out)
+        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' missing", client.out)
+        self.assertIn(f"Found compatible package '{package_id}'", client.out)
         client.assert_listed_binary({"pkg/0.1": (package_id, "Cache")})
 
         client.run("graph info --requires=pkg/0.1@ -s os=Windows")
-        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' "
-                      f"missing. Using compatible package '{package_id}'",
-                      client.out)
+        self.assertIn(f"pkg/0.1: Main binary package '{missing_id}' missing", client.out)
+        self.assertIn(f"Found compatible package '{package_id}'", client.out)
         self.assertIn(f"package_id: {package_id}", client.out)
 
         client.run("graph info --requires=pkg/0.1@ -s os=Windows --build=pkg*")
@@ -743,7 +740,7 @@ class TestCompatibleSettingsTarget(unittest.TestCase):
 
         client.save({"conanfile.py": app_conanfile})
         client.run("create . --name=app --version=0.1 -s os=Linux -s:h arch=armv7")
-        assert f"Using compatible package '{package_id}'" in client.out
+        assert f"Found compatible package '{package_id}'" in client.out
 
     def test_settings_target_in_compatibility_in_global_compatibility_py(self):
         client = TestClient()
@@ -787,7 +784,7 @@ class TestCompatibleSettingsTarget(unittest.TestCase):
 
         client.save({"conanfile.py": app_conanfile})
         client.run("create . --name=app --version=0.1 -s os=Linux -s:h arch=armv7")
-        assert f"Using compatible package '{package_id}'" in client.out
+        assert f"Found compatible package '{package_id}'" in client.out
 
     def test_no_settings_target_in_recipe_but_in_compatibility_method(self):
         client = TestClient()
@@ -922,7 +919,7 @@ class TestCompatibleSettingsTarget(unittest.TestCase):
 
         client.save({"conanfile.py": app_conanfile})
         client.run("create . --name=app --version=0.1 -s os=Linux -s:h arch=armv7 -s:b arch=x86_64")
-        assert f"Using compatible package '{package_id_tool_a}'" in client.out
+        assert f"Found compatible package '{package_id_tool_a}'" in client.out
         assert package_id_tool_b in client.out
 
     def test_settings_target_in_compatibility_method_within_recipe_package_info(self):
@@ -949,4 +946,4 @@ class TestCompatibleSettingsTarget(unittest.TestCase):
 
         client.run("install --tool-requires=tool/0.1 -s os=Linux -s:b arch=armv7")
         # This used to crash, not anymore
-        assert f"Using compatible package '{package_id}'" in client.out
+        assert f"Found compatible package '{package_id}'" in client.out