Creating a package on Hackage
October 25, 2014
How do you go from a pile of Haskell code on your machine to a finished package on Hackage in all its tactfully understated glory? I recently took this journey and some of the steps were tricky enough that I thought someone ought to write a guide.
Step 1 - the account
Register a user account. Like many steps in Hackage, this is a somewhat human and manual process. A person has to review your submission and deem you worthy. So I guess don’t pick a profile name like javascriptFTW
that would anger them.
Step 2 - package structure
Structure your package in a standard way. Source directories are nested and named after the exposed module path. Your test suite lives in its own place and needs some boilerplate configuration. And of course you’ll need a good cabal file. Want the shortcut? Run fujimura/hi to generate your project structure. By default it will choose a BSD license and use hspec
for tests. Speaking for myself I changed it to an MIT license and hspec2.
$ hi --module-name "Foo.Bar.Baz" --author "J Doe" --email "jdoe@me.com"
Step 3 - cabal
Customize the package cabal file. If you’re wondering whether it includes enough information, check with
$ cabal check
which will point out any problems. In fact Hackage will refuse a package upload that fails the check. However there are fields you might like to add beyond the bare minimum, such as the Homepage
field with a link to the project on Github. See this reference of all the fields.
Step 4 - docs
Add Haddock documentation to your code as comments. There is more to Haddock than can reasonably fit in a little guide like this, but a good way to go is copy what people do in a popular package you admire. Generally the structure of generated docs is determined in your module declaration. Be sure to include some example code since it gives a quick overview to would-be users.
module Foo.Bar.Baz
(
-- * Example usage
-- $use
-- * A section
Thingie(..)
-- * More stuff
, fun
, joy
) where
Notice you can create chunks by name like $use
and interpolate them at the right place in the module declaration. To preview your docs locally run
$ cabal haddock
which will build the docs in dist/doc/html/your-pkg/index.html
. Note that unlike the custom in other languages, Haskellers usually don’t put much info into READMEs. At first I thought they were being negligent but I learned that the combination of strong types and Haddock’s structure provide a standardized documentation experience that works better. I like to add a link in my Github readme to point at the Hackage docs to help those unfamiliar with the convention.
Step 5 - CI
Enable continuous integration. You want to ensure that
- your usual tests pass
- your project works in a few versions of GHC
- your cabal file is well-formed
- a source distribution can be generated
- docs build cleanly
Here’s a nice Travis config adapted from bitemyapp who got it from hvr.
language: haskell
ghc:
- 7.6
- 7.8
before_install:
- sudo add-apt-repository -y ppa:hvr/ghc
- sudo apt-get update
- sudo apt-get install happy-1.19.3
- sudo apt-get install alex-3.1.3
- export PATH=~/.cabal/bin:$PATH # for newer alex
- cabal update
- cabal install alex happy
install:
- cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls
script:
# -v2 provides useful information for debugging
- cabal configure --enable-tests --enable-benchmarks -v2
# this builds all libraries and executables
# (including tests/benchmarks)
- cabal build
- cabal test
- cabal check
# tests that a source-distribution can be generated
- cabal sdist
# check that the generated source-distribution can be built & installed
- export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;
cd dist/;
if [ -f "$SRC_TGZ" ]; then
cabal install "$SRC_TGZ";
else
echo "expected '$SRC_TGZ' not found";
exit 1;
fi
Step 6 - dependencies
Add some constraints to your library’s dependencies in the cabal file. Locking major versions of dependencies will help prevent surprise Cabal build failures by your users. This is about the only rule of thumb I know. Setting constraints more intelligently is certainly possible, and I welcome your comments about your own strategies.
Step 7 - candidate package
Your tests pass, your docs look good. It’s time to upload a “package candidate” for a last check that things are OK on the real Hackage.
Create a source distribution
cabal sdist
This will generate dist/your-pkg-x.y.z.tar.gz
. Select this file in the candidate uploader and give it a go.
Step 8 - release it
Some people like to leave their packages as candidates for a while to find bugs etc. The quality on hackage is generally pretty high so you want to avoid throwing things up there half-baked. Generally if you’ve followed the previous steps you should be in pretty good shape to release your package for real though.
$ cabal upload dist/your-pkg-x.y.z.tar.gz
All done, right? Time to celebrate! Not necessarily. Recently Hackage has been failing to run haddock remotely to generate documentation. The maintainers claim there is a small delay but I have found sometimes it never happens. Thankfully Edward Kmett created a script to build your own docs and push them to Hackage.
#!/bin/bash
set -e
if [ "$#" -ne 1 ]; then
echo "Usage: scripts/hackage-docs.sh HACKAGE_USER"
exit 1
fi
user=$1
cabal_file=$(find . -maxdepth 1 -name "*.cabal" -print -quit)
if [ ! -f "$cabal_file" ]; then
echo "Run this script in the top-level package directory"
exit 1
fi
pkg=$(awk -F ":[[:space:]]*" 'tolower($1)=="name" { print $2 }' < "$cabal_file")
ver=$(awk -F ":[[:space:]]*" 'tolower($1)=="version" { print $2 }' < "$cabal_file")
if [ -z "$pkg" ]; then
echo "Unable to determine package name"
exit 1
fi
if [ -z "$ver" ]; then
echo "Unable to determine package version"
exit 1
fi
echo "Detected package: $pkg-$ver"
dir=$(mktemp -d build-docs.XXXXXX)
trap 'rm -r "$dir"' EXIT
cabal haddock --hoogle --hyperlink-source --html-location='/package/$pkg-$version/docs' --contents-location='/package/$pkg-$version'
cp -R dist/doc/html/$pkg/ $dir/$pkg-$ver-docs
tar cvz -C $dir --format=ustar -f $dir/$pkg-$ver-docs.tar.gz $pkg-$ver-docs
curl -X PUT \
-H 'Content-Type: application/x-tar' \
-H 'Content-Encoding: gzip' \
-u "$user" \
--data-binary "@$dir/$pkg-$ver-docs.tar.gz" \
"https://hackage.haskell.org/package/$pkg-$ver/docs"
Now you will have published a package that looks good and will be be easy for people to use. So get that code off your computer and onto Hackage and contribute to the glorious Haskell ecosystem!