conan-io / conan

Conan - The open-source C and C++ package manager
https://conan.io
MIT License
8.14k stars 970 forks source link

[feature] Build fails with CMake privately linking to a library with transitive dependencies #7192

Open rddesmond opened 4 years ago

rddesmond commented 4 years ago

This falls somewhere between a bug, a feature request, and a comment for the various issues about RPATH handling. It's also troublesome in CMake alone, where their #19556 and #19707 are similar (though involving shared and static libraries, respectively, not imported ones).

When using the cmake generator and linking an application that privately consumes a library in its build path, the library's transitive dependencies cannot be found at link time (but it's okay at runtime). The minimal chain (where app and C are in the same project, and B and A are independent conan projects with empty RPATHs) is:

app  --(any link)-->  libraryC  --(PRIVATE link)-->  libraryB  --(any link)-->  libraryA

In the build tree, the app should be able to link and run. In the install tree, libraryB and libraryA needs to be in a known location, but that's a different story.

Environment Details (include every applicable attribute)

Steps to reproduce

private-transitive-issue.tar.gz contains a small example. Untar and run ./test.sh.

This conan creates proj_A (libprojA), proj_B (libprojB), and proj_C (libprojC and app), where each calls a function in the one before it (app:main -> libprojC:hello_C -> libprojB:hello_B -> libprojA:hello_A). It will fail during the linking of the app. All libraries are privately linked from CMake's perspective.

[100%] Linking CXX executable bin/app
/usr/bin/ld: warning: libproj_A.so, needed by //home/rdesmond/.conan/data/proj_B/0.1/test/test/package/45b5368b05e5bb05d84f1dd43b93bf7b9dd52802/lib/libproj_B.so, not found (try using -rpath or -rpath-link)
//home/rdesmond/.conan/data/proj_B/0.1/test/test/package/45b5368b05e5bb05d84f1dd43b93bf7b9dd52802/lib/libproj_B.so: undefined reference to `hello_A()'
collect2: error: ld returned 1 exit status

If you build it with workaround 2 below, it will run in the build tree, because the RPATH of libprojC will include the paths to libprojA and libprojB.

Workarounds

  1. Link libprojC to libprojB publicly (switch the commented line in "C/src/CMakelists.txt" from lines 9 to 8).
  2. Instruct gcc's linker to ignore the unresolved symbols with -Wl,--unresolved-symbols=ignore-in-shared-libs (uncomment "C/src/CMakelists.txt" line 13)
rddesmond commented 4 years ago

I think the above captures everything that of value that conan outputs, but for completeness: consolelog.txt. Of course, I'm happy to run and gather information from any experiments you think are useful.

And thank you for continuing to make conan awesome!

jgsogo commented 4 years ago

Hi!

Conan, as a package manager, by default removes rpaths from the generated libraries (+info). From the point of view of a package manager that distributes already compiled artifacts, rpath are not going to work as binaries generated in one machine won't be in the same path when used in a different machine.

In your case, you are building proj_C locally, and rpaths might help you to find packages that are in your cache because you are linking to them when building that project.

The way to go would be to import those shared libraries when running you application or, much better, to use the virtualrunenv generator:

$ conan install proj_C/0.1@test/test -g virtualrunenv
$ source activate_run.sh 
$ app
$ source deactivate_run.sh  

This generator will populate the environment with the required variables (PATH, DYLD_LIBRARY_PATH,...) to find the shared libraries that are needed in runtime.


Regarding the build process of proj_C, I can't see the same error you are reporting, it works (both in the cache with conan create and locally using plain CMake). I'm running Mac, but it should be more or less the same, the information about proj_B is added to the CMake target and it should be propagated to the consumers.

./test.sh

proj_C/0.1@test/test: Calling build()

