Skip to content

9. Multi-package projects

Everything we have done with Stack so far has used a single-package project, where the project directory is also the package's directory. However, a Stack project can have more than one project package.

Let us demonstrate this with a project that has two project packages named packageA and packageB. We will create a project directory named my-project and, for our example, create the two project packages in subdirectories. Command:

mkdir my-project
cd my-project
stack new packageA --no-init
stack new packageB --no-init
stack init

The --no-init flags above stop Stack from creating project-level configuration files in the packageA and packageB directories that stack new will create.

The stack init command above creates a project-level configuration file (stack.yaml) in the my-project directory. The command should report something like this:

Looking for Cabal or package.yaml files to use to initialise Stack's
project-level YAML configuration file.

Using the Cabal packages:
* packageA\
* packageB\

Selecting the best among 14 snapshots...

Note: Matches https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml

Selected the snapshot https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml.
Initialising Stack's project-level configuration file using snapshot https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml.
Considered 2 user packages.
Writing configuration to stack.yaml.
Stack's project-level configuration file has been initialised.

Ignoring comments in the file, the content of the created stack.yaml file should be something like this:

snapshot:
  url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml

packages:
- packageA
- packageB

The value of the packages key is a list of paths (relative paths, in this example) to project package directories.

If we command stack ide targets, Stack reports the build targets for these two project packages:

packageA:lib
packageA:exe:packageA-exe
packageA:test:packageA-test
packageB:lib
packageB:exe:packageB-exe
packageB:test:packageB-test

If we command stack build, Stack will build all the library and executable components of all the project packages.

One project package can depend on another. Let us demonstrate this by modifying the main library of the packageB package to depend on that of the packageA package.

Currently, the source code of the packageA and packageB packages are the same. Let us first modify the someFunc function exported by the Lib module exposed by the packageA package, as follows:

someFunc :: IO ()
someFunc = putStrLn "someFunc of packageA's Lib module"

and the source code of the Lib module exposed by the packageB package to become:

{-# LANGUAGE PackageImports #-}

module Lib
    ( someFunc
    ) where

import qualified "packageA" Lib as LibA

someFunc :: IO ()
someFunc = do
    putStrLn "someFunc of packageB's Lib module"
    LibA.someFunc

In this example, as the packageA and packageB packages both expose a module named Lib, we have to use GHC's language extension PackageImports to allow imports from the Lib module exposed by the packageA package to be distiguished.

In the package description file (package.yaml) for the packageB package, we need to specify that the dependencies of its main library now include the main library of the packageA package, as follows (extract):

library:
  source-dirs: src
  dependencies:
  - packageA # Add the dependency on the main library of the packageA package

Now, if we command stack build packageB, Stack will build the library and executable components of the packageA package (the dependency) and then the library and executable (named packageB-exe) of packageB.

To execute the built packageB-exe executable, we can command:

stack exec packageB-exe

giving the expected output:

someFunc of packageB's Lib module
someFunc of packageA's Lib module

Note

A project package can depend on another project package, as above. It can also depend on a local package that is specified as an extra-dep. Although both dependencies are local, the former is part of the project and the latter is not.