Reproduzierbare CocoaPods-Installationen auf Cloud Macs

DevOps & CI/CD ·ca. 5 Min. Lesezeit

Reproduzierbare CocoaPods-Installationen auf Cloud Macs

Derselbe Commit lässt sich auf dem Entwicklungsrechner erfolgreich bauen, doch ein neuer Job auf einem Cloud Mac löst plötzlich andere Abhängigkeiten auf oder schreibt nach pod install unbemerkt die Podfile.lock um. Die Ursache solcher Probleme liegt häufig nicht bei Xcode selbst. Vielmehr werden die CocoaPods-Version, Caches und der Installationsmodus nicht als Build-Eingaben verwaltet. Das richtige Ziel lautet nicht, dass die Installation „jedes Mal irgendwie durchläuft“. Dieselben Eingaben müssen stets denselben Abhängigkeitsgraphen ergeben, und jede Abweichung muss den Prozess sofort stoppen.

Eingaben für reproduzierbare Installationen definieren

Das Ergebnis von CocoaPods hängt von mindestens fünf Eingaben ab: Podfile, Podfile.lock, der CocoaPods-Version, der Ruby- und Bundler-Umgebung sowie dem Quellcode, auf den lokale Entwicklungs-Pods verweisen. Wird nur das Lockfile eingecheckt, während der Runner eine beliebige CocoaPods-Version verwendet, können sich das generierte Projektformat und das Installationsverhalten weiterhin unterscheiden.

Im Repository sollten .ruby-version, Gemfile, Gemfile.lock, Podfile und Podfile.lock eingecheckt werden. Die CocoaPods-Version wird im Gemfile ausdrücklich festgelegt, statt sich auf die neueste global installierte Version des Rechners zu verlassen:

source ENV.fetch("BUNDLE_GEM_SOURCE")

ruby "3.3.4"
gem "cocoapods", "1.15.2"

Als CI-Einstiegspunkt wird durchgehend bundle exec pod verwendet. Dadurch stimmt die in den Logs sichtbare Version mit der fixierten Umgebung überein. Außerdem wird verhindert, dass eine interaktive Shell zufällig den global installierten Befehl bevorzugt.

Podfile.lock fixiert das Ergebnis der Abhängigkeitsauflösung. Die CocoaPods-Version, die diese Auflösung durchführt und das Projekt generiert, wird dadurch nicht festgelegt.

Installationsbefehl in einen strikten Modus versetzen

Normale Builds dürfen kein pod update ausführen. Dieser Befehl löst Abhängigkeiten innerhalb der Versionsbeschränkungen neu auf. Das ist für manuell angestoßene Upgrade-Aufgaben sinnvoll, aber nicht für die Überprüfung eines vorhandenen Commits. In der CI kann folgender Installationseinstieg verwendet werden:

#!/bin/bash
set -euo pipefail

export LANG=en_US.UTF-8
export COCOAPODS_DISABLE_STATS=true
export CP_HOME_DIR="${RUNNER_TEMP}/cocoapods-home"

bundle config set path "${RUNNER_TEMP}/bundle"
bundle check || bundle install
bundle exec pod install --deployment --clean-install
git diff --exit-code -- Podfile.lock

Mit --deployment schlägt die Installation fehl, sobald das Lockfile geändert werden müsste. --clean-install verringert zugleich das Risiko, dass ein alter Installationszustand ein Problem verdeckt. Das abschließende git diff bildet eine zweite Schranke: Selbst wenn ein Skript das Lockfile während der Installation verändert, darf der Job nicht mit dem Build fortfahren.

Enthält das Repository mehrere Workspaces, sollte jeder davon ein eigenes Installationsverzeichnis und eine eigene Logdatei erhalten. Parallele Jobs dürfen sich nicht dasselbe Pods-Verzeichnis teilen.

Caches isolieren, aber nicht blind deaktivieren

Nicht der Cache selbst ist das Problem, sondern eine unklare Abgrenzung. Auf Cloud Macs entstehen Verunreinigungen häufig dadurch, dass mehrere Repositories denselben CocoaPods-Download-Cache verwenden oder zwei Jobs gleichzeitig in dasselbe Bundler-Verzeichnis schreiben und daraus lesen. Cache-Schlüssel sollten aus „Lockfile-Prüfsumme + Tool-Version“ gebildet werden. In den Cache darf erst geschrieben werden, nachdem die Installation erfolgreich abgeschlossen wurde.

Cache-Objekt Empfohlener Cache-Schlüssel Ungültig bei
Bundler-Installationsverzeichnis Gemfile.lock-Prüfsumme + Ruby-Version Änderung einer der Eingaben
CocoaPods-Download-Cache Podfile.lock-Prüfsumme + CocoaPods-Version Änderung des Lockfiles oder Tools
Pods-Verzeichnis Podfile.lock-Prüfsumme + Prüfsumme der Projektkonfiguration Änderung an Podfile, Skripten oder Konfiguration

