Integration von Travis CI mit Dist::Zilla

Sowohl mit Dist::Zilla als auch mit Travis CI lässt sich die Qualität von Perl-Modulen steigern. Allerdings verursacht die Kombination der beiden Techniken Probleme, die aber glücklicherweise einfach zu lösen sind.

Dist::Zilla erleichtert nicht nur die Entwicklung von CPAN-Modulen, sondern dient auch der Qualitätssicherung, weil es fehlerträchtige Schritte während des Release-Prozesses automatisiert. Travis CI testet ein Perl-Modul nach jedem Push in das Versionskontrollsystem automatisch gegen eine konfigurierbare Liste von Perl-Versionen, bzw. sogar gegen eine Versions-Feature-Matrix.

Vorbereitung der Distribution für Travis CI

Dist::Zilla generiert Build.PL und Makefile.PL aus der Datei dist.in. Normalerweise stellt man generierte Dateien jedoch nicht unter Versionskontrolle, so dass die Distribution ohne weitere Maßnahmen von Travis CI nicht mehr gebaut, und somit auch nicht getestet werden kann.

Die einfachste Lösung besteht darin, Dist::Zilla als Abhängigkeit in .travis.yml einzutragen. Das hat allerdings Nachteile:

  • Dist::Zilla hat eine sehr lange Liste von eigenen Abhängigkeiten, was den Testprozess stark verlangsamt, und außerdem auch unnötig Last auf den Servern von Travis CI verursacht.
  • Alle Plug-Ins für Dist::Zilla, die von der Distribution benötigt werden, müssen ebenfalls Abhängigkeit hinzugefügt werden. Das ist fehlerträchtig, jedenfalls, solange es noch kein Plug-In für Dist::Zilla gibt, dass die Datei travis.yml generiert.

Aber auch jeder andere, der das Modul aus einem Git-Checkout bauen will, muss Dist::Zilla und alle notwendigen Plug-Ins installieren. Weil Dist:Zilla durchaus kontrovers gesehen wird, kann das eine gewisse Abschreckungswirkung entfalten.

Glücklicherweise kann man sich leicht aus der Schusslinie entfernen, und sich trotz Dist::Zilla auf die Erfordernisse von Travis CI und Leuten, die direkt mit den Quellen aus Git arbeiten wollen, einstellen. Dazu müssen einfach die generierten Dateien in das Wurzelverzeichnis der Distribution zurückkopiert werden.

Das Dist::Zilla-Plug-In CopyFilesFromBuild hilft hier, wenn der folgende Abschnitt zu dist.in zugefügt wird:

[CopyFilesFromBuild]
copy = Build.PL
copy = LICENSE
copy = MANIFEST
copy = Makefile.PL

Beim nächsten Aufruf von dzil build, werden diese Dateien vom Verzeichnis MODUL-VERSION ins Wurzelverzeichnis zurückkopiert. Sie sollten natürlich auch in Git eingecheckt werden, damit sie allen, die die Distribution auschecken --- unter anderem auch Travis CI --- zur Verfügung stehen.

Damit entweiht man natürlich das Mantra generierte Dateien gehören nicht unter Versionskontrolle. Aber diese Regel sollte man immer dann ohne viel Aufhebens ignorieren, wenn auch nur einem Mitglied des Teams irgendein Werkzeug fehlt, das nicht zwingend für die Entwicklung benötigt wird. Dist::Zilla gehört ganz sicher zu diesen Tools.

Die Dateien MANIFEST und LICENSE sind auch nicht zwingend notwendig, um die Distribution zu bauen, aber sie machen github oder andere Leute glücklich.

Es gilt jedoch einige Klippen zu umschiffen:

Die zurückzuschreibenden Dateien werden nur nach eine Aufruf von dzil build aktualisiert. Man darf diesen Schritt also nicht weglassen, wenn sie aktualisiert werden müssten.

