Ein iOS-Projekt kann im Entwicklungsbranch problemlos kompilieren, während sich unbemerkt dynamische Frameworks, absolute Verweise auf Verzeichnisse des Entwicklungsrechners oder globale Symbole, die eigentlich nur intern verwendet werden sollten, im Archive wiederfinden. Durch eine Prüfung der Projektdateien lassen sich solche Änderungen kaum vollständig erfassen, denn ausgeliefert wird letztlich das fertig gelinkte Mach-O. Zuverlässiger ist es, die archivierten Artefakte direkt im Cloud-Mac-CI zu prüfen und Änderungen an Abhängigkeiten und Symbolen als kontrollpflichtige Diffs sichtbar zu machen.
Zuerst die zu blockierenden Änderungen definieren
Ein solches Gate sollte nicht bewerten, ob eine bestimmte Bibliothek „gut oder schlecht“ ist. Stattdessen sollte es drei überprüfbare Fragen beantworten:
- Wovon hängen die Hauptanwendung und die eingebetteten Frameworks tatsächlich ab?
- Sind die über
@rpathreferenzierten mitgelieferten Bibliotheken wirklich im App-Bundle vorhanden? - Gehören neu hinzugekommene globale Symbole zur vorgesehenen Schnittstelle?
Eine Baseline ist keine dauerhafte Positivliste, sondern eine manuell bestätigte Momentaufnahme des Artefakts. Nach einem Upgrade der Toolchain, einer Aktualisierung von Abhängigkeiten oder einer Schnittstellenänderung darf sie aktualisiert werden, muss aber gemeinsam mit der Codeänderung geprüft werden.
Die Prüfung sollte nach dem Release Archive erfolgen. Debug-Builds können Diagnosebibliotheken enthalten und andere Optimierungsergebnisse erzeugen. Daher bilden sie die endgültige Auslieferungsgrenze nicht zuverlässig ab. Bei der ersten Einführung empfiehlt es sich, nur Berichte zu erstellen und den Build noch nicht zu blockieren. Erst nachdem das Team den Inhalt der Baseline bestätigt hat, sollten Abweichungen zu einem Fehler führen.
Prüfobjekte aus dem Archive extrahieren
Erstelle zunächst ein separates Archive, damit keine älteren Artefakte vom Entwicklungsrechner wiederverwendet werden:
rm -rf out/App.xcarchive
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$PWD/out/App.xcarchive" \
clean archive
Wenn die Signierung in einem separaten nachgelagerten Job erfolgt, kann je nach Projekt CODE_SIGNING_ALLOWED=NO gesetzt werden. Diese Überschreibung sollte jedoch nicht pauschal in jedes Projekt übernommen werden. Bei Projekten mit bestimmten Entitlements oder benutzerdefinierten Build-Phasen muss zunächst geprüft werden, ob das unsignierte Archive vollständig ist.
Die Hauptanwendung befindet sich im Archive normalerweise unter Products/Applications. Der Prüfumfang sollte mindestens die ausführbare Hauptdatei der App und die ausführbare Datei jedes Frameworks im Verzeichnis Frameworks einschließen. Erweiterungs-Targets müssen ebenfalls separat berücksichtigt werden, da sie eigene Abhängigkeitsgrenzen besitzen.
| Objekt | Wichtigste Prüfungen | Typische Abweichung |
|---|---|---|
| App-Hauptprogramm | Systembibliotheken, mitgelieferte Frameworks, globale Symbole | Absoluter Pfad außerhalb des Systems |
| Dynamisches Framework | Transitive Abhängigkeiten, Installationsname, öffentliche Symbole | Abhängigkeit wird nicht im Bundle ausgeliefert |
| App Extension | Eigene Baseline für Abhängigkeiten und Symbole | Annahmen der Hauptanwendung werden fälschlich übernommen |
Abhängigkeits- und Symbolinventar erstellen
Das folgende Skript liest die Hauptanwendung und die eingebetteten Frameworks ein und gibt deren Abhängigkeiten und globale Symbole jeweils stabil sortiert aus. Die Sortierung verhindert, dass eine veränderte Ausgabereihenfolge der Werkzeuge bedeutungslose Diffs erzeugt.
set -euo pipefail
archive="${1:?archive path required}"
app=$(find "$archive/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)
test -n "$app"
mkdir -p audit/current
main=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist")
printf '%s
' "$app/$main" > audit/current/binaries.txt
if test -d "$app/Frameworks"; then
while IFS= read -r framework; do
executable=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$framework/Info.plist")
printf '%s
' "$framework/$executable"
done < <(find "$app/Frameworks" -maxdepth 1 -name '*.framework' -type d | sort)
fi >> audit/current/binaries.txt
: > audit/current/dependencies.txt
: > audit/current/symbols.txt
while IFS= read -r binary; do
name=${binary#"$app/"}
otool -L "$binary" |
sed '1d' |
awk -v n="$name" '{$1=$1; print n " " $1}' \
>> audit/current/dependencies.txt
xcrun nm -gU "$binary" |
awk -v n="$name" 'NF {print n " " $NF}' \
>> audit/current/symbols.txt
done < audit/current/binaries.txt
LC_ALL=C sort -u -o audit/current/dependencies.txt audit/current/dependencies.txt
LC_ALL=C sort -u -o audit/current/symbols.txt audit/current/symbols.txt
Beim Einchecken der Baseline sollten nur relative Objektnamen gespeichert werden, nicht das Arbeitsverzeichnis des Cloud Mac. Anschließend führt die Pipeline Folgendes aus:
diff -u audit/baseline/dependencies.txt audit/current/dependencies.txt
diff -u audit/baseline/symbols.txt audit/current/symbols.txt
Aus dem Bericht ein zuverlässiges Gate machen
Ein reiner Vergleich des gesamten Inhalts reagiert relativ empfindlich. Praxistauglicher ist es, zuerst feste Regeln anzuwenden und danach Änderungen gegenüber der Baseline zu prüfen. Abhängigkeiten, die mit /System/Library/Frameworks/, /usr/lib/ oder einem kontrollierten @rpath/ beginnen, sind in der Regel plausibel. Andere absolute Pfade sollten unmittelbar zum Fehlschlagen führen. Bei @rpath/Example.framework/Example muss das Skript außerdem prüfen, ob das zugehörige Framework tatsächlich im Bundle vorhanden ist. Ein korrekter Präfix allein darf nicht zur Freigabe führen.
Änderungen an Symbolen eignen sich dagegen für eine manuelle Prüfung. Nach dem Name Mangling können Swift-Namen sehr lang sein; das Risiko sollte daher nicht anhand der Zeichenlänge bewertet werden. Besondere Aufmerksamkeit verdienen plötzlich auftretende große Mengen globaler Symbole, Debug-Hilfsschnittstellen, Test-Einstiegspunkte sowie C- oder Objective-C-Symbole, die eigentlich verborgen sein sollten, aber modulübergreifend sichtbar sind.
Fehlalarme reduzieren
Lege die Xcode-Version und die Release-Konfiguration fest und stelle sicher, dass bei jedem Vergleich dasselbe scheme verwendet wird. Enthält das Projekt mehrere Erweiterungen, sollte für jede Binärdatei eine eigene Baseline gespeichert werden, statt alle Ergebnisse zu einer gemeinsamen Menge zusammenzufassen. Wenn ein Toolchain-Upgrade Änderungen an Systemsymbolen verursacht, ist die Baseline im zugehörigen Upgrade-Merge-Request zu aktualisieren. So bleibt die Ursache der Abweichungen auch später nachvollziehbar.
Fehler anhand des Artefakts zurückverfolgen
Wenn eine neue Abhängigkeit auftaucht, lässt sich zunächst mit otool -L feststellen, welche Binärdatei sie einführt. Danach sollten die Linker-Einstellungen, die bedingte Kompilierung und die Phase zum Einbetten von Abhängigkeiten des jeweiligen Targets geprüft werden. Fehlt ein @rpath-Ziel, ist insbesondere zu kontrollieren, ob das Framework zwar gelinkt, aber nicht in das Bundle kopiert wurde. Bei unerwarteten Symbolen kann das betreffende Objekt mit nm -gU lokalisiert werden. Anschließend sind Sichtbarkeitsdeklarationen, Bridging-Code und die Exportkonfiguration des Linkers zu prüfen.
Nach einem Fehler darf die Baseline nicht einfach überschrieben werden. Die richtige Reihenfolge lautet: Ursprung der Änderung feststellen, Auswirkungen auf die Auslieferungsgrenze bewerten, die Änderung korrigieren oder genehmigen und erst danach die Baseline neu erzeugen. Ein auf diese Weise eingerichtetes Gate ist nicht vom temporären Zustand eines bestimmten Cloud Mac abhängig und hinterlässt für jede Veröffentlichung eine überprüfbare Dokumentation der Binärgrenzen.
Häufig gestellte Fragen
Warum reicht die Abhängigkeitsliste des Projekts nicht aus?
Bedingte Builds, Linker-Optionen und Skripte können das fertige Archive verändern. Maßgeblich ist deshalb die tatsächlich erzeugte Mach-O-Datei.
Muss jede Änderung an exportierten Symbolen den Release stoppen?
Nein. Die Schranke erzwingt eine Prüfung. Nach einer bestätigten Werkzeugaktualisierung oder beabsichtigten API-Änderung darf die Baseline aktualisiert werden.
Findet eine Symbolliste eingebettete Zugangsdaten?
Nicht zuverlässig. Sie zeigt Schnittstellenänderungen, ersetzt aber keine separate Prüfung von Quelltext, Konfigurationen, Ressourcen und Build-Protokollen.
Dedizierter physischer Knoten
Entwicklungs- und Build-Aufgaben auf einem dedizierten Cloud-Mac ausführen
Wählen Sie Modell, Knotenregion und Mietdauer passend zur Aufgabe. Jede Instanz läuft auf einem dedizierten physischen Rechner und nicht in einer virtuellen Umgebung.