Engineering-Notizen

Eine reproduzierbare Homebrew-Toolchain auf dem Cloud-Mac einrichten

Eine reproduzierbare Homebrew-Toolchain auf dem Cloud-Mac einrichten

Wenn ein Cloud-Mac mehrere Wochen lang ununterbrochen Build-Aufgaben ausführt, liegt das häufigste Umgebungsproblem nicht darin, dass Werkzeuge fehlen. Vielmehr wurden sie zu unterschiedlichen Zeitpunkten von verschiedenen Personen installiert: Ein Job setzt jq voraus, ein anderes Skript erwartet swiftlint, und bei einer kurzfristigen Fehleranalyse sind weitere Formeln hinzugekommen. Übernimmt eine neue Maschine, lässt sich diese Ebene der Systemabhängigkeiten nicht allein durch das Kopieren des Repositorys wiederherstellen. Ein Brewfile eignet sich für die Frage, welche Werkzeuge benötigt werden, ist aber keine Lockdatei für exakte Versionen. Ein zuverlässiger Ansatz verwaltet Deklarationen, Versionsnachweise und Bereinigungsabläufe getrennt voneinander.

Zuerst den Rechner erfassen, nicht sofort alles exportieren

Prüfen Sie zunächst den Homebrew-Pfad und die aktuelle Architektur. So verhindern Sie, dass Skripte weiterhin einen anderen, historisch gewachsenen Pfad verwenden.

command -v brew
brew --prefix
uname -m
brew doctor

Sehen Sie sich anschließend die direkt installierten Formeln, sämtliche Versionen und die Hintergrunddienste getrennt an:

brew leaves
brew list --formula --versions
brew services list

brew leaves eignet sich besser als Ausgangspunkt für die Deklaration als die vollständige Liste, da der Befehl hauptsächlich die bewusst installierten Top-Level-Werkzeuge anzeigt. Die vollständige Versionsliste sollte dagegen als Build-Nachweis archiviert und nicht unverändert in das Brewfile übernommen werden.

Führen Sie keine Upgrades oder Bereinigungen aus, solange noch Build-Aufgaben laufen. Stoppen Sie zuerst die Verteilung neuer Jobs und vergewissern Sie sich anschließend, dass keine Compiler, Datenbanken oder Hilfsdienste von laufenden Prozessen verwendet werden.

Wird der Rechner bereits seit längerer Zeit genutzt, können Sie zunächst eine Kandidatendatei exportieren und diese anschließend Zeile für Zeile prüfen:

mkdir -p ci/homebrew
brew bundle dump --force --file=ci/homebrew/Brewfile.candidate
git diff -- ci/homebrew/Brewfile.candidate

Die Kandidatendatei kann persönliche Werkzeuge, vorübergehend installierte Debugging-Software oder grafische Anwendungen ohne Projektbezug enthalten. In die endgültige Liste gehören nur Einträge, die tatsächlich für Builds oder die Fehleranalyse benötigt werden.

Das Brewfile als Anforderungsdeklaration behandeln

Eine grundlegende Liste für kontinuierliche iOS-Builds kann sehr kurz gehalten werden:

brew "git"
brew "jq"
brew "swiftlint"
brew "xcbeautify"

Committen Sie die Datei als ci/homebrew/Brewfile und geben Sie ihren Pfad bei der Installation explizit an:

brew bundle check --file=ci/homebrew/Brewfile
brew bundle install --file=ci/homebrew/Brewfile --no-upgrade

check eignet sich für den Einstiegspunkt eines Jobs. Der Befehl prüft lediglich, ob die deklarierten Anforderungen erfüllt sind; ein vollständiges Upgrade sollte nicht automatisch bei jedem Build ausgeführt werden. Mit install --no-upgrade lassen sich fehlende Werkzeuge ergänzen, während zugleich das Risiko sinkt, dass ein gewöhnlicher Build unbeabsichtigt die bestehende Umgebung verändert.

Brewfiles nach Zuständigkeit aufteilen

Muss derselbe VMKeep Cloud-Mac mehrere Arten von Aufgaben übernehmen, empfiehlt sich eine Aufteilung in eine Basisliste und projektspezifische Listen. Die Basisebene kann beispielsweise nur Git, Werkzeuge zur JSON-Verarbeitung und Protokollierungswerkzeuge enthalten. Werkzeuge für Codekonventionen oder die Veröffentlichung kommen erst auf Projektebene hinzu. Die Installationsreihenfolge bleibt fest: zuerst die Basisebene, danach die Projektebene.

Verwenden Sie kein ständig wachsendes globales Brewfile für sämtliche Repositorys. Andernfalls lässt sich bei einer Bereinigung kaum noch abschätzen, welche Auswirkungen sie hat. Zudem könnten neue Projekte fälschlicherweise davon ausgehen, dass historisch vorhandene Werkzeuge zu ihren notwendigen Abhängigkeiten gehören.

Versionen und Laufzeitkontext separat protokollieren

Ein Brewfile fixiert gewöhnliche Formeln normalerweise nicht auf exakte Versionen. Dass zwei Rechner dieselbe Liste verwenden, beweist daher nicht, dass sie identische Ergebnisse erzeugen. Nach jeder Änderung der Umgebung sollte ein Snapshot der tatsächlich installierten Versionen gespeichert werden:

{
  date -u
  sw_vers
  xcodebuild -version
  brew --version
  brew list --formula --versions
} > ci/homebrew/toolchain.snapshot.txt

