Conan: Improving shared library dependencies by using relative RPATHs

Created on 19 May 2017  路  2Comments  路  Source: conan-io/conan

I researched the current situation with finding shared library dependencies in Conan (as of version 0.22.3) a bit, and found that there are some major inconsistencies and areas where the experience could probably be improved.

This is a summary of the current situation, as I understand it (please correct me if something is wrong):

  • One of Conan goals is to have both reusable (including across machines) and relocatable (without dependencies on their absolute path in filesystem) binary packages, which could contain shared libraries.
  • When an artifact R in a Conan package A depends on a shared library L from Conan package B, the exact name/location in package B of L and exact package id of B are always known at the time of A build. Conan takes care of recording id A -> id B dependency itself. If the artifact depending on L is capable (excluding static libraries, unfortunately), the name of L is recorded as a dependency in the artifact.
  • When a binary package of A is used, Conan ensures that contents of B (including L) are available, but the absolute path to L can vary. Dynamic linker must be able to find L for R to work, and RPATH metadata embedded in R are a common way to ensure that.
  • Many build systems set RPATHs automatically when linking to a shared library not in a well-known set of system-wide locations. This is very convenient for developers, as software they work on often works even without system-wide installation, and embedding unstable RPATHs is not a concern for intermediate build artifacts.
  • CMake build system by default uses unstable, absolute RPATHs for any build step artifacts, and patches (or re-links) artifacts on the install step, using separate install RPATH (which is empty by default, so RPATH gets removed). Because of this, the behavior is inconsistent for simple Conan CMake packages: if they omit the install step and package intermediate artifacts, unstable absolute paths leak into the binary package by default, and if the do the proper install step, resulting artifacts have no RPATH at all and fail to find their dependencies by default.
  • I'm less familiar with Autotools+libtool build system, but they seem to have some sort of similar automatic RPATH generation for dependencies in custom locations. Personally, I failed to get it to work in Conan at all, but even if it did, it would just leak unstable absolute paths into the binary package.
  • In case of Autotools, the library dependencies (B.cpp_info.libs) are used even when linking/running temporary programs in the configure phase, so failure to find L at run time simply prevents A from being built at all.
  • Copying L into A using import() can be an alternative solution, except that: it does not work on Linux by default (dynamic linker searches ONLY system-wide paths unless told otherwise) and half-broken on OS X (#1238). It also seems to be a non-optimal solution (#776), except for deploying self-contained final applications. It also is the only way to go on Windows, probably.
  • Static libraries depending on shared ones are totally broken (when not using import()), because due to format limitations, any dependency information must be propagated out-of-band. Legacy libtool archives (*.la) are related for Autotools. This could likely be handled by storing their dependency info in autogenerated pkg-config files (#334), including proper RPATH ldflags.
  • Many dynamic linkers support RPATHs relative to the depending object: $ORIGIN on Linux/Solaris, @loader_path on Mac OS X (see #337).

So based on these observations, I suggest the following behavior:

  • In binary packages, relative RPATH should be used for R -> L dependency. If the structure of Conan package store is stable, this will just work out of the box, no matter the absolute path changes.
  • When packages import() libraries with relative RPATHs, it could be patched by using chrpath or patchelf utilities (either by the package itself, or automatically).
  • When using static libraries with shared dependencies, their RPATH settings should be propagated to all users automatically (either by using pkg-config, or by Conan itself).
  • No behavior change on Windows is suggested: import() is only way that should work, as far as I understand. Maybe it can somehow be used by default there?

With these changes, depending on shared libraries should work seamlessly in many cases, and the goal of supporting both reusable and relocatable binary packages should not be compromised.

look into

Most helpful comment

Another possible solution that I thought of is to make it easy to create symlinks/hardlinks when doing import() instead of copying, and then import() all the dependencies everywhere (and use trivial relative RPATHS, like $ORIGIN or $ORIGIN/../lib for binaries). Maybe packages could declare some of their files to be 'auto-imported'.

Also, regarding python helpers to alter the rpaths: there are at least https://github.com/rmcgibbo/pypatchelf and https://github.com/jimporter/patchelf-wrapper to help with the distribution/dependency.

All 2 comments

Hi @himikof

Your analysis is good. In the CMake case, we set by default in OSX the set(CMAKE_SKIP_RPATH 1). That way CMake don't mess with rpaths. In the case of Linux, the problem still exists, but the different with OSX is that it doesn't crash if the library is not found in a declared rpath, just continues searching in the specified paths.

We (as package creators, in our recipes) also perform replacements in autotools script files of the install_nameparameter to remove the absolute paths.

About the tools to alter the rpaths, yes, definitely is a possibility, but we have not wanted to mess with external tools and rpaths, maybe we could study if we can provide some python helper to alter the rpaths.

In general, we don't force to use any strategy, you can package your libraries using relative rpaths if it fits better for you. In our experience, there is no a single/perfect approach so we try to keep it simple as possible and let the user take the decision. But of course, we can think about tools, or document different alternatives. It's certainly a pending task ;).

So thank you for your comments, it will help.

Another possible solution that I thought of is to make it easy to create symlinks/hardlinks when doing import() instead of copying, and then import() all the dependencies everywhere (and use trivial relative RPATHS, like $ORIGIN or $ORIGIN/../lib for binaries). Maybe packages could declare some of their files to be 'auto-imported'.

Also, regarding python helpers to alter the rpaths: there are at least https://github.com/rmcgibbo/pypatchelf and https://github.com/jimporter/patchelf-wrapper to help with the distribution/dependency.

Was this page helpful?
0 / 5 - 0 ratings