----Running------
> cd '/Users/jgsogo/.conan/data/proj_C/0.1/test/test/build/d9a252b88eb0520168e02076d2a46030a497d625' && cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_OSX_ARCHITECTURES="x86_64" -DCMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk" -DCONAN_IN_LOCAL_CACHE="ON" -DCONAN_COMPILER="apple-clang" -DCONAN_COMPILER_VERSION="11.0" -DCONAN_CXX_FLAGS="-m64" -DCONAN_SHARED_LINKER_FLAGS="-m64" -DCONAN_C_FLAGS="-m64" -DCONAN_LIBCXX="libc++" -DBUILD_SHARED_LIBS="ON" -DCMAKE_INSTALL_PREFIX="/Users/jgsogo/.conan/data/proj_C/0.1/test/test/package/d9a252b88eb0520168e02076d2a46030a497d625" -DCMAKE_INSTALL_BINDIR="bin" -DCMAKE_INSTALL_SBINDIR="bin" -DCMAKE_INSTALL_LIBEXECDIR="bin" -DCMAKE_INSTALL_LIBDIR="lib" -DCMAKE_INSTALL_INCLUDEDIR="include" -DCMAKE_INSTALL_OLDINCLUDEDIR="include" -DCMAKE_INSTALL_DATAROOTDIR="share" -DCMAKE_MODULE_PATH="/Users/jgsogo/.conan/data/proj_C/0.1/test/test/build/d9a252b88eb0520168e02076d2a46030a497d625" -DCMAKE_EXPORT_NO_PACKAGE_REGISTRY="ON" -DCONAN_EXPORTED="1" -Wno-dev '/Users/jgsogo/.conan/data/proj_C/0.1/test/test/build/d9a252b88eb0520168e02076d2a46030a497d625/src'
-----------------
-- The CXX compiler identification is AppleClang 11.0.3.11030032
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: called by CMake conan helper
-- Conan: called inside local cache
-- Conan: Adjusting output directories
-- Conan: Using cmake targets configuration
-- Library proj_B found /Users/jgsogo/.conan/data/proj_B/0.1/test/test/package/b156c2519ed8956fef9acdac5e396e6bf4c72901/lib/libproj_B.dylib
-- Library proj_A found /Users/jgsogo/.conan/data/proj_A/0.1/test/test/package/e992dca89c56300b2901b18c23dbae33f62b3a9e/lib/libproj_A.dylib
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Conan: C++ stdlib: libc++
-- Configuring done
-- Generating done
CMake Warning:
  Manually-specified variables were not used by the project:

    CMAKE_EXPORT_NO_PACKAGE_REGISTRY
    CMAKE_INSTALL_BINDIR
    CMAKE_INSTALL_DATAROOTDIR
    CMAKE_INSTALL_INCLUDEDIR
    CMAKE_INSTALL_LIBDIR
    CMAKE_INSTALL_LIBEXECDIR
    CMAKE_INSTALL_OLDINCLUDEDIR
    CMAKE_INSTALL_SBINDIR

-- Build files have been written to: /Users/jgsogo/.conan/data/proj_C/0.1/test/test/build/d9a252b88eb0520168e02076d2a46030a497d625

----Running------
> cmake --build '/Users/jgsogo/.conan/data/proj_C/0.1/test/test/build/d9a252b88eb0520168e02076d2a46030a497d625' '--' '-j16'
-----------------
Scanning dependencies of target proj_C
[ 25%] Building CXX object CMakeFiles/proj_C.dir/hello_C.cpp.o
[ 50%] Linking CXX shared library lib/libproj_C.dylib
[ 50%] Built target proj_C
Scanning dependencies of target app
[ 75%] Building CXX object CMakeFiles/app.dir/app.cpp.o
[100%] Linking CXX executable bin/app
[100%] Built target app
proj_C/0.1@test/test: Package 'd9a252b88eb0520168e02076d2a46030a497d625' built
rddesmond commented 4 years ago

Good morning! Thank you for looking at this.

This is definitely an issue regarding building (and neither running in the install tree, where rpaths are allowable, nor the running after install, where we bundle things together).

I just tested this same procedure on my mac, it works for me there like it did for you. I think the environment difference is the compiler, so I tried it on a mac and all of the x86-64 docker images on https://github.com/conan-io/conan-docker-tools. I found that it worked on clang, xcode, gcc for centos6, and gcc for mingw. It didn't work on the other gcc docker images.

environment status
mac 10.15.5 with xcode 11.0.3 success
docker conan/clang_10 success
docker conan/clang_3.8 success
docker conan/clang_3.9 success
docker conan/clang_4.0 success
docker conan/clang_5.0 success
docker conan/clang_6.0 success
docker conan/clang_7 success
docker conan/clang_8 success
docker conan/clang_9 success
docker conan/gcc_10 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_4.6 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_4.8 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_5 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_5.2 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_5.3 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_5.4 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_6 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_6.2 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_6.3 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_6.4 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_7 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_7-centos6 success
docker conan/gcc_7-mingw success
docker conan/gcc_7.2 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_8 failure (libproj_B.so: undefined reference to 'hello_A()')
docker conan/gcc_9 failure (libproj_B.so: undefined reference to 'hello_A()')

