Создание пакета приложений OSX



предположим, что я сделал приложение osX без использования Xcode. После компиляции с GCC я получаю исполняемый файл, который связан с несколькими другими библиотеками. Некоторые из этих библиотек могут быть снова динамически связаны с другими нестандартными системными библиотеками



существует ли какой-либо инструмент, который создает пакет приложений OSX, сначала создавая необходимые структуры каталогов, а затем рекурсивно копируя/проверяя / фиксируя ссылки, чтобы убедиться, что все динамические зависимости также находятся в приложении бандл?



Я думаю, я могу попробовать написать что-то вроде этого, но мне было интересно, если нечто подобное уже существует.

664   7  

7 ответов:

Существует два способа создания пакета приложений на MacOSX: простой и уродливый.

Самый простой способ-это просто использовать XCode. Сделанный.

Проблема в том, что иногда вы не можете.

В моем случае я создаю приложение, которое создает другие приложения. Я не могу предположить, что у пользователя установлен XCode. Я также использую MacPorts чтобы построить библиотеки, от которых зависит мое приложение. Мне нужно убедиться, что эти dylibs поставляются в комплекте с приложением, прежде чем распространять оно.

отказ от ответственности: Я совершенно не квалифицирован, чтобы написать этот пост, все в is был отблеск от Apple docs, выбирая между существующими приложениями и методом проб и ошибок. Это работает для меня, но, скорее всего, неправильно. Пожалуйста напишите Мне, если у вас есть какие-то корректировки.

Первое, что вы должны знать, это то, что пакет приложений-это просто каталог.
Рассмотрим структуру гипотетического foo.приложение.

foo.app/
    Contents/
        Info.plist
        MacOS/
            foo
        Resources/
            foo.icns

Информация.plist-это простой XML-файл. Вы можете редактировать его с помощью текстового редактора или редактора списка свойств, который поставляется в комплекте с XCode. (Он находится в каталоге/Developer/Applications/ Utilities/).

Ключевые вещи, которые вы должны включить являются:

CFBundleName - имя приложения.

CFBundleIcon - файл значка, предположительно находящийся в Contents/Resources dir. Используйте значок Композитор приложение для создания значка. (Он также находится в каталоге/разработчик/приложения/ утилиты / каталог) Вы можете просто перетащить png на его окно и должны автоматически генерировать MIP-уровни для вас.

CFBundleExecutable - имя исполняемого файла, который предположительно находится в папке Contents / MacOS/sub.

Есть еще много вариантов, те, которые перечислены выше, являются только самым минимальным. Вот некоторые документы Apple о информация.файл plist файл и структура пакета приложений.

Кроме того, Вот пример информации.файл 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>

В идеальном мире вы могли бы просто бросить свой исполняемый файл в Содержание / MacOS / dir и быть сделано. Однако, если ваше приложение имеет какие-либо нестандартных зависимостей dylib нужна, он не будет работать. Как Windows, MacOS поставляется с собственным особым видом DLL ад.

Если вы используете MacPorts для создания библиотек, с которыми вы связываетесь, местоположения dylibs будут жестко закодированы в вашем исполняемом файле. Если вы запустите приложение на машине, которая имеет dylibs в том же месте, он будет работать нормально. Однако большинство пользователей не будут их устанавливать; когда они дважды щелкнут ваше приложение, оно просто рухнет.

Прежде чем распространять исполняемый файл, вам нужно собрать все загружаемые им dylibs и скопировать их в пакет приложений. Вы также нужно отредактировать исполняемый файл так, чтобы он искал dylibs в правильном месте. то есть, куда вы их скопировали.

Ручное редактирование исполняемого файла звучит опасно, не так ли? К счастью, есть инструменты командной строки, чтобы помочь.

otool -L executable_name

Эта команда выведет список всех dylibs, от которых зависит ваше приложение. Если вы видите какие-либо из них, которые не находятся в папке System/Library или usr/lib, это те, которые вам нужно скопировать в пакет приложений. Скопируйте их в /Содержимое папки/Макосе/. Затем вам нужно будет отредактировать исполняемый файл, чтобы использовать новые 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.приложение в папке / Applcations, то текущий каталог при запуске приложения будет папка /Applications. Не то / приложения / фу.приложение / содержание / MacOS / как и следовало ожидать.

Вы можете изменить свое приложение, чтобы учесть это, или вы можете использовать этот волшебный маленький скрипт запуска, который изменит текущий каталог и запустит ваш приложение.

#!/bin/bash
cd "${0%/*}"
./foo

Убедитесь, что вы настроили информацию.файл plist, так что CFBundleExecutable указывает на сценарий запуска, а не на предыдущий исполняемый файл.

Хорошо, теперь все сделано. К счастью, как только вы узнаете все это, вы похороните его в сценарии сборки.

Я на самом деле нашел очень удобный инструмент, который заслуживает уважения ... Нет - я этого не развивал;)

https://github.com/auriamg/macdylibbundler/

он разрешит все зависимости и" исправит " ваш исполняемый файл, а также ваши файлы dylib для бесперебойной работы в вашем пакете приложений.

... он также будет проверять зависимости ваших зависимых динамических библиотек :D

Я использую это в моем Makefile... Он создает пакет приложений. Прочитайте его и поймите, потому что вам понадобится файл значка png в папке macosx/ вместе с PkgInfo и Info.plist файлы, которые я включаю здесь...

"Она работает на моем компьютере"... Я использую это для нескольких приложений на Маверикс...

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/[email protected]
    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/[email protected]
    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/[email protected]
    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/[email protected]
    sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png
    cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/[email protected]
    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>

PkgInfo

APPLMyAp

самое простое решение: создайте один раз проект Xcode, ничего не меняя (т. е. сохраните простое приложение с одним окном, которое Xcode создает для вас), создайте его и скопируйте пакет, который он создал для вас. Затем отредактируйте файлы (в частности, информацию.plist) в соответствии с вашим контентом и поместите свой собственный двоичный файл в каталог Contents/MacOS/.

в этом пакете действительно нет ничего волшебного-просто прочитайте документацию Apple по нему и имитируйте ее. На базе, вам нужна информация.plist, содержимое / MacOS / binary и значок.

существуют некоторые инструменты с открытым исходным кодом, которые помогают создавать пакеты приложений с зависимыми библиотеками для конкретных сред, например,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=
    local prefix=
    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=
    local prefix=
    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)

надеюсь, что это может быть полезным кому-то.

Comments

    Ничего не найдено.