(This is a long post that’s not going to make much sense unless you’re interested in FFI (foreign function interface) libraries for Haskell. If you don’t care about Haskell FFI libraries, then go check out Banjo Cat.)
I maintain a small Haskell library which provides Haskell bindings to the Augeas API. Occasionally I put out a new version of the library when the Augueas API changes. Of course, it usually takes a couple months for the API changes to surface in the Ubuntu augeas package, so I need build the Augeas libraries from source in order to update my library. As a result, my Haskell library contains references to symbols that will be unresolved unless my users also build their Augeas library from source.
But, I want my users to be able to use the Haskell bindings with whatever version they may have available, so I like I’d like it better if I didn’t have to force my users to also build Augeas from source. Instead, it would be better if somehow my build to somehow poll the user’s system to see what API methods were available, and then only compiled in support for the available methods.
The GNU autoconf tools can do this sort of library method polling, and there’s an autoconfUserHooks function that can be used in Setup.hs. Here’s how I tied the two of these together for my project.
I’m going to assume you have the autoconf tools available in your development environment. If you don’t have them installed, it shouldn’t be too hard to find them for your distribution of choice, or you could get the latest code from the folks at GNU..
Step 1: Write a configure.ac file:
The configure.ac file will be used by autoreconf to create your configure script. Go through the template below and replace anything with ‘your-*’ with the values from your project.
## Replace yout-autoconf-vesion with your current autoconf version (autoconf --version)
## It should be an unquoted decimal value, like 2.65
AC_PREREQ(your-autoconf-version)
## Replace your-library with the name of your library
## Replace your-version with the Version value from your cabal file
AC_INIT([your-library],[your-version])
## Replace this with a file which should exist in your base source directory if you don't have a Setup.hs
## Your cabal file might be a good choice.
AC_CONFIG_SRCDIR(Setup.hs)
## This is the name of an include file that the ./configure script will create.
## I'll describe it more below
AC_CONFIG_HEADERS([Config.h])
## autoconfUserHooks requires this
AC_ARG_WITH([compiler],[])
## Check if your foreign library is available at all.
## Replace your-sanity-check-function with a function name from the foreign library which should be available from the very first release of the library.
## Replace your-foreign-library-name with the name of your foreign library
## Replace you-foreign-include-filename with the name of an include file which should be installed on the system if the development version of the library is installed.
## This block is first going to check if your base library is installed by checking if some 'should always exist' function can be found.
## Then its going to check if it can find the include file associated with the library .
## If it fails these tests, configure will return a non-zero value, and the build will fail.
sanity_check=no
dev_check=no
AC_SEARCH_LIBS([your-sanity-check-function],[your-foreign-library-name],[sanity-check=yes])
if test "x${sanity_check}" = "xyes" ; then
AC_CHECK_HEADERS([your-foreign-include-file-name],[dev_check=yes],[dev_check=no])
else
AC_MSG_ERROR([Unable to find your-sanity-check-function in your-foreign-library-name library.])
fi
if test "x${dev_check}" = "xno"; then
AC_MSG_ERROR([Unable to find your-foreign-include-filename])
fi
## Checking for available symbols.
## Go through your source and collect all the symbols you expect to be available in your foreign library.
## Then for each of the foreign functions you expect to be available, call the HAS_EXTERNAL_FUNC macro with
## the function name as the first argument, and the name for a unique macro definition you want written to your Config.h file
##
## Notes
## (Yes, If I mad more m4-fu, I could probably write this as a single argument macro. I leave this as an exercise for the reader.)
AC_DEFUN([HAS_EXTERNAL_FUNC],
[
has_$1=no
AC_SEARCH_LIBS([$1],[your-foreign-library],[has_$1=yes])
if test "x${has_$1}" = "xyes"; then
AC_DEFINE([$2],[],[your-foreign-library has the $1 function.])
fi
])
HAS_EXTERNAL_FUNC(your-foreign-function-name,HAS_YOUR_FOREIGN_FUNCTION_NAME)
HAS_EXTERNAL_FUNC(your-foreign-function-name2,HAS_YOUR_FOREIGN_FUNCTION_NAME2)
## ...
HAS_EXTERNAL_FUNC(your-foreign-function-namez,HAS_YOUR_FOREIGN_FUNCTION_NAMEZ)
## Builds an config.status file after ./configure is run
AC_OUTPUT
Step 2: Build the configure script from the configure.ac file.
> autoreconf
> ./configure
The configure script should create a Config.h file. For each found symbol, Config.h should contain something like
/* your-foreign-library has the your-foreign-function function. */
#define HAS_YOUR_FOREIGN_FUNCTION /**/
For each missing symbol, Configu.h should include something like
/* your-foregn-library library has the missing function. */
/* #undef HAS_MISSING */
Step 3: Uglify your Haskell source with conditional compilation.
Include the CPP pragma in your source. You most likely already using ForeignFunctionInterface, so your pragma line should look something like below. Also include the Config.h file that ./configure created
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
#ifndef _config_h
#include "../Config.h"
#define _config_h
#endif
Then for portion of the source where a foreign function is used, wrap it in a “#ifdef HAS_FUNCTION” wrapper. Below is an example from the haskell-augeas source for the small aug_error function.
Note if HAS_AUGEAS_ERROR isn’t defined, then the code compiles, but will report an error if called. There may be a better way to report the error, but this worked for my purposes.
-- ---------
-- aug_error
-- ---------
#ifdef HAS_AUGEAS_ERROR
foreign import ccall safe "augeas.h aug_error"
c_aug_error :: Ptr Augeas -- aug
-> IO (AugErrCode) -- error code
#endif
{-|
Return a human-readable message for the error code */
-}
aug_error :: Ptr Augeas -- ^ Augeas pointer
-> IO (AugErrCode) -- ^ return value
aug_error aug_ptr =
#ifndef HAS_AUGEAS_ERROR
Prelude.error "aug_error requires at least augeas version 0.6.0"
#else
do
ret <- c_aug_error aug_ptr
return(ret)
#endif
Also remember to add the wrappers around any test code that’s using foreign function calls.
Step 4: Update your Setup.hs to call configure
If you’re using defaultUserHooks or simpleUserHooks in your Setup.hs, switch to autoconfUserHooks. This way running runghc Setup.hs build will call the ./configure script before compiling the source, so the Config.h include file will already exist. This was just a simple swap in my case.
Step 5: Update your cabal file
In order for the Setup.hs sdist target to create a package which can be compiled from source, add the new configure.ac and configure scripts to the Extra-Sources-Files list in your cabal file. Technically the configure script shouldn’t be included in the sdist package since it’s derived from the configure.ac file, but I think users expect there to already be a working configure script available out of the box.
Also add CPP to the Extentions list in the library block, otherwise ‘runghc Setup.hs haddock’ will give an ‘error ../Config.h: No such file or dictionary’ error.
Name: your-package
...
Extra-Source-Files: Makefile, configure.ac, configure, Config.h.in
library
...
Extensions: CPP
Step 6: Add configure.ac to the VCS
The configure.ac file is a new source file, so don’t forgot to do your ‘darcs add’ equivilent for whatever VCS you’re using.
Step 7: Maintenance
A new method may be added to the foreign library. At that point you’ll have to add support the new method in your library. To update your configure script, add a new HAS_EXTERNAL_FUNCTION line to your configure.ac file, and run ‘autoreconf’ to rebuild the configure script again.
And that’s all there is to it. Hopefully you’ve found this helpful. If you find any errors, or if you have a better way of handling potentially missing symbols, please let me know about it.
(editted Dec 6th, 2010: Updated step #5 to add Extensions:CPP for haddock support)