Pantry in Stack

Beginning with Stack 1.11, Stack uses the Pantry library for its specification of snapshots and package locations. Under the surface, Pantry is geared towards reproducible build plans with cryptographically secure specification of packages and snapshots.

There are three user-visible components to Pantry's configuration which affect usage of Stack:

  • Snapshot location specification (in the resolver field)
  • Package location specification (in the extra-deps field and inside snapshots)
  • Snapshot specification, for creating custom snapshots

Snapshot location

There are essentially four different ways of specifying a snapshot location:

  • Via a compiler version, which is a "compiler only" snapshot. This could be, e.g., resolver: ghc-8.6.5.
  • Via a URL pointing to a snapshot configuration file, e.g. resolver: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2018/8/21.yaml
  • Via a local file path pointing to a snapshot configuration file, e.g. resolver: my-local-snapshot.yaml
  • Via a convenience synonym, which provides a short form for some common URLs. These are:
    • Github: github:user/repo:path is treated as https://raw.githubusercontent.com/user/repo/master/path
    • LTS Haskell: lts-X.Y is treated by default as github:commercialhaskell/stackage-snapshots:lts/X/Y.yaml
    • Stackage Nightly: nightly-YYYY-MM-DD is treated by default as github:commercialhaskell/stackage-snapshots:nightly/YYYY/M/D.yaml

By default, LTS Haskell/Stackage Nightly snapshot configurations are retrieved from commercialhaskell's GitHub repository. You can set a custom location in the snapshot-location-base yaml configuration field.

For safer, more reproducible builds, you can optionally specify a URL together with a cryptographic hash of its content, e.g.:

resolver:
  size: 499143
  url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/12/0.yaml
  sha256: 781ea577595dff08b9c8794761ba1321020e3e1ec3297fb833fe951cce1bee11

Where the size is the number of bytes in the file, and sha256 is its SHA256 hash. If not provided, the information will automatically be generated and stored in a lock file.

Package location

Pantry supports three types of package locations:

  • Hackage packages
  • Repositories
  • Archives

All three of these formats support optional tree metadata to be added, which can be used for reproducibility and faster downloads. This information can automatically be generated in a lock file.

Hackage

Packages can be stated by a name/version combination. The basic syntax for this is:

extra-deps:
- acme-missiles-0.3

Using this syntax, the most recent Cabal file revision available will be used. For more reproducibility of builds, it is recommended to state the SHA256 hash of the cabal file contents as well, like this:

extra-deps:
- acme-missiles-0.3@sha256:2ba66a092a32593880a87fb00f3213762d7bca65a687d45965778deb8694c5d1

Or, better yet, including the cabal file size too:

extra-deps:
- acme-missiles-0.3@sha256:2ba66a092a32593880a87fb00f3213762d7bca65a687d45965778deb8694c5d1,631

Or a specific revision number, with 0 being the original file:

extra-deps:
- acme-missiles-0.3@rev:0

Note that specifying via SHA256 is slightly more resilient in that it does not rely on correct ordering in the package index, while revision number is likely simpler to use. In practice, both should guarantee equally reproducible build plans.

Finally, you can include the Pantry tree information. The following would be generated and stored in the lock file:

- hackage: acme-missiles-0.3@sha256:2ba66a092a32593880a87fb00f3213762d7bca65a687d45965778deb8694c5d1,613
  pantry-tree:
    size: 226
    sha256: 614bc0cca76937507ea0a5ccc17a504c997ce458d7f2f9e43b15a10c8eaeb033

Git and Mercurial repos

You can give a Git or Mercurial repo at a specific commit, and Stack will clone that repo.

extra-deps:
- git: git@github.com:commercialhaskell/stack.git
  commit: 6a86ee32e5b869a877151f74064572225e1a0398
- git: git@github.com:snoyberg/http-client.git
  commit: "a5f4f3"
- hg: https://example.com/hg/repo
  commit: da39a3ee5e6b4b0d3255bfef95601890afd80709

NOTE It is highly recommended that you only use SHA1 values for a Git or Mercurial commit. Other values may work, but they are not officially supported, and may result in unexpected behavior (namely, Stack will not automatically pull to update to new versions). Another problem with this is that your build will not be deterministic, because when someone else tries to build the project they can get a different checkout of the package.

A common practice in the Haskell world is to use "megarepos", or repositories with multiple packages in various subdirectories. Some common examples include wai and digestive-functors. To support this, you may also specify subdirs for repositories, e.g.:

extra-deps:
- git: git@github.com:yesodweb/wai
  commit: 2f8a8e1b771829f4a8a77c0111352ce45a14c30f
  subdirs:
  - auto-update
  - wai

