Werkzeuge zur Verwaltung von Haskell-Projekten

(J. Waldmann)

    1. April 2017: init
    1. März 2020: Hinweis auf CI hinzugefügt

Pakete

Ein Haskell-Paket besteht aus einer Menge von Quelltexten sowie einer Beschreibungsdatei mit der Endung .cabal. Das Paket hat einen Namen und eine (numerische) Version, z.B. smallcheck-1.1.1. Die Cabal-Datei enthält eine Menge von benötigten Paketen mit Versionsgrenzen, z.B.

Name:          smallcheck
Version:       1.1.1
Build-Depends: base >= 4.5 && < 5, mtl, logict, ghc-prim >= 0.2, pretty

Cabal

Bei der Installation eines Paketes durch z.B. cabal install smallcheck (vgl. cabal) werden das Paket und die transitiv vorausgesetzten Pakete in einer zu den Grenzen passenden Version entweder benutzt (falls schon installiert) oder aus den Quelltexten (von hackage) kompiliert. Es können gleichzeitig mehrere Versionen eines Paketes installiert sein, jedoch nicht gleichzeitig von einem Paket aus benutzt werden.

Alle installierten Pakete landen in $HOME/.cabal (wegen des Defaults user-install: True in .cabal/config) und der Compiler findet das danach auch von selbst. Wenn die Pakete ausführbare Dateien enthalten, dann werden diese in $HOME/.cabal/bin installiert. Wenn die Shell das sehen soll, muß dieses Verzeichnis in Ihrem $PATH stehen.

Die Installation mit Cabal kann zu nicht reproduzierbaren oder gar inkonsistenen Builds führen (“cabal hell”) durch Änderungen in den installierten Bibliotheken durch vorausgehende Builds und den aktuellen Build selbst sowie durch Neu-Einträge auf hackage, wo im Prinzip jedermann (mit einem Account) ein Paket oder eine neue Version eines Paketes hochladen kann.

Stack

Der Paketmanager stack benutzt sogenannte “Resolver”, das sind kuratierte (d.h. von menschlichen Experten zusammengestellte) Mengen von Paketennamen mit jeweils exakter Versionsnummer, die zueinander passen. Resolver werden auf stackage veröffentlicht.

Durch z.B. stack install --resolver=lts-8.8 smallcheck wird das Paket nebst allen transitiv vorausgesetzen in genau der Version gebaut, die im Resolver steht. Für lts-8.8 bedeutet das smallcheck-1.1.1 und z.B. pretty-1.1.3.3, obwohl pretty laut Cabal-File von smallcheck nicht eingeschränkt ist. Zum Vergleich: beim Bauen mit cabal würde für pretty eine beliebige bereits installierte oder die dann gerade neueste Version von hackage benutzt werden.

Projekte

Ein Projekt ist eine Menge von Paketen und wird durch eine gemeinsame Datei stack.yaml beschrieben. (Beispiel) Beim Bauen des Projektes werden die dabei kompilierten Pakete in einem lokalen Snapshot-Verzeichnis abgelegt, z.B. .stack-work/install/x86_64-linux/lts-8.4/8.0.2/, sowie die transtiv vorausgesetzen aus dem Resolver in $HOME/.stack/snapshots/x86_64-linux/lts-8.4/8.0.2/ (der letzte Bestandteil ist die Kompiler-Version).

Automatisierung

Hier ist ein Beispiel für die Build- und Test-Automatisierung eines Haskell-Projektes.

Einordnung, Kritik

Bauen mit Cabal kann zur Cabal Hell führen. Die (soziale) Ursache dafür ist die Freizügigkeit der Ausgangsdaten: auf dem zentralen Quelltext-Repository Hackage erfolgt nur eine schwache Prüfung der Kompatibilität der hochgeladenen Pakete untereinander.

