Personal tools

Cabal/Developer-FAQ

From HaskellWiki

< Cabal
Revision as of 18:07, 9 January 2011 by Lemming (Talk | contribs)

Jump to: navigation, search

Contents

1 How do I handle Haskell language extensions?

If your code uses some of the advanced Haskell extensions, you have a number of options.

Each language extension has a standard name. You can find the list in Language.Haskell.Extension.

  1. The simplest way, if you're distributing via Cabal, is to add extensions: and the list of extensions names to your .cabal file. For example extensions: CPP, ForeignFunctionInterface. This allows every module in the package to use the listed extensions. Cabal will pass the appropriate flags to the compiler and it even checks that the compiler supports those extensions.
  2. The best way to do it, if you know your users are using GHC 6.8.x is the new LANGUAGE pragma. This allows you to enable just those extensions that you are using in a particular module. For example stick
    {-# LANGUAGE CPP, ForeignFunctionInterface #-}
    at the top of the file.
  3. An equivalent of the LANGUAGE pragma for older versions of GHC is the OPTIONS_GHC pragma (or for GHC 6.4 and older the OPTIONS pragma). The downside is that you have to know the GHC command line flag for the extensions you want. For example stick
    {-# OPTIONS_GHC -fffi -cpp #-}
    at the top of the file. Many other language extensions are enabled by -fglasgow-exts.


2 Conditional compilation

2.1 Executables with additional dependencies

Example Question: I want to build an executable for automated tests that depend on QuickCheck. However, building of tests is not necessary for installing the library and thus the dependency on QuickCheck is not always necessary. How can I explain that to Cabal?

Answer: Write something like the following into your Cabal file:

Flag buildTests

 description: Build test suite
 default:     False

Library

 ...

Executable test

 If flag(buildTests)
   Build-Depends: QuickCheck
 Else
   Buildable: False
 GHC-Options:    -Wall
 Hs-Source-Dirs: src
 Main-Is:        Test.hs

2.2 Enabling additional features via Cabal flags

Question: I like to let the user enable extended functionality using a Cabal flag. Is this the right way?

Answer: Certainly not. Since other packages can distinguish packages only according to their name and their version, it is not a good idea to allow different APIs for the same package version. Cumbersome as it is you have to move extra features to a separate package.

2.3 Adapt to different systems without CPP

Question: The Cabal documentation suggests to use the C preprocessor on Haskell code in order to adapt to system specific behaviour. I find this ugly. Is there another way?

Answer: You can put modules with the same name and API in different directories and choose the right module using a Cabal test.

-- necessary packaging all module variants when doing 'sdist' Extra-Source-Files:

 src-i386/CPU/Dependent/Module.hs
 src-gen/CPU/Dependent/Module.hs

Exposed-Modules:

 CPU.Dependent.Module
 ...

If arch(i386)

 Hs-Source-Dirs: src-i386

Else

 Hs-Source-Dirs: src-gen

On the one hand it is a bit cumbersome to maintain the separate directory trees, on the other hand the modules can be read as they are and the reader does not need to untangle nested #if directives. Separation of OS dependent code into individual modules is certainly a good idea anyway, however if you want to adapt to many combinations of compilers and their versions, operating systems and processors then the above approach will make things worse.

3 Unix

3.1 Installing manpages

Unfortunately this is not natively supported by Cabal (1.10 as of writing). You have to write some code into your Setup module. See the pandoc package for an example.

4 Windows

4.1 Building DLLs with Cabal

Cabal does not currently support building dlls on windows out of the box. Some details about why can be found here: http://www.haskell.org/ghc/docs/6.4.2/html/users_guide/packages.html http://www.haskell.org/ghc/docs/6.4.2/html/users_guide/win32-dlls.html

This means that we have to do a bit of hackery to get Cabal to build a dll from a library. As long as we build a single DLL from the entire project the dll should behave as expected. Using the Setup.lhs listed below and following the directions listed above (the second link) I was able to use Visual Haskell to build a dll which I could load into Visual Basic for testing.

The following Setup.lhs should do the trick for most projects which consist of a single library which needs to be built as a dll. If you need to specify a dll export file you'll need to modify the function cmd to take this into account. Another possible addition is specifying a static dll. Check the ghc manual above.

#! /usr/bin/runghc
 
> import Distribution.Simple
> import Distribution.Simple.LocalBuildInfo
> import Distribution.PackageDescription
> import System.Cmd
> import System.Directory
> import Data.List
>
>
> main = defaultMainWithHooks (defaultUserHooks { postBuild = buildDll })
>   where
>   buildDll _ _ pkg info = do putStrLn "Building Dll..."
>                              setCurrentDirectory (buildDir info)
>                              let buildCmd = cmd pkg info
>                              putStrLn buildCmd
>                              system buildCmd
>                              let dll = dllFile pkg
>                              let cpDllCmd = "cp " ++ dll ++ " " ++ (name pkg) ++ "\\" ++ dll
>                              putStrLn cpDllCmd
>                              system cpDllCmd
>   ghcExe :: LocalBuildInfo -> String
>   ghcExe info = "\"" ++ (compilerPath (compiler info)) ++ "\""
>   mainOFile :: PackageDescription -> String
>   mainOFile pd = "HS" ++ (name pd) ++ "-" ++ (showVersion (pkgVersion (package pd))) ++ ".o"
>   cmd :: PackageDescription -> LocalBuildInfo -> String
>   cmd pd i = (ghcExe i) ++ " --mk-dll -o " ++ (dllFile pd) ++ " " ++ (mainOFile pd) ++ " " ++ (packages i)
>   packages :: LocalBuildInfo -> String
>   packages i = foldl1 (\x y -> x ++ " " ++ y) (map showPackage (packageDeps i))
>   showPackage :: PackageIdentifier -> String
>   showPackage pi = "-package " ++ showPackageId pi
>   name :: PackageDescription -> String
>   name = pkgName . package 
>   dllFile :: PackageDescription -> String
>   dllFile pd = (name pd) ++ ".dll"