Dasselbe Swift-Paket generiert auf dem Rechner eines Entwicklers problemlos Code, schlägt aber in einem sauberen Arbeitsbereich auf einem Cloud-Mac mit Operation not permitted fehl – oder der Build ist erfolgreich, kompiliert jedoch weiterhin eine veraltete Datei. Meist liegt das nicht an der Leistung des Rechners. Häufiger greift das Build-Tool-Plugin auf nicht deklarierte Dateien zu, schreibt in das falsche Verzeichnis oder der Fehler wird lokal durch übrig gebliebene Artefakte verdeckt. Statt wiederholt Caches zu löschen, sollten die Eingaben, Ausgaben und Ausführungsgrenzen des Plugins systematisch rekonstruiert werden.
Zuerst die fehlerhafte Ebene bestimmen
Bei SwiftPM-Build-Tool-Plugins sind drei Ebenen beteiligt: SwiftPM plant die Befehle, das ausführbare Plugin-Werkzeug generiert die Inhalte und Xcode verarbeitet anschließend die erzeugten Dateien. Zeichnen Sie zunächst im Stammverzeichnis des Repositorys das vollständige Protokoll auf:
set -o pipefail
rm -rf .ci
mkdir -p .ci
xcodebuild \
-scheme App \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$PWD/.ci/DerivedData" \
-clonedSourcePackagesDirPath "$PWD/.ci/SourcePackages" \
build 2>&1 | tee .ci/build.log
grep -E 'sandbox|deny|plugin|Operation not permitted|No such file' .ci/build.log
Zeigt das Protokoll, dass das Plugin-Werkzeug überhaupt nicht gefunden wurde, prüfen Sie, ob es als ausführbares Tool eines Package-Targets deklariert ist. Scheitert es erst nach dem Start, untersuchen Sie als Nächstes die Lese- und Schreibpfade. Ist der Build erfolgreich, ohne dass die generierten Inhalte aktualisiert wurden, sollten vor allem die Deklarationen der Ein- und Ausgaben überprüft werden.
Die Sandbox zu deaktivieren, sollte nicht der erste Reparaturversuch sein. Eine Sandbox-Verweigerung weist häufig präzise darauf hin, dass der Generator Inhalte außerhalb des Build-Graphen liest oder verändert. Das Umgehen dieser Einschränkung verschiebt das Problem lediglich bis zum nächsten sauberen Build.
Ein- und Ausgaben im Build-Graphen deklarieren
Ein robustes Plugin liest ausschließlich explizit deklarierte Eingaben und schreibt seine Ergebnisse in das pluginWorkDirectory. In der folgenden Struktur verfolgt SwiftPM sowohl das Schema als auch die generierte Swift-Datei:
import PackagePlugin
@main
struct CodegenPlugin: BuildToolPlugin {
func createBuildCommands(
context: PluginContext,
target: Target
) async throws -> [Command] {
let tool = try context.tool(named: "schema-gen")
let input = target.directory.appending("Schemas/api.json")
let output = context.pluginWorkDirectory
.appending("Generated/API.swift")
return [
.buildCommand(
displayName: "Generate API.swift",
executable: tool.path,
arguments: [input.string, output.string],
inputFiles: [input],
outputFiles: [output]
)
]
}
}
Nach dem Start sollte der Generator das übergeordnete Verzeichnis Generated selbst erstellen und ausschließlich in den übergebenen Ausgabepfad schreiben. Er darf nicht standardmäßig in Sources des Repositorys, auf den Schreibtisch, in das Benutzerverzeichnis oder in einen fest codierten Pfad wie /Users/... schreiben. Build-Tool-Plugins sind dafür vorgesehen, abgeleitete Dateien zu erzeugen, die in den aktuellen Kompiliervorgang eingehen. Muss eine Aufgabe den Inhalt des Repositorys verändern, sollte sie stattdessen als Command-Plugin umgesetzt werden, das Entwickler ausdrücklich ausführen, und nicht bei jedem Build unbemerkt den Quellcode ändern.
Implizite Abhängigkeiten erkennen
Typische implizite Eingaben sind Konfigurationsdateien im aktuellen Arbeitsverzeichnis, über Umgebungsvariablen referenzierte Vorlagen, Caches im Benutzerverzeichnis und vollständige Ordner, die der Generator automatisch durchsucht. Beantworten Sie für jede Kategorie die folgenden vier Fragen:
| Prüfpunkt | Korrekte Vorgehensweise | Warnsignal |
|---|---|---|
| Eingabedateien | Alle Dateien in inputFiles aufführen |
Nicht deklarierte Verzeichnisse werden zur Laufzeit rekursiv durchsucht |
| Ausgabedateien | Alle Dateien in outputFiles aufführen |
Dateinamen ändern sich je nach Zeitpunkt oder Rechner |
| Arbeitspfad | Als Argument übergebene absolute Pfade verwenden | Abhängigkeit von pwd oder dem Benutzerverzeichnis |
| Generierungsreihenfolge | Sortieren und stabil ausgeben | Abhängigkeit von der Aufzählungsreihenfolge des Dateisystems |
Den Generator deterministisch halten
Selbst bei korrekten Zugriffsrechten kann ein Generator inkrementelle Builds dauerhaft ungültig machen. Besonders häufig werden der aktuelle Zeitpunkt, ein temporäres Verzeichnis oder der Hostname in den Dateikopf geschrieben. Wenn identische Eingaben bei jedem Lauf andere Bytes erzeugen, kann SwiftPM nicht feststellen, ob tatsächlich eine relevante Änderung vorliegt.
Führen Sie auf dem Cloud-Mac zwei saubere Generierungsläufe nacheinander aus und vergleichen Sie die Prüfsummen:
rm -rf .ci/first .ci/second
mkdir -p .ci/first .ci/second
.build/debug/schema-gen \
Schemas/api.json .ci/first/API.swift
.build/debug/schema-gen \
Schemas/api.json .ci/second/API.swift
shasum -a 256 .ci/first/API.swift .ci/second/API.swift
cmp .ci/first/API.swift .ci/second/API.swift
Beide Prüfsummen müssen übereinstimmen. Der Generator sollte außerdem Felder, Dateien und Deklarationen sortieren, einheitliche Zeilenumbrüche verwenden und die Zieldatei nur ersetzen, wenn sich ihr Inhalt geändert hat. Dazu kann er zunächst eine temporäre Datei schreiben, sie vergleichen und anschließend atomar verschieben. So bleibt bei einem abgebrochenen Build keine unvollständige Swift-Datei zurück.
Cache-Probleme in einem sauberen Arbeitsbereich reproduzieren
Ein lokal erfolgreicher Build beweist nicht, dass alle Deklarationen vollständig sind. Lokale DerivedData, alte Plugin-Ausgaben oder früher manuell generierte Quelldateien können fehlende Deklarationen vorübergehend verdecken. Ein Cloud-Build sollte deshalb mindestens einmal ohne historischen Zustand geprüft werden; anschließend sollte ein zweiter inkrementeller Build folgen.
Prüfung in zwei Durchläufen
Löschen Sie im ersten Durchlauf das vorgesehene Build-Verzeichnis und führen Sie den Build aus. Prüfen Sie dabei, ob sämtliche generierten Dateien tatsächlich vom Plugin erzeugt werden. Starten Sie den Build im zweiten Durchlauf erneut, ohne Eingaben zu verändern, und vergewissern Sie sich, dass das Plugin seine Ausgaben nicht unnötig neu schreibt. Ändern Sie danach für einen dritten Durchlauf genau ein Schema-Feld und prüfen Sie, ob die zugehörige generierte Datei wirklich aktualisiert wird.
Mit den folgenden Befehlen lässt sich schnell erkennen, ob generierte Dateien am falschen Ort landen:
find "$PWD" -type f -name 'API.swift' -print
git status --short
Im Normalfall befinden sich abgeleitete Dateien im Build-Verzeichnis, und git status sollte keine vom Plugin automatisch veränderten, versionsverwalteten Quelldateien anzeigen. Wird das Plugin auch im zweiten Durchlauf erneut ausgeführt, prüfen Sie, ob die Ausgabe tatsächlich vorhanden ist, ob das Tool Zeitstempel zwangsweise aktualisiert und ob ein Eingabeverzeichnis zu weit gefasst deklariert wurde.
Abnahmegrenzen für den Cloud-Mac festlegen
In der entfernten Build-Umgebung von HireVM oder auf anderen sauberen macOS-Knoten empfiehlt es sich, die folgenden Prüfungen in die Pipeline aufzunehmen, anstatt sich auf manuelle Beobachtungen zu verlassen:
- Legen Sie die Xcode-Auswahl fest und protokollieren Sie am Anfang
xcodebuild -versionsowieswift --version. - Verwenden Sie für jeden Arbeitsbereich ein eigenes DerivedData- und Package-Checkout-Verzeichnis, damit parallele Jobs nicht gegenseitig in ihre Verzeichnisse schreiben.
- Beginnen Sie den ersten Build in einem leeren Verzeichnis, um sicherzustellen, dass das Plugin nicht von historischen Dateien im Benutzerverzeichnis abhängt.
- Speichern Sie ausführliche Build-Protokolle für den Zeitraum vor und nach einem Plugin-Fehler, schreiben Sie jedoch keine Token, Signaturmaterialien oder vollständigen Umgebungsvariablen in die Protokolle.
- Vergleichen Sie die Prüfsummen wichtiger generierter Dateien, um zu bestätigen, dass derselbe Commit reproduzierbar generiert werden kann.
- Ändern Sie genau eine Eingabe und führen Sie den Build erneut aus, um zu prüfen, ob der inkrementelle Build nur die erwarteten Ausgaben aktualisiert.
- Prüfen Sie in der Konsole die aktuell verfügbaren Konfigurationen und wählen Sie die Ressourcen anhand der Anzahl paralleler Builds. Die Korrektheitsprüfung des Plugins darf nicht vom zufälligen Zustand eines bestimmten Rechners abhängen.
Das Ziel besteht nicht darin, dass ein einzelner Build „zufällig funktioniert“. SwiftPM muss den Generierungsschritt vollständig verstehen können: Bei geänderten Eingaben wird präzise neu gebaut, bei unveränderten Eingaben bleibt der Build ruhig und in einem neuen Arbeitsbereich auf einem Cloud-Mac entstehen dieselben Artefakte. Sobald diese Voraussetzungen erfüllt sind, ist die Sandbox kein Hindernis mehr, sondern eine automatische Prüfung, ob die Grenzen des Builds realistisch und vollständig definiert sind.
Häufig gestellte Fragen
Darf ein SwiftPM-Build-Tool-Plugin das Quellverzeichnis verändern?
Das sollte vermieden werden. Generierte Dateien gehören in das Plugin-Arbeitsverzeichnis und müssen über outputFiles deklariert werden. Änderungen am Repository sollten ein bewusst gestartetes Command-Plugin übernehmen.
Warum funktioniert das Plugin lokal, aber nicht auf dem Cloud-Mac?
Lokale Restdateien können fehlende Ausgabedeklarationen verdecken. Häufig hängt der Generator außerdem vom Benutzerverzeichnis, aktuellen Verzeichnis oder absoluten Pfaden ab. Ein sauberer Checkout mit festen Buildpfaden macht das sichtbar.
Ist das Abschalten der Sandbox eine geeignete Lösung?
Nein. Dauerhaft zuverlässig wird der Build erst, wenn alle Ein- und Ausgaben deklariert sind, nur erlaubte Verzeichnisse beschrieben werden und identische Eingaben identische Dateien erzeugen.
Führe deine nächste Remote-Mac-Aufgabe auf einem dedizierten Knoten aus
Wähle je nach Projektumfang aus zwei verfügbaren Konfigurationen, fünf Standorten sowie Tages-, Wochen-, Monats- oder Quartalslaufzeiten. Vor der Bestellung kannst du Konfiguration und USD-Betrag vollständig prüfen.