1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
|
<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>
# Build overview
!!! warning
This document should not be considered accurate until this warning is
removed.
This is a work-in-progress document covering the build process used by
Stack. It was started following the Pantry rewrite work in Stack 2.1.1, and
contains some significant changes/simplifications from how things used to
work. This document will likely not fully be reflected in the behavior of
Stack itself until late in the Stack 2.0 development cycle.
## Terminology
* Project package: anything listed in `packages` in stack.yaml
* Dependency: anything listed in extra-deps or a snapshot
* Target: package and/or component listed on the command line to be built. Can
be either project package or dependency. If none specified, automatically
targets all project packages
* Immutable package: a package which comes from Hackage, an archive, or a
repository. In contrast to...
* Mutable package: a package which comes from a local file path. The contents
of such a package are assumed to mutate over time.
* Write only database: a package database and set of executables for a given set
of _immutable_ packages. Only packages from immutable sources and which
depend exclusively on other immutable packages can be in this database.
*NOTE* formerly this was the _snapshot database_.
* Mutable database: a package database and set of executables for packages which
are either mutable or depend on such mutable packages. Importantly, packages
in this database can be unregister, replaced, etc, depending on what happens
with the source packages. *NOTE* formerly this was the *local database*.
Outdated terminology to be purged:
* Wanted
* Local
* Snapshot package
## Inputs
Stack pays attention to the following inputs:
* Current working directory, used for finding the default `stack.yaml` file and
resolving relative paths
* The `STACK_YAML` environment variable
* Command line arguments (CLI args), as will be referenced below
Given these inputs, Stack attempts the following process when performing a build.
## Find the `stack.yaml` file
* Check for a `--stack-yaml` CLI arg, and use that
* Check for a `STACK_YAML` env var
* Look for a `stack.yaml` in this directory or ancestor directories
* Fall back to the default global project
This file is parsed to provide the following config values:
* `snapshot` (or, alternatively, `resolver`) (required field)
* `compiler` (optional field)
* `packages` (optional field, defaults to `["."]`)
* `extra-deps` (optional field, defaults to `[]`)
* `flags` (optional field, defaults to `{}`)
* `ghc-options` (optional field, defaults to `{}`)
`flags` and `ghc-options` break down into both _by name_ (applied to a
specific package) and _general_ (general option `*` for flags is only available
in CLI).
## Wanted compiler, dependencies, and project packages
* If the `--snapshot` CLI is present, ignore the `snapshot` (or `resolver`) and
`compiler` config values
* Load up the indicated snapshot (either config value or CLI arg). This will
provide:
* A map from package name to package location, flags, GHC options,
and if a package should be hidden. All package locations here
are immutable.
* A wanted compiler version, e.g. `ghc-8.6.5`
* If the `--compiler` CLI arg is set, or the `compiler` config value
is set (and `--snapshot` CLI arg is not set), ignore the wanted
compiler from the snapshot and use the specified wanted compiler
* Parse `extra-deps` into a `Map PackageName PackageLocation`,
containing both mutable and immutable package locations. Parse
`packages` into a `Map PackageName ProjectPackage`.
* Ensure there are no duplicates between these two sets of packages
* Delete any packages from the snapshot packages that appear in
`packages` or `extra-deps`
* Perform a left biased union between the immutable `extra-deps`
values and the snapshot packages. Ignore any settings in the
snapshot packages that have been replaced.
* Apply the `flags` and `ghc-options` by name to these packages overwriting
any previous values coming from a snapshot. If any values are specified
but no matching package is found, it's an error. If a flag is not defined
in the corresponding package cabal file, it's an error.
* We are now left with the following:
* A wanted compiler version
* A map from package name to immutable packages with package config (flags,
GHC options, hidden)
* A map from package name to mutable packages as dependencies with package
config
* A map from package name to mutable packages as project packages with
package config
## Get actual compiler
Use the wanted compiler and various other Stack config values (not all
listed here) to find the actual compiler, potentially installing it in
the process.
## Global package sources
With the actual compiler discovered, list out the packages available
in its database and create a map from package name to
version/GhcPkgId. Remove any packages from this map which are present
in one of the other three maps mentioned above.
## Resolve targets
Take the CLI args for targets as raw text values and turn them into
actual targets.
* Do a basic parse of the values into one of the following:
* Package name
* Package identifier
* Package name + component
* Directory
* An empty target list is equivalent to listing the package names of
all project packages
* For any directories specified, find all project packages in that
directory or subdirectories therefore and convert to those package
names
* For all package identifiers, ensure that either the package name
does not exist in any of the three parsed maps from the "wanted
compiler" step above, or that the package is present as an immutable
dependency from Hackage. If so, create an immutable dependency entry
with default flags, GHC options, and hidden status, and add this
package to the set of immutable package dependencies.
* For all package names, ensure the package is in one of the four maps
we have, and if so add to either the dependency or project package
target set.
* For all package name + component, ensure that the package is a
project package, and add that package + component to the set of
project targets.
* Ensure that no target has been specified multiple times. (*FIXME*
Mihai states: I think we will need an extra consistency step for
internal libraries. Sometimes stack needs to use the mangled name
(`z-package-internallibname-z..`), sometimes the
`package:internallibname` one. But I think this will become obvious
when doing the code changes.)
We now have an update four package maps, a new set of dependency
targets, and a new set of project package targets (potentially with
specific components).
## Apply named CLI flags
Named CLI flags are applied to specific packages by updating the
config in one of the four maps. If a flag is specified and no package
is found, it's an error. Note that flag settings are added _on top of_
previous settings in this case, and does not replace them. That is, if
previously we have `singleton (FlagName "foo") True` and now add
`singleton (FlagName "bar") True`, both `foo` and `bar` will now be
true. If any flags are specified but no matching package is found,
it's an error. If a flag is not defined in the corresponding package
cabal file, it's an error.
## Apply CLI GHC options
CLI GHC options are applied as general GHC options according to
`apply-ghc-options` setting.
## Apply general flags from CLI
`--flag *:flagname[:bool]` specified on the CLI are applied to any
project package which uses that flag name.
## Apply general GHC options
General options are divided into the following categories:
* `$locals` is deprecated, it's now a synonym for `$project`
* `$project` applies to all project packages, not to any dependencies
* `$targets` applies to all project packages that are targets, not to any
dependencies or non-target project packages. This is the default option
for `apply-ghc-options`
* `$everything` applies to all packages in the source map excluding
global packages
These options get applied to any corresponding packages in
the source map. If some GHC options already exist for such a package then
they get prepended otherwise they get used as is.
## Determine snapshot hash
Use some deterministic binary serialization and SHA256 thereof to get
a hash of the following information:
* Actual compiler (GHC version, path, *FIXME* probably some other
unique info from GHC, I've heard that `ghc --info` gives you
something)
* Global database map
* Immutable dependency map
Motivation: Any package built from the immutable dependency map and
installed in this database will never need to be rebuilt.
!!! bug "To do"
Caveat: do we need to take profiling settings into account here? How about
Haddock status?
## Determine actual target components
* Dependencies: "default" components (all libraries and executables)
* Project packages:
* If specific components named: only those, plus any libraries present
* If no specific components, include the following:
* All libraries, always
* All executables, always
* All test suites, _if_ `--test` specified on command line
* All benchmarks, _if_ `--bench` specified on command line
## Construct build plan
* Applied to every target (project package or dependency)
* Apply flags, platform, and actual GHC version to resolve
dependencies in any package analyzed
* Include all library dependencies for all enabled components
* Include all build tool dependencies for all enabled components
(using the fun backwards compat logic for `build-tools`)
* Apply the logic recursively to come up with a full build plan
* If a task depends exclusively on immutable packages, mark it as
immutable. Otherwise, it's mutable. The former go into the snapshot
database, the latter into the local database.
We now have a set of tasks of packages/components to build, with full
config information for each package, and dependencies that must be
built first.
!!! bug "To do"
There's some logic to deal with cyclic dependencies between test suites and
benchmarks, where a task can be broken up into individual components versus
be kept as a single task. Need to document this better. Currently it's the
"all in one" logic.
## Unregister local modified packages
* For all mutable packages in the set of tasks, see if any files have
changed since last successful build and, if so, unregister + delete
their executables
* For anything which depends on them directly or transitively,
unregister + delete their executables
## Perform the tasks
* Topological sort, find things which have no dependencies remaining
* Check if already installed in the relevant database
* Check package database
* Check Stack specific "is installed" flags, necessary for
non-library packages
* For project packages, need to also check which components were
built, if tests were run, if we need to rerun tests, etc
* If all good: do nothing
* Otherwise, for immutable tasks: check the precompiled cache for an
identical package installation (same GHC, dependencies, etc). If
present: copy that over, and we're done.
* Otherwise, perform the build, register, write to the Stack specific
"is installed" stuff, and (for immutable tasks) register to the
precompiled cache
"Perform the build" consists of:
* Do a cabal configure, if needed
* Build the desired components
* For all test suites built, unless "no rerun tests" logic is on and
we already ran the test, _or_ "no run tests" is on, run the test
* For all benchmarks built, unless "no run benchmarks" is on, run the
benchmark
|