|
| 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. |
0 commit comments