6

I'm trying to have a submodule in my program compile conditionally to switch small parts of the code between release builds and development builds.

Currently I'm attempting to use cpphs however when I change the flags passed into GHC to define a variable and change an ifdef statement stack won't recompile those files.

For example, I've got a port number that I want to switch based on which target I have built. The code I have defining this number looks like this.

#ifdef StableRelease 
  port = 12345
#else 
  port = 54321 
#endif

the stable build has the following options in its cabal file

ghc-options: -threaded -rtsopts -with-rtsopts=-N -pgmP cpphs -optP "-DStableRelease"

When I run stack build though it doesn't seem to actually pre-process the code above. Does anyone have experience with cpphs or another preprocessing solution?

jkeuhlen
  • 4,401
  • 23
  • 36

1 Answers1

5

I would solve this with the help of flags

cppstuf.cabal-file

name:                cppstuf
version:             0.1.0.0
...
cabal-version:       >=1.10

flag StableRelease {
  Description: Stable release settings like port ...
  Default:     False
  }

executable cppstuf
  main-is:             Main.hs
  build-depends:       base >=4.9 && <5.00
  default-language:    Haskell2010
  extensions: CPP
  if flag(StableRelease) {
    GHC-Options: -DSTABLE
  }

Main.hs

module Main where

main :: IO ()
main =
#if STABLE
  putStrLn "Hello, Haskell!"
#else
  putStrLn "Hello, Haskell?"
#endif

and compiling it

stack build

or

stack build --flag cppstuf:stablerelease

Personal Note aside from the answer

I would not use CPP to manage configuration options - either providing command-line options I like optparse-applicative but there is also cmdargs,and or a configuration file I have used configurator for that, but there are several options out there on hackage. One being configurator-ng as @Shersh said - the other one is not being developed anymore.

CPP on the other hand I tend to use for making libraries work across multiple GHC/library versions.

Update - w\ regards to the comment.

  • If I deliver a program, the operations team should be able to change ports, hostname or handle file location of input files etc. without knowing haskell let alone recompiling a project.
  • It makes error chasing a lot easier if your production source code does not differ from your development sources, say you would have a postgres db in dev mode, but an oracle in production - you'll never find the oracle specific bug.
  • For things like optimization levels - I don't mind too much.
  • But make sure you test multi-threaded stuff properly, I once ran out of file handles because I was opening a lot but not closing fast enough - If your dev-setting is single threaded you'll deliver a bug.
epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
  • Thanks, out of curiosity why would you avoid using CPP like this? If I have multiple modules that will need to change based on being a release or not isn't it easier to adjust these at compile time rather than passing a variable through the whole program? – Erik Feller Jan 08 '18 at 23:36
  • You can also combine cabal flags with the following approach in order to get rid of CPP altogether in many cases http://blog.haskell-exists.com/yuras/posts/stop-abusing-cpp-in-haskell.html – Li-yao Xia Jan 09 '18 at 02:20
  • Also, don't use `configurator`. It's outdated and not maintained. use `configurator-ng` instead: https://hackage.haskell.org/package/configurator-ng – Shersh Jan 09 '18 at 14:43