|
2 | 2 |
|
3 | 3 | # 9. Multi-package projects |
4 | 4 |
|
5 | | -Until now, everything we have done with Stack has used a single-package project. |
6 | | -However, Stack's power truly shines when you are working on multi-package |
7 | | -projects. All the functionality you'd expect to work just does: dependencies |
8 | | -between packages are detected and respected, dependencies of all packages are |
9 | | -just as one cohesive whole, and if anything fails to build, the build commands |
10 | | -exits appropriately. |
| 5 | +Everything we have done with Stack so far has used a single-package project, |
| 6 | +where the project directory is also the package's directory. However, a Stack |
| 7 | +project can have more than one project package. |
11 | 8 |
|
12 | | -Let us demonstrate this with the `wai-app-static` and `yackage` packages, |
13 | | -starting in the root directory for all our Haskell projects. Command: |
14 | | - |
15 | | -~~~text |
16 | | -mkdir multi |
17 | | -cd multi |
18 | | -stack unpack wai-app-static yackage |
19 | | -~~~ |
20 | | - |
21 | | -The last command should report something like: |
22 | | - |
23 | | -~~~text |
24 | | -... |
25 | | -Unpacked wai-app-static (from Hackage) to .../multi/wai-app-static-3.1.9/. |
26 | | -Unpacked yackage (from Hackage) to .../multi/yackage-0.8.1/. |
27 | | -~~~ |
28 | | - |
29 | | -Then command: |
| 9 | +Let us demonstrate this with a project that has two project packages named |
| 10 | +`packageA` and `packageB`. We will create a project directory named `my-project` |
| 11 | +and, for our example, create the two project packages in subdirectories. |
| 12 | +Command: |
30 | 13 |
|
31 | 14 | ~~~text |
| 15 | +mkdir my-project |
| 16 | +cd my-project |
| 17 | +stack new packageA --no-init |
| 18 | +stack new packageB --no-init |
32 | 19 | stack init |
33 | 20 | ~~~ |
34 | 21 |
|
35 | | -The command should report something like: |
| 22 | +The `--no-init` flags above stop Stack from creating project-level configuration |
| 23 | +files in the `packageA` and `packageB` directories that |
| 24 | +[`stack new`](../commands/new_command.md) will create. |
| 25 | + |
| 26 | +The [`stack init`](../commands/init_command.md) command above creates a |
| 27 | +project-level configuration file (`stack.yaml`) in the `my-project` directory. |
| 28 | +The command should report something like this: |
36 | 29 |
|
37 | 30 | ~~~text |
38 | 31 | Looking for Cabal or package.yaml files to use to initialise Stack's |
39 | | -project-level configuration file. |
| 32 | +project-level YAML configuration file. |
40 | 33 |
|
41 | 34 | Using the Cabal packages: |
42 | | -* wai-app-static-3.1.9/ |
43 | | -* yackage-0.8.1/ |
| 35 | +* packageA\ |
| 36 | +* packageB\ |
44 | 37 |
|
45 | | -Cabal file warning in .../multi/yackage-0.8.1/yackage.cabal@47:40: version |
46 | | -operators used. To use version operators the package needs to specify at least |
47 | | -'cabal-version: >= 1.8'. |
48 | | -Cabal file warning in .../multi/yackage-0.8.1/yackage.cabal@21:36: version |
49 | | -operators used. To use version operators the package needs to specify at least |
50 | | -'cabal-version: >= 1.8'. |
51 | | -Selecting the best among 12 snapshots... |
| 38 | +Selecting the best among 14 snapshots... |
52 | 39 |
|
53 | | -Note: Matches ... |
| 40 | +Note: Matches https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml |
54 | 41 |
|
55 | | -Selected the snapshot ... |
56 | | -Initialising Stack's project-level configuration file using snapshot ... |
| 42 | +Selected the snapshot https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml. |
| 43 | +Initialising Stack's project-level configuration file using snapshot https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml. |
57 | 44 | Considered 2 user packages. |
58 | 45 | Writing configuration to stack.yaml. |
59 | 46 | Stack's project-level configuration file has been initialised. |
60 | 47 | ~~~ |
61 | 48 |
|
62 | | -Then command: |
| 49 | +Ignoring comments in the file, the content of the created `stack.yaml` file |
| 50 | +should be something like this: |
| 51 | + |
| 52 | +~~~yaml |
| 53 | +snapshot: |
| 54 | + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/25.yaml |
| 55 | + |
| 56 | +packages: |
| 57 | +- packageA |
| 58 | +- packageB |
| 59 | +~~~ |
| 60 | + |
| 61 | +The value of the [`packages`](../configure/yaml/project.md#packages) key is a |
| 62 | +list of paths (relative paths, in this example) to project package directories. |
| 63 | + |
| 64 | +If we command |
| 65 | +[`stack ide targets`](../commands/ide_command.md#the-stack-ide-targets-command), |
| 66 | +Stack reports the build targets for these two project packages: |
63 | 67 |
|
64 | 68 | ~~~text |
65 | | -stack build --haddock --test |
| 69 | +packageA:lib |
| 70 | +packageA:exe:packageA-exe |
| 71 | +packageA:test:packageA-test |
| 72 | +packageB:lib |
| 73 | +packageB:exe:packageB-exe |
| 74 | +packageB:test:packageB-test |
66 | 75 | ~~~ |
67 | 76 |
|
68 | | -Stack should build and test the project packages. |
| 77 | +If we command |
| 78 | +[`stack build`](../commands/build_command.md#no-targets-specified), Stack will |
| 79 | +build all the library and executable components of all the project packages. |
69 | 80 |
|
70 | | -If you look at the `stack.yaml` file, you will see exactly what you'd expect: |
| 81 | +One project package can depend on another. Let us demonstrate this by modifying |
| 82 | +the main library of the `packageB` package to depend on that of the `packageA` |
| 83 | +package. |
| 84 | + |
| 85 | +Currently, the source code of the `packageA` and `packageB` packages are the |
| 86 | +same. Let us first modify the `someFunc` function exported by the `Lib` module |
| 87 | +exposed by the `packageA` package, as follows: |
| 88 | + |
| 89 | +~~~haskell |
| 90 | +someFunc :: IO () |
| 91 | +someFunc = putStrLn "someFunc of packageA's Lib module" |
| 92 | +~~~ |
| 93 | + |
| 94 | +and the source code of the `Lib` module exposed by the `packageB` package to |
| 95 | +become: |
| 96 | + |
| 97 | +~~~haskell |
| 98 | +{-# LANGUAGE PackageImports #-} |
| 99 | + |
| 100 | +module Lib |
| 101 | + ( someFunc |
| 102 | + ) where |
| 103 | + |
| 104 | +import qualified "packageA" Lib as LibA |
| 105 | + |
| 106 | +someFunc :: IO () |
| 107 | +someFunc = do |
| 108 | + putStrLn "someFunc of packageB's Lib module" |
| 109 | + LibA.someFunc |
| 110 | +~~~ |
| 111 | + |
| 112 | +In this example, as the `packageA` and `packageB` packages both expose a module |
| 113 | +named `Lib`, we have to use GHC's language extension |
| 114 | +[`PackageImports`](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/package_qualified_imports.html) |
| 115 | +to allow imports from the `Lib` module exposed by the `packageA` package to be |
| 116 | +distiguished. |
| 117 | + |
| 118 | +In the package description file (`package.yaml`) for the `packageB` package, we |
| 119 | +need to specify that the dependencies of its main library now include the main |
| 120 | +library of the `packageA` package, as follows (extract): |
71 | 121 |
|
72 | 122 | ~~~yaml |
73 | | -snapshot: |
74 | | - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/31.yaml |
75 | | -packages: |
76 | | -- wai-app-static-3.1.9 |
77 | | -- yackage-0.8.1 |
| 123 | +library: |
| 124 | + source-dirs: src |
| 125 | + dependencies: |
| 126 | + - packageA # Add the dependency on the main library of the packageA package |
78 | 127 | ~~~ |
79 | 128 |
|
80 | | -Notice that multiple directories are listed in the `packages` key. |
| 129 | +Now, if we command `stack build packageB`, Stack will build the library and |
| 130 | +executable components of the `packageA` package (the dependency) and then the |
| 131 | +library and executable (named `packageB-exe`) of `packageB`. |
| 132 | + |
| 133 | +To execute the built `packageB-exe` executable, we can command: |
81 | 134 |
|
82 | | -In addition to local directories, you can also refer to packages available in a |
83 | | -Git repository or in a tarball over HTTP/HTTPS. This can be useful for using a |
84 | | -modified version of a dependency that has not yet been released upstream. |
| 135 | +~~~text |
| 136 | +stack exec packageB-exe |
| 137 | +~~~ |
| 138 | + |
| 139 | +giving the expected output: |
| 140 | + |
| 141 | +~~~text |
| 142 | +someFunc of packageB's Lib module |
| 143 | +someFunc of packageA's Lib module |
| 144 | +~~~ |
85 | 145 |
|
86 | 146 | !!! note |
87 | 147 |
|
88 | | - When adding upstream packages directly to your project it is important to |
89 | | - distinguish _project packages_ located locally from the upstream |
90 | | - _dependency packages_. Otherwise you may have trouble running `stack ghci`. |
91 | | - See [stack.yaml documentation](../configure/yaml/project.md#packages) for |
92 | | - more details. |
| 148 | + A project package can depend on another project package, as above. It can |
| 149 | + also depend on a local package that is specified as an |
| 150 | + [extra-dep](../configure/yaml/project.md#extra-deps). Although both |
| 151 | + dependencies are local, the former is part of the project and the latter is |
| 152 | + not. |
0 commit comments