Ein Unity-Projekt lässt sich lokal als iOS-Projekt exportieren, bleibt auf einem Cloud-Mac jedoch beim Asset-Import, bei nativen Abhängigkeiten oder beim Linken hängen. Das eigentliche Problem ist dabei weniger der Fehlschlag selbst als die Bündelung aller Schritte in einem einzigen Befehl: Im Protokoll steht nur „Build fehlgeschlagen“, ohne erkennen zu lassen, ob Unity erneut ausgeführt, der Cache geleert oder das Xcode-Projekt geprüft werden muss. Robuster ist eine Pipeline mit zwei unabhängigen Prüfstufen: Zuerst wird ein nachvollziehbares Xcode-Projekt erzeugt, anschließend folgt eine native Kompilierungsprüfung ohne Codesignierung.
Eingaben und Artefakte klar abgrenzen
Für einen reproduzierbaren Build müssen mindestens vier Arten von Eingaben festgeschrieben werden: Commit-Version, Unity-Editor-Version, Lockdatei der Paketabhängigkeiten und Szenenliste. Das Skript sollte weder kurzfristig Editor-Einstellungen vom Rechner eines Entwicklers einlesen noch das zuletzt exportierte Xcode-Projekt überschreiben und anschließend weiterkompilieren.
Für jeden Auftrag empfiehlt sich eine eigene Verzeichnisstruktur:
| Pfad | Zweck | Cachefähig |
|---|---|---|
Source/ |
Unity-Projekt des aktuellen Commits | Nein |
Library/ |
Ergebnisse des Asset-Imports | Bedingt |
Build/iOS/ |
Für diesen Auftrag exportiertes Xcode-Projekt | Nein |
.build/DerivedData/ |
Zwischendateien der nativen Kompilierung | Bedingt |
Artifacts/ |
Protokolle, Zusammenfassungen und Prüfergebnisse | Nein |
Der Cache-Schlüssel für Library sollte die Unity-Version, die Zielplattform sowie Prüfsummen von Packages/manifest.json und Packages/packages-lock.json enthalten. Ändert sich eine dieser Angaben, müssen die Assets erneut importiert werden. Auch DerivedData sollte nicht unverändert zwischen verschiedenen Xcode-Versionen oder unterschiedlichen Projekteinstellungen wiederverwendet werden.
Ein Cache kann nur fest definierte Eingaben beschleunigen; eine Versionsfixierung ersetzt er nicht. Ein Treffer in einem unklar abgegrenzten Cache ist meist schwieriger zu untersuchen als ein vollständiger Neuaufbau.
Xcode-Projekt im Batch-Modus exportieren
Der Export-Einstiegspunkt wird in Assets/Editor/IosExport.cs abgelegt. Die Szenenliste entsteht aus den in den Build Settings aktivierten Einträgen, damit nicht parallel je eine Liste im Skript und in der Editor-Oberfläche gepflegt werden muss.
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class IosExport
{
public static void Run()
{
var scenes = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => scene.path)
.ToArray();
if (scenes.Length == 0)
throw new InvalidOperationException("No enabled scenes.");
var options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = "Build/iOS",
target = BuildTarget.iOS,
options = BuildOptions.CleanBuildCache
};
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result != BuildResult.Succeeded)
throw new InvalidOperationException("Unity export failed.");
}
}
Beim Aufruf werden Projektverzeichnis, Protokollpfad und Methodenname explizit übergeben:
"$UNITY_EDITOR" \
-batchmode \
-nographics \
-quit \
-projectPath "$PWD" \
-executeMethod IosExport.Run \
-logFile "$PWD/Artifacts/unity-export.log"
Vor Beginn des Auftrags sollte Build/iOS gelöscht werden, Library jedoch nicht pauschal. Nach Abschluss müssen Exit-Code, Protokolldatei und Build/iOS/Unity-iPhone.xcodeproj gemeinsam geprüft werden. Ein vorhandenes Verzeichnis allein belegt keinen erfolgreichen Export, da auch fehlgeschlagene Aufträge unvollständige Artefakte hinterlassen können.
Native Abhängigkeiten und Kompilierungsprüfung trennen
Ein erfolgreicher Unity-Export bestätigt lediglich, dass der Generator seine Arbeit abgeschlossen hat. Er beweist nicht, dass Objective-C, Swift, IL2CPP-Artefakte und native Bibliotheken gemeinsam kompiliert werden können. Führen Sie zunächst den projektspezifischen Installationsschritt für native Abhängigkeiten aus und entscheiden Sie danach, ob der Workspace oder das Projekt gebaut werden muss. Hat das Abhängigkeitswerkzeug eine .xcworkspace erzeugt, führt das weitere Bauen der .xcodeproj häufig zu nicht gefundenen Modulen oder fehlenden Bibliotheken beim Linken.
set -euo pipefail
cd Build/iOS
rm -rf ../../.build/DerivedData
if [ -d "Unity-iPhone.xcworkspace" ]; then
xcodebuild \
-workspace Unity-iPhone.xcworkspace \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
else
xcodebuild \
-project Unity-iPhone.xcodeproj \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
fi
Ziel dieses Schritts ist die Prüfung von Kompilierung und Linker-Lauf, nicht die Erzeugung eines verteilbaren Installationspakets. Die Signierung gehört in eine nachgelagerte, kontrollierte Phase, damit gewöhnliche Commit-Prüfungen keinen Zugriff auf unnötige sensible Materialien benötigen.
Cache-Invalidierung statt Rätselraten
Ein häufiger Fehler bei Unity-Projekten besteht darin, ausschließlich den Branch-Namen als Cache-Schlüssel zu verwenden. Zwei Commits im selben Branch müssen jedoch nicht dieselben Asset-Import-Ergebnisse erzeugen. Zunächst lässt sich eine Prüfsumme der Eingaben erstellen:
{
printf '%s
' "$UNITY_VERSION"
printf '%s
' "$XCODE_VERSION"
shasum -a 256 Packages/manifest.json
shasum -a 256 Packages/packages-lock.json
find Assets -name '*.asmdef' -o -name '*.rsp' | sort | xargs shasum -a 256
} | shasum -a 256 | awk '{print $1}' > Artifacts/cache-key.txt
Nicht das gesamte Arbeitsverzeichnis cachen
Wird das komplette Projektverzeichnis als Cache gepackt, gelangen alte Exportartefakte, temporäre Einstellungen und bereits gelöschte Dateien hinein. Library und DerivedData sollten getrennt verwaltet werden; nach der Wiederherstellung ist die Prüfsumme der Eingaben weiterhin zu validieren. Treten nicht erklärbare Unterschiede bei der Kompilierung auf, sichern Sie zuerst die Fehlerprotokolle und wiederholen Sie den Build einmal mit leerem Cache. Ist dieser Durchlauf erfolgreich, liegt das Problem an der Cache-Abgrenzung und sollte nicht vorschnell durch Änderungen am Anwendungscode behoben werden.
Minimalen Beweissatz für spätere Analysen archivieren
Nach einem erfolgreichen Build genügt es nicht, nur eine Erfolgsmeldung aufzubewahren. Archivieren Sie mindestens das vollständige Unity-Protokoll, die Xcode-Ausgabe, den Commit-Hash, die Werkzeugversionen, den Cache-Schlüssel und eine Zusammenfassung des exportierten Projekts. Die Projektzusammenfassung kann Scheme, Konfiguration und Build-Einstellungen enthalten, sollte aber Anmeldedaten, Token und private lokale Pfade ausschließen.
Typische Fehler lassen sich anhand der jeweiligen Phase schnell einordnen:
- Das Unity-Protokoll erreicht
IosExport.Runnicht: Methodennamen, Fehler bei der Skriptkompilierung und Editor-Version prüfen. - Die Exportphase meldet eine leere Szenenliste: die in den Build Settings aktivierten Szenen prüfen, statt Pfade kurzfristig fest im Skript zu hinterlegen.
- Ein Workspace ist vorhanden, das Skript baut aber das Projekt: Prüfzeitpunkt anpassen und sicherstellen, dass die Installation der Abhängigkeiten abgeschlossen ist.
- IL2CPP oder eine native Bibliothek scheitert beim Linken: Zielplattform, Plug-in-Architektur und Einstellungen für den bedingten Import abgleichen.
- Derselbe Commit ist nur gelegentlich erfolgreich: Cache deaktivieren, erneut ausführen und die Cache-Schlüssel sowie Werkzeugversionen beider Durchläufe vergleichen.
- Der Build funktioniert lokal, aber nicht auf dem entfernten System: Groß- und Kleinschreibung in Pfaden, nicht eingecheckte Dateien und Umgebungsvariablen vergleichen, statt zunächst die Rechnerleistung verantwortlich zu machen.
Definieren Sie abschließend „Export erfolgreich“ und „native Kompilierung erfolgreich“ als zwei unabhängige Zustände. Schlägt der erste fehl, muss Xcode nicht gestartet werden. Schlägt der zweite fehl, müssen nicht wiederholt sämtliche Assets importiert werden. Dadurch lässt sich die Pipeline gezielter neu ausführen, die Protokolle bleiben kürzer und die Zuständigkeiten sind klarer abgegrenzt.
Häufig gestellte Fragen
Warum sollte Unity nicht direkt das endgültige Paket erzeugen?
Getrennte Stufen unterscheiden Unity-Skript- und Importfehler von Problemen mit nativen Abhängigkeiten, Compiler oder Linker. Ein unsignierter Build prüft den Code, bevor kontrollierte Signaturdaten eingesetzt werden.
Darf das Library-Verzeichnis zwischen Projekten geteilt werden?
Nicht ohne passenden Cache-Schlüssel. Dieser muss Unity-Version, Abhängigkeits-Sperrdateien und Zielplattform enthalten; bei einer Abweichung wird Library neu erzeugt.
Wann muss ein xcworkspace gebaut werden?
Wenn die Vorbereitung nativer Abhängigkeiten einen Workspace erzeugt, wird xcworkspace gebaut. Nur ohne Workspace kommt xcodeproj zum Einsatz; die Prüfung erfolgt nach der Abhängigkeitsinstallation.
Den nächsten iOS-Build auf einem MiniDebug M4 ausführen.
M4, 16 GB RAM und 256 GB SSD sind standardmäßig verfügbar. Die Miete ist tage-, wochen-, monats- oder quartalsweise möglich. Zur Auswahl stehen fünf Standorte: Singapur, Tokio in Japan, Seoul in Südkorea, Hongkong und die US-Ostküste. Die tatsächliche Verfügbarkeit wird in Echtzeit von der Konsole angezeigt.