coldwa.st
Alle LeitfädenProgrammierungWebDatenWerkzeugeDatenbankenHaskellKonzepteCabal & BuildsToolchainCompilerPerformanceEditor & HLS

Haskell · Cabal · Build-Werkzeuge

Cabal-Sandboxes: Was sie waren und was sie ersetzt hat

Von ColdwastAktualisiert am 13. Juni 20269 Min. Lesezeit#haskell#cabal#build
Drei isolierte Cabal-Sandboxes, jede mit ihrem eigenen Satz an Paketen, neben einem Terminal, das cabal sandbox init ausführt
Projektbezogene Abhängigkeitsisolation — die Idee, die Sandboxes einführten, heute das Standardverhalten.
Dies ist eine aktualisierte, von der Community gepflegte Neufassung zu einem Thema, für das diese Domain historisch bekannt war. Es handelt sich um eigenständige Inhalte, weder vom früheren Domain-Inhaber verfasst noch mit ihm verbunden. cabal sandbox selbst gehört inzwischen der Geschichte an — wenn Sie nur den modernen Workflow wollen, springen Sie direkt zu was Sie heute verwenden sollten.

Als cabal sandbox im August 2013 in cabal-install 1.18 erschien, behob es das damals Lästigste an der Haskell-Entwicklung: die globale Paketdatenbank. Vor den Sandboxes schrieb jedes cabal install in einen einzigen, gemeinsam genutzten ~/.cabal-Store. Installieren Sie zwei Projekte, die unterschiedliche Versionen derselben Bibliothek benötigen, und Sie landeten in der berüchtigten „dependency hell“ — einer beschädigten globalen Datenbank, die oft mit rm -rf ~/.ghc ~/.cabal und einem Neuanfang endete.

Sandboxes machten Abhängigkeiten projektbezogen. Dieses Prinzip ist nie verschwunden — der Mechanismus aber schon. Hier das vollständige Bild, vom Funktionieren der Sandboxes bis zum nix-artigen Workflow, der sie ersetzt hat.

Was eine Sandbox eigentlich war

Eine Sandbox war eine projektlokale Paketdatenbank. Sie erstellten eine im Verzeichnis Ihres Projekts:

$ cd my-project
$ cabal sandbox init
Writing a default package environment file to
  /home/you/my-project/cabal.sandbox.config
Creating a new sandbox at
  /home/you/my-project/.cabal-sandbox

Von da an verwendeten cabal install und cabal build, in diesem Verzeichnis ausgeführt, .cabal-sandbox/ anstelle des globalen Stores. Zwei Projekte konnten von widersprüchlichen Versionen von aeson oder text abhängen, ohne sich je in die Quere zu kommen, weil jedes seine eigene isolierte Datenbank hatte.

Lokale Quell-Abhängigkeiten

Die andere wirklich nützliche Funktion war add-source, die es einer Sandbox erlaubte, eine Bibliothek direkt aus einem lokalen Repository einzubinden — unschätzbar, wenn man an einer Bibliothek und einer Anwendung zugleich bastelt:

$ cabal sandbox add-source ../my-library
$ cabal install --dependencies-only
$ cabal build

Ändern Sie ../my-library, bauen Sie die Anwendung neu, und cabal erkannte die Änderung und kompilierte die Abhängigkeit neu. Das war 2013 das Nächste, was Haskell einem Monorepo-Workflow hatte.

Warum Sandboxes schließlich entfernt wurden

Sandboxes lösten die Isolation, ließen aber zwei Probleme ungelöst:

  • Keine projektübergreifende gemeinsame Nutzung. Jede Sandbox baute die Welt von Grund auf neu. Zehn Projekte, die von lens abhingen, bedeuteten zehn separate Builds von lens und seinem riesigen Abhängigkeitsbaum — langsam und plattenhungrig.
  • Keine maschinenübergreifende Reproduzierbarkeit. Eine Sandbox erfasste, wo die Pakete lagen, nicht einen präzisen, reproduzierbaren Plan, welche genauen Versionen gewählt würden. Zwei Entwickler, die dieselben Befehle eine Woche später ausführten, konnten trotzdem unterschiedliche Builds erhalten.

