Engineering-Praxis

Open-Source-Lizenzen für iOS in CI prüfen

Open-Source-Lizenzen für iOS in CI prüfen

Nach einem Abhängigkeits-Upgrade erstellt das Team erfolgreich ein Archiv, stellt jedoch erst kurz vor der Veröffentlichung fest, dass eine neue Komponente nicht im Verzeichnis der Open-Source-Lizenzen enthalten ist. Das Problem liegt meist nicht beim Kompilieren, sondern bei den verteilten Quellen der Abhängigkeiten: SwiftPM verwendet eine Sperrdatei, CocoaPods eine eigene Verzeichnisstruktur, und zusätzlich können Quelltexte direkt in das Repository kopiert oder Binärpakete eingebunden worden sein. Wird die Prüfung in eine cloudbasierte Mac-CI verlagert, kann dasselbe Projekt in einem fest definierten Arbeitsverzeichnis eine Bestandsliste erzeugen, sie mit den Richtlinien abgleichen und Nachweise sichern. So muss die für das Release verantwortliche Person nicht kurzfristig Verzeichnisse durchsuchen.

Zuerst festlegen, welche Fragen das Gate beantworten muss

Ein ausführbares Gate muss mindestens vier Fragen beantworten: Welche Komponenten wurden für diesen Build tatsächlich aufgelöst? Welche Version oder welcher Commit wird verwendet? Wo befindet sich der vollständige Lizenztext? Wurde diese Lizenzbedingung bereits genehmigt? Eine Freigabe darf nicht allein aufgrund eines gefundenen Dateinamens erfolgen. Das Vorhandensein einer LICENSE-Datei bedeutet weder, dass ihr Inhalt unverändert ist, noch dass die Komponente tatsächlich mit dem aktuellen Target verknüpft wurde.

Es empfiehlt sich, die Ergebnisse in ein maschinenlesbares Inventar und einen Bericht für die manuelle Prüfung aufzuteilen. Das maschinenlesbare Inventar wird als JSON geführt und enthält Komponentenname, Quelle, gesperrte Version, Lizenztyp, Text-Hash und Fundort. Der Bericht wird als Markdown erstellt und weist hinzugefügte, entfernte und geänderte Einträge aus. Beide Dateien werden als Pipeline-Artefakte aufbewahrt.

Das Gate prüft, ob die tatsächlichen Abhängigkeiten mit den festgelegten Richtlinien übereinstimmen. Bei unbekannten Einträgen muss die Veröffentlichung gestoppt und eine Prüfung angefordert werden, statt die erforderliche Behandlung automatisch aus dem Lizenznamen abzuleiten.

Tatsächliche Abhängigkeiten anhand des Build-Ergebnisses erfassen

Lassen Sie Xcode zunächst die Abhängigkeiten auflösen und lesen Sie anschließend die vom Workspace tatsächlich verwendeten Verzeichnisse aus. Dadurch werden veraltete Caches auf Entwicklerrechnern nicht versehentlich mitgescannt. Jeder Auftrag verwendet ein eigenes DerivedData-Verzeichnis, das erst nach Abschluss des Auftrags bereinigt wird.

set -euo pipefail

ROOT="$(pwd)"
OUT="$ROOT/build/license-audit"
DD="$ROOT/build/DerivedData"
mkdir -p "$OUT"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -resolvePackageDependencies \
  -derivedDataPath "$DD"

find "$DD/SourcePackages/checkouts" \
  -maxdepth 3 \
  -type f \
  \( -iname 'license*' -o -iname 'copying*' -o -iname 'notice*' \) \
  -print0 | sort -z > "$OUT/swiftpm-license-files.bin"

find "$ROOT/Pods" \
  -maxdepth 4 \
  -type f \
  \( -iname 'license*' -o -iname 'copying*' -o -iname 'notice*' \) \
  -print0 | sort -z > "$OUT/pods-license-files.bin"

