La maquette utilise une police personnalisée pour les titres et tout fonctionne parfaitement en local. Pourtant, une fois l’App issue de la compilation automatisée installée sur un appareil de test, la police système prend discrètement le relais. Ce type de problème ne fait généralement pas échouer la compilation : la police peut être absente de la cible, le chemin relatif indiqué dans Info.plist peut être incorrect, ou le code peut utiliser le nom du fichier au lieu du nom PostScript interne de la police. La méthode la plus fiable ne consiste pas à inspecter encore une fois le répertoire du projet, mais à auditer directement le paquet App final après sa compilation sur le Mac distant.
Définir les contrôles de la barrière de validation des polices
La barrière de validation des polices doit répondre à au moins quatre questions : les fichiers déclarés existent-ils, Core Text peut-il les analyser, leurs noms internes respectent-ils les conventions du code et plusieurs fichiers produisent-ils des noms en double ? Le contrôle doit porter sur le répertoire .app, et non sur le dossier Resources des sources, car l’appartenance aux cibles et l’étape Copy Bundle Resources peuvent toutes deux modifier le résultat final.
| Élément contrôlé | Signification de l’échec | Action recommandée |
|---|---|---|
Entrées UIAppFonts |
La configuration de compilation ne déclare pas les polices | Corriger la configuration Info de la cible |
| Fichiers dans le paquet App | Les polices n’ont pas été copiées dans l’artefact | Vérifier l’appartenance aux cibles et l’étape des ressources |
| Noms PostScript | Les noms utilisés par le code sont peut-être incorrects | Lire les noms réels depuis les descripteurs de police |
| Unicité des noms | Des familles ou versions de polices sont en conflit | Supprimer les fichiers en double ou définir explicitement leur remplacement |
Le fait qu’une police « ressemble à la bonne » dans l’interface ne constitue pas un critère de validation. Lorsqu’une police de secours est utilisée, l’écart peut être peu visible sur un court texte anglais, mais les différences de largeur des caractères affectent les retours à la ligne, la taille des boutons et la comparaison des captures d’écran.
Il est recommandé de maintenir un fichier ci/expected-fonts.txt dans le dépôt, avec un nom PostScript autorisé par ligne. Ce fichier constitue un contrat stable entre les appels effectués dans le code et les fichiers de ressources. Il ne doit pas répertorier toutes les polices installées sur l’ordinateur d’un développeur.
Extraire les informations du paquet App final
Commencez par générer le paquet App avec le processus de compilation existant, puis transmettez son chemin au script de contrôle. Les configurations Debug et Release peuvent utiliser des paramètres de ressources différents. Le pipeline de livraison doit donc vérifier la configuration réellement destinée à être distribuée. Le script ne doit pas enregistrer les polices auprès du système avant de les valider, car une police homonyme déjà installée sur la machine pourrait masquer son absence dans le paquet App.
Le script Swift suivant lit UIAppFonts dans Info.plist, vérifie l’existence de chaque fichier et extrait ses noms internes avec Core Text. Enregistrez-le sous ci/check_fonts.swift :
import Foundation
import CoreText
let arguments = CommandLine.arguments
guard arguments.count == 2 else {
FileHandle.standardError.write(Data("usage: check_fonts.swift /path/App.app
".utf8))
exit(64)
}
let appURL = URL(fileURLWithPath: arguments[1], isDirectory: true)
let plistURL = appURL.appendingPathComponent("Info.plist")
guard
let data = try? Data(contentsOf: plistURL),
let plist = try? PropertyListSerialization.propertyList(from: data) as? [String: Any],
let declared = plist["UIAppFonts"] as? [String],
!declared.isEmpty
else {
FileHandle.standardError.write(Data("UIAppFonts is missing or empty
".utf8))
exit(1)
}
var failed = false
var seen = Set<String>()
for relativePath in declared {
let url = appURL.appendingPathComponent(relativePath)
guard FileManager.default.fileExists(atPath: url.path) else {
FileHandle.standardError.write(Data("missing: \(relativePath)
".utf8))
failed = true
continue
}
let descriptors = CTFontManagerCreateFontDescriptorsFromURL(url as CFURL) as? [CTFontDescriptor] ?? []
if descriptors.isEmpty {
FileHandle.standardError.write(Data("unreadable: \(relativePath)
".utf8))
failed = true
}
for descriptor in descriptors {
let value = CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute)
guard let name = value as? String, !name.isEmpty else {
FileHandle.standardError.write(Data("unnamed: \(relativePath)
".utf8))
failed = true
continue
}
if !seen.insert(name).inserted {
FileHandle.standardError.write(Data("duplicate: \(name)
".utf8))
failed = true
}
print("\(name) \(relativePath)")
}
}
exit(failed ? 1 : 0)
Lors de l’exécution, utilisez le chemin réel de l’artefact compilé :
xcrun swift ci/check_fonts.swift "$APP_PATH" | sort > build/actual-fonts.txt
diff -u ci/expected-fonts.txt build/actual-fonts.txt
Si la liste ne contient que les noms, vous pouvez ajouter cut -f1 à la sortie. L’essentiel est que toute différence produise un code de sortie différent de zéro afin d’interrompre immédiatement le pipeline.
Figer les noms PostScript plutôt que les noms de fichiers
Headline-Bold.otf n’est que le nom du fichier sur le disque. Le nom interne de la police peut être HeadlinePro-Bold. Font.custom dans SwiftUI, l’initialisation des polices dans UIKit et le code de test doivent tous utiliser le nom interne. Renommer le fichier de police ne modifie pas automatiquement ce nom. En revanche, une mise à jour de la police par son fournisseur peut le faire évoluer.
Soumettre explicitement les changements de nom à une revue
Lorsque actual-fonts.txt diffère de la liste autorisée, le pipeline ne doit pas accepter automatiquement les nouvelles valeurs. Vérifiez d’abord que le changement provient bien d’une mise à niveau planifiée, puis recherchez tous les appels à cette police dans le projet. Les noms de polices peuvent être regroupés dans un fichier de constantes du code source afin d’éviter leur dispersion dans les vues et les fixtures de test.
Un même fichier de police peut également contenir plusieurs descripteurs. Le script doit conserver tous les noms au lieu de se limiter au premier. Si l’équipe utilise réellement des polices variables, ajoutez aussi une série de tests d’intégrité visuels pour les graisses courantes afin de vérifier que les réglages des axes restent conformes à la référence de conception.
Compléter le contrôle par des validations à l’exécution et dans l’interface
La barrière statique peut prouver que la structure des ressources est correcte, mais pas que chaque écran utilise la bonne graisse. Il est recommandé d’ajouter une page d’inventaire des polices réservée à la cible de test. Elle doit couvrir les titres, le corps du texte, les nombres, la ponctuation chinoise et les mots anglais longs, puis faire l’objet d’une validation par captures d’écran sur un simulateur aux dimensions fixes.
Les assertions à l’exécution doivent vérifier le fontName de l’instance de police, et pas seulement comparer la point size. Pour les polices dynamiques, les tests doivent également couvrir au moins deux catégories de taille de contenu afin de détecter les troncatures après agrandissement. Si l’interface autorise le recours à une police de secours lorsqu’une police manque, cette logique doit être explicitement consignée dans le code. Pour une police essentielle à l’identité de la marque, l’environnement de test doit au contraire déclencher directement un échec.
Lors de tests parallèles, chaque tâche doit utiliser ses propres répertoires de compilation et de résultats afin d’éviter que la liste autorisée produite par une branche n’écrase celle d’une autre. Les fichiers de polices sont des ressources d’entrée et ne doivent pas être modifiés sur place par les scripts de compilation.
Intégrer le contrôle à un pipeline reproductible
L’ordre complet doit être le suivant : nettoyer un répertoire de compilation isolé, compiler la configuration cible, localiser l’unique paquet App, exécuter le contrôle structurel des polices, comparer la liste autorisée, puis lancer les tests d’interface. N’utilisez pas une commande approximative telle que find | head -1 pour sélectionner l’artefact. Si plusieurs App se trouvent dans le même répertoire, elle risque de contrôler l’hôte de test ou une ancienne compilation.
En cas d’échec de la barrière, conservez actual-fonts.txt, la liste des fichiers de polices présents dans le paquet App et les extraits pertinents du journal de compilation, sans téléverser d’identifiants sans rapport avec le diagnostic. Commencez par rechercher les fichiers absents, examinez ensuite UIAppFonts, puis vérifiez les noms internes. Cet ordre permet de distinguer rapidement une police « non intégrée » d’un « nom d’appel incorrect ».
Lorsqu’une mise à niveau de police est approuvée, le fichier de police, la liste autorisée, les constantes centralisées contenant les noms et les captures d’écran de référence doivent être validés dans le même changement. Ainsi, l’annulation de n’importe quel commit rétablit un état complet et cohérent, sans rendre le résultat de la compilation sur le Mac distant dépendant de polices préinstallées sur le poste d’un développeur.
Questions fréquentes
Pourquoi vérifier le paquet App plutôt que le dossier du projet ?
La présence d’un fichier dans le dépôt ne prouve pas qu’il a été copié. Le paquet App permet de contrôler le résultat réel des phases de ressources et de UIAppFonts.
Le nom utilisé par une police SwiftUI correspond-il au nom du fichier ?
Pas nécessairement. Il faut généralement utiliser le nom PostScript interne, lu depuis le descripteur de police puis comparé à une liste attendue.
Où placer ce contrôle dans la CI ?
Après la production du paquet App installable et avant toute étape d’envoi ou de distribution.
Exécutez vos workflows Mac mini en continu dans le cloud
Choisissez RunAMac M4, la durée de location et le nœud de déploiement pour exécuter vos tâches de développement, de compilation ou d’expérimentation sur une machine physique dédiée.