Déploiement sur macOS
Les applications macOS sont généralement distribuées dans un .app package d'application. Pour faire fonctionner les projets .NET Core et Avalonia dans un package .app, un travail supplémentaire doit être effectué après que votre application a traversé le processus de publication.
Avec Avalonia, vous aurez une structure de dossier .app qui ressemble à ceci :
MyProgram.app
|
----Contents\
|
------_CodeSignature\ (stocke les informations de signature de code)
| |
| ------CodeResources
|
------MacOS\ (tous vos fichiers DLL, etc. -- la sortie de `dotnet publish`)
| |
| ---MyProgram
| |
| ---MyProgram.dll
| |
| ---Avalonia.dll
|
------Resources\
| |
| -----MyProgramIcon.icns (fichier d'icône)
|
------Info.plist (stocke des informations sur votre identifiant de package, version, etc.)
------embedded.provisionprofile (fichier contenant des informations de signature)
Pour plus d'informations sur Info.plist, consultez la documentation d'Apple ici.
Création du package d'application
Il existe plusieurs options pour créer la structure de fichiers/dossiers .app. Vous pouvez le faire sur n'importe quel système d'exploitation, car un fichier .app est simplement un ensemble de dossiers disposés dans un format spécifique et les outils ne sont pas spécifiques à un système d'exploitation. Cependant, si vous construisez sur Windows en dehors de WSL, l'exécutable peut ne pas avoir les bons attributs pour l'exécution sur macOS -- vous devrez peut-être exécuter chmod +x sur la sortie binaire publiée (la sortie générée par dotnet publish) depuis une machine Unix. C'est la sortie binaire qui se retrouve dans le dossier MyApp.app/Contents/MacOS/, et le nom doit correspondre à CFBundleExecutable.
La structure .app repose sur le fichier Info.plist étant correctement formaté et contenant les bonnes informations. Utilisez Xcode pour éditer Info.plist, il dispose de l'auto-complétion pour toutes les propriétés. Assurez-vous que :
- La valeur de
CFBundleExecutablecorrespond au nom binaire généré pardotnet publish-- typiquement, c'est le même que le nom de votre assemblage.dllsans.dll. CFBundleNameest défini sur le nom d'affichage de votre application. Si cela dépasse 15 caractères, définissez égalementCFBundleDisplayName.CFBundleIconFileest défini sur le nom de votre fichier d'icôneicns(y compris l'extension).CFBundleIdentifierest défini sur un identifiant unique, généralement au format reverse-DNS -- par exemplecom.myapp.macos.NSHighResolutionCapableest défini sur vrai (<true/>dans leInfo.plist).CFBundleVersionest défini sur la version de votre bundle, par exemple 1.4.2.CFBundleShortVersionStringest défini sur la chaîne visible par l'utilisateur pour la version de votre application, par exempleMajor.Minor.Patch.
Si vous avez besoin d'un enregistrement de protocole ou d'associations de fichiers, ouvrez les fichiers plist d'autres applications dans le dossier Applications et vérifiez leurs champs.
Exemple de protocole :
<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>
Exemple d'association de fichiers :
<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>
Plus de documentation sur les clés possibles de Info.plist est disponible ici.
Si à un moment donné, l'outil vous donne une erreur indiquant que votre fichier d'assets n'a pas de cible pour osx-64, ajoutez les identifiants d'exécution suivants en haut du <PropertyGroup> dans votre .csproj :
<RuntimeIdentifiers>osx-x64</RuntimeIdentifiers>
Ajoutez d'autres identifiants d'exécution si nécessaire. Chacun d'eux doit être séparé par un point-virgule (;).
Remarques sur la création de fichiers d'icônes
Ce type de fichier d'icône ne peut pas seulement être créé sur des appareils Apple, mais il est également possible de le faire sur des appareils Linux.
Vous pouvez trouver plus d'informations sur la façon d'y parvenir dans cet article de blog :
Création d'icônes macOS (icns) sur Linux
Remarques sur le fichier exécutable .app
Le fichier qui est réellement exécuté par macOS lors du démarrage de votre bundle .app n'aura pas l'extension standard .dll. Si le contenu de votre dossier de publication, qui va à l'intérieur du bundle .app, ne contient pas à la fois un MyApp (exécutable) et un MyApp.dll, les choses ne se génèrent probablement pas correctement, et macOS ne pourra probablement pas démarrer votre .app correctement.
Quelques changements récents dans la façon dont .NET Core est distribué et notarié sur macOS ont empêché la génération de l'exécutable MyApp (également appelé "hôte d'application" dans la documentation liée). Vous avez besoin que ce fichier soit généré pour que votre .app fonctionne correctement. Pour vous assurer que cela soit généré, faites l'une des choses suivantes :
- Ajoutez ce qui suit à votre fichier
.csproj:
<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>
- Ajoutez
-p:UseAppHost=trueà votre commandedotnet publish.
dotnet-bundle
dotnet-bundle n'est plus maintenu mais devrait encore fonctionner.
Il est recommandé de cibler net6-macos, qui gérera la génération de paquets.
dotnet-bundle est un paquet NuGet qui publie votre projet et crée ensuite le fichier .app pour vous.
Vous devrez d'abord ajouter le projet en tant que PackageReference dans votre projet. Ajoutez-le à votre projet via le gestionnaire de paquets NuGet ou en ajoutant la ligne suivante à votre fichier .csproj :
<PackageReference Include="Dotnet.Bundle" Version="*" />
Après cela, vous pouvez créer votre .app en exécutant ce qui suit dans la ligne de commande :
dotnet restore -r osx-x64
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -p:UseAppHost=true
Vous pouvez spécifier d'autres paramètres pour la commande dotnet msbuild. Par exemple, si vous souhaitez publier en mode release :
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -property:Configuration=Release -p:UseAppHost=true
ou si vous souhaitez spécifier un nom d'application différent :
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -p:CFBundleDisplayName=MyBestThingEver -p:UseAppHost=true
Au lieu de spécifier CFBundleDisplayName, etc., sur la ligne de commande, vous pouvez également les spécifier dans votre fichier de projet :
<PropertyGroup>
<CFBundleName>AppName</CFBundleName> <!-- Définit également le nom du fichier .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> <!-- Sera copié depuis le répertoire de sortie -->
<NSPrincipalClass>NSApplication</NSPrincipalClass>
<NSHighResolutionCapable>true</NSHighResolutionCapable>
</PropertyGroup>
Par défaut, dotnet-bundle mettra le fichier .app au même endroit que la sortie de publish : [répertoire du projet]/bin/{Configuration}/netcoreapp3.1/osx-x64/publish/MyBestThingEver.app.
Pour plus d'informations sur les paramètres que vous pouvez envoyer, consultez la documentation dotnet-bundle.
Si vous avez créé le .app sur Windows, assurez-vous d'exécuter chmod +x MyApp.app/Contents/MacOS/AppName depuis une machine Unix. Sinon, l'application ne démarrera pas sur macOS.
Manuel
Tout d'abord, publiez votre application (documentation dotnet publish) :
dotnet publish -r osx-x64 --configuration Release -p:UseAppHost=true
Créez votre fichier Info.plist, en ajoutant ou en modifiant les clés si nécessaire :
<?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>
Vous pouvez ensuite créer la structure de dossier de votre .app comme décrit en haut de cette page. Si vous souhaitez un script pour le faire pour vous, vous pouvez utiliser quelque chose comme ceci (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 doit pointer vers le répertoire de sortie de votre commande dotnet publish.
# Un exemple est /path/to/your/csproj/bin/Release/netcoreapp3.1/osx-x64/publish/.
# Si vous souhaitez changer les répertoires de sortie, ajoutez `--output /my/directory/path` à votre commande `dotnet publish`.
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"
Si vous avez créé le .app sur Windows, assurez-vous d'exécuter chmod +x MyApp.app/Contents/MacOS/AppName depuis une machine Unix. Sinon, l'application ne démarrera pas sur macOS.
Signature de votre application
Une fois que vous avez créé votre fichier .app, vous voudrez probablement signer votre application afin qu'elle puisse être notariée et distribuée à vos utilisateurs sans que Gatekeeper ne vous pose de problème. La notarisation est requise pour les applications distribuées en dehors de l'App Store à partir de macOS 10.15 (Catalina), et vous devrez activer le runtime renforcé et exécuter codesign sur votre .app afin de le notariser avec succès.
Vous aurez besoin d'un ordinateur Mac pour cette étape, malheureusement, car nous devons exécuter l'outil en ligne de commande codesign qui est fourni avec Xcode.
Exécution de codesign et activation du runtime renforcé
L'activation du runtime renforcé se fait dans la même étape que la signature du code. Vous devez signer tout dans le bundle .app sous le dossier Contents/MacOS, ce qui est plus facile à faire avec un script étant donné qu'il y a beaucoup de fichiers. Pour signer vos fichiers, vous avez besoin d'un compte développeur Apple. Pour notariser votre application, vous devrez suivre les étapes suivantes avec un certificat d'identité de développeur, ce qui nécessite un abonnement payant au programme développeur Apple.
Vous devrez également avoir les outils de ligne de commande Xcode installés. Vous pouvez les obtenir en installant Xcode et en l'exécutant ou en exécutant xcode-select --install dans la ligne de commande et en suivant les instructions pour installer les outils.
Tout d'abord, activez le runtime renforcé avec des exceptions en créant un fichier 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>
Ensuite, exécutez ce script pour effectuer toute la signature de code pour vous :
#!/bin/bash
APP_NAME="/chemin/vers/votre/sortie/MyApp.app"
ENTITLEMENTS="/chemin/vers/votre/MyAppEntitlements.entitlements"
SIGNING_IDENTITY="Identité de développeur : MonNomDeSociété" # correspond au nom du certificat dans le Trousseau d'accès
find "$APP_NAME/Contents/MacOS/"|while read fname; do
if [[ -f $fname ]]; then
echo "[INFO] Signature de $fname"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$fname"
fi
done
echo "[INFO] Signature du fichier app"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$APP_NAME"
La partie --options=runtime de la ligne codesign est ce qui active le runtime renforcé avec votre application. Parce que .NET Core peut ne pas être entièrement compatible avec le runtime renforcé, nous ajoutons quelques exceptions pour utiliser du code compilé JIT et permettre l'envoi d'événements Apple. L'exception pour le code compilé JIT est nécessaire pour exécuter des applications Avalonia sous un runtime renforcé. Nous ajoutons la deuxième exception pour les événements Apple afin de corriger une erreur qui apparaît dans Console.app.
Remarque : Microsoft liste d'autres exceptions de runtime durcies comme étant requises pour .NET Core. La seule dont vous avez réellement besoin pour exécuter une application Avalonia est com.apple.security.cs.allow-jit. Les autres peuvent imposer des risques de sécurité à votre application. Utilisez avec prudence.
Une fois que votre application est signée, vous pouvez vérifier qu'elle est correctement signée en vous assurant que la commande suivante ne renvoie aucune erreur :
codesign --verify --verbose /path/to/MyApp.app
Notarisation de votre logiciel
La notarisation permet à votre application d'être distribuée en dehors du Mac App Store. Vous pouvez en lire plus ici. Si vous rencontrez des problèmes pendant le processus, Apple a un document utile sur les solutions potentielles ici.
Pour plus d'informations sur la personnalisation de votre flux de travail de notarisation et d'autres options que vous pourriez avoir besoin d'envoyer lors de l'exécution de xcrun altool, consultez la documentation d'Apple.
Les étapes suivantes ont été modifiées à partir de ce post StackOverflow :
- Assurez-vous que votre
.appest correctement signé. - Mettez votre
.appdans un fichier.zip, par exempleMyApp.zip. Notez que l'utilisation dezipfera échouer la notarisation, utilisez plutôtdittocomme ceci :ditto -c -k --sequesterRsrc --keepParent MyApp.app MyApp.zip. - Exécutez
xcrun altool --notarize-app -f MyApp.zip --primary-bundle-id com.unique-identifier-for-this-upload -u nom_utilisateur -p mot_de_passe. Vous pouvez utiliser un mot de passe dans votre trousseau en passant-p "@keychain:AC_PASSWORD", où AC_PASSWORD est la clé. Le compte doit être enregistré en tant que développeur Apple. - Si le téléchargement réussit, vous recevrez un UUID pour votre jeton de demande comme ceci :
28fad4c5-68b3-4dbf-a0d4-fbde8e6a078f. - Vous pouvez vérifier l'état de la notarisation en utilisant ce jeton comme ceci :
xcrun altool --notarization-info 28fad4c5-68b3-4dbf-a0d4-fbde8e6a078f -u nom_utilisateur -p mot_de_passe. Cela peut prendre du temps -- finalement, cela réussira ou échouera. - Si cela réussit, vous devez coller la notarisation à l'application :
xcrun stapler staple MyApp.app. Vous pouvez valider cela en exécutantxcrun stapler validate MyApp.app.
Une fois la notarisation terminée, vous devriez pouvoir distribuer votre application !
Si vous distribuez votre application dans un .dmg, vous voudrez modifier légèrement les étapes :
- Notarisez votre
.appcomme d'habitude (dans un fichier.zip). - Ajoutez votre application notarisée et collée (
xcrun stapler) dans le DMG (le DMG contient maintenant le fichier.appnotarisé/collé à l'intérieur). - Notarisez votre fichier
.dmg(même commande de basexcrun altool, juste avec le fichier.dmgpour le drapeau-fau lieu du.zip) - Collez la notarisation au fichier
.dmg:xcrun stapler staple MyApp.dmg
Emballage pour l'App Store
Vous avez besoin de beaucoup de choses :
- Votre application respecte les Directives de révision de l'App Store.
- Votre application respecte les Directives d'interface humaine de macOS.
- Compte développeur Apple, avec votre identifiant Apple connecté.
- Votre application est enregistrée dans App Store Connect.
- Application Transporter installée depuis l'App Store.
- Dernière version de Xcode installée avec votre identifiant Apple autorisé.
- Deux certificats :
Installateur de développeur Mac tierspour signer le fichier.pkgetApplication de développeur Mac tierspour signer un bundle. - Profil de provisionnement de l'App Store - obtenez-le pour votre application ici.
- Deux droits : un pour signer le
.appet l'autre pour signer les helpers d'application. - Le contenu de votre application est emballé correctement.
- Votre bundle est signé correctement.
- Vos fichiers
.dylibne contiennent aucune architecture non-ARM/x64. Vous pouvez les supprimer en utilisant l'outil en ligne de commandelipo. - Votre application est prête à être lancée depuis l'intérieur d'un sandbox.
Obtention de certificats
- Allez dans Xcode > Préférences > Compte > Gérer les certificats...
- Ajoutez-les s'ils n'existent pas.
- Exportez-les avec un mot de passe.
- Ouvrez-les et importez-les dans le Trousseau d'accès.
- Dans le Trousseau, vous devriez voir ces certificats
Installateur de développeur Mac tiersetDistribution Apple. Si les noms des certificats commencent par d'autres chaînes, vous avez créé un certificat incorrect. Réessayez. - Développez les clés importées dans le Trousseau et double-cliquez sur une clé privée à l'intérieur.
- Allez dans l'onglet Contrôle d'accès.
- Sélectionnez
Autoriser toutes les applications à accéder à cet élémentsi vous ne souhaitez pas entrer un mot de passe de profil Mac pour chaque signature de fichier.
Sandbox et bundle
L'App Store exige que l'application soit lancée dans un sandbox. Cela signifie que l'application n'aura pas accès à quoi que ce soit et ne pourra pas nuire au PC de l'utilisateur.
Votre application doit être prête pour cela et ne pas se bloquer si un dossier est protégé en lecture/écriture.
Les applications .NET 6 ne se bloqueront pas dans un sandbox uniquement si elles sont publiées avec l'option de fichier unique activée. Exemple :
dotnet publish src/MyApp.csproj -c Release -f net6.0 -r osx-x64 --self-contained true -p:PublishSingleFile=true
Le contenu de votre application doit être correctement emballé. Voici un article d'Apple avec beaucoup d'infos utiles.
Les règles les plus importantes de l'article :
- Les fichiers
.dllne sont pas considérés comme du code par Apple. Ils doivent donc être placés dans le dossier/Resourceset peuvent ne pas être signés. - Les fichiers
/MacOSne doivent contenir que des exécutables mach-o - l'exécutable de votre application et tout autre exécutable auxiliaire. - Tous les autres fichiers mach-o
.dylibdoivent être dans le dossierFrameworks/.
Pour satisfaire cette exigence sans trop de douleur, vous pouvez utiliser des liens symboliques relatifs depuis le dossier MacOS/ vers les dossiers Resources/ et Frameworks/. Par exemple :
ln -s fromFile toFile
Il est également préférable de réécrire le schéma d'accès aux ressources de votre application pour accéder directement au dossier Resources/ sans utiliser de liens symboliques, car vous pourriez rencontrer des problèmes d'accès I/O en sandbox.
Droits et signature de sandbox
Vous devez lire toute la documentation sur les droits et choisir ceux dont votre application a besoin.
D'abord, pour le fichier des droits, il faut signer tous les exécutables auxiliaires dans le dossier .app/Content/MacOS/. Cela devrait ressembler à ceci :
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
Ensuite, il faut signer l'exécutable de l'application et l'ensemble du bundle de l'application. Cela devrait contenir toutes les permissions de l'application. Voici un exemple :
<?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.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.coreservices.launchservicesd</string>
</array>
</dict>
</plist>
Voici quelques permissions optionnelles dont votre application pourrait avoir besoin :
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.document-scope</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>[Your Team ID].[Your App ID]</string>
</array>
Script de packaging
Voici un exemple de script de packaging avec des commentaires
#Nettoyer les dossiers
rm -rf "App/AppName.app/Contents/MacOS/"
rm -rf "App/AppName.app/Contents/CodeResources"
rm -rf "App/AppName.app/Contents/_CodeSignature"
rm -rf "App/AppName.app/Contents/embedded.provisionprofile"
mkdir -p "App/AppName.app/Contents/Frameworks/"
mkdir -p "App/AppName.app/Contents/MacOS/"
#Construire l'application
dotnet publish ../../ProjectFolder/AppName.csproj -c release -f net5.0 -r osx-x64 --self-contained true -p:PublishSingleFile=true
#Déplacer l'application
cd ..
cd ..
cp -R -f ProjectFolder/bin/release/net5.0/osx-x64/publish/* "build/osx/App/AppName.app/Contents/MacOS/"
cd "build/osx/"
APP_ENTITLEMENTS="AppEntitlements.entitlements"
APP_SIGNING_IDENTITY="Application de développeur Mac tiers : [***]"
INSTALLER_SIGNING_IDENTITY="Installateur de développeur Mac tiers : [***]"
APP_NAME="App/AppName.app"
#<ici, déplacer les ressources de votre application vers le dossier Resources à l'aide de liens symboliques relatifs>
#<ici, déplacer vos fichiers .dylib vers le dossier Frameworks à l'aide de liens symboliques relatifs>
echo "[INFO] Changer le profil de provisionnement pour AppStore"
\cp -R -f AppNameAppStore.provisionprofile "App/AppName.app/Contents/embedded.provisionprofile"
echo "[INFO] Corriger les architectures de libuv.dylib"
lipo -remove i386 "App/AppName.app/Contents/Frameworks/libuv.dylib" "App/AppName.app/Contents/Frameworks/libuv.dylib"
find "$APP_NAME/Contents/Frameworks/"|while read fname; do
if [[ -f $fname ]]; then
echo "[INFO] Signature de $fname"
codesign --force --sign "$APP_SIGNING_IDENTITY" "$fname"
fi
done
echo "[INFO] Signature de l'exécutable de l'application"
codesign --force --entitlements "$FILE_ENTITLEMENTS" --sign "$APP_SIGNING_IDENTITY" "App/AppName.app/Contents/MacOS/AppName"
echo "[INFO] Signature du bundle de l'application"
codesign --force --entitlements "$APP_ENTITLEMENTS" --sign "$APP_SIGNING_IDENTITY" "$APP_NAME"
echo "[INFO] Création de AppName.pkg"
productbuild --component App/AppName.app /Applications --sign "$INSTALLER_SIGNING_IDENTITY" AppName.pkg
Tester un paquet
Copiez votre .app dans le dossier Applications et lancez-le. S'il se lance correctement - vous avez tout fait correctement. S'il plante - ouvrez l'application Console et vérifiez le rapport de plantage.
Téléchargement d'un paquet sur l'App Store
Ouvrez l'application Transporter, connectez-vous, sélectionnez votre paquet *.pkg et attendez la validation et le téléchargement sur l'App Store.
Si vous recevez des erreurs - corrigez-les, regroupez à nouveau l'application, supprimez le fichier dans Transporter et sélectionnez-le à nouveau.
Lorsque le téléchargement réussit - vous verrez votre paquet dans App Store Connect.
Dépannage
L'élément de menu de l'application affiche À propos d'Avalonia
Cela signifie que votre application ne spécifie probablement pas de menu. Au démarrage, Avalonia crée les éléments de menu par défaut pour une application et ajoute automatiquement l'élément À propos d'Avalonia lorsque aucun menu n'a été configuré. Cela peut être résolu en en ajoutant un à votre App.xaml :
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RoadCaptain.App.RouteBuilder"
x:Class="RoadCaptain.App.RouteBuilder.App">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="About MyApp" Click="AboutMenuItem_OnClick" />
</NativeMenu>
</NativeMenu.Menu>
</Application>
Le reste des éléments de menu par défaut de macOS sera toujours généré par Avalonia.
Le nom de l'application dans la barre de menu ne correspond pas
Lorsque vous exécutez une application à partir d'un bundle, le nom de l'application affiché dans la barre de menu est tiré du Info.plist dans le bundle au lieu de la propriété Name dans App.xaml.
Si les noms ne correspondent pas, vérifiez que les valeurs pour CFBundleName, CFBundleDisplayName et la propriété Name sont identiques.
Notez que CFBundleName est limité à 15 caractères, si le nom de votre application est plus long, vous devez définir CFBundleDisplayName.
Packaging dans le workflow GitHub Actions
Construire l'application dans un pipeline CI/CD est simple en utilisant la commande dotnet. Pour que la signature du code et la notarisation fonctionnent, un peu de travail supplémentaire est nécessaire.
codesign et notarytool lisent le certificat et les identifiants pour communiquer avec le service de notarisation à partir d'un trousseau sur la machine de construction :
# Créer un nouveau trousseau
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
# Le définir comme le trousseau par défaut
security default-keychain -s build.keychain
# Déverrouiller le trousseau afin qu'il puisse être utilisé sans demande d'autorisation
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
KEYCHAIN_PASSWORD est un mot de passe que vous générez spécifiquement pour ce trousseau. Il peut être généré à chaque construction ou un mot de passe que vous utilisez pour chaque construction.
Ensuite, le certificat à signer doit être importé dans le trousseau. Comme les secrets GitHub ne prennent en charge que les chaînes, le fichier de certificat .p12 doit être stocké sous forme encodée en base64. Dans le pipeline, la chaîne est décodée en un fichier et ajoutée au trousseau :
# Décoder le certificat en fichier
echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12
# Importer dans le trousseau
security import certificate.p12 -k build.keychain -P "${{ secrets.MACOS_CERTIFICATE_PWD}}" -T /usr/bin/codesign
MACOS_CERTIFICATE est le fichier .p12 encodé en base64, MACOS_CERTIFICATE_PWD est le mot de passe du fichier .p12.
Pour éviter les fenêtres de demande d'autorisation lors de la signature de code, demandez au trousseau de permettre l'accès à codesign :
# Autoriser codesign à accéder au trousseau
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
Comme Apple exige une authentification multi-facteurs (MFA) sur les comptes développeurs, notarytool utilise un mot de passe d'application dédié que vous pouvez générer sur le site des développeurs Apple. Nous allons ajouter le mot de passe d'application pour notarytool afin qu'il puisse être utilisé plus tard :
xcrun notarytool store-credentials "AC_PASSWORD" --apple-id "${{ secrets.APPLE_ID }}" --team-id ${{ env.TEAM_ID }} --password "${{ secrets.NOTARY_TOOL_PASSWORD }}"
TEAM_ID est l'identifiant de l'équipe dans App Store Connect, APPLE_ID est l'adresse e-mail de votre compte Apple, NOTARY_TOOL_PASSWORD est le mot de passe d'application que vous avez généré.
Pour utiliser ces étapes dans votre flux de travail GitHub Actions, ajoutez-les en tant qu'étape au travail qui construit votre application :
jobs:
build_osx:
runs_on: macos-11
env:
TEAM_ID: MY_TEAM_ID
steps:
- name: Setup Keychain
run: |
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12
security import certificate.p12 -k build.keychain -P "${{ secrets.MACOS_CERTIFICATE_PWD}}" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD}}" build.keychain
xcrun notarytool store-credentials "AC_PASSWORD" --apple-id "${{ secrets.APPLE_ID }}" --team-id ${{ env.TEAM_ID }} --password "${{ secrets.NOTARY_TOOL_PASSWORD }}"
Lorsqu'il est configuré de cette manière, vous n'aurez pas à spécifier un fichier de trousseau spécifique pour que codesign ou notarytool l'utilisent.
Les étapes suivantes consistent à publier l'application et à la signer. Commencez par ajouter cette variable d'environnement au travail :
env:
SIGNING_IDENTITY: thumbprint_of_certificate_added_to_keychain
Et ensuite, ajoutez ces étapes :
- name: Publish app
run: dotnet publish -c Release -r osx-x64 -o $RUNNER_TEMP/MyApp.app/Contents/MacOS MyApp.csproj
- name: Codesign app
run: |
find "$RUNNER_TEMP/MyApp.app/Contents/MacOS/"|while read fname; do
if [ -f "$fname" ]
then
echo "[INFO] Signing $fname"
codesign --force --timestamp --options=runtime --entitlements MyApp.entitlements --sign "${{ env.$SIGNING_IDENTITY }}" "$fname"
fi
done
codesign --force --timestamp --options=runtime --entitlements MyApp.entitlements --sign "${{ env.SIGNING_IDENTITY }}" "$RUNNER_TEMP/MyApp.app"
Note:
RUNNER_TEMPest une variable d'environnement fournie par GitHub Actions.
Après la signature du code, le bundle de l'application peut maintenant être notarié, en ajoutant cette étape au travail :
- name: Notarise app
run: |
ditto -c -k --sequesterRsrc --keepParent "$RUNNER_TEMP/MyApp.app" "$RUNNER_TEMP/MyApp.zip"
xcrun notarytool submit "$RUNNER_TEMP/MyApp.zip" --wait --keychain-profile "AC_PASSWORD"
xcrun stapler staple "$RUNNER_TEMP/MyApp.app"
Lorsque vous exécutez ce flux de travail, vous aurez un bundle d'application qui est signé et notarié, prêt à être empaqueté dans une image disque ou un installateur.
Pour vérifier que la signature du code a fonctionné, vous devrez d'abord le télécharger pour déclencher la fonctionnalité de quarantaine de macOS. Vous pouvez le faire en vous l'envoyant par e-mail ou en utilisant un service comme WeTransfer ou similaire.
Une fois que vous avez téléchargé le bundle de l'application et que vous souhaitez le démarrer, vous devriez voir la fenêtre contextuelle de macOS indiquant que l'application a été scannée et qu'aucun logiciel malveillant n'a été trouvé.