Repository: conan-io/conan
Pull request: add build_requires to conandeps.props file in MSBuildDeps
Changelog: Fix: Include ``build_requires`` in the global ``conandeps.props`` file generated by MSBuildDeps.
Changelog: Fix: Change `MSBuildDeps` file ``conan_deps.props`` to ``conandeps.props`` to avoid collision with a package named "deps".
Docs: Omit

Close https://github.com/conan-io/conan/issues/8170

Problem statement:
[bug] MSBuildDeps generator: build_requires not added to conan_deps.props
Build requirements are not added to the generated `conan_foo.props` file.

### Environment Details (include every applicable attribute)
  * Operating System+version:
  * Compiler+version:
  * Conan version: 1.32.0
  * Python version:

### Steps to reproduce (Include if Applicable)

1. add a library package (e.g. `foo`) to `build_requires`
2. execute `conan install` using the `MSBuildDeps`/`msbuild` generator

- `conan_foo.props` is generated
- generated `conan_deps.props` does not contain an import of `conan_foo.props`

Background:
We have a test helper library as a dependency that is only used in the test. Previously we added it as a private requirement, which showed up in the `conan_foo.props`. From our current understanding of Conan, it however is better written as a build requirements.

Hints from issue discussion:
Hi @lieser 

This could be a bug, but please take into account that the new ``MSBuildDeps`` generator is an experimental one and might change its behavior, and one of the things that might change is the management of ``build_requires``. As documented, explained in the trainings, etc: 

> build_requires are designed for packaging tools, utilities that only run at build-time, but are not part of the final binary code. Anything that is linked into consumer packages like all type of libraries (header only, static, shared) most likely are not build_requires but regular requires. The only exception would be testing libraries and frameworks, as long as the tests are not included in the final package.

So unless you are using it for things like ``cmake`` tool or the ``gtest`` testing library, I would suggest reconsidering your approach. The way that you can see how this could change is using the new ``profile:build`` and the build-context. When using it, the build-requires do NOT longer propagate things like ``includedirs``, ``libdirs``, ``libs``, etc. because those should be regular ``requires`` instead.

We will try to have a look and maybe fix it for next 1.33, but please keep an eye how it evolves regarding ``build_requires``. Thanks!
We are aware that `MSBuildDeps` is experimental, but thanks for reminding us.
Note that this is not an urgent/important bugfix for us (easy workaround available by simply adding another import).

I described our usage of `build_requires` more closely in #8176. Would be nice if you could take a look at it, as it currently still causes problems, and would be nice to know if we are maybe just misusing it.
Hi @lieser I am proposing to add it in #8186

Commit message: using deps_cpp_info.direct_deps instead of conanfile attrs

Patch to review:
diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py
index 2853bd973c8..cea59cf308b 100644
--- a/conan/tools/microsoft/msbuilddeps.py
+++ b/conan/tools/microsoft/msbuilddeps.py
@@ -217,11 +217,12 @@ def _content(self):
         if not self._conanfile.settings.get_safe("build_type"):
             raise ConanException("The 'msbuild' generator requires a 'build_type' setting value")
         result = {}
-        general_name = "conan_deps.props"
+        general_name = "conandeps.props"
         conf_name = self._config_filename()
         condition = self._condition()
-        public_deps = self._conanfile.requires.keys()
-        result[general_name] = self._deps_props(general_name, public_deps)
+        # Include all direct build_requires for both build & host context. This might change
+        direct_deps = self._conanfile.deps_cpp_info.direct_deps
+        result[general_name] = self._deps_props(general_name, direct_deps)
         for dep_name, cpp_info in self._conanfile.deps_cpp_info.dependencies:
             # One file per configuration, with just the variables
             vars_props_name = "conan_%s%s.props" % (dep_name, conf_name)
diff --git a/conans/client/installer.py b/conans/client/installer.py
index 300894a0c0d..9f662e4bfa3 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -588,6 +588,7 @@ def _propagate_info(self, node, using_build_profile):
                     env_info.PATH.extend(dep_cpp_info.bin_paths)
                     conan_file.deps_env_info.update(env_info, n.ref.name)
 
+        conan_file.deps_cpp_info.direct_deps = [n.name for n in node.neighbors()]
         # Update the info but filtering the package values that not apply to the subtree
         # of this current node and its dependencies.
         subtree_libnames = [node.ref.name for node in node_order]
diff --git a/conans/test/functional/generators/msbuild_test.py b/conans/test/functional/generators/msbuild_test.py
index ac03ea27b60..0408c26ba1e 100644
--- a/conans/test/functional/generators/msbuild_test.py
+++ b/conans/test/functional/generators/msbuild_test.py
@@ -573,10 +573,10 @@ def build(self):
         myproject_cpp = gen_function_cpp(name="main", msg="MyProject")
         files = {"MyProject.sln": sln_file,
                  "MyProject/MyProject.vcxproj": myproject_vcxproj.replace("conan_Hello3.props",
-                                                                          "conan_deps.props"),
+                                                                          "conandeps.props"),
                  "MyProject/MyProject.cpp": myproject_cpp,
                  "MyApp/MyApp.vcxproj": myapp_vcxproj.replace("conan_Hello1.props",
-                                                              "conan_deps.props"),
+                                                              "conandeps.props"),
                  "MyApp/MyApp.cpp": myapp_cpp,
                  "conanfile.py": conanfile}
 
@@ -585,3 +585,27 @@ def build(self):
         self.assertIn("'msbuild' has been deprecated and moved.", client.out)
         client.run("build .")
         self.assertNotIn("warning MSB4011", client.out)
+
+    def test_install_build_requires(self):
+        # https://github.com/conan-io/conan/issues/8170
+        client = TestClient()
+        client.save({"conanfile.py": GenConanfile()})
+        client.run("create . tool/1.0@")
+
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile, load
+            class HelloConan(ConanFile):
+                settings = "os", "build_type", "compiler", "arch"
+                build_requires = "tool/1.0"
+                generators = "MSBuildDeps"
+                def build(self):
+                    deps = load("conandeps.props")
+                    assert "conan_tool.props" in deps
+                    self.output.info("Conan_tools.props in deps")
+            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("install .")
+        deps = client.load("conandeps.props")
+        self.assertIn("conan_tool.props", deps)
+        client.run("create . pkg/0.1@")
+        self.assertIn("Conan_tools.props in deps", client.out)


Code review comments:
diff_hunk: @@ -217,11 +217,12 @@ def _content(self):
         if not self._conanfile.settings.get_safe("build_type"):
             raise ConanException("The 'msbuild' generator requires a 'build_type' setting value")
         result = {}
-        general_name = "conan_deps.props"
+        general_name = "conandeps.props"
         conf_name = self._config_filename()
         condition = self._condition()
-        public_deps = self._conanfile.requires.keys()
-        result[general_name] = self._deps_props(general_name, public_deps)
+        # Include all direct build_requires for both build & host context. This might change
path: conan/tools/microsoft/msbuilddeps.py
text: @user1:
```suggestion
        # Include all direct build_requires for host context
```



@author:
Done, and fixed also, filtering CONTEXT_HOST in the code, not only the comment