如何使自制图书馆的Xcode项目可移植?

时间:2013-10-06 13:42:07

标签: xcode macos opengl homebrew freetype

我使用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位处理器编译fredype的.dylib,64位的.dylib副本给了我错误,例如'错误的架构!'

3 个答案:

答案 0 :(得分:4)

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

通常,当您希望将应用程序部署/分发到与构建应用程序的计算机不同的计算机时,您将使用安装程序包/ bundle包含库。但是你可能希望它们在运行时使用相对于你的应用程序的路径,因此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,而是在运行时相对于可执行文件的位置搜索它(在本例中,在子目录中{ {1}})。

答案 1 :(得分:0)

这是一个基于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 (...)
    ...

答案 2 :(得分:0)

最后,这并不像我原先想象的那样容易。它有效,但有一些警告。

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

下面的脚本将liblo库的.dylib复制到app bundle中。添加新的库应该就像在脚本底部添加新的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符号链接来改进。

UPDATE 您可能还需要确保新的dylib已签名,否则代码签名步骤将在构建时失败,至少它最终会为我做。在SO post之后,将--deep添加到项目构建设置中的其他代码签名标志选项。

可能有更好的方法只签署脚本添加的文件,但我并不想手动挖掘codesign。但是,从我所读到的内容来看,Apple并未建议--deep提供除临时修复之外的任何内容,因此它可能不是最佳做法。

更新2 :正在运行--deep并没有真正解决问题,而且应用仍无法在其他计算机上运行。最后,我需要手动对复制的.dylib进行编码签名,结果相对简单。

一个注意事项:我遇到了一个模棱两可的Mac开发者&#34;运行codesign时出现身份错误,这似乎被Keychain中具有相同名称的两个证书混淆了:ie。当前的开发人员证书和以前过期的开发人员证书。打开Keychain访问并删除过期的证书解决了这个问题。

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

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

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

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