как сделать проект Xcode с доморощенными библиотеками портативными?

Я установил FreeType на свой Mac с помощью Brew. Код на моем 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) 

Все каталоги библиотек и включаемые каталоги Freetype включаются в каталог $SRCROOT/ проекта, когда я пытаюсь запустить код на другом Mac.

Путь, который вы видите в ошибке связывания для библиотеки, — это путь, по которому brew установил freetype на Mac, где я создал этот проект.

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

Я скопировал все необходимые каталоги lib/include/ в домашнюю папку моего проекта.
И я установил библиотеку и включил пути в Xcode.

Что мне здесь не хватает? Что еще мне нужно сделать, чтобы сделать мой код переносимым на любой другой Mac. Я получил проект для запуска на другом Mac, установив Brew, но я хочу сделать это без необходимости устанавливать brew.

PS: мне пришлось установить freetype с помощью brew, так как я не мог скомпилировать .dylib для freetype для 32-битного процессора, 64-битная копия .dylib выдавала мне ошибку, такую ​​​​как «неправильная архитектура!»


person 2am    schedule 06.10.2013    source источник
comment
Вы случайно не используете разные версии OS X? Фреймворк Freetype2 поставляется с более новыми версиями OS X/Xcode, и эта dylib будет загружена раньше любого .dylib, который вы встраиваете в свое .app, если вы не настроите свой бандл должным образом. В цепочке инструментов командной строки вам нужно будет использовать что-то вроде install_name_tool -change , чтобы исправить эту проблему.   -  person Andon M. Coleman    schedule 06.10.2013
comment
@ AndonM.Coleman, обе ОС одинаковы. Но каким-то образом .dylib для фритипа ищет в /usr/local/opt/..... я не знаю, почему это происходит, потому что я включил /lib и /include для фритипа в папку проекта... а также установить пути в настройках сборки Xcode   -  person 2am    schedule 06.10.2013
comment
Я должен отметить, что это не флаг, это только начало команды. Когда вы используете install_name_tool -change в двоичном файле в OS X, он используется для изменения расположения файлов .dylib при запуске из пакета .app. Обычно речь идет о чем-то вроде @executable_path/../Frameworks/MyLib.dylib. Но если вы используете графический интерфейс Xcode, он должен делать это за вас, когда вы связываете свой .app. Посмотрите здесь. Это также обсуждалось на SO, но на данный момент я не могу найти актуальный вопрос.   -  person Andon M. Coleman    schedule 06.10.2013


Ответы (3)


Основная идея, о которой я говорил в своих комментариях, заключается в том, что 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 в том месте, где оно было при компиляции программного обеспечения, и вместо этого будет искать его относительно местоположения вашего исполняемого файла во время выполнения (в данном случае в подкаталоге lib/).

person Andon M. Coleman    schedule 07.10.2013
comment
Это сработало.. :) Одна вещь, однако, новый путь, который я дал, не содержит @executable_path Я не включил его, потому что он не нашел dylib по адресу... @executable_path/Contents/Frameworks/libfreetype.6.dylib Каков синтаксис для этого? - person 2am; 08.10.2013
comment
неважно, я использовал @executable_path/.../Contents вместо @executable_path/../Contents - person 2am; 08.10.2013
comment
У вас есть идеи, как мы можем сделать это с помощью Xcode? Потому что у меня есть исполняемый файл .app для работы, но всякий раз, когда я создаю свой проект и пытаюсь его запустить, ошибка dylib не найдена все еще существует. Я должен каждый раз архивировать проект и получать .app, а затем каждый раз использовать install_name_tool, прежде чем использовать его на другом компьютере Mac. Почему Xcode не может построить .app таким образом, чтобы он не искал dylib в /usr/local/opt/, а в /@executable_path/ - person 2am; 08.10.2013
comment
Посмотрите здесь, developer.apple.com /library/mac/documentation/MacOSX/Conceptual/ и прокрутите вниз до раздела с пометкой Embedding a Private Framework in Your Application Bundle. По общему признанию, я не очень часто использую Xcode IDE (я работаю в основном с командной строкой и Makefiles), поэтому я не могу сказать вам, какие именно меню и т. д. использовать. Если вы сделаете свою библиотеку зависимой от сборки и соответствующим образом зададите место установки, я полагаю, что Xcode IDE сделает то, что вы хотите. - person Andon M. Coleman; 08.10.2013
comment
Именно командная строка намного быстрее, чем IDE, но ради загрузки нашего приложения в App Store я должен использовать IDE. Я смог запустить .app без IDE. Глупая IDE требует времени. :) Во всяком случае, вы очень помогли, спасибо.! - person 2am; 10.10.2013
comment
взгляните на эту EXC_BAD_INSTRUCTION в MacOS У меня был этот сбой, когда я запускал приложение на другой машине, все зависимости библиотеки были исправлены с помощью решения, которое вы предложили выше... - person 2am; 15.10.2013

