Sunday, April 29, 2012

Building qtHaskell on OS X

Update

The author of qtHaskell recommends a simpler approach. In addition to the "slotReject" patch below, simply change lines 308-309 of build.pl as follows:

$ghcdv = ((($ghcmv == 6) && ($ghcsv >= 12)) || ($ghcmv > 6)) ? 1 : 0;
$ghcfv = ((($ghcmv == 6) && ($ghcsv < 12)) || ($ghcmv < 6)) ? 1 : 0;

Original post

Finally! I spent the entire weekend on this, but I finally managed to compile qtHaskell on OS X. I spent a lot of that time on the internet googling for the error messages I encountered, to no avail. So, to save some time to the next person who google them, here is what worked for me. The qtHaskell documentation recommends that you use their ./build perl script. That almost works, but not quite. Thankfully, by giving arguments to the scripts, we can run the parts which work, and skip over the parts which don't. Speaking of arguments, one the most annoying errors is the "scattered reloc r_address too large" error message which only occurs at the very end of a very length compilation. I stumbled upon a page recommending to use "--extra-ld-opt=--architecture=x86_64" to fix a similar error message, and since then I have superstitiously stuck the argument everywhere I could, just to be sure. So, let's build qtHaskell, shall we? First of all, qtHaskell is a set of Haskell bindings for Qt, so you need to install Haskell and Qt. I used Qt 4.7.1, qtHaskell 1.1.4, and GHC 7.0.4 (64 bits). Actually I had the 32 bits version of GHC at first, and that let to the aforementioned r_address problems, followed by linking problems afterwards. Better upgrade to 64 bits!
brew uninstall ghc
brew install ghc --64bits
We should now be ready to compile qtHaskell proper. There are actually two parallel hierarchies to be compiled, the C++ implementation and its Haskell FFI interface. Both are very large, because, well, Qt is large. First, the C++ part: no problems here, the build script works just fine for this part.
./build user cpp qmake cmake \
    --extra-ld-opt=--architecture=x86_64
./build user cpp-install \
    --extra-ld-opt=--architecture=x86_64 --no-sudo
Note that even though the "user" flag was given, the build script will still install the files globally in /usr/local/lib/libqtc_*. Presumably, the flag is only for the Haskell part, for which the steps would ideally be as follows.
./build user haskell configure \
    --extra-ld-opt=--architecture=x86_64
./build user haskell build \
    --extra-ld-opt=--architecture=x86_64
./build user haskell-install \
    --extra-ld-opt=--architecture=x86_64 --no-sudo
Unfortunately, the middle step fails with the following error message.
unrecognised command: makefile (try --help)
runghc Setup.hs makefile: No such file or directory
Fear not! This cryptic-looking error message is actually a clue towards the solution. Apparently, "runghc Setup.hs makefile" is the command used for building the Haskell part. Using the help, as recommended in the error message, leads to the following alternative build commands.
runghc Setup.hs configure --user \
    --extra-ld-opt=--architecture=x86_64
runghc Setup.hs build
Unfortunately, that doesn't work either, at least not with a recent GHC. There is a problem in the qtHaskell code, which leads to the following error message.
Qtc/Core/Attributes.hs:583:13:
    Could not deduce (Qstt a (QDialogSc b))
      arising from a use of `slotReject''
    from the context (Qstt a (QDialogSc b1))
      bound by the instance declaration
      at Qtc/Core/Attributes.hs:581:10-52
    Possible fix:
      add (Qstt a (QDialogSc b)) to the context of
        the instance declaration
      or add an instance declaration for (Qstt a (QDialogSc b))
    In the expression: slotReject'
    In an equation for `reject'': reject' = slotReject'
    In the instance declaration for `QsaSlotReject a'
If you are familiar with Haskell, you should be able to fix the problem on your own. Alternatively, you could grab Uduki's hsQt fork of qtHaskell, which comes pre-patched. Or you might apply the following patch yourself:
diff --git a/Qtc/Core/Attributes.hs b/Qtc/Core/Attributes.hs
index 197c506..217d585 100755
--- a/Qtc/Core/Attributes.hs
+++ b/Qtc/Core/Attributes.hs
@@ -580,7 +580,7 @@ class QsaSlotReject w where
 
 instance (Qstt a (QDialogSc b)) => QsaSlotReject (a) where
   slotReject' = (Qslot "reject()", \_ -> ())
-  reject' = slotReject'
+  reject'     = (Qslot "reject()", \_ -> ())
 
 class QsaSignalRejected_nt_f w x f where
   signalRejected', rejected' :: x -> SltConf w f
Now that the source is patched, the Haskell part should finally compile.
runghc Setup.hs build
If you run out of memory during this phase, just run the command again. And again. As many times as it takes to compile all the targets. When you get the the very last target, qtHaskell is finally linked... and then, like me, you might get one of those nasty "scattered reloc r_address too large" errors. Did you? If so, you might have skipped the very first step and be stuck with a 32 bits version of GHC. Check using "ghc --info". If you insist on using the 32 bits version, you could try the following variant of the build command, which got me through that step. Still got troubles when linking programs using qtHaskell, though.
runghc Setup.hs configure --user --disable-library-for-ghci
Now that the Haskell part is built, we can finally install it! This time, the "user" flag is honored.
./build user haskell-install \
    --extra-ld-opt=--architecture=x86_64 --no-sudo
Let's test this! The qtHaskell helpfully provides the following Hello World code, just put it in a file with the "hs" extension and build it into an executable using "ghc --make".
module Main where

import Qtc.Classes.Qccs
import Qtc.Classes.Gui
import Qtc.Gui.Base
import Qtc.Gui.QApplication
import Qtc.Gui.QPushButton

main :: IO Int
main = do
  qApplication ()
  hello <- 60::int="" ello="" hello="" nt="" pre="" qapplicationexec="" qpushbutton="" qshow="" qthaskell="" resize="" world="">
Tada!
At long last. A button!