Es gibt zwar recht strenge Empfehlungen für Paket-Autoren zur Gestaltung der Versionsgrenzen von vorausgesetzten Paketen in Cabal-Files, aber eine Befolgung dieser Regeln erscheint aufwendig (man muß oft das eigene Cabal-File kosmetisch ändern, wenn sich bei vorausgesetzen Paketen etwas ändert) und Abweichen von der Empfehlung wird nicht sanktioniert.

Stackage ersetzt diese schwache Konsistenzprüfung durch eine sehr strenge: das Paket muß (von einer Gruppe von Experten) in einer bestimmten Version für den Resolver akzeptiert werden. Dadurch wird eine Abhängigkeit von einer zentralen Autorität eingeführt.

Datenschutz

Wenn Pakete benutzt werden, die (durch cabal oder stack) bereits installiert sind, werden keine Daten nach außen übertragen. Das gilt insbesondere für die Benutzung von ghc in Lehrveranstaltungen in IMN-Computerpools.

Wenn Quelltexte nachzuladen sind, fallen Daten beim jeweiligen Service-Provider an (welche IP-Adresse lädt wann welche Quelltext-Pakete). Aus der IP-Adresse kann auf den Ort oder gar die Person geschlossen werden, aus den Paketdaten auf die installierte Compilerversion und evtl. auf bereits installierte Software.

Hackage wird von der Industrial Haskell Group, Well Typed LLP und Fastly unterstützt. Stackage wird von FP Complete betrieben. Das sind dann auch die Stellen, welche die Daten auswerten könnten. Es ist anzunehmen, daß das wenigstens zur Überprüfung der Servicequalität auch geschieht, und zu hoffen, daß ausreichend anonymisiert wird.

Von einem funktionierenden Haskell-Ökosystem sowie der Werbung dafür profitieren die Firmen, die Haskell selbst anwenden, und man kann das als ausreichendes Motiv für das Sponsoring der Infrastruktur unterstellen.

Sicherheit

Die von den Stack-Autoren empfohlene Installationsmethode ist nun allerdings der binäre Download in seiner brutalsten Form: (curl -sSL https://get.haskellstack.org/ | sh). So etwas sollte man nur tun, wenn man dem Absender absolut vertraut oder alles in einer Sandbox ohne Zugriff auf Festplatte und Netzwerk stattfindet.

Stack und Cabal und weitere Werkzeuge (wie Ghc) sind quelltext-offen. Damit kann man

  • die Korrektheit des Quelltextes untersuchen
  • und danach den Quelltext kompilieren.

Aber sowohl zur Analyse als auch zur Kompilation benutzen wir weitere Software-Werkzeuge …

Dann nehmen wir vielleicht doch die gegebene Binärdatei (aber nicht aus der Pipe) in der Annahme, daß eingefügter Schadcode schon bemerkt worden wäre, von Leuten, die mehr Zeit und Energie auf die Analyse verwenden.

Aber nehmen wir mal an, wir glauben gar nichts.

Es ist leicht möglich, stack aus Quelltexten zu kompilieren. Dazu braucht (erstmal git, curl, tar und) dann den Haskell-Compiler Ghc. Ist Open-Source, kann man auch selbst kompilieren. Ghc ist aber in Haskell geschrieben. Man kann Ghc über Umwege herstellen: früher gab es einige Haskell-Compiler, die in anderer Sprache geschrieben sind oder aus anderen Sprachen initialisiert werden können, z.B. hbc über ML, nhc98 über C, siehe Übersicht. Damit kann man aber nicht die aktuelle Ghc-Version kompilieren, jedoch frühere, und sich dann hocharbeiten.

Dafür braucht man aber einen open-source C-Compiler wie gcc oder clang. Auch den kann man selbst kompilieren … wenn man einen C-Compiler hat. Selbst wenn man diesen selbst schreibt, muß man schließlich dem Betriebssystemkern trauen sowie der Hardware und dort eingebetteter Software.

Darüber wird schon lange nachgedacht, auch schon lange konkret etwas getan. Ich zitiere und empfehle die Artikel: