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 dir
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.
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.