Wie Patches mit Composer einfach eingespielt werden können

Code patchen möchte niemand. Wenn es aber dennoch sein muss, sollte es reproduzierbar, gut dokumentiert und nachvollziehbar geschehen. Mit composer-patches können Patch-Dateien für Composer-Packages unkompliziert eingespielt und versioniert werden.

Lesedauer: ca. 3 Minuten

Wieso eigentlich patchen?

Eigentlich möchte niemand Code patchen. Leider ist es dennoch oft notwendig, denn Fehler schleichen sich selbst in stabile Software-Versionen gerne ein. Und wenn es darum geht, Sicherheitslücken schnell zu schließen oder Fehler zeitnah auszumerzen, dauert das Warten auf eine Fehlerbehebung durch einen der nächsten Releases oft zu lange.

Patches haben viele Nachteile. Sie müssen verwaltet werden. Wurde einmal eine Fremd-Extension oder ein Core modifiziert, ist ein einfaches Aktualisieren nicht mehr möglich. Der veränderte Code muss mitgeführt werden. Manchmal ist dann ein Fork die Lösung, aber auch hier sind Aktualisierungen nicht ohne Mehraufwand möglich. Und ist der Patch schlecht oder gar nicht im Projekt dokumentiert, geht er mit der Zeit eventuell verloren und muss im schlimmsten Fall neu entdeckt werden.

composer-patches von Cameron Eagans

Cameron Eagans hat für solche Fälle ein schlankes Composer-Paket namens composer-patches veröffentlicht, mit dem Patches für Composer-Pakete sehr einfach eingesetzt und verwaltet werden können. Mit dem Befehl

composer require "cweagans/composer-patches:~1.0"

wird composer-patches dem Projekt und der composer.json hinzugefügt. Die Patches können auf zwei Arten verwaltet werden. Sie können direkt in der composer.json konfiguriert werden.

"extra": {
  "patches": {
    "vendor/package": {
      "Bug #1234: Something is wrong": "patches/1234.diff"
    }
  }
}

Soll die composer.json durch die Konfigurationen nicht zu überladen sein, bietet sich alternativ eine externe Datei an, um die Patches zu definieren. Im Folgenden heißt diese Datei composer.patches.json. In der composer.json wird nur noch die Referenz zu dieser Datei hinterlegt.

"extra": {
  "patches-file": "composer.patches.json",
  "enable-patching": true
}

Die Datei composer.patches.json beinhaltet die einzelnen Patches und die dazugehörige Patch-Datei.

{
  "patches": {
    "vendor/package": {
      "Bug #1234: Something is wrong": "patches/1234.diff"
    }
  }
}

Statt einer, wie hier, im Projekt abgelegten Patch-Datei kann auch ein Link auf die externe Patch-Datei verwendet werden.

{
  "patches": {
    "vendor/package": {
      "Bug #1234: Something is wrong": "https://url.to-the-patch-file.com/1234.diff"
    }
  }
}


Fallbeispiel: Aktuellen TYPO3 8.6.1-Core patchen mit composer-patches

Da bei einem unserer Kunden nach einer Aktualisierung auf die TYPO3-Version 8.6.1 keine Inhalte mehr im Frontend zu sehen waren, gingen wir auf Spurensuche. Es stellte sich heraus, dass ein Bug im Core dafür verantwortlich war: styles.content.get not available in FSC anymore. Kurze Zeit später war ein Patch auf dem TYPO3-Review-Server verfügbar: Make styles.content.get available after major FSC rewrite.

Um nicht von der nächsten TYPO3-Version abhängig zu sein und dennoch auf der aktuellen Version arbeiten zu können, entschieden wir, den Core mit Hilfe von composer-patches zu patchen. Die Datei composer.patches.json für dieses Projekt enthält eine Beschreibung des Fehlers und einen Link zu dem Patch, der eingesetzt wird.

{
  "patches": {
    "typo3/cms": {
      "Bug #80044: styles.content.get not available in FSC anymore (https://review.typo3.org/#/c/51884)": "patches/3a02a648.diff"
    }
  }
}

Somit ist sichergestellt, dass der Patch bei Aufruf von composer install eingesetzt wird, und jeder Entwickler nachvollziehen kann, warum der Patch notwendig ist. Außerdem ist die Konfiguration mit dem Projekt versioniert. Die Patch-Datei selbst kann nicht über einen Link angezogen werden, da composer-patches leider weder mit den von TYPO3 bereitgestellten Patch-Dateien in Base64 noch mit Archiven umgehen kann.

Screenshot der zeigt, dass die Patch-Datei selbst nicht über einen Link angezogen werden kann

Die extrahierte Datei aus dem Patch-File-Archiv (3307a5d.diff.zip) von TYPO3 liegt deshalb in einem Ordner patches innerhalb des Projekts. Damit werden zusätzlich Abhängkeiten zum externen TYPO3-Review-Server vermieden.

Beim Aufruf von composer install wird der Patch eingefügt.

Anzeige im Terminal, der zeigt, wie beim Aufruf von composer install der Patch eingefügt wird


Fallbeispiel: Ein Patch für Neos

Soll eine Neos-Installation mit einem Patch versehen werden, kann analog vorgegangen werden. Das Neos-Team arbeitet zur Zeit ausschließlich in Github. Beispielhaft soll der Commit c448b4e als Patch eingespielt werden. Patch-Dateien können mit Github durch das Anhängen von .diff bzw. .patch an die URL generiert werden.

Screenshot der zeigt, wo man .patch oder .doff anhängen soll

Die Konfiguration in der composer.patches.json beinhaltet also einen Link als Patch-Quelle.

{
  "patches": {
    "neos/neos": {
      "TASK: Add preset attribute description to fusion reference": "https://github.com/neos/neos/commit/c448b4e64050235cd4302996a7630dd2ffcec930.patch"
    }
  }
}

Beim Aufruf von composer install wird der Patch eingefügt.

Das Terminal zeigt: Beim Aufruf von composer install wird der Patch eingefügt.

Wenn schon patchen, dann mit Köpfchen

Führt kein Weg an einem Patch vorbei, ist die Arbeit mit Tools wie composer-patches sehr zu empfehlen. Die Nachteile bleiben im kontrollierbaren Rahmen und der Überblick über die geflickten Stellen im Projekt bleibt gewahrt. Wird der Konfiguration für die Patches eine saubere Beschreibung und somit schon eine implizite Dokumentation hinzugefügt, ist die Wahrscheinlichkeit, dass das Wissen um den Patch verloren geht, sehr gering, und auch projektfremde Kollegen haben sofort einen Einblick. Durch die Versionierung der Patch-Dateien und die Automatisierung mit Composer reduziert sich außerdem der Aufwand, den Patch im Projekt mitzuführen, stark.

Teilen:

Weitere Beiträge

router bgp 16188
Patrick M. Hausen, Technik bei punkt.de
Arbeiten bei punkt.de