MacOS
macOS приложения обычно распространяются в формате .app
application bundle. Для работы .NET Core и Avalonia проектов в .app
пакете необходимо выполнить несколько дополнительных шагов после публикации приложения.
С Avalonia у вас будет структура папок .app
, которая выглядит следующим образом:
MyProgram.app
|
----Contents\
|
------_CodeSignature\ (содержит информацию о подписании кода)
| |
| ------CodeResources
|
------MacOS\ (все ваши DLL файлы и т.д. -- выходные данные `dotnet publish`)
| |
| ---MyProgram
| |
| ---MyProgram.dll
| |
| ---Avalonia.dll
|
------Resources\
| |
| -----MyProgramIcon.icns (файл значка)
|
------Info.plist (содержит информацию о вашем пакете, идентификаторе, версии и т.д.)
------embedded.provisionprofile (файл с информацией о подписании)
Для получения дополнительной информации о Info.plist
, смотрите документацию Apple.
Создание пакета приложения
Существует несколько вариантов создания структуры папок .app
. Вы можете сделать это на любой операционной системе, так как .app
файл - это просто набор папок, организованных в определенном формате, и инструменты не зависят от операционной системы. Однако, если вы создаете на Windows за пределами WSL, исполняемый файл может не иметь правильных атрибутов для выполнения на macOS -- возможно, вам придется выполнить chmod +x
для выходного бинарного файла (выходные данные, сгенерированные dotnet publish
) с Unix машины. Это бинарный выход, который попадает в папку MyApp.app/Contents/MacOS/
, и его имя должно соответствовать CFBundleExecutable
.
Структура .app
зависит от правильного форматирования и содержания файла Info.plist
. Используйте Xcode для редактирования Info.plist
, так как он имеет автозаполнение для всех свойств. Убедитесь, что:
- Значение
CFBundleExecutable
соответствует имени бинарного файла, сгенерированногоdotnet publish
-- обычно это то же имя, что и у вашего .dll файла, без.dll
. CFBundleName
установлено на отображаемое имя вашего приложения. Если это имя длиннее 15 символов, также установитеCFBundleDisplayName
.CFBundleIconFile
установлено на имя вашегоicns
файла значка (включая расширение).CFBundleIdentifier
установлено на уникальный идентификатор, обычно в формате обратного DNS -- например,com.myapp.macos
.NSHighResolutionCapable
установлено на true (<true/>
вInfo.plist
).CFBundleVersion
установлено на версию вашего пакета, например, 1.4.2.CFBundleShortVersionString
установлено на строку версии вашего приложения, видимую пользователю, например,Major.Minor.Patch
.
Если вам нужно зарегистрировать протокол или ассоциации файлов - откройте plist файлы других приложений в папке Applications и изучите их поля.
Пример протокола:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>AppName</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>i8-AppName</string>
</array>
</dict>
</array>
Пример ассоциации файлов
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Sketch</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>sketch</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>icon.icns</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
Дополнительная документация по возможным ключам Info.plist
доступна здесь.
Если в какой-то момент инструменты выдают ошибку о том, что ваш файл с активами не имеет цели для osx-64
, добавьте следующие идентификаторы среды выполнения в верхнюю <PropertyGroup>
в вашем .csproj
:
<RuntimeIdentifiers>osx-x64</RuntimeIdentifiers>
Добавьте другие идентификаторы среды выполнения по мере необходимости. Каждый идентификатор должен быть разделен точкой с запятой (;).
Примечания по созданию файлов значков
Этот тип значков можно создать не только на устройствах Apple, но также возможно на устройствах под управлением Linux.
Вы можете найти больше информации о том, как это сделать, в этом посте в блоге:
Creating Mac OS X Icons (icns) on Linux
Примечания по исполняемому файлу .app
Файл, который фактически исполняется macOS при запуске вашего .app
пакета, не будет иметь стандартное расширение .dll
. Если содержимое вашей папки публикации, которое попадает внутрь .app
пакета, не имеет как MyApp
(исполняемый файл), так и MyApp.dll
, вероятно, что-то не генерируется правильно, и macOS, вероятно, не сможет запустить ваш .app
правильно.
Недавние изменения в способе распространения и нотаризации .NET Core на macOS привели к тому, что исполняемый файл MyApp
(также называемый "app host" в связанной документации) не генерируется. Вам нужно, чтобы этот файл был сгенерирован для правильной работы вашего .app
. Чтобы убедиться, что он генерируется, выполните одно из следующих действий:
- Добавьте следующее в ваш
.csproj
файл:
<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>
- Добавьте
-p:UseAppHost=true
в вашу командуdotnet publish
.
dotnet-bundle
dotnet-bundle не поддерживается, но все же должен работать.
Рекомендуется использовать net6-macos
, который обработает создание пакета.
dotnet-bundle - это NuGet пакет, который публикует ваш проект, а затем создает .app
файл для вас.
Сначала добавьте проект как PackageReference
в ваш проект. Добавьте его через менеджер пакетов NuGet или добавив следующую строку в ваш .csproj
файл:
<PackageReference Include="Dotnet.Bundle" Version="*" />
После этого вы можете создать ваш .app
, выполнив следующую команду в командной строке:
dotnet restore -r osx-x64
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -p:UseAppHost=true
Вы можете указать другие параметры для команды dotnet msbuild
. Например, если вы хотите опубликовать в режиме release:
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -property:Configuration=Release -p:UseAppHost=true
или если вы хотите указать другое имя приложения:
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -p:CFBundleDisplayName=MyBestThingEver -p:UseAppHost=true
Вместо того чтобы указывать CFBundleDisplayName
и прочие параметры в командной строке, вы также можете указать их в вашем файле проекта:
<PropertyGroup>
<CFBundleName>AppName</CFBundleName> <!-- Определяет также имя файла .app -->
<CFBundleDisplayName>MyBestThingEver</CFBundleDisplayName>
<CFBundleIdentifier>com.example</CFBundleIdentifier>
<CFBundleVersion>1.0.0</CFBundleVersion>
<CFBundlePackageType>APPL</CFBundlePackageType>
<CFBundleSignature>????</CFBundleSignature>
<CFBundleExecutable>AppName</CFBundleExecutable>
<CFBundleIconFile>AppName.icns</CFBundleIconFile> <!-- Будет скопирован из выходного каталога -->
<NSPrincipalClass>NSApplication</NSPrincipalClass>
<NSHighResolutionCapable>true</NSHighResolutionCapable>
</PropertyGroup>
По умолчанию dotnet-bundle
поместит файл .app
в ту же папку, что и выходные данные publish
: [директория проекта]/bin/{Конфигурация}/netcoreapp3.1/osx-x64/publish/MyBestThingEver.app
.
Для получения дополнительной информации о параметрах, которые можно передать, смотрите документацию dotnet-bundle.
Если вы создали .app
на Windows, убедитесь, что выполнили chmod +x MyApp.app/Contents/MacOS/AppName
с Unix-машини. В противном случае приложение не запустится на macOS.
Ручной метод
Сначала опубликуйте ваше приложение (документация dotnet publish):
dotnet publish -r osx-x64 --configuration Release -p:UseAppHost=true
Here's the translated version of the detailed tutorial on creating and packaging a macOS application using Avalonia and .NET Core:
Создание Info.plist
файла, добавление или изменение ключей по необходимости:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIconFile</key>
<string>myicon-logo.icns</string>
<key>CFBundleIdentifier</key>
<string>com.identifier</string>
<key>CFBundleName</key>
<string>MyApp</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
<key>CFBundleExecutable</key>
<string>MyApp.Avalonia</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
Теперь можно создать структуру папки .app
, как указано в начале этой страницы. Если вам нужен скрипт для автоматизации, вы можете использовать что-то подобное (macOS/Unix):
#!/bin/bash
APP_NAME="/путь/к/вашему/выходному/MyApp.app"
PUBLISH_OUTPUT_DIRECTORY="/путь/к/вашему/публикационному/выходу/netcoreapp3.1/osx-64/publish/."
# PUBLISH_OUTPUT_DIRECTORY должен указывать на выходной каталог вашей команды dotnet publish.
# Один из примеров: /путь/к/вашему/csproj/bin/Release/netcoreapp3.1/osx-x64/publish/.
# Если вы хотите изменить выходные каталоги, добавьте `--output /мой/каталог/пути` в вашу команду `dotnet publish`.
INFO_PLIST="/путь/к/вашему/Info.plist"
ICON_FILE="/путь/к/вашему/myapp-logo.icns"
if [ -d "$APP_NAME" ]
then
rm -rf "$APP_NAME"
fi
mkdir "$APP_NAME"
mkdir "$APP_NAME/Contents"
mkdir "$APP_NAME/Contents/MacOS"
mkdir "$APP_NAME/Contents/Resources"
cp "$INFO_PLIST" "$APP_NAME/Contents/Info.plist"
cp "$ICON_FILE" "$APP_NAME/Contents/Resources/$ICON_FILE"
cp -a "$PUBLISH_OUTPUT_DIRECTORY" "$APP_NAME/Contents/MacOS"
Если вы создали .app
на Windows, убедитесь, что выполняете chmod +x MyApp.app/Contents/MacOS/AppName
из Unix-машины. В противном случае приложение не запустится в macOS.
Подписание вашего приложения
После создания файла .app
вам, вероятно, захочется подписать его, чтобы его можно было подтвердить и распространять пользователям без проблем с Gatekeeper. Нотаризация обязательна для приложений, распространяемых вне магазина приложений, начиная с macOS 10.15 (Catalina). Для успешной нотаризации вам нужно будет включить усиленный режим и выполнить codesign
для вашего .app
.
Для этого потребуется компьютер Mac, так как нам нужно будет использовать инструмент командной строки codesign
из Xcode.
Запуск codesign и включение усиленного режима
Включение усиленного режима производится в том же шаге, что и подписание кода. Вам нужно подписать все файлы в пакете .app
в папке Contents/MacOS
, что проще сделать с помощью скрипта, так как файлов там много. Для подписания ваших файлов вам понадобится учетная запись разработчика Apple. Для нотаризации вашего приложения вам нужно будет выполнить следующие шаги с сертификатом разработчика Apple, требующим платной подписки Apple.
Также вам нужно будет установить инструменты командной строки Xcode. Вы можете сделать это, установив Xcode и запустив его, или выполнить xcode-select --install
в командной строке и следовать подсказкам для установки инструментов.
Для начала включите усиленный режим с исключениями, создав файл MyAppEntitlements.entitlements
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
Затем выполните этот скрипт для всех операций подписи:
#!/bin/bash
APP_NAME="/путь/к/вашему/выходному/MyApp.app"
ENTITLEMENTS="/путь/к/вашему/MyAppEntitlements.entitlements"
SIGNING_IDENTITY="Developer ID: MyCompanyName" # соответствует названию сертификата в доступе к ключам Keychain
find "$APP_NAME/Contents/MacOS/"|while read fname; do
if [[ -f $fname ]]; then
echo "[INFO] Подписание $fname"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$fname"
fi
done
echo "[INFO] Подписание файла приложения"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$APP_NAME"
Часть --options=runtime
в строке codesign
активирует усиленный режим с вашим приложением. Поскольку .NET Core может быть не полностью совместим с усиленным режимом, мы добавляем некоторые исключения для использования компилированного JIT-кода и разрешения отправки событий Apple. Исключение для компилированного JIT-кода необходимо для запуска приложений Avalonia под усиленным режимом. Второе исключение для событий Apple используется для устранения ошибок, которые могут появляться в Console.app.
Примечание: Microsoft перечисляет некоторые другие исключения усиленного режима, которые требуются для .NET Core. Единственное исключение, действительно необходимое для запуска приложения Avalonia, - com.apple.security.cs.allow-jit
. Остальные могут представлять угрозу безопасности для вашего приложения. Используйте с осторожностью.
После подписания вашего приложения вы можете проверить, что оно подписано правильно, убедившись, что следующая команда не выводит ошибок:
codesign --verify --verbose /путь/к/MyApp.app