Перед началом майских праздников я провел 5 плотных дней за написанием приложения под iOS и Mac для конкурса. Разработка шла планомерно, к последнему дню работа приложения меня начала более-менее удовлетворять. Я решил отложить отправление на утро самого последнего дня, чтобы сделать это со свежой головой (там нужно было еще приложить текстовое описание). Утром, за несколько часов до поезда в отпуск, я сел и со спокойной душой сделал финальную обкатку основного функционала. И тут мне показалось хорошей идеей потестить, как будет вести себя приложение на абсолютно другой машине, на которой не велась разработка. Я архивирую свое приложение, перекидываю его на другой Mac и… оно не запускается. С супер-информативной ошибкой "Image not found" и путём явно указывающим на проблему с dylib. Итак: 3 часа до поезда и не запускающиеся по непонятным причинам приложение. Почему это произошло, как этого можно было бы избежать и как я справился с этой проблемой — обо всём этом под катом.
В суте проблемы я разобрался довольно быстро, а вот её решение заняло некоторое время. Давайте все же разбремся, что же случилось. В моём приложении использовалась OpenCV. Я собрал её из оригинальных сорсов с дефолтным конфигом прямиком с официального сайта, используя CMake. После этого, указал пару Search Path в XCode и разработка продвигалась, что называется, seamless. Как вы уже наверное поняли, проблема заключалась в том, что на рабочей машине мое приложение использовала библиотеки, которые лежали по адресу /usr/local/lib/
.
Именно этот путь был указан для поиска в XCode и т.к. я слинковал их с приложением, я ошибочно полагал, что все необходимые библиотеки подтянуться во время архивации, теперь я уже знаю, что это не так. Первая мысль, которая пришла мне тогда: сделать инсталлятор. В принципе, это рабочее решение и многие именно так и поступают. Однако на это не было времени, т.к. помимо затрат времени на разработку самого инсталлятора, потребовалось еще тестирование и т.д., а поезд отъезжал через пару часов.
Я начал гуглить эту проблему и понял, что мне надо закинуть dylib в само приложение. Я недолго думая, закинул их в XCode проект, добавил linkage, заархивировал… на рабочей машине всё ок, на сторонней — всё та же ошибка. Проблема была в зашитих в dylib путях установки и поиска зависимостей. В моём случае это был монстроузный
/Users/s1ddok/Downloads/opencv-3.1.0/build/lib/libopencv_core.3.1.dylib
Это потому что я добавил в проект промежуточные dylib из папки build, это те, которые в последствии устанвливаются командой make install
, но даже, если бы я взял их из /usr/local/lib/
, это не решило проблему.
Как можно было бы избежать эту проблему? Указывать сразу необходимые пути установки во время компиляции dylib. Если Вы комплируете свои библиотеки, скорее всего у Вас уже всё правильно установлено, если же нет, это легко сделать в XCode. Конкретно в случае с OpenCV нужно было всего лишь изменить дефолтный конфиг CMake и добавить туда префикс для installation dir, но возможности сделать этого на тот момент у меня не было.
Итак мне нужно было как-то изменить install path в уже готовых dylib, чтобы они запускались. Я опущу подробные описания того, как я пришёл к результату, лишь расскажу, что же в итоге сработало.
Первым делом нам нужно, чтобы наши dylibs, которые мы добавили в проект, копировались в бандл с нашим приложением. В принципе, все равно куда, поидее можно хоть в main bundle их засунуть, но логичны кажется копирование в папку Frameworks. Для этого идем в XCode -> Target Settings -> Build Phases и добавляем новую фазу Copy Files. Мое приложение используёт только 2 библиотеки из OpenCV набора, поэтому у меня это выглядит вот так:
Ок, теперь внутри нашего приложения хранятся все нужные dylib, однако оно вываливается с той же ошибкой, потому что пути в них все еще старые. На помощь нам приходят такие утилиты как otool
и install_name_tool
. Первая нужна была для того, чтобы узнать какие install path "зашиты" в имеющиеся библиотеки, а вторая для того, чтобы их изменить. Добавляем новую фазу сборки в XCode, на этот раз Run Script
. Сам скрипт получился вот таким:
# Говорим куда смотреть нашему приложению в поисках библиотек
install_name_tool -change lib/libopencv_core.3.1.dylib @executable_path/../Frameworks/libopencv_core.3.1.0.dylib $BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH
install_name_tool -change lib/libopencv_imgproc.3.1.dylib @executable_path/../Frameworks/libopencv_imgproc.3.1.0.dylib $BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH
# Переходим в папку Frameworks внутри нашего приложения
cd $BUILT_PRODUCTS_DIR
cd MyApp.app
cd Contents
cd Frameworks
# Здесь мы говорим библиотекам куда они установлены
install_name_tool -id @executable_path/../Frameworks/libopencv_core.3.1.0.dylib libopencv_core.3.1.0.dylib
install_name_tool -id @executable_path/../Frameworks/libopencv_imgproc.3.1.0.dylib libopencv_imgproc.3.1.0.dylib
# Следующей линией можно использоваться, чтобы посмотреть, какие зависимости имеет эта библиотека.
# otool -L libopencv_imgproc.3.1.0.dylib
# В моем случае она зависит от opencv-core и нам нужно сказать ей, где она лежит
install_name_tool -change /Users/s1ddok/Downloads/opencv-3.1.0/build/lib/libopencv_core.3.1.dylib @executable_path/../Frameworks/libopencv_core.3.1.0.dylib libopencv_imgproc.3.1.0.dylib
Это, конечно же, ужасное решение и скрипт придется переделывать под каждую библиотеку, он не универсален. На тот момент у меня не было времени разрабатывать generic-решение.
Я надеюсь мой опыт будет кому-нибудь полезен, я разбирался со всем этим в очень нервной и напряженной обстановке, поэтому вдвойне рад, что приложение таки запустилось и я смог успешно его отправить, правда эссе пришлось написать за 20 мин без проверки, но это уже мелочи.
Автор: s1dd0k