(2006-11-16)

eisfair :: create_eislists.pl

Motivation

Im Laufe der Zeit habe ich einige Pakete für eisfair erstellt. Mit der Erstellung der eigentlichen Pakete ist es allerdings nicht getan. Um diese installieren zu können müssen auf dem Webserver, wo die Pakete abgelegt sind, zusätzlich zu den eigentlichen Paketen sozusagen »Inhaltsverzeichnisse« liegen, die dem Installationsmechanismus von eisfair verraten, wo die Paketdateien liegen. Diese Inhaltsverzeichnisse sieht man, wenn man im Setup-Menü die Package administration aufruft und dann Install new package. Für ein halbes Dutzend Pakete lassen sich die notwendigen Dateien, die auch in der Entwickler-Doku beschrieben sind, noch recht leicht von Hand anlegen. Ich wollte aber gern nicht nur die letzte Version meiner Pakete sondern am besten neben dem aktuellen Pre-Release zu Testzwecken auch alle Vorversionen nicht nur automatisch archiviert haben sondern auch noch leicht installieren können. Da reicht eine einzige eis-list.txt nicht mehr aus und mal eben von Hand geht das nicht mehr, also habe ich ein Skript geschrieben, was abgestimmt auf den bestehenden Teil meiner Entwicklungsumgebung die notwendigen Dateien automatisch erstellt.

Überblick

In den Wochen vor der Programmierung von create_eislists.pl habe ich die Versionsverwaltung für meine Pakete umgestellt. Zu den kompilierten Binaries kommen ja auch noch »Quelldateien« für Installationsskripte, Dokumentation etc. Gerade bei Serverdiensten, wo noch Menüeinträge usw. nötig sind, kommen da schon einige Shell-Skripte zusammen, wo auch programmiertechnischer Aufwand drin steckt. Deshalb verwalte ich die Pakete mit Subversion. Einen Eindruck von der Struktur des Repositories bekommt man auf dem folgenden Bild:

package development repository structure

Jedes Paket hat seinen eigenen Ordner. In trunk liegt das in der Entwicklung befindliche Pre-Release, in branches mögliche abweichende Entwicklungszweige (denkbar, wenn in einem testing-Zweig noch was dazugefügt wird) und in tags die fertigen Releases. Mein bisher schon funktionierendes Skript make_package.sh kann jetzt schon aus dem trunk- und aus dem tags-Zweig die Dateien nehmen und zu einem Paket zusammenpacken. Das ganze wird dann direkt in ein Verzeichnis kopiert, das der Webserver erreicht, so dass ich genauso schnell ein Pre-Release für meine eigenen Tests erzeugen kann, wie ein Paket zur Veröffentlichung, das in tags liegt.

public html webserver directory structure

Auf dem Webserver liegen dann im »root«-Verzeichnis die eis-list-Dateien und es gibt für jedes einzelne Paket einen Ordner, wo das Pre-Release und alle tags drin sind. Das Skript create_eislists.pl erstellt ausgehend von dieser Ordnerstruktur die notwendigen Listen um alle Paketversionen installieren zu können.

root eis
          list    eis list
          with first 8 devel packages

Das Skript erzeugt drei verschiedene »Ausgangs-eis-lists«: eine, die auch auf Pack-Eis eingetragen wird und nur auf die aktuell höchsten stable, testing und unstable-Versionen jedes Pakets führt. Eine weitere, die oben zu sehen ist, die zusätzlich auch alle bisher veröffentlichen Releases verfügbar macht und dann noch eine persönliche, die es mir erlaubt die Pre-Releases zu testen.

  

Wer Interesse an den Paketen selbst hat, oder einfach nur das Ergebnis des Skripts betrachten will, sollte sich folgende Seiten/eis-lists ansehen!

  • http://alex.antiblau.de/eisfair/eis-list.txt
  • http://alex.antiblau.de/eisfair/eis-list-with-archive.txt

Interessante Details

Warum Perl?

Es gab im Grunde nur zwei sinnvolle Möglichkeiten, dieses Skript umzusetzen. Ich hätte entweder ein Shell-Skript schreiben können. Das hätte möglicherweise längere Experimente, schlechte Performance und längere Entwicklungszeit erfordert. Oder ich hätte mir eine leistungsfähige Skriptsprache suchen können, in der die Aufgabe mit vertretbarem Aufwand umzusetzen ist. Hochsprachen wie C/C++, die erst noch kompiliert werden mussten, kamen wegen Bequemlichkeit nicht in Frage. Perl drängte sich geradezu auf, weil es leistungsfähige Mechanismen zur Stringbehandlung aufweist und sowieso auf den meisten Eisfair-Installation dabei ist. Ich hatte mit Perl auch schon minimale Erfahrungen von einem Skript, das mir Künstler und Titel des aktuell im MPd gespielten Tracks ausliest und ans LCD weiterreicht. Perl war sozusagen erste Wahl, auch wenn ich die Einarbeitungszeit in die Sprache einplanen musste.

Sortierung nach Versionsnummern

