Ein Build-Skript funktioniert auf dem Rechner eines Entwicklers, schlägt nach der Übertragung auf einen Mac in der Cloud jedoch mit Permission denied, bad interpreter oder einem unerklärlichen $'\r' am Zeilenende fehl. Solche Fehler liegen meist nicht am Anwendungscode, sondern daran, dass Git-Dateimodus, Zeilenenden und der tatsächlich verwendete Interpreter keinen einheitlichen Vertrag bilden. Sichern Sie zuerst den fehlgeschlagenen Befehl, den Commit-Hash und den Skriptpfad. Prüfen Sie anschließend die folgenden Punkte in dieser Reihenfolge – das ist meist schneller, als wiederholt Caches zu leeren.
Zuerst die Ebene des Fehlers bestimmen
Fügen Sie nicht sofort chmod +x hinzu, nur weil ein Skript fehlgeschlagen ist. Bei Shell-Skripten sind mindestens vier Ebenen zu unterscheiden: die Berechtigungen im Arbeitsverzeichnis, der im Git-Index gespeicherte Modus, die tatsächlichen Bytes der Datei und die Art, wie der Runner das Skript aufruft.
script="ci/build.sh"
ls -l "$script"
git ls-files -s -- "$script"
file "$script"
head -n 1 "$script" | cat -vet
Wenn die erste Spalte der Ausgabe von git ls-files -s den Wert 100755 enthält, hat Git das Ausführungsbit gespeichert. 100644 kennzeichnet dagegen eine normale Datei. Zeigt cat -vet am Zeilenende ^M, enthält die Datei CRLF-Zeilenenden. Prüfen Sie außerdem, ob die erste Zeile einen gültigen Shebang wie #!/bin/zsh oder #!/usr/bin/env bash enthält.
Auch die Aufrufart muss dokumentiert werden. Bei bash ci/build.sh benötigt die Datei kein Ausführungsbit. ./ci/build.sh prüft dagegen die Berechtigungen und wertet den Shebang aus. Wenn eine Xcode-Build-Phase, ein Skript des Aufgabenplaners und das lokale Terminal unterschiedliche Aufrufarten verwenden, kann fälschlich der Eindruck entstehen, das Problem trete nur in CI auf.
Ziel der Korrektur ist nicht, einen einzelnen Job zufällig erfolgreich abzuschließen. Das Repository muss eindeutig festlegen, welcher Interpreter das Skript ausführt, ob es direkt gestartet werden darf und welche Zeilenenden vorgeschrieben sind.
Ausführungsbit im Git-Index korrigieren
Direkt ausgeführte Einstiegsskripte müssen mit Ausführungsbit gespeichert werden. Fragmente, die per source eingebunden oder ausdrücklich an einen Interpreter übergeben werden, sollten dagegen normalerweise den Modus 100644 behalten. Erstellen Sie zunächst eine Liste der Einstiegspunkte, statt pauschal alle .sh-Dateien ausführbar zu machen.
git update-index --chmod=+x ci/build.sh
git update-index --chmod=+x ci/test.sh
git diff --summary
git ls-files -s -- ci/build.sh ci/test.sh
Falls git diff --summary keine Modusänderung anzeigt, prüfen Sie, ob die Berechtigungsverfolgung lokal deaktiviert ist:
git config --get core.fileMode
git config core.fileMode true
core.fileMode beeinflusst nur, wie Änderungen im aktuellen Arbeitsverzeichnis erkannt werden. Mit einem Commit weitergegeben wird ausschließlich der Git-Index. Verwenden Sie chmod -R +x . beim Start des Runners nicht als dauerhafte Lösung. Dadurch werden Fehler im Repository verdeckt und möglicherweise Konfigurationen, Schlüsselmaterial oder gewöhnliche Datendateien mit unnötigen Berechtigungen versehen.
Die Skripte, die direkt ausgeführt werden dürfen, können zeilenweise in ci/entrypoints.txt eingetragen werden. Bei der Abnahme muss dann nur diese Liste geprüft werden. Neue Einstiegspunkte erfordern zugleich eine Aktualisierung der Registrierungsdatei, sodass auch im Code-Review nachvollziehbar bleibt, warum sich die Berechtigungsgrenze geändert hat.
LF festlegen und vorhandene Dateien kontrolliert normalisieren
Die .gitattributes im Stammverzeichnis des Repositorys sollte die einzige Quelle für Regeln zu Zeilenenden sein. Für typische Build-Dateien eignet sich folgende Basiskonfiguration:
*.sh text eol=lf
*.command text eol=lf
*.zsh text eol=lf
*.xcconfig text eol=lf
*.pbxproj text eol=lf
*.png binary
*.zip binary
Neue Regeln schreiben bereits eingecheckte Dateien nicht automatisch um. Führen Sie deshalb in einem sauberen Branch Folgendes aus:
git add --renormalize .
git status --short
git diff --check
git diff --cached --stat
Prüfen Sie zuerst die Unterschiede und committen Sie die Normalisierung anschließend separat. Vermischen Sie sie nicht mit funktionalen Änderungen. Wenn dabei zahlreiche Projektdateien geändert werden, kontrollieren Sie stichprobenartig, dass tatsächlich nur die Zeilenenden angepasst wurden. Archive, Codegeneratoren und externe Skripte können weiterhin CRLF-Dateien einbringen. .gitattributes ersetzt daher keine Prüfung in der CI-Pipeline.
| Prüfergebnis | Häufige Ursache | Richtige Maßnahme |
|---|---|---|
100644 und direkte Ausführung |
Berechtigung nicht im Index gespeichert | Mit git update-index --chmod=+x korrigieren |
^M am Zeilenende |
Datei verwendet CRLF | Auf LF normalisieren und committen |
bad interpreter |
Shebang enthält CR oder verweist auf einen ungültigen Pfad | Erste Zeile korrigieren und Verfügbarkeit des Interpreters prüfen |
| Lokal erfolgreich, in CI fehlgeschlagen | Andere Aufrufart oder Shell | Aufrufbefehl ausgeben und Shell ausdrücklich festlegen |
Interpreter-Unterschiede als ausdrücklichen Vertrag definieren
Die interaktive Standard-Shell unter macOS muss nicht mit der Shell übereinstimmen, die CI tatsächlich verwendet. Nutzt ein Skript Bash-Arrays, [[ ]] oder Prozesssubstitution, darf es nicht implizit von sh interpretiert werden. Abhängigkeiten von der zsh-Syntax müssen ebenfalls ausdrücklich deklariert werden.
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root"
Verlassen Sie sich nicht darauf, dass interaktive Konfigurationsdateien PATH, Aliasse oder Umgebungsvariablen bereitstellen. Der CI-Einstiegspunkt sollte selbst einen minimalen PATH setzen und mit Befehlen wie command -v xcodebuild und command -v ruby prüfen, ob die benötigten Werkzeuge vorhanden sind. Erforderliche Umgebungsvariablen sollten von der Jobkonfiguration injiziert werden. Das Skript prüft nur ihre Namen und gibt keine vertraulichen Werte aus.
Vermeiden Sie außerdem, eine Datei mit Bash-Shebang über sh script.sh aufzurufen, da das ausdrücklich angegebene sh den Shebang umgeht. Auch Jobs auf DebugMac-Knoten müssen sich nach den Deklarationen im Repository richten, statt die Ausführungsumgebung aus der Shell abzuleiten, die zufällig in einer bestimmten Sitzung verfügbar war.
Doppelte Prüfung vor dem Commit und in CI einrichten
Lokale Hooks verkürzen die Rückmeldezeit, dürfen aber nicht die einzige Schutzmaßnahme sein, weil sie bei einem normalen Klon nicht automatisch aktiviert werden. CI sollte vor der Installation von Abhängigkeiten und vor dem Xcode-Build eine schlanke Prüfung ausführen.
#!/usr/bin/env bash
set -euo pipefail
while IFS= read -r script; do
[[ -z "$script" ]] && continue
mode="$(git ls-files -s -- "$script" | awk '{print $1}')"
[[ "$mode" == "100755" ]] || {
printf 'entrypoint is not executable: %s\n' "$script" >&2
exit 1
}
done < ci/entrypoints.txt
if git grep -Il $'\r' -- '*.sh' '*.zsh' '*.command' > /tmp/crlf-files; then
printf 'CRLF detected:\n' >&2
cat /tmp/crlf-files >&2
exit 1
fi
Die Prüfung sollte außerdem sicherstellen, dass die in der Registrierungsdatei aufgeführten Pfade tatsächlich vorhanden sind, und nicht committe Normalisierungsunterschiede ablehnen. Die abschließende Abnahme kann anhand von vier Punkten erfolgen: frischer Checkout, direkte Ausführung der Einstiegsskripte, leere Ausgabe von git status --porcelain und ein minimaler Build in einer nicht interaktiven Sitzung. So werden Probleme mit Berechtigungen und Zeilenenden erkannt, bevor zeitaufwendige Downloads und Kompiliervorgänge beginnen. Zugleich entfällt die Notwendigkeit, den Runner vor Ort nachträglich anzupassen.
Häufig gestellte Fragen
Braucht ein mit bash gestartetes Skript das Ausführungsrecht?
Für bash script.sh nicht, wohl aber für direkten Aufruf, Build-Phasen und manche Runner. Echte Einstiegspunkte sollten im Git-Index den Modus 100755 besitzen.
Korrigiert .gitattributes bereits vorhandene CRLF-Dateien?
Nein. Führen Sie git add --renormalize . aus, prüfen Sie den Diff und committen Sie die Normalisierung. Eine CI-Prüfung sollte für importierte Dateien bestehen bleiben.
Den nächsten Build auf einem dedizierten physischen Apple-Silicon-Knoten ausführen
Wählen Sie Laufzeit und Knotenregion aus, prüfen Sie Mac Mini M4, 16 GB RAM, 256 GB SSD und den fälligen USD-Betrag und geben Sie anschließend die Bestellung auf.