macOS 部署
macOS应用程序通常以 .app
应用程序包 的形式分发。要让 .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
的更多信息,请参阅苹果的文档。
创建应用程序包
有几个选项可用于创建 .app
文件/文件夹结构。您可以在任何操作系统上执行此操作,因为 .app
文件只是按照特定格式排列的一组文件夹,工具并不特定于一个操作系统。但是,如果您在 Windows 上构建(而不是在 WSL 中),可执行文件可能没有适合 macOS 上执行的正确属性 -- 您可能需要在来自 Unix 设备的已发布二进制输出(由 dotnet publish
生成的输出)上运行 chmod +x
。这是最终位于文件夹 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(在Info.plist
中是<true/>
)。CFBundleVersion
设置为捆绑包的版本,例如 1.4.2。CFBundleShortVersionString
设置为应用程序版本的用户可见字符串,例如Major.Minor.Patch
。
如果需要协议注册或文件关联,请打开 Applications 文件夹中其他应用程 序的 plist 文件并查看它们的字段。
示例协议:
<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
的目标,那么请在 .csproj
的顶层 <PropertyGroup>
中添加以下运行时标识符:
<RuntimeIdentifiers>osx-x64</RuntimeIdentifiers>
根据需要添加其他运行时标识符。每个标识符应以分号(;)分隔。
关于创建图标文件的说明
这种类型的图标文件不仅可以在 Apple 设备上创建,也可以在 Linux 设备上创建。
您可以在此博客文章中找到有关如何实现这一点的更多信息。
关于 .app
可执行文件的说明
实际上在 macOS 启动您的 .app
包时执行的文件将不会具有标准的 .dll
扩展名。如果您的发布文件夹内容(放在 .app
包
内)中没有 MyApp
(可执行文件)和 MyApp.dll
,可能没有正确生成东西,macOS 可能无法正确启动您的 .app
。
最近对 macOS 上的 .NET Core 分发和验签进行的一些更改导致未生成 MyApp
可执行文件(也称为链接文档中的 "app host")。**您需要此文件才能生成您的 .app
包。**为确保它被生成,可以执行以下操作之一:
- 将以下内容添加到您的
.csproj
文件中:
<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>
- 在
dotnet publish
命令中添加-p:UseAppHost=true
。
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
输出相同的位置:[project directory]/bin/{Configuration}/netcoreapp3.1/osx-x64/publish/MyBestThingEver.app
。
有关可以发送的其他参数的更多信息,请参见 dotnet-bundle 文档。
如果在 Windows 上创建了 .app
,请确保从 Unix 设备上运行 chmod +x MyApp.app/Contents/MacOS/AppName
。否则,应用程序将无法在 macOS 上启动。
手动方法
首先,发布您的应用程序(dotnet publish 文档):
dotnet publish -r osx-x64 --configuration Release -p:UseAppHost=true
创建您的 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="/path/to/your/output/MyApp.app"
PUBLISH_OUTPUT_DIRECTORY="/path/to/your/publish/output/netcoreapp3.1/osx-64/publish/."
# PUBLISH_OUTPUT_DIRECTORY should point to the output directory of your dotnet publish command.
# One example is /path/to/your/csproj/bin/Release/netcoreapp3.1/osx-x64/publish/.
# If you want to change output directories, add `--output /my/directory/path` to your `dotnet publish` command.
INFO_PLIST="/path/to/your/Info.plist"
ICON_FILE="/path/to/your/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"
如果您在Windows上创建了 .app
文件,请确保在Unix计算机上运行 chmod +x MyApp.app/Contents/MacOS/AppName
。否则,应用程序将无法 在 macOS 上启动。
签署您的应用程序
创建了 .app
文件之后,您可能希望对应用程序进行签署,以便进行公证,并无需Gatekeeper给您带来麻烦即可将其分发给用户。从macOS 10.15(Catalina)开始,分发到App Store之外的应用程序需要进行公证,您需要启用hardened runtime并在 .app
上运行 codesign
命令才能成功进行公证。
很遗憾,这一步需要使用Mac计算机,因为我们必须运行Xcode附带的 codesign
命令行工具。
运行 codesign 并启用 hardened runtime
启用 hardened runtime 是与代码签名的相同步骤。您必须对 Contents/MacOS
文件夹下的 .app
包中的所有内容进行代码签名,使用脚本进行签名最为简便,因为其中包含大量文件。为了签名您的文件,您需要一个Apple开发者帐户。为了对您的应用程序进行公证,您需要使用Developer ID证书执行以下步骤,这需要付费的Apple开发者订阅。
您还需要安装Xcode命令行工具。您可以通过安装Xcode并运行它,或者在命令行中运行 xcode-select --install
并按照提示安装工具来获取这些工具。
首先,通过创建 MyAppEntitlements.entitlements
文件来启用带有例外的Hardened Runtime:
<?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="/path/to/your/output/MyApp.app"
ENTITLEMENTS="/path/to/your/MyAppEntitlements.entitlements"
SIGNING_IDENTITY="Developer ID: MyCompanyName" # matches Keychain Access certificate name
find "$APP_NAME/Contents/MacOS/"|while read fname; do
if [[ -f $fname ]]; then
echo "[INFO] Signing $fname"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$fname"
fi
done
echo "[INFO] Signing app file"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$APP_NAME"
codesign
行中的 --options=runtime
部分是用于启用带有您的应用程序的 hardened runtime 的部分。因为 .NET Core 可能不完全兼容 hardened runtime,所以我们添加了一些例外情况来使用 JIT 编译的代码和允许发送 Apple Events。JIT 编译的代码例外情况是在 hardened runtime 下运行 Avalonia 应用程序所必需的。我们添加第二个例外情况是为了修复 Console.app 中出现的错误。
注意:Microsoft 列出了一些其他 hardened runtime 的例外情况,这些例外情况可能会对您的应用程序造成安全风险。请谨慎使用。
一旦您的应用程序被代码签名,您可以通过确保以下命令不输出任何错误来验证签名是否成功:
codesign --verify --verbose /path/to/MyApp.app
对软件进行公证
公证允许您的应用程序在 macOS App Store 之外进行分发。您可以在此处了解更多信息。如果在此过程中遇到任何问题,Apple 提供了一个有关潜在解决方案的有用文档 here。