Ein interessantes Problem ist die Sortierung der Pakete nach ihren Versionsnummern. Diese bestehen bei eisfair aus drei Teilen, die nenne ich hier mal major, minor und release. Bei einer Paketversionsnummer steht sie in dieser Reihenfolge durch Punkte getrennt. Viele (allerdings nicht alle) Paketautoren vergeben ein beliebige Major-Nummer, meistens die 1, die Minor-Nummer ist bei unstable- und testing-Paketen in der Regel ungerade, während stable-Versionen gerade minor-Nummern haben. Die release-Nummern werden einfach hochgezählt. Sortiert wird einleuchtenderweise zunächst nach major, dann nach minor und dann nach release, d.h. 1.3.1 ist beispielsweise neuer als 1.2.4 und das wiederum neuer als 1.2.2.

Rein intuitiv würde ein Mensch jetzt zunächst nach den major-Nummern sortieren, hätte dann mehrere Gruppen mit z.B. 0, 1 und 2 und würde diese Gruppen dann weiter nach minor sortieren usw. In der Informatik dröselt man das Pferd von hinten auf. Man sortiert zunächst die ganze Liste von Versionsnummern nach dem letzten Kriterium, also nach release. Wenn man ein stabiles Sortierverfahren verwendet, kann man nun im zweiten Durchlauf nochmal die ganze Liste nach minor sortieren und behält dank des stabilen Verfahrens bei gleichen minor-Nummern trotzdem die Sortierung der release-Nummern bei. Genauso verhält es sich, wenn man anschließend noch nach den major-Nummern sortiert.

An sich ist das noch nicht so interessant. Allerdings haben bekommen wir die Versionsnummern hier als eine Liste von Zeichenketten geliefert, d.h. wir müssen aus jeder einzelnen Zeichenkette erst noch die drei Nummern extrahieren. Ein Verfahren, das von Randal L. Schwartz in Perl formuliert wurde, auch als "Decorate-Sort-Undecorate" (DSU) bekannt ist und bei dem man im allgemeinen von Schwartz Transformation spricht, bietet sich hier an. Der Trick ist, aus dem String die numerischen Daten in eine temporäre Liste zu extrahieren. Diese wird dann sortiert und dann kehrt man wieder zu den ursprünglichen Daten zurück. So muss man die Extraktion der numerischen Werte nur ein einziges Mal pro Element vornehmen. Dank der Funktion map() von Perl kann man diese Operationen ohne explizite Zwischenspeicherung der Daten in einer Zeile ausführen. Ausführlich und gut erklärt ist das Verfahren z.B. auf der Seite von Eike Grothe oder auch in der englischen Wikipedia. So konnte ich den Ausdruck leicht auf die drei Dimensionen meiner Versionsnummern erweitern und fertig in Perl sieht das dann so aus:

@taglist = map { $_->[0] }
    sort { $a->[1] <=> $b->[1] }
    sort { $a->[2] <=> $b->[2] }
    sort { $a->[3] <=> $b->[3] }
    map { [ $_, ( /(\d+)\.(\d+).(\d+)/ ) ] } @taglist; 

Also ganz ehrlich, ohne Hilfe, hätte ich das nie hinbekommen und allein anhand der Zeile da würde ich es auch nicht verstehen, aber mit Erklärung leuchtet es ein und es funktioniert super.

Aufteilung auf mehrere Seiten

Die Aufteilung vieler Pakete auf mehrere Seiten ging da viel leichter von der Hand. Hört sich erstmal einfach an: Anzahl der Pakete durch Anzahl der maximalen Einträge pro Seite, auf der halben Seite, die da nach dem Komma rauskommt, bricht man dann einfach ab. Ich hab dann zuerst einmal intuitiv die Anzahl der Seiten (die wird für die äußere Schleife gebraucht und muss ja dementsprechend ganzzahlig sein) wie folgt berechnet:

my $numberofpages =
    int($#{$category_packagelist{$category}}/$listmaxentry)+1; 

Allerdings habe ich dann später folgendes herausgefunden:

# die rechnung funktioniert hier mit trick. da ein array bei 0 losgezählt
# wird, ist der letzte index eigentlich anzahl elemente minus 1. das wird
# aber durch int abgerundet und am ende 1 addiert. so hat man trotzdem
# immer die richtige seitenanzahl 

Klingt irgendwie wie »von hinten durch die Brust ins Auge« aber weil's zuverlässig funktioniert, wollte ich's nicht mehr ändern.
Also läuft die äußere Schleife so oft durch wie's Seiten gibt und die innere dann immer so oft, wie es Einträge pro Liste geben soll. Ich habe da einen Wert von acht Paketen angesetzt. So passt immer alles auf eine Seite und man kann mit der 9 eine Seite weiter ohne schon zwei Ziffern eintippen zu müssen. In der inneren Schleife wird dann zu Anfang »abgebrochen« wenn schon alle Listenelemente durch sind, d.h. auf der letzten Seite tauchen dann je nach Länge der Liste ein bis acht Pakete auf. (Die Schleife wird übrigens immer acht mal durchlaufen, nur durch das next ist sie manchmal extrem kurz, weil ohne Inhalt. ;-))

# abbruch, wenn wir schon am letzten element vorbei sind
next if $i*$listmaxentry+$j > $#{$category_packagelist{$category}}; 

Download des Skripts und der Pakete

Wer bis hierhin durchgehalten hat, wird nun also mit den entscheidenden Links belohnt. Zunächst Die öffentlichen Eis-Listen, die können ganz normal im Eisfair-Setup eingetragen werden und darüber lassen sich alle meine Pakete installieren:

Und zu guter letzt dann noch das eigentliche Skript: create_eislists.pl – wer Vorschläge, Änderungswünsche oder ähnliches hat, kann sich gern an mich wenden.