Der Versions-Snapshot kann als Pipeline-Artefakt gespeichert oder nach einem geprüften Upgrade der Umgebung in die Betriebsdokumentation übernommen werden. Treten Abweichungen auf, vergleichen Sie zuerst die Versionen von Xcode, macOS, Homebrew und den direkten Abhängigkeiten. Prüfen Sie danach die Lockdateien des Projekts, statt sofort sämtliche Caches zu löschen.

Versionsgrenzen klar festlegen

brew pin verhindert lediglich lokale Upgrades auf dem aktuellen Rechner. Der Befehl garantiert nicht, dass dieselbe historische Version auf einem neuen Rechner weiterhin verfügbar ist. Er kann daher als kurzfristige Schutzmaßnahme dienen, ersetzt aber weder ein fest definiertes Maschinen-Image noch einen projektspezifischen Versionsmanager oder geprüfte Binärartefakte.

Bei Werkzeugen, die das Build-Artefakt direkt beeinflussen, sollte das Skript beim Start den zulässigen Versionsbereich prüfen und bei einer Abweichung mit einer eindeutigen Fehlermeldung abbrechen. Für Werkzeuge, die lediglich die Protokollausgabe aufbereiten, kann dagegen ein größerer Bereich zugelassen werden. So führen unkritische Unterschiede nicht unnötig zu einem Build-Abbruch.

Mit Vorschau und Wartungsfenster sicher bereinigen

Prüfen Sie vor der eigentlichen Bereinigung zunächst, welche Einträge nicht im Brewfile enthalten sind:

brew bundle cleanup --file=ci/homebrew/Brewfile

Dieser Schritt dient ausschließlich der Prüfung der Ausgabe. Erst wenn sichergestellt ist, dass keine andere Anwendung, kein LaunchAgent und kein Hintergrunddienst von den aufgeführten Formeln abhängt, führen Sie Folgendes aus:

brew bundle cleanup --file=ci/homebrew/Brewfile --force
brew autoremove
brew cleanup

Bei gemeinsam genutzten Rechnern ist besondere Vorsicht geboten: Nur weil eine Formel nicht im Brewfile des aktuellen Repositorys steht, bedeutet das nicht, dass sie von keinem anderen Job verwendet wird. Eine zuverlässigere Abgrenzung besteht darin, jeder Art langfristiger Arbeitslast einen exklusiven physischen Knoten zuzuweisen. Mindestens sollten jedoch separate Ausführungskonten, eigene Arbeitsverzeichnisse und jeweils eigene Listen verwendet werden.

Führen Sie nach der Bereinigung erneut brew bundle check aus und starten Sie anschließend eine minimale Abnahmekette: Werkzeugversionen ausgeben, die Projektkonfiguration einlesen und einen Build abschließen, ohne Artefakte hochzuladen. Erst wenn alle drei Schritte erfolgreich waren, gilt die Umgebungsänderung als abgeschlossen.

Umgebungsänderungen überprüfbar machen

Ein stabiler Ablauf darf nicht zulassen, dass Build-Skripte nebenbei brew install ausführen. Wird ein neues Werkzeug hinzugefügt, sollte der Commit zugleich die Änderung am Brewfile, eine Beschreibung des Verwendungszwecks, den Versions-Snapshot und eine Rücksetzstrategie enthalten. Upgrades erfolgen in einem separaten Wartungsfenster: Zuerst werden repräsentative Projekte überprüft, danach wird die Jobverteilung wieder aktiviert.

Die regelmäßige Prüfung lässt sich auf vier Punkte konzentrieren:

  1. Ist brew bundle check erfolgreich?
  2. Entsprechen die Versionen von Xcode und den wichtigen Formeln den Erwartungen?
  3. Enthält das Brewfile ungeprüfte Änderungen?
  4. Laufen nicht deklarierte Hintergrunddienste dauerhaft weiter?

Der Wert eines Brewfiles liegt nicht darin, automatisch möglichst viele Werkzeuge zu installieren. Es macht aus Systemabhängigkeiten, die zuvor lediglich eine Gegebenheit auf einem bestimmten Rechner waren, eine im Repository diskutierbare, vergleichbare und rücksetzbare Deklaration. In Kombination mit Versions-Snapshots und einer vorsichtigen Bereinigung werden Umgebungsprobleme auf dem Cloud-Mac nicht mehr anhand spontaner Vermutungen untersucht, sondern als belegbare Konfigurationsabweichungen behandelt.

Häufig gestellte Fragen

Fixiert eine Brewfile exakte Homebrew-Paketversionen?

Nein. Eine Brewfile beschreibt die benötigten Werkzeuge, normale Formeln folgen jedoch den Aktualisierungen des Repositorys. Für exakte Versionen sind ein Versionsprotokoll, ein festes Maschinenabbild oder projektspezifische Versionsmanager nötig.

Sollte brew bundle cleanup --force direkt auf einem gemeinsam genutzten Cloud-Mac laufen?

Nein. Zuerst sollte die Löschliste ohne --force geprüft werden. Erst wenn keine anderen Jobs die Werkzeuge benötigen, darf die Bereinigung in einer Leerlaufphase erfolgen.

Dedizierter physischer Knoten

Diesen Workflow auf einem Cloud-Mac weiter validieren

Wählen Sie aus drei Apple-Silicon-Konfigurationen die passende Kombination aus Arbeitsspeicher, Speicher, Knoten und Mietdauer.

Konfiguration auswählen und bestellen