简体   繁体   中英

Library versioning on Mach-O platforms

I was looking at versioning a shared library for a small personal project when the docs for the SOVERSION target property mentioned that on Mach-O systems such as OS X and iOS, it corresponds to the "compatibility version", while VERSION corresponds to the "current version".

On Linux, it's simply something like VERSION 5.4.2 and SOVERSION 5 to indicate that the library is compatible with version 5.0 and newer, and VERSION is used as the DLL image version in the form <major>.<minor> on Windows (I'm not sure what difference SOVERSION makes on Windows).

However, the example in the docs for the referenced FRAMEWORK target property illustrate how you might have VERSION 16.4.0 and SOVERSION 1.0.0 on Mach-O platforms (I'm not interested in building a framework, just wondering about the foreign versioning scheme.)

Just how does versioning work in the Mach-O world? I'm used to just bumping the major version if I remove some functionality, which would be a compatibility break, so how is it possible that a library at version 16.4.0 remains compatible with the 1.0.0 version of the library? What does "compatible" mean?

First off, just to get this out of the way, frameworks are just dylibs that are named Something.framework/Something rather than libsomething.dylib . The file format is exactly the same though, so throughout this post I'll be simply referring to them as dylibs.

Now, let's start with an excerpt from the mach-o/loader.h header (the de-facto authoritative source for the Mach-O file format):

/*
 * Dynamicly linked shared libraries are identified by two things.  The
 * pathname (the name of the library as found for execution), and the
 * compatibility version number.  The pathname must match and the compatibility
 * number in the user of the library must be greater than or equal to the
 * library being used.  The time stamp is used to record the time a library was
 * built and copied into user so it can be use to determined if the library used
 * at runtime is exactly the same as used to built the program.
 */
struct dylib {
    union lc_str  name;         /* library's path name */
    uint32_t timestamp;         /* library's build time stamp */
    uint32_t current_version;       /* library's current version number */
    uint32_t compatibility_version; /* library's compatibility vers number*/
};

/*
 * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
 * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
 * An object that uses a dynamically linked shared library also contains a
 * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
 * LC_REEXPORT_DYLIB) for each library it uses.
 */
struct dylib_command {
    uint32_t    cmd;        /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
                       LC_REEXPORT_DYLIB */
    uint32_t    cmdsize;    /* includes pathname string */
    struct dylib    dylib;      /* the library identification */
};

As explained in the comments, a struct dylib is embedded in both the library as well as in the binary linking against it, both containing a copy of current_version and compatibility_version . How the latter works is explained right there, but the former is not addresses.
Documentation for that can be found on the dyld man page (source is here , but not pretty to look at outside of man ):

DYLD_VERSIONED_FRAMEWORK_PATH
    This  is a colon separated list of directories that contain potential override frame-
    works.  The dynamic linker searches  these  directories  for  frameworks.   For  each
    framework  found  dyld  looks  at  its  LC_ID_DYLIB  and gets the current_version and
    install name.  Dyld then looks for the framework at the install name path.  Whichever
    has the larger current_version value will be used in the process whenever a framework
    with that install name is required.  This is similar  to  DYLD_FRAMEWORK_PATH  except
    instead  of  always overriding, it only overrides is the supplied framework is newer.
    Note: dyld does not check the framework's Info.plist to find its version.  Dyld  only
    checks the -current_version number supplied when the framework was created.

[...]

DYLD_VERSIONED_LIBRARY_PATH
    This is a colon  separated  list  of  directories  that  contain  potential  override
    libraries.  The dynamic linker searches these directories for dynamic libraries.  For
    each library found dyld looks at its LC_ID_DYLIB and  gets  the  current_version  and
    install  name.   Dyld then looks for the library at the install name path.  Whichever
    has the larger current_version value will be used in the  process  whenever  a  dylib
    with  that  install  name  is  required.  This is similar to DYLD_LIBRARY_PATH except
    instead of always overriding, it only overrides is the supplied library is newer.

So in short:

  • compatibility_version is used to determine whether a library is "new enough" for a binary that wants to load it.
  • current_version is used to pick a library when more than one are available.

As for your confusion about having a current version of 16.4.0 with a compatibility version of 1.0.0 : from looking at some sources , Apple seems to bump the major version whenever there is any kind of feature introduced, and use the minor version(s) for pretty much only bug fixes, AFAIK.
So what they call 16.4.0 , I'd probably call 1.16.4 . ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM