Jump to content

Haskell/Packaging

From Wikibooks, open books for an open world

A guide to the best practice for creating a new Haskell project or program.

[edit | edit source]

Almost all new Haskell projects use the following tools. Each is intrinsically useful, but using a set of common tools also benefits everyone by increasing productivity, and you're more likely to get patches.

Build system

[edit | edit source]

Use Cabal. You should read the Cabal User's Guide. Particularly, section two and also section three will be very helpful. For generating a project, Cabal depends on Git, so we recommend installing it if you don't have it already.

Documentation

[edit | edit source]

For libraries, use Haddock.

Testing

[edit | edit source]

Pure code can be tested using QuickCheck (for tasty integration, which is recommended, tasty-quickcheck), hedgehog, or SmallCheck (though as of 2023 they recommend using falsify instead with tasty), and impure code can be tested with tasty-hunit.

To get started, try Haskell/Testing. For a slightly more advanced introduction, Simple Unit Testing in Haskell is a blog article about creating a testing framework for QuickCheck using some Template Haskell.

Structure of a simple project

[edit | edit source]

You can generate a new Haskell project using `cabal init`. It will interactively ask you a few questions by default to help set your project up. For most of the questions, the default are fine.

If you disable the interactive mode though, it will not ask you the questions. Assume that $DIR is the name of the current directory. It'll generate a project with this shape:

  • app/Main.hs -- the main haskell source file
  • $DIR.cabal -- the cabal build description
  • CHANGELOG.md

You can of course modify this, with subdirectories and multiple modules.

Here is a transcript on how you'd create a minimal Haskell project using Git and Cabal build it, install it and release.

The command tool 'cabal init' automates all this for you, but it's important that you understand all the parts first.

We will now walk through the creation of the infrastructure for a simple Haskell executable. Advice for libraries follows after.

Create a directory

[edit | edit source]

Create a directory for the source:

$ mkdir haq
$ cd haq

Generate a project skeleton using Cabal

[edit | edit source]

First, run cabal update to update cabal's repositories.

As directed earlier, run cabal init and answer the question with what you see fit.

What does the package build:
   1) Library
 * 2) Executable
   3) Library and Executable
   4) Test suite
Your choice? [default: Executable] 2
Do you wish to overwrite existing files (backups will be created) (y/n)? [default: n] y
Please choose version of the Cabal specification to use:
   1) 1.24  (legacy)
   2) 2.0   (+ support for Backpack, internal sub-libs, '^>=' operator)
   3) 2.2   (+ support for 'common', 'elif', redundant commas, SPDX)
   4) 2.4   (+ support for '**' globbing)
 * 5) 3.0   (+ set notation for ==, common stanzas in ifs, more redundant commas, better pkgconfig-depends)
   6) 3.4   (+ sublibraries in 'mixins', optional 'default-language')
Your choice? [default: 3.0] 5
Package name? [default: haq]
Package version? [default: 0.1.0.0]
Please choose a license:
   1) BSD-2-Clause
   2) BSD-3-Clause
   3) Apache-2.0
   4) MIT
   5) MPL-2.0
   6) ISC
   7) GPL-2.0-only
   8) GPL-3.0-only
   9) LGPL-2.1-only
  10) LGPL-3.0-only
  11) AGPL-3.0-only
  12) GPL-2.0-or-later
  13) GPL-3.0-or-later
  14) LGPL-2.1-or-later
  15) LGPL-3.0-or-later
  16) AGPL-3.0-or-later
  17) Other (specify)
Your choice? 16
Author name? [default: John Doe]
Maintainer email? [default: johndoe@example.com]
Project homepage URL? [optional]
Project synopsis? [optional] Haqify code
Project category:
   1) Codec
   2) Concurrency
   3) Control
   4) Data
   5) Database
   6) Development
   7) Distribution
   8) Game
   9) Graphics
  10) Language
  11) Math
  12) Network
  13) Sound
  14) System
  15) Testing
  16) Text
  17) Web
  18) Other (specify)
Your choice? [default: (none)]
What is the main module of the executable:
 * 1) Main.hs
   2) Main.lhs
   3) Other (specify)
Your choice? [default: Main.hs]
Application directory:
 * 1) app
   2) exe
   3) src-exe
   4) Other (specify)
Your choice? [default: app]
Choose a language for your executable:
 * 1) Haskell2010
   2) Haskell98
   3) GHC2021 (requires at least GHC 9.2)
   4) Other (specify)
Your choice? [default: Haskell2010] 3
Add informative comments to each field in the cabal file. (y/n)? [default: y]
[Log] Using cabal specification: 3.0
[Log] Creating fresh file LICENSE...
[Log] Creating fresh file CHANGELOG.md...
[Log] Creating fresh directory ./app...
[Log] Creating fresh file app/Main.hs...
[Log] Creating fresh file haq.cabal...
[Warning] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.

For most of these you can just pick your own, but as we pointed out earlier, the defaults are fine. For the question about choosing a language, you should pick the latest existing language if possible.

If your package uses other packages, e.g. array, you'll need to add them to the Build-Depends: field of the Cabal file.


Write some Haskell source

[edit | edit source]

Write your program:

$ cat > app/Main.hs
--
-- Copyright (c) 2006 Don Stewart - http://www.cse.unsw.edu.au/~dons
-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
--
import System.Environment

-- 'main' runs the main program
main :: IO ()
main = getArgs >>= print . haqify . head

haqify s = "Haq! " ++ s

Stick it in Git

[edit | edit source]
$ git init
$ git add --all
$ git commit -m 'Import haq source'
$ ls -A
.git app CHANGELOG.md LICENSE haq.cabal

Build your project

[edit | edit source]

Now build it!

$ cabal build

Run it

[edit | edit source]

And now you can run your cool project:

$ cabal run exe:haq -- me
"Haq! me"

Build some haddock documentation

[edit | edit source]

