Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

changing how Setup.hs is built

Tags:

haskell

cabal

The encoding package uses HaXml in its build script (in Setup.hs). It happens to use bits of the interface that changed between HaXml-1.19 and HaXml-1.22. It would be nice if the encoding package were able to build with either version. I tried using the usual Cabal trick, namely, doing something like

{-# LANGUAGE CPP #-}
#if MIN_VERSION_HaXml(1,22,0)
-- HaXml-1.22 code
#else
-- HaXml-1.19 code
#endif

...but the magic defines can't exist before the package is configured, and this file is being built to make the configure step possible. What are my options? Is there a way to change the command that cabal-install calls to compile Setup.hs? Is there another mechanism for conditionally selecting code that sidesteps cabal?

like image 646
Daniel Wagner Avatar asked Apr 21 '12 03:04

Daniel Wagner


2 Answers

The Data.Data interface is capable (just about!) of constructing and deconstructing values of a type that may or may not exist. Unfortunately, HaXml doesn't appear to have Data instances for its types, and you can't define one since you can't refer to the type that might or might not exist, so we have to resort to Template Haskell:

The following module exports qnameCompat:

{-# LANGUAGE TemplateHaskell #-}
module HaXmlCompat (qnameCompat) where

import Language.Haskell.TH

qnameCompat :: Q [Dec]
qnameCompat = do
  mi <- maybeReify "N"
  case mi of
    Nothing -> sequence [
      tySynD (mkName "QName") [] [t| String |],
      valD [p| toQName |] (normalB [| id |]) [],
      valD [p| fromQName |] (normalB [| Just |]) []]
    Just (DataConI n _ _ _) -> do
      s <- newName "s"
      sequence [
        valD [p| toQName |] (normalB (conE n)) [],
        funD (mkName "fromQName") [
          clause [conP n [varP s]] (normalB (appE [| Just |] (varE s))) [],
          clause [ [p| _ |] ] (normalB [| Nothing |]) []]]
    Just i -> fail $
      "N exists, but isn't the sort of thing I expected: " ++ show i

maybeReify :: String -> Q (Maybe Info)
maybeReify = recover (return Nothing) . fmap Just . reify . mkName

When spliced at the top level using Template Haskell, qnameCompat will check if N exists. If it does, it produces the following code:

toQName = N
fromQName (N s) = Just s
fromQName _ = Nothing

If it doesn't, the following is produced:

type QName = String
toQName = id
fromQName = Just

Now you can create and deconstruct Elements, e.g. using the ViewPatterns extension:

myElt :: String -> Element i
myElt = Elem (toQName "elemName") [] []

eltName :: Element i -> String
eltName (Elem (fromQName -> Just n) _ _) = n

ViewPatterns is convenient, but not essential, of course: using ordinary pattern matching on the result of fromQName will work just as well.

(These ideas are what led me to develop the notcpp package, which includes maybeReify and some other useful utilities)

like image 58
Ben Millwood Avatar answered Nov 12 '22 06:11

Ben Millwood


There don't seem to be very many knobs in cabal-install/Distribution/Client/SetupWrapper.hs controlling the compilation of Setup.hs, so your best bet may be to create a stub Setup.hs file which performs the version test, and then hands off to real Setup.hs once it has figured out what the version is.

Another trick is to make a compatibility shim library which your Setup script uses, which has the appropriate version tricks.

But maybe the real question to ask, is this: why is Setup.hs using external libraries?

like image 38
Edward Z. Yang Avatar answered Nov 12 '22 06:11

Edward Z. Yang