Skip to content

Commit 21d6949

Browse files
authored
Merge pull request #3752 from commercialhaskell/stack-vs-cabal-config
Stack vs cabal config files (docs)
2 parents db62080 + b046f09 commit 21d6949

File tree

3 files changed

+141
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)