Repository: conan-io/conan
Pull request #17909 (2025-03-07 17:56:43)

fixing build-order --reduce for recipes
Changelog: Bugfix: Fix ``conan graph build-order --reduce --order-by=recipe`` that was not filtering all packages != "Build"
Docs: Omit



Patch:
diff --git a/conan/internal/default_settings.py b/conan/internal/default_settings.py
index e85390b09..3f19ec831 100644
--- a/conan/internal/default_settings.py
+++ b/conan/internal/default_settings.py
@@ -128,7 +128,8 @@ compiler:
     clang:
         version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
                   "5.0", "6.0", "7.0", "7.1",
-                  "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]
+                  "8", "9", "10", "11", "12", "13", "14", "15", "16", "17",
+                  "18", "19", "20"]
         libcxx: [null, libstdc++, libstdc++11, libc++, c++_shared, c++_static]
         cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23, 26, gnu26]
         runtime: [null, static, dynamic]
diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py
index d8d607778..d65ad659f 100644
--- a/conan/tools/cmake/cmakedeps/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps/cmakedeps.py
@@ -137,7 +137,7 @@ class CMakeDeps(object):
         Using this method you can overwrite the :ref:`property<CMakeDeps Properties>` values set by
         the Conan recipes from the consumer. This can be done for `cmake_file_name`, `cmake_target_name`,
         `cmake_find_mode`, `cmake_module_file_name`, `cmake_module_target_name`, `cmake_additional_variables_prefixes`,
-        `cmake_config_version_compat`, `system_package_version`, `cmake_set_interface_link_directories`,
+        `cmake_config_version_compat`, `system_package_version`,
         `cmake_build_modules`, `nosoname`, and `cmake_target_aliases`.
 
         :param dep: Name of the dependency to set the :ref:`property<CMakeDeps Properties>`. For
diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
index 747b2bf01..d31cb1dc1 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
@@ -29,6 +29,11 @@ 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, check_type=bool)
+        if auto_link:
+            out = self.cmakedeps._conanfile.output # noqa
+            out.warning("CMakeDeps: cmake_set_interface_link_directories is legacy, not necessary",
+                        warn_tag="deprecated")
+
         return {"pkg_name": self.pkg_name,
                 "root_target_name": self.root_target_name,
                 "config_suffix": self.config_suffix,
@@ -119,16 +124,6 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
             set_property(TARGET {{root_target_name}}
                          APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
                          $<$<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_var(pkg_name, 'LIB_DIRS', config_suffix) }}>)
-            {%- endif %}
-
-
         {%- else %}
 
         ########## COMPONENTS TARGET PROPERTIES {{ configuration }} ########################################
@@ -188,19 +183,12 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
                              $<$<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 }}>:{{ comp_var(pkg_name, comp_variable_name, 'INCLUDE_DIRS', config_suffix) }}>)
-                set_property(TARGET {{comp_target_name }} APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
+                set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_LINK_DIRECTORIES
                              $<$<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 }}>:{{ comp_var(pkg_name, comp_variable_name, 'COMPILE_DEFINITIONS', config_suffix) }}>)
                 set_property(TARGET {{ comp_target_name }} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
                              $<$<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 }}>:{{ comp_var(pkg_name, comp_variable_name, 'LIB_DIRS', config_suffix) }}>)
-
-                {%- endif %}
             {%endfor %}
 
 
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index 9af4ea614..e85df76fd 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -20,6 +20,13 @@ class TargetConfigurationTemplate2:
         self._require = require
 
     def content(self):
+        auto_link = self._cmakedeps.get_property("cmake_set_interface_link_directories",
+                                                 self._conanfile, check_type=bool)
+        if auto_link:
+            out = self._cmakedeps._conanfile.output  # noqa
+            out.warning("CMakeConfigDeps: cmake_set_interface_link_directories deprecated and "
+                        "invalid. The package 'package_info()' must correctly define the (CPS) "
+                        "information", warn_tag="deprecated")
         t = Template(self._template, trim_blocks=True, lstrip_blocks=True,
                      undefined=jinja2.StrictUndefined)
         return t.render(self._context)
diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py
index c071b0d3b..4a0fdaf9e 100644
--- a/conans/client/graph/install_graph.py
+++ b/conans/client/graph/install_graph.py
@@ -127,6 +127,15 @@ class _InstallRecipeReference:
                 return True
         return False
 
+    def reduce(self):
+        result = _InstallRecipeReference()
+        result.ref = self.ref
+        result._node = self._node
+        result.depends = self.depends
+        result.packages = {pid: pkg for pid, pkg in self.packages.items()
+                           if pkg.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD)}
+        return result
+
     @property
     def node(self):
         return self._node
@@ -236,6 +245,9 @@ class _InstallConfiguration:
     def need_build(self):
         return self.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD)
 
+    def reduce(self):
+        return self
+
     @property
     def pref(self):
         return PkgReference(self.ref, self.package_id, self.prev)
@@ -436,7 +448,7 @@ class InstallGraph:
         result = {}
         for k, node in self._nodes.items():
             if node.need_build:
-                result[k] = node
+                result[k] = node.reduce()
             else:  # Eliminate this element from the graph
                 dependencies = node.depends
                 # Find all consumers


Test patch:
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index ac0606062..14ed165c6 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -480,101 +480,6 @@ def test_cmake_toolchain_definitions_complex_strings():
     assert 'answer_debug=21' in client.out
 
 
-class TestAutoLinkPragma:
-    # TODO: This is a CMakeDeps test, not a CMakeToolchain test, move it to the right place
-
-    test_cf = textwrap.dedent("""
-        import os
-
-        from conan import ConanFile
-        from conan.tools.cmake import CMake, cmake_layout, CMakeDeps
-        from conan.tools.build import cross_building
-
-
-        class HelloTestConan(ConanFile):
-            settings = "os", "compiler", "build_type", "arch"
-            generators = "CMakeToolchain", "VirtualBuildEnv", "VirtualRunEnv"
-            apply_env = False
-            test_type = "explicit"
-
-            def generate(self):
-                deps = CMakeDeps(self)
-                deps.generate()
-
-            def requirements(self):
-                self.requires(self.tested_reference_str)
-
-            def build(self):
-                cmake = CMake(self)
-                cmake.configure()
-                cmake.build()
-
-            def layout(self):
-                cmake_layout(self)
-
-            def test(self):
-                if not cross_building(self):
-                    cmd = os.path.join(self.cpp.build.bindirs[0], "example")
-                    self.run(cmd, env="conanrun")
-        """)
-
-    @pytest.mark.skipif(platform.system() != "Windows", reason="Requires Visual Studio")
-    @pytest.mark.tool("cmake")
-    def test_autolink_pragma_components(self):
-        """https://github.com/conan-io/conan/issues/10837
-
-        NOTE: At the moment the property cmake_set_interface_link_directories is only read at the
-        global cppinfo, not in the components"""
-
-        client = TestClient()
-        client.run("new cmake_lib -d name=hello -d version=1.0")
-        cf = client.load("conanfile.py")
-        cf = cf.replace('self.cpp_info.libs = ["hello"]', """
-            self.cpp_info.components['my_component'].includedirs.append('include')
-            self.cpp_info.components['my_component'].libdirs.append('lib')
-            self.cpp_info.components['my_component'].libs = []
-            self.cpp_info.set_property("cmake_set_interface_link_directories", True)
-        """)
-        hello_h = client.load("include/hello.h")
-        hello_h = hello_h.replace("#define HELLO_EXPORT __declspec(dllexport)",
-                                  '#define HELLO_EXPORT __declspec(dllexport)\n'
-                                  '#pragma comment(lib, "hello")')
-
-        test_cmakelist = client.load("test_package/CMakeLists.txt")
-        test_cmakelist = test_cmakelist.replace("target_link_libraries(example hello::hello)",
-                                                "target_link_libraries(example hello::my_component)")
-        client.save({"conanfile.py": cf,
-                     "include/hello.h": hello_h,
-                     "test_package/CMakeLists.txt": test_cmakelist,
-                     "test_package/conanfile.py": self.test_cf})
-
-        client.run("create .")
-
-    @pytest.mark.skipif(platform.system() != "Windows", reason="Requires Visual Studio")
-    @pytest.mark.tool("cmake")
-    def test_autolink_pragma_without_components(self):
-        """https://github.com/conan-io/conan/issues/10837"""
-        client = TestClient()
-        client.run("new cmake_lib -d name=hello -d version=1.0")
-        cf = client.load("conanfile.py")
-        cf = cf.replace('self.cpp_info.libs = ["hello"]', """
-            self.cpp_info.includedirs.append('include')
-            self.cpp_info.libdirs.append('lib')
-            self.cpp_info.libs = []
-            self.cpp_info.set_property("cmake_set_interface_link_directories", True)
-        """)
-        hello_h = client.load("include/hello.h")
-        hello_h = hello_h.replace("#define HELLO_EXPORT __declspec(dllexport)",
-                                  '#define HELLO_EXPORT __declspec(dllexport)\n'
-                                  '#pragma comment(lib, "hello")')
-
-        client.save({"conanfile.py": cf,
-                     "include/hello.h": hello_h,
-                     "test_package/conanfile.py": self.test_cf})
-
-        client.run("create .")
-
-
 @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
 def test_cmake_toolchain_runtime_types():
     # everything works with the default cmake_minimum_required version 3.15 in the template
diff --git a/test/integration/command/info/test_info_build_order.py b/test/integration/command/info/test_info_build_order.py
index 45e8e37bc..a9605cea5 100644
--- a/test/integration/command/info/test_info_build_order.py
+++ b/test/integration/command/info/test_info_build_order.py
@@ -766,6 +766,22 @@ class TestBuildOrderReduce:
         assert "dep/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Invalid configuration" in tc.out
         assert "IndexError: list index out of range" not in tc.out
 
+    def test_reduce_should_remove_recipe(self):
+        tc = TestClient()
+        tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_settings("os")})
+        tc.run("export dep")
+        tc.run("create dep -s os=Windows")
+        tc.run("graph build-order -s os=Windows --build=missing --order=recipe --requires=dep/1.0 "
+               "--format=json", redirect_stdout="windows.json")
+        tc.run("graph build-order -s os=Linux --build=missing --order=recipe --requires=dep/1.0 "
+               "--format=json", redirect_stdout="linux.json")
+        tc.run("graph build-order-merge --file=windows.json --file=linux.json --reduce "
+               "--format=json")
+        order = json.loads(tc.stdout)
+        assert order["order"][0][0]["ref"] == "dep/1.0#1674c18bb63f0c9778d2811c21f581a0"
+        assert len(order["order"][0][0]["packages"][0]) == 1
+        assert order["order"][0][0]["packages"][0][0]["binary"] == "Build"
+
 
 def test_multi_configuration_profile_args():
     c = TestClient()
@@ -863,4 +879,3 @@ def test_build_order_build_context_compatible():
                                 "bar/1.0": ["5e4ffcc1ff33697a4ee96f66f0d2228ec458f25c", "Build"]})
         c.assert_listed_binary({"foo/1.0": ["4e2ae338231ae18d0d43b9e119404d2b2c416758", "Build"]},
                                build=True)
-
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
index 76f0aee4b..e54bb2a61 100644
--- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -178,3 +178,23 @@ def test_system_wrappers():
     cmake = c.load("lib-Targets-release.cmake")
     assert "add_library(lib::lib INTERFACE IMPORTED)" in cmake
     assert "target_link_libraries(lib::lib INTERFACE my_system_cool_lib)" in cmake
+
+
+def test_autolink_pragma():
+    """https://github.com/conan-io/conan/issues/10837"""
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            def package_info(self):
+                self.cpp_info.set_property("cmake_set_interface_link_directories", True)
+        """)
+    c.save({"conanfile.py": conanfile,
+            "test_package/conanfile.py": GenConanfile().with_test("pass")
+                                                       .with_settings("build_type")
+                                                       .with_generator("CMakeDeps")})
+    c.run("create . --name=pkg --version=0.1")
+    assert "CMakeDeps: cmake_set_interface_link_directories is legacy, not necessary" in c.out
+    c.run("create . --name=pkg --version=0.1 -c tools.cmake.cmakedeps:new=will_break_next")
+    assert "CMakeConfigDeps: cmake_set_interface_link_directories deprecated and invalid. " \
+           "The package 'package_info()' must correctly define the (CPS) information" in c.out