Geeky things and other research from a work in progress

2009-03-02

Template Haskell 2.3 or Cabal 1.2? EMGM can't have both!

Updates below!

I'm about ready to give up. I would like EMGM to support both GHC 6.8 and 6.10 to allow for more potential uses. This effectively means it should build with Cabal version 1.2 (which is distributed with GHC 6.8) and 1.6 (distributed with GHC 6.10). I didn't think this would be a difficult problem, — shows you what little I know — but it has turned into a reasonably sized annoyance.

As of version 0.2, EMGM supports generating code for type representations using Template Haskell (TH). Since TH is now a dependency, I thought it would be handy to provide the representation values and instances for it. And so I do that here. If you look at the linked file, you'll notice an #ifdef surrounding $(derive ''Loc). This is because the template-haskell package includes a new Loc datatype in version 2.3, and in order to support versions 2.2 (for GHC 6.8) and 2.3 (for GHC 6.10), I needed to do some funky C preprocessor (CPP) stuff.

Well, I followed the advice given in this thread started by Jason Ducek with useful responses from Duncan Coutts. The result was this emgm.cabal file with a flag for determining which version of template-haskell was available and whether or not to define the necessary macro. I later learned that this does't work when attempting to cabal-install GHC 6.8, because template-haskell-2.3 fails to correctly specify the version of base or GHC that it requires.

Now, to work around this problem, Duncan described how to hook into Cabal to get the actual package dependencies and insert the CPP option into the build info. It was not too difficult to figure this out. And in fact, the code for my Setup.lhs is in the appendix of this article in case it is later useful for me or someone else.

Unfortunately, as soon as I had this implemented, I discovered it didn't work in GHC 6.8.3/Cabal 1.2. There's a very minor difference in Cabal that simply breaks my code, and I don't know how to work around it. The difference is in PackageIdentifier:

Cabal 1.2:

> data PackageIdentifier = PackageIdentifier { pkgName :: String, pkgVersion :: Version }

Cabal 1.6:

> data PackageIdentifier = PackageIdentifier { pkgName :: PackageName, pkgVersion :: Version }
> newtype PackageName = PackageName String

I need PackageIdentifier to determine which version of template-haskell is being used as a dependency. But I either use a String or a PackageName depending on which version of Cabal is used. I don't think there's a way to know which version of Cabal is used when building a Setup.lhs file.

As far as I can tell, my options are the following:

  1. Hack more on the Setup.lhs to figure out a different way of dealing with the template-haskell issue.
  2. Release for GHC 6.10 only. Note that the problem only occurs when mixing cabal-install and template-haskell. EMGM builds fine with GHC 6.8 in general.
  3. Remove the TH deriving code and the CPP macro.
  4. Leave things as they are and warn people about the issue. If/when template-haskell gets patched, it may fix the problem.

I'm probably going to go with the last option for now.

Appendix

This is a literate Haskell Setup.lhs containing a build hook for passing a CPP option when a version of the template-haskell package greater than or equal to 2.3 is specified as a dependency. You might find it a useful example of using part of the Cabal library.

> module Main (main) where
> import System.Cmd
> import System.FilePath
> import Data.Version
> import Distribution.Simple
> import Distribution.Simple.LocalBuildInfo
> import Distribution.Simple.Program
> import Distribution.Simple.Setup
> import Distribution.Package
> import Distribution.PackageDescription
> main :: IO ()
> main = defaultMainWithHooks hooks
> where
> hooks = simpleUserHooks { buildHook = buildHook' }
> -- Insert CPP flag for building with template-haskell versions >= 2.3.
> buildHook' :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
> buildHook' pkg lbi hooks flags =
> buildHook simpleUserHooks pkg (lbi { localPkgDescr = newPkgDescr }) hooks flags
> where
>     -- Old local package description
> oldPkgDescr = localPkgDescr lbi
>     -- New local package description
> newPkgDescr =
> case thVersion of
> Nothing ->
> oldPkgDescr
> Just version ->
> if version >= Version [2,3] []
> then
> oldPkgDescr
> { library = addThCppToLibrary (library oldPkgDescr)
> , executables = map addThCppToExec (executables oldPkgDescr)
> }
> else
> oldPkgDescr
>     -- Template Haskell package name
> thPackageName = "template-haskell"
>     -- template-haskell version
> thVersion = findThVersion (packageDeps lbi)
>     -- CPP options for template-haskell >= 2.3
> thCppOpt = "-DTH_LOC_DERIVEREP"
>     -- Find the version of the template-haskell package
> findThVersion [] = Nothing
> findThVersion (PackageIdentifier (PackageName name) version:ps)
> | name == thPackageName = Just version
> | otherwise = findThVersion ps
>     -- Add the template-haskell CPP flag to a BuildInfo
> addThCppToBuildInfo :: BuildInfo -> BuildInfo
> addThCppToBuildInfo bi =
> bi { cppOptions = thCppOpt : cppOptions bi }
>     -- Add the template-haskell CPP flag to a library package description
> addThCppToLibrary :: Maybe Library -> Maybe Library
> addThCppToLibrary ml = do
> lib <- ml
> return (lib { libBuildInfo = addThCppToBuildInfo (libBuildInfo lib) })
>     -- Add the template-haskell CPP flag to an executable package description
> addThCppToExec :: Executable -> Executable
> addThCppToExec exec =
> exec { buildInfo = addThCppToBuildInfo (buildInfo exec) }

P.S. I used the recently released pandoc 1.2 for this article. Nice highlighting, eh?

Update #1: Apparently, Blogger's feed and/or Planet Haskell aren't ready for the games I played with pandoc. Thus, I have removed the syntax highlighting. That's unfortunate, because it looked good on the web.

Update #2: Thanks to the magic of the internets and the fact that there are people much smarter than I, there is a solution. In hindsight, it's obvious (of course): dynamically typed programming!

Neil Mitchell sent me this little piece of code which does just the trick:


> mkPackageName :: (Read a) => String -> a
> mkPackageName nm =
>   fst $ head $ reads shownNm ++ reads ("PackageName " ++ shownNm)
>   where
>     shownNm = show nm

So, I plugged this into thPackageName, removed PackageName from the pattern in findThVersion, and—voilĂ !—it worked.

2 comments:

  1. Could you use SYB to extract the string from the pkgName component?

    ReplyDelete
  2. See update #2. Perhaps there are other solutions. If you come up with one, please share it here and we can collect them.

    ReplyDelete