Tuesday, December 6, 2011

Haskell Cabal and Protocol Buffers

I always wanted to use Haskell at work and now I've found small place for it - writing internal test utilities. Actually it can be more easily accomplished using Python but it is certainly less interesting. Now I need to test server application using Google Protocol Buffers for message exchange with client. Hackage database contains packages needed to use protobuf in haskell projects, hprotoc is one of them that produces haskell source files from protocol definitions. But I've found it impossible to accomplish this task with cabal by just editing myproject.cabal file. I googled a bit but found only few questions, not answers.
This is my dirty solution via use of PreProcessor in Setup.hs:
    1 #!/usr/bin/env runhaskell
    2 
    3 import Distribution.Simple
    4 import Distribution.Simple.PreProcess
    5 import Distribution.Simple.Utils
    6 import Distribution.PackageDescription
    7 import Distribution.Simple.LocalBuildInfo
    8 import System.Cmd (rawSystem)
    9 import System.FilePath ((</>))
   10 
   11 -- Hook .proto module extension to be processed with hprotoc
   12 main = let hooks = simpleUserHooks 
   13            protoc  = ("proto", protoC)
   14         in defaultMainWithHooks hooks { hookedPreProcessors = protoc:knownSuffixHandlers  }
   15 
   16 protoC :: BuildInfo -> LocalBuildInfo -> PreProcessor 
   17 protoC build local = PreProcessor {
   18   platformIndependent = True,
   19   runPreProcessor = \(inPath, inFile) (outPath, outFile) verbosity -> do
   20     notice verbosity (inPath </> inFile ++ " is being preprocessed to " ++ 
   21                       outPath </> outFile ++ " and a few more maybe")
   22     rawSystem "hprotoc" ["--haskell_out=" ++ outPath, inPath </> inFile] 
   23     return ()
   24   }
.cabal file must contain "Build-type: Custom" to make Cabal actually use Setup.hs
This is quite ugly because user is forced to have .proto and resulting .hs file have the same name and consequently "package" directive in .proto file with the same package name. For example to generate TestProto.hs and TestProto subdirectory from TestProto.proto with "package TestProto;" line user should add something like "Other-modules: TestProto" to .cabal file.
I am too lazy to make better solution but if it already exists please let me know.