file GET_RUNTIME_DEPENDENCIES picks up incorrect shared objects

file (GET_RUNTIME_DEPENDENCIES ) returns incorrect dependencies to /usr/lib instead of /usr/lib64 on Linux.

Examples are libstdc++.so, libX11.so

The cmake documentation states that libraries are searched on paths returned by calling ldconfig. A call on my computer:

/sbin/ldconfig -v -N -X 2>/dev/null | egrep "^([^\t:]*):"

/usr/local/lib64:
/usr/local/lib:
/usr/lib64/graphviz:
/usr/lib64/R/lib:
/lib:
/lib64:
/usr/lib:
/usr/lib64:

The system has been “normally” installed and administered.

Maybe the implementation in cmake is overly complex and can be done a lot easier and less error prone. The tool “ldd” returns the full path to the dependent shared objects. These can be taken as is and do not need any search paths, e.g. returned from ldconfig. This works for 64Bit builds as well as 32Bit builds which is probably not the case using the paths returned by ldconfig.

I am running cmake 3.17.2 on Linux.

The problem with using ldd is that it returns things that might otherwise have been excluded by the {PRE,POST}_{INCLUDE,EXCLUDE}_REGEX arguments of file(GET_RUNTIME_DEPENDENCIES).

Cc: @ben.boeckel

I would have thought that it is up to the user to specify the desired REGEX to match his/her intention.

The current implementation however returns incorrect dependencies. Maybe there is another way to use GET_RUNTIME_DEPENDENCIES that I am not aware of.

It is. The problem is that there is not a way to make ldd work with the REGEXes, because ldd will continue to follow dependencies that would have otherwise been excluded by the REGEXes.

For example, if your program depends on /usr/lib/libfoo.so, and that in turn depends on /lib/bar.so, and you have regexes that exclude /usr/lib, then you could filter /usr/lib/libfoo.so from the ldd output, but you would still get /lib/bar.so. With our approach, all of /usr/lib/libfoo.so's dependencies are also excluded (unless they’re pulled in by something else) because it doesn’t make sense to pull in a dependency for something that we’re not including in the first place.

Thanks for the great example. I was not aware of the fact that ldd returns direct and transitive dependencies. Maybe it would be an idea to use

readelf  -d myprogram | grep NEEDED

to get only the direct dependencies of myprogram and then use

ldd myprogram

to get the location of the direct dependencies. These could then be used in the regex filters.

What distro are you using? Why are /usr/lib and /usr/lib64 in the list? Seems like a distro configuration problem to me…

How does the loader exclude the libraries it finds in /usr/lib first? Does it do an ELF machine flag check?

This is effectively what we do (we also extract RPATH and RUNPATH at this point). However, we then perform the search manually because we don’t want to have LD_LIBRARY_PATH interfere here (and we can’t necessarily clear it because it may be necessary to make ldd run).

We are using OpenSuse Leap. I am not a Linux specialist and regrettfully cannot tell you why /usr/lib and /usr/lib64 are in the list, what ldconfig is used for and how the loader works. Most probably it needs to have some idea of the architectur else it could not distinguish 32Bit and 64Bit libraries which may reside with the same name in /lib and /lib64.

@kyle.edwards Seems we have to filter libraries based on their machine.

% readelf -h -d /path/to/bin
<snip>
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
<snip>

Arghhhh: the usual hassle when using global variables such as environment variables.

My understanding of LD_LIBRARY_PATH is that the loader evaluates the variable and gives libraries on LD_LIBRARY_PATH precedence over system defaults or other locations where libraries can be found. This means that other libraries may be used at load time than were used at link time. I do not think that this is what is desired when using cmake to detect dependencies for packaging. We want to have exactly those libraries defined when linking. To my understanding this means that LD_LIBRARY_PATH may not be set when using ldd for the purpose. If ldd does not work correctly without LD_LIBRARY_PATH then there may be a more serious problem on the build system. The approach currently taken by cmake seems to require reimplemenation of some of the logic of the linker/loader which could be very fragile.

Please take all of this with a grain of salt: I have no intimate knowledge of loader/linker and an extreme allergy against global variables.

An article I just found: https://www.hpc.dtu.dk/?page_id=1180

Cross compilation (though a naked ldconfig is a problem in this case anyways I guess), custom binutils… I can’t seem to find the emails where we discussed using ldd vs. a re-implementation right now; maybe it was a face-to-face meeting. I seem to remember symlinks being involved now that I’m writing this (ldd does realpath on its found libraries maybe?). But we do need to search on Windows (because its lookup rules are just searches anyways) and macOS (we also memoize library IDs and whatnot for future bundling).

It basically is a reimplementation. We had problems with O(n²) on big projects when we couldn’t cache the lookups (not all builds happen on SSDs :frowning: ). Memoizing found library dependency results cut our times on some projects down by 90% or so (IIRC; they were large in any case). Now some of that was because of the CMake -> Python implementation that had been done for that. I don’t know the performance difference between those Python scripts and the resolvers in CMake.