Предположим, я создал приложение OSX без использования Xcode. После компиляции с GCC я получаю исполняемый файл, связанный с несколькими другими библиотеками. Некоторые из этих библиотек могут быть снова динамически связаны с другими нестандартными системными библиотеками.
Существует ли какой-либо инструмент, который создает пакет приложений OSX, сначала создавая необходимые структуры каталогов, а затем рекурсивно копируя / проверяя / исправляя ссылки, чтобы убедиться, что все динамические зависимости также присутствуют в пакете приложений?
Думаю, я могу попробовать написать что-то подобное, но мне было интересно, существует ли что-то подобное уже.
Ответы:
Есть два способа создать пакет приложений в MacOSX: Простой и Уродливый.
Самый простой способ - просто использовать XCode. Выполнено.
Проблема в том, что иногда вы не можете.
В моем случае я создаю приложение, которое собирает другие приложения. Я не могу предположить, что у пользователя установлен XCode. Я также использую MacPorts для создания библиотек, от которых зависит мое приложение. Мне нужно убедиться, что эти дилибы включены в приложение, прежде чем я его распространю.
Отказ от ответственности: я совершенно не квалифицирован, чтобы писать этот пост, все в нем было отражено в документации Apple, отбирая существующие приложения и методом проб и ошибок. У меня работает, но скорее всего не так. Пожалуйста, напишите мне, если у вас есть исправления.
Первое, что вам следует знать, это то, что набор приложений - это просто каталог.
Давайте рассмотрим структуру гипотетического foo.app.
Info.plist - это простой файл XML. Вы можете редактировать его с помощью текстового редактора или приложения Property List Editor, которое поставляется вместе с XCode. (Он находится в каталоге / Developer / Applications / Utilities /).
Ключевые вещи, которые вам нужно включить:
CFBundleName - название приложения.
CFBundleIcon - файл значка, который предположительно находится в каталоге Contents / Resources. Используйте приложение Icon Composer для создания значка. (Он также находится в каталоге / Developer / Applications / Utilities /). Вы можете просто перетащить png в его окно и автоматически сгенерировать для вас уровни mip.
CFBundleExecutable - имя исполняемого файла, который должен находиться в подпапке Contents / MacOS /.
Есть еще много вариантов, перечисленные выше - это лишь минимум. Вот некоторая документация Apple по файлу Info.plist и структуре пакетов приложений .
Кроме того, вот образец Info.plist.
В идеальном мире вы можете просто поместить свой исполняемый файл в папку Contents / MacOS / и все готово. Однако, если ваше приложение имеет нестандартные зависимости dylib, оно не будет работать. Как и Windows, MacOS имеет свой особый вид DLL Hell .
Если вы используете MacPorts для создания библиотек, которые вы связываете, расположение dylibs будет жестко закодировано в вашем исполняемом файле. Если вы запустите приложение на компьютере, на котором файлы dylib находятся в одном и том же месте, оно будет работать нормально. Однако у большинства пользователей они не установлены; когда они дважды щелкают по вашему приложению, оно просто вылетает.
Перед распространением исполняемого файла вам необходимо собрать все загружаемые им дилибы и скопировать их в пакет приложений. Вам также потребуется отредактировать исполняемый файл, чтобы он искал дилибы в нужном месте. т.е. куда вы их скопировали.
Редактирование исполняемого файла вручную кажется опасным, не так ли? К счастью, в этом могут помочь инструменты командной строки.
Эта команда выведет список всех дилибов, от которых зависит ваше приложение. Если вы видите что-то, чего НЕТ в папке System / Library или usr / lib, это те, которые вам нужно скопировать в пакет приложений. Скопируйте их в папку / Contents / MacOS /. Затем вам нужно отредактировать исполняемый файл, чтобы использовать новые файлы dylib.
Во-первых, вам нужно убедиться, что вы устанавливаете ссылку с помощью флага -headerpad_max_install_names. Это просто гарантирует, что если новый путь dylib длиннее предыдущего, для него останется место.
Во-вторых, используйте install_name_tool для изменения каждого пути к dylib.
В качестве практического примера предположим, что ваше приложение использует libSDL , а otool указывает его местоположение как "/opt/local/lib/libSDL-1.2.0.dylib".
Сначала скопируйте его в комплект приложений.
Затем отредактируйте исполняемый файл, чтобы использовать новое местоположение (ПРИМЕЧАНИЕ: убедитесь, что вы создали его с флагом -headerpad_max_install_names)
Уф, мы почти закончили. Теперь есть небольшая проблема с текущим рабочим каталогом.
Когда вы запускаете приложение, текущим каталогом будет каталог, расположенный выше, в котором расположено приложение. Например: если вы поместите файл foo.app в папку / Applcations, тогда текущим каталогом при запуске приложения будет папка / Applications. Не /Applications/foo.app/Contents/MacOS/, как вы могли ожидать.
Вы можете изменить свое приложение, чтобы учесть это, или вы можете использовать этот волшебный маленький скрипт запуска, который изменит текущий каталог и запустит ваше приложение.
Убедитесь, что вы настроили файл Info.plist так, чтобы CFBundleExecutable указывал на сценарий запуска, а не на предыдущий исполняемый файл.
Хорошо, теперь все готово. К счастью, когда вы знаете все это, вы закапываете это в скрипт сборки.
источник
На самом деле я нашел очень удобный инструмент, заслуживающий похвалы ... НЕТ - я не разрабатывал его;)
https://github.com/auriamg/macdylibbundler/
Он разрешит все зависимости и «исправит» ваш исполняемый файл, а также ваши файлы dylib для бесперебойной работы в вашем пакете приложений.
... он также проверит зависимости ваших зависимых динамических библиотек: D
источник
Я использую это в моем Makefile ... Он создает пакет приложений. Прочтите его и поймите, потому что вам понадобится файл значка png в папке macosx / вместе с файлами PkgInfo и Info.plist, которые я включаю здесь ...
"это работает на моем компьютере" ... Я использую это для нескольких приложений на Mavericks ...
APPNAME=MyApp APPBUNDLE=$(APPNAME).app APPBUNDLECONTENTS=$(APPBUNDLE)/Contents APPBUNDLEEXE=$(APPBUNDLECONTENTS)/MacOS APPBUNDLERESOURCES=$(APPBUNDLECONTENTS)/Resources APPBUNDLEICON=$(APPBUNDLECONTENTS)/Resources appbundle: macosx/$(APPNAME).icns rm -rf $(APPBUNDLE) mkdir $(APPBUNDLE) mkdir $(APPBUNDLE)/Contents mkdir $(APPBUNDLE)/Contents/MacOS mkdir $(APPBUNDLE)/Contents/Resources cp macosx/Info.plist $(APPBUNDLECONTENTS)/ cp macosx/PkgInfo $(APPBUNDLECONTENTS)/ cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/ cp $(OUTFILE) $(APPBUNDLEEXE)/$(APPNAME) macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png rm -rf macosx/$(APPNAME).iconset mkdir macosx/$(APPNAME).iconset sips -z 16 16 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16@2x.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32.png sips -z 64 64 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32@2x.png sips -z 128 128 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128@2x.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256@2x.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/icon_512x512@2x.png iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset rm -r macosx/$(APPNAME).iconset
Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>MyApp</string> <key>CFBundleGetInfoString</key> <string>0.48.2, Copyright 2013 my company</string> <key>CFBundleIconFile</key> <string>MyApp.icns</string> <key>CFBundleIdentifier</key> <string>com.mycompany.MyApp</string> <key>CFBundleDocumentTypes</key> <array> </array> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>0.48.2</string> <key>CFBundleSignature</key> <string>MyAp</string> <key>CFBundleVersion</key> <string>0.48.2</string> <key>NSHumanReadableCopyright</key> <string>Copyright 2013 my company.</string> <key>LSMinimumSystemVersion</key> <string>10.3</string> </dict> </plist>
PkgInfo
источник
Самое простое решение: создать один раз проект Xcode, ничего не меняя (т.е. сохранить простое однооконное приложение, которое Xcode создает для вас), построить его и скопировать созданный им пакет. Затем отредактируйте файлы (в частности, Info.plist) в соответствии с вашим контентом и поместите свой собственный двоичный файл в каталог Contents / MacOS /.
источник
Есть несколько инструментов с открытым исходным кодом, которые помогают создавать пакеты приложений с зависимыми библиотеками для определенных сред, например py2app для приложений на основе Python. Если вы не найдете более общего, возможно, вы сможете адаптировать его к своим потребностям.
источник
Хотел бы я найти этот пост раньше ....
Вот мой схематичный способ решения этой проблемы с использованием
Run script
фазы, которая вызывается каждый раз, когда я создаюRelease
версию своего приложения:# this is an array of my dependencies' libraries paths # which will be iterated in order to find those dependencies using otool -L libpaths=("$NDNRTC_LIB_PATH" "$BOOST_LIB_PATH" "$NDNCHAT_LIB_PATH" "$NDNCPP_LIB_PATH" "/opt/local/lib") frameworksDir=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH executable=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH #echo "libpaths $libpaths" bRecursion=0 lRecursion=0 # this function iterates through libpaths array # and checks binary with "otool -L" command for containment # of dependency which has "libpath" path # if such dependency has been found, it will be copied to Frameworks # folder and binary will be fixed with "install_name_tool -change" command # to point to Frameworks/<dependency> library # then, dependency is checked recursively with resolveDependencies function function resolveDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$((lRecursion*20)) printf "%s :\t%s\n" $prefix "resolving $binname..." for path in ${libpaths[@]}; do local temp=$path #echo "check lib path $path" local pattern="$path/([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname=${BASH_REMATCH[1]} otool -L ${binfile} #echo "found match $libname" printf "%s :\t%s\n" $prefix "fixing $libname..." local libpath="${path}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) local installLibPath="@rpath/$libname" #echo "install_name_tool -change $libpath $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libpath $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname dependency resolved." let lRecursion++ resolveDependencies "$frameworksDir/$libname" "$prefix>$libname" resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let lRecursion-- fi path=$temp done # while done # for printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } # resolveDependencies # for some reason, unlike other dependencies which maintain full path # in "otool -L" output, boost libraries do not - they just appear # as "libboost_xxxx.dylib" entries, without fully qualified path # thus, resolveDependencies can't be used and a designated function is needed # this function works pretty much in a similar way to resolveDependencies # but targets only dependencies starting with "libboost_", copies them # to the Frameworks folder and resolves them recursively function resolveBoostDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$(((bRecursion+lRecursion)*20)) printf "%s :\t%s\n" $prefix "resolving Boost for $(basename $binfile)..." local pattern="[[:space:]]libboost_([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname="libboost_${BASH_REMATCH[1]}" #echo "found match $libname" local libpath="${BOOST_LIB_PATH}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) installLibPath="@rpath/$libname" #echo "install_name_tool -change $libname $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libname $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname Boost dependency resolved." let bRecursion++ resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let bRecursion-- fi done # while printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } resolveDependencies $executable $(basename $executable) resolveBoostDependencies $executable $(basename $executable)
Надеюсь, это может быть кому-то полезно.
источник
Чтобы заставить меню работать на Mac с кодом wxWidget, можно просто:
Я согласен с тем, что набор приложений - это правильный способ создания программы на Mac. Это простой обходной путь для помощи во время отладки.
Изменить: это на Mac Catalina, wxWidgets 3.1.4 с g ++ 4.2.1 (ноябрь 2020 г.)
источник