Linking against non versioned or major library

Hello,

I am trying to compile a cpp code which has some external library calls (libnova) among others.

That lib is found using the find_library call:

 find_library(NOVA_LIBRARIES NAMES nova
    PATHS ${NOVA_ROOT} ENV INSTALL_LOCATION 
    PATH_SUFFIXES lib lib64
    ${_obLinkDir}
    ${GNUWIN32_DIR}/lib
  )

The NOVA_LIBRARIES cmake var outputs NOVA_LIBRARIES /usr/lib/x86_64-linux-gnu/libnova.so
When the library is linked the makefile output is (object file list removed):

[100%] Linking CXX shared library libfoo.so
cd /tst/build/src/time && /usr/bin/cmake -E cmake_link_script CMakeFiles/TIME.dir/link.txt --verbose=1
/usr/bin/c++ -fPIC  -fopenmp -O3 -DNDEBUG  -g   -Wall -Wextra -Werror -ggdb -fpermissive -gdwarf-3
-fprofile-arcs -ftest-coverage  -fprofile-arcs 
-shared -Wl,-soname,libfoo.so -o libfoo.so object_files.o 
-Wl,-rpath,::::::::::::::::::::::::: /usr/lib/x86_64-linux-gnu/libnova.so 

As you can see the libnova lib is a non versioned linked lib, tut if I objdump the generated lib I actually get:

  NEEDED               libnova-0.16.so.0
  NEEDED               libstdc++.so.6
  NEEDED               libm.so.6
  NEEDED               libgomp.so.1
  NEEDED               libgcc_s.so.1
  NEEDED               libpthread.so.0
  NEEDED               libc.so.6

And If I ldd the lib:

root@46bdf6475137:/tst/build/src/time# ldd libfoo.so 
        linux-vdso.so.1 (0x00007ffe4ddad000)
        libnova-0.16.so.0 => /usr/lib/x86_64-linux-gnu/libnova-0.16.so.0
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f2762523000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f27623a0000)
        libgomp.so.1 => /usr/lib/x86_64-linux-gnu/libgomp.so.1 (0x00007f276236f000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f2762355000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2762332000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2762171000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f27626d4000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f276216c000)

There are some symlinks but are not used at all:

lrwxrwxrwx 1 root root      21 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova-0.16.so.0 -> libnova-0.16.so.0.0.0
-rw-r--r-- 1 root root 3669184 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova-0.16.so.0.0.0
-rw-r--r-- 1 root root 3811162 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.a
lrwxrwxrwx 1 root root      21 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.so -> libnova-0.16.so.0.0.0

Also, I tried to remove the symlink “libnova-0.16.so.0” to see if now the linker takes the non-versioned or at least, the major versioned symlink:

-rw-r--r-- 1 root root 3811162 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.a
lrwxrwxrwx 1 root root      17 Aug 13 10:35 /usr/lib/x86_64-linux-gnu/libnova.so -> libnova.so.0.16.0
lrwxrwxrwx 1 root root      17 Aug 13 10:23 /usr/lib/x86_64-linux-gnu/libnova.so.0 -> libnova.so.0.16.0
-rw-r--r-- 1 root root 3669184 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.so.0.16.0

But it still points to an unexisting lib (since I removed it):

  NEEDED               libnova-0.16.so.0
  NEEDED               libstdc++.so.6
  NEEDED               libm.so.6
  NEEDED               libgomp.so.1
  NEEDED               libgcc_s.so.1
  NEEDED               libpthread.so.0
  NEEDED               libc.so.6

How can I get the non versioned lib as the linked lib?

Thanks in advance

The version that will be written to the NEEDED section depends on the SONAME of the linked library. That means the linker reads it from the file your /usr/lib/x86_64-linux-gnu/libnova.so ultimately points to. This has nothing to do with CMake, it’s how the linking works.

So you’d have to build libnova with appropriate SONAME. I do not recommend this however as it’ll break stuff sooner or later when whoever installs your software would have an incompatible version of libnova on their system.

1 Like

Thanks Jakub for replying.

But I already notice what you meant and for that reason I tried to remove the symlink libnova-0.16.so.0 and change the libnova.so symlink to point to a proper SONAME version but it stills pointing to the previous one, even I removed the build directory where the cmake files are created and tried to recreate the build system again by calling cmake. In detail I did:

root@18be343e093c:/usr/lib/x86_64-linux-gnu# ls -l libnova*      
lrwxrwxrwx 1 root root      21 Jan 23  2019 libnova-0.16.so.0 -> libnova-0.16.so.0.0.0
-rw-r--r-- 1 root root 3669184 Jan 23  2019 libnova-0.16.so.0.0.0
-rw-r--r-- 1 root root 3811162 Jan 23  2019 libnova.a
lrwxrwxrwx 1 root root      21 Jan 23  2019 libnova.so -> libnova-0.16.so.0.0.0

root@18be343e093c:/usr/lib/x86_64-linux-gnu# rm libnova-0.16.so.0

root@18be343e093c:/usr/lib/x86_64-linux-gnu# mv libnova-0.16.so.0.0.0 libnova.so.0.16.0

