O's Blog

How to produce multiple executables from a stack project

Ever wondered how to produce multiple executables from your stack project? Neither did I until I found myself unable to do so in an experiment I was playing with. I've figured it out now though, and for future reference, whenever things of this nature come up you'll probably want to look at how it's done in Cabal then extrapolate for Stack making use of the hpack repository for reference, or just use Cabal. I don't want to yet cause I'm in too deep

Anyway, with that said, When you want to generate multiple executables from your stack project, you do so by defining additional entries under the executables stanza in your package.yaml file. The snippet below is an example. In it, we’ve defined two executables that our project may produce. You’ll notice that the main module for each entry is under the same directory.

executables:
  initial-exe:
    main: Main.hs
    source-dirs: app
    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - package-name

  scenario1:
    main: Scenario1.hs
    source-dirs: app
    other-modules: [] # Prevents hpack from auto-discovering `main` modules listed in the other executable entry within the same directory. If we don't add this, stack will try to compile those modules while compiling the project to create this executable
    ghc-options:
      - -fprof-auto
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - package-name

Note that in this case, the module in app/Scenario1.hs must be named Main, not Scenario1. This is because GHC expects the entry point module to be named Main and export a main function. You can work around this using the -main-is ghc-option like so

executables:
  initial-exe:
    main: Main.hs
    source-dirs: app
    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - package-name

  scenario1:
    main: Scenario1.hs
    source-dirs: app
    other-modules: [] # Necessary to have `stack` not also compile `Main.hs` as defined above
    ghc-options:
      - -fprof-auto
      - -threaded
      - -rtsopts
      - -main-is Scenario1 # This allows us avoid needing to set the module name to `Main` in `Scenario1.hs`
      - -with-rtsopts=-N
    dependencies:
      - package-name

People also seem to suggest separating your executables into different directories, like what we have in the snippet below. It is also common for you to hear each executables being referred to as a "targets".

.
├── app <- Holds one executable module
│   └── Main.hs
├── scenario1 <- Holds another executable module
│   └── Main.hs
└── src <- project lib

Then your package.yaml configuration ought to look something like

executables:
  initial-exe:
    main: Main.hs
    source-dirs: app
    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - package-name

  scenario1:
    main: Scenario1.hs
    source-dirs: scenario1
    ghc-options:
      - -fprof-auto
      - -threaded
      - -rtsopts
      # - -main-is Scenario1
      - -with-rtsopts=-N
    dependencies:
      - package-name

By placing the main module of each of our executables in a separate directory, we no longer need to add the other-modules: [] field to each entry since cabal (which stack uses under the hood) becomes smart enough to know that the Main.hs module in the app directory has no relation to the Scenario1.hs module in the scenario1 directory.

Now, if we wanted to execute one of these "targets", we could do

stack run scenario1 -- <options-to-pass-to-executable>

stack run will first compile, then run the resulting executable. If you don't pass an executable name, stack run will compile and run the first executable listed under your project's executables stanza.

stack run  -- <options-to-pass-to-executable>

# Synonymous with

stack run initial-exe -- <options-to-pass-to-executable>

There is also stack exec, but it will only run the executable. It does not re-compile it.

stack exec scenario1 -- <options-to-pass-to-executable>

If you wanted to just compile/build a specific target without running it, you'd need to use the stack build

stack build my-package:exe:my-executable
stack build my-package:exe:my-executable-2
stack build other-package-in-proj:exe:exe3

#Haskell #software-engineering