【问题标题】:how to make Xcode project with homebrew libraries portable?如何使带有自制库的 Xcode 项目可移植?
【发布时间】:2013-10-13 02:55:34
【问题描述】:

我已经使用 Brew 在我的 Mac 上安装了 FreeType。我的 Mac 上的代码工作正常,但是当我尝试在其他 Mac 上运行该项目时,我得到下面提到的链接错误。

dyld: Library not loaded: /usr/local/opt/freetype/lib/libfreetype.6.dylib
Referenced from: /Users/ashutosh/Library/Developer/Xcode/DerivedData/InstrumentChamp- 
etytleaabhxmokadgrjzbgtmwxfl/Build/Products/Debug/instrumentchamp.app/Contents/MacOS/instrumentchamp
Reason: image not found (lldb) 

当我尝试在其他 mac 上运行代码时,Freetype 的所有库目录和包含目录都包含在项目的“$SRCROOT/”目录中。

您在库的链接错误中看到的路径是 brew 在我创建此项目的 mac 中安装 freetype 的位置。

/usr/local/opt/freetype/lib/libfreetype.6.dylib

我已将所需的所有 lib/include/ 目录复制到项目的主文件夹中。
我已经在 Xcode 中设置了库和包含路径。

我在这里缺少什么?我还需要做什么才能使我的代码在任何其他 Mac 上可移植。 我通过安装 Brew 让项目在其他 Mac 上运行,但我想在不需要安装 brew 的情况下这样做。

PS:我必须使用 brew 安装 freetype,因为我无法为 32 位处理器编译 freetype 的 .dylib,.dylib 的 64 位副本给了我诸如“架构错误!”之类的错误!

【问题讨论】:

  • 您是否偶然使用了不同版本的 OS X? Freetype2 框架随 OS X/Xcode 的较新版本一起提供,如果您没有正确配置捆绑包,则此 dylib 将在您捆绑在 .app 中的任何 .dylib 之前命中。在命令行工具链中,您需要使用 install_name_tool -change 之类的东西来纠正此问题。
  • @AndonM.Coleman,两个操作系统都是一样的。但不知何故,freetype 的 .dylib 正在 /usr/local/opt/..... 我不知道为什么会这样,因为我已经在项目文件夹中包含了 /lib 和 /include for freetype ...在 Xcode 构建设置中设置路径
  • 我应该提一下,这不是一个标志,它只是一个命令的开始。当您在 OS X 中的二进制文件上使用 install_name_tool -change 时,它用于更改从 .app 包运行时 .dylib 文件的位置。通常它涉及@executable_path/../Frameworks/MyLib.dylib 之类的东西。但是,如果您使用的是 Xcode GUI,它应该会在您捆绑您的 .app 时为您执行此操作。看看here。这也已在 SO 上讨论过,但我目前找不到实际问题。

标签: xcode macos opengl homebrew freetype


【解决方案1】:

我在 cmets 中得到的基本想法是 OS X 在搜索库的位置方面非常愚蠢,它将使用编译期间使用的相同绝对路径在运行时解析它们。

通常,当您想要将应用程序部署/分发到与构建它的机器不同的机器时,您会将您的库包含在安装包/捆绑包中。但是您可能希望他们在运行时使用相对于您的应用程序的路径,因此 install_name_tool -change 允许您将讨厌的绝对路径替换为相对路径。

希望这是有道理的,Apple 让在 OS X 上使用系统范围的框架变得非常容易,自定义库没有那么多。如果您使用系统范围的框架进行编译,/System/Library/Frameworks/... 在所有 OS X 安装中普遍可用(给定相同的目标发布版本)。


为了解决您的问题,我将执行以下操作:

install_name_tool -change /usr/local/opt/freetype/lib/libfreetype.6.dylib @executable_path/lib/libfreetype.6.dylib <executable_name_here>

然后它将停止在您编译软件时所在的位置查找 libfreetype.6.dylib,而是在运行时相对于可执行文件的位置进行搜索(在这种情况下,在子目录 @ 987654324@).