root@18be343e093c:/usr/lib/x86_64-linux-gnu# rm libnova.so 

root@18be343e093c:/usr/lib/x86_64-linux-gnu# ln -s libnova.so.0.16.0 libnova.so        

root@18be343e093c:/usr/lib/x86_64-linux-gnu# ln -s libnova.so.0.16.0 libnova.so.0

root@18be343e093c:/usr/lib/x86_64-linux-gnu# ls -l libnova*
-rw-r--r-- 1 root root 3811162 Jan 23  2019 libnova.a
lrwxrwxrwx 1 root root      17 Aug 13 11:40 libnova.so -> libnova.so.0.16.0
lrwxrwxrwx 1 root root      17 Aug 13 11:40 libnova.so.0 -> libnova.so.0.16.0
-rw-r--r-- 1 root root 3669184 Jan 23  2019 libnova.so.0.16.0

After 6 minutes I realized the system created again the removed symlink:

lrwxrwxrwx 1 root root      17 Aug 13 11:46 /usr/lib/x86_64-linux-gnu/libnova-0.16.so.0 -> libnova.so.0.16.0
-rw-r--r-- 1 root root 3811162 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.a
lrwxrwxrwx 1 root root      17 Aug 13 11:40 /usr/lib/x86_64-linux-gnu/libnova.so -> libnova.so.0.16.0
lrwxrwxrwx 1 root root      17 Aug 13 11:40 /usr/lib/x86_64-linux-gnu/libnova.so.0 -> libnova.so.0.16.0
-rw-r--r-- 1 root root 3669184 Jan 23  2019 /usr/lib/x86_64-linux-gnu/libnova.so.0.16.0

I agree this linking issue is something that probably CMake has nothing to do, but I wondered if there is some CMake option to tell the linker the correct lib filename.

Thanks in advance,

Does objdump -p /usr/lib/x86_64-linux-gnu/libnova.so | grep SONAME confirm that?

Thank you very much,

Now I can see what you meant, sorry for the misunderstanding:

root@18be343e093c:/tst/build# objdump -p /usr/lib/x86_64-linux-gnu/libnova.so | grep SONAME
  SONAME               libnova-0.16.so.0

So, the linker takes this name to look for the library.

Very pleased!

Regarding this, I checked out the thirty party libraries that our library use (libnova and boost) and both points the SONAME to the MAJOR.MINOR.PATCH version.

This makes cross compiling (compile in Debian, execute in Centos for instance) more difficult. Is there a way to achieve this?

Thanks in advance!

This is always a problem. You cannot compile against libfoo’s version x.y and expect to work when run against x.z in general. Some projects make ABI guarantees if z > y, but these are rare (glibc, libX11, libstdc++, glib, Qt; certainly not Boost). You’ll need to either compile against the lowest common denominator or ship the libraries you built against.

Exactly this is what we think about, for instance If

  • Centos system has Boost 1.66
  • Debian system has Boost 1.69

We could compile our library in Centos and execute on Debian. This assumes that there is the same major version in both OS.

However, due to the fact that the SONAME points to the the exact version:

libboost_atomic.so
  SONAME               libboost_atomic.so.1.67.0
libboost_chrono.so
  SONAME               libboost_chrono.so.1.67.0
libboost_container.so
  SONAME               libboost_container.so.1.67.0
libboost_context.so
  SONAME               libboost_context.so.1.67.0
libboost_coroutine.so
  SONAME               libboost_coroutine.so.1.67.0
libboost_date_time.so
  SONAME               libboost_date_time.so.1.67.0
libboost_fiber.so

Even upgrading those libraries within the same OS would require recompile our library since the linker would not be able to find the previous one, would be it?

Thank you all for this amazing support!

I wouldn’t trust two Boost builds by different vendors even for the same version. Boost, specifically, is too sensitive to compilers and their version differences. All kinds of compiler flags can affect the ABI making things silently incompatible. The only solution that is reliable (on Linux) is to:

  • build against the oldest version that you support and/or need for libraries with ABI compatibility offered; and
  • ship copies of everything else.

Just to make sure you understand - the problem is not the SONAME per se but the compatibility guarantees the library authors provide. The SONAME only reflects that so don’t try to get around that by some tricks. Basically your options are:

  • only provide source code so users have to build it themselves… :frowning:
  • vendor the libraries
  • manage separate builds for different Distros… :frowning:

Hahaha, yeah I understand, it seems most library dev teams do not like/follow semantic versioning :stuck_out_tongue:

Actually what we do right now is to have several pipelines (one for distro) but this is a bit unmanageable and we would like to minimize the number of jobs that we have.

Vendoring libraries, as far I understand, it would be pretty much the same as using static libraries of those dependencies, since they are placed into the library or binary that you want to build.

vendoring - The same what @ben.boeckel means with “ship copies of everything else”.

At work I develop few project. Depending on few factors we do two things:

  • static builds - useful for tools that are not really expected to be “installed” in any way
  • build all used libraries with the product and ship them together. This requires either setting LD_LIBRARY_PATH before running, or getting the RPATH right for all the libs and executables you build.

Both approaches have down- and upsides but with combination of both we can build on oldest supported CentOS and it “just works” everywhere.

1 Like