Multiple public libraries in a single Cabal package (GSoC 2018)
Reading time: 4 minutesTable of contents
In summer 2018, during my last GSoC, I developed the "multiple public libraries in a single package" Cabal feature. In this long overdue post I explain why and how to use the feature.
2022-08-11: The post was updated in light of the cabal 3.8.1.0 release.
¶ Why
Large scale Haskell projects tend to have a problem with lockstep distribution of packages (especially backpack projects, being extremely granular). The unit of distribution (package) coincides with the buildable unit of code (library), and consequently each library of such an ecosystem (ex. amazonka) requires duplicate package metadata (and tests, benchmarks…).
Having multiple libraries in a single package allows the separation of these two concerns, and prevents redundant work and potential inconsistencies.
¶ How
To use the multiple public libraries feature you need at minimum Cabal>=3.0.0.0 and GHC>=8.81.
Many of the bugs listed at https://github.com/haskell/cabal/issues/5660 are not fixed in cabal<3.8. To avoid problems, if you are using an older version consider upgrading to 3.8.1.0 or later.
The feature was introduced with the .cabal
spec version 3.0, so you'll have to
have at least cabal-version: 3.0
in your .cabal
file.
If you are starting a new project you can use cabal init --cabal-version=3.0
(or later).
¶ Exposing a sublibrary
To expose a sublibrary to other packages, simply add a visibility: public
field to your library stanza:
library sublibname
visibility: public
While cabal-install
's solver is aware of public libraries, and Setup.hs
will give proper errors when trying to depend on a private library, it's still
a good idea to perform a
major version bump
when changing the visibility
of a library from public
to private
.
¶ Depending on a public sublibrary
To depend on a public sublibrary, add the package it belongs to to your
dependencies, followed by a :
, followed by the name of the sublibrary:
executable my-exe
build-depends: packagename:sublibname >=1.0 && <1.1
If you omit the :sublibname
part, you are specifying a dependency on the
main (unnamed) library, so you don't need to change existing dependencies.
You can explicitly depend on the main library by using the package name as sublibrary name (this is mostly needed when depending on multiple sublibraries in a single line, see next paragraph):
executable my-exe
build-depends: packagename:packagename >=1.0 && <1.1
When depending on multiple libraries belonging to a single package you can also use this shorthand syntax:
executable my-exe
build-depends: packagename:{lib1, lib2} >=1.0 && <1.1
which is equivalent to
executable my-exe
build-depends: packagename:lib1 >=1.0 && <1.1
build-depends: packagename:lib2 >=1.0 && <1.1
While cabal-install
's solver will rule out package versions that don't
provide the required libraries, it's still a good idea to enforce that with
version bounds.
Ensure that the lower bound is strict enough that older versions of the dependency where the sublibrary wasn't public / didn't exist are correctly excluded, and that the upper bound is strict enough to exclude breaking changes.
¶ Known bugs
Known bugs and missing features are tracked at https://github.com/haskell/cabal/issues/5660.
¶ Acknowledgements
Of course this wouldn't have been possible without the help of my mentors, Mikhail and Edward, and many other members of the Haskell community. Thanks to all!
¶ Links
¶ Footnotes
Older GHCs are not able to properly store the visibility of a library, so with GHC<8.8 all sublibraries will be treated as private.