Repository: conan-io/conan

Issue:
[question] How to make dependency of dependency unvisible
### What is your question?

I have a shared library A, which depend spdlog/1.13.0, and spdlog depend fmt/10.2.1. A make spdlog unvisible with `visisble=False`. Now another B depend A and fmt/8.1.1, which will cause conflict. 

> │ERROR: Version conflict: spdlog/1.13.0->fmt/10.2.1, B/None->fmt/8.1.1

How to totally hide dependencies like `self.build_requires("spdlog/1.13.0", force_host_context=True)` in conan 1.x. The library A is shared library and hide all denpendencies symbols, I am sure that this behavior will not cause any problems.

### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide


Discussion:
Hi @xyz1001 

Thanks for your question.

The first important thing to consider is that in most cases in C and C++ it is not possible to ignore the conflicts, because basically it is not possible to link together 2 different libraries for the same code. These conflicts are preventing later and more challenging problems at link time or even worse, at runtime.

Even in the cases where there is full hiding of a dependency, like a shared library linking a static library as a pure implementation detail, making sure it doesn't leak any API or ABI, it is still recommended to depend on the same version of the transitive dependency when there are diamond structures in the graph.

For the exceptional cases where we want to hide the transitive dependency, under the user responsibility, it would be the ``private=True`` argument in Conan 1.X, which is not recommended in most cases, it has its challenges, and using the new ``visible=False`` trait in Conan 2.0 (but making sure using ``self.requires(..., visible=False)``, not build-requires)
My shared library A has beem mark spdlog/1.13.0 as unvisible with `self.requires("spdlog/1.13.0", visible=False)`. However
 another library B which depend A and fmt/8.1.1 still has conflict.
Could you please provide something that we can reproduce? Providing a few very simple ``conanfile.py`` files, and instructions to reproduce really helps.

Please also report your Conan version, OS, etc.
Here is the demo repo: https://github.com/xyz1001/conan_hide_dependency
reproduce command:
`cd a && conan create .`
`cd .. && cd b && conan create .`

conan version: 2.0.17
OS: macOS 14.2.1
Thanks very much for the reproducible case, it really helped a lot! 🙂 

There was indeed an unfortunate subtle bug that happens when the transitive library behind ``visible=False`` is a header-only library that has transitive dependencies. 

I have submitted already a PR in https://github.com/conan-io/conan/pull/15564 that hopefully fix it for next release (2.1 most like, but we don't discard a 2.0.18 yet).



Patch:
diff --git a/conans/model/requires.py b/conans/model/requires.py
index 8d6dcdef654..e744217a405 100644
--- a/conans/model/requires.py
+++ b/conans/model/requires.py
@@ -316,11 +316,11 @@ def transform_downstream(self, pkg_type, require, dep_pkg_type):
         if self.transitive_libs is not None:
             downstream_require.transitive_libs = self.transitive_libs
 
+        if self.visible is False:
+            downstream_require.visible = False
+
         if pkg_type is not PackageType.HEADER:  # These rules are not valid for header-only
             # If non-default, then the consumer requires has priority
-            if self.visible is False:
-                downstream_require.visible = False
-
             if self.headers is False:
                 downstream_require.headers = False
 


Test patch:
diff --git a/conans/test/integration/graph/core/graph_manager_test.py b/conans/test/integration/graph/core/graph_manager_test.py
index d75cfcfa52c..c034da23132 100644
--- a/conans/test/integration/graph/core/graph_manager_test.py
+++ b/conans/test/integration/graph/core/graph_manager_test.py
@@ -1595,6 +1595,40 @@ def test_diamond_transitive_private(self):
         _check_transitive(libc, [(libb, True, True, False, False),
                                  (liba, True, True, False, False)])
 
+    def test_private_transitive_headers_no_conflict(self):
+        # https://github.com/conan-io/conan/issues/15559
+        # app -->liba/0.1 -(private)-> spdlog/0.1(header-only) -> fmt/0.1 (header-only)
+        #   \ --------------------------------------------------> fmt/0.2
+        self.recipe_conanfile("fmt/0.1",
+                              GenConanfile("fmt", "0.1").with_package_type("header-library"))
+        self.recipe_conanfile("fmt/0.2", GenConanfile("fmt", "0.2"))
+        self.recipe_conanfile("spdlog/0.1",
+                              GenConanfile("spdlog", "0.1").with_package_type("header-library")
+                                                           .with_requires("fmt/0.1"))
+        self.recipe_conanfile("liba/0.1",
+                              GenConanfile("liba", "0.2").with_requirement("spdlog/0.1",
+                                                                           visible=False))
+        consumer = self.consumer_conanfile(GenConanfile("app", "0.1")
+                                           .with_requires("liba/0.1", "fmt/0.2"))
+        deps_graph = self.build_consumer(consumer)
+
+        self.assertEqual(5, len(deps_graph.nodes))
+        app = deps_graph.root
+        liba = app.dependencies[0].dst
+        spdlog = liba.dependencies[0].dst
+        fmt01 = spdlog.dependencies[0].dst
+        fmt02 = app.dependencies[1].dst
+
+        self._check_node(app, "app/0.1", deps=[liba, fmt02])
+        self._check_node(liba, "liba/0.1#123", deps=[spdlog], dependents=[app])
+        self._check_node(spdlog, "spdlog/0.1#123", deps=[fmt01], dependents=[liba])
+        self._check_node(fmt01, "fmt/0.1#123", deps=[], dependents=[spdlog])
+        self._check_node(fmt02, "fmt/0.2#123", dependents=[app])
+
+        # node, headers, lib, build, run
+        _check_transitive(app, [(liba, True, True, False, False),
+                                (fmt02, True, True, False, False)])
+
 
 class TestDiamondMultiple(GraphManagerTest):