在 macOS 上使用当前版本和兼容版本

Posted

技术标签:

【中文标题】在 macOS 上使用当前版本和兼容版本【英文标题】:Usage of current version and compatibility version on macOS 【发布时间】:2021-07-07 09:07:15 【问题描述】:

在 macOS 上,dylib 有一个兼容版本和一个当前版本作为元数据,每个都有x.y.z.的形式。 otool -L 可以显示这些。

系统或各种工具/程序如何以及何时使用这些版本?

对于兼容版本,我发现:

将程序与库链接时,该库的兼容版本将记录到程序中。动态链接器将拒绝链接到兼容版本小于记录的库。 (reference,虽然这是针对框架的)。

问题:

系统(例如动态链接器)是否在任何情况下使用当前版本? 比较兼容性版本时,是否使用了x.y.z 方案的所有部分?比较是字典吗?还是分别对 x、y 和 z 有特殊意义? 是否有文档说明这些版本号的使用位置?请注意,我询问的是在实践中实际在何处/何时使用它们,而不仅仅是建议如何设置它们的指南。 系统的任何部分是否对元数据中的两种类型的版本应如何与文件名或为库创建的符号链接名称相关联有任何期望?例如。对于ls -l /usr/lib/libz*,我看到同一个库有许多不同的符号链接,每个符号链接的名称都有不同的版本。

【问题讨论】:

【参考方案1】:

1。系统(例如动态链接器)在任何情况下都使用当前版本吗?

是的,当使用 DYLD_VERSIONED_LIBRARY_PATHDYLD_VERSIONED_FRAMEWORK_PATH 环境变量时。来自man dyld

DYLD_VERSIONED_LIBRARY_PATH
       This is a colon separated list of directories that contain potential over-
       ride 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.

DYLD_VERSIONED_FRAMEWORK_PATH
       This is a colon separated list of directories that contain potential over-
       ride frameworks.  The dynamic linker searches these directories for frame-
       works.  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 if the supplied framework is newer.  Note:
       dyld does not check the framework's Info.plist to find its version.   Dyld
       only  checks  the -currrent_version number supplied when the framework was
       created.

这些变量仅支持 macOS 和 DriverKit 目标。

此外,库的 Mach-O 标头中的 current_version 可以通过 NSVersionOfRunTimeLibrary() 查询,而 Mach-O 标头中的 current_version 可以通过 NSVersionOfLinkTimeLibrary() 与库链接。

2。在比较 兼容性 版本时,是否使用了 x.y.z 方案的所有部分?比较是字典吗?还是分别对x、y、z有特殊意义?

所有部分都使用了,比较是字典顺序的。 从技术上讲,x.y.z 部分构成了一个 32 位的数字,形式为 xxxxyyzz,即 16 位 x、8 位 y 和 z。

3。是否有关于在何处使用这些版本号的文档?请注意,我询问的是在实践中实际在何处/何时使用它们,而不仅仅是关于建议如何设置它们的指南。

man ld 中有一些文档:

-compatibility_version number
            Specifies the compatibility version number of the library.  When a
            library is loaded by dyld, the compatibility version is checked and if
            the program's version is greater that the library's version, it is an
            error.  The format of number is X[.Y[.Z]] where X must be a positive
            non-zero number less than or equal to 65535, and .Y and .Z are
            optional and if present must be non-negative numbers less than or
            equal to 255.  If the compatibility version number is not specified,
            it has a value of 0 and no checking is done when the library is used.
            This option is also called -dylib_compatibility_version for compati-
            bility.

但这只是事实的一半。对于真正发生的事情,我们必须看看dyld sources:

// check found library version is compatible
// <rdar://problem/89200806> 0xFFFFFFFF is wildcard that matches any version
if ( (requiredLibInfo.info.minVersion != 0xFFFFFFFF) && (actualInfo.minVersion < requiredLibInfo.info.minVersion)
        && ((dyld3::MachOFile*)(dependentLib->machHeader()))->enforceCompatVersion() ) 
    // record values for possible use by CrashReporter or Finder
    dyld::throwf("Incompatible library version: %s requires version %d.%d.%d or later, but %s provides version %d.%d.%d",
            this->getShortName(), requiredLibInfo.info.minVersion >> 16, (requiredLibInfo.info.minVersion >> 8) & 0xff, requiredLibInfo.info.minVersion & 0xff,
            dependentLib->getShortName(), actualInfo.minVersion >> 16, (actualInfo.minVersion >> 8) & 0xff, actualInfo.minVersion & 0xff);

除了0xffffffff 可以用作通配符之外,这里有趣的一点是对enforceCompatVersion() 的调用:

bool MachOFile::enforceCompatVersion() const

    __block bool result = true;
    forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) 
        switch ( platform ) 
            case Platform::macOS:
                if ( minOS >= 0x000A0E00 )  // macOS 10.14
                    result = false;
                break;
            case Platform::ios:
            case Platform::tvOS:
            case Platform::iOS_simulator:
            case Platform::tvOS_simulator:
                if ( minOS >= 0x000C0000 )  // iOS 12.0
                    result = false;
                break;
            case Platform::watchOS:
            case Platform::watchOS_simulator:
                if ( minOS >= 0x00050000 )  // watchOS 5.0
                    result = false;
                break;
            case Platform::bridgeOS:
                if ( minOS >= 0x00030000 )  // bridgeOS 3.0
                    result = false;
                break;
            case Platform::driverKit:
            case Platform::iOSMac:
                result = false;
                break;
            case Platform::unknown:
                break;
        
    );
    return result;

如您所见,如果库将其支持的最低操作系统版本声明为较新,那么 dyld 将完全忽略兼容版本。

因此,如果您完全依赖于强制执行的兼容性版本,您将需要使用像 --target=arm64-macos10.13 这样的选项来构建您的库。

4。系统的任何部分是否对元数据中的两种类型的版本应如何与文件名或为库创建的符号链接名称相关联有任何期望?

动态链接仅严格要求,如果您的二进制文件要求/usr/lib/libz.dylib,那么库也必须完全具有该设置作为其名称。如果该库具有 /usr/lib/libz.0.dylib 的嵌入式安装路径,则该库将被视为不同的库。

但是,在绝大多数情况下,您将依赖于在文件系统的安装路径中找到的库,这要求/usr/lib/libz.dylib 处有一个文件,该文件要么是您要查找的库,或指向它的符号链接。但是在这个阶段通常没有理由涉及符号链接。

现在,您看到版本化文件编号的原因是 API 损坏。 compatibility_version 字段处理前向兼容性:如果您链接到版本1.2.3,那么任何大于或等于1.2.3 的版本都可以使用。但是,如果您以破坏向后兼容性的方式删除或更改导出的 API,则必须使用新的安装名称创建一个新库,并继续提供旧库的最新版本的副本以实现向后兼容性。 然后,符号链接仅用于链接时间,以指向库的最新版本。

示例: 假设你有一个/usr/lib/libz.0.dylib,它有许多修复错误、扩展 API 和碰撞compatibility_version 的更新。所有这些都将作为/usr/lib/libz.0.dylib 发布,并且该库的最新版本仍然可以使用与它的第一个版本链接的二进制文件。 现在您要删除其中一个导出的函数,由于这是一个重大更改,因此从现在开始的任何版本都不能以/usr/lib/libz.0.dylib 的形式发布。因此,您创建 /usr/lib/libz.1.dylib 并发布两个库,dyld 将加载构建主二进制文件所针对的任何库。 但是现在任何链接到该库的人都必须在命令行上传递-lz.0lz.1,这不是特别好,需要手动更新,如果您希望人们采用新版本,这通常是不好的。因此,您创建了一个从 /usr/lib/libz.dylib/usr/lib/libz.1.dylib 的符号链接,以便 -lz 链接到库的最新版本。

【讨论】:

以上是关于在 macOS 上使用当前版本和兼容版本的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Xcode 构建与旧 MacOS 兼容的应用程序?

Qt Jambi 版本兼容性

语义版本控制 - 向后兼容性和版本 0.x.y

如何确定组件是不是与当前版本的 Spring 兼容

无法在 macOS Sierra 上运行旧版本的 Xcode

处理序列化框架的不兼容版本更改