Repository: conan-io/conan

Issue:
Resolved dependencies are not the same if a package needs to be built
Hello,

Using conan 1.7.0 (changelog don't show any modification about this problem for 1.7.3) on ubuntu 18.04.

I've encountered the following use case ([conan_dep.tar.gz](https://github.com/conan-io/conan/files/2370339/conan_dep.tar.gz)):

PackageC is independent and a shared lib
PackageB is a static lib and requires PackageC as public
PackageA is a dynamic, requires PackageB as private and PackageC as public

UseOfA requires PackageA as public

I would expect that in the buildinfo constructed by conan when I make a conan install of UseOfA that build info of PackageA and PackageC are available.

**When I install UseOfA:**
If all my packages are in my local repository info about PackageC are missing.
If the PackageA is missing from my repository and the recipe is on the server, installing with build missing provides all info I need.
[other test] If the PackageA is missing from my repository and the binary is on the server, info about PackageC are missing.

**To test it:**
You can untar the archive and run test.sh, it will create two build folder: build_problem and build_ok and then you can compare conanbuildinfo.txt the check the differences.

Regards,

- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.




Discussion:
I was able to reproduce it, indeed a bug in the model of private dependencies. Seems complex to fix but we are on it.

It doesn't seem a regression, up to my knowledge, but a long standing bug in privates (which are not very recommended, unless strictly necesssary, so they are not used that much, that could be the reason the bug was there for some time). I'd like to ask about the urgency, if it is not that critical, we could fix it in conan 1.8, otherwise, please tell and we will check what can be done. Thanks for reporting it!

Resolving patch:
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -3,7 +3,8 @@
 from conans.model.conan_file import get_env_context_manager
 from conans.model.requires import Requirements
 from conans.model.ref import ConanFileReference
-from conans.errors import ConanException, conanfile_exception_formatter, ConanExceptionInUserConanfileMethod
+from conans.errors import (ConanException, conanfile_exception_formatter,
+                           ConanExceptionInUserConanfileMethod)
 from conans.client.output import ScopedOutput
 from conans.util.log import logger
 from conans.client.graph.graph import DepsGraph, Node, RECIPE_WORKSPACE
@@ -33,7 +34,8 @@ def load_graph(self, conanfile, check_updates, update, remote_name, processed_pr
         t1 = time.time()
         loop_ancestors = []
         self._load_deps(root_node, Requirements(), dep_graph, public_deps, None, None,
-                        loop_ancestors, aliased, check_updates, update, remote_name, processed_profile)
+                        loop_ancestors, aliased, check_updates, update, remote_name,
+                        processed_profile)
         logger.debug("Deps-builder: Time to load deps %s" % (time.time() - t1))
         t1 = time.time()
         dep_graph.compute_package_ids()
@@ -91,13 +93,13 @@ def _load_deps(self, node, down_reqs, dep_graph, public_deps, down_ref, down_opt
             new_loop_ancestors.append(require.conan_reference)
             previous = public_deps.get(name)
             if require.private or not previous:  # new node, must be added and expanded
-                if require.private:  # Make sure the subgraph is truly private
-                    public_deps = {}
                 new_node = self._create_new_node(node, dep_graph, require, public_deps, name,
                                                  aliased, check_updates, update, remote_name,
                                                  processed_profile)
                 # RECURSION!
-                self._load_deps(new_node, new_reqs, dep_graph, public_deps, node.conan_ref,
+                # Make sure the subgraph is truly private
+                new_public_deps = {} if require.private else public_deps
+                self._load_deps(new_node, new_reqs, dep_graph, new_public_deps, node.conan_ref,
                                 new_options, new_loop_ancestors, aliased, check_updates, update,
                                 remote_name, processed_profile)
             else:  # a public node already exist with this name
@@ -186,7 +188,8 @@ def _config_node(self, node, down_reqs, down_ref, down_options):
                         conanfile.requirements()
 
                 new_options = conanfile.options.deps_package_values
-                new_down_reqs = conanfile.requires.update(down_reqs, self._output, conanref, down_ref)
+                new_down_reqs = conanfile.requires.update(down_reqs, self._output, conanref,
+                                                          down_ref)
         except ConanExceptionInUserConanfileMethod:
             raise
         except ConanException as e:
@@ -211,8 +214,8 @@ def _create_new_node(self, current_node, dep_graph, requirement, public_deps, na
                                                 check_updates, update, remote_name, self._recorder)
             except ConanException as e:
                 base_ref = str(current_node.conan_ref or "PROJECT")
-                self._output.error("Failed requirement '%s' from '%s'" % (requirement.conan_reference,
-                                                                          base_ref))
+                self._output.error("Failed requirement '%s' from '%s'"
+                                   % (requirement.conan_reference, base_ref))
                 raise e
             conanfile_path, recipe_status, remote, _ = result
 


Test patch:
diff --git a/conans/test/integration/private_deps_test.py b/conans/test/integration/private_deps_test.py
--- a/conans/test/integration/private_deps_test.py
+++ b/conans/test/integration/private_deps_test.py
@@ -11,6 +11,36 @@
 
 
 class PrivateBinariesTest(unittest.TestCase):
+    def test_transitive_private(self):
+        # https://github.com/conan-io/conan/issues/3523
+        client = TestClient()
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    pass
+    def package_info(self):
+        self.cpp_info.libs = [self.name]
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("create . PkgC/0.1@user/channel")
+        client.save({"conanfile.py": conanfile.replace("pass", "requires='PkgC/0.1@user/channel'")})
+        client.run("create . PkgB/0.1@user/channel")
+        client.save({"conanfile.py":
+                     conanfile.replace("pass", "requires=('PkgB/0.1@user/channel', 'private'), "
+                                               "'PkgC/0.1@user/channel'")})
+        client.run("create . PkgA/0.1@user/channel")
+        client.save({"conanfile.py": conanfile.replace("pass", "requires='PkgA/0.1@user/channel'")})
+        client.run("install . -g=cmake")
+        self.assertIn("PkgC/0.1@user/channel:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache",
+                      client.out)
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.txt"))
+        self.assertIn("[libs];PkgA;PkgC", ";".join(conanbuildinfo.splitlines()))
+        self.assertIn("PkgC/0.1/user/channel/package", conanbuildinfo)
+        self.assertIn("[includedirs_PkgC]", conanbuildinfo)
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
+        self.assertIn("set(CONAN_LIBS PkgA PkgC ${CONAN_LIBS})", conanbuildinfo)
+        client.run("info . --graph=file.html")
+        html = load(os.path.join(client.current_folder, "file.html"))
+        self.assertEqual(2, html.count("label: 'PkgC/0.1', shape: 'box'"))
 
     def test_private_regression_skip(self):
         # https://github.com/conan-io/conan/issues/3166