Eine weitere Implikation ist, dass MANIFEST dirty wird, wann immer der Distribution eine Datei zugefügt wird. Analog werden die Makefile.PL und Build.PL dirty, wenn sich Abhängigkeiten oder die Versionsnummern ändern. Das kann man natürlich genauso gut als freundliche Erinnerung betrachten, und nicht als Problem.

Es gibt aber auch ein echtes Problem. Wer Git als Versionskontrollsystem einsetzt, wird normalerweise das Plug-In [GatherDir] durch [GitGatherDir] ersetzen, damit nur Dateien unter Versionskontrolle ins Ausgabeverzeichnis kopiert werden. Die vier fraglichen Dateien erzeugen deshalb einen Konflikt, weil sie gleichzeitig Quelle und Ziel sind.

Das lässt sich einfach beheben, indem man [Git::GatherDir] anweist, diese Dateien nicht zu kopieren:

[Git::GatherDir]
exclude_filename = Build.PL
exclude_filename = LICENSE
exclude_filename = MANIFEST
exclude_filename = Makefile.PL

Autorentests von Travis CI ausführen lassen

Ein weiteres nettes Feature von Dist::Zilla ist die Behandlung von sogenannten Autorentests. Das sind Tests, die nur Autoren des jeweiligen Moduls ausführen wollen, weil sie hauptsächlich kosmetische Checks durchführen, die das Laufzeitverhalten eines Moduls nicht beeinflussen.

Eine typische Konfiguration in dist.ini sieht beispielsweise so aus:

[Test::Perl::Critic]
[PodCoverageTests]
[PodSyntaxTests]

Beim Bauen der Distribution wird Dist::Zilla daraufhin die Tests t/author-critic.t, t/author-pod-coverage.t, und t/author-pod-syntax.t so erzeugen, dass sie mit dzil test laufen, aber übersprungen werden, wenn jemand die Distribution als Tarball heruntergeladen hat.

Weil sie generiert sind, fehlen sie aber auch im Test-Verzeichnis t des Wurzelverzeichnisses und dementsprechend werden sie auch nicht von Travis CI ausgeführt. Ich führe Tests normalerweise mit prove -l und nicht mit dzil test aus, wodurch die Tests bei mir ebenfalls regelmäßig übersprungen werden, weil sie nicht im Verzeichnis t vorhanden sind.

Dieses Verhalten kann durchaus genau das gewünschte sein. Ich allerdings bevorzuge, möglichst früf auf eventuelle Probleme gestoßen zu werden.

Die Lösung ist die gleiche, wie weiter oben für Makefile.PL und Freunde beschrieben. Zuerst wird sichergestellt, dass die generierten Tests ins Wurzelverzeichnis zurückgeschrieben werden. Dazu muss dist.ini folgendermaßen geändert werden:

[Git::GatherDir]
exclude_filename = Build.PL
exclude_filename = LICENSE
exclude_filename = MANIFEST
exclude_filename = Makefile.PL
exclude_match = t/author-.*\.t
[Test::Perl::Critic]
[PodCoverageTests]
[PodSyntaxTests]
[CopyFilesFromBuild]
copy = Build.PL
copy = LICENSE
copy = MANIFEST
copy = Makefile.PL
copy = t/author-critic.t
copy = t/author-pod-coverage.t
copy = t/author-pod-syntax.t]

Damit ignoriert [Git::GatherDir] auch die Autoren-Tests, und sie werden von [CopyFilesFromBuild] zurückkopiert.

Das reicht allerdings noch nicht. Travis CI würde die Test-Skripte zwar ausführen, aber sie würden übersprungen, weil die Umgebungsvariable AUTHOR_TESTING nicht gesetzt ist. Das muss in travis.yml geschehen.:

env:
  - AUTHOR_TESTING=1

Und, voilà, Travis CI setzt jetzt die entsprechende Umgebungsvariable, womit die Autoren-Tests dann vollständig durchgeführt werden.

Es gibt aber noch einen weiteren Fallstrick. Travis CI muss die Abhängigkeiten für die Autoren-Tests installieren. Die ursprüngliche Version dieses Blog-Posts empfahl dafür die folgende Änderung an dist.ini:

