Nachdem ein großes iOS-Repository mehrere Wochen lang auf einem Cloud Mac gebaut wurde, kann ein bestimmter Branch plötzlich den Skriptfehler argument list too long auslösen, während sich benachbarte Branches weiterhin problemlos archivieren lassen. Löschen Sie nicht vorschnell DerivedData und führen Sie das Problem nicht auf einen sporadischen Xcode-Fehler zurück. Meist stammt die Meldung von E2BIG, das durch execve zurückgegeben wird: Befehlsargumente, deren Abschlusszeichen und die geerbte Umgebung belegen zusammen den gesamten für den Prozess verfügbaren Platz.
Zuerst die fehlerhafte Ebene bestimmen
Im ersten Schritt müssen der vollständige Befehl und der Exit-Status erhalten bleiben. Zeigt das Protokoll lediglich an, dass eine Skriptphase fehlgeschlagen ist, aktivieren Sie im betreffenden Run Script vorübergehend set -x. So lässt sich feststellen, ob find, rm, ein Archivierungswerkzeug, ein Codegenerator oder der Compiler-Treiber fehlschlägt. Geben Sie nicht die gesamte Umgebung in einem öffentlichen Protokoll aus, da sie Token enthalten kann. Erfassen Sie nur Variablennamen, Byte-Größen und eine bereinigte Darstellung der Befehlsstruktur.
Drei Auslöser treten besonders häufig auf: Ein Platzhalter wird auf einmal zu Zehntausenden Pfaden expandiert; ein Skript fügt sämtliche Quelldateien in einer einzigen Variablen zusammen; oder das CI-System injiziert umfangreiche JSON-Daten, Zertifikatsinhalte oder mehrzeilige Konfigurationen als Umgebungsvariablen. Lange Arbeitsbereichspfade sowie wiederholte -I-, -F- und -D-Argumente verringern den verbleibenden Spielraum ebenfalls kontinuierlich.
Dass ein Befehl in einem kleinen Branch funktioniert, bedeutet nicht, dass seine Struktur korrekt ist. Tritt der Fehler erst mit wachsender Dateizahl auf, fehlt bei der Übergabe der Eingaben in der Regel eine klar definierte Obergrenze.
Spielraum für Argumente und Umgebung messen
Unter macOS lässt sich die verfügbare Obergrenze nicht allein anhand der Länge des Befehlstextes bestimmen. Erfassen Sie die Werte zunächst mit demselben Benutzer und derselben Startmethode wie beim Build-Job:
getconf ARG_MAX
python3 - <<'PY'
import os
limit = os.sysconf("SC_ARG_MAX")
env_bytes = sum(len(k) + len(v) + 2 for k, v in os.environ.items())
largest = sorted(
((len(k) + len(v) + 2, k) for k, v in os.environ.items()),
reverse=True
)[:10]
print("arg_max", limit)
print("environment_bytes", env_bytes)
for size, key in largest:
print(size, key)
PY
Diese Ergebnisse dienen dem Vergleich und dürfen nicht als vollständig nutzbares Budget betrachtet werden. Das System benötigt zusätzlich Platz für Zeiger, Abschlusszeichen und den Startaufwand. In der Praxis sollte ein deutlicher Puffer eingeplant werden. Achten Sie außerdem darauf, ob die Größe der Umgebung gegenüber dem bisherigen Basiswert sprunghaft gestiegen ist.
Übermäßig große Variablen finden
Prüfen Sie zuerst PATH, Suchpfade, temporäre Verzeichnisse, Parameter von Abhängigkeitsmanagern und durch das CI-System injizierte Variablen. Erreicht eine JSON-Konfiguration mehrere Dutzend KB, sollte sie in eine zugriffsgeschützte temporäre Datei geschrieben werden. Übergeben Sie dem Unterprozess anschließend nur den Dateipfad. Wird PATH mehrfach erweitert, entfernen Sie zunächst Duplikate, anstatt den Wert in jedem Schritt erneut zusammenzusetzen.
Geerbte Umgebung verkleinern, ohne die Toolchain zu beschädigen
Starten Sie einen vollständigen Xcode-Build nicht direkt mit einer leeren Umgebung. Fehlende Werte für HOME, PATH, das temporäre Verzeichnis oder das Entwicklerverzeichnis verursachen neue Fehler. Robuster ist eine Positivliste für ein einzelnes Werkzeug:
env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
TMPDIR="$TMPDIR" \
DEVELOPER_DIR="$DEVELOPER_DIR" \
/bin/zsh -lc 'xcrun --find xcodebuild'
Für produktive Jobs müssen je nach tatsächlichen Abhängigkeiten zusätzlich Gebietsschema, Cache-Verzeichnisse und Proxy-Konfigurationen aufgenommen werden. Übergeben Sie vertrauliche Inhalte über kurzlebige Dateien und löschen Sie diese nach der Verwendung. Gewöhnliche boolesche Schalter und kurze Kennungen können weiterhin als Umgebungsvariablen geführt werden. Auch Automatisierungsjobs auf SetMini sollten die Initialisierung der Umgebung in einem einzigen Einstiegsskript bündeln. Dadurch wird verhindert, dass interaktive Shell, CI-Runner und Xcode-Skripte dieselbe Konfiguration jeweils erneut anhängen.
Große Dateimengen aus der Befehlszeile auslagern
In allgemeinen Skripten nullterminierte Stapel verwenden
Dateinamen können Leerzeichen oder Zeilenumbrüche enthalten. Verwenden Sie daher nicht for f in $(find ...). Werkzeuge mit Stapelverarbeitung lassen sich mit find -print0 und xargs -0 kombinieren:
find "$PWD/Artifacts" -type f -name '*.dSYM' -print0 |
xargs -0 -n 50 /usr/bin/file
Mit -n 50 erhält jeder Stapel eine eindeutige Obergrenze. Kann ein Werkzeug eine Liste über die Standardeingabe einlesen, sollte diese Möglichkeit bevorzugt werden, um wiederholte Prozessstarts zu vermeiden.
Dateilisten in Xcode-Phasen verwenden
Die Ein- und Ausgaben eines Run Script sollten in .xcfilelist-Dateien geschrieben und anschließend unter Input File Lists beziehungsweise Output File Lists konfiguriert werden. So kann Xcode Abhängigkeiten verfolgen, und das Skript muss nicht Tausende Pfade zu einem einzigen Befehl expandieren. Unterstützt ein Compiler oder Linker ausdrücklich Antwortdateien, können stabile Argumente in eine solche Datei ausgelagert werden. Gehen Sie jedoch nicht davon aus, dass jedes Drittanbieterwerkzeug die Syntax @file versteht.
Bei zahlreichen Optionen aus den Build-Einstellungen gehören gemeinsam verwendete Werte in eine .xcconfig-Datei. Das Skript sollte nur wenige notwendige Argumente erhalten. Pfadmengen werden über Dateilisten und strukturierte Konfigurationen über temporäre Dateien übergeben. Sind diese drei Eingabearten getrennt, lassen sich auch die Protokolle leichter prüfen.
Wachstumsgrenzen und Regressionstests einführen
Führen Sie nach der Korrektur mindestens einen sauberen Build mit dem Branch aus, der die meisten Dateien und die längsten Pfade enthält. Führen Sie anschließend einen inkrementellen Build durch. Prüfen Sie, ob das Skript Dateinamen mit Leerzeichen korrekt verarbeitet, Fehler bei der Stapelverarbeitung einen von null verschiedenen Status zurückgeben und temporäre Dateien auf allen Exit-Pfaden entfernt werden.
Zu Beginn eines CI-Laufs können ARG_MAX, die Gesamtgröße der Umgebung in Byte und die Namen der größten Variablen protokolliert werden, nicht jedoch deren Werte. Definieren Sie für die Umgebungsgröße einen Basiswert im Team und lösen Sie beim Überschreiten eines Schwellenwerts eine Warnung aus, statt zu warten, bis das System die Prozesserstellung verweigert. Erfassen Sie bei wachsenden Dateimengen zusätzlich die Anzahl der Dateien und die Länge des längsten Pfads.
Das Ziel besteht nicht darin, den aktuellen Befehl nur knapp unter die Obergrenze zu bringen. Seine Länge darf nicht länger linear mit der Größe des Repositorys wachsen. Wenn die Umgebung kompakt bleibt, Dateimengen über dafür vorgesehene Kanäle übergeben werden und die Stapelverarbeitung feste Obergrenzen besitzt, wird E2BIG von einem sporadischen Fehler zu einem Konfigurationsproblem, das sich frühzeitig erkennen lässt.
Häufig gestellte Fragen
Warum schlägt derselbe Build-Befehl nur in bestimmten Branches fehl?
Argumente und geerbte Umgebung teilen sich dieselbe Prozessgrenze. Mehr Dateien, längere Pfade oder zusätzliche Variablen können den verbleibenden Spielraum eines Branches aufbrauchen.
Wann ist xargs besser als eine Response-Datei?
Für allgemeine Stapelverarbeitung eignet sich xargs mit NUL-Trennung. Unterstützt der Compiler oder Linker Response-Dateien offiziell, sind diese für lange Eingabelisten vorzuziehen.
Sollte ARG_MAX unter macOS erhöht werden?
Nein. Verkleinern Sie die Umgebung, entfernen Sie wiederholte Optionen und übergeben Sie große Dateimengen per Datei, Standardeingabe oder begrenzten Stapeln.
Entwicklungsschritte in eine jederzeit erreichbare Cloud-Mac-Umgebung verlagern
Wählen Sie zwischen zwei Apple-Silicon-Konfigurationen und vier Rechenzentren. Die Ressourcen werden nicht mit anderen Mietern geteilt; maßgeblich ist der aktuelle Status in der Konsole.