【讨论】:

  • 那行得通.. :) 但有一件事,我给出的新路径不包含@executable_path 我没有包括它,因为它没有在...找到 dylib 987654326@那个语法是什么?
  • 没关系,我用的是@executable_path/.../Contents而不是@executable_path/../Contents
  • 您知道我们如何使用 Xcode 做到这一点吗?因为我让 .app 可执行文件可以工作,但是每当我构建我的项目并尝试运行它时,dylib not found 错误仍然存​​在。我每次都必须存档项目,并获取.app,然后每次都使用 install_name_tool ,然后再在另一台mac机器上使用它。为什么 Xcode 不能以不在 /usr/local/opt/ 而是在 /@executable_path/ 中寻找 dylib 的方式构建 .app
  • 看看这里,developer.apple.com/library/mac/documentation/MacOSX/Conceptual/… 并向下滚动到标有Embedding a Private Framework in Your Application Bundle 的部分。诚然,我不经常使用 Xcode IDE(我主要使用命令行和 Makefiles),所以我无法告诉您要使用的确切菜单等。如果你让你的库成为构建依赖并相应地设置安装位置,我相信 Xcode IDE 会做你想做的。
  • 确切地说命令行比 IDE 快得多,但是为了将我们的应用程序上传到 App Store,我必须使用 IDE.. 我能够在没有 IDE 的情况下运行 .app..愚蠢的 IDE 需要时间。 :) 不管怎样,你帮了大忙,谢谢!
【解决方案2】:

这是一个基于 Andon 回答的自动化脚本,它将所有 @executable_path/../Frameworks/ 替换为 @executable_path/,以便所有框架都可以与可执行文件位于同一文件夹中。如果它们相互依赖,这也会调整框架本身。

请注意,这只对命令行应用程序有意义。如果它是一个常规的 OSX 捆绑应用程序,可能不应该更改“../Frameworks”约定。

说明

  • 在 Xcode 项目构建阶段的末尾使用 Shell = /bin/sh 将其添加为“运行脚本”。

  • 还要确保所有框架都有“复制文件”步骤和“目标 = 产品目录”。

脚本

# change the hardcoded ../Frameworks relative path that Xcode does by rewriting the binaries after the build
# check with:
#   otool -L <binary>

# this is the new location
# make sure this is the same Destination the Frameworks are copied to in the "Copy Files" step
# the default "@executable_path/" would be Destination = Products Directory
NEW_FRAMEWORKS_PATH="@executable_path/"

# the one we want to replace
DEFAULT_FRAMEWORKS_PATH="@executable_path/../Frameworks/"

function change_binary {
    local libpaths=`otool -L "$1" | grep "$DEFAULT_FRAMEWORKS_PATH" | tr '\t' ' ' | cut -d " " -f2`
    local lib
    for lib in $libpaths; do
        if [ "$2" == "recursive" ]; then
            local libbinary=`echo $lib | sed "s,$DEFAULT_FRAMEWORKS_PATH,,"`
            change_binary "$libbinary"
        fi
        local newlib=`echo $lib | sed "s,$DEFAULT_FRAMEWORKS_PATH,$NEW_FRAMEWORKS_PATH,"`;
        echo "changing library path in '$1': '$lib' => '$newlib'"
        install_name_tool -change "$lib" "$newlib" "$1"
    done
}

cd $BUILT_PRODUCTS_DIR
change_binary "$EXECUTABLE_NAME" recursive

请注意,将 NEW_FRAMEWORKS_PATH 更改为例如相对子路径不起作用,脚本需要变得更复杂才能处理。

结果结构

$BUILT_PRODUCTS_DIR/
     executable
     Some.framework/
     Another.framework/
     ...

otool -L executable 看起来像这样:

executable:
    @executable_path/Some.framework/Versions/A/Some (...)
    @executable_path/Another.framework/Versions/A/Another (...)
    ...