Bei binären Abhängigkeiten reicht es nicht aus, nur die Dateierweiterung zu betrachten. Zusätzlich müssen die interne Komponentenkennung zur Downloadadresse, die Prüfsumme, die Version und die mitgelieferten Erklärungen erfasst werden. Für in das Repository kopierte Quelltexte wird eine Datei namens compliance/manual-components.json gepflegt, in der die Verantwortlichen für die jeweiligen Verzeichnisse eindeutig festgelegt sind. So muss sich nicht jeder Scan auf unscharfe Suchmuster verlassen.

Sperrdateien müssen vollständig erfasst werden

Kopieren Sie Package.resolved, Podfile.lock und das manuell gepflegte Komponentenverzeichnis in das Berichtsverzeichnis. Das Gate muss prüfen, ob jede im Verzeichnis aufgeführte Komponente einer gesperrten Quelle zugeordnet werden kann. Umgekehrt muss es sicherstellen, dass keine direkte Abhängigkeit aus den Sperrdateien im Bericht fehlt. Enthält das Projekt mehrere Workspaces, müssen diese einzeln aufgelöst werden; die Prüfung darf sich nicht auf die Hauptanwendung beschränken.

Lizenztexte normalisieren und Fingerabdrücke berechnen

Lizenztexte unterscheiden sich häufig nur durch Zeilenumbrüche oder nachgestellte Leerzeichen. Vor der Berechnung des SHA-256-Hashs können sie einheitlich in UTF-8 mit LF-Zeilenumbrüchen umgewandelt und von Leerzeichen am Zeilenende bereinigt werden. Absätze, Copyright-Zeilen oder Jahreszahlen dürfen nicht entfernt werden, da sonst echte Änderungen verborgen bleiben.

python3 - <<'PY'
from pathlib import Path
import hashlib
import json

root = Path("build/license-audit/licenses")
rows = []
for path in sorted(root.rglob("*")):
    if not path.is_file():
        continue
    text = path.read_text(encoding="utf-8", errors="strict")
    normalized = "\n".join(line.rstrip() for line in text.splitlines()).strip() + "\n"
    digest = hashlib.sha256(normalized.encode("utf-8")).hexdigest()
    rows.append({"path": str(path), "sha256": digest})
