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

2019-11-14

Reading time: 4 minutes

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.

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.

To use the multiple public libraries feature you need Cabal>=3.0.0.0 and GHC>=8.8 .

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:

#6038: To have sublibraries, a package must also have a main (nameless) library. The main library can be empty: […other attributes/stanzas…] library […other attributes/stanzas…]

#6083: Due to a bug in the handling of qualified and unqualified dependency syntaxes, internal libraries (sublibraries of the same package) will take priority over external packages with the same name. So, if you have an internal library somelib , you won't be able to depend on a package named somelib , even if you use the somelib:somelib syntax.

, you won't be able to depend on a package named , even if you use the syntax. #5846: In some cases, omitting the version bounds causes a parsing failure.

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!