Die Lösung, konzeptionell von Nix entlehnt, bestand darin, Builds inhaltsadressiert und global geteilt zu machen: eine gegebene Paket-Version-Optionen-Kombination einmal bauen, sie unter einem Hash in einem unveränderlichen globalen Store ablegen, und jedes Projekt, das genau diese Kombination braucht, sie wiederverwenden lassen. Das sind die „nix-artigen lokalen Builds“, die unter dem Namen new-build in Cabal 2.0 erschienen.

Was Sie heute verwenden sollten (2026)

Quellcode auf einem Computerbildschirm
Quellcode auf einem Computerbildschirm

cabal sandbox wurde während der Cabal-2.x-Reihe als veraltet markiert und in späteren Versionen vollständig entfernt. Der moderne Standard-Workflow des heutigen cabal-install erfordert keinen init-Schritt — die Isolation ist automatisch:

# In any project directory with a .cabal file:
$ cabal build          # nix-style local build (v2 is the default now)
$ cabal run
$ cabal repl
$ cabal test

Was unter der Haube geschieht, ist genau die projektbezogene Isolation, die Sandboxes versprachen, plus die gemeinsame Nutzung, die ihnen fehlte:

  • Die Build-Artefakte Ihres Projekts landen in einem lokalen Verzeichnis dist-newstyle/ (der geistige Nachfolger von .cabal-sandbox/).
  • Kompilierte Abhängigkeiten landen in einem unveränderlichen globalen Store (~/.cabal/store oder unter ~/.local/state/cabal in neueren Layouts), indiziert über einen Hash aus Paket, Version, Abhängigkeiten und Optionen. Bauen Sie lens einmal; jedes Projekt, das denselben Plan wiederverwendet, referenziert denselben Store-Eintrag.
  • Eine Datei cabal.project ersetzt cabal.sandbox.config für Konfiguration, Multi-Paket-Builds und Pinning.

Das moderne Äquivalent von add-source

Lokale und entfernte Quell-Abhängigkeiten leben jetzt in cabal.project:

# cabal.project
packages: . ../my-library

-- or pull a dependency straight from git:
source-repository-package
    type: git
    location: https://github.com/owner/some-lib
    tag: 0a1b2c3d

Das ist strikt mächtiger als add-source: Es bewältigt mehrere lokale Pakete, exakte git-Pins und reproduzierbare Multi-Repository-Builds.

Reproduzierbares Versions-Pinning

Für die Reproduzierbarkeit, die Sandboxes nie boten, erzeugen Sie eine Freeze-Datei, die jede transitive Version festschreibt:

$ cabal freeze        # writes cabal.project.freeze
# commit it — every machine now resolves the identical plan

Und was ist mit Stack?

Stack geht dieselben Ziele aus einem anderen Winkel an: Statt Versionen pro Projekt aufzulösen, baut es gegen kuratierte Snapshots (die Stackage-Resolver wie lts-22.x), bei denen ein ganzer Satz von Paketen nachweislich zusammen kompiliert, und teilt Builds global pro Snapshot. Beide Werkzeuge sind 2026 solide und weit verbreitet; die praktische Aufteilung lautet grob „ich will die Standard-Toolchain und feine Kontrolle“ (Cabal) gegen „ich will ein kuratiertes, verlässliches Set schlüsselfertig“ (Stack). Keines von beiden verwendet Sandboxes — das Konzept ist auf beiden Seiten vollständig überholt.

Migrations-Spickzettel

cabal sandbox init                 →  (nothing — just `cabal build`)
cabal sandbox add-source ../lib    →  packages: . ../lib   (in cabal.project)
cabal install --dependencies-only  →  cabal build --only-dependencies
.cabal-sandbox/                    →  dist-newstyle/ (local) + ~/.cabal/store (shared)
cabal.sandbox.config               →  cabal.project (+ cabal.project.freeze)

Die Lektion, die Sandboxes lehrten — niemals globalen veränderlichen Zustand verschmutzen, pro Projekt isolieren — setzte sich so vollständig durch, dass sie unsichtbar wurde: Es ist einfach die Art, wie sich cabal build verhält. Wenn Sie dies lesen, weil ein alter Link oder ein Tutorial Sie auf cabal sandbox init verwiesen hat, können Sie diesen Befehl bedenkenlos vergessen. Führen Sie einfach cabal build aus.


Lesen Sie auch: Cabal 2.0 und die nix-artigen Builds · cabal-install parallelisieren · alle Leitfäden