Path("build/license-audit/license-hashes.json").write_text(
    json.dumps(rows, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8"
)
PY

Lizenztyp und Textfingerabdruck müssen getrennt gespeichert werden. Der Typ dient zum Abgleich mit den Richtlinien, der Fingerabdruck zum Erkennen unbemerkter Änderungen durch Upstream-Projekte. Lässt sich der Typ nicht zuverlässig bestimmen, wird er als UNKNOWN gekennzeichnet. Ein vertraut wirkender Dateiname ist kein Grund für eine automatische Klassifizierung.

Änderungen mit einer Richtliniendatei steuern

Die Richtliniendatei muss versioniert und ausschließlich über einen klar definierten Code-Review-Prozess geändert werden. Für Lizenzkennungen lassen sich die Zustände allow, review und deny festlegen; zusätzlich können bereits genehmigte Text-Hashes einzelner Komponenten hinterlegt werden. Für die Bewertung der Prüfergebnisse empfiehlt sich folgende Zuordnung:

Situation CI-Ergebnis Nächster Schritt
Bekannte Komponente, Version und Text unverändert Bestanden Bericht speichern
Neue Version, Lizenzfingerabdruck unverändert Prüfen Komponentenänderung bestätigen
Neue Komponente oder unbekannte Lizenz Blockiert Quelle und Genehmigung ergänzen
Lizenztext einer vorhandenen Komponente geändert Blockiert Vollständigen Textvergleich prüfen
Komponente in der Sperrdatei, aber nicht im Bericht Blockiert Erkennungsregeln korrigieren

Die Freigabeliste darf nicht nur allgemein gefasste Lizenztypen enthalten. Eine Komponente kann zusätzliche NOTICE-Dateien, eine Doppellizenzierung oder separate Nutzungsrechte für Ressourcen mitbringen. Daher müssen alle relevanten Dateien im Bericht erhalten bleiben. Bei Binärpaketen ist außerdem zu prüfen, ob die Prüfsumme des aktuell heruntergeladenen Artefakts mit dem genehmigten Eintrag übereinstimmt.

Berichte in Merge- und Release-Abläufe integrieren

Bei regulären Merge Requests können die Auflösung der Abhängigkeiten, die Differenzprüfung und die Berichtserstellung ausgeführt werden. Der Release-Auftrag ergänzt eine Prüfung des Archivinhalts, um sicherzustellen, dass das endgültige Artefakt derselben Sperrdatei entspricht. Als Baseline empfiehlt sich compliance/license-baseline.json. Die CI vergleicht ausschließlich strukturierte Felder und keine farbige Terminalausgabe.

Schlägt das Gate fehl, muss das Protokoll direkt die Komponente, den alten Wert, den neuen Wert und den Pfad zum Nachweis ausgeben, statt lediglich „Lizenzprüfung fehlgeschlagen“ zu melden. Das Berichtsverzeichnis muss mindestens Kopien der Sperrdateien, normalisierte Texte, das Hash-Verzeichnis, die Richtlinienversion und eine Zusammenfassung der Unterschiede enthalten. Parallele Aufträge auf ArmCLI müssen jeweils eigene Arbeitsverzeichnisse verwenden, damit die Aktualisierung von Abhängigkeiten in einem Auftrag die Scanergebnisse eines anderen Auftrags nicht beeinflusst.

Prüfpunkte vor der Veröffentlichung

Prüfen Sie vor der Veröffentlichung, dass bei der Auflösung der Abhängigkeiten keine nicht dokumentierten lokalen Pfade verwendet wurden, dass für alle direkten und transitiven Abhängigkeiten eine Versionsquelle vorliegt, dass keine unbekannten Lizenzen vorhanden sind, dass die im manuellen Komponentenverzeichnis aufgeführten Verzeichnisse weiterhin existieren und dass Bericht und Archiv auf demselben Commit basieren. Außerdem dürfen die CI-Artefakte keine Zugriffstoken, privaten Downloadadressen oder nicht anonymisierten Umgebungsvariablen enthalten.

Der Nutzen dieses Ablaufs beschränkt sich nicht auf das Erzeugen einer Seite mit Lizenzangaben. Er macht Änderungen an Abhängigkeiten zu überprüfbaren technischen Ereignissen. Solange Sperrdateien, Lizenzfingerabdrücke und Richtlinien-Baseline synchron bleiben, kann das Team Probleme bereits beim Eingang einer Abhängigkeit in den Release-Branch erkennen, statt nach Fertigstellung des Archivs die Herkunft von Komponenten nachträglich klären zu müssen.

Häufig gestellte Fragen

Reicht die Prüfung von Package.resolved aus?

Nein. Die Datei hält SwiftPM-Versionen fest, belegt aber keine gefundenen Lizenztexte und erfasst weder CocoaPods noch kopierte Quellen oder binäre Abhängigkeiten.

Welche Änderungen sollten eine Veröffentlichung blockieren?

Eine neue Abhängigkeit ohne Lizenzdatei, eine nicht freigegebene Bedingung, Abweichungen von Sperrdateien oder ein geänderter Lizenztext müssen bis zur manuellen Prüfung blockieren.

ArmCLI Cloud-Mac

Einen exklusiven physischen Mac für den nächsten Build vorbereiten

Modell, Rechenzentrum und Mietdauer passend zur Aufgabe auswählen. Die Knoten laufen ganzjährig stabil; maßgeblich ist der in der Konsole angezeigte Status in Echtzeit.