(The script for to run all the docker images, if you'd like to recreate)

wget https://github.com/conan-io/conan/files/4773393/private-transitive-issue.tar.gz
tar xvf private-transitive-issue
git clone https://github.com/conan-io/conan-docker-tools.git
cd https://github.com/conan-io/conan-docker-tools
find . -type d | grep -v .git | grep -v conan | grep -v jenkins |  grep -v x86 | grep -v arm | grep -v android | grep -v example | sort | xargs -n1 -- bash -c 'docker build -t conan/$(basename $0) $0'
docker image list | sed -n 's|\(conan/[^ ]*\).*|\1|gp' | xargs -n1 -I {}  docker run -v/home/ubuntu:/home/ubuntu {} bash -c "cd /home/ubuntu/tmp/private-transitive-issue/ && ./test.sh 2>&1 && echo '| {} | success |' || echo '| {} | failure |'" | grep -e '|' -e 'undefined reference'
jgsogo commented 4 years ago

It fails with SHARED + PRIVATE. I'm trying to figure out what happens with gcc

jgsogo commented 4 years ago

Having a look to the verbose output from make command:

Private

[ 75%] Linking CXX executable bin/app
/usr/bin/cmake -E cmake_link_script CMakeFiles/app.dir/link.txt --verbose=1
/usr/bin/g++      CMakeFiles/app.dir/app.cpp.o  -o bin/app  -Wl,-rpath,/home/ubuntu/private-transitive-issue/C/build/lib lib/libproj_C.so 
/usr/bin/ld: warning: libproj_A.so, needed by /home/conan/.conan/data/proj_B/0.1/test/test/package/f97905d0e3c50231893bade53d9d0c121662b6e4/lib/libproj_B.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: /home/conan/.conan/data/proj_B/0.1/test/test/package/f97905d0e3c50231893bade53d9d0c121662b6e4/lib/libproj_B.so: undefined reference to `hello_A()'

If we remove RPATHS (SET(CMAKE_SKIP_BUILD_RPATH TRUE)), then the error is related to proj_B instead, because the path to libproj_B.so is not encoded into C library.

[ 75%] Linking CXX executable bin/app
/usr/bin/cmake -E cmake_link_script CMakeFiles/app.dir/link.txt --verbose=1
/usr/bin/g++      CMakeFiles/app.dir/app.cpp.o  -o bin/app  lib/libproj_C.so 
/usr/bin/ld: warning: libproj_B.so, needed by lib/libproj_C.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: lib/libproj_C.so: undefined reference to `hello_B()'

Public

/usr/bin/cmake -E cmake_link_script CMakeFiles/app.dir/link.txt --verbose=1
/usr/bin/g++      CMakeFiles/app.dir/app.cpp.o  -o bin/app  -Wl,-rpath,/home/ubuntu/private-transitive-issue/C/build/lib:/home/conan/.conan/data/proj_B/0.1/test/test/package/f97905d0e3c50231893bade53d9d0c121662b6e4/lib:/home/conan/.conan/data/proj_A/0.1/test/test/package/c315fa4cdecf2499320fcabbd4fdfda4282d90d9/lib lib/libproj_C.so /home/conan/.conan/data/proj_B/0.1/test/test/package/f97905d0e3c50231893bade53d9d0c121662b6e4/lib/libproj_B.so /home/conan/.conan/data/proj_A/0.1/test/test/package/c315fa4cdecf2499320fcabbd4fdfda4282d90d9/lib/libproj_A.so 

I see the problem is that extra ld check that GCC is doing. I would expect clang to fail exactly the same. But probably this is something for CMake itself, I cannot figure out how to resolve it in Conan.

rddesmond commented 4 years ago

Thanks for looking at it! I'll refocus this, submit it to CMake's repo, and leave a link to the issue here.

This does seem like a compiler-specific thing that fits in their wheelhouse, but it seemed insane to describe it without conan -- with conan it's a natural use case to have.

jgsogo commented 4 years ago

Thanks! So... spread the word in the CMake realm 😄

stefanbuettner commented 3 years ago

@rddesmond Nice examples and problem description. I have the same problem and can reproduce it with the example you provided with CMake 3.10.2, conan version 1.34.1, and gcc version 7.5.0. What's the state of this? Did you carry the issue to the CMake repo? Could not find it there.

derived-coder commented 2 years ago

@rddesmond Any update on this issue?

Is the error also reproducible without conan?

derived-coder commented 2 years ago

I have some links to share: https://fedoraproject.org/wiki/UnderstandingDSOLinkChange https://fedoraproject.org/wiki/Features/ChangeInImplicitDSOLinking https://wiki.ubuntu.com/NattyNarwhal/ToolchainTransition

I think this is a "feature" of gcc However, I do not really understand why they did this:

Under the old system, a program that links with libxml2 and uses dlopen need not link with libdl, and a program that links with libxml2 and uses gzopen need not link with libz. While these programs will work, they will break if libxml2 is ever changed to omit the dependency on libdl/libz.

You cannot just call a function of a private dependency...that doesn't work. What is hidden is hidden, you dont have the includes to get the definition.

derived-coder commented 2 years ago

One possible solution for the problem Add this to your cmake target:

# The LD linker complains even about missing link of transitive dependencies,
# altough they are private and hidden (wrapper library)
# LD needs to see them. This would require for consumer to have find_package
# in cmake for transitive and private dependencies.
# which is not what we want. We can fix this with this workaround
target_link_options(${TARGET_NAME} PUBLIC "-Wl,--unresolved-symbols=ignore-in-shared-libs")
Tobulus commented 1 year ago

@jgsogo This is still an issue for us, to my understanding this was fixed in CMake here: https://gitlab.kitware.com/cmake/cmake/-/issues/19556

In our case this is happening if an executable is linked against a lib inside the same package. And this lib has private dependencies coming from another conan package.

The proposed "solution" above is not a good workaround imo.

memsharded commented 1 year ago

Hi all,

I think this issue is outdated:

There are new templates in 2.0, like conan new cmake_lib -d name=pkga -d version=1.0 -d requires=pkgb/1.0 that provide fully working modern template that compile libraries (there is also cmake_exe template). So I think it should be relatively easy to create an updated reproducible case with the modern integrations and testing it for 2.0.

Tobulus commented 1 year ago

Hi @memsharded ,

I'm using the new CMakeDeps generator combined with cmake-conan so for Conan 1.x I'm using state-of-the-art approaches.

Forwarding to Conan 2.0 isn't really helping as we will not move there until a solution is available for cmake-conan.

But I can try to setup a recent example.

memsharded commented 1 year ago

This is the thing, the issue reported above, with the code reported above is using the previous integrations, which can have a very high effect in how things are managed. I can see that with the new integrations it is still an issue, but it can be a bit confusing mixing both in the same ticket.

Also, the fact that cmake-conan is not ready for 2.0 doesn't mean that Conan 2.0 new graph model couldn't be a solution for this issue, as it is not really related to the linking issue. In any case, I don't think that 2.0 will fix the issue, but we should probably test first in 2.0 with the new integrations, as that would be a bit easier to prioritize.

tbsuht commented 1 year ago

@memsharded I've setup a small example. If this is not related to the topic of the discussion above, we can also move this into a new issue.

The relationship is as follows ("->" == "depends on"): app -> libB libB -> libA libA -> libX & libY

Build via:

cd libX
conan create . -pr:h default -pr:b default -s build_type=Release

cd libY
conan create . -pr:h default -pr:b default -s build_type=Release

cd libA
conan create . -pr:h default -pr:b default -s build_type=Release

cd app
conan create . -pr:h default -pr:b default -s build_type=Release

This is failing with:

Scanning dependencies of target app
[ 80%] Building CXX object src/app/CMakeFiles/app.dir/app.cpp.o
[ 80%] Building CXX object src/app/CMakeFiles/app.dir/main.cpp.o
[100%] Linking CXX executable app
/usr/bin/ld: warning: liblibX.so, needed by /home/user/.conan/data/libA/1.0.0/_/_/package/b2bcf496ea6094c509a11d681ff126c83f01fc2f/lib/liblibA.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: warning: liblibY.so, needed by /home/user/.conan/data/libA/1.0.0/_/_/package/b2bcf496ea6094c509a11d681ff126c83f01fc2f/lib/liblibA.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: /home/user/.conan/data/libA/1.0.0/_/_/package/b2bcf496ea6094c509a11d681ff126c83f01fc2f/lib/liblibA.so: undefined reference to `libY()'
/usr/bin/ld: /home/user/.conan/data/libA/1.0.0/_/_/package/b2bcf496ea6094c509a11d681ff126c83f01fc2f/lib/liblibA.so: undefined reference to `libX()'
collect2: error: ld returned 1 exit status
make[2]: *** [src/app/CMakeFiles/app.dir/build.make:100: src/app/app] Error 1
make[1]: *** [CMakeFiles/Makefile2:160: src/app/CMakeFiles/app.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

The error is gone once you link libB PUBLIC against libA. target_link_libraries(libB PUBLIC libA::libA)

I was wondering whether this is related to this issue, but I might be wrong.

$ conan --version
Conan version 1.53.0
memsharded commented 1 year ago

Yes, moved your comment to: https://github.com/conan-io/conan/issues/13302

576347172 commented 6 months ago

Any updates on this issue? cause I am facing some same problem in my work.

memsharded commented 6 months ago

@576347172 Have you seen the comments in https://github.com/conan-io/conan/issues/13302?, specifically https://github.com/conan-io/conan/issues/13302#issuecomment-1452411731

Using PUBLIC would be a good enough workaround, as Conan will not really propagated it downstream, it is just for the local build.

If not, please create a new ticket with details to reproduce, it might be a different thing.