【讨论】:

    【解决方案3】:

    最后,这并没有我最初想象的那么容易。它有效,但有一些警告。

    基于 Andon M. Coleman 和 Alexander Klimetschek 的答案,这里有一个脚本函数,它从 Homebrew 中获取 dylib 名称,将它们复制到应用程序包的 Contents/Frameworks 目录中,并使用 install_name_tool 设置可执行文件的相应搜索路径.我注意到有时实际的 .dylib 位置与 Xcode 链接器在将可执行文件放在一起时发现的位置不同,这也可以通过使用 otool -L 查询 exe 来处理。

    下面的脚本将 liblo 库的 .dylib 复制到应用程序包中。添加新库应该像在脚本底部添加新的copy lib ###.dlyib 行一样简单。

    # exit on error
    set -e
    
    # paths
    LOCALLIBS_PATH="$SRCROOT/libs"
    HOMEBREW_PATH="/usr/local"
    FRAMEWORKS_PATH="$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH"
    EXE_PATH="$BUILT_PRODUCTS_DIR/$EXECUTABLE_FOLDER_PATH/$EXECUTABLE_NAME"
    
    # code signing identity
    IDENTITY="$EXPANDED_CODE_SIGN_IDENTITY_NAME"
    if [ "$IDENTITY" == "" ] ; then
        IDENTITY="$CODE_SIGN_IDENTITY"
    fi
    
    # copy lib into Contents/Frameworks and set lib search path
    # $1: library .dylib
    # $2: optional path to libs folder, otherwise use Homebrew
    copylib() {
        LIB=$1
        if [ "$2" == "" ] ; then
            # use homebrew
            LIB_PATH=$(find "$HOMEBREW_PATH" -type f -name $LIB)
        else
            # use given path
            LIB_PATH="$2/$LIB"
        fi
        echo "$LIB:"
        echo "  $LIB_PATH -> $FRAMEWORKS_FOLDER_PATH/$LIB"
    
        # copy lib, set permissions, and sign
        mkdir -p "$FRAMEWORKS_PATH"
        cp "$LIB_PATH" "$FRAMEWORKS_PATH/"
        chmod 755 "$FRAMEWORKS_PATH/$LIB"
        codesign --verify --force --sign "$IDENTITY" "$FRAMEWORKS_PATH/$LIB"
    
        # set new path within executable
        OLD=$(otool -L "$EXE_PATH" | grep $LIB | awk '{print $1}')
        NEW="@executable_path/../Frameworks/$LIB"
        install_name_tool -change "$OLD" "$NEW" "$EXE_PATH"
    
        # print to confirm new path
        otool -L "$EXE_PATH" | grep $LIB
    }
    
    ##### GO
    
    # use lib from homebrew
    copy lib liblo.7.dylib
    
    # or use local lib
    #copylib liblo.7.dylib "$LOCALLIBS_PATH/liblo/lib"
    

    将此作为运行脚本构建阶段添加到 Xcode 项目中。您还可以将其保存为外部文件并在运行脚本阶段使用sh 调用它:

    sh $PROJECT_DIR/path-to/copy-libs.sh
    

    构建报告输出示例:

    liblo.7.dylib:
        /usr/local/Cellar/liblo/0.29/lib/liblo.7.dylib ->> YOURAPP.app/Contents/Frameworks/liblo.7.dylib
        @executable_path/../Frameworks/liblo.7.dylib (compatibility version 11.0.0, current version 11.0.0)
    

    $(find $HOMEBREW_PATH -type f -name $LIB) 可能会通过遵循 /usr/local/lib 中的 .dylib 符号链接来改进。

    更新您可能还需要确保对新的 dylib 进行签名,否则在构建时代码签名步骤将失败,至少它最终对我有用。在此SO post 之后,将--deep 添加到项目Build Settings 中的Other Code Signing Flags 选项有效。

    可能有更好的方法来仅对脚本添加的文件进行签名,但我不想深入研究手动运行codesign。然而,根据我的阅读,Apple 不建议将 --deep 用于临时修复,因此这可能不是最佳做法。

    更新 2:运行 --deep 并没有真正解决问题,应用程序仍然无法在其他机器上运行。最后,我需要手动对复制的 .dylib 进行代码签名,结果证明相对容易。

    注意:我在运行 codesign 时遇到了一个模棱两可的“Mac Developer”身份错误,这似乎被钥匙串中两个同名的证书混淆了:即。当前的开发者证书和之前过期的开发者证书。打开 Keychain Access 并删除过期证书即可解决问题。

    更新 3:引用路径变量的用法来修复带有空格的路径。

    更新 4:此解决方案适用于为最近的系统构建应用程序,但我在 macOS 10.10 上运行应用程序时遇到了代码签名问题。从this SO post 来看,较旧的 macOS 版本使用不同的代码签名哈希算法,该应用程序可以在 10.12 系统上正常运行,但在 10.10 上会因代码签名错误而失败。在这两个系统上,codesign 都验证了所有内容都已正确签名。

    您基本上需要使用 -mmacosx-version-min 集构建库,以便 codesign 能够判断使用哪些算法来支持最小和最近的 macOS 版本。在从源代码构建 liblo 时,我尝试将自定义 CFLAGS/LDFLAGS 传递给 Homebrew,但如果不编辑 brew 公式,这是不可能的。最后,我决定自己编写一个构建脚本来从源代码构建 liblo,并修改了复制脚本,以便它也可以获取本地位置。现在一切都终于正常工作了。

    TLDR:Homebrew 库非常适合初始开发和测试,但手动构建库更适合部署。

    【讨论】:

      猜你喜欢
      • 2015-11-30
      • 2014-03-19
      • 2013-12-29
      • 2018-05-30
      • 1970-01-01
      • 2016-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多