Skip to content

Commit af5c97f

Browse files
committed
Stack vs cabal config files (docs)
1 parent e940f49 commit af5c97f

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

doc/stack_vs_cabal_config.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<div class="hidden-warning"><a href="https://docs.haskellstack.org/"><img src="https://rawgit.com/commercialhaskell/stack/master/doc/img/hidden-warning.svg"></a></div>
2+
3+
# stack vs cabal config files
4+
5+
Due to their apparent overlap, the purpose of the following three files can be
6+
unclear:
7+
8+
* `stack.yaml`
9+
* A cabal file, e.g. `my-package.cabal`
10+
* `package.yaml`
11+
12+
The last two are easy to explain: `package.yaml` is a file format supported by
13+
[hpack](https://github.com/sol/hpack#readme), which adds some niceties on top
14+
of cabal like YAML syntax support and automatic generation of `exposed-modules`
15+
lists. However, it's just a frontend to cabal files. So for this document,
16+
we're instead going to focus on the first two and try to answer:
17+
18+
_What's the difference between a `stack.yaml` file and a cabal file?_
19+
20+
## Package versus project
21+
22+
Cabal is a build system, which is used by Stack. Cabal defines the concept of a
23+
_package_. A package has:
24+
25+
* A name and version
26+
* 0 or 1 libraries
27+
* 0 or more executables
28+
* A cabal file (or, as mentioned above, an hpack `package.yaml` that
29+
generates a cabal file)
30+
* And a bunch more
31+
32+
That last bullet bears repeating: there's a 1-to-1 correspondence between
33+
packages and cabal files.
34+
35+
Stack is a build tool that works on top of the Cabal build system, and defines
36+
a new concept called a _project_. A project has:
37+
38+
* A _resolver_, which tells it about a snapshot (more on this later)
39+
* Extra dependencies on top of the snapshot
40+
* 0 or more local Cabal packages
41+
* Flag and GHC options configurations
42+
* And a bunch more Stack configuration
43+
44+
A source of confusion is that, often, you'll have a project that defines
45+
exactly one package you're working on, and in that situation it's unclear why,
46+
for example, you need to specify an extra depedency in both your `stack.yaml`
47+
_and_ cabal file. To explain, let's take a quick detour to talk about snapshots
48+
and how Stack resolves dependencies.
49+
50+
## Resolvers and snapshots
51+
52+
Stack follows a rule that says, for any projects, there is precisely 1 version
53+
of each package available. Obviously there are _many_ versions of many
54+
different packages available in the world. But when resolving a `stack.yaml`
55+
file, Stack requires that you have chosen a specific version for each package
56+
available.
57+
58+
The most common means by which this set of packages is defined is via a
59+
Stackage Snapshot. For example, if you go to the page
60+
<https://www.stackage.org/lts-10.2>, you will see a list of 2,666 packages at
61+
specific version numbers. When you then specify `resolver: lts-10.2`, you're
62+
telling Stack to use those package versions in resolving dependencies down to
63+
concrete version numbers.
64+
65+
Sometimes a snapshot doesn't have all of the packages you want. Or you want a
66+
different version. Or you want to work on a local modification of a package. In
67+
all of those cases, you can add more configuration data to your `stack.yaml` to
68+
override the values it received from your `resolver` setting. At the end of the
69+
day, each of your projects will end up with some way of resolving a package
70+
name into a concrete version number.
71+
72+
## Why specify deps twice?
73+
74+
When you add something like this to your `stack.yaml` file:
75+
76+
```yaml
77+
extra-deps:
78+
- acme-missiles-0.3
79+
```
80+
81+
What you're saying to Stack is: if at any point you find that you need to build
82+
the `acme-missiles` package, please use version `0.3`. You are _not_ saying
83+
"please build `acme-missiles` now." You are also not saying "my package depends
84+
on `acme-missiles." You are simply making it available should the need arise.
85+
86+
When you add `build-depends: acme-missiles` to your cabal file or
87+
`dependencies: [acme-missiles]` to your `package.yaml` file, you're saying
88+
"this package requires that `acme-missiles` be available." Since
89+
`acme-missiles` doesn't appear in your snapshot, without also modifying your
90+
`stack.yaml` to mention it via `extra-deps`, Stack will complain about the
91+
dependency being unavailable.
92+
93+
You may challenge: but why go through all of that annoyance? Stack knows what
94+
package I want, why not just go grab it? The answer is that, if Stack just
95+
grabbed `acme-missiles` for you without it being specified in the `stack.yaml`
96+
somehow, you'd lose reproducibility. How would Stack know which version to use?
97+
It may elect to use the newest version, but if a new version is available in
98+
the future, will it automatically switch to that?
99+
100+
Stack's baseline philosoph is that build plans are always reproducible\*. The
101+
purpose of the `stack.yaml` file is to define an immutable set of packages. No
102+
matter when in time you use it, and no matter how many new release happen in
103+
the interim, the build plan generated should be the same.
104+
105+
\* There's at least one hole in this theory today, which is Hackage revisions.
106+
When you specify `extra-deps: [acme-missiles-0.3]`, it doesnt' specify which
107+
revision of the cabal file to use, and Stack will just choose the latest. Stack
108+
version 1.6 added the ability to specify exact revisions of cabal files, but
109+
this isn't enforced as a requirement as it's so different from the way most
110+
people work with packages.
111+
112+
And now, how about the other side: why doesn't Stack automatically add
113+
`acme-missiles` to `build-depends` in your cabal file if you add it as an
114+
extra-dep? There are a surprising number reasons actually:
115+
116+
* The cabal spec doesn't support anything like that
117+
* There can be multiple packages in a project, and how do we know which package
118+
actually needs the dependency?
119+
* There can be multiple components (libraries, executable, etc) in a package,
120+
and how do we know which of those actually needs the dependency?
121+
* The dependency may only be conditionally needed, based on flags, OS, or
122+
architecture. As an extreme example, we wouldn't want a Linux-only package to
123+
be force-built on Windows.
124+
125+
While for simple use cases it seems like automatically adding dependencies from
126+
the cabal file to the `stack.yaml` file or vice-versa would be a good thing, it
127+
breaks down immediately for any semi-difficult case. Therefore, Stack requires
128+
you to add it to both places.
129+
130+
And a final note, in case it wasn't clear. The example I gave above used
131+
`acme-missiles`, which is not in Stackage snapshots. If, however, you want to
132+
depend on a package already present in the snapshot you've selected, there's no
133+
need to add it explicitly to your `stack.yaml` file: it's already there
134+
implicitly via the `resolver` setting. This is what you do the majority of the
135+
time, such as when you add `vector` or `mtl` as a `build-depends` value.

doc/yaml_configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ specific options from `~/.stack/global-project/stack.yaml`. When stack is
2222
invoked inside a stack project, only options from `<project dir>/stack.yaml` are
2323
used, and `~/.stack/global-project/stack.yaml` is ignored.
2424

25+
*Note 2:* A common source of confusion is the distinction between configuration
26+
in a `stack.yaml` file versus a cabal file. If you're trying to understand this
27+
breakdown, see [stack vs cabal config](stack_vs_cabal_config.md)
28+
2529
## Project-specific config
2630

2731
Project-specific options are only valid in the `stack.yaml` file local to a

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pages:
1919
- User guide: GUIDE.md
2020
- FAQ: faq.md
2121
- Configuration (Project and Global): yaml_configuration.md
22+
- stack.yaml vs cabal config: stack_vs_cabal_config.md
2223
- Build command: build_command.md
2324
- Dependency visualization: dependency_visualization.md
2425
- Docker integration: docker_integration.md

0 commit comments

Comments
 (0)