Pods/Manifest.lock darf nicht aus einem Cache wiederhergestellt werden und anschließend ein neues Installationsergebnis überschreiben. Die Datei beschreibt den tatsächlich installierten Zustand der aktuellen Sandbox und muss von der jeweiligen Installation erzeugt werden. Treten nach der Wiederherstellung eines Caches Fehler auf, sollte zunächst eine Installation ohne Cache in einem isolierten Verzeichnis ausgeführt werden. Erst wenn diese erfolgreich ist, lässt sich das Problem dem Cache zuordnen.

Abhängigkeitsgraph vor dem Build überprüfen

Eine erfolgreiche Installation bedeutet nicht automatisch, dass der Abhängigkeitsgraph korrekt ist. Mindestens muss geprüft werden, ob Podfile.lock und Pods/Manifest.lock übereinstimmen und ob nicht eingecheckte Änderungen vorhanden sind:

test -f Podfile.lock
test -f Pods/Manifest.lock

cmp -s Podfile.lock Pods/Manifest.lock || {
  echo "CocoaPods sandbox differs from Podfile.lock" >&2
  exit 1
}

git status --porcelain -- Podfile Podfile.lock Gemfile Gemfile.lock

Liefert der letzte Befehl eine Ausgabe, muss der Job fehlschlagen, statt lediglich eine Warnung auszugeben. Bei lokalen Pods, die :path verwenden, muss zusätzlich der Commit-Hash oder die Inhaltsprüfsumme des Zielverzeichnisses erfasst werden. Andernfalls kann sich der Verzeichnisinhalt trotz unverändertem Lockfile ändern, womit weiterhin keine Reproduzierbarkeit gegeben ist.

Einen separaten Upgrade-Pfad einrichten

Ein Job für Abhängigkeits-Upgrades darf pod update SomePod für ein bestimmtes Ziel ausführen. Er muss jedoch drei Arten von Änderungen ausgeben: Versionsänderungen im Lockfile, Änderungen an SPEC CHECKSUMS sowie hinzugefügte oder entfernte transitive Abhängigkeiten. Ein Upgrade-Commit sollte ausschließlich Abhängigkeitsänderungen enthalten und nicht gleichzeitig Anwendungscode ändern. Nur so lässt sich bei einem Rollback direkt zum vorherigen Lockfile zurückkehren.

Fehlerzustand sichern und schrittweise diagnostizieren

Nach einem Fehler sollten nicht sofort sämtliche Caches gelöscht werden. Zunächst werden die CocoaPods-Version, die Ruby-Version, die Lockfile-Prüfsumme, das Installationsprotokoll und die Ausgabe von git status gesichert. Erst danach wird bestimmt, auf welcher Ebene der Fehler liegt.

  1. Schlägt bundle check fehl, werden zuerst Ruby und die Plattform in Gemfile.lock überprüft.
  2. Verlangt die strikte Installation eine Änderung des Lockfiles, ist zu prüfen, ob jemand Podfile geändert, aber das zugehörige Lockfile nicht eingecheckt hat.
  3. Schlagen ausschließlich Jobs nach einer Cache-Wiederherstellung fehl, wird in einem separaten Verzeichnis eine Installation ohne Cache ausgeführt und das Ergebnis verglichen.
  4. Stimmt Manifest.lock nicht überein, ist zu prüfen, ob die Installation unterbrochen wurde oder mehrere Jobs dasselbe Pods-Verzeichnis verwenden.
  5. Ändert sich der Inhalt eines lokalen Pods unkontrolliert, wird für das Quellverzeichnis eine Prüfung des Commit-Hashs oder der Inhaltsprüfsumme ergänzt.

Ein stabiler CocoaPods-Ablauf sollte am Ende ausgesprochen unspektakulär sein: Bleiben die Eingaben unverändert, bleibt auch das Installationsergebnis unverändert. Ändern sich Eingaben, gelangen die Unterschiede ins Code-Review. Ein beschädigter Cache darf nur die Geschwindigkeit beeinträchtigen, nicht aber den Abhängigkeitsgraphen verändern. Werden diese Prüfungen vor dem Xcode-Build ausgeführt, lässt sich ein schwer verständlicher Fehler nach mehreren Dutzend Minuten meist durch einen eindeutigen Abbruch nach wenigen Dutzend Sekunden ersetzen.

Häufig gestellte Fragen

Soll CI pod install oder pod update ausführen?

Reguläre CI-Aufträge verwenden pod install --deployment. pod update gehört in einen separaten Aktualisierungsauftrag, dessen Änderungen an Podfile.lock geprüft werden.

Warum können Ergebnisse trotz Podfile.lock abweichen?

Häufige Ursachen sind andere Ruby- oder CocoaPods-Versionen, veränderliche lokale Pods, beschädigte Caches oder Skripte, die Podfile vor der Installation bearbeiten.

Soll das Verzeichnis Pods in Git liegen?

In der Regel nicht. Podfile, Podfile.lock, Gemfile und Gemfile.lock reichen aus, wenn CI die Abhängigkeiten zuverlässig aus den fixierten Eingaben wiederherstellt.

Dedizierter physischer Knoten

LemonVM Cloud Mac passend zur Laufzeit wählen

Lemon M4 und Lemon M4 Pro sind in Singapur, Tokio, Seoul, Hongkong und im Westen der USA verfügbar – mit Tages-, Wochen-, Monats- und Quartalsabrechnung.

Cloud Mac jetzt mieten