Generate some API documentation into dist/doc/*

$ cabal haddock --haddock-all

Which will tell you where the documentation is generated. You can view it via

$ w3m -dump dist-newstyle/build/$ARCH/ghc-x.x.x/haq-x.x.x.x/doc/html/package/Main.html # this is only to show what the path will vaguely look like
 haq Contents Index
 Main

 Synopsis
 main :: IO ()

 Documentation

 main :: IO ()
 main runs the main program

 Produced by Haddock version 0.7

No output? Make sure you have actually installed haddock.

Add some automated testing: QuickCheck

[edit | edit source]

We'll use QuickCheck to specify a simple property of our Haq.hs code. Create a tests module, Tests.hs, with some QuickCheck boilerplate:

$ cat > Tests.hs
import Char
import List
import Test.QuickCheck
import Text.Printf

main  = mapM_ (\(s,a) -> printf "%-25s: " s >> a) tests

instance Arbitrary Char where
    arbitrary     = choose ('\0', '\128')
    coarbitrary c = variant (ord c `rem` 4)

Now let's write a simple property:

$ cat >> Tests.hs 
-- reversing twice a finite list, is the same as identity
prop_reversereverse s = (reverse . reverse) s == id s
    where _ = s :: [Int]

-- and add this to the tests list
tests  = [("reverse.reverse/id", test prop_reversereverse)]

We can now run this test, and have QuickCheck generate the test data:

$ runhaskell Tests.hs
reverse.reverse/id       : OK, passed 100 tests.

Let's add a test for the 'haqify' function:

-- Dropping the "Haq! " string is the same as identity
prop_haq s = drop (length "Haq! ") (haqify s) == id s
    where haqify s = "Haq! " ++ s

tests  = [("reverse.reverse/id", test prop_reversereverse)
        ,("drop.haq/id",        test prop_haq)]

and let's test that:

$ runhaskell Tests.hs
reverse.reverse/id       : OK, passed 100 tests.
drop.haq/id              : OK, passed 100 tests.

Great!

Running the test suite from darcs

[edit | edit source]

We can arrange for darcs to run the test suite on every commit:

$ darcs setpref test "runhaskell Tests.hs"
Changing value of test from '' to 'runhaskell Tests.hs'

will run the full set of QuickChecks. (If your test requires it you may need to ensure other things are built too e.g.: darcs setpref test "alex Tokens.x;happy Grammar.y;runhaskell Tests.hs").

Let's commit a new patch:

$ darcs add Tests.hs
$ darcs record --all
What is the patch name? Add testsuite
Do you want to add a long comment? [yn]n
Running test...
reverse.reverse/id       : OK, passed 100 tests.
drop.haq/id              : OK, passed 100 tests.
Test ran successfully.
Looks like a good patch.
Finished recording patch 'Add testsuite'

Excellent, now patches must pass the test suite before they can be committed.

Tag the stable version, create a tarball, and sell it!

[edit | edit source]

Tag the stable version:

$ darcs tag
What is the version name? 0.0
Finished tagging patch 'TAG 0.0'

Advanced Darcs functionality: lazy get

[edit | edit source]

As your repositories accumulate patches, new users can become annoyed at how long it takes to accomplish the initial darcs get. (Some projects, like yi or GHC, can have thousands of patches.) Darcs is quick enough, but downloading thousands of individual patches can still take a while. Isn't there some way to make things more efficient?

Darcs provides the --lazy option to darcs get. This enables to download only the latest version of the repository. Patches are later downloaded on demand if needed.


Distribution

[edit | edit source]

When distributing your Haskell program, you have roughly three options:

  1. distributing via a Darcs repository
  2. distributing a tarball
    1. a Darcs tarball
    2. a Cabal tarball

With a Darcs repository, if it is public, then you are done. However: perhaps you don't have a server with Darcs, or perhaps your computer isn't set up for people to darcs pull from it. In which case you'll need to distribute the source via tarball.

Tarballs via darcs
[edit | edit source]

Darcs provides a command where it will make a compressed tarball, and it will place a copy of all the files it manages into it. (Note that nothing in _darcs will be included - it'll just be your source files, no revision history.)

$ darcs dist -d haq-0.0
Created dist as haq-0.0.tar.gz

And you're all set up!

Tarballs via Cabal
[edit | edit source]

Since our code is cabalised, we can create a tarball with Cabal directly:

$ runhaskell Setup.lhs sdist
Building source dist for haq-0.0...
Source tarball created: dist/haq-0.0.tar.gz

This has advantages and disadvantages compared to a Darcs-produced tarball. The primary advantage is that Cabal will do more checking of our repository, and more importantly, it'll ensure that the tarball has the structure needed by HackageDB and cabal-install.

However, it does have a disadvantage: it packages up only the files needed to build the project. It will deliberately fail to include other files in the repository, even if they turn out to be necessary at some point[1]. To include other files (such as Test.hs in the above example), we need to add lines to the cabal file like:

extra-source-files: Tests.hs

If we had them, we could make sure files like AUTHORS or the README get included as well:

data-files: AUTHORS, README

Summary

[edit | edit source]

The following files were created:

   $ ls
   Haq.hs           Tests.hs         dist             haq.cabal
   Setup.lhs        _darcs           haq-0.0.tar.gz

Libraries

[edit | edit source]

The process for creating a Haskell library is almost identical. The differences are as follows, for the hypothetical "ltree" library:

Hierarchical source

[edit | edit source]

The source should live under a directory path that fits into the existing module layout guide. So we would create the following directory structure, for the module Data.LTree:

   $ mkdir Data
   $ cat > Data/LTree.hs 
   module Data.LTree where

So our Data.LTree module lives in Data/LTree.hs

The Cabal file

[edit | edit source]

Cabal files for libraries list the publicly visible modules, and have no executable section:

   $ cat ltree.cabal 
   Name:                ltree
   Version:             0.1
   Description:         Lambda tree implementation
   License:             BSD3
   License-file:        LICENSE
   Author:              Don Stewart
   Maintainer:          dons@cse.unsw.edu.au
   Build-Depends:       base
   Exposed-modules:     Data.LTree

We can thus build our library:

   $ runhaskell Setup.lhs configure --prefix=$HOME --user
   $ runhaskell Setup.lhs build    
   Preprocessing library ltree-0.1...
   Building ltree-0.1...
   [1 of 1] Compiling Data.LTree       ( Data/LTree.hs, dist/build/Data/LTree.o )
   /usr/bin/ar: creating dist/build/libHSltree-0.1.a

and our library has been created as a object archive. On *nix systems, you should probably add the --user flag to the configure step (this means you want to update your local package database during installation). Now install it:

   $ runhaskell Setup.lhs install
   Installing: /home/dons/lib/ltree-0.1/ghc-6.6 & /home/dons/bin ltree-0.1...
   Registering ltree-0.1...
   Reading package info from ".installed-pkg-config" ... done.
   Saving old package config file... done.
   Writing new package config file... done.

And we're done! You can use your new library from, for example, ghci:

   $ ghci -package ltree
   Prelude> :m + Data.LTree
   Prelude Data.LTree> 

The new library is in scope, and ready to go.

More complex build systems

[edit | edit source]

For larger projects it is useful to have source trees stored in subdirectories. This can be done simply by creating a directory, for example, "src", into which you will put your src tree.

To have Cabal find this code, you add the following line to your Cabal file:

   hs-source-dirs: src

Cabal can set up to also run configure scripts, along with a range of other features. For more information consult the Cabal documentation.

Internal modules

[edit | edit source]

If your library uses internal modules that are not exposed, do not forget to list them in the other-modules field:

   other-modules: My.Own.Module

Failing to do so (as of GHC 6.8.3) may lead to your library deceptively building without errors but actually being unusable from applications, which would fail at build time with a linker error.

Automation

[edit | edit source]

cabal init

[edit | edit source]

A package management tool for Haskell called cabal-install provides a command line tool to help developers create a simple cabal project. Just run and answer all the questions. Default values are provided for each.

$ cabal init
Package name [default "test"]? 
Package version [default "0.1"]? 
Please choose a license:
...

mkcabal

[edit | edit source]

mkcabal is a tool that existed before cabal init, which also automatically populates a new cabal project :

   darcs get http://code.haskell.org/~dons/code/mkcabal

N.B. This tool does not work in Windows. The Windows version of GHC does not include the readline package that this tool needs.

Usage is:

$ mkcabal
Project name: haq
What license ["GPL","LGPL","BSD3","BSD4","PublicDomain","AllRightsReserved"] ["BSD3"]: 
What kind of project [Executable,Library] [Executable]: 
Is this your name? - "Don Stewart " [Y/n]: 
Is this your email address? - "<dons@cse.unsw.edu.au>" [Y/n]: 
Created Setup.lhs and haq.cabal
$ ls
Haq.hs    LICENSE   Setup.lhs _darcs    dist      haq.cabal

which will fill out some stub Cabal files for the project 'haq'.

To create an entirely new project tree:

$ mkcabal --init-project
Project name: haq
What license ["GPL","LGPL","BSD3","BSD4","PublicDomain","AllRightsReserved"] ["BSD3"]: 
What kind of project [Executable,Library] [Executable]: 
Is this your name? - "Don Stewart " [Y/n]: 
Is this your email address? - "<dons@cse.unsw.edu.au>" [Y/n]: 
Created new project directory: haq
$ cd haq
$ ls
Haq.hs    LICENSE   README    Setup.lhs haq.cabal

Licenses

[edit | edit source]

Code for the common base library package must be BSD licensed or something more Free/Open. Otherwise, it is entirely up to you as the author.

Choose a licence (inspired by this). Check the licences of things you use, both other Haskell packages and C libraries, since these may impose conditions you must follow.

Use the same licence as related projects, where possible. The Haskell community is split into 2 camps, roughly, those who release everything under BSD or public domain, and the GPL/LGPLers (this split roughly mirrors the copyleft/noncopyleft divide in Free software communities). Some Haskellers recommend specifically avoiding the LGPL, due to cross module optimisation issues. Like many licensing questions, this advice is controversial. Several Haskell projects (wxHaskell, HaXml, etc.) use the LGPL with an extra permissive clause to avoid the cross-module optimisation problem.

Releases

[edit | edit source]

It's important to release your code as stable, tagged tarballs. Don't just rely on darcs for distribution.

  • darcs dist generates tarballs directly from a darcs repository

For example:

$ cd fps
$ ls       
Data      LICENSE   README    Setup.hs  TODO      _darcs    cbits dist      fps.cabal tests
$ darcs dist -d fps-0.8
Created dist as fps-0.8.tar.gz

You can now just post your fps-0.8.tar.gz

You can also have darcs do the equivalent of 'daily snapshots' for you by using a post-hook.

put the following in _darcs/prefs/defaults:

 apply posthook darcs dist
 apply run-posthook

Advice:

  • Tag each release using darcs tag. For example:
$ darcs tag 0.8
Finished tagging patch 'TAG 0.8'

Then people can darcs get --lazy --tag 0.8, to get just the tagged version (and not the entire history).

Hosting

[edit | edit source]

You can host public and private Darcs repositories on http://patch-tag.com/ for free. Otherwise, a Darcs repository can be published simply by making it available from a web page. Another option is to host on the Haskell Community Server at http://code.haskell.org/. You can request an account via http://community.haskell.org/admin/. You can also use https://github.com/ for Git hosting.

Example

[edit | edit source]

A complete example of writing, packaging and releasing a new Haskell library under this process has been documented.



Notes

  1. This is actually a good thing, since it allows us to do things like create an elaborate test suite which doesn't get included in the tarball, so users aren't bothered by it. It also can reveal hidden assumptions and omissions in our code - perhaps your code was only building and running because of a file accidentally generated.