Below the process of creating a new archive that includes both the C and Haskell objects is explained in detail and it is not simple. The complexity occurs because there is no way (as of Cabal 1.16.0.2) to hook into the linker part of the build process.
Setting the flags in the Cabal file is trivial so it is not described here.
Set the build type to custom
by adding:
build-type: custom
to the cabal file.
Insert customized build logic by replacing the main
method in Setup.hs
with:
main = defaultMainWithHooks simpleUserHooks {
buildHook = myBuildHook,
...
}
This tells the build process that instead of going with the default build process defined in simpleUserHooks
it should use the myBuildHook
function which is defined below. Similarly the clean up process is overridden with the custom function myCleanHook
.
Define the build hook. This build hook will run make
on the command line to build the C++, and C portions and then use the C object files when creating linking the Haskell bindings.
We start off myBuildHook
:
myBuildHook pkg_descr local_bld_info user_hooks bld_flags = do
by first running make
with no arguments:
rawSystemExit normal "make" []
Then add the locations of the header files and library directories and the library itself to the PackageDescription record and update the LocalBuildInfo with the new package description:
let new_pkg_descr = (addLib . addLibDirs . addIncludeDirs $ pkg_descr)
new_local_bld_info = local_bld_info {localPkgDescr = new_pkg_descr}
Before the buildHook
fired the configureHook
stored the order of compilation in the compBuildOrder
(component build order) key of the LocalBuildInfo
record. We need to isolate the building of the library so we separate the library building and executable building parts of the build process.
The build order is just a list and we know the build component is a library if it's just a plain CLibName
type constructor so we isolate those elements from the list and update the LocalBuildInfo
record with only them:
let (libs, nonlibs) = partition
(c -> case c of
CLibName -> True
_ -> False)
(compBuildOrder new_local_bld_info)
lib_lbi = new_local_bld_info {compBuildOrder = libs}
Now we run the default build hook with the updated records:
buildHook simpleUserHooks new_pkg_descr lib_lbi user_hooks bld_flags
Once it's done building an archive has been created but we have to re-create it to include the C objects generated by the make
command in step 1. So we grab some settings and a list of the C object file paths:
let verbosity = fromFlag (buildVerbosity bld_flags)
info verbosity "Relinking archive ..."
let pref = buildDir local_bld_info
verbosity = fromFlag (buildVerbosity bld_flags)
cobjs <- getLibDirContents >>= return . map (f -> combine clibdir f)
. filter (f -> takeExtension f == ".o")
And then hand it off to withComponentsLBI
which acts on each component of the build. In this case since we're only dealing with the library part there is only one component. Cabal
provides getHaskellObjects
for getting a list of the Haskell object files and createArLibArchive
for creating an archive so we can re-run the linker:
withComponentsLBI pkg_descr local_bld_info $ comp clbi ->
case comp of
(CLib lib) -> do
hobjs <- getHaskellObjects lib local_bld_info pref objExtension True
let staticObjectFiles = hobjs ++ cobjs
(arProg, _) <- requireProgram verbosity arProgram (withPrograms local_bld_info)
let pkgid = packageId pkg_descr
vanillaLibFilePath = pref </> mkLibName pkgid
Ar.createArLibArchive verbosity arProg vanillaLibFilePath staticObjectFiles
_ -> return ()
The default buildHook
which was run in Step 4 created a temporary package database file named "package.conf.inplace" which holds the description of the library that was built so that executable can link against it without the library needing to be installed to the default system package file. Unfortunately every buildHook
run blanks it out so we need to hold on to a temporary copy:
let distPref = fromFlag (buildDistPref bld_flags)
dbFile = distPref </> "package.conf.inplace"
(tempFilePath, tempFileHandle) <- openTempFile distPref "package.conf"
hClose tempFileHandle
copyFile dbFile tempFilePath
Now we store a path to that copy into the LocalBuildInfo
structure along with the executable parts of the build process which were filtered out in Step 3.
let exe_lbi = new_local_bld_info {
withPackageDB = withPackageDB
new_local_bld_info ++
[SpecificPackageDB tempFilePath],
compBuildOrder = nonlibs
}
and store the path again in the extraTmpFiles
part of the PackageDescription
so it can be removed by the default clean up hook.
exe_pkg_descr = new_pkg_descr {extraTmpFiles = extraTmpFiles new_pkg_descr ++ [tempFilePath]}
Now we finally run the default buildHook
again with the updated records (which now know about the new archive) on just the executable components:
buildHook simpleUserHooks exe_pkg_descr exe_lbi user_hooks bld_flags