Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a Haskell cabal project with library+executables that still run with runhaskell/ghci?

If you declare a library + executable sections in a cabal file while avoiding double compilation of the library by putting the library into a hs-source-dirs directory, you cannot usually run your project with ghci and runhaskell anymore, especially if the executables have helper modules themselves.

What is a recommended project layout that

  • only builds what is needed once
  • allows using runhaskell
  • has a clean structure without hacks?
like image 696
nh2 Avatar asked Sep 06 '12 18:09

nh2


2 Answers

Let's assume you have a mylib library, and mylib-commandline and mylib-server executables.

You use hs-source-dirs for the library and each executable so that each has their own project root, avoiding double compilation:

mylib/                      # Project root   mylib.cabal   src/                      # Root for the library   tests/   mylib-commandline/        # Root for the command line utility + helper modules   mylib-server/             # Root for the web service + helper modules 

Full directory layout:

mylib/                      # Project root   mylib.cabal   src/                      # Root for the library     Web/       Mylib.hs              # Main library module       Mylib/         ModuleA             # Mylib.ModuleA         ModuleB             # Mylib.ModuleB   tests/     ...   mylib-commandline/        # Root for the command line utility     Main.hs                 # "module Main where" stub with "main = Web.Mylib.Commandline.Main.main"     Web/       Mylib/         Commandline/           Main.hs           # CLI entry point           Arguments.hs      # Programm command line arguments parser   mylib-server/             # Root for the web service     Server.hs               # "module Main where" stub with "main = Web.Mylib.Server.Main.main"     Web/       Mylib/         Server/           Main.hs           # Server entry point           Arguments.hs      # Server command line arguments parser 

The stub-like entry point file mylib-commandline/Main.hs looks like this:

module Main where  import qualified Web.Mylib.Server.Main as MylibServer  main :: IO () main = MylibServer.main 

You need them because an executable must start on a module simply called Main.

Your mylib.cabal looks like this:

library   hs-source-dirs:   src   exposed-modules:     Web.Mylib     Web.Mylib.ModuleA     Web.Mylib.ModuleB   build-depends:       base >= 4 && <= 5     , [other dependencies of the library]  executable mylib-commandline   hs-source-dirs:   mylib-commandline   main-is:          Main.hs   other-modules:     Web.Mylib.Commandline.Main     Web.Mylib.Commandline.Arguments   build-depends:       base >= 4 && <= 5     , mylib     , [other depencencies for the CLI]  executable mylib-server   hs-source-dirs:   mylib-server   main-is:          Server.hs   other-modules:     Web.Mylib.Server.Main   build-depends:       base >= 4 && <= 5     , mylib     , warp >= X.X     , [other dependencies for the server] 

cabal build will build the library and the two executables without double compilation of the library, because each is in their own hs-source-dirs and the executables depend on the library.

You can still run the executables with runghc from your project root, using the -i switch to tell where it shall look for modules (using : as separator):

runhaskell -isrc:mylib-commandline mylib-commandline/Main.hs  runhaskell -isrc:mylib-server mylib-server/Server.hs 

This way, you can have a clean layout, executables with helper modules, and everything still works with runhaskell/runghc and ghci. To avoid typing this flag repeatedly, you can add something similar to

:set -isrc:mylib-commandline:mylib-server 

to your .ghci file.


Note that sometimes should split your code into separate packages, e.g. mylib, mylib-commandline and mylib-server.

like image 84
2 revs, 2 users 97% Avatar answered Sep 20 '22 08:09

2 revs, 2 users 97%


You can use cabal repl to start ghci with the configuration from the cabal file and cabal run to compile and run the executables. Unlike runhaskell and ghci, using cabal repl and cabal run also picks up dependencies from cabal sandboxes correctly.

like image 32
Toxaris Avatar answered Sep 18 '22 08:09

Toxaris