Since v1.7.1, you can specify packages from GitHub repository name using github:

extra-deps:
- github: snoyberg/http-client
  commit: a5f4f30f01366738f913968163d856366d7e0342

If unspecified, subdirs defaults to ['.'] meaning looking for a package in the root of the repo. Note that if you specify a value of subdirs, then '.' is not included by default and needs to be explicitly specified if a required package is found in the top-level directory of the repository.

Limited git-annex support

Pantry does not support git-annex. This is because git archive does not handle symbolic links outside the work tree. It is still possible to use repositories which use git-annex but do not require the annex files for the package to be built.

To do so, ensure that any files or directories stored by git-annex are marked export-ignore in the .gitattributes file in the repository. See #4579 for more information.

For example, if the directory fonts/ is controlled by git-annex, use the following line.

fonts export-ignore

Archives (HTTP(S) or local filepath)

You can use HTTP and HTTPS URLs and local filepaths referring to either tarballs or ZIP files.

NOTE Stack assumes that these files never change after downloading to avoid needing to make an HTTP request on each build. Use hashes to provide more security.

extra-deps:
- https://example.com/foo/bar/baz-0.0.2.tar.gz
- archive: http://github.com/yesodweb/wai/archive/2f8a8e1b771829f4a8a77c0111352ce45a14c30f.zip
  subdirs:
  - wai
  - warp
- archive: ../acme-missiles-0.3.tar.gz
  sha256: e563d8b524017a06b32768c4db8eff1f822f3fb22a90320b7e414402647b735b

Snapshots

NOTE Stack has supported custom snapshots properly since version 1.6. In version 1.11, the support for snapshots was moved to Pantry, and Stackage snapshots have moved over to using the same format. Therefore, there is no longer such a thing as "custom snapshots," there are simply "snapshots." Pantry snapshots follow the same format as Stack 1.6 "custom snapshots."

Snapshots provide a list of packages to use, along with flags, ghc-options, and a few other settings. Snapshots may extend any other snapshot that can be specified in a resolver field. The packages specified follow the same syntax mentioned above for dependencies. Unlike extra-deps, however, no support for local directories is available in snapshots to ensure reproducibility.

resolver: lts-8.21 # Inherits GHC version and package set
compiler: ghc-8.0.1 # Overwrites GHC version in the resolver, optional

# Additional packages, follows extra-deps syntax
packages:
- unordered-containers-0.2.7.1
- hashable-1.2.4.0
- text-1.2.2.1

# Override flags, can also override flags in the parent snapshot
flags:
  unordered-containers:
    debug: true

# Packages from the parent snapshot to ignore
drop-packages:
- wai-extra

# Packages which should be hidden (affects script command's import
# parser
hidden:
  wai: true
  warp: false

# Set GHC options for specific packages
ghc-options:
  warp:
  - -O2

If you put this in a snapshot.yaml file in the same directory as your project, you can now use the custom snapshot like this:

resolver: snapshot.yaml

This is an example of a custom snapshot stored in the filesystem. They are assumed to be mutable, so you are free to modify it. We detect that the snapshot has changed by hashing the contents of the involved files, and using it to identify the snapshot internally. It is often reasonably efficient to modify a custom snapshot, due to stack sharing snapshot packages whenever possible.

Overriding the compiler

The following snapshot specification will be identical to lts-7.1, but instead use ghc-7.10.3 instead of ghc-8.0.1:

resolver: lts-7.1
compiler: ghc-7.10.3

Dropping packages

The following snapshot specification will be identical to lts-7.1, but without the text package in our snapshot. Removing this package will cause all the packages that depend on text to be unbuildable, but they will still be present in the snapshot.

resolver: lts-7.1
drop-packages:
  - text

Specifying ghc-options

In order to specify ghc-options for a package, you use the same syntax as the ghc-options field for build configuration. The following snapshot specification will be identical to lts-7.1, but provides -O1 as a ghc-option for text:

resolver: lts-7.1
packages:
  - text-1.2.2.1
ghc-options:
  text: -O1

This works somewhat differently than the stack.yaml ghc-options field, in that options can only be specified for packages that are mentioned in the custom snapshot's packages list. It sets the ghc-options, rather than extending those specified in the snapshot being extended.

Another difference is that the * entry for ghc-options applies to all packages in the packages list, rather than all packages in the snapshot.

Specifying flags

In order to specify flags for a package, you use the same syntax as the flags field for build configuration. The following snapshot specification will be identical to lts-7.1, but it enables the developer cabal flag:

resolver: lts-7.1
packages:
  - text-1.2.2.1
flags:
  text:
    developer: true