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

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?

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.

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.

Related

Build dependency in library or executable section of cabal file?

First off, I'm new to using cabal and external packages with Haskell.
I'm trying to use the Graphics.Gloss package inside of MyLib. I can make it work if I include gloss in both the build-depends of library and executable.
Here is the relevant portion of the cabal file:
library
exposed-modules: MyLib
build-depends: base ^>=4.13.0.0,
gloss ^>=1.13.1.1
default-language: Haskell2010
executable ray-tracer
main-is: Main.hs
other-modules: MyLib
build-depends: base ^>=4.13.0.0, ray-tracer,
haskell-say ^>=1.0.0.0,
gloss ^>=1.13.1.1
MyLib.hs
module MyLib (someFunc) where
import Graphics.Gloss
someFunc :: IO ()
someFunc = display (InWindow "My Window" (200,200) (10,10)) white (Circle 80)
Main.hs
module Main where
import qualified MyLib (someFunc)
import HaskellSay (haskellSay)
main :: IO ()
main = do
MyLib.someFunc
Why doesn't this work when gloss is only included in the library dependencies?
You can make it work. There is a problem in your current set up, which is that the files for the library and the executable are in the same directory. See also this question How to avoid recompiling in this cabal file? which is a symptom of the same underlying problem: when you build the executable, it rebuilds MyLib from scratch (which requires the gloss dependency) instead of reusing your library that was already built.
MyLib/
ray-tracer.cabal
MyLib.hs
Main.hs # Bad
Move the .hs files in separate directories (technically you only need to move one of them, but I think it's better to keep the root directory as uniform as possible):
MyLib/
MyLib.cabal
src/
MyLib.hs
exe/
Main.hs
And in the cabal file, add hs-source-dirs: src and hs-source-dirs: exe to the corresponding sections:
library
hs-source-dirs: src
...
executable ray-tracer
hs-source-dirs: exe
...

How can I configure Cabal to build two executables?

I'm trying to build a Haskell project using Cabal. I have a file src/Main.hs which contains the module Main and a function main. That file runs the web interface of my app. And I have another file, src/CLI.hs which contains the module CLI and a function main. I can run it just fine with runhaskell CLI ..., but I can't seem to compile it using cabal.
The thing is, even if I specify CLI.hs as the main file (main-is: CLI.hs), it still compiles the project with the main from src/Main.hs, thereby giving me the web app instead of the CLI.
I want to be able to compile two executables, one which is the web app, and one which is the CLI, specifying the entry points of each as main in CLI.hs and main in Main.hs, respectively.
Here's the segment of the .cabal file I'm using at the moment:
executable color-word-analyzer-cli
main-is: CLI.hs
other-modules: AnnotateColors
, CategorizeColor
, ColorMaps
, FindColors
, PlotColors
, Types
, Main
build-depends: base
, lucid
...
, wai-middleware-static
hs-source-dirs: src/
default-language: Haskell2010
executable color-word-analyzer-web
main-is: Main.hs
other-modules: AnnotateColors
, CategorizeColor
, ColorMaps
, FindColors
, PlotColors
, Types
, CLI
build-depends: base
, lucid
...
, wai-middleware-static
hs-source-dirs: src/
default-language: Haskell2010
Which throws the error (among others):
Building executable 'color-word-analyzer-cli' for color-word-analyzer-0.1.0.0..
Warning: Enabling workaround for Main module 'Main' listed in 'other-modules'
illegally!
<no location info>: warning: [-Wmissing-home-modules]
These modules are needed for compilation but not listed in your .cabal file's other-modules:
Main
which is funny, since Main is clearly listed there in other-modules.
I'm using cabal version 3.0.0.0, and ghc version 8.8.2.
The fully explicit form of a compilation unit is <package>:<category>:<ident> where in the case the packages is color-word-analyzer, category is exe and ident is the executables. So for your case you can call:
cabal build color-word-analyzer:exe:color-word-analyzer-cli color-word-analyzer:exe:color-word-analyzer-web
Now you don't actually need to specify all of that. When the executable is unique the fact that it is an executable (and not, say, a test from some other package or another package name itself) and the fact that it is from color-word-analyzer is clear in this context. You can therefore call:
cabal build color-word-analyzer-cli color-word-analyzer-web
EDIT: because your link didn't have a stanza for -web I used one of my own creation which didn't include the CLI module. Notice your CLI file is module Main so that explains the error you see - you can't include a module Main as a library module.

How to fix a "could not find module" error in Cabal, when it seems correctly specified?

I'm currently not using stack at all on this project (just Cabal), and all was going well when everything was in Main.hs. I decided to split up code, moving my dhall dep and related code from my executable deps to my library deps, and now seem to have this strange error when running cabal new-build:
Building executable 'FarmDataServer.exe' for FarmDataServer-0.1.0.0..
<no location info>: warning: [-Wmissing-home-modules]
These modules are needed for compilation but not listed in your .cabal file's other-modules: FDS
FDS.Config.Core
FDS.Config.DhallConf
[2 of 4] Compiling FDS.Config.DhallConf ( src/FDS/Config/DhallConf.hs, /home/brandon/workspace/CIDA/FarmDataServer/dist-newstyle/buil
d/x86_64-linux/ghc-8.4.4/FarmDataServer-0.1.0.0/x/FarmDataServer.exe/build/FarmDataServer.exe/FarmDataServer.exe-tmp/FDS/Config/Dhall
Conf.o )
src/FDS/Config/DhallConf.hs:7:1: error:
Could not find module `Dhall'
Use -v to see a list of the files searched for.
|
7 | import Dhall
| ^^^^^^^^^^^^^^^^^^^^^^
Certainly I'm also a bit confused by the Wmissing-home-modules message since I seem to have added those in my cabal file.
Relevant bits of my .cabal file:
cabal-version: 2.4
name: FarmDataServer
version: 0.1.0.0
library
exposed-modules:
FDS
other-modules:
FDS.Config.Core
, FDS.Config.DhallConf
build-depends: base ^>=4.11.1.0
, conduit ^>=1.3.1
, csv-conduit ^>=0.7.0.0
, dhall ^>=1.20.0
, text ^>=1.2.3.1
hs-source-dirs: src
executable FarmDataServer.exe
main-is: Main.hs
build-depends: base ^>=4.11.1.0
, conduit ^>=1.3.1
, csv-conduit ^>=0.7.0.0
, scotty ^>=0.11.3
, text ^>=1.2.3.1
, FarmDataServer ^>=0.1.0.0
My src folder:
$ pwd
/home/brandon/workspace/CIDA/FarmDataServer/src
$ du -a
4 ./FDS/Config/DhallConf.hs
4 ./FDS/Config/Core.hs
12 ./FDS/Config
16 ./FDS
4 ./FDS.hs
4 ./Main.hs
28 .
For the missing modules, sink your program executable into a directory such that the module hierarchy for yoru library isn't visible:
mkdir program ; mv src/Main.hs program/
and in cabal for the executable
hs-source-dirs: program
For your missing module Dhall, add the dhall build dependency to your executable stanza in the cabal file.

Testing Haskell code defined in the Main module

I'm looking for help regarding how to write tests for Haskell code that is defined in the Main module.
The project I want to test is a cabal package in which I've defined multiple executables.
Each executable code is declared only made of a single file (one for each project euler problem), and I usually run them individually with the cabal run command.
I have tried to write a test, also in the Main module, but when compiling, the function I'm trying to test is not found ("Not in scope" error).
What is the right way to write tests in this case?
For information, below is the directory layout of my project:
pe/ # root
pe.cabal
src/
Util.hs
Problem001.hs # "module Main where" and declares a main function
Problem002.hs # "module Main where" and declares a main function
(...)
test/
TestProblem001.hs # "module Main where" and declares a main function
Below is an extract from pe.cabal:
test-suite test-all
hs-source-dirs: test
type: exitcode-stdio-1.0
main-is: TestProblem001.hs
build-depends: base, HUnit, Cabal >= 1.9.2
executable problem-001
hs-source-dirs: src
main-is: Problem001.hs
build-depends: base
ghc-options: -Wall -Werror -O2
[edit]
As I couldn't find any resource for this exact requirement, I opted for a project architecture which is easier to test: problems are defined as library and not individual executables anymore.
I do so
test1 = ...
test2 = ...
main = do
args <- getArgs
case args of
... -> check test1
... -> check test2

Install part of program with cabal like library

I have simple program written with haskell, i build it with cabal. For example i my program has next directory structure:
my-prog
* Main.hs
* my-prog.cabal
* SomeDirWithHsFiles
- File1.hs
- File2.hs
I want that when i'll make cabal build and cabal install (maybe something else), SomeDirWithHsFiles with *.hs files, installed like a normal haskell library, and then i'll use File1.hs and File2.hs modules in other programm.
How can i do this?
Thank you.
You need to declare your additional files in a library section, like so:
library
exposed-modules: File1
File2
executable foo
main-is: Main.hs
See for example, xmonad's .cabal file.

Resources