Создание пакета приложений для OSX
Вопрос
Предположим, я создал приложение для osX без использования Xcode.После компиляции с помощью GCC я получаю исполняемый файл, который связан с несколькими другими библиотеками.Некоторые из этих библиотек могут быть снова динамически связаны с другими нестандартными системными библиотеками
Существует ли какой-либо инструмент, который создает пакет приложений OSX, сначала создавая необходимые структуры каталогов, а затем рекурсивно копируя / проверяя / исправляя ссылки, чтобы убедиться, что все динамические зависимости также находятся в пакете приложений?
Я думаю, я могу попробовать написать что-то подобное, но мне было интересно, существует ли что-то подобное уже.
Решение
Есть два способа создать пакет приложений на MacOSX: Простой и некрасивый.
Самый простой способ - просто использовать XCode.Выполнено.
Проблема в том, что иногда вы не можете этого сделать.
В моем случае я создаю приложение, которое создает другие приложения.Я не могу предположить, что у пользователя установлен XCode.Я также использую Порты MacPorts для создания библиотек, от которых зависит мое приложение.Мне нужно убедиться, что эти файлы поставляются в комплекте с приложением, прежде чем я буду распространять его.
Отказ от ответственности: Я совершенно неквалифицирован для написания этого поста, все, что есть в is, было взято из документов Apple, с разбором существующих приложений и методом проб и ошибок.У меня это работает, но, скорее всего, неправильно.Пожалуйста, напишите мне, если у вас есть какие-либо исправления.
Первое, что вы должны знать, это то, что пакет приложений - это всего лишь каталог.
Давайте рассмотрим структуру гипотетического foo.app.
foo.app/ Contents/ Info.plist MacOS/ foo Resources/ foo.icns
Info.plist - это обычный XML-файл.Вы можете отредактировать его с помощью текстового редактора или приложения Property List Editor, которое поставляется в комплекте с XCode.(Он находится в каталоге /Developer/Applications/Utilities/).
Ключевыми вещами, которые вам нужно включить, являются:
Имя CFBundleName - Название приложения.
CFBundleIcon ( связка ) - Файл значка, который предположительно находится в каталоге Contents /Resources.Используйте приложение Icon Composer для создания значка.(Это также находится в каталоге /Developer/Applications / Utilities /) Вы можете просто перетащить png-файл в его окно, и он автоматически сгенерирует для вас уровни mip.
CFBundleExecutable CFBundleExecutable - Имя исполняемого файла, который предположительно находится в папке Contents/macOS/.
Есть еще много вариантов, те, что перечислены выше, - это лишь самый минимум.Вот некоторая документация Apple по Информация.plist файл и Структура пакета приложений.
Кроме того, Вот пример 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>CFBundleGetInfoString</key> <string>Foo</string> <key>CFBundleExecutable</key> <string>foo</string> <key>CFBundleIdentifier</key> <string>com.your-company-name.www</string> <key>CFBundleName</key> <string>foo</string> <key>CFBundleIconFile</key> <string>foo.icns</string> <key>CFBundleShortVersionString</key> <string>0.01</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>IFMajorVersion</key> <integer>0</integer> <key>IFMinorVersion</key> <integer>1</integer> </dict> </plist>
В идеальном мире вы могли бы просто поместить свой исполняемый файл в каталог Contents/macOS / и все готово.Однако, если в вашем приложении есть какие-либо нестандартные зависимости dylib, оно не будет работать.Как и Windows, macOS поставляется со своим особым видом DLL - Ад.
Если вы используете Порты MacPorts чтобы создавать библиотеки, на которые вы ссылаетесь, расположения dylibs будут жестко запрограммированы в вашем исполняемом файле.Если вы запустите приложение на компьютере, на котором dylibs находятся точно в том же месте, оно будет работать нормально.Однако у большинства пользователей они не будут установлены;когда они дважды щелкнут по вашему приложению, оно просто выйдет из строя.
Прежде чем распространять исполняемый файл, вам нужно будет собрать все файлы, которые он загружает, и скопировать их в пакет приложений.Вам также нужно будет отредактировать исполняемый файл, чтобы он искал файлы dylibs в нужном месте.т. е.куда вы их скопировали.
Ручное редактирование исполняемого файла звучит опасно, не так ли?К счастью, есть инструменты командной строки, которые могут помочь.
otool -L executable_name
Эта команда отобразит список всех библиотек, от которых зависит ваше приложение.Если вы видите что-либо, чего НЕТ в папке System / Library или usr / lib, это те, которые вам нужно скопировать в пакет приложений.Скопируйте их в папку /Contents/macOS/.Далее вам нужно будет отредактировать исполняемый файл, чтобы использовать новые файлы dylibs.
Во-первых, вам нужно убедиться, что вы ссылаетесь, используя флаг -headerpad_max_install_names.Это просто гарантирует, что если новый путь dylib длиннее предыдущего, то для него найдется место.
Во-вторых, используйте install_name_tool для изменения каждого пути к dylib.
install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name
В качестве практического примера предположим, что ваше приложение использует libSDL, и otool указывает его местоположение как "/opt/local/lib/libSDL-1.2.0.dylib".
Сначала скопируйте его в пакет приложений.
cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/
Затем отредактируйте исполняемый файл, чтобы использовать новое местоположение (ПРИМЕЧАНИЕ:убедитесь, что вы создали его с флагом -headerpad_max_install_names)
install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo
Фух, мы почти закончили.Теперь возникла небольшая проблема с текущим рабочим каталогом.
Когда вы запускаете свое приложение, текущим каталогом будет каталог выше, где расположено приложение.Например:Если вы поместите foo.app в папку /Applcations, то текущим каталогом при запуске приложения будет папка /Applications.Не /Applications/foo.app/Contents/macOS/, как вы могли бы ожидать.
Вы можете изменить свое приложение, чтобы учесть это, или вы можете использовать этот волшебный маленький скрипт запуска, который изменит текущий каталог и запустит ваше приложение.
#!/bin/bash cd "${0%/*}" ./foo
Убедитесь, что вы настроили файл Info.plist таким образом, чтобы CFBundleExecutable CFBundleExecutable указывает на сценарий запуска, а не на предыдущий исполняемый файл.
Хорошо, теперь все готово.К счастью, как только вы узнаете все это, вы закопаете это в сценарий сборки.
Другие советы
На самом деле я нашел очень удобный инструмент, заслуживающий некоторой похвалы...НЕТ - я это не разрабатывал ;)
https://github.com/auriamg/macdylibbundler/
Он разрешит все зависимости и «исправит» ваш исполняемый файл, а также файлы dylib для бесперебойной работы в вашем пакете приложений.
...он также проверит зависимости ваших зависимых динамических библиотек: D
Самое простое решение:создайте один раз проект Xcode, ничего не меняя (т.е.сохраните простое приложение с одним окном, созданное для вас Xcode), создайте его и скопируйте созданный им пакет.Затем отредактируйте файлы (особенно Info.plist) в соответствии с вашим содержимым и поместите свой собственный двоичный файл в каталог Contents/MacOS/.
Я использую это в своем 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
Инфо.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>
Информация о пакете
APPLMyAp
Существует несколько инструментов с открытым исходным кодом, которые помогают создавать пакеты приложений с зависимыми библиотеками для конкретных сред, например: 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)
Надеюсь, это может быть кому-то полезно.