2

I am getting so much conflicting information about this, both on SE and other tutorials. Most people seem to think the so version is a semantic version. Then others correct them saying it must conform to libtools conventions, and go on to implicitly assume or imply that the -version-info C:R:A numbers passed to libtools create files named libfoo.so.C.R.A. But I don't see anywhere in the libtools manual that explicitly states how the files are named, and that doesn't match the behavior that I'm seeing.

I'm building a third party package (gdal), and during the build process, it calls libtool --mode=link -version-info 25:4:5 <many other arguments>, however the so files left in .libs after the build have version libgdal.20.5.4.

I tried with a couple other versions of the same library, and they all seem to follow the same pattern. When you call libtools you pass in current:revision:age, and it generates libfoo.so.current-age.age.revision. This results in an soname of libfoo.so.current-age that is always the minimum version that the library is compatible with, not the maximum as other posts suggest.

I did this testing on both RHEL 7 and Debian 10.

Is this how things are supposed to work? Is this library doing something weird? Is there any place that documents this authoritatively, or at least correctly?

2 Answers 2

5

The typical expectation on Linux is that a shared library has a form libfoo.so.N, where N is an integer. It is also possible that it has forms with additional integers, which may have additional meaning depending on what convention you're using (libtool or otherwise). It is possible to use other forms, and some libraries, such as OpenSSL, do.

Part of a proper shared library on Linux is the ELF entry SONAME, which specifies the shared object name for the binary. For example, with libz:

$ readelf -a /lib/x86_64-linux-gnu/libz.so.1 | grep SONAME
 0x000000000000000e (SONAME)             Library soname: [libz.so.1]

You see that in this case, the SONAME is libz.so.1. When your binary links against this shared library, it will have a NEEDED section embedded into it. The dynamic linker, when resolving the shared libraries, will look in the search path for a shared library with a name equivalent to the SONAME. For example, Git requires these four shared libraries:

$ readelf -a /usr/bin/git | grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libpcre2-8.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libz.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

So the thing that practically matters is what's in the SONAME, which is equivalent to a major version for a semantically versioned piece of code. Making an incompatible change in a shared library without bumping the SONAME (usually to the next integer) value is a grave error that will result in hordes of angry users descending upon your project (and rightfully so).

So it doesn't really matter for the parts of the shared library that aren't part of the SONAME. The libtool convention and the semantic versioning convention both make the first integer after the .so portion of the name the version for incompatible changes, which both do the same thing. Since that's the portion that's usually in the SONAME, they're equivalent for this purpose.

I mentioned that some shared libraries do different things. For example, OpenSSL has libssl.so.1.1 and libcrypto.so.1.1 as the shared library SONAMEs it uses. While this is not the recommended convention, it happens to work. They're changing to semantic versioning in future versions, though, because people find this confusing.

3
  • The libtools manual talks about maintaining a minimum and maximum interface version that a shared library supports, and considering those when linking. Is this information captured in compiled binaries and used by the loader, or is it based solely on SONAME?
    – pavon
    Apr 27, 2020 at 15:01
  • If it is just the SONAME that means I could compile against libfoo.so.1.4, but the loader would happily link me against libfoo.so.1.2, since the SONAME matches, and I could have runtime errors due to missing symbols correct? Eg only backwards compatibility is supported, not forwards compatibility like libtools claims to support. Are there any situations where using libtools and it's conventions actually provide forward compatibility checking on Linux? Or is it a lost cause because other tools in the ecosystem don't support it.
    – pavon
    Apr 27, 2020 at 15:03
  • The minimum interface supported is the SONAME. libtool doesn't add any additional information into the binary here, only conventions for naming. Generally if you need symbols added in a newer version of the library with the same SONAME, you handle that with your package manager; there isn't a way to specify that in the ELF shared object itself.
    – bk2204
    Apr 27, 2020 at 22:54
2

Found the same as you:

C:R:A gets mapped to (C-A).A.R

I found documentation here but it didn't make it very clear in them that this is what it would do.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .