Repository: conan-io/conan

Issue:
GCC Link order 
Hi

Sorry about the weak problem description, but I have yet to find out what is really causing it. 

Tools: GCC, CMake, Linux (and MingW-64 on Windows), using conan BASIC and not TARGET.

When I moved from conan 1.3.3 to conan 1.4.2 (also tried with 1.4.0 and 1.4.3) my builds where unable to link. The ordering of the libraries to link has changed.

I have a project A that depends on B and C and where project B also depends on C. B and C are both static libraries where A is a shared library.

When linking A, I get an undefined reference from project B to project C. 

When looking at the generated link.txt file I can see that the order of the libraries in the file have changed from 1.3.3 to 1.4.x. In version 1.3.3 I had library C twice in the link.txt file, in version 1.4.x it is only listed once, and unfortunately not after library B.

I have tried to establish a small test project to see if I could recreate it in a way where you could see the problem, but I have not been able to so far.

Do you have any thoughts about changes in the link ordering determination that might help me locate my issue. And possibly provide you with a working example (or rather not working)

Regards
Lau



Discussion:
Hi @lbakman 

There was a small change in 1.4, that changes the order of libraries from alphabetical to the order they are declared in the recipe, which is the correct behavior (alphabetical makes no sense). But this only applies when they are in the same dependency level, i.e, if A => C, B, it will make A link with C, B in that order. But only if C, B do not depend on each other. If they depend on each other, then that order will be respected, as before. In your case the link order should keep being the same A, B, C.

You might try to change the order in which your dependencies are declared, too, just an idea, in theory it shouldn't affect.

You can also try to send what you have, privately to info@conan.io, and we could investigate in your project. But of course a minimal reproducible example would be much better.

Thanks for reporting this!
Hi @memsharded 

Thank you for your response. Changing the order of the dependencies in project A fixes the problem for me in this specific case. 

It would still be good to find out why the new ordering takes precedence over multiple dependencies, so I will try to create a minimal reproducible example today. Otherwise I will gather as much information as I can about the problem and send it privately. 

Regards

Lau
>It would still be good to find out why the new ordering takes precedence over multiple dependencies, so I will try to create a minimal reproducible example today. Otherwise I will gather as much information as I can about the problem and send it privately.

Yes, yes, please do so, it is important to check this out. Thanks for the collaboration!
Hi

Made some progress. I have created an example that works using conan 1.3.3 but does not work using conan 1.4.x. 

The undefined reference occurs for functions declared globally or in a namespace, not for classes. 

The example includes LibA, LibB and LibC. LibC has no dependencies, LibB depends on LibC and LibA depends on LibB and LibC. LibC declares and implements a function in the namespace LibC. LibB calls this function. When linking LibA (shared library and executable) the linker fails with an undefined reference to the function. If you change the order of the requirements in the conanfile.py for LibA, the problem is resolved. Notice that removing the "private" flag from the deps.libc reference also resolves the problem. This is also the case if using the def requirements(self): method. If private=True the build also fails.

Regards Lau