[Prereqs / TestRequires]
Pod::Coverage::TrustPod = 0
Test::Perl::Critic = 0

Das stellte sich als schlechte Entscheidung heraus, weil die meisten Leute die Tests laufen lassen, wenn sie Perl-Module installieren. Dadurch werde also Pod::Coverage::TrustPod und Test::Perl::Critic Abhängigkeiten des eigenen Moduls, was nicht wünschenswert ist, weil ausgerechnet diese beiden Module etliche weitere Abhängigkeiten haben. Das lässt sich vermeiden, wenn man sie nur als Abhängigkeiten für Travis CI in .travis.yml definiert:

before_install:
  - cpanm Pod::Coverage::TrustPod Test::Perl::Critic

Dummerweise schlug diese separate Installation für Perl 5.10 auf den Servern von Travis CI fehl, und ich musste die minimale Perl-Version, gegen die getestet wird, von 5.10 auf 5.14 erhöhen.

Der Casus Irreducibilis

In File-Globstar verwende ich das Plug-In [PkgVersion], das automatisch Versionsinformationen in alle .pm-Dateien einfügt. Das verträgt sich allerdings zur Zeit nicht mit einem automatischen Test darauf, dass alle Perl-Dateien die Anweisung use strict enthalten, siehe https://github.com/rjbs/Dist-Zilla/issues/602.

Mein erster Lösungsansatz bestand darin, die Konfigurationsoption use_package von [PkgVersion] zu verwenden. Das Plug-In patcht daraufhin die package-Direktiven zu etwas wie package File::Globstar 0.2 statt Zeilen in der Form von $File::Globstar::VERSION = '0.2' einzufügen. Mir war allerdings nicht bewusst, dass diese Syntax für package-Direktiven erst mit Perl 5.12 (plus/minus 0.02 Minor-Versionen) eingeführt wurde. Meine eigene lokale Perl-Version ist natürlich neuer, und Travis CI testet mit den konfigurierten Perl-Versionen lib/File/Globstar.pm und nicht die generierte Datei File-Globstar-0.2/lib/File/Globstar.pm, die später tatsächlich auf dem CPAN landet.

Das war der Preis für das Brechen der Regel Niemals Dateien für ein Release patchen. File-Globstar-0.2 wurde veröffentlicht und die Tests mit älteren Perlversionen schlugen durchgehend fehl, weil sie die neue Syntax der package-Direktive nutzten.

Die einzige Möglichkeit, mit der diese Art von Problemen vermieden werden kann, besteht darin, Dist::Zilla und alle erforderlichen Plug-Ins doch als Abhängigkeit für Travis CI zuzufügen, und das Test-Kommando entspechend umzukonfigurieren. Ich bin allerdings keineswegs sicher, dass es überhaupt möglich ist, Dist::Zilla mit Perl 5.10 zu bauen, wodurch dieser Ansatz auch nicht unbedingt erfolgreich sein muss.

Dennoch, kann man natürlich so vorgehen. Ein guter Kompromiss sähe vielleicht so aus, travis.yml nur unmittelbar vor einem Release entsprechend zu ändern.

Eine andere Lösung könnte so aussehen, dass von Dist::Zilla gebaute Releases in ein separates Repository gepusht werden, und Travis CI für dieses Repository konfiguriert wird. Das wäre vielleicht sogar die bessere Lösung, weil damit die Distribution so getestet wird, wie sie auf den Maschinen der End-User gebaut wird. Lässt man hingegen Travis CI Dist::Zilla in der Testumgebung installieren, testet man letztendlich Distributionen, die mit dem auf Travis CI ad hoc installierten Dist::Zilla gebaut würden, und nicht die Distribution, die tatsächlich veröffentlicht wird.

Allerdings muss man sich einen solchen Workflow zur Zeit noch von Hand zusammenbasteln. Es gibt kein Plug-In für Dist::Zilla, dass imstande ist, einen Checkout eines anderes Repositories aus dem Build-Verzeichnis zu aktualisieren.


blog comments powered by Disqus