Вот автоматизированный скрипт, основанный на ответе Андона, который заменит все @executable_path/../Frameworks/ на @executable_path/, чтобы все фреймворки могли находиться в той же папке, что и исполняемый файл. Это также адаптирует сами фреймворки, если они зависят друг от друга.

Осторожно, это имеет смысл только для приложений командной строки. Если это обычное приложение для пакетов OSX, вероятно, не следует изменять соглашение «../Frameworks».

инструкции

  • Добавьте это как «Выполнить сценарий», используя Shell = /bin/sh в конце ваших фаз сборки проекта Xcode.

  • Также убедитесь, что у вас есть шаг «Копировать файлы» с «Пунктом назначения = Каталог продуктов» для всех фреймворков.

Скрипт

# 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 (...)
    ...
person Alexander Klimetschek    schedule 20.12.2017

В конце концов, все оказалось не так просто, как я думал. Это работает, но есть оговорки.

Основываясь на ответах Андона М. Коулмана и Александра Климетчека, вот функция сценария, которая берет имена dylib из Homebrew, копирует их в каталог Contents/Frameworks пакета приложений и устанавливает соответствующий путь поиска исполняемого файла, используя install_name_tool. Я заметил, что иногда фактическое местоположение .dylib отличается от того, что находит компоновщик Xcode при сборке исполняемого файла, и это также обрабатывается, запрашивая exe с помощью otool -L.

Сценарий ниже копирует .dylib для библиотеки liblo в комплект приложения. Добавление новых библиотек должно быть таким же простым, как добавление новой строки 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"

Добавьте это как фазу сборки Run Script в проекте 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) можно было бы улучшить, перейдя вместо этого по символической ссылке .dylib в /usr/local/lib.

ОБНОВЛЕНИЕ: Возможно, вам также потребуется убедиться, что новые dylibs подписаны, иначе шаг подписи кода завершится сбоем при сборке, по крайней мере, это в конечном итоге произошло для меня. После этого Опубликовать SO, добавив --deep к параметру Другие флаги подписи кода в настройках сборки проекта.

Возможно, есть лучший способ подписать только те файлы, которые добавляет скрипт, но мне не хотелось копаться в запуске codesign вручную. Однако из того, что я читал, Apple не предлагает --deep ни для чего, кроме временных исправлений, так что это, вероятно, не лучшая практика.

ОБНОВЛЕНИЕ 2. Запуск --deep на самом деле не решил проблему, и приложение по-прежнему не запускалось на других компьютерах. В конце концов, мне пришлось подписать код скопированного .dylib вручную, что оказалось относительно просто.

Одно примечание: я столкнулся с двусмысленной ошибкой идентификации «Mac Developer» при запуске кода, который, казалось, был сбит с толку двумя сертификатами с одинаковым именем в цепочке ключей: т.е. текущий сертификат разработчика и предыдущий сертификат разработчика с истекшим сроком действия. Открытие Keychain Access и удаление сертификата с истекшим сроком действия решили проблему.

ОБНОВЛЕНИЕ 3: Использование переменной пути в кавычках для исправления путей с пробелами.

ОБНОВЛЕНИЕ 4: это решение отлично подходит для создания приложений для последних систем, но я столкнулся с проблемой подписи кода при запуске приложения в macOS 10.10. Судя по этому сообщению SO, более старые версии macOS используют другой алгоритм хеширования кодового знака, и приложение будет нормально работать в системе 10.12, но с ошибкой подписи кода в 10.10. В обеих системах codesign подтвердил, что все было подписано правильно.

По сути, вам нужно собрать библиотеки с набором -mmacosx-version-min, чтобы codesign мог сказать, какие алгоритмы использовать для поддержки минимальной и последних версий macOS. Я пытался передать пользовательские CFLAGS/LDFLAGS в Homebrew при сборке liblo из исходного кода, но это невозможно без редактирования формулы варева. В конце концов, я решил написать скрипт сборки для самостоятельной сборки liblo из исходного кода и модифицировал скрипт копирования, чтобы он также мог размещаться локально. Теперь все наконец-то работает.

TLDR: библиотеки Homebrew отлично подходят для первоначальной разработки и тестирования, но создание библиотек вручную лучше подходит для развертывания.

person danomatika    schedule 10.04.2018