Linking executables and the library of a Cabal package description

John D. Ramsdell ramsdell at mitre.org
Thu Oct 18 13:00:24 EDT 2007


Simon Marlow <simonmarhaskell at gmail.com> writes:

> Libraries and executables in the same package are not well supported
> by Cabal.

I'm starting to believe the situation is not as bad as you suggest.  I
recently realized a post configuration hook can be used to generate
the needed package configuration information, so that it is easy to
write packages in which executables are linked to the library in the
package.  I have enclosed the code that does so.  There is one
remaining problem, you have to figure out how to force executables to
be rebuilt when only the library changes.  I use a makefile to delete
all the binaries before I run Setup.hs build.

John

$ make
find dist -type f -perm /u+x -delete
runghc Setup.hs build
Preprocessing library a-1.0...
Preprocessing executables for a-1.0...
Building a-1.0...
[1 of 1] Compiling A                ( A.hs, dist/build/A.o )
/usr/bin/ar: creating dist/build/libHSa-1.0.a
[1 of 1] Compiling Main             ( exec/Main.hs, dist/build/b/b-tmp/Main.o )
Linking dist/build/b/b ...
$ make clean
runghc Setup.hs clean
cleaning...
$ rm package.conf 
$ more `find . -type f ` > ../a.txt
$ cat ../a.txt
::::::::::::::
./a.cabal
::::::::::::::
Name:			a
Version:		1.0
Build-Depends:		base
Exposed-Modules:	A

Executable:		b
Main-Is:		Main.hs
Hs-Source-Dirs:		exec
ghc-options:		-package-conf package.conf -package a
::::::::::::::
./Setup.hs
::::::::::::::
-- Provide support for executables that link with the library defined
-- within a package.  The trick is to generate a package description
-- that supports the linking phase.

import Distribution.Simple
import qualified Distribution.InstalledPackageInfo as I
import Distribution.PackageDescription
import Distribution.Setup
import Distribution.Simple.LocalBuildInfo
import System.IO
import System.Exit

-- The package description is generated using a post configuration hook.

main = defaultMainWithHooks userHooks

userHooks = defaultUserHooks { postConf = conf }

-- The hook always runs the old actions last.
old = postConf defaultUserHooks

-- If the package defines no library, do nothing.
conf :: Args -> ConfigFlags -> PackageDescription -> 
        LocalBuildInfo -> IO ExitCode
conf args flags pkg bld = 
    case library pkg of
      Nothing ->
          old args flags pkg bld
      Just lib ->
          confLib lib args flags pkg bld

-- Create package descriptions and then print it.
confLib :: Library ->  Args -> ConfigFlags -> PackageDescription -> 
           LocalBuildInfo -> IO ExitCode
confLib lib args flags pkg bld =
    do
      let hsLib = "HS" ++ showPackageId (package pkg)
      let info0 = I.emptyInstalledPackageInfo
      let info1 =
              info0 { I.package = package pkg,
                      I.exposed = True,
                      I.exposedModules = exposedModules lib,
                      I.importDirs = [buildDir bld],
                      I.libraryDirs = [buildDir bld],
                      I.hsLibraries = [hsLib]
                    }
      writeFile "package.conf" (show [info1])
      old args flags pkg bld

-- A clean hook should be written that deletes package.conf.
::::::::::::::
./Makefile
::::::::::::::
SETUP	= runghc Setup.hs
CONFIGURE = $(SETUP) configure --ghc

all:
	@if test ! -f .setup-config; \
		then echo $(CONFIGURE); $(CONFIGURE); fi
	find dist -type f -perm /u+x -delete
	$(SETUP) build

Makefile:
	@echo make $@

%:	force
	$(SETUP) $@

.PHONY:	all force
::::::::::::::
./A.hs
::::::::::::::
module A where

a :: Int
a = 3
::::::::::::::
./exec/Main.hs
::::::::::::::
module Main (main) where

import A

main :: IO ()
main =
    print a


More information about the Libraries mailing list