fgaz

I am a student of computer science. I build things, and sometimes write here

Multiple public libraries in a single Cabal package (GSoC 2018)

Reading time: 4 minutes
Table of contents
  1. Why
  2. How
    1. Exposing a sublibrary
    2. Depending on a public sublibrary
  3. Known bugs
  4. Acknowledgements
  5. Links
  6. Footnotes

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.

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 Cabal>=3.0.0.0 and GHC>=8.81.

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.

Exposing a sublibrary

To expose a sublibrary to other packages, simply add a visibility: public field to your library stanza:

library sublibname
    visibility: public
⚠ Warning - for library authors

cabal-install's solver isn't public-library-aware yet, and until #6047 is merged it will happily choose to depend on a private library if it falls within the specified version range, and then it will fail at the configure step. To keep things working smoothly, remember that making a library private is a breaking change, much like removing a function or module, and thus requires a major version bump, as per the PVP.

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, see next paragraph):

executable my-exe
    build-depends: packagename:packagename >=1.0 && <1.1

When depending on multiple libraries from a single package you can also use this syntax:

executable my-exe
    build-depends: packagename:{lib1, lib2} >=1.0 && <1.1
⚠ Warning - for all developers when depending on an external sublibrary

cabal-install's solver isn't public-library-aware yet, and until #6047 is merged it will happily choose to depend on a private library if it falls within the specified version range, and then fail at the configure step. To keep things working smoothly, remember to specify correct version bounds when depending on sublibraries.

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

There are a few minor bugs in the implementation. They are mostly edge cases, but it's good to know about them:

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!

Footnotes

1

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.