[gcc_link_order.zip](https://github.com/conan-io/conan/files/2080350/gcc_link_order.zip)

Hi @lbakman,

Thank you for the example.
I have just tested it in Windows with conan 1.3.0, 1.3.3 and 1.4.3 and it did not work for me. I got link errors in LibA.

Let's wait for @memsharded and see what he thinks
Hi @danimtb 

Right, sorry. Did not check with Visual Studio. I can see that my "testsuite" does not link properly to the dll. Will try to fix that. But my original problem was with the GCC compiler. Edit: Sorry, you did not say Visual Studio, that was my assumption.

I have used the following profile for windows (profiles/mingw64):
```
[settings]
os=Windows
arch=x86_64
compiler=gcc
compiler.version=7
compiler.libcxx=libstdc++11
compiler.threads=win32
compiler.exception=seh
build_type=Release
os_build=Windows
arch_build=x86_64

[build_requires]
mingw_installer/1.0@conan/stable
```

Also I perform the following steps:

```
cd LibC
conan export . lba/testing
cd ..
cd LibB
conan export . lba/testing
cd ..
cd LibA
conan create . lba/testing --profile=mingw64 --build=missing
```

Regards Lau
Hi

I have added the test project to github at the following URL: https://github.com/lbakman/conan_link_order

The example now builds using Visual Studio as well (on my machine I guess), but it does not fail as it does using GCC.

Regards Lau
thank you so much for the feedback @lbakman.

FInally I was able to reproduce your issue with the mingw profile you provided. Seems this in fack a link problem the way you are definin your requirements.

In LibA I see:
```
    requires = (
        ("deps.libc/1.0@lba/testing", "private"),
        "deps.libb/1.0@lba/testing",
    )
```

As linkage order matters, you would need to link LibA with LibB first and then LibC. Changing the requierments order to this should solve your issue:

```
    requires = (
        "deps.libb/1.0@lba/testing",
        ("deps.libc/1.0@lba/testing", "private"),
    )
```

We have realized though that there is room for improvement here and will look into it a little bit more for next releases.

Thanks a lot for the feedback and for help with this issue!! 😄 
Hi

Sure, and that is how I have fixed it for my "real" projects. It just took some time to figure out why it changed all of a sudden.

Also, why does it work if you keep the original ordering and remove the "private" flag? 

And finally (From https://docs.conan.io/en/latest/changelog.html): "Conan 1.4 shouldn’t break any existing 1.0 recipe, or command line invocation. If it does, please report in github." :wink:

Regards Lau


Resolving patch:
diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py
--- a/conans/client/graph/graph.py
+++ b/conans/client/graph/graph.py
@@ -141,6 +141,20 @@ def direct_requires(self):
         open_nodes = nodes_by_level[1]
         return open_nodes
 
+    def full_closure(self, node):
+        closure = OrderedDict()
+        current = node.neighbors()
+        while current:
+            new_current = []
+            for n in current:
+                closure[n] = n
+            for n in current:
+                for neigh in n.public_neighbors():
+                    if neigh not in new_current and neigh not in closure:
+                        new_current.append(neigh)
+            current = new_current
+        return closure
+
     def closure(self, node):
         closure = OrderedDict()
         current = node.neighbors()
diff --git a/conans/client/installer.py b/conans/client/installer.py
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -414,12 +414,13 @@ def _log_built_package(self, build_folder, package_ref, duration):
     @staticmethod
     def _propagate_info(node, levels, deps_graph):
         # Get deps_cpp_info from upstream nodes
-        closure = deps_graph.closure(node)
+        closure = deps_graph.full_closure(node)
         node_order = []
         for level in levels:
-            for n in closure.values():
+            for n in closure:
                 if n in level:
                     node_order.append(n)
+
         conan_file = node.conanfile
         for n in node_order:
             conan_file.deps_cpp_info.update(n.conanfile.cpp_info, n.conan_ref.name)


Test patch:
diff --git a/conans/test/integration/order_libs_test.py b/conans/test/integration/order_libs_test.py
--- a/conans/test/integration/order_libs_test.py
+++ b/conans/test/integration/order_libs_test.py
@@ -10,6 +10,62 @@ class OrderLibsTest(unittest.TestCase):
     def setUp(self):
         self.client = TestClient()
 
+    def private_order_test(self):
+        # https://github.com/conan-io/conan/issues/3006
+        client = TestClient()
+        conanfile = """from conans import ConanFile
+class LibBConan(ConanFile):
+    def package_info(self):
+        self.cpp_info.libs = ["LibC"]
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("create . LibC/0.1@user/channel")
+
+        conanfile = """from conans import ConanFile
+class LibCConan(ConanFile):
+    requires = "LibC/0.1@user/channel"
+    def package_info(self):
+        self.cpp_info.libs = ["LibB"]
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("create . LibB/0.1@user/channel")
+
+        conanfile = """from conans import ConanFile
+class LibCConan(ConanFile):
+    requires = ("LibB/0.1@user/channel", "private"), "LibC/0.1@user/channel"
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("install . -g cmake")
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
+        self.assertIn("set(CONAN_LIBS LibB LibC ${CONAN_LIBS})", conanbuildinfo)
+        # Change private
+        conanfile = """from conans import ConanFile
+class LibCConan(ConanFile):
+    requires = "LibB/0.1@user/channel", ("LibC/0.1@user/channel", "private")
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("install . -g cmake")
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
+        self.assertIn("set(CONAN_LIBS LibB LibC ${CONAN_LIBS})", conanbuildinfo)
+        # Change order
+        conanfile = """from conans import ConanFile
+class LibCConan(ConanFile):
+    requires = ("LibC/0.1@user/channel", "private"), "LibB/0.1@user/channel"
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("install . -g cmake")
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
+        self.assertIn("set(CONAN_LIBS LibB LibC ${CONAN_LIBS})", conanbuildinfo)
+        # Change order
+        conanfile = """from conans import ConanFile
+class LibCConan(ConanFile):
+    requires = "LibC/0.1@user/channel", ("LibB/0.1@user/channel", "private")
+"""
+        client.save({"conanfile.py": conanfile})
+        client.run("install . -g cmake")
+        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
+        self.assertIn("set(CONAN_LIBS LibB LibC ${CONAN_LIBS})", conanbuildinfo)
+
     def _export(self, name, deps=None, export=True):
         def _libs():
             if name == "LibPNG":