I've been working on sprucing up some of my libraries on Hackage. In particular I've been working on making sure the code is portable to Hugs as well as GHC, since I'm not relying on GHC-specific extensions. This is made tricky, however, because I am using both CPP and the FFI. Along the way I've been stumbling into Dead Code Land, the place where code that was never documented goes when the project dies. Perhaps my charting of these territories will help anyone else misguided by portability.
Some background. The latest release of Hugs is September 2006, which was contemporary with, though prior to, GHC 6.6. Now that GHC 6.10 has been released, GHC 6.8 is quickly nearing the end of it's life cycle, though many people still use it (since installation is always a headache, or since 6.10 still has a few unfortunate performance regressions). For my code I'd like to support Hugs Sept06, GHC 6.8, and GHC 6.10. I'm fine to call GHC 6.6 dead; porting the code to it shouldn't be too difficult, but there are some things like LANGUAGE pragma which are just too nice to loose. There are other compilers out there like nhc98, yhc, jhc, and lhc though they tend to be niche and don't support all the standard extensions to Haskell 98.
Beyond compilers. Cabal is the de-facto packaging tool for Haskell. On GHC 6.8 we have Cabal 220.127.116.11, though because of the rapid evolution of the project, this version is ancient and all it's version-specific documentation has been blown away by the documentation for newer versions. Haddock is the de-facto documentation tool. Here we have version 18.104.22.168 which is pretty recent, though all the documentation is on how to write doc code, rather than how to run the program that compiles that into html.
And now on down to the dirt. The use of CPP for conditional compilation of Haskell has a long, if dubious, history. There's a cpphs project which aims to clean up C's original cpp, in order to make it easier to use for Haskell code which is sensitive to whitespace and other things that C isn't. The only other project even vaguely in this domain is Template Haskell, but that's GHC-only and it does far more besides. Using CPP with GHC is trivially easy. For Hugs it's a bit more difficult. Hugs doesn't have a
--cpp flag, but instead has flags for being able to filter your code with any arbitrary program. A canonical invocation looks something like this:
hugs -F'cpp -P -traditional -D__HUGS__=200609' MyProgram.hs
Some tricky things to note. First is that C's cpp requires the
-P flag in order to work in "pipe mode" rather than reading a file directly from disk. Second is the
-traditional flag which turns off some newer cpp features which can interfere with Haskell code. And finally, for our conditional code, we define a macro that says we're running under Hugs Sept06.
Another tricky thing to note is that this will work on the command line, but Cabal 22.214.171.124 is hard-coded to use cpphs instead. In order to mix things up, cpphs has a different set of flags than cpp does, and so telling Cabal to just use cpp instead won't work. Another tricky bit is that Hugs installs a version under the name
cpphs-hugs, so you may need to set up an alias or tell cabal
--with-cpphs=/blah/bin/cpphs-hugs during configure.
Haddock (126.96.36.199 re Cabal 188.8.131.52) also has some misfeatures regarding CPP. In your .cabal file you can set the field
cpp-options in order to define your macros, and from here they get included in the preprocessor runs for GHC or Hugs. They do not, however, get called when haddock is called. Thus, since haddock interprets the code in order to extract type information, you'll need to set the
ghc-options field, which does get passed to haddock. To complicate things further, recent updates to Hackage have filters to detect setting CPP macros in
ghc-options and will reject the package, telling you to use the
cpp-options field instead. (A good practice in general, but doesn't work as well as it should.)
Update (Sunday, 15 March 2009): This misfeature re Haddock has been fixed in Cabal 1.6.
The foreign function interface was an early extension to the venerable Haskell 98 spec. Compiling to use the FFI is again trivial in GHC. For Hugs it's a bit more complicated since Hugs is an interpreter, not a compiler. The solution is, before calling hugs, just call
ffihugs with all the same options and it will compile and link a DLL that gets loaded in when the source code is interpreted.
Again, this will work on the command line, but Cabal 184.108.40.206 has other ideas. To be fair, Cabal does automatically detect if you need to call ffihugs and will do so on your behalf. The problems are, again, more to do with option passing. The call to ffihugs does not inherit the
hugs-options field in your .cabal file. There is no
ffihugs-options field, but you can pass the
--ffihugs-options="-98 +o" flag to
configure. (Similarly there is no
haddock-options field, but you can pass
--haddock-options=-D__BLAH__ in order to get around the Hackage restriction.) The other problem is that cpphs is not called before ffihugs is called, therefore you must pass in the flag for the filter. This part is especially tricksy.
Scripting 101: if something has too many (or too few) levels of quoting, blame your shell first. Blame the program later, if ever.
The secret incantation is
--ffihugs-option=-F'cpp -P -traditional -D__HUGS__=200609'. First trick is to use
--ffihugs-option instead of
--ffihugs-options. The plural does some whitespace tokenizing for being able to pass multiple flags at once, but it goes awry here. Second trick is to use single ticks in order to protect the spaces, but do not embed those within double quotes (like we usually do with
--prog-options="..."). If you have that second level of quoting, then Cabal will end up looking for the program which is named by the whole quoted token, instead of evaluating it as
cpp and some arguments passed to it.
Through much trial and tribulation, I can finally get Cabal to compile my packages for Hugs. Haddock still doesn't work, since it must be run with the same version (of GHC) as it was compiled with, but that's a whole other can of worms. The big sticking point now, is that all those CPP macros should be set in the .cabal file once and for all (using Cabal flags), and the user shouldn't have to know anything about them. At this point I'm thinking that there's simply no way around it without requiring a newer version of Cabal which fixes the option-passing bugs. So I guess it's back to README files to tell folks how to compile it. That's too bad really.