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

90

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

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

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

Йог
источник
4
Я не могу использовать Xcode по нескольким причинам. одна из которых связана с тем, что я использую собственный gcc. Xcode не позволяет мне указывать другой gcc. Я использую cmake для создания своих make-файлов.
Йоги,
>> Некоторые из этих библиотек могут быть снова динамически связаны с другими нестандартными системными библиотеками << Выбранный ответ не помогает в этом случае. Как вы ее решили?
FlowUI. SimpleUITesting.com

Ответы:

143

Есть два способа создать пакет приложений в MacOSX: Простой и Уродливый.

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

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

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

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

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

foo.app/
    Содержание /
        Info.plist
        MacOS /
            фу
        Ресурсы/
            foo.icns

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.

<? 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> Фу </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> ПРИЛОЖЕНИЕ </string>
  <key> IFMajorVersion </key>
  <integer> 0 </integer>
  <key> IFMinorVersion </key>
  <integer> 1 </integer>
</dict>
</plist>

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

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

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

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

otool -L имя_исполняемого_файла

Эта команда выведет список всех дилибов, от которых зависит ваше приложение. Если вы видите что-то, чего НЕТ в папке System / Library или usr / lib, это те, которые вам нужно скопировать в пакет приложений. Скопируйте их в папку / Contents / MacOS /. Затем вам нужно отредактировать исполняемый файл, чтобы использовать новые файлы dylib.

Во-первых, вам нужно убедиться, что вы устанавливаете ссылку с помощью флага -headerpad_max_install_names. Это просто гарантирует, что если новый путь dylib длиннее предыдущего, для него останется место.

Во-вторых, используйте install_name_tool для изменения каждого пути к dylib.

install_name_tool -change existing_path_to_dylib @ executable_path / blah.dylib имя_исполняемого_файла

В качестве практического примера предположим, что ваше приложение использует 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 указывал на сценарий запуска, а не на предыдущий исполняемый файл.

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

гиперлогический
источник
7
+1. Однако зомби-собака меня пугает. Не могли бы вы проиллюстрировать ад DLL менее пугающей картинкой? (Не говоря уже о том, что термин «ад DLL» не применяется. Предпочтительный метод распространения динамических библиотек - через фреймворки, которые позволяют избежать большинства проблем. Тем не менее, подход .dylib полезен для библиотек в стиле UNIX.)
Генрих Апфельмус,
1
Как исправить двойные зависимости? Как дилиб ссылается на другой дилиб?
FlowUI. SimpleUITesting.com
Нет ли способа скомпилировать исполняемый файл с правильным расположением, чтобы не редактировать его?
синхронизатор
Будет ли это работать даже на Каталине, учитывая, насколько строгими стали меры безопасности? (изменение дилибов, двоичных файлов и т. д.)
синхронизатор
20

На самом деле я нашел очень удобный инструмент, заслуживающий похвалы ... НЕТ - я не разрабатывал его;)

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

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

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

Крис
источник
это довольно полезный инструмент! Спасибо, что поделились и разработали его.
xpnimi
Здесь отсутствуют зависимости зависимостей. Исправить?
Питер
9

Я использую это в моем 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

APPLMyAp
субатомный
источник
Вы можете сделать Info.plist заполнителями текста, а затем использовать что-то вроде sed или awk для создания окончательного Info.plist с правильными полями. Или, может быть, даже используйте plutil для создания списка.
Марк Хит
8

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

F'x
источник
4
Вопрос явно спрашивает, как это сделать без xcode. Я нахожусь в одной лодке и хотел бы получить реальный ответ.
hyperlogic
5
Что ж, вам нужно использовать XCode только один раз в жизни :) или попросить кого-нибудь сделать это за вас.
F'x
@ F'x Можете ли вы просто опубликовать где-нибудь сгенерированный .app в качестве примера?
эндолиты
3

Есть несколько инструментов с открытым исходным кодом, которые помогают создавать пакеты приложений с зависимыми библиотеками для определенных сред, например py2app для приложений на основе Python. Если вы не найдете более общего, возможно, вы сможете адаптировать его к своим потребностям.

Нед Дейли
источник
2

Хотел бы я найти этот пост раньше ....

Вот мой схематичный способ решения этой проблемы с использованием 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)

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

Peetonn
источник
0

Чтобы заставить меню работать на Mac с кодом wxWidget, можно просто:

  1. Запустите программу в терминале как обычно (./appname)
  2. Графический интерфейс программы запускается как обычно, и щелкните терминал, чтобы приложение потеряло фокус.
  3. Щелкните графический интерфейс, чтобы восстановить фокус и элементы меню работают.

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

Изменить: это на Mac Catalina, wxWidgets 3.1.4 с g ++ 4.2.1 (ноябрь 2020 г.)

евгенедакин
источник