Packages and Cabal
A Haskell program is built from packages — units of distribution that contain modules, libraries, and executables — managed by Cabal (the original build system) or Stack (a curated workflow built on top of Cabal). Both tools manage dependencies declared against repositories: Hackage (the open registry of community packages) and Stackage (a curator-style snapshot of Hackage with build-tested, mutually-compatible package versions). The compilation pipeline runs through GHC (the Glasgow Haskell Compiler), the reference implementation; alternative compilers (GHCJS for JavaScript, Eta for the JVM) exist but are uncommon in modern use.
This page covers the package model, the Cabal and Stack workflows, the principal repositories, the build pipeline, and the conventions for modern Haskell project organisation. The treatment of modules within a single project — module declarations, imports, exports — is in Modules.
Packages
A package is the unit of distribution. A package contains:
- One or more libraries — collections of modules made available for import.
- Zero or more executables — programs that can be built and run.
- Zero or more test suites — programs that test the package.
- Zero or more benchmarks.
- Metadata — name, version, dependencies, license, build configuration.
The package is described by either a .cabal file or a package.yaml (with hpack translating to .cabal). The .cabal format is the canonical one; the package.yaml form is shorthand.
A typical package layout:
myproject/
├── myproject.cabal -- the package description
├── README.md
├── LICENSE
├── src/ -- library source
│ ├── Data/Foo.hs
│ └── Data/Foo/Internal.hs
├── app/ -- executable source
│ └── Main.hs
└── test/ -- test source
└── Spec.hs
A minimal .cabal file:
cabal-version: 2.4
name: myproject
version: 0.1.0.0
synopsis: A short description.
description: A longer description.
license: BSD-3-Clause
author: Author Name
maintainer: author@example.com
build-type: Simple
library
exposed-modules: Data.Foo, Data.Bar
other-modules: Data.Foo.Internal
build-depends: base ^>=4.18, text, containers
hs-source-dirs: src
default-language: Haskell2010
executable myprogram
main-is: Main.hs
build-depends: base ^>=4.18, myproject
hs-source-dirs: app
default-language: Haskell2010
test-suite myproject-tests
type: exitcode-stdio-1.0
main-is: Spec.hs
build-depends: base ^>=4.18, myproject, hspec
hs-source-dirs: test
default-language: Haskell2010
The principal stanzas:
library— a library that other packages may depend on.executable <name>— a program that can be built and run.test-suite <name>— a test program.benchmark <name>— a benchmark.common <name>— shared configuration (since cabal 2.2).
Each stanza has its own build-depends (the packages it imports), hs-source-dirs (where to look for source), default-language (Haskell2010 or Haskell98), and default-extensions (a list of LANGUAGE pragmas applied to every module).
Cabal
Cabal is the original Haskell build system, distributed with GHC. Modern Cabal (cabal 3+) uses a Nix-style approach: dependencies are stored in a per-user package cache, and each project pinpoints its dependencies through a local solver run.
The principal commands:
cabal init # create a new project
cabal build # compile the project
cabal run # build and run the executable
cabal test # run tests
cabal repl # open a REPL with the project loaded
cabal install # install an executable
cabal update # update the package index
cabal freeze # lock dependencies in cabal.project.freeze
cabal outdated # check for newer versions
cabal sdist # create a source distribution
The conventional cabal-based project includes:
myproject.cabal— the package description.cabal.project— multi-package configuration (optional; for projects with multiple packages).cabal.project.freeze— pinned dependency versions (optional; produced bycabal freeze).
The cabal.project file admits configuration:
packages: .
optional-packages: vendored/*/
constraints:
text >= 2.0,
aeson >= 2.0
Stack
Stack is an alternative workflow built on top of Cabal. Its principal contribution is snapshots — curated, build-tested combinations of package versions from Stackage:
myproject/
├── stack.yaml -- Stack-specific configuration
├── package.yaml -- the package description (using hpack)
├── ...
A typical stack.yaml:
resolver: lts-22.0
packages:
- .
extra-deps:
- some-package-1.2.3
- another-package-4.5.6@sha256:...
flags: {}
The resolver specifies a Stackage snapshot — a known-good combination of package versions. Stack pinpoints every package to either the snapshot’s version or to an explicitly-listed extra-deps.
The principal commands:
stack init # create a new project
stack build # compile
stack run # build and run
stack test # run tests
stack repl # REPL
stack install # install executables
stack update # update package index
stack new <name> # new project from a template
stack new <name> simple # minimal template
Stack downloads the appropriate GHC version automatically; multiple GHC versions coexist on the same machine, each used by its corresponding snapshot.
Cabal vs Stack
The conventional contemporary advice:
- Cabal if the project’s dependencies are well-known and the snapshot constraint is too restrictive.
- Stack if reproducibility across machines and stable dependency versions matter.
Both tools produce essentially equivalent outputs; the choice is largely a matter of workflow preference. Modern cabal (3+) has caught up substantially with Stack on dependency management.
For library development, the conventional choice is Cabal — it is the standard tooling and what Hackage understands. For application development with stable deployment requirements, Stack is often preferred.
Hackage
Hackage is the open Haskell package registry — every published Haskell package is uploaded here. The site hosts:
- Documentation — generated by Haddock from the source.
- Source distributions —
.tar.gzarchives for download. - Version history — every published version of every package.
- Dependencies — tracked at the package level.
Browsing Hackage is the conventional way to discover packages; Hoogle (treated below) is the conventional way to search for functions by type signature.
To upload a package:
cabal sdist
cabal upload path/to/dist/sdist/myproject-0.1.0.0.tar.gz
Maintainers publish a candidate first, verify Hackage’s automated build, and then promote to a regular release.
Stackage
Stackage is a curated subset of Hackage that ships in snapshots — combinations of package versions known to build together. The principal snapshots:
- LTS Haskell (Long-Term Support) — stable, slowly-updating snapshots; the conventional choice for production.
- Nightly — the latest packages; the conventional choice for development.
Each snapshot pins every included package to a specific version. Stack uses a snapshot as the default dependency constraint; Cabal users can also use Stackage snapshots through import directives in cabal.project.
Hoogle
Hoogle is a search engine for Haskell functions and types. It admits queries by:
- Function name:
Hoogle mapfinds the variousmap-named functions. - Type signature:
Hoogle (a -> b) -> [a] -> [b]findsmap,fmap, and similar. - Module name:
Hoogle Data.ListlistsData.List’s exports.
The mechanism is the conventional Haskell “I want a function with this shape” discovery tool. The web interface is at hoogle.haskell.org; offline copies are admitted via hoogle generate plus hoogle server.
Haddock
Haddock is the standard Haskell documentation tool. It extracts documentation from comments in source files and produces HTML:
-- | The principal greeting function.
--
-- Returns a personalised greeting based on the time of day.
greet :: TimeOfDay -> Name -> String
greet time name =
case time of
Morning -> "Good morning, " ++ name
Afternoon -> "Good afternoon, " ++ name
Evening -> "Good evening, " ++ name
Night -> "Hello, " ++ name
The -- | introduces the documentation for the following declaration; -- ^ documents the preceding (typically used after a constructor argument).
Haddock supports:
- Module documentation — at the top of each module.
- Section markers —
-- *,-- **,-- ***for grouping in the export list. - Embedded HTML and links —
[link text](URL)and Haddock-specific cross-references. - Code examples — between
>markers.
Generation:
cabal haddock # generate Haddock for the project
stack haddock --open # generate and open in browser
Haddock is the conventional Haskell documentation system; nearly every public package on Hackage has Haddock-generated documentation visible on the package’s page.
The build pipeline
The conventional Haskell build pipeline:
- Source files (
.hs) — Haskell source. - Preprocessing — for files with
.lhs(literate Haskell) or with CPP/LANGUAGE directives. - GHC compilation — produces interface files (
.hi), object files (.o), and (for the executable) the linked binary. - Linking — combines object files and library code into the final executable.
The compiler can be invoked directly:
ghc Main.hs # compile and link
ghc -c Main.hs # compile only
ghc --make Main.hs # compile dependencies first
ghc -O2 Main.hs # optimisation level 2
But the conventional tools (Cabal, Stack) handle the orchestration. The --make flag tells GHC to walk the dependency graph; the -O2 flag enables substantial optimisation (the default for Cabal/Stack release builds).
GHC produces:
.hifiles — interface files, recording the module’s exports..ofiles — object files, the compiled bytecode..dyn_ofiles — when building dynamic libraries.
The interface files cache the compiler’s analysis; recompilation only redoes work for changed modules.
Project structure
A typical multi-module Haskell project:
myapp/
├── myapp.cabal -- package description
├── cabal.project -- multi-package config
├── README.md
├── LICENSE
├── src/ -- library
│ ├── MyApp/
│ │ ├── Core.hs
│ │ ├── Database.hs
│ │ └── Web.hs
│ └── MyApp.hs -- the public entry point
├── app/ -- executable
│ └── Main.hs
└── test/
└── Spec.hs
The MyApp.hs is the conventional “public façade” — it re-exports the names users should see. The MyApp.*.hs modules are the implementation details.
For larger projects, multi-package layouts admit clearer separation:
myapp/
├── cabal.project
├── core/ -- core library
│ ├── core.cabal
│ └── src/
├── web/ -- web layer
│ ├── web.cabal
│ └── src/
└── cli/ -- command-line interface
├── cli.cabal
└── app/
Each subdirectory is its own package with its own .cabal file; the cabal.project lists them all.
LANGUAGE extensions
Modern Haskell code uses substantial extensions through {-# LANGUAGE … #-} pragmas:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
module MyModule where
-- ...
For project-wide enabling, the default-extensions in the .cabal file:
library
default-extensions:
OverloadedStrings
DeriveGeneric
DeriveFunctor
StandaloneDeriving
BangPatterns
Modern projects often use GHC2021 (a curated set introduced with GHC 9.2) as the default language, plus selected additional extensions:
library
default-language: GHC2021
default-extensions:
OverloadedStrings
TypeFamilies
The combination admits a productive modern Haskell vocabulary without per-file boilerplate.
Common patterns
Library + executable
library
exposed-modules: MyApp.Core
build-depends: base, text
hs-source-dirs: src
executable myapp
main-is: Main.hs
build-depends: base, myapp
hs-source-dirs: app
The library exposes the implementation; the executable depends on the library and provides the entry point. The library is reusable; the executable is a thin wrapper.
Internal modules
-- Public API:
module Data.Foo (Foo, mkFoo, runFoo) where
import Data.Foo.Internal (Foo(..), mkFoo, runFoo)
-- Internals (in case some library or test needs deep access):
module Data.Foo.Internal where
data Foo = Foo Int String -- the constructor is here, not in Data.Foo
The pattern admits exporting a public surface from Data.Foo while making the internals available (with the implicit “use at your own risk”) in Data.Foo.Internal.
Setup.hs
For most modern Cabal packages, build-type: Simple is sufficient and no Setup.hs is needed. For packages that need custom build steps (running c2hs, generating code), a custom Setup.hs:
import Distribution.Simple
main = defaultMain
The defaultMain invokes the standard build; custom builds replace it with a more elaborate procedure.
cabal.project for multi-package
packages:
core/
web/
cli/
constraints:
base ^>=4.18,
text ^>=2.0
The mechanism admits a single cabal build for the whole project.
A note on the contemporary toolchain
The Haskell toolchain is well-established but has rough edges. The conventional contemporary recommendations:
- GHC: the reference compiler. Version 9.x is current; LTS releases pair well with Stackage LTS snapshots.
- GHCup: the conventional installer; manages multiple GHC versions, Cabal versions, and HLS (the language server).
- Cabal: the build tool; cabal 3+ is substantially better than older versions.
- Stack: the alternative workflow; favours reproducible builds.
- HLS (Haskell Language Server): the LSP server for editor integration; supported by VSCode, Vim, Emacs, IntelliJ.
- Hoogle: the search engine.
- Hackage: the package registry.
- Stackage: the curated snapshots.
- Haddock: the documentation tool.
The combination admits a productive modern workflow. Programmers coming from Rust’s cargo or Java’s mvn should expect a less polished but adequate tooling experience; the language’s strengths are elsewhere.