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