packages feed

stack 0.1.6.0 → 0.1.8.0

raw patch · 86 files changed

+4915/−2963 lines, 86 filesdep +edit-distancedep +semigroupsdep −ignoredep ~Cabaldep ~QuickCheckdep ~Win32

Dependencies added: edit-distance, semigroups

Dependencies removed: ignore

Dependency ranges changed: Cabal, QuickCheck, Win32, async, bytestring, conduit, conduit-extra, containers, cryptohash, deepseq, directory, exceptions, filepath, hspec, http-conduit, monad-logger, optparse-applicative, path, process, resourcet, retry, stack, temporary, text, transformers, unix, unix-compat

Files

ChangeLog.md view
@@ -1,11 +1,83 @@-## Unreleased changes+## 0.1.8.0 +Major changes: +* GHCJS can now be used with stackage snapshots via the new `compiler` field.+* Windows installers are now available:+  [download them here](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#windows)+  [#613](https://github.com/commercialhaskell/stack/issues/613)+* Docker integration works with non-FPComplete generated images+  [#531](https://github.com/commercialhaskell/stack/issues/531)++Other enhancements:++* Added an `allow-newer` config option+  [#922](https://github.com/commercialhaskell/stack/issues/922)+  [#770](https://github.com/commercialhaskell/stack/issues/770)+* When a Hackage revision invalidates a build plan in a snapshot, trust the+  snapshot [#770](https://github.com/commercialhaskell/stack/issues/770)+* Added a `stack config set resolver RESOLVER` command. Part of work on+  [#115](https://github.com/commercialhaskell/stack/issues/115)+* `stack setup` can now install GHCJS on windows. See+  [#1145](https://github.com/commercialhaskell/stack/issues/1145) and+  [#749](https://github.com/commercialhaskell/stack/issues/749)+* `stack hpc report` command added, which generates reports for HPC tix files+* `stack ghci` now accepts all the flags accepted by `stack build`. See+  [#1186](https://github.com/commercialhaskell/stack/issues/1186)+* `stack ghci` builds the project before launching GHCi. If the build fails,+  optimistically launch GHCi anyway. Use `stack ghci --no-build` option to+  disable [#1065](https://github.com/commercialhaskell/stack/issues/1065)+* `stack ghci` now detects and warns about various circumstances where it is+  liable to fail. See+  [#1270](https://github.com/commercialhaskell/stack/issues/1270)+* Added `require-docker-version` configuration option+* Packages will now usually be built along with their tests and benchmarks. See+  [#1166](https://github.com/commercialhaskell/stack/issues/1166)+* Relative `local-bin-path` paths will be relative to the project's root+  directory, not the current working directory.+  [#1340](https://github.com/commercialhaskell/stack/issues/1340)+* `stack clean` now takes an optional `[PACKAGE]` argument for use in+  multi-package projects. See+  [#583](https://github.com/commercialhaskell/stack/issues/583)+* Ignore cabal_macros.h as a dependency+  [#1195](https://github.com/commercialhaskell/stack/issues/1195)+* Pad timestamps and show local time in --verbose output+  [#1226](https://github.com/commercialhaskell/stack/issues/1226)+* GHCi: Import all modules after loading them+  [#995](https://github.com/commercialhaskell/stack/issues/995)+* Add subcommand aliases: `repl` for `ghci`, and `runhaskell` for `runghc`+  [#1241](https://github.com/commercialhaskell/stack/issues/1241)+* Add typo recommendations for unknown package identifiers+  [#158](https://github.com/commercialhaskell/stack/issues/158)+* Add `stack path --local-hpc-root` option+* Overhaul dependencies' haddocks copying+  [#1231](https://github.com/commercialhaskell/stack/issues/1231)+* Support for extra-package-dbs in 'stack ghci'+  [#1229](https://github.com/commercialhaskell/stack/pull/1229)+* `stack new` disallows package names with "words" consisting solely of numbers+  [#1336](https://github.com/commercialhaskell/stack/issues/1336)+* `stack build --fast` turns off optimizations++Bug fixes:++* Fix: Haddocks not copied for dependencies+  [#1105](https://github.com/commercialhaskell/stack/issues/1105)+* Fix: Global options did not work consistently after subcommand+  [#519](https://github.com/commercialhaskell/stack/issues/519)+* Fix: 'stack ghci' doesn't notice that a module got deleted+  [#1180](https://github.com/commercialhaskell/stack/issues/1180)+* Rebuild when cabal file is changed+* Fix: Paths in GHC warnings not canonicalized, nor those for packages in+  subdirectories or outside the project root+  [#1259](https://github.com/commercialhaskell/stack/issues/1259)+* Fix: unlisted files in tests and benchmarks trigger extraneous second build+  [#838](https://github.com/commercialhaskell/stack/issues/838)+ ## v0.1.6.0  Major changes: -* "stack setup" now supports building and booting GHCJS from source tarball.+* `stack setup` now supports building and booting GHCJS from source tarball. * On Windows, build directories no longer display "pretty" information   (like x86_64-windows/Cabal-1.22.4.0), but rather a hash of that   content. The reason is to avoid the 260 character path limitation on
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2015, stack+Copyright (c) 2015, Stack contributors All rights reserved.  Redistribution and use in source and binary forms, with or without@@ -15,7 +15,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY+DISCLAIMED. IN NO EVENT SHALL STACK CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
README.md view
@@ -1,7 +1,7 @@ ## The Haskell Tool Stack  [![Build Status](https://travis-ci.org/commercialhaskell/stack.svg?branch=master)](https://travis-ci.org/commercialhaskell/stack)-[![Windows build status](https://ci.appveyor.com/api/projects/status/github/commercialhaskell/stack)](https://ci.appveyor.com/project/snoyberg/stack)+[![Windows build status](https://ci.appveyor.com/api/projects/status/c1c7uvmw6x1dupcl?svg=true)](https://ci.appveyor.com/project/snoyberg/stack) [![Release](https://img.shields.io/github/release/commercialhaskell/stack.svg)](https://github.com/commercialhaskell/stack/releases)  `stack` is a cross-platform program for developing Haskell@@ -22,7 +22,7 @@ Downloads are available by operating system:  * [Windows](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#windows)-* [OS X](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#os-x)+* [Mac OS X](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#mac-os-x) * [Ubuntu](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#ubuntu) * [Debian](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#debian) * [CentOS / Red Hat / Amazon Linux](https://github.com/commercialhaskell/stack/blob/release/doc/install_and_upgrade.md#centos--red-hat--amazon-linux)
doc/GUIDE.md view
@@ -864,7 +864,7 @@ which contains the libraries and executables specified in the snapshot itself. Finally, GHC itself ships with a number of libraries and executables, which forms the *global* database. To get a quick idea of this, we can look at the-output of the `ghc-pkg list` command in our helloworld project:+output of the `stack exec ghc-pkg list` command in our helloworld project:  ``` /home/michael/.stack/programs/x86_64-linux/ghc-7.10.2/lib/ghc-7.10.2/package.conf.d@@ -939,7 +939,7 @@ Let's look at a subset of the `stack --help` output:  ```-build    Build the project(s) in this directory/configuration+build    Build the package(s) in this directory/configuration install  Shortcut for 'build --copy-bins' test     Shortcut for 'build --test' bench    Shortcut for 'build --bench'@@ -1603,7 +1603,7 @@ # Download and unpack the stack executable - mkdir -p ~/.local/bin - export PATH=$HOME/.local/bin:$PATH-- travis_retry curl -L https://github.com/commercialhaskell/stack/releases/download/v0.1.6.0/stack-0.1.6.0-linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'+- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'  # This line does all of the work: installs GHC if necessary, build the library, # executables, and test suites, and runs the test suites. --no-terminal works
doc/MAINTAINER_GUIDE.md view
@@ -1,38 +1,135 @@+## Pre-release checks+ The following should be tested minimally before a release is considered good-to go. This list will likely expand over time:+to go: -* Run `etc/scripts/release.hs check` on Linux (32-bit and 64-bit), Windows (`--arch=i386` and `--arch=x86_64`), and OS X. See its-  [README](../etc/scripts/README.md#release.hs) for build and invocation instructions.-  This performs the following checks automatically:-    * `stack install && stack clean && stack install --pedantic && stack test --flag stack:integration-tests` on Linux, Windows, and OS X, which covers:-        * Self-hosting-        * Unit tests-        * Integration tests-        * stack can install GHC-    * Working tree is clean.-* Ensure that `stack --version` gives the correct version number and Git hash, and does not have a dirty tree+* Integration tests pass on a representative sample of platforms: `stack test+  --flag stack:integration-tests`. The actual release script will perform a more+  thorough test for every platform/variant prior to uploading, so this is just a+  pre-check * stack can build the wai repo * Running `stack build` a second time on either stack or wai is a no-op-* Build something that depends on `happy` (suggestion: `hlint`), since `happy` has special logic for moving around the `dist` directory-* Make sure to bump the version number in the .cabal file and the ChangeLog appropriately-* Review man page and other documentation for any changes that need to be made.+* Build something that depends on `happy` (suggestion: `hlint`), since `happy`+  has special logic for moving around the `dist` directory+* Make sure to bump the version number in the .cabal file and the ChangeLog+  appropriately (check for any entries that snuck into the previous version's+  changes)+* In release candidate, remove the Changelog's "unreleased changes" section+* Review documentation for any changes that need to be made+    * Search for old Stack version and replace with new version+* Ensure all `doc/*.md` files are listed in `stack.cabal`'s 'extra-source-files`+* Check that any new Linux distribution versions added to+  `etc/scripts/release.hs` and `etc/scripts/vagrant-releases.sh`+* Check that no new entries need to be added to+  [releases.yaml](https://github.com/fpco/stackage-content/blob/master/stack/releases.yaml),+  [install_and_upgrade.md](https://github.com/commercialhaskell/stack/blob/master/doc/install_and_upgrade.md),+  and+  [README.md](https://github.com/commercialhaskell/stack/blob/master/README.md) -Release checklist after testing:+## Release process -* Create a draft Github release with tag `vX.Y.Z` (where X.Y.Z is the stack package's version).-* Run `etc/scripts/release.hs release` on Linux (Debian 7 32-bit [Vagrantfile](https://github.com/commercialhaskell/stack/tree/master/etc/vagrant/debian-7-i386)/64-bit [Vagrantfile](https://github.com/commercialhaskell/stack/tree/master/etc/vagrant/debian-7-amd64)), Windows (`--arch=i386` and `--arch=x86_64`), and OS X.  This performs the following tasks automatically:-    * Binaries for Linux, Windows, and OS X uploaded to draft Github release.-* Run `etc/scripts/release.hs --binary-variant=gmp4 release` on CentOS 6 32-bit [Vagrantfile](https://github.com/commercialhaskell/stack/tree/master/etc/vagrant/centos-6-i386)/64-bit [Vagrantfile](centos-6-x86_64)).-* Run `etc/scripts/release.hs ubuntu-upload debian-upload` in Linux (Ubuntu or Debian - [Vagrantfile](https://github.com/commercialhaskell/stack/tree/master/etc/vagrant/debian-7-amd64))-* Run `etc/scripts/release.hs centos-upload fedora-upload` on Linux (CentOS or Fedora - [Vagrantfile](https://github.com/commercialhaskell/stack/tree/master/etc/vagrant/centos-7-x86_64))-* Upload Arch Linux packages (manual process)-* Build new MinGHC distribution (See https://github.com/fpco/minghc/commit/51490f398e6722672364548a3855a0bfcba48ffe)+See+[stack-release-script's README](https://github.com/commercialhaskell/stack/blob/master/etc/scripts/README.md#prerequisites)+for requirements to perform the release, and more details about the tool. -After binaries uploaded:+* Create a+  [new draft Github release](https://github.com/commercialhaskell/stack/releases/new)+  with tag `vX.Y.Z` (where X.Y.Z is the stack package's version) -* Push signed Git tag (matching Github release tag name).-* Publish Github release.-* Upload package to Hackage.-* Announce to haskell-cafe, commercialhaskell, and haskell-stack mailing lists.+* On each machine you'll be releasing from, set environment variables:+  `GITHUB_AUTHORIZATION_TOKEN`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` -For more information, see: https://github.com/commercialhaskell/stack/issues/324+* On a machine with Vagrant installed:+    * Run `etc/scripts/vagrant-releases.sh`++* On Mac OS X:+    * Run `etc/scripts/osx-release.sh`++* On Windows:+    * Ensure your working tree is in `C:\stack` (or a similarly short path)+    * Run `etc\scripts\windows-releases.bat`++* Push signed Git tag, matching Github release tag name, e.g.: `git tag -u+  9BEFB442 vX.Y.Z && git push origin vX.Y.Z`++* Reset the `release` branch to the released commit, e.g.: `git merge --ff-only+  vX.Y.Z && git push origin release`++* Publish Github release++* Edit+  [stack-setup-2.yaml](https://github.com/fpco/stackage-content/blob/master/stack/stack-setup-2.yaml),+  and add the new linux64 stack bindist++* Upload package to Hackage: `stack upload . --pvp-bounds=both`++* On a machine with Vagrant installed:+    * Run `etc/scripts/vagrant-distros.sh`++* Update in Arch Linux's+  [haskell-stack.git](ssh+git://aur@aur.archlinux.org/haskell-stack.git):+  `PKGBUILD` and `.SRCINFO`+    * Be sure to reset `pkgrel` in both files, and update the SHA1 sum++* Submit a PR for the+  [haskell-stack Homebrew formula](https://github.com/Homebrew/homebrew/blob/master/Library/Formula/haskell-stack.rb).+  The commit message should just be `haskell-stack <VERSION>`+      * Note: for v0.1.8.0, check if `pcre` should still be a dependency+          * Also, update the homepage++* Build Windows installers.  See https://github.com/borsboom/stack-installer#readme++* [Build new MinGHC distribution](#build_minghc)++* [Upload haddocks to Hackage](#upload_haddocks), if hackage couldn't build on its own++* Announce to haskell-cafe, commercialhaskell, and haskell-stack mailing lists++# Extra steps++## Upload haddocks to Hackage <a name="upload_haddocks"></a>++* Set `STACKVER` environment variable to the Stack version (e.g. `0.1.6.0`)+* Run:++```+STACKDOCDIR=stack-$STACKVER-docs+rm -rf _release/$STACKDOCDIR+mkdir -p _release+cp -r $(stack path --local-doc-root)/stack-$STACKVER _release/$STACKDOCDIR+sed -i '' 's/href="\.\.\/\([^/]*\)\//href="..\/..\/\1\/docs\//g' _release/$STACKDOCDIR/*.html+(cd _release && tar cvz --format=ustar -f $STACKDOCDIR.tar.gz $STACKDOCDIR)+curl -X PUT \+     -H 'Content-Type: application/x-tar' \+     -H 'Content-Encoding: gzip' \+     -u borsboom \+     --data-binary "@_release/$STACKDOCDIR.tar.gz" \+     "https://hackage.haskell.org/package/stack-$STACKVER/docs"+```++## Update MinGHC <a name="build_minghc"></a>++Full details of prerequisites and steps for building MinGHC are in its+[README](https://github.com/fpco/minghc#building-installers). What follows is an+abbreviated set specifically for including the latest stack version.++* Ensure `makensis.exe` and `signtool.exe` are on your PATH.+* If you edit build-post-install.hs, run `stack exec -- cmd /c build-post-install.bat`+* Set `STACKVER` environment variable to latest Stack verion (e.g. `0.1.6.0`)+* Adjust commands below for new GHC versions+* Run:++```+stack build+stack exec -- minghc-generate 7.10.2 --stack=%STACKVER%+signtool sign /v /n "FP Complete, Corporation" /t "http://timestamp.verisign.com/scripts/timestamp.dll" .build\minghc-7.10.2-i386.exe+stack exec -- minghc-generate 7.10.2 --arch64 --stack=%STACKVER%+signtool sign /v /n "FP Complete, Corporation" /t "http://timestamp.verisign.com/scripts/timestamp.dll" .build\minghc-7.10.2-x86_64.exe+stack exec -- minghc-generate 7.8.4 --stack=%STACKVER%+signtool sign /v /n "FP Complete, Corporation" /t "http://timestamp.verisign.com/scripts/timestamp.dll" .build\minghc-7.8.4-i386.exe+stack exec -- minghc-generate 7.8.4 --arch64 --stack=%STACKVER%+signtool sign /v /n "FP Complete, Corporation" /t "http://timestamp.verisign.com/scripts/timestamp.dll" .build\minghc-7.8.4-x86_64.exe+```++* Upload the built binaries to a new Github release+* Edit [README.md](https://github.com/fpco/minghc/blob/master/README.md#using-the-installer) and update download links
doc/dependency_visualization.md view
@@ -1,4 +1,4 @@-You can use stack to visualize the dependencies between your projects and optionally also external dependencies.+You can use stack to visualize the dependencies between your packages and optionally also external dependencies.  As an example, let's look at `wreq`: @@ -39,5 +39,3 @@ $ stack dot --external --prune base,lens,wreq-examples,http-client,aeson,tls,http-client-tls,exceptions | twopi -Groot=wreq -Goverlap=false -Tpng -o wreq_pruned.png ``` [![wreq_pruned](https://cloud.githubusercontent.com/assets/591567/8495538/9fae1184-216e-11e5-9931-99e6147f8aed.png)](https://cloud.githubusercontent.com/assets/591567/8495538/9fae1184-216e-11e5-9931-99e6147f8aed.png)--
doc/docker_integration.md view
@@ -203,6 +203,10 @@       # When the Docker Engine is remote (accessed by tcp), defaults to false.       set-user: true +      # Require the version of the Docker client to be within the specified+      # Cabal-style version range (e.g., ">= 1.6.0 && < 1.9.0")+      require-docker-version: "any"+ Image Repositories ------------------------------------------------------------------------------- @@ -230,6 +234,11 @@ These images can also be used directory with `docker run` and provide a complete Haskell build environment. +In addition, most Docker images that contain the basics for running GHC can be+used with Stack's Docker integration. For example, the+[official Haskell image repository](https://hub.docker.com/_/haskell/) works.+See [Custom images](#custom-images) for more details.+ Prerequisites ------------------------------------------------------------------------------- @@ -369,6 +378,26 @@ process. You could also use a Dockerfile to make this reusable. Consult the [Docker user guide](https://docs.docker.com/userguide/) for more on creating Docker images.++### <a name="custom-images"></a>Custom images++The easiest way to create your own custom image us by extending FP Complete's+images, but if you prefer to start from scratch, most images that include the+basics for building code with GHC will work. The image doesn't even, strictly+speaking, need to include GHC, but it does need to have libraries and tools that+GHC requires (e.g., libgmp, gcc, etc.).++There are also a few ways to set up images that tightens the integration:++* Create a user and group named `stack`, and create a `~/.stack` directory for+  it. Any build plans and caches from it will be copied from the image by Stack,+  meaning they don't need to be downloaded separately.+* Any packages in GHC's global package database will be available. This can be+  used to add private libraries to the image, or the make available a set of+  packages from an LTS release.+* The `DOCKER_SANDBOX_ID` environment variable (set via `ENV` in the Dockerfile)+  introduces extra isolation between images, to ensure that parts of the home+  directory and stack root are kept separate.  Troubleshooting -------------------------------------------------------------------------------
doc/faq.md view
@@ -275,3 +275,7 @@       /home/philip/.stack/programs/x86_64-linux/ghc-7.10.1/bin/runghc-7.10.1 -package=Cabal-1.22.2.0 -clear-package-db -global-package-db /home/philip/tmp/Setup.hs --builddir=dist-stack/x86_64-linux/Cabal-1.22.2.0/ build     Process exited with code: ExitFailure 1 ```++#### Where does the output from `--ghc-options=-ddump-splices` (and other `-ddump*` options) go?++These are written to `*.dump-*` files inside the package's `.stack-work` directory.
doc/install_and_upgrade.md view
@@ -1,24 +1,75 @@-Distribution packages are available for [Ubuntu](#ubuntu), [Debian](#debian), [CentOS / Red Hat](#centos--red-hat), [Fedora](#fedora) and [Arch Linux](#arch-linux). Binaries for other operating systems are available on [the releases page](https://github.com/fpco/stack/releases). For the future, we have plans to support more OSes.+Distribution packages are available for [Ubuntu](#ubuntu), [Debian](#debian),+[CentOS / Red Hat](#centos--red-hat), [Fedora](#fedora) and+[Arch Linux](#arch-linux). Binaries for other operating systems are listed+below, and available on+[the Github releases page](https://github.com/fpco/stack/releases). For the+future, we are open to supporting more OSes (to request one, please+[submit an issue](https://github.com/commercialhaskell/stack/issues/new)).  Binary packages are signed with this [signing key](SIGNING_KEY.md). +If you are writing a script that needs to download the latest binary, you can+find links that always point to the latest bindists+[here](https://www.stackage.org/stack).+ ## Windows -*Note*: Due to specific Windows limitations, [some temporary workarounds](https://www.fpcomplete.com/blog/2015/08/stack-ghc-windows) may be required. It is strongly advised to set your `STACK_ROOT` environment variable similarly to your root (e.g., `set STACK_ROOT=c:\stack_root`) *before* running `stack`.+*Note*: Due to specific Windows limitations,+ [some temporary workarounds](https://www.fpcomplete.com/blog/2015/08/stack-ghc-windows)+ may be required. It is strongly advised to set your `STACK_ROOT` environment+ variable similarly to your root (e.g., `set STACK_ROOT=c:\stack_root`) *before*+ running `stack`. -* Download [the latest release](https://github.com/commercialhaskell/stack/releases/latest). Note: while generally i386/32-bit GHC is better tested on Windows, there are reports that recent versions of Windows only work with the 64-bit version of stack (see [issue #393](https://github.com/commercialhaskell/stack/issues/393)).-* Unpack the archive and place `stack.exe` somewhere on your `%PATH%` (see [Path section below](#path)) and you can then run `stack` on the command line.+*Note:* while generally 32-bit GHC is better tested on Windows, there are+reports that recent versions of Windows only work with the 64-bit version of+Stack (see+[issue #393](https://github.com/commercialhaskell/stack/issues/393)).++### Installer++We recommend installing to the default location with these installers, as that+will make `stack install` and `stack upgrade` work correctly out of the box.++  * [Windows 32-bit Installer](https://www.stackage.org/stack/windows-i386-installer)+  * [Windows 64-bit Installer](https://www.stackage.org/stack/windows-x86_64-installer)++### Manual download++* Download the latest release:++      * [Windows 32-bit](https://www.stackage.org/stack/windows-i386)+      * [Windows 64-bit](https://www.stackage.org/stack/windows-x86_64)++* Unpack the archive and place `stack.exe` somewhere on your `%PATH%` (see+  [Path section below](#path)) and you can then run `stack` on the command line.+ * Now you can run `stack` from the terminal. -NOTE: These executables have been built and tested on a Windows 7, 8.1, and 10 64-bit machines. They should run on older Windows installs as well, but have not been tested. If you do test, please edit and update this page to indicate as such.+NOTE: These executables have been built and tested on a Windows 7, 8.1, and 10+64-bit machines. They should run on older Windows installs as well, but have not+been tested. If you do test, please edit and update this page to indicate as+such. -## OS X+## Mac OS X -* Download [the latest release](https://github.com/commercialhaskell/stack/releases/latest)-* Extract the archive and place `stack` somewhere on your `$PATH` (see [Path section below](#path))+### Using brew++If you have a popular [brew](http://brew.sh/) tool installed, you can just do:++```+brew install haskell-stack+```++### Manual download++* Download the latest release:+    * [Mac OS X 64-bit](https://www.stackage.org/stack/osx-x86_64)+* Extract the archive and place `stack` somewhere on your `$PATH` (see+  [Path section below](#path)) * Now you can run `stack` from the terminal. -We generally test on the current version of OS X, but stack is known to work on Mavericks as well, and may also work on older versions (YMMV).+We generally test on the current version of Mac OS X, but stack is known to work on+Yosemite and Mavericks as well, and may also work on older versions (YMMV).  ## Ubuntu @@ -28,7 +79,7 @@          wget -q -O- https://s3.amazonaws.com/download.fpcomplete.com/ubuntu/fpco.key | sudo apt-key add - -2. Add the appropriate source repository:+2. Add the appropriate source repository (if not sure, run ``lsb_release -a`` to find out your Ubuntu version):      * Ubuntu 15.10 (amd64): @@ -102,6 +153,10 @@  1. Add the appropriate source repository: +    * Fedora 23 (x86_64)++            curl -sSL https://s3.amazonaws.com/download.fpcomplete.com/fedora/23/fpco.repo | sudo tee /etc/yum.repos.d/fpco.repo+     * Fedora 22 (x86_64)              curl -sSL https://s3.amazonaws.com/download.fpcomplete.com/fedora/22/fpco.repo | sudo tee /etc/yum.repos.d/fpco.repo@@ -112,7 +167,7 @@  2. Install: -    * Fedora 22++    * Fedora 22 and above              sudo dnf -y install stack @@ -162,8 +217,18 @@  (64-bit and 32-bit options available) -* Download [the latest release](https://github.com/commercialhaskell/stack/releases/latest).  Note: the `-gmp4` variants are for older distributions (such as CentOS 6.x) that only include libgmp4 (libgmp.so.3).+* Download the latest release:++      * [Linux 64-bit, standard](https://www.stackage.org/stack/linux-x86_64)+      * [Linux 32-bit, standard](https://www.stackage.org/stack/linux-i386)++    If you are on an older distribution that only includes libgmp4 (libgmp.so.3), such as CentOS/RHEL/Amazon Linux 6.x, use one of these instead:++      * [Linux 64-bit, libgmp4](https://www.stackage.org/stack/linux-x86_64-gmp4)+      * [Linux 32-bit, libgmp4](https://www.stackage.org/stack/linux-i386-gmp4)+ * Extract the archive and place `stack` somewhere on your `$PATH` (see [Path section below](#path))+ * Now you can run `stack` from the terminal.  Tested on Fedora 20: make sure to install the following packages `sudo yum install perl make automake gcc gmp-devel`.
doc/nonstandard_project_init.md view
@@ -1,15 +1,15 @@ # Introduction-The purpose of this page is to collect information about issues that arise when users either have an existing cabal project or another nonstandard setup such as a private hackage database. +The purpose of this page is to collect information about issues that arise when users either have an existing cabal project or another nonstandard setup such as a private hackage database.  # Using a Cabal File-New users may be confused by the fact that you must add dependencies to the projects cabal file, even in the case when you have already listed the package in the `stack.yaml`. In most cases, dependencies for your project that are in the Stackage snapshot need *only* be added to the cabal file. stack makes heavy use of Cabal the library under the hood. In general, your stack packages should also end up being valid cabal-install packages.+New users may be confused by the fact that you must add dependencies to the package's cabal file, even in the case when you have already listed the package in the `stack.yaml`. In most cases, dependencies for your package that are in the Stackage snapshot need *only* be added to the cabal file. stack makes heavy use of Cabal the library under the hood. In general, your stack packages should also end up being valid cabal-install packages.  ## Issues Referenced   - https://github.com/commercialhaskell/stack/issues/105  # Passing Flags to Cabal -Any build command, `bench`, `install`, `haddock`, `test`, etc. takes a `--flag` option which passes flags to cabal. Another way to do this is using the flags field in a `stack.yaml`, with the option to specify flags on a per project basis. +Any build command, `bench`, `install`, `haddock`, `test`, etc. takes a `--flag` option which passes flags to cabal. Another way to do this is using the flags field in a `stack.yaml`, with the option to specify flags on a per package basis.  As an example, in a `stack.yaml` for multi-package project with packages `foo`, `bar`, `baz`: @@ -25,7 +25,7 @@  It is also possible to pass the same flag to multiple packages, i.e. `stack build --flag *:necessary` -Currently one needs to list all of your modules that interpret flags in the `other-modules` section of a cabal file. `cabal-install` has a different behavior currently and doesn't require that the modules be listed. This may change in a future release. +Currently one needs to list all of your modules that interpret flags in the `other-modules` section of a cabal file. `cabal-install` has a different behavior currently and doesn't require that the modules be listed. This may change in a future release.   ## Issues Referenced@@ -87,7 +87,7 @@   - https://github.com/commercialhaskell/stack/issues/137  # Intra-package Targets-stack supports intra-package targets, similar to `cabal build COMPONENTS` for situations when you don't want to build every target inside your package. +stack supports intra-package targets, similar to `cabal build COMPONENTS` for situations when you don't want to build every target inside your package.  Example: ```
doc/shell_autocompletion.md view
@@ -4,7 +4,7 @@ file was automatically installed (you may need the `bash-completion` package to have it take effect). -The following adds support for shell tab completion for standard Stack arguments, although completion for filenames and executables etc. within stack is still lacking (see [issue 82](https://github.com/commercialhaskell/stack/issues/832)).+The following adds support for shell tab completion for standard Stack arguments, although completion for filenames and executables etc. within stack is still lacking (see [issue 823](https://github.com/commercialhaskell/stack/issues/832)).  ### for bash users 
+ doc/stack_and_ghcjs.md view
@@ -0,0 +1,75 @@+# Stack & GHCJS++To set up and use GHCJS with stack, you should place GHCJS `resolver` in your project's `stack.yaml` (see instructions for [old base](#ghcjs-old-base) and [`master` - a.k.a. improved base](#ghcjs-master-aka-improved-base)).  Once this has been done, Stack will use GHCJS for most commands, with the exception of  `ghc`, `runghc` and `ide`.  (Support for `ghc` and `runghc` will likely be added before the next release - see [#1054](https://github.com/commercialhaskell/stack/issues/1054)).++After creating a `stack.yaml`, which specifies that ghcjs is to be used (see below), you can have Stack automatically set up and boot GHCJS. Stack must be newer than `0.1.6`:++```+$ stack setup+```++(this will take a long time)++## Using a stackage snapshot++The configurations below use a compiler resolver, which means that you can't use packages from a stackage snapshot.  The latest development version (which will probably be released as `0.1.7`) also supports using GHCJS with a stackage snapshot, via a `compiler` field.  To use the latest development version, do the following:++```+$ stack upgrade --git+```++For example, the old-base configuration looks like this:++```yaml+resolver: lts-3.10+compiler: ghcjs-0.1.0.20150924_ghc-7.10.2+compiler-check: match-exact+```++You can also build existing stack projects which target GHC and instead build them with GHCJS.  To do this, invoke stack like this: `stack build --compiler ghcjs-0.1.0.20150924_ghc-7.10.2`++## Example Configurations++### GHCJS (old base)++You can use this resolver for GHCJS (old base) in your `stack.yaml`:++```yaml+resolver: ghcjs-0.1.0.20150924_ghc-7.10.2+compiler-check: match-exact+```++### GHCJS `master` (a.k.a. improved base)++To use the master branch, a.k.a improved base, add the following to your `stack.yaml`:++```yaml+resolver: ghcjs-0.2.0.20151001_ghc-7.10.2+compiler-check: match-exact+setup-info:+  ghcjs:+    source:+      ghcjs-0.2.0.20151001_ghc-7.10.2:+        url: "https://github.com/fizruk/ghcjs/releases/download/v0.2.0.20151001/ghcjs-0.2.0.20151001.tar.gz"+```++or for the 2015-10-29 master branch +```yaml+resolver: ghcjs-0.2.0.20151029_ghc-7.10.2+compiler-check: match-exact+setup-info:+  ghcjs:+      source:+            ghcjs-0.2.0.20151029_ghc-7.10.2:+                    url: "https://github.com/nrolland/ghcjs/releases/download/v0.2.0.20151029/ghcjs-0.2.0.20151029.tar.gz"+```++### Custom installed GHCJS (development branch)++In order to use a GHCJS installed on your path, just add the following to your `stack.yaml`:++```yaml+resolver: ghcjs-0.2.0_ghc-7.10.2+```++(Or, `ghcjs-0.1.0_ghc-7.10.2` if you are working with an older version)
doc/yaml_configuration.md view
@@ -102,7 +102,7 @@ Packages that come from the snapshot global database or are not affected.  ### image-The image settings are used for the creation of container images using `stack container image`, e.g.+The image settings are used for the creation of container images using `stack image container`, e.g. ```yaml image:   container:@@ -118,7 +118,7 @@  ### docker -See [Docker configuration](Docker#configuration).+See [Docker configuration](https://github.com/commercialhaskell/stack/blob/release/doc/docker_integration.md).  ### connection-count @@ -222,6 +222,23 @@ * `match-exact`: the entire version number must match precisely * `newer-minor`: the third component can be increased, e.g. if your resolver is `ghc-7.10.1`, then 7.10.2 will also be allowed. This was the default up through stack 0.1.3 +### compiler++(Since 0.1.7)++Overrides the compiler version in the resolver. Note that the `compiler-check`+flag also applies to the version numbers. This uses the same syntax as compiler+resolvers like `ghc-7.10.2` or `ghcjs-0.1.0.20150924_ghc-7.10.2` (version used+for the 'old-base' version of GHCJS).  While it's useful to override the+compiler for a variety of reasons, the main usecase is to use GHCJS with a+stackage snapshot, like this:++```yaml+resolver: lts-3.10+compiler: ghcjs-0.1.0.20150924_ghc-7.10.2+compiler-check: match-exact+```+ ### ghc-options  (Since 0.1.4)@@ -238,7 +255,7 @@ Caveat emptor: setting options like this will affect your snapshot packages, which can lead to unpredictable behavior versus official Stackage snapshots. This is in contrast to the `ghc-options` command line flag, which will only-affect local targets.+affect local packages.  ### ghc-variant @@ -306,15 +323,14 @@  (Since 0.1.6) -Decide whether a custom `Setup.hs` script should be run with an explicit list-of dependencies based on the dependencies of the package itself, or simply-provided the global package database. This option is most often needed when-overriding packages in the global database, see [issue #1110](https://github.com/commercialhaskell/stack/issues/1110).--Setting the list explicitly can help when a Setup.hs depends on packages in the-local package database. For more information on that case, see [issue #897](https://github.com/commercialhaskell/stack/issues/897).+Decide whether a custom `Setup.hs` script should be run with an explicit list of+dependencies, based on the dependencies of the package itself. It associates the+name of a local package with a boolean. When it's `true`, the `Setup.hs` script+is built with an explicit list of packages. When it's `false` (default), the+`Setup.hs` script is built without access to the local DB, but can access any+package in the snapshot / global DB. -Note that in the future, this should all disappear once Cabal provides full+Note that in the future, this will be unnecessary, once Cabal provides full support for explicit Setup.hs dependencies.  ```yaml@@ -346,3 +362,46 @@ ```  Note that `everything` is a slightly dangerous value, as it can break invariants about your snapshot database.++### allow-newer++(Since 0.1.7)++Ignore version bounds in .cabal files. Default is false.++```yaml+allow-newer: true+```++Note that this also ignores lower bounds. The name "allow-newer" is chosen to+match the commonly used cabal option.++### templates++Templates used with `stack new` have a number of parameters that affect the generated code. These can be set for all new projects you create. The result of them can be observed in the generated LICENSE and cabal files.++The 5 parameters are: `author-email`, `author-name`, `category`, `copyright` and `github-username`.++* _author-email_ - sets the `maintainer` property in cabal+* _author-name_ - sets the `author` property in cabal and the name used in LICENSE+* _category_ - sets the `category` property in cabal. This is used in Hackage. For examples of categories see [Packages by category](https://hackage.haskell.org/packages/). It makes sense for `category` to be set on a per project basis because it is uncommon for all projects a user creates to belong to the same category. The category can be set per project by passing `-p "category:value"` to the `stack new` command.+* _copyright_ - sets the `copyright` property in cabal. It is typically the name of the holder of the copyright on the package and the year(s) from which copyright is claimed. For example: `Copyright: (c) 2006-2007 Joe Bloggs`+* _github-username_ - used to generate `homepage` and `source-repository` in cabal. For instance `github-username: myusername` and `stack new my-project new-template` would result:+```yaml+homepage: http://github.com/myusername/my-project#readme++source-repository head+  type: git+  location: https://github.com/myusername/my-project+```++These properties can be set in `config.yaml` as follows:+```yaml+templates:+  params:+    author-name: Your Name+    author-email: youremail@example.com+    category: Your Projects Category+    copyright: Copyright: (c) 2015 Your Name+    github-username: yourusername+```
src/Control/Concurrent/Execute.hs view
@@ -24,6 +24,7 @@  data ActionType     = ATBuild+    | ATBuildFinal     | ATFinal     deriving (Show, Eq, Ord) data ActionId = ActionId !PackageIdentifier !ActionType@@ -94,7 +95,7 @@         if null as             then return $ return ()             else inner as-    loop = join $ atomically $ breakOnErrs $ withActions $ \as -> do+    loop = join $ atomically $ breakOnErrs $ withActions $ \as ->         case break (Set.null . actionDeps) as of             (_, []) -> do                 inAction <- readTVar esInAction
src/Data/Aeson/Extended.hs view
@@ -67,7 +67,7 @@          do a <- fmap snd p             fmap (, a) (fmap fst p .!= d) --- | Tell warning parser about about an expected field, so it doesn't warn about it.+-- | Tell warning parser about an expected field, so it doesn't warn about it. tellJSONField :: Text -> WarningParser () tellJSONField key = tell (mempty { wpmExpectedFields = Set.singleton key}) 
src/Data/Attoparsec/Args.hs view
@@ -31,7 +31,7 @@  -- | Parse arguments using 'argsParser'. parseArgs :: EscapingMode -> Text -> Either String [String]-parseArgs mode t = P.parseOnly (argsParser mode) t+parseArgs mode = P.parseOnly (argsParser mode)  -- | A basic argument parser. It supports space-separated text, and -- string quotation with identity escaping: \x -> x.
src/Data/Binary/VersionTagged.hs view
@@ -25,53 +25,55 @@ import Data.Binary.Get (ByteOffset) import Data.Binary.Tagged (HasStructuralInfo, HasSemanticVersion) import qualified Data.Binary.Tagged as BinaryTagged+import Data.Monoid ((<>)) import Data.Typeable (Typeable) import Control.Exception.Enclosed (tryAnyDeep)-import System.FilePath (takeDirectory)-import System.Directory (createDirectoryIfMissing)+import Path+import Path.IO (createTree) import qualified Data.Text as T  type BinarySchema a = (Binary a, NFData a, HasStructuralInfo a, HasSemanticVersion a)  -- | Write to the given file, with a binary-tagged tag. taggedEncodeFile :: (BinarySchema a, MonadIO m)-                 => FilePath+                 => Path Abs File                  -> a                  -> m () taggedEncodeFile fp x = liftIO $ do-    createDirectoryIfMissing True $ takeDirectory fp-    BinaryTagged.taggedEncodeFile fp x+    createTree (parent fp)+    BinaryTagged.taggedEncodeFile (toFilePath fp) x  -- | Read from the given file. If the read fails, run the given action and -- write that back to the file. Always starts the file off with the version -- tag. taggedDecodeOrLoad :: (BinarySchema a, MonadIO m, MonadLogger m)-                   => FilePath+                   => Path Abs File                    -> m a                    -> m a taggedDecodeOrLoad fp mx = do-    $logDebug $ T.pack $ "Trying to decode " ++ fp+    let fpt = T.pack (toFilePath fp)+    $logDebug $ "Trying to decode " <> fpt     eres <- decodeFileOrFailDeep fp     case eres of         Left _ -> do-            $logDebug $ T.pack $ "Failure decoding " ++ fp+            $logDebug $ "Failure decoding " <> fpt             x <- mx             taggedEncodeFile fp x             return x         Right x -> do-            $logDebug $ T.pack $ "Success decoding " ++ fp+            $logDebug $ "Success decoding " <> fpt             return x  -- | Ensure that there are no lurking exceptions deep inside the parsed -- value... because that happens unfortunately. See -- https://github.com/commercialhaskell/stack/issues/554 decodeFileOrFailDeep :: (BinarySchema a, MonadIO m, MonadThrow n)-                     => FilePath+                     => Path loc File                      -> m (n a) decodeFileOrFailDeep fp = liftIO $ fmap (either throwM return) $ tryAnyDeep $ do-    eres <- BinaryTagged.taggedDecodeFileOrFail fp+    eres <- BinaryTagged.taggedDecodeFileOrFail (toFilePath fp)     case eres of-        Left (offset, str) -> throwM $ DecodeFileFailure fp offset str+        Left (offset, str) -> throwM $ DecodeFileFailure (toFilePath fp) offset str         Right x -> return x  data DecodeFileFailure = DecodeFileFailure FilePath ByteOffset String
src/Data/Maybe/Extra.hs view
@@ -2,9 +2,24 @@  module Data.Maybe.Extra where +import Control.Applicative import Control.Monad+import Data.Traversable hiding (mapM) import Data.Maybe+import Prelude -- Silence redundant import warnings +-- | Applicative 'mapMaybe'.+mapMaybeA :: Applicative f => (a -> f (Maybe b)) -> [a] -> f [b]+mapMaybeA f = fmap catMaybes . traverse f++-- | @'forMaybeA' '==' 'flip' 'mapMaybeA'@+forMaybeA :: Applicative f => [a] -> (a -> f (Maybe b)) -> f [b]+forMaybeA = flip mapMaybeA+ -- | Monadic 'mapMaybe'.-mapMaybeM :: Monad f => (a -> f (Maybe b)) -> [a] -> f [b]+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b] mapMaybeM f = liftM catMaybes . mapM f++-- | @'forMaybeM' '==' 'flip' 'mapMaybeM'@+forMaybeM :: Monad m => [a] -> (a -> m (Maybe b)) -> m [b]+forMaybeM = flip mapMaybeM
src/Network/HTTP/Download.hs view
@@ -24,6 +24,7 @@  import           Control.Exception           (Exception) import           Control.Exception.Enclosed  (handleIO)+import           Control.Monad               (void) import           Control.Monad.Catch         (MonadThrow, throwM) import           Control.Monad.IO.Class      (MonadIO, liftIO) import           Control.Monad.Reader        (MonadReader, ReaderT, ask,@@ -52,8 +53,8 @@                                               removeFile,                                               renameFile) import           System.FilePath             (takeDirectory, (<.>))-import           System.IO                   (IOMode (ReadMode))-import           System.IO                   (IOMode (WriteMode),+import           System.IO                   (IOMode (ReadMode),+                                              IOMode (WriteMode),                                               withBinaryFile)  -- | Download the given URL to the given location. If the file already exists,@@ -123,7 +124,7 @@              return True         | responseStatus res == status304 -> return False-        | otherwise -> throwM $ RedownloadFailed req2 dest $ fmap (const ()) res+        | otherwise -> throwM $ RedownloadFailed req2 dest $ void res  -- | Download a JSON value and parse it using a 'FromJSON' instance. downloadJSON :: (FromJSON a, MonadReader env m, HasHttpManager env, MonadIO m, MonadThrow m)
src/Network/HTTP/Download/Verified.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP                   #-} {-# LANGUAGE DeriveDataTypeable    #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings     #-}@@ -157,7 +158,7 @@             -- https://github.com/commercialhaskell/stack/issues/240             || b == actualDigestHexByteString -    when (not passedCheck) $+    unless passedCheck $         throwM $ WrongDigest req (show hashCheckAlgorithm) hashCheckHexDigest actualDigestString  assertLengthSink :: MonadThrow m@@ -200,8 +201,12 @@     env <- ask     liftIO $ whenM' getShouldDownload $ do         createDirectoryIfMissing True dir-        withBinaryFile fptmp WriteMode $ \h -> do+        withBinaryFile fptmp WriteMode $ \h ->+#if MIN_VERSION_retry(0,7,0)+            recovering drRetryPolicy handlers $ const $+#else             recovering drRetryPolicy handlers $+#endif                 flip runReaderT env $                     withResponse req (go h)         renameFile fptmp fp@@ -254,7 +259,7 @@         when (fileSize /= expectedFileSize) $             throwM $ WrongFileSize expectedFileSize fileSizeInteger -    checkContentLengthHeader headers expectedContentLength = do+    checkContentLengthHeader headers expectedContentLength =         case List.lookup hContentLength headers of             Just lengthBS -> do               let lengthStr = displayByteString lengthBS
src/Options/Applicative/Builder/Extra.hs view
@@ -20,30 +20,57 @@ import Data.Text (Text) import qualified Data.Text as T --- | Enable/disable flags for a @Bool@.-boolFlags :: Bool -> String -> String -> Mod FlagFields Bool -> Parser Bool+-- | Enable/disable flags for a 'Bool'.+boolFlags :: Bool                 -- ^ Default value+          -> String               -- ^ Flag name+          -> String               -- ^ Help suffix+          -> Mod FlagFields Bool+          -> Parser Bool boolFlags defaultValue = enableDisableFlags defaultValue True False --- | Enable/disable flags for a @Bool@, without a default case (to allow chaining @<|>@s).-boolFlagsNoDefault :: (Maybe Bool) -> String -> String -> Mod FlagFields Bool -> Parser Bool+-- | Enable/disable flags for a 'Bool', without a default case (to allow chaining with '<|>').+boolFlagsNoDefault :: Maybe Bool           -- ^ Hide the enabling or disabling flag from the+                                           -- brief description?+                   -> String               -- ^ Flag name+                   -> String               -- ^ Help suffix+                   -> Mod FlagFields Bool+                   -> Parser Bool boolFlagsNoDefault = enableDisableFlagsNoDefault True False --- | Enable/disable flags for a @(Maybe Bool)@.-maybeBoolFlags :: String -> String -> Mod FlagFields (Maybe Bool) -> Parser (Maybe Bool)+-- | Enable/disable flags for a @('Maybe' 'Bool')@.+maybeBoolFlags :: String                       -- ^ Flag name+               -> String                       -- ^ Help suffix+               -> Mod FlagFields (Maybe Bool)+               -> Parser (Maybe Bool) maybeBoolFlags = enableDisableFlags Nothing (Just True) (Just False)  -- | Enable/disable flags for any type.-enableDisableFlags :: (Eq a) => a -> a -> a -> String -> String -> Mod FlagFields a -> Parser a+enableDisableFlags :: (Eq a)+                   => a                 -- ^ Default value+                   -> a                 -- ^ Enabled value+                   -> a                 -- ^ Disabled value+                   -> String            -- ^ Name+                   -> String            -- ^ Help suffix+                   -> Mod FlagFields a+                   -> Parser a enableDisableFlags defaultValue enabledValue disabledValue name helpSuffix mods =   enableDisableFlagsNoDefault enabledValue disabledValue (Just defaultValue) name helpSuffix mods <|>   pure defaultValue --- | Enable/disable flags for any type, without a default (to allow chaining @<|>@s)-enableDisableFlagsNoDefault :: (Eq a) => a -> a -> (Maybe a) -> String -> String -> Mod FlagFields a -> Parser a+-- | Enable/disable flags for any type, without a default (to allow chaining with '<|>')+enableDisableFlagsNoDefault :: (Eq a)+                            => a                 -- ^ Enabled value+                            -> a                 -- ^ Disabled value+                            -> Maybe a           -- ^ Hide the enabling or disabling flag+                                                 -- from the brief description??+                            -> String            -- ^ Name+                            -> String            -- ^ Help suffix+                            -> Mod FlagFields a+                            -> Parser a enableDisableFlagsNoDefault enabledValue disabledValue maybeHideValue name helpSuffix mods =   last <$> some (enableDisableFlagsNoDefault' enabledValue disabledValue maybeHideValue name helpSuffix mods) -enableDisableFlagsNoDefault' :: (Eq a) => a -> a -> (Maybe a) -> String -> String -> Mod FlagFields a -> Parser a+enableDisableFlagsNoDefault' :: (Eq a) => a -> a -> Maybe a -> String -> String -> Mod FlagFields a -> Parser a enableDisableFlagsNoDefault' enabledValue disabledValue maybeHideValue name helpSuffix mods =     let hideEnabled = Just enabledValue == maybeHideValue         hideDisabled = Just disabledValue == maybeHideValue@@ -70,24 +97,36 @@             help                 (concat $ concat                      [ ["Disable ", helpSuffix]-                     , [" (--no-" ++ name ++ " to enable)" | hideEnabled]]) <>+                     , [" (--" ++ name ++ " to enable)" | hideEnabled]]) <>             mods) <|>        flag'            disabledValue            (hidden <> internal <> long ("disable-" ++ name) <> mods)  -- | Show an extra help option (e.g. @--docker-help@ shows help for all @--docker*@ args).--- To actually show have that help appear, use 'execExtraHelp' before executing the main parser.-extraHelpOption :: String -> String -> String -> Parser (a -> a)-extraHelpOption progName fakeName helpName =+--+-- To actually have that help appear, use 'execExtraHelp' before executing the main parser.+extraHelpOption :: Bool             -- ^ Hide from the brief description?+                -> String           -- ^ Program name, e.g. @"stack"@+                -> String           -- ^ Option glob expression, e.g. @"docker*"@+                -> String           -- ^ Help option name, e.g. @"docker-help"@+                -> Parser (a -> a)+extraHelpOption hide progName fakeName helpName =     infoOption (optDesc' ++ ".") (long helpName <> hidden <> internal) <*>-    infoOption (optDesc' ++ ".") (long fakeName <> help optDesc')+    infoOption (optDesc' ++ ".") (long fakeName <>+                                  help optDesc' <>+                                  (if hide then hidden <> internal else idm))   where optDesc' = concat ["Run '", takeBaseName progName, " --", helpName, "' for details"] --- | Display extra help if extea help option passed in arguments.--- Since optparse-applicative doesn't allow an arbirary IO action for an 'abortOption', this+-- | Display extra help if extra help option passed in arguments.+--+-- Since optparse-applicative doesn't allow an arbitrary IO action for an 'abortOption', this -- was the best way I found that doesn't require manually formatting the help.-execExtraHelp :: [String] -> String -> Parser a -> String -> IO ()+execExtraHelp :: [String]  -- ^ Command line arguments+              -> String    -- ^ Extra help option name, e.g. @"docker-help"@+              -> Parser a  -- ^ Option parser for the relevant command+              -> String    -- ^ Option description+              -> IO () execExtraHelp args helpOpt parser pd =     when (args == ["--" ++ helpOpt]) $       withArgs ["--help"] $ do@@ -99,8 +138,10 @@         return ()   where hiddenHelper = abortOption ShowHelpText (long "help" <> hidden <> internal) +-- | 'option', specialized to 'Text'. textOption :: Mod OptionFields Text -> Parser Text textOption = option (T.pack <$> readerAsk) +-- | 'argument', specialized to 'Text'. textArgument :: Mod ArgumentFields Text -> Parser Text textArgument = argument (T.pack <$> readerAsk)
+ src/Options/Applicative/Complicated.hs view
@@ -0,0 +1,141 @@+-- | Simple interface to complicated program arguments.+--+-- This is a "fork" of the @optparse-simple@ package that has some workarounds for+-- optparse-applicative issues that become problematic with programs that have many options and+-- subcommands. Because it makes the interface more complex, these workarounds are not suitable for+-- pushing upstream to optparse-applicative.++module Options.Applicative.Complicated+  ( addCommand+  , addSubCommands+  , complicatedOptions+  , complicatedParser+  ) where++import           Control.Monad.Trans.Class (lift)+import           Control.Monad.Trans.Either+import           Control.Monad.Trans.Writer+import           Data.Monoid+import           Data.Version+import           Options.Applicative+import           Options.Applicative.Types+import           Options.Applicative.Builder.Internal+import           System.Environment++-- | Generate and execute a complicated options parser.+complicatedOptions+  :: Monoid a+  => Version+  -- ^ numeric version+  -> Maybe String+  -- ^ version string+  -> String+  -- ^ header+  -> String+  -- ^ program description+  -> Parser a+  -- ^ common settings+  -> EitherT b (Writer (Mod CommandFields (b,a))) ()+  -- ^ commands (use 'addCommand')+  -> IO (a,b)+complicatedOptions numericVersion versionString h pd commonParser commandParser =+  do args <- getArgs+     (a,(b,c)) <- case execParserPure (prefs noBacktrack) parser args of+       Failure _ | null args -> withArgs ["--help"] (execParser parser)+       parseResult -> handleParseResult parseResult+     return (mappend c a,b)+  where parser = info (helpOption <*> versionOptions <*> complicatedParser commonParser commandParser) desc+        desc = fullDesc <> header h <> progDesc pd+        versionOptions =+          case versionString of+            Nothing -> versionOption (showVersion numericVersion)+            Just s -> versionOption s <*> numericVersionOption+        versionOption s =+          infoOption+            s+            (long "version" <>+             help "Show version")+        numericVersionOption =+          infoOption+            (showVersion numericVersion)+            (long "numeric-version" <>+             help "Show only version number")++-- | Add a command to the options dispatcher.+addCommand :: String   -- ^ command string+           -> String   -- ^ title of command+           -> String   -- ^ footer of command help+           -> (a -> b) -- ^ constructor to wrap up command in common data type+           -> Parser c -- ^ common parser+           -> Parser a -- ^ command parser+           -> EitherT b (Writer (Mod CommandFields (b,c))) ()+addCommand cmd title footerStr constr =+  addCommand' cmd title footerStr (\a c -> (constr a,c))++-- | Add a command that takes sub-commands to the options dispatcher.+addSubCommands+  :: Monoid c+  => String+  -- ^ command string+  -> String+  -- ^ title of command+  -> String+  -- ^ footer of command help+  -> Parser c+  -- ^ common parser+  -> EitherT b (Writer (Mod CommandFields (b,c))) ()+  -- ^ sub-commands (use 'addCommand')+  -> EitherT b (Writer (Mod CommandFields (b,c))) ()+addSubCommands cmd title footerStr commonParser commandParser =+  addCommand' cmd+              title+              footerStr+              (\(c1,(a,c2)) c3 -> (a,mconcat [c3, c2, c1]))+              commonParser+              (complicatedParser commonParser commandParser)++-- | Add a command to the options dispatcher.+addCommand' :: String   -- ^ command string+            -> String   -- ^ title of command+            -> String   -- ^ footer of command help+            -> (a -> c -> (b,c)) -- ^ constructor to wrap up command in common data type+            -> Parser c -- ^ common parser+            -> Parser a -- ^ command parser+            -> EitherT b (Writer (Mod CommandFields (b,c))) ()+addCommand' cmd title footerStr constr commonParser inner =+  lift (tell (command cmd+                      (info (constr <$> inner <*> commonParser)+                            (progDesc title <> footer footerStr))))++-- | Generate a complicated options parser.+complicatedParser+  :: Monoid a+  => Parser a+  -- ^ common settings+  -> EitherT b (Writer (Mod CommandFields (b,a))) ()+  -- ^ commands (use 'addCommand')+  -> Parser (a,(b,a))+complicatedParser commonParser commandParser =+   (,) <$>+   commonParser <*>+   case runWriter (runEitherT commandParser) of+     (Right (),d) -> hsubparser' d+     (Left b,_) -> pure (b,mempty)++-- way to do in 'addCommand' | Subparser with @--help@ argument. Borrowed with slight modification+-- from Options.Applicative.Extra.+hsubparser' :: Mod CommandFields a -> Parser a+hsubparser' m = mkParser d g rdr+  where+    Mod _ d g = m `mappend` metavar "COMMAND"+    (cmds, subs) = mkCommand m+    rdr = CmdReader cmds (fmap add_helper . subs)+    add_helper pinfo = pinfo+      { infoParser = infoParser pinfo <**> helpOption }++-- | Non-hidden help option.+helpOption :: Parser (a -> a)+helpOption =+    abortOption ShowHelpText $+    long "help" <>+    help "Show this help text"
+ src/Path/Extra.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE ViewPatterns #-}++-- | Extra Path utilities.++module Path.Extra+  (toFilePathNoTrailingSep+  ,dropRoot+  ,parseCollapsedAbsDir+  ,parseCollapsedAbsFile+  ) where++import           Control.Monad.Catch+import           Path+import           Path.Internal (Path(..))+import qualified System.FilePath as FP++-- | Convert to FilePath but don't add a trailing slash.+toFilePathNoTrailingSep :: Path loc Dir -> FilePath+toFilePathNoTrailingSep = FP.dropTrailingPathSeparator . toFilePath++-- | Collapse intermediate "." and ".." directories from path, then parse+-- it with 'parseAbsDir'.+-- (probably should be moved to the Path module)+parseCollapsedAbsDir :: MonadThrow m => FilePath -> m (Path Abs Dir)+parseCollapsedAbsDir = parseAbsDir . collapseFilePath++-- | Collapse intermediate "." and ".." directories from path, then parse+-- it with 'parseAbsFile'.+-- (probably should be moved to the Path module)+parseCollapsedAbsFile :: MonadThrow m => FilePath -> m (Path Abs File)+parseCollapsedAbsFile = parseAbsFile . collapseFilePath++-- | Collapse intermediate "." and ".." directories from a path.+--+-- > collapseFilePath "./foo" == "foo"+-- > collapseFilePath "/bar/../baz" == "/baz"+-- > collapseFilePath "/../baz" == "/../baz"+-- > collapseFilePath "parent/foo/baz/../bar" ==  "parent/foo/bar"+-- > collapseFilePath "parent/foo/baz/../../bar" ==  "parent/bar"+-- > collapseFilePath "parent/foo/.." ==  "parent"+-- > collapseFilePath "/parent/foo/../../bar" ==  "/bar"+--+-- (borrowed from @Text.Pandoc.Shared@)+collapseFilePath :: FilePath -> FilePath+collapseFilePath = FP.joinPath . reverse . foldl go [] . FP.splitDirectories+  where+    go rs "." = rs+    go r@(p:rs) ".." = case p of+                            ".." -> "..":r+                            (checkPathSeperator -> Just True) -> "..":r+                            _ -> rs+    go _ (checkPathSeperator -> Just True) = [[FP.pathSeparator]]+    go rs x = x:rs+    isSingleton [] = Nothing+    isSingleton [x] = Just x+    isSingleton _ = Nothing+    checkPathSeperator = fmap FP.isPathSeparator . isSingleton++-- | Drop the root (either @\/@ on POSIX or @C:\\@, @D:\\@, etc. on+-- Windows).+dropRoot :: Path Abs t -> Path Rel t+dropRoot (Path l) = Path (FP.dropDrive l)
src/Path/Find.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}  -- | Finding files. @@ -23,7 +22,7 @@            -> (Path Abs File -> Bool)     -- ^ Predicate to match the file.            -> Maybe (Path Abs Dir)        -- ^ Do not ascend above this directory.            -> m (Maybe (Path Abs File))  -- ^ Absolute file path.-findFileUp s p d = findPathUp snd s p d+findFileUp = findPathUp snd  -- | Find the location of a directory matching the given predicate. findDirUp :: (MonadIO m,MonadThrow m)@@ -31,7 +30,7 @@           -> (Path Abs Dir -> Bool)      -- ^ Predicate to match the directory.           -> Maybe (Path Abs Dir)        -- ^ Do not ascend above this directory.           -> m (Maybe (Path Abs Dir))   -- ^ Absolute directory path.-findDirUp s p d = findPathUp fst s p d+findDirUp = findPathUp fst  -- | Find the location of a path matching the given predicate. findPathUp :: (MonadIO m,MonadThrow m)@@ -45,15 +44,9 @@   do entries <- listDirectory dir      case find p (pathType entries) of        Just path -> return (Just path)-       Nothing ->-         if Just dir == upperBound-            then return Nothing-            else if parent dir == dir-                    then return Nothing-                    else findPathUp pathType-                                    (parent dir)-                                    p-                                    upperBound+       Nothing | Just dir == upperBound -> return Nothing+               | parent dir == dir -> return Nothing+               | otherwise -> findPathUp pathType (parent dir) p upperBound  -- | Find files matching predicate below a root directory. findFiles :: Path Abs Dir            -- ^ Root directory to begin with.
src/Path/IO.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE ViewPatterns #-}  -- | IO actions that might be put in a package at some point. @@ -31,9 +30,6 @@   ,copyFileIfExists   ,copyDirectoryRecursive   ,createTree-  ,dropRoot-  ,parseCollapsedAbsFile-  ,parseCollapsedAbsDir   ,withCanonicalizedSystemTempDirectory   ,withCanonicalizedTempDirectory)   where@@ -43,10 +39,9 @@ import           Control.Monad.Catch import           Control.Monad.IO.Class import           Data.Either-import           Data.Maybe+import           Data.Maybe.Extra import           Data.Typeable import           Path-import           Path.Internal (Path(..)) import qualified System.Directory as D import qualified System.FilePath as FP import           System.IO.Error@@ -68,11 +63,17 @@  -- | Parse a directory path. If it's relative, then the absolute version -- is yielded, based off the working directory.+--+-- NOTE that this only works if the directory exists, but does not+-- ensure that it's a directory. parseRelAsAbsDir :: (MonadThrow m, MonadIO m) => FilePath -> m (Path Abs Dir) parseRelAsAbsDir fp = parseAbsDir =<< liftIO (D.canonicalizePath fp)  -- | Parse a file path. If it's relative, then the absolute version is -- yielded, based off the working directory.+--+-- NOTE that this only works if the file exists, but does not ensure+-- that it's a file. parseRelAsAbsFile :: (MonadThrow m, MonadIO m) => FilePath -> m (Path Abs File) parseRelAsAbsFile fp = parseAbsFile =<< liftIO (D.canonicalizePath fp) @@ -129,50 +130,12 @@                  => Path Abs Dir -> FilePath -> m (Maybe (Path Abs File)) resolveFileMaybe = resolveCheckParse D.doesFileExist parseAbsFile --- | Collapse intermediate "." and ".." directories from path, then parse--- it with 'parseAbsFile'.--- (probably should be moved to the Path module)-parseCollapsedAbsFile :: MonadThrow m => FilePath -> m (Path Abs File)-parseCollapsedAbsFile = parseAbsFile . collapseFilePath---- | Collapse intermediate "." and ".." directories from path, then parse--- it with 'parseAbsDir'.--- (probably should be moved to the Path module)-parseCollapsedAbsDir :: MonadThrow m => FilePath -> m (Path Abs Dir)-parseCollapsedAbsDir = parseAbsDir . collapseFilePath---- | Collapse intermediate "." and ".." directories from a path.------ > collapseFilePath "./foo" == "foo"--- > collapseFilePath "/bar/../baz" == "/baz"--- > collapseFilePath "/../baz" == "/../baz"--- > collapseFilePath "parent/foo/baz/../bar" ==  "parent/foo/bar"--- > collapseFilePath "parent/foo/baz/../../bar" ==  "parent/bar"--- > collapseFilePath "parent/foo/.." ==  "parent"--- > collapseFilePath "/parent/foo/../../bar" ==  "/bar"------ (borrowed from @Text.Pandoc.Shared@)-collapseFilePath :: FilePath -> FilePath-collapseFilePath = FP.joinPath . reverse . foldl go [] . FP.splitDirectories-  where-    go rs "." = rs-    go r@(p:rs) ".." = case p of-                            ".." -> ("..":r)-                            (checkPathSeperator -> Just True) -> ("..":r)-                            _ -> rs-    go _ (checkPathSeperator -> Just True) = [[FP.pathSeparator]]-    go rs x = x:rs-    isSingleton [] = Nothing-    isSingleton [x] = Just x-    isSingleton _ = Nothing-    checkPathSeperator = fmap FP.isPathSeparator . isSingleton- -- | List objects in a directory, excluding "@.@" and "@..@".  Entries are not sorted. listDirectory :: (MonadIO m,MonadThrow m) => Path Abs Dir -> m ([Path Abs Dir],[Path Abs File]) listDirectory dir =   do entriesFP <- liftIO (D.getDirectoryContents dirFP)-     maybeEntries <--       forM (map (dirFP ++) entriesFP)+     entries <-+       forMaybeM (map (dirFP ++) entriesFP)             (\entryFP ->                do isDir <- liftIO (D.doesDirectoryExist entryFP)                   if isDir@@ -185,7 +148,6 @@                      else case parseAbsFile entryFP of                             Nothing -> return Nothing                             Just entryFile -> return (Just (Right entryFile)))-     let entries = catMaybes maybeEntries      return (lefts entries,rights entries)   where dirFP = toFilePath dir @@ -281,12 +243,6 @@                 case stripDir srcDir srcSubDir of                   Nothing -> return ()                   Just relSubDir -> copyDirectoryRecursive srcSubDir (destDir </> relSubDir))----- | Drop the root (either @\/@ on POSIX or @C:\\@, @D:\\@, etc. on--- Windows).-dropRoot :: Path Abs t -> Path Rel t-dropRoot (Path l) = Path (FP.dropDrive l)  -- Utility function for a common pattern of ignoring does-not-exist errors. ignoreDoesNotExist :: MonadIO m => IO () -> m ()
src/Stack/Build.hs view
@@ -10,11 +10,10 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} --- | Build project(s).+-- | Build the project.  module Stack.Build   (build-  ,clean   ,withLoadPackage   ,mkBaseConfigOpts   ,queryBuildInfo)@@ -29,20 +28,20 @@ import           Data.Aeson (Value (Object, Array), (.=), object) import           Data.Function import qualified Data.HashMap.Strict as HM+import           Data.IORef.RunOnce (runOnce) import qualified Data.Map as Map import           Data.Map.Strict (Map) import           Data.Set (Set) import qualified Data.Set as Set import           Data.Text (Text) import qualified Data.Text as T-import qualified Data.Text.IO as TIO import           Data.Text.Encoding (decodeUtf8)+import qualified Data.Text.IO as TIO import           Data.Text.Read (decimal) import qualified Data.Vector as V import qualified Data.Yaml as Yaml import           Network.HTTP.Client.Conduit (HasHttpManager) import           Path-import           Path.IO import           Prelude hiding (FilePath, writeFile) import           Stack.Build.ConstructPlan import           Stack.Build.Execute@@ -50,7 +49,6 @@ import           Stack.Build.Installed import           Stack.Build.Source import           Stack.Build.Target-import           Stack.Constants import           Stack.Fetch as Fetch import           Stack.GhcPkg import           Stack.Package@@ -87,7 +85,7 @@            $ Set.unions            $ map lpFiles locals -    (installedMap, globallyRegistered, locallyRegistered) <-+    (installedMap, globalDumpPkgs, snapshotDumpPkgs, localDumpPkgs) <-         getInstalled menv                      GetInstalledOpts                          { getInstalledProfiling = profiling@@ -96,7 +94,7 @@      baseConfigOpts <- mkBaseConfigOpts bopts     plan <- withLoadPackage menv $ \loadPackage ->-        constructPlan mbp baseConfigOpts locals extraToBuild locallyRegistered loadPackage sourceMap installedMap+        constructPlan mbp baseConfigOpts locals extraToBuild localDumpPkgs loadPackage sourceMap installedMap      -- If our work to do is all local, let someone else have a turn with the snapshot.     -- They won't damage what's already in there.@@ -114,8 +112,9 @@     if boptsDryrun bopts         then printPlan plan         else executePlan menv bopts baseConfigOpts locals-                         globallyRegistered-                         sourceMap+                         globalDumpPkgs+                         snapshotDumpPkgs+                         localDumpPkgs                          installedMap                          plan   where@@ -160,7 +159,7 @@                 -> m a withLoadPackage menv inner = do     econfig <- asks getEnvConfig-    withCabalLoader menv $ \cabalLoader ->+    withCabalLoader' <- runOnce $ withCabalLoader menv $ \cabalLoader ->         inner $ \name version flags -> do             bs <- cabalLoader $ PackageIdentifier name version -- TODO automatically update index the first time this fails @@ -169,6 +168,7 @@             -- resolving the package index.             (_warnings,pkg) <- readPackageBS (depPackageConfig econfig flags) bs             return pkg+    withCabalLoader'   where     -- | Package config to be used for dependencies     depPackageConfig :: EnvConfig -> Map FlagName Bool -> PackageConfig@@ -180,14 +180,6 @@         , packageConfigPlatform = configPlatform (getConfig econfig)         } --- | Reset the build (remove Shake database and .gen files).-clean :: (M env m) => m ()-clean = do-    econfig <- asks getEnvConfig-    forM_-        (Map.keys (envConfigPackages econfig))-        (distDirFromDir >=> removeTreeIfExists)- -- | Set the code page for this process as necessary. Only applies to Windows. -- See: https://github.com/commercialhaskell/stack/issues/738 fixCodePage :: (MonadIO m, MonadMask m, MonadLogger m) => m a -> m a@@ -242,7 +234,7 @@ queryBuildInfo :: M env m                => [Text] -- ^ selectors                -> m ()-queryBuildInfo selectors0 = do+queryBuildInfo selectors0 =         rawBuildInfo     >>= select id selectors0     >>= liftIO . TIO.putStrLn . decodeUtf8 . Yaml.encode
src/Stack/Build/Cache.hs view
@@ -21,12 +21,6 @@     , setTestSuccess     , unsetTestSuccess     , checkTestSuccess-    , setTestBuilt-    , unsetTestBuilt-    , checkTestBuilt-    , setBenchBuilt-    , unsetBenchBuilt-    , checkBenchBuilt     , writePrecompiledCache     , readPrecompiledCache     ) where@@ -121,7 +115,9 @@             => (Path Abs Dir -> m (Path Abs File))             -> Path Abs Dir             -> m (Maybe a)-tryGetCache get' dir = get' dir >>= decodeFileOrFailDeep . toFilePath+tryGetCache get' dir = do+    fp <- get' dir+    decodeFileOrFailDeep fp  -- | Write the dirtiness cache for this package's files. writeBuildCache :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m, MonadLogger m, HasEnvConfig env)@@ -130,9 +126,9 @@     writeCache         dir         buildCacheFile-        (BuildCache+        BuildCache          { buildCacheTimes = times-         })+         }  -- | Write the dirtiness cache for this package's configuration. writeConfigCache :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m, MonadLogger m, HasEnvConfig env)@@ -167,7 +163,7 @@            -> m () writeCache dir get' content = do     fp <- get' dir-    taggedEncodeFile (toFilePath fp) content+    taggedEncodeFile fp content  flagCacheFile :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env)               => Installed@@ -184,8 +180,9 @@ tryGetFlagCache :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env)                 => Installed                 -> m (Maybe ConfigCache)-tryGetFlagCache gid =-    flagCacheFile gid >>= decodeFileOrFailDeep . toFilePath+tryGetFlagCache gid = do+    fp <- flagCacheFile gid+    decodeFileOrFailDeep fp  writeFlagCache :: (MonadIO m, MonadReader env m, HasEnvConfig env, MonadThrow m)                => Installed@@ -195,7 +192,7 @@     file <- flagCacheFile gid     liftIO $ do         createTree (parent file)-        taggedEncodeFile (toFilePath file) cache+        taggedEncodeFile file cache  -- | Mark a test suite as having succeeded setTestSuccess :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)@@ -226,64 +223,6 @@         (fromMaybe False)         (tryGetCache testSuccessFile dir) --- | Mark a test suite as having built-setTestBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-               => Path Abs Dir-               -> m ()-setTestBuilt dir =-    writeCache-        dir-        testBuiltFile-        True---- | Mark a test suite as not having built-unsetTestBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-                 => Path Abs Dir-                 -> m ()-unsetTestBuilt dir =-    writeCache-        dir-        testBuiltFile-        False---- | Check if the test suite already built-checkTestBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-                 => Path Abs Dir-                 -> m Bool-checkTestBuilt dir =-    liftM-        (fromMaybe False)-        (tryGetCache testBuiltFile dir)---- | Mark a bench suite as having built-setBenchBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-               => Path Abs Dir-               -> m ()-setBenchBuilt dir =-    writeCache-        dir-        benchBuiltFile-        True---- | Mark a bench suite as not having built-unsetBenchBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-                 => Path Abs Dir-                 -> m ()-unsetBenchBuilt dir =-    writeCache-        dir-        benchBuiltFile-        False---- | Check if the bench suite already built-checkBenchBuilt :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)-                 => Path Abs Dir-                 -> m Bool-checkBenchBuilt dir =-    liftM-        (fromMaybe False)-        (tryGetCache benchBuiltFile dir)- -------------------------------------- -- Precompiled Cache --@@ -342,7 +281,7 @@                       -> PackageIdentifier                       -> ConfigureOpts                       -> Set GhcPkgId -- ^ dependencies-                      -> Maybe GhcPkgId -- ^ library+                      -> Installed -- ^ library                       -> Set Text -- ^ executables                       -> m () writePrecompiledCache baseConfigOpts pkgident copts depIDs mghcPkgId exes = do@@ -350,14 +289,14 @@     createTree $ parent file     mlibpath <-         case mghcPkgId of-            Nothing -> return Nothing-            Just ipid -> liftM Just $ do+            Executable _ -> return Nothing+            Library _ ipid -> liftM Just $ do                 ipid' <- parseRelFile $ ghcPkgIdString ipid ++ ".conf"                 return $ toFilePath $ bcoSnapDB baseConfigOpts </> ipid'     exes' <- forM (Set.toList exes) $ \exe -> do         name <- parseRelFile $ T.unpack exe         return $ toFilePath $ bcoSnapInstallRoot baseConfigOpts </> bindirSuffix </> name-    liftIO $ taggedEncodeFile (toFilePath file) PrecompiledCache+    liftIO $ taggedEncodeFile file PrecompiledCache         { pcLibrary = mlibpath         , pcExes = exes'         }@@ -371,4 +310,4 @@                      -> m (Maybe PrecompiledCache) readPrecompiledCache pkgident copts depIDs = do     file <- precompiledCacheFile pkgident copts depIDs-    decodeFileOrFailDeep $ toFilePath file+    decodeFileOrFailDeep file
src/Stack/Build/ConstructPlan.hs view
@@ -3,17 +3,20 @@ {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE RecordWildCards       #-} {-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TemplateHaskell       #-} {-# LANGUAGE TupleSections         #-}+{-# LANGUAGE ViewPatterns          #-} -- | Construct a @Plan@ for how to build module Stack.Build.ConstructPlan     ( constructPlan     ) where +import           Control.Arrow ((&&&)) import           Control.Exception.Lifted import           Control.Monad import           Control.Monad.Catch (MonadCatch) import           Control.Monad.IO.Class-import           Control.Monad.Logger (MonadLogger)+import           Control.Monad.Logger (MonadLogger, logWarn) import           Control.Monad.RWS.Strict import           Control.Monad.Trans.Resource import qualified Data.ByteString.Char8 as S8@@ -31,7 +34,7 @@ import           Data.Text.Encoding (decodeUtf8With) import           Data.Text.Encoding.Error (lenientDecode) import           Distribution.Package (Dependency (..))-import           Distribution.Version         (anyVersion)+import           Distribution.Version (anyVersion) import           Network.HTTP.Client.Conduit (HasHttpManager) import           Prelude hiding (pi, writeFile) import           Stack.Build.Cache@@ -40,25 +43,24 @@ import           Stack.Build.Source import           Stack.Types.Build import           Stack.BuildPlan- import           Stack.Package-import           Stack.PackageIndex+import           Stack.PackageDump import           Stack.Types  data PackageInfo-    = PIOnlyInstalled Version InstallLocation Installed+    = PIOnlyInstalled InstallLocation Installed     | PIOnlySource PackageSource     | PIBoth PackageSource Installed  combineSourceInstalled :: PackageSource-                       -> (Version, InstallLocation, Installed)+                       -> (InstallLocation, Installed)                        -> PackageInfo-combineSourceInstalled ps (version, location, installed) =-    assert (piiVersion ps == version) $+combineSourceInstalled ps (location, installed) =+    assert (piiVersion ps == installedVersion installed) $     assert (piiLocation ps == location) $     case location of         -- Always trust something in the snapshot-        Snap -> PIOnlyInstalled version location installed+        Snap -> PIOnlyInstalled location installed         Local -> PIBoth ps installed  type CombinedMap = Map PackageName PackageInfo@@ -67,25 +69,27 @@ combineMap = Map.mergeWithKey     (\_ s i -> Just $ combineSourceInstalled s i)     (fmap PIOnlySource)-    (fmap (\(v, l, i) -> PIOnlyInstalled v l i))+    (fmap (uncurry PIOnlyInstalled))  data AddDepRes     = ADRToInstall Task-    | ADRFound InstallLocation Version Installed+    | ADRFound InstallLocation Installed     deriving Show  data W = W-    { wFinals :: !(Map PackageName (Either ConstructPlanException (Task, LocalPackageTB)))+    { wFinals :: !(Map PackageName (Either ConstructPlanException Task))     , wInstall :: !(Map Text InstallLocation)     -- ^ executable to be installed, and location where the binary is placed     , wDirty :: !(Map PackageName Text)     -- ^ why a local package is considered dirty     , wDeps :: !(Set PackageName)     -- ^ Packages which count as dependencies+    , wWarnings :: !([Text] -> [Text])+    -- ^ Warnings     } instance Monoid W where-    mempty = W mempty mempty mempty mempty-    mappend (W a b c d) (W w x y z) = W (mappend a w) (mappend b x) (mappend c y) (mappend d z)+    mempty = W mempty mempty mempty mempty mempty+    mappend (W a b c d e) (W w x y z z') = W (mappend a w) (mappend b x) (mappend c y) (mappend d z) (mappend e z')  type M = RWST     Ctx@@ -104,6 +108,7 @@     , extraToBuild   :: !(Set PackageName)     , latestVersions :: !(Map PackageName Version)     , wanted         :: !(Set PackageName)+    , localNames     :: !(Set PackageName)     }  instance HasStackRoot Ctx@@ -121,29 +126,27 @@               -> BaseConfigOpts               -> [LocalPackage]               -> Set PackageName -- ^ additional packages that must be built-              -> Map GhcPkgId PackageIdentifier -- ^ locally registered+              -> [DumpPackage () ()] -- ^ locally registered               -> (PackageName -> Version -> Map FlagName Bool -> IO Package) -- ^ load upstream package               -> SourceMap               -> InstalledMap               -> m Plan-constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 locallyRegistered loadPackage0 sourceMap installedMap = do-    menv <- getMinimalEnvOverride-    caches <- getPackageCaches menv-    let latest = Map.fromListWith max $ map toTuple $ Map.keys caches+constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap = do+    let locallyRegistered = Map.fromList $ map (dpGhcPkgId &&& dpPackageIdent) localDumpPkgs+    bconfig <- asks getBuildConfig+    let latest =+            Map.fromListWith max $+            map toTuple $+            Map.keys (bcPackageCaches bconfig)      econfig <- asks getEnvConfig-    let onWanted lp = do-            case lpExeComponents lp of-                Nothing -> return ()-                Just _ -> void $ addDep False $ packageName $ lpPackage lp--            case lpTestBench lp of-                Just tb -> addFinal lp tb-                Nothing -> return ()+    let onWanted = void . addDep False . packageName . lpPackage     let inner = do             mapM_ onWanted $ filter lpWanted locals             mapM_ (addDep False) $ Set.toList extraToBuild0-    ((), m, W efinals installExes dirtyReason deps) <- liftIO $ runRWST inner (ctx econfig latest) M.empty+    ((), m, W efinals installExes dirtyReason deps warnings) <-+        liftIO $ runRWST inner (ctx econfig latest) M.empty+    mapM_ $logWarn (warnings [])     let toEither (_, Left e)  = Left e         toEither (k, Right v) = Right (k, v)         (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m@@ -151,7 +154,7 @@         errs = errlibs ++ errfinals     if null errs         then do-            let toTask (_, ADRFound _ _ _) = Nothing+            let toTask (_, ADRFound _ _) = Nothing                 toTask (name, ADRToInstall task) = Just (name, task)                 tasks = M.fromList $ mapMaybe toTask adrs                 takeSubset =@@ -176,13 +179,14 @@         , loadPackage = loadPackage0         , combinedMap = combineMap sourceMap installedMap         , toolToPackages = \ (Dependency name _) ->-          maybe Map.empty (Map.fromSet (\_ -> anyVersion)) $+          maybe Map.empty (Map.fromSet (const anyVersion)) $           Map.lookup (S8.pack . packageNameString . fromCabalPackageName $ name) toolMap         , ctxEnvConfig = econfig         , callStack = []         , extraToBuild = extraToBuild0         , latestVersions = latest         , wanted = wantedLocalPackages locals+        , localNames = Set.fromList $ map (packageName . lpPackage) locals         }     -- TODO Currently, this will only consider and install tools from the     -- snapshot. It will not automatically install build tools from extra-deps@@ -215,14 +219,14 @@       where         name = packageIdentifierName ident -addFinal :: LocalPackage -> LocalPackageTB -> M ()-addFinal lp lptb = do+addFinal :: LocalPackage -> Package -> Bool -> M ()+addFinal lp package isAllInOne = do     depsRes <- addPackageDeps False package     res <- case depsRes of         Left e -> return $ Left e         Right (missing, present, _minLoc) -> do             ctx <- ask-            return $ Right (Task+            return $ Right Task                 { taskProvides = PackageIdentifier                     (packageName package)                     (packageVersion package)@@ -238,13 +242,13 @@                             package                 , taskPresent = present                 , taskType = TTLocal lp-                }, lptb)+                , taskAllInOne = isAllInOne+                }     tell mempty { wFinals = Map.singleton (packageName package) res }-  where-    package = lptbPackage lptb  addDep :: Bool -- ^ is this being used by a dependency?-       -> PackageName -> M (Either ConstructPlanException AddDepRes)+       -> PackageName+       -> M (Either ConstructPlanException AddDepRes) addDep treatAsDep' name = do     ctx <- ask     let treatAsDep = treatAsDep' || name `Set.notMember` wanted ctx@@ -253,46 +257,31 @@     case Map.lookup name m of         Just res -> return res         Nothing -> do-            res <- addDep' treatAsDep name+            res <- if name `elem` callStack ctx+                then return $ Left $ DependencyCycleDetected $ name : callStack ctx+                else local (\ctx' -> ctx' { callStack = name : callStack ctx' }) $+                    case Map.lookup name $ combinedMap ctx of+                        -- TODO look up in the package index and see if there's a+                        -- recommendation available+                        Nothing -> return $ Left $ UnknownPackage name+                        Just (PIOnlyInstalled loc installed) -> do+                            -- slightly hacky, no flags since they likely won't affect executable names+                            tellExecutablesUpstream name (installedVersion installed) loc Map.empty+                            return $ Right $ ADRFound loc installed+                        Just (PIOnlySource ps) -> do+                            tellExecutables name ps+                            installPackage treatAsDep name ps Nothing+                        Just (PIBoth ps installed) -> do+                            tellExecutables name ps+                            installPackage treatAsDep name ps (Just installed)             modify $ Map.insert name res             return res -addDep' :: Bool -- ^ is this being used by a dependency?-        -> PackageName -> M (Either ConstructPlanException AddDepRes)-addDep' treatAsDep name = do-    ctx <- ask-    if name `elem` callStack ctx-        then return $ Left $ DependencyCycleDetected $ name : callStack ctx-        else local-            (\ctx' -> ctx' { callStack = name : callStack ctx' }) $ do-            (addDep'' treatAsDep name)--addDep'' :: Bool -- ^ is this being used by a dependency?-         -> PackageName -> M (Either ConstructPlanException AddDepRes)-addDep'' treatAsDep name = do-    ctx <- ask-    case Map.lookup name $ combinedMap ctx of-        -- TODO look up in the package index and see if there's a-        -- recommendation available-        Nothing -> return $ Left $ UnknownPackage name-        Just (PIOnlyInstalled version loc installed) -> do-            tellExecutablesUpstream name version loc Map.empty -- slightly hacky, no flags since they likely won't affect executable names-            return $ Right $ ADRFound loc version installed-        Just (PIOnlySource ps) -> do-            tellExecutables name ps-            installPackage treatAsDep name ps-        Just (PIBoth ps installed) -> do-            tellExecutables name ps-            needInstall <- checkNeedInstall treatAsDep name ps installed (wanted ctx)-            if needInstall-                then installPackage treatAsDep name ps-                else return $ Right $ ADRFound (piiLocation ps) (piiVersion ps) installed--tellExecutables :: PackageName -> PackageSource -> M () -- TODO merge this with addFinal above?+tellExecutables :: PackageName -> PackageSource -> M () tellExecutables _ (PSLocal lp)     | lpWanted lp = tellExecutablesPackage Local $ lpPackage lp     | otherwise = return ()-tellExecutables name (PSUpstream version loc flags) = do+tellExecutables name (PSUpstream version loc flags) =     tellExecutablesUpstream name version loc flags  tellExecutablesUpstream :: PackageName -> Version -> InstallLocation -> Map FlagName Bool -> M ()@@ -309,75 +298,127 @@     let myComps =             case Map.lookup (packageName p) cm of                 Nothing -> assert False Set.empty-                Just (PIOnlyInstalled _ _ _) -> Set.empty+                Just (PIOnlyInstalled _ _) -> Set.empty                 Just (PIOnlySource ps) -> goSource ps                 Just (PIBoth ps _) -> goSource ps -        goSource (PSLocal lp) = fromMaybe Set.empty $ lpExeComponents lp-        goSource (PSUpstream _ _ _) = Set.empty+        goSource (PSLocal lp)+            | lpWanted lp = exeComponents (lpComponents lp)+            | otherwise = Set.empty+        goSource (PSUpstream{}) = Set.empty -    tell mempty { wInstall = m myComps }+    tell mempty { wInstall = Map.fromList $ map (, loc) $ Set.toList $ filterComps myComps $ packageExes p }   where-    m myComps = Map.fromList $ map (, loc) $ Set.toList-              $ filterComps myComps $ packageExes p-     filterComps myComps x         | Set.null myComps = x-        | otherwise = Set.intersection x $ Set.map toExe myComps--    toExe x = fromMaybe x $ T.stripPrefix "exe:" x---- TODO There are a lot of duplicated computations below. I've kept that for--- simplicity right now+        | otherwise = Set.intersection x myComps  installPackage :: Bool -- ^ is this being used by a dependency?-               -> PackageName -> PackageSource -> M (Either ConstructPlanException AddDepRes)-installPackage treatAsDep name ps = do+               -> PackageName+               -> PackageSource+               -> Maybe Installed+               -> M (Either ConstructPlanException AddDepRes)+installPackage treatAsDep name ps minstalled = do     ctx <- ask-    package <- psPackage name ps-    depsRes <- addPackageDeps treatAsDep package-    case depsRes of-        Left e -> return $ Left e-        Right (missing, present, minLoc) -> do-            return $ Right $ ADRToInstall Task-                { taskProvides = PackageIdentifier-                    (packageName package)-                    (packageVersion package)-                , taskConfigOpts = TaskConfigOpts missing $ \missing' ->-                    let allDeps = Map.union present missing'-                        destLoc = piiLocation ps <> minLoc-                     in configureOpts-                            (getEnvConfig ctx)-                            (baseConfigOpts ctx)-                            allDeps-                            (psWanted ps)-                            (psLocal ps)-                            -- An assertion to check for a recurrence of-                            -- https://github.com/commercialhaskell/stack/issues/345-                            (assert (destLoc == piiLocation ps) destLoc)-                            package-                , taskPresent = present-                , taskType =-                    case ps of-                        PSLocal lp -> TTLocal lp-                        PSUpstream _ loc _ -> TTUpstream package $ loc <> minLoc-                }+    case ps of+        PSUpstream version _ flags -> do+            package <- liftIO $ loadPackage ctx name version flags+            resolveDepsAndInstall False treatAsDep ps package minstalled+        PSLocal lp ->+            case lpTestBench lp of+                Nothing -> resolveDepsAndInstall False treatAsDep ps (lpPackage lp) minstalled+                Just tb -> do+                    -- Attempt to find a plan which performs an all-in-one+                    -- build.  Ignore the writer action + reset the state if+                    -- it fails.+                    s <- get+                    res <- pass $ do+                        res <- addPackageDeps treatAsDep tb+                        let writerFunc w = case res of+                                Left _ -> mempty+                                _ -> w+                        return (res, writerFunc)+                    case res of+                        Right deps -> do+                          adr <- installPackageGivenDeps True ps tb minstalled deps+                          -- FIXME: this redundantly adds the deps (but+                          -- they'll all just get looked up in the map)+                          addFinal lp tb True+                          return $ Right adr+                        Left _ -> do+                            -- Reset the state to how it was before+                            -- attempting to find an all-in-one build+                            -- plan.+                            put s+                            -- Otherwise, fall back on building the+                            -- tests / benchmarks in a separate step.+                            res' <- resolveDepsAndInstall False treatAsDep ps (lpPackage lp) minstalled+                            when (isRight res') $ do+                                -- Insert it into the map so that it's+                                -- available for addFinal.+                                modify $ Map.insert name res'+                                addFinal lp tb False+                            return res' -checkNeedInstall :: Bool-                 -> PackageName -> PackageSource -> Installed -> Set PackageName -> M Bool-checkNeedInstall treatAsDep name ps installed wanted = assert (piiLocation ps == Local) $ do-    package <- psPackage name ps-    depsRes <- addPackageDeps treatAsDep package-    case depsRes of-        Left _e -> return True -- installPackage will find the error again-        Right (missing, present, _loc)-            | Set.null missing -> checkDirtiness ps installed package present wanted-            | otherwise -> do-                tell mempty { wDirty = Map.singleton name $-                    let t = T.intercalate ", " $ map (T.pack . packageNameString . packageIdentifierName) (Set.toList missing)-                     in T.append "missing dependencies: " $ addEllipsis t }-                return True+resolveDepsAndInstall :: Bool+                      -> Bool+                      -> PackageSource+                      -> Package+                      -> Maybe Installed+                      -> M (Either ConstructPlanException AddDepRes)+resolveDepsAndInstall isAllInOne treatAsDep ps package minstalled = do+    res <- addPackageDeps treatAsDep package+    case res of+        Left err -> return $ Left err+        Right deps -> liftM Right $ installPackageGivenDeps isAllInOne ps package minstalled deps +installPackageGivenDeps :: Bool+                        -> PackageSource+                        -> Package+                        -> Maybe Installed+                        -> ( Set PackageIdentifier+                           , Map PackageIdentifier GhcPkgId+                           , InstallLocation )+                        -> M AddDepRes+installPackageGivenDeps isAllInOne ps package minstalled (missing, present, minLoc) = do+    let name = packageName package+    ctx <- ask+    mRightVersionInstalled <- case (minstalled, Set.null missing) of+        (Just installed, True) -> do+            shouldInstall <- checkDirtiness ps installed package present (wanted ctx)+            return $ if shouldInstall then Nothing else Just installed+        (Just _, False) -> do+            let t = T.intercalate ", " $ map (T.pack . packageNameString . packageIdentifierName) (Set.toList missing)+            tell mempty { wDirty = Map.singleton name $ "missing dependencies: " <> addEllipsis t }+            return Nothing+        (Nothing, _) -> return Nothing+    return $ case mRightVersionInstalled of+        Just installed -> ADRFound (piiLocation ps) installed+        Nothing -> ADRToInstall Task+            { taskProvides = PackageIdentifier+                (packageName package)+                (packageVersion package)+            , taskConfigOpts = TaskConfigOpts missing $ \missing' ->+                let allDeps = Map.union present missing'+                    destLoc = piiLocation ps <> minLoc+                 in configureOpts+                        (getEnvConfig ctx)+                        (baseConfigOpts ctx)+                        allDeps+                        (psWanted ps)+                        (psLocal ps)+                        -- An assertion to check for a recurrence of+                        -- https://github.com/commercialhaskell/stack/issues/345+                        (assert (destLoc == piiLocation ps) destLoc)+                        package+            , taskPresent = present+            , taskType =+                case ps of+                    PSLocal lp -> TTLocal lp+                    PSUpstream _ loc _ -> TTUpstream package $ loc <> minLoc+            , taskAllInOne = isAllInOne+            }+ addEllipsis :: Text -> Text addEllipsis t     | T.length t < 100 = t@@ -398,14 +439,45 @@                             UnknownPackage name -> assert (name == depname) NotInBuildPlan                             _ -> Couldn'tResolveItsDependencies                  in return $ Left (depname, (range, mlatest, bd))-            Right adr | not $ adrVersion adr `withinRange` range ->-                return $ Left (depname, (range, mlatest, DependencyMismatch $ adrVersion adr))-            Right (ADRToInstall task) -> return $ Right-                (Set.singleton $ taskProvides task, Map.empty, taskLocation task)-            Right (ADRFound loc _ (Executable _)) -> return $ Right-                (Set.empty, Map.empty, loc)-            Right (ADRFound loc _ (Library ident gid)) -> return $ Right-                (Set.empty, Map.singleton ident gid, loc)+            Right adr -> do+                inRange <- if adrVersion adr `withinRange` range+                    then return True+                    else do+                        let warn reason =+                                tell mempty { wWarnings = (msg:) }+                              where+                                msg = T.concat+                                    [ "WARNING: Ignoring out of range dependency"+                                    , reason+                                    , ": "+                                    , T.pack $ packageIdentifierString $ PackageIdentifier depname (adrVersion adr)+                                    , ". "+                                    , T.pack $ packageNameString $ packageName package+                                    , " requires: "+                                    , versionRangeText range+                                    ]+                        allowNewer <- asks $ configAllowNewer . getConfig+                        if allowNewer+                            then do+                                warn " (allow-newer enabled)"+                                return True+                            else do+                                x <- inSnapshot (packageName package) (packageVersion package)+                                y <- inSnapshot depname (adrVersion adr)+                                if x && y+                                    then do+                                        warn " (trusting snapshot over Hackage revisions)"+                                        return True+                                    else return False+                if inRange+                    then case adr of+                        ADRToInstall task -> return $ Right+                            (Set.singleton $ taskProvides task, Map.empty, taskLocation task)+                        ADRFound loc (Executable _) -> return $ Right+                            (Set.empty, Map.empty, loc)+                        ADRFound loc (Library ident gid) -> return $ Right+                            (Set.empty, Map.singleton ident gid, loc)+                    else return $ Left (depname, (range, mlatest, DependencyMismatch $ adrVersion adr))     case partitionEithers deps of         ([], pairs) -> return $ Right $ mconcat pairs         (errs, _) -> return $ Left $ DependencyPlanFailures@@ -415,7 +487,7 @@             (Map.fromList errs)   where     adrVersion (ADRToInstall task) = packageIdentifierVersion $ taskProvides task-    adrVersion (ADRFound _ v _) = v+    adrVersion (ADRFound _ installed) = installedVersion installed  checkDirtiness :: PackageSource                -> Installed@@ -441,7 +513,7 @@             , configCacheComponents =                 case ps of                     PSLocal lp -> Set.map renderComponent $ lpComponents lp-                    PSUpstream _ _ _ -> Set.empty+                    PSUpstream{} -> Set.empty             , configCacheHaddock =                 shouldHaddockPackage buildOpts wanted (packageName package) ||                 -- Disabling haddocks when old config had haddocks doesn't make dirty.@@ -497,17 +569,26 @@         go       where         go [] = []-        go ("--ghc-option":_:xs) = go xs-        go ("--ghc-options":_:xs) = go xs-        go (x:xs)-          | isPrefixed x = go xs-          | otherwise = x : go xs+        go ("--ghc-option":x:xs) = go' x xs+        go ("--ghc-options":x:xs) = go' x xs+        go ((T.stripPrefix "--ghc-option=" -> Just x):xs) = go' x xs+        go ((T.stripPrefix "--ghc-options=" -> Just x):xs) = go' x xs+        go (x:xs) = x : go xs -        isPrefixed t = any (`T.isPrefixOf` t)-            [ "--ghc-option="-            , "--ghc-options="-            ]+        go' x xs = checkKeepers x $ go xs +        checkKeepers x xs =+            case filter isKeeper $ T.words x of+                [] -> xs+                keepers -> "--ghc-options" : T.unwords keepers : xs++        -- GHC options which affect build results and therefore should always+        -- force a rebuild+        --+        -- For the most part, we only care about options generated by Stack+        -- itself+        isKeeper = (== "-fhpc") -- more to be added later+     userOpts = filter (not . isStackOpt)              . (if configRebuildGhcOptions config                    then id@@ -526,21 +607,15 @@  psDirty :: PackageSource -> Maybe (Set FilePath) psDirty (PSLocal lp) = lpDirtyFiles lp-psDirty (PSUpstream _ _ _) = Nothing -- files never change in an upstream package+psDirty (PSUpstream {}) = Nothing -- files never change in an upstream package  psWanted :: PackageSource -> Bool psWanted (PSLocal lp) = lpWanted lp-psWanted (PSUpstream _ _ _) = False+psWanted (PSUpstream {}) = False  psLocal :: PackageSource -> Bool psLocal (PSLocal _) = True-psLocal (PSUpstream _ _ _) = False--psPackage :: PackageName -> PackageSource -> M Package-psPackage _ (PSLocal lp) = return $ lpPackage lp-psPackage name (PSUpstream version _ flags) = do-    ctx <- ask-    liftIO $ loadPackage ctx name version flags+psLocal (PSUpstream {}) = False  -- | Get all of the dependencies for a given package, including guessed build -- tool dependencies.@@ -577,3 +652,13 @@  markAsDep :: PackageName -> M () markAsDep name = tell mempty { wDeps = Set.singleton name }++-- | Is the given package/version combo defined in the snapshot?+inSnapshot :: PackageName -> Version -> M Bool+inSnapshot name version = do+    p <- asks mbp+    ls <- asks localNames+    return $ fromMaybe False $ do+        guard $ not $ name `Set.member` ls+        mpi <- Map.lookup name (mbpPackages p)+        return $ mpiVersion mpi == version
− src/Stack/Build/Coverage.hs
@@ -1,259 +0,0 @@-{-# LANGUAGE ConstraintKinds       #-}-{-# LANGUAGE FlexibleContexts      #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}-{-# LANGUAGE TemplateHaskell       #-}--- | Generate HPC (Haskell Program Coverage) reports-module Stack.Build.Coverage-    ( updateTixFile-    , generateHpcReport-    , generateHpcUnifiedReport-    , generateHpcMarkupIndex-    ) where--import           Control.Applicative-import           Control.Exception.Lifted-import           Control.Monad                  (liftM, when, void)-import           Control.Monad.Catch            (MonadCatch)-import           Control.Monad.IO.Class-import           Control.Monad.Logger-import           Control.Monad.Reader           (MonadReader, asks)-import           Control.Monad.Trans.Resource-import qualified Data.ByteString.Char8          as S8-import           Data.Foldable                  (forM_)-import           Data.Function-import           Data.List-import qualified Data.Map.Strict                as Map-import           Data.Maybe-import           Data.Monoid                    ((<>))-import           Data.Text                      (Text)-import qualified Data.Text                      as T-import qualified Data.Text.Encoding             as T-import qualified Data.Text.IO                   as T-import qualified Data.Text.Lazy                 as LT-import           Data.Traversable               (forM)-import           Trace.Hpc.Tix-import           Path-import           Path.IO-import           Prelude                        hiding (FilePath, writeFile)-import           Stack.Constants-import           Stack.Package-import           Stack.Types-import           System.Process.Read-import           Text.Hastache                  (htmlEscape)---- | Move a tix file into a sub-directory of the hpc report directory.--- Deletes the old one if one is present.-updateTixFile :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)-            => Path Abs File -> String -> m ()-updateTixFile tixSrc pkgId = do-    exists <- fileExists tixSrc-    when exists $ do-        outputDir <- hpcReportDir-        pkgIdRel <- parseRelDir pkgId-        let tixDest = outputDir </> pkgIdRel </> filename tixSrc-        removeFileIfExists tixDest-        createTree (parent tixDest)-        renameFile tixSrc tixDest---- | Get the tix file location, given the name of the file (without--- extension), and the package identifier string.-tixFilePath ::  (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)-            => String -> String ->  m (Path Abs File)-tixFilePath pkgId tixName = do-    outputDir <- hpcReportDir-    pkgIdRel <- parseRelDir pkgId-    tixRel <- parseRelFile (tixName ++ ".tix")-    return (outputDir </> pkgIdRel </> tixRel)---- | Generates the HTML coverage report and shows a textual coverage--- summary for a package.-generateHpcReport :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)-                  => Package -> [Text] -> (PackageName -> m (Maybe Text)) -> m ()-generateHpcReport package tests getGhcPkgKey = do-    -- If we're using > GHC 7.10, the hpc 'include' parameter must specify a-    -- ghc package key. See-    -- https://github.com/commercialhaskell/stack/issues/785-    let pkgName = packageNameText (packageName package)-        pkgId = packageIdentifierString (packageIdentifier package)-    compilerVersion <- asks (envConfigCompilerVersion . getEnvConfig)-    includeName <--        if getGhcVersion compilerVersion < $(mkVersion "7.10")-            then return pkgId-            else do-                mghcPkgKey <- getGhcPkgKey (packageName package)-                case mghcPkgKey of-                    Nothing -> fail $ "Before computing test coverage report, failed to find GHC package key for " ++ T.unpack pkgName-                    Just ghcPkgKey -> return $ T.unpack ghcPkgKey-    forM_ tests $ \testName -> do-        tixSrc <- tixFilePath pkgId (T.unpack testName)-        subdir <- parseRelDir (T.unpack testName)-        let report = "coverage report for " <> pkgName <> "'s test-suite \"" <> testName <> "\""-            -- Restrict to just the current library code (see #634 --            -- this will likely be customizable in the future)-            extraArgs = ["--include", includeName ++ ":"]-        generateHpcReportInternal tixSrc subdir report extraArgs extraArgs--generateHpcReportInternal :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)-                          => Path Abs File -> Path Rel Dir -> Text -> [String] -> [String] -> m ()-generateHpcReportInternal tixSrc subdir report extraMarkupArgs extraReportArgs = do-    -- If a .tix file exists, move it to the HPC output directory-    -- and generate a report for it.-    tixFileExists <- fileExists tixSrc-    if not tixFileExists-        then $logError $ T.concat-            [ "Didn't find .tix for "-            , report-            , " - expected to find it at "-            , T.pack (toFilePath tixSrc)-            , "."-            ]-        else (`onException` $logError ("Error occurred while producing " <> report)) $ do-            -- Directories for .mix files.-            hpcRelDir <- (</> dotHpc) <$> hpcRelativeDir-            -- Compute arguments used for both "hpc markup" and "hpc report".-            pkgDirs <- Map.keys . envConfigPackages <$> asks getEnvConfig-            let args =-                    -- Use index files from all packages (allows cross-package-                    -- coverage results).-                    concatMap (\x -> ["--srcdir", toFilePath x]) pkgDirs ++-                    -- Look for index files in the correct dir (relative to-                    -- each pkgdir).-                    ["--hpcdir", toFilePath hpcRelDir, "--reset-hpcdirs"]-                reportDest = parent tixSrc </> subdir-            menv <- getMinimalEnvOverride-            $logInfo $ "Generating " <> report-            outputLines <- liftM S8.lines $ readProcessStdout Nothing menv "hpc"-                ( "report"-                : toFilePath tixSrc-                : (args ++ extraReportArgs)-                )-            if all ("(0/0)" `S8.isSuffixOf`) outputLines-                then $logError $ T.concat-                    [ "Error: The "-                    , report-                    , " did not consider any code. One possible cause of this is"-                    , " if your test-suite builds the library code (see stack"-                    , " issue #1008). It may also indicate a bug in stack or"-                    , " the hpc program. Please report this issue if you think"-                    , " your coverage report should have meaningful results."-                    ]-                else do-                    -- Print output, stripping @\r@ characters because-                    -- Windows.-                    forM_ outputLines ($logInfo . T.decodeUtf8 . S8.filter (not . (=='\r')))-                    $logInfo-                        ("The " <> report <> " is available at " <>-                         T.pack (toFilePath (reportDest </> $(mkRelFile "hpc_index.html"))))-            -- Generate the markup.-            void $ readProcessStdout Nothing menv "hpc"-                ( "markup"-                : toFilePath tixSrc-                : ("--destdir=" ++ toFilePath reportDest)-                : (args ++ extraMarkupArgs)-                )--generateHpcUnifiedReport :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)-                     => m ()-generateHpcUnifiedReport = do-    outputDir <- hpcReportDir-    createTree outputDir-    (dirs, _) <- listDirectory outputDir-    tixFiles <- liftM concat $ forM dirs $ \dir -> do-        (_, files) <- listDirectory dir-        return (filter ((".tix" `isSuffixOf`) . toFilePath) files)-    if length tixFiles < 2-        then $logInfo $ T.concat $-            [ if null tixFiles then "No tix files" else "Only one tix file"-            , " found in "-            , T.pack (toFilePath outputDir)-            , ", so not generating a unified coverage report."-            ]-        else do-            tixes <- mapM (liftM (fmap removeExeModules) . readTixOrLog) tixFiles-            let (errs, tix) = unionTixes (catMaybes tixes)-            when (not (null errs)) $ $logWarn $ T.concat $-                "The following modules are left out of the unified report due to version mismatches: " :-                intersperse ", " (map T.pack errs)-            let tixDest = outputDir </> $(mkRelFile "unified/unified.tix")-            createTree (parent tixDest)-            liftIO $ writeTix (toFilePath tixDest) tix-            generateHpcReportInternal tixDest $(mkRelDir "unified") "unified report" [] []--readTixOrLog :: (MonadLogger m, MonadIO m) => Path b File -> m (Maybe Tix)-readTixOrLog path = do-    mtix <- liftIO $ readTix (toFilePath path)-    when (isNothing mtix) $-        $logError $ "Failed to read tix file " <> T.pack (toFilePath path)-    return mtix---- | Module names which contain '/' have a package name, and so they--- weren't built into the executable.-removeExeModules :: Tix -> Tix-removeExeModules (Tix ms) = Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms)--unionTixes :: [Tix] -> ([String], Tix)-unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs))-  where-    (errs, outputs) = Map.mapEither id $ Map.unionsWith merge $ map toMap tixes-    toMap (Tix ms) = Map.fromList (map (\x@(TixModule k _ _ _) -> (k, Right x)) ms)-    merge (Right (TixModule k hash1 len1 tix1))-          (Right (TixModule _ hash2 len2 tix2))-        | hash1 == hash2 && len1 == len2 = Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2))-    merge _ _ = Left ()--generateHpcMarkupIndex :: (MonadIO m,MonadReader env m,MonadLogger m,MonadCatch m,HasEnvConfig env)-                       => m ()-generateHpcMarkupIndex = do-    outputDir <- hpcReportDir-    let outputFile = outputDir </> $(mkRelFile "index.html")-    createTree outputDir-    (dirs, _) <- listDirectory outputDir-    rows <- liftM (catMaybes . concat) $ forM dirs $ \dir -> do-        (subdirs, _) <- listDirectory dir-        forM subdirs $ \subdir -> do-            let indexPath = subdir </> $(mkRelFile "hpc_index.html")-            exists' <- fileExists indexPath-            if not exists' then return Nothing else do-                relPath <- stripDir outputDir indexPath-                let package = dirname dir-                    testsuite = dirname subdir-                return $ Just $ T.concat-                  [ "<tr><td>"-                  , pathToHtml package-                  , "</td><td><a href=\""-                  , pathToHtml relPath-                  , "\">"-                  , pathToHtml testsuite-                  , "</a></td></tr>"-                  ]-    liftIO $ T.writeFile (toFilePath outputFile) $ T.concat $-        [ "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"-        -- Part of the css from HPC's output HTML-        , "<style type=\"text/css\">"-        , "table.dashboard { border-collapse: collapse; border: solid 1px black }"-        , ".dashboard td { border: solid 1px black }"-        , ".dashboard th { border: solid 1px black }"-        , "</style>"-        , "</head>"-        , "<body>"-        ] ++-        (if null rows-            then-                [ "<b>No hpc_index.html files found in \""-                , pathToHtml outputDir-                , "\".</b>"-                ]-            else-                [ "<table class=\"dashboard\" width=\"100%\" boder=\"1\"><tbody>"-                , "<p><b>NOTE: This is merely a listing of the html files found in the coverage reports directory.  Some of these reports may be old.</b></p>"-                , "<tr><th>Package</th><th>TestSuite</th></tr>"-                ] ++-                rows ++-                ["</tbody></table>"]) ++-        ["</body></html>"]-    $logInfo $ "\nAn index of the generated HTML coverage reports is available at " <>-        T.pack (toFilePath outputFile)--pathToHtml :: Path b t -> Text-pathToHtml = T.dropWhileEnd (=='/') . LT.toStrict . htmlEscape . LT.pack . toFilePath
src/Stack/Build/Execute.hs view
@@ -18,80 +18,83 @@  import           Control.Applicative import           Control.Arrow ((&&&))+import           Control.Concurrent.Async (withAsync, wait) import           Control.Concurrent.Execute-import           Control.Concurrent.Async       (withAsync, wait) import           Control.Concurrent.MVar.Lifted import           Control.Concurrent.STM-import           Control.Exception.Enclosed     (catchIO, tryIO)+import           Control.Exception.Enclosed (catchIO, tryIO) import           Control.Exception.Lifted-import           Control.Monad                  (liftM, when, unless, void, join, guard, filterM, (<=<))-import           Control.Monad.Catch            (MonadCatch, MonadMask)+import           Control.Monad (liftM, when, unless, void, join, guard, filterM, (<=<))+import           Control.Monad.Catch (MonadCatch, MonadMask) import           Control.Monad.IO.Class import           Control.Monad.Logger-import           Control.Monad.Reader           (MonadReader, asks)-import           Control.Monad.Trans.Control    (liftBaseWith)+import           Control.Monad.Reader (MonadReader, asks)+import           Control.Monad.Trans.Control (liftBaseWith) import           Control.Monad.Trans.Resource-import qualified Data.ByteString                as S-import           Data.ByteString                (ByteString)-import qualified Data.ByteString.Char8          as S8+import           Data.ByteString (ByteString)+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8 import           Data.Conduit-import qualified Data.Conduit.Binary            as CB-import qualified Data.Conduit.List              as CL-import           Data.Foldable                  (forM_)+import qualified Data.Conduit.Binary as CB+import qualified Data.Conduit.List as CL+import           Data.Foldable (forM_, any) import           Data.Function-import           Data.IORef.RunOnce             (runOnce)-import           Data.List-import           Data.Map.Strict                (Map)-import qualified Data.Map.Strict                as Map+import           Data.IORef.RunOnce (runOnce)+import           Data.List hiding (any)+import           Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map import           Data.Maybe-import           Data.Monoid                    ((<>))-import           Data.Set                       (Set)-import qualified Data.Set                       as Set-import           Data.Streaming.Process         hiding (callProcess, env)-import qualified Data.Streaming.Process         as Process-import           Data.Traversable               (forM)-import           Data.Text                      (Text)-import qualified Data.Text                      as T-import           Data.Time.Clock                (getCurrentTime)-import           Data.Word8                     (_colon)+import           Data.Maybe.Extra (forMaybeM)+import           Data.Monoid ((<>))+import           Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Streaming.Process as Process+import           Data.Streaming.Process hiding (callProcess, env)+import           Data.Text (Text)+import qualified Data.Text as T+import           Data.Text.Encoding (decodeUtf8)+import           Data.Time.Clock (getCurrentTime)+import           Data.Traversable (forM)+import           Data.Word8 (_colon) import           Distribution.System            (OS (Windows),                                                  Platform (Platform)) import qualified Distribution.Text-import           Language.Haskell.TH            as TH (location)-import           Network.HTTP.Client.Conduit    (HasHttpManager)+import           Language.Haskell.TH as TH (location)+import           Network.HTTP.Client.Conduit (HasHttpManager) import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO-import           Prelude                        hiding (FilePath, writeFile)+import           Prelude hiding (FilePath, writeFile, any) import           Stack.Build.Cache-import           Stack.Build.Coverage import           Stack.Build.Haddock import           Stack.Build.Installed import           Stack.Build.Source-import           Stack.Types.Build-import           Stack.Fetch                    as Fetch+import           Stack.Constants+import           Stack.Coverage+import           Stack.Fetch as Fetch import           Stack.GhcPkg import           Stack.Package import           Stack.PackageDump-import           Stack.Constants import           Stack.Types-import           Stack.Types.StackT import           Stack.Types.Internal-import qualified System.Directory               as D-import           System.Environment             (getExecutablePath)-import           System.Exit                    (ExitCode (ExitSuccess))-import qualified System.FilePath                as FP+import           Stack.Types.StackT+import qualified System.Directory as D+import           System.Environment (getExecutablePath)+import           System.Exit (ExitCode (ExitSuccess))+import qualified System.FilePath as FP import           System.IO-import           System.PosixCompat.Files       (createLink)+import           System.PosixCompat.Files (createLink)+import           System.Process.Log (showProcessArgDebug) import           System.Process.Read import           System.Process.Run-import           System.Process.Log             (showProcessArgDebug)  #if !MIN_VERSION_process(1,2,1)-import           System.Process.Internals       (createProcess_)+import           System.Process.Internals (createProcess_) #endif  type M env m = (MonadIO m,MonadReader env m,HasHttpManager env,HasBuildConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,MonadMask m,HasLogLevel env,HasEnvConfig env,HasTerminal env) +-- | Fetch the packages necessary for a build, for example in combination with a dry run. preFetch :: M env m => Plan -> m () preFetch plan     | Set.null idents = $logDebug "Nothing to fetch"@@ -111,6 +114,7 @@                 name                 (packageVersion package) +-- | Print a description of build plan for human consumption. printPlan :: M env m           => Plan           -> m ()@@ -138,10 +142,10 @@             $logInfo "Would build:"             mapM_ ($logInfo . displayTask) xs -    let hasTests = not . Set.null . lptbTests-        hasBenches = not . Set.null . lptbBenches-        tests = Map.elems $ fmap fst $ Map.filter (hasTests . snd) $ planFinals plan-        benches = Map.elems $ fmap fst $ Map.filter (hasBenches . snd) $ planFinals plan+    let hasTests = not . Set.null . testComponents . taskComponents+        hasBenches = not . Set.null . benchComponents . taskComponents+        tests = Map.elems $ Map.filter hasTests $ planFinals plan+        benches = Map.elems $ Map.filter hasBenches $ planFinals plan      unless (null tests) $ do         $logInfo ""@@ -204,9 +208,10 @@     , eeTotalWanted    :: !Int     , eeWanted         :: !(Set PackageName)     , eeLocals         :: ![LocalPackage]-    , eeSourceMap      :: !SourceMap     , eeGlobalDB       :: !(Path Abs Dir)-    , eeGlobalPackages :: ![DumpPackage () ()]+    , eeGlobalDumpPkgs :: !(Map GhcPkgId (DumpPackage () ()))+    , eeSnapshotDumpPkgs :: !(TVar (Map GhcPkgId (DumpPackage () ())))+    , eeLocalDumpPkgs  :: !(TVar (Map GhcPkgId (DumpPackage () ())))     }  -- | Get a compiled Setup exe@@ -273,16 +278,18 @@             renameFile tmpExePath exePath             return $ Just exePath +-- | Execute a callback that takes an 'ExecuteEnv'. withExecuteEnv :: M env m                => EnvOverride                -> BuildOpts                -> BaseConfigOpts                -> [LocalPackage]                -> [DumpPackage () ()] -- ^ global packages-               -> SourceMap+               -> [DumpPackage () ()] -- ^ snapshot packages+               -> [DumpPackage () ()] -- ^ local packages                -> (ExecuteEnv -> m a)                -> m a-withExecuteEnv menv bopts baseConfigOpts locals globals sourceMap inner = do+withExecuteEnv menv bopts baseConfigOpts locals globalPackages snapshotPackages localPackages inner = do     withCanonicalizedSystemTempDirectory stackProgName $ \tmpdir -> do         configLock <- newMVar ()         installLock <- newMVar ()@@ -292,6 +299,8 @@         setupExe <- getSetupExe setupHs tmpdir         cabalPkgVer <- asks (envConfigCabalVersion . getEnvConfig)         globalDB <- getGlobalDB menv =<< getWhichCompiler+        snapshotPackagesTVar <- liftIO $ newTVarIO (toDumpPackagesByGhcPkgId snapshotPackages)+        localPackagesTVar <- liftIO $ newTVarIO (toDumpPackagesByGhcPkgId localPackages)         inner ExecuteEnv             { eeEnvOverride = menv             , eeBuildOpts = bopts@@ -310,10 +319,13 @@             , eeTotalWanted = length $ filter lpWanted locals             , eeWanted = wantedLocalPackages locals             , eeLocals = locals-            , eeSourceMap = sourceMap             , eeGlobalDB = globalDB-            , eeGlobalPackages = globals+            , eeGlobalDumpPkgs = toDumpPackagesByGhcPkgId globalPackages+            , eeSnapshotDumpPkgs = snapshotPackagesTVar+            , eeLocalDumpPkgs = localPackagesTVar             }+  where+    toDumpPackagesByGhcPkgId = Map.fromList . map (\dp -> (dpGhcPkgId dp, dp))  -- | Perform the actual plan executePlan :: M env m@@ -321,13 +333,14 @@             -> BuildOpts             -> BaseConfigOpts             -> [LocalPackage]-            -> [DumpPackage () ()] -- ^ globals-            -> SourceMap+            -> [DumpPackage () ()] -- ^ global packages+            -> [DumpPackage () ()] -- ^ snapshot packages+            -> [DumpPackage () ()] -- ^ local packages             -> InstalledMap             -> Plan             -> m ()-executePlan menv bopts baseConfigOpts locals globals sourceMap installedMap plan = do-    withExecuteEnv menv bopts baseConfigOpts locals globals sourceMap (executePlan' installedMap plan)+executePlan menv bopts baseConfigOpts locals globalPackages snapshotPackages localPackages installedMap plan = do+    withExecuteEnv menv bopts baseConfigOpts locals globalPackages snapshotPackages localPackages (executePlan' installedMap plan)      unless (Map.null $ planInstallExes plan) $ do         snapBin <- (</> bindirSuffix) `liftM` installationRootDeps@@ -352,7 +365,7 @@          currExe <- liftIO getExecutablePath -- needed for windows, see below -        installed <- forM (Map.toList $ planInstallExes plan) $ \(name, loc) -> do+        installed <- forMaybeM (Map.toList $ planInstallExes plan) $ \(name, loc) -> do             let bindir =                     case loc of                         Snap -> snapBin@@ -382,7 +395,7 @@                         _ -> D.copyFile (toFilePath file) destFile                     return $ Just (destDir', [T.append name (T.pack ext)]) -        let destToInstalled = Map.fromListWith (++) (catMaybes installed)+        let destToInstalled = Map.fromListWith (++) installed         unless (Map.null destToInstalled) $ $logInfo ""         forM_ (Map.toList destToInstalled) $ \(dest, executables) -> do             $logInfo $ T.concat@@ -419,7 +432,8 @@              -> Plan              -> ExecuteEnv              -> m ()-executePlan' installedMap plan ee@ExecuteEnv {..} = do+executePlan' installedMap0 plan ee@ExecuteEnv {..} = do+    when (toCoverage $ boptsTestOpts eeBuildOpts) deleteHpcReports     wc <- getWhichCompiler     cv <- asks $ envConfigCompilerVersion . getEnvConfig     case Map.toList $ planUnregisterLocal plan of@@ -440,6 +454,9 @@                     ]                 unregisterGhcPkgId eeEnvOverride wc cv localDB id' ident +    liftIO $ atomically $ modifyTVar' eeLocalDumpPkgs $ \initMap ->+        foldl' (flip Map.delete) initMap $ Map.keys (planUnregisterLocal plan)+     -- Yes, we're explicitly discarding result values, which in general would     -- be bad. monad-unlift does this all properly at the type system level,     -- but I don't want to pull it in for this one use case, when we know that@@ -482,16 +499,18 @@         if total > 1             then loop 0             else return ()-    unless (null errs) $ throwM $ ExecutionFailure errs-    when (boptsHaddock eeBuildOpts) $ do-        generateLocalHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeLocals-        generateDepsHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeLocals-        generateSnapHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeGlobalDB     when (toCoverage $ boptsTestOpts eeBuildOpts) $ do         generateHpcUnifiedReport         generateHpcMarkupIndex+    unless (null errs) $ throwM $ ExecutionFailure errs+    when (boptsHaddock eeBuildOpts) $ do+        snapshotDumpPkgs <- liftIO (readTVarIO eeSnapshotDumpPkgs)+        localDumpPkgs <- liftIO (readTVarIO eeLocalDumpPkgs)+        generateLocalHaddockIndex eeEnvOverride wc eeBaseConfigOpts localDumpPkgs eeLocals+        generateDepsHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeGlobalDumpPkgs snapshotDumpPkgs localDumpPkgs eeLocals+        generateSnapHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeGlobalDumpPkgs snapshotDumpPkgs   where-    installedMap' = Map.difference installedMap+    installedMap' = Map.difference installedMap0                   $ Map.fromList                   $ map (\(ident, _) -> (packageIdentifierName ident, ()))                   $ Map.elems@@ -501,7 +520,7 @@           => InstalledMap           -> (m () -> IO ())           -> ExecuteEnv-          -> (Maybe Task, Maybe (Task, LocalPackageTB)) -- build and final+          -> (Maybe Task, Maybe Task) -- build and final           -> [Action] toActions installedMap runInBase ee (mbuild, mfinal) =     abuild ++ afinal@@ -514,39 +533,60 @@                     { actionId = ActionId taskProvides ATBuild                     , actionDeps =                         (Set.map (\ident -> ActionId ident ATBuild) (tcoMissing taskConfigOpts))-                    , actionDo = \ac -> runInBase $ singleBuild runInBase ac ee task installedMap+                    , actionDo = \ac -> runInBase $ singleBuild runInBase ac ee task installedMap False                     }                 ]     afinal =         case mfinal of             Nothing -> []-            Just (task@Task {..}, lptb) ->+            Just task@Task {..} ->+                (if taskAllInOne then [] else+                    [Action+                        { actionId = ActionId taskProvides ATBuildFinal+                        , actionDeps = addBuild ATBuild+                            (Set.map (\ident -> ActionId ident ATBuild) (tcoMissing taskConfigOpts))+                        , actionDo = \ac -> runInBase $ singleBuild runInBase ac ee task installedMap True+                        }]) ++                 [ Action                     { actionId = ActionId taskProvides ATFinal-                    , actionDeps = addBuild taskProvides $-                        (Set.map (\ident -> ActionId ident ATBuild) (tcoMissing taskConfigOpts))+                    , actionDeps = addBuild (if taskAllInOne then ATBuild else ATBuildFinal) Set.empty                     , actionDo = \ac -> runInBase $ do-                        unless (Set.null $ lptbTests lptb) $ do-                            singleTest runInBase topts lptb ac ee task installedMap-                        unless (Set.null $ lptbBenches lptb) $ do-                            singleBench runInBase beopts lptb ac ee task installedMap+                        let comps = taskComponents task+                            tests = testComponents comps+                            benches = benchComponents comps+                        unless (Set.null tests) $ do+                            singleTest runInBase topts (Set.toList tests) ac ee task installedMap+                        unless (Set.null benches) $ do+                            -- FIXME: shouldn't this use the list of benchmarks to run?+                            singleBench runInBase beopts ac ee task installedMap                     }                 ]-      where-        addBuild ident =-            case mbuild of-                Nothing -> id-                Just _ -> Set.insert $ ActionId ident ATBuild-+              where+                addBuild aty =+                    case mbuild of+                        Nothing -> id+                        Just _ -> Set.insert $ ActionId taskProvides aty     bopts = eeBuildOpts ee     topts = boptsTestOpts bopts     beopts = boptsBenchmarkOpts bopts  -- | Generate the ConfigCache getConfigCache :: MonadIO m-               => ExecuteEnv -> Task -> [Text]+               => ExecuteEnv -> Task -> InstalledMap -> Bool -> Bool                -> m (Map PackageIdentifier GhcPkgId, ConfigCache)-getConfigCache ExecuteEnv {..} Task {..} extra = do+getConfigCache ExecuteEnv {..} Task {..} installedMap enableTest enableBench = do+    let extra =+            -- We enable tests if the test suite dependencies are already+            -- installed, so that we avoid unnecessary recompilation based on+            -- cabal_macros.h changes when switching between 'stack build' and+            -- 'stack test'. See:+            -- https://github.com/commercialhaskell/stack/issues/805+            case taskType of+                TTLocal lp -> concat+                    [ ["--enable-tests" | enableTest || (depsPresent installedMap $ lpTestDeps lp)]+                    , ["--enable-benchmarks" | enableBench || (depsPresent installedMap $ lpBenchDeps lp)]+                    ]+                _ -> []     idMap <- liftIO $ readTVarIO eeGhcPkgIds     let getMissing ident =             case Map.lookup ident idMap of@@ -601,7 +641,9 @@         announce         menv <- getMinimalEnvOverride         let programNames =-                if eeCabalPkgVer < $(mkVersion "1.22") then ["ghc"] else ["ghc", "ghcjs"]+                if eeCabalPkgVer < $(mkVersion "1.22")+                    then ["ghc", "ghc-pkg"]+                    else ["ghc", "ghc-pkg", "ghcjs", "ghcjs-pkg"]         exes <- forM programNames $ \name -> do             mpath <- findExecutable menv name             return $ case mpath of@@ -688,14 +730,18 @@      withCabal package pkgDir mlogFile inner = do         config <- asks getConfig-        menv <- liftIO $ configEnvOverride config EnvSettings-            { esIncludeLocals = taskLocation task == Local-            , esIncludeGhcPackagePath = False-            , esStackExe = False-            , esLocaleUtf8 = True-            }-        getGhcPath <- runOnce $ liftIO $ join $ findExecutable menv "ghc"-        getGhcjsPath <- runOnce $ liftIO $ join $ findExecutable menv "ghcjs"+        let envSettings = EnvSettings+                { esIncludeLocals = taskLocation task == Local+                , esIncludeGhcPackagePath = False+                , esStackExe = False+                , esLocaleUtf8 = True+                }+        menv <- liftIO $ configEnvOverride config envSettings+        -- When looking for ghc to build Setup.hs we want to ignore local binaries, see:+        -- https://github.com/commercialhaskell/stack/issues/1052+        menvWithoutLocals <- liftIO $ configEnvOverride config envSettings { esIncludeLocals = False }+        getGhcPath <- runOnce $ liftIO $ join $ findExecutable menvWithoutLocals "ghc"+        getGhcjsPath <- runOnce $ liftIO $ join $ findExecutable menvWithoutLocals "ghcjs"         distRelativeDir' <- distRelativeDir         esetupexehs <-             -- Avoid broken Setup.hs files causing problems for simple build@@ -711,25 +757,31 @@                                                           eeCabalPkgVer)                 packageArgs =                     case mdeps of+                        -- This branch is taken when+                        -- 'explicit-setup-deps' is requested in your+                        -- stack.yaml file.                         Just deps | explicitSetupDeps (packageName package) config ->                             -- Stack always builds with the global Cabal for various                             -- reproducibility issues.                             let depsMinusCabal                                  = map ghcPkgIdString                                  $ Set.toList-                                 $ addGlobalPackages deps eeGlobalPackages+                                 $ addGlobalPackages deps (Map.elems eeGlobalDumpPkgs)                             in                                 ( "-clear-package-db"                                 : "-global-package-db"-                                : map (("-package-db=" ++) . toFilePath) (bcoExtraDBs eeBaseConfigOpts)+                                : map (("-package-db=" ++) . toFilePathNoTrailingSep) (bcoExtraDBs eeBaseConfigOpts)                                 ) ++-                                ( ("-package-db=" ++ toFilePath (bcoSnapDB eeBaseConfigOpts))-                                : ("-package-db=" ++ toFilePath (bcoLocalDB eeBaseConfigOpts))+                                ( ("-package-db=" ++ toFilePathNoTrailingSep (bcoSnapDB eeBaseConfigOpts))+                                : ("-package-db=" ++ toFilePathNoTrailingSep (bcoLocalDB eeBaseConfigOpts))                                 : "-hide-all-packages"                                 : cabalPackageArg                                 : map ("-package-id=" ++) depsMinusCabal                                 )-                        -- This branch is debatable. It adds access to the+                        -- This branch is usually taken for builds, and+                        -- is always taken for `stack sdist`.+                        --+                        -- This approach is debatable. It adds access to the                         -- snapshot package database for Cabal. There are two                         -- possible objections:                         --@@ -747,10 +799,10 @@                               cabalPackageArg                             : "-clear-package-db"                             : "-global-package-db"-                            : map (("-package-db=" ++) . toFilePath) (bcoExtraDBs eeBaseConfigOpts)-                           ++ ["-package-db=" ++ toFilePath (bcoSnapDB eeBaseConfigOpts)]+                            : map (("-package-db=" ++) . toFilePathNoTrailingSep) (bcoExtraDBs eeBaseConfigOpts)+                           ++ ["-package-db=" ++ toFilePathNoTrailingSep (bcoSnapDB eeBaseConfigOpts)] -                setupArgs = ("--builddir=" ++ toFilePath distRelativeDir') : args+                setupArgs = ("--builddir=" ++ toFilePathNoTrailingSep distRelativeDir') : args                 runExe exeName fullArgs = do                     $logProcessRun (toFilePath exeName) fullArgs @@ -761,8 +813,8 @@                      ec <-                         liftIO $-                        withAsync (runInBase $ maybePrintBuildOutput stripTHLoading makeAbsolute LevelInfo mlogFile moutH) $ \outThreadID ->-                        withAsync (runInBase $ maybePrintBuildOutput False makeAbsolute LevelWarn mlogFile merrH) $ \errThreadID -> do+                        withAsync (runInBase $ maybePrintBuildOutput stripTHLoading makeAbsolute pkgDir LevelInfo mlogFile moutH) $ \outThreadID ->+                        withAsync (runInBase $ maybePrintBuildOutput False makeAbsolute pkgDir LevelWarn mlogFile merrH) $ \errThreadID -> do                             ec <- waitForProcess ph                             wait errThreadID                             wait outThreadID@@ -819,8 +871,8 @@                             Ghcjs -> getGhcjsPath                     runExe compilerPath $                         [ "--make"-                        , "-odir", toFilePath setupDir-                        , "-hidir", toFilePath setupDir+                        , "-odir", toFilePathNoTrailingSep setupDir+                        , "-hidir", toFilePathNoTrailingSep setupDir                         , "-i", "-i."                         ] ++ packageArgs ++                         [ toFilePath setuphs@@ -832,12 +884,12 @@                     return (outputFile, setupArgs)             runExe exeName $ (if boptsCabalVerbose eeBuildOpts then ("--verbose":) else id) fullArgs -    maybePrintBuildOutput stripTHLoading makeAbsolute level mlogFile mh =+    maybePrintBuildOutput stripTHLoading makeAbsolute pkgDir level mlogFile mh =         case mh of             Just h ->                 case mlogFile of                   Just{} -> return ()-                  Nothing -> printBuildOutput stripTHLoading makeAbsolute level h+                  Nothing -> printBuildOutput stripTHLoading makeAbsolute pkgDir level h             Nothing -> return ()  singleBuild :: M env m@@ -846,9 +898,10 @@             -> ExecuteEnv             -> Task             -> InstalledMap+            -> Bool             -- ^ Is this a final build?             -> m ()-singleBuild runInBase ac@ActionContext {..} ee@ExecuteEnv {..} task@Task {..} installedMap = do-    (allDepsMap, cache) <- getCache+singleBuild runInBase ac@ActionContext {..} ee@ExecuteEnv {..} task@Task {..} installedMap isFinalBuild = do+    (allDepsMap, cache) <- getConfigCache ee task installedMap enableTests enableBenchmarks     mprecompiled <- getPrecompiled cache     minstalled <-         case mprecompiled of@@ -863,25 +916,29 @@     pname = packageIdentifierName taskProvides     shouldHaddockPackage' = shouldHaddockPackage eeBuildOpts eeWanted pname     doHaddock package = shouldHaddockPackage' &&+                        not isFinalBuild &&                         -- Works around haddock failing on bytestring-builder since it has no modules                         -- when bytestring is new enough.                         packageHasExposedModules package -    getCache = do-        let extra =-              -- We enable tests if the test suite dependencies are already-              -- installed, so that we avoid unnecessary recompilation based on-              -- cabal_macros.h changes when switching between 'stack build' and-              -- 'stack test'. See:-              -- https://github.com/commercialhaskell/stack/issues/805-              case taskType of-                  TTLocal lp -> concat-                      [ ["--enable-tests" | depsPresent installedMap $ lpTestDeps lp]-                      , ["--enable-benchmarks" | depsPresent installedMap $ lpBenchDeps lp]-                      ]-                  _ -> []-        getConfigCache ee task extra+    buildingFinals = isFinalBuild || taskAllInOne+    enableTests = buildingFinals && any isCTest (taskComponents task)+    enableBenchmarks = buildingFinals && any isCBench (taskComponents task) +    annSuffix = if result == "" then "" else " (" <> result <> ")"+      where+        result = T.intercalate " + " $ concat $+            [ ["lib" | taskAllInOne && hasLib]+            , ["exe" | taskAllInOne && hasExe]+            , ["test" | enableTests]+            , ["bench" | enableBenchmarks]+            ]+        (hasLib, hasExe) = case taskType of+            TTLocal lp -> (packageHasLibrary (lpPackage lp), not (Set.null (exesToBuild lp)))+            -- This isn't true, but we don't want to have this info for+            -- upstream deps.+            TTUpstream{} -> (False, False)+     getPrecompiled cache =         case taskLocation task of             Snap | not shouldHaddockPackage' -> do@@ -890,7 +947,15 @@                     (configCacheDeps cache)                 case mpc of                     Nothing -> return Nothing-                    Just pc -> do+                    Just pc | maybe False+                                    (bcoSnapInstallRoot eeBaseConfigOpts `isParentOf`)+                                    (parseAbsFile =<< (pcLibrary pc)) ->+                        -- If old precompiled cache files are left around but snapshots are deleted,+                        -- it is possible for the precompiled file to refer to the very library+                        -- we're building, and if flags are changed it may try to copy the library+                        -- to itself. This check prevents that from happening.+                        return Nothing+                    Just pc | otherwise -> do                         let allM _ [] = return True                             allM f (x:xs) = do                                 b <- f x@@ -912,7 +977,18 @@                 menv' <- modifyEnvOverride menv                        $ Map.insert                             "GHC_PACKAGE_PATH"-                            (T.pack $ toFilePath $ bcoSnapDB eeBaseConfigOpts)+                            (T.pack $ toFilePathNoTrailingSep $ bcoSnapDB eeBaseConfigOpts)++                -- In case a build of the library with different flags already exists, unregister it+                -- before copying.+                catch+                    (readProcessNull Nothing menv' "ghc-pkg"+                        [ "unregister"+                        , "--force"+                        , packageIdentifierString taskProvides+                        ])+                    (\(ReadProcessException _ _ _ _) -> return ())+                 readProcessNull Nothing menv' "ghc-pkg"                     [ "register"                     , "--force"@@ -929,18 +1005,22 @@         -- Find the package in the database         wc <- getWhichCompiler         let pkgDbs = [bcoSnapDB eeBaseConfigOpts]-        mpkgid <- findGhcPkgId eeEnvOverride wc pkgDbs pname -        return $ Just $-            case mpkgid of-                Nothing -> Executable taskProvides-                Just pkgid -> Library taskProvides pkgid+        case mlib of+            Nothing -> return $ Just $ Executable taskProvides+            Just _ -> do+                mpkgid <- loadInstalledPkg eeEnvOverride wc pkgDbs eeSnapshotDumpPkgs pname++                return $ Just $+                    case mpkgid of+                        Nothing -> assert False $ Executable taskProvides+                        Just pkgid -> Library taskProvides pkgid       where         bindir = toFilePath $ bcoSnapInstallRoot eeBaseConfigOpts </> bindirSuffix      realConfigAndBuild cache allDepsMap = withSingleContext runInBase ac ee task (Just allDepsMap) Nothing         $ \package cabalfp pkgDir cabal announce console _mlogFile -> do-            _neededConfig <- ensureConfig cache pkgDir ee (announce "configure") cabal cabalfp+            _neededConfig <- ensureConfig cache pkgDir ee (announce ("configure" <> annSuffix)) cabal cabalfp              if boptsOnlyConfigure eeBuildOpts                 then return Nothing@@ -951,45 +1031,23 @@          markExeNotInstalled (taskLocation task) taskProvides         case taskType of-            TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp+            TTLocal lp -> do+                when enableTests $ unsetTestSuccess pkgDir+                writeBuildCache pkgDir $ lpNewBuildCache lp             TTUpstream _ _ -> return () -        () <- announce "build"+        () <- announce ("build" <> annSuffix)         config <- asks getConfig         extraOpts <- extraBuildOptions eeBuildOpts         preBuildTime <- modTime <$> liftIO getCurrentTime-        cabal (console && configHideTHLoading config) $-            (case taskType of-                TTLocal lp -> concat-                    [ ["build"]-                    , ["lib:" ++ packageNameString (packageName package)-                      -- TODO: get this information from target parsing instead,-                      -- which will allow users to turn off library building if-                      -- desired-                      | packageHasLibrary package]-                    , map (T.unpack . T.append "exe:") $ Set.toList $-                        case lpExeComponents lp of-                            Just exes -> exes-                            -- Build all executables in the event that no-                            -- specific list is provided (as happens with-                            -- extra-deps).-                            Nothing -> packageExes package-                    ]-                TTUpstream _ _ -> ["build"]) ++ extraOpts--        case taskType of-            TTLocal lp -> do-                (addBuildCache,warnings) <--                    addUnlistedToBuildCache-                        preBuildTime-                        (lpPackage lp)-                        (lpCabalFile lp)-                        (lpNewBuildCache lp)-                mapM_ ($logWarn . ("Warning: " <>) . T.pack . show) warnings-                unless (null addBuildCache) $-                    writeBuildCache pkgDir $-                    Map.unions (lpNewBuildCache lp : addBuildCache)-            TTUpstream _ _ -> return ()+        cabal (console && configHideTHLoading config) $ ("build" :) $ (++ extraOpts) $+            case (taskType, taskAllInOne, isFinalBuild) of+                (_, True, True) -> fail "Invariant violated: cannot have an all-in-one build that also has a final build step."+                (TTLocal lp, False, False) -> primaryComponentOptions lp+                (TTLocal lp, False, True) -> finalComponentOptions lp+                (TTLocal lp, True, False) -> primaryComponentOptions lp ++ finalComponentOptions lp+                (TTUpstream{}, _, _) -> []+        checkForUnlistedFiles taskType preBuildTime pkgDir          when (doHaddock package) $ do             announce "haddock"@@ -1009,36 +1067,30 @@             cabal False (concat [["haddock", "--html", "--hoogle", "--html-location=../$pkg-$version/"]                                 ,sourceFlag]) -        withMVar eeInstallLock $ \() -> do-            announce "install"-            cabal False ["install"]+        unless isFinalBuild $ withMVar eeInstallLock $ \() -> do+            announce "copy/register"+            cabal False ["copy"]+            when (packageHasLibrary package) $ cabal False ["register"] -        let pkgDbs =+        let (installedPkgDb, installedDumpPkgsTVar) =                 case taskLocation task of-                    Snap -> [bcoSnapDB eeBaseConfigOpts]+                    Snap ->+                         ( bcoSnapDB eeBaseConfigOpts+                         , eeSnapshotDumpPkgs )                     Local ->-                        [ bcoSnapDB eeBaseConfigOpts-                        , bcoLocalDB eeBaseConfigOpts-                        ]-        mpkgid <- findGhcPkgId eeEnvOverride wc pkgDbs (packageName package)+                        ( bcoLocalDB eeBaseConfigOpts+                        , eeLocalDumpPkgs )         let ident = PackageIdentifier (packageName package) (packageVersion package)-        mpkgid' <- case (packageHasLibrary package, mpkgid) of-            (False, _) -> assert (isNothing mpkgid) $ do+        mpkgid <- if packageHasLibrary package+            then do+                mpkgid <- loadInstalledPkg eeEnvOverride wc [installedPkgDb] installedDumpPkgsTVar (packageName package)+                case mpkgid of+                    Nothing -> throwM $ Couldn'tFindPkgId $ packageName package+                    Just pkgid -> return $ Library ident pkgid+            else do                 markExeInstalled (taskLocation task) taskProvides -- TODO unify somehow with writeFlagCache?                 return $ Executable ident-            (True, Nothing) -> throwM $ Couldn'tFindPkgId $ packageName package-            (True, Just pkgid) -> return $ Library ident pkgid -        when (doHaddock package && shouldHaddockDeps eeBuildOpts) $-            withMVar eeInstallLock $ \() ->-                copyDepHaddocks-                    eeEnvOverride-                    wc-                    eeBaseConfigOpts-                    (pkgDbs ++ [eeGlobalDB])-                    (PackageIdentifier (packageName package) (packageVersion package))-                    Set.empty-         case taskLocation task of             Snap -> writePrecompiledCache eeBaseConfigOpts taskProvides                 (configCacheOpts cache)@@ -1046,65 +1098,57 @@                 mpkgid (packageExes package)             Local -> return () -        return mpkgid'+        return mpkgid +    loadInstalledPkg menv wc pkgDbs tvar name = do+        dps <- ghcPkgDescribe name menv wc pkgDbs $ conduitDumpPackage =$ CL.consume+        case dps of+            [] -> return Nothing+            [dp] -> do+                liftIO $ atomically $ modifyTVar' tvar (Map.insert (dpGhcPkgId dp) dp)+                return $ Just (dpGhcPkgId dp)+            _ -> error "singleBuild: invariant violated: multiple results when describing installed package"++-- | Check if any unlisted files have been found, and add them to the build cache.+checkForUnlistedFiles :: M env m => TaskType -> ModTime -> Path Abs Dir -> m ()+checkForUnlistedFiles (TTLocal lp) preBuildTime pkgDir = do+    (addBuildCache,warnings) <-+        addUnlistedToBuildCache+            preBuildTime+            (lpPackage lp)+            (lpCabalFile lp)+            (lpNewBuildCache lp)+    mapM_ ($logWarn . ("Warning: " <>) . T.pack . show) warnings+    unless (null addBuildCache) $+        writeBuildCache pkgDir $+        Map.unions (lpNewBuildCache lp : addBuildCache)+checkForUnlistedFiles (TTUpstream _ _) _ _ = return ()+ -- | Determine if all of the dependencies given are installed depsPresent :: InstalledMap -> Map PackageName VersionRange -> Bool depsPresent installedMap deps = all     (\(name, range) ->         case Map.lookup name installedMap of-            Just (version, _, _) -> version `withinRange` range+            Just (_, installed) -> installedVersion installed `withinRange` range             Nothing -> False)     (Map.toList deps)  singleTest :: M env m            => (m () -> IO ())            -> TestOpts-           -> LocalPackageTB+           -> [Text]            -> ActionContext            -> ExecuteEnv            -> Task            -> InstalledMap            -> m ()-singleTest runInBase topts lptb ac ee task installedMap = do-    (allDepsMap, cache) <- getConfigCache ee task $-        case taskType task of-            TTLocal lp -> concat-                [ ["--enable-tests"]-                , ["--enable-benchmarks" | depsPresent installedMap $ lpBenchDeps lp]-                ]-            _ -> []-    withSingleContext runInBase ac ee task (Just allDepsMap) (Just "test") $ \package cabalfp pkgDir cabal announce console mlogFile -> do-        neededConfig <- ensureConfig cache pkgDir ee (announce "configure (test)") cabal cabalfp+singleTest runInBase topts testsToRun ac ee task installedMap = do+    -- FIXME: Since this doesn't use cabal, we should be able to avoid using a+    -- fullblown 'withSingleContext'.+    (allDepsMap, _cache) <- getConfigCache ee task installedMap True False+    withSingleContext runInBase ac ee task (Just allDepsMap) (Just "test") $ \package _cabalfp pkgDir _cabal announce _console mlogFile -> do         config <- asks getConfig--        testBuilt <- checkTestBuilt pkgDir--        let needBuild = neededConfig ||-                (case taskType task of-                    TTLocal lp ->-                        case lpDirtyFiles lp of-                            Just _ -> True-                            Nothing -> False-                    _ -> assert False True) ||-                not testBuilt--            needHpc = toCoverage topts--            testsToRun = Set.toList $ lptbTests lptb-            components = map (T.unpack . T.append "test:") testsToRun--        when needBuild $ do-            announce "build (test)"-            unsetTestBuilt pkgDir-            unsetTestSuccess pkgDir-            case taskType task of-                TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp-                TTUpstream _ _ -> assert False $ return ()-            extraOpts <- extraBuildOptions (eeBuildOpts ee)-            cabal (console && configHideTHLoading config) $-                "build" : (components ++ extraOpts)-            setTestBuilt pkgDir+        let needHpc = toCoverage topts          toRun <-             if toDisableRun topts@@ -1179,7 +1223,7 @@                         -- directory into the hpc work dir, for                         -- tidiness.                         when needHpc $-                            updateTixFile nameTix (packageIdentifierString (packageIdentifier package))+                            updateTixFile (packageName package) nameTix                         return $ case ec of                             ExitSuccess -> Map.empty                             _ -> Map.singleton testName $ Just ec@@ -1192,13 +1236,7 @@                             ]                         return $ Map.singleton testName Nothing -            when needHpc $ do-                wc <- getWhichCompiler-                let pkgDbs =-                        [ bcoSnapDB (eeBaseConfigOpts ee)-                        , bcoLocalDB (eeBaseConfigOpts ee)-                        ]-                generateHpcReport package testsToRun (findGhcPkgKey (eeEnvOverride ee) wc pkgDbs)+            when needHpc $ generateHpcReport pkgDir package testsToRun              bs <- liftIO $                 case mlogFile of@@ -1213,48 +1251,19 @@                 (fmap fst mlogFile)                 bs -            setTestSuccess pkgDir- singleBench :: M env m             => (m () -> IO ())             -> BenchmarkOpts-            -> LocalPackageTB             -> ActionContext             -> ExecuteEnv             -> Task             -> InstalledMap             -> m ()-singleBench runInBase beopts _lptb ac ee task installedMap = do-    (allDepsMap, cache) <- getConfigCache ee task $-        case taskType task of-            TTLocal lp -> concat-                [ ["--enable-tests" | depsPresent installedMap $ lpTestDeps lp]-                , ["--enable-benchmarks"]-                ]-            _ -> []-    withSingleContext runInBase ac ee task (Just allDepsMap) (Just "bench") $ \_package cabalfp pkgDir cabal announce console _mlogFile -> do-        neededConfig <- ensureConfig cache pkgDir ee (announce "configure (benchmarks)") cabal cabalfp--        benchBuilt <- checkBenchBuilt pkgDir--        let needBuild = neededConfig ||-                (case taskType task of-                    TTLocal lp ->-                        case lpDirtyFiles lp of-                            Just _ -> True-                            Nothing -> False-                    _ -> assert False True) ||-                not benchBuilt-        when needBuild $ do-            announce "build (benchmarks)"-            unsetBenchBuilt pkgDir-            case taskType task of-                TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp-                TTUpstream _ _ -> assert False $ return ()-            config <- asks getConfig-            extraOpts <- extraBuildOptions (eeBuildOpts ee)-            cabal (console && configHideTHLoading config) ("build" : extraOpts)-            setBenchBuilt pkgDir+singleBench runInBase beopts ac ee task installedMap = do+    -- FIXME: Since this doesn't use cabal, we should be able to avoid using a+    -- fullblown 'withSingleContext'.+    (allDepsMap, _cache) <- getConfigCache ee task installedMap False True+    withSingleContext runInBase ac ee task (Just allDepsMap) (Just "bench") $ \_package _cabalfp _pkgDir cabal announce _console _mlogFile -> do         let args = maybe []                          ((:[]) . ("--benchmark-options=" <>))                          (beoAdditionalArgs beopts)@@ -1276,10 +1285,11 @@ printBuildOutput :: (MonadIO m, MonadBaseControl IO m, MonadLogger m)                  => Bool -- ^ exclude TH loading?                  -> Bool -- ^ convert paths to absolute?+                 -> Path Abs Dir -- ^ package's root directory                  -> LogLevel                  -> Handle -> m ()-printBuildOutput excludeTHLoading makeAbsolute level outH = void $-         CB.sourceHandle outH+printBuildOutput excludeTHLoading makeAbsolute pkgDir level outH = void $+    CB.sourceHandle outH     $$ CB.lines     =$ CL.map stripCarriageReturn     =$ CL.filter (not . isTHLoading)@@ -1301,10 +1311,10 @@         mabs <-             if isValidSuffix y                 then do-                    efp <- liftIO $ tryIO $ D.canonicalizePath $ S8.unpack x+                    efp <- liftIO $ tryIO $ resolveFile pkgDir (S8.unpack x)                     case efp of                         Left _ -> return Nothing-                        Right fp -> return $ Just $ S8.pack fp+                        Right fp -> return $ Just $ S8.pack (toFilePath fp)                 else return Nothing         case mabs of             Nothing -> return bs@@ -1320,7 +1330,7 @@         guard $ S.head bs1 == _colon         (_, bs2) <- S8.readInt $ S.drop 1 bs1 -        guard $ bs2 == ":"+        guard $ (bs2 == ":" || bs2 == ": Warning:")      -- | Strip @\r@ characters from the byte vector. Used because Windows.     stripCarriageReturn :: ByteString -> ByteString@@ -1351,9 +1361,47 @@     let ddumpOpts = " -ddump-hi -ddump-to-file"     case toCoverage (boptsTestOpts bopts) of       True -> do-        hpcIndexDir <- toFilePath . (</> dotHpc) <$> hpcRelativeDir+        hpcIndexDir <- toFilePathNoTrailingSep <$> hpcRelativeDir         return ["--ghc-options", "-hpcdir " ++ hpcIndexDir ++ ddumpOpts]       False -> return ["--ghc-options", ddumpOpts]++-- Library and executable build components.+primaryComponentOptions :: LocalPackage -> [String]+primaryComponentOptions lp = concat+    [ ["lib:" ++ packageNameString (packageName (lpPackage lp))+      -- TODO: get this information from target parsing instead,+      -- which will allow users to turn off library building if+      -- desired+      | packageHasLibrary (lpPackage lp)]+    , map (T.unpack . T.append "exe:") $ Set.toList $ exesToBuild lp+    ]++exesToBuild :: LocalPackage -> Set Text+exesToBuild lp = packageExes (lpPackage lp)+    -- NOTE: Ideally we'd do something like the following code, allowing+    -- the user to control which executables get built. However, due to+    -- https://github.com/haskell/cabal/issues/2780 we must build all+    -- exes...+    --+    -- if lpWanted lp+    --     then exeComponents (lpComponents lp)+    --     -- Build all executables in the event that no+    --     -- specific list is provided (as happens with+    --     -- extra-deps).+    --     else packageExes (lpPackage lp)++-- Test-suite and benchmark build components.+finalComponentOptions :: LocalPackage -> [String]+finalComponentOptions lp =+    map (T.unpack . decodeUtf8 . renderComponent) $+    Set.toList $+    Set.filter (\c -> isCTest c || isCBench c) (lpComponents lp)++taskComponents :: Task -> Set NamedComponent+taskComponents task =+    case taskType task of+        TTLocal lp -> lpComponents lp+        TTUpstream{} -> Set.empty  -- | Take the given list of package dependencies and the contents of the global -- package database, and construct a set of installed package IDs that:
src/Stack/Build/Haddock.hs view
@@ -3,39 +3,44 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE RecordWildCards       #-}+{-# LANGUAGE ScopedTypeVariables   #-} {-# LANGUAGE TemplateHaskell       #-}  -- | Generate haddocks module Stack.Build.Haddock-    ( copyDepHaddocks-    , generateLocalHaddockIndex+    ( generateLocalHaddockIndex     , generateDepsHaddockIndex     , generateSnapHaddockIndex     , shouldHaddockPackage     , shouldHaddockDeps     ) where -import           Control.Exception              (tryJust)+import           Control.Exception              (tryJust, onException) import           Control.Monad import           Control.Monad.Catch            (MonadCatch) import           Control.Monad.IO.Class import           Control.Monad.Logger import           Control.Monad.Trans.Resource-import           Control.Monad.Writer+import qualified Data.Foldable                  as F import           Data.Function+import qualified Data.HashSet                   as HS import           Data.List+import           Data.List.Extra                (nubOrd)+import           Data.Map.Strict                (Map)+import qualified Data.Map.Strict                as Map import           Data.Maybe+import           Data.Maybe.Extra               (mapMaybeM) import           Data.Set                       (Set) import qualified Data.Set                       as Set import           Data.Text                      (Text) import qualified Data.Text                      as T+import           Data.Time                      (UTCTime) import           Path+import           Path.Extra import           Path.IO import           Prelude-import           Safe                           (maximumMay) import           Stack.Types.Build-import           Stack.GhcPkg-import           Stack.Package+import           Stack.PackageDump import           Stack.Types import           System.Directory               (getModificationTime) import qualified System.FilePath                as FP@@ -43,7 +48,12 @@ import           System.Process.Read  -- | Determine whether we should haddock for a package.-shouldHaddockPackage :: BuildOpts -> Set PackageName -> PackageName -> Bool+shouldHaddockPackage :: BuildOpts+                     -> Set PackageName  -- ^ Packages that we want to generate haddocks for+                                         -- in any case (whether or not we are going to generate+                                         -- haddocks for dependencies)+                     -> PackageName+                     -> Bool shouldHaddockPackage bopts wanted name =     if Set.member name wanted         then boptsHaddock bopts@@ -53,119 +63,90 @@ shouldHaddockDeps :: BuildOpts -> Bool shouldHaddockDeps bopts = fromMaybe (boptsHaddock bopts) (boptsHaddockDeps bopts) --- | Copy dependencies' haddocks to documentation directory.  This way, relative @../$pkg-$ver@--- links work and it's easy to upload docs to a web server or otherwise view them in a--- non-local-filesystem context. We copy instead of symlink for two reasons: (1) symlinks aren't--- reliably supported on Windows, and (2) the filesystem containing dependencies' docs may not be--- available where viewing the docs (e.g. if building in a Docker container).-copyDepHaddocks :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m, MonadBaseControl IO m)-                => EnvOverride-                -> WhichCompiler-                -> BaseConfigOpts-                -> [Path Abs Dir]-                -> PackageIdentifier-                -> Set (Path Abs Dir)-                -> m ()-copyDepHaddocks envOverride wc bco pkgDbs pkgId extraDestDirs = do-    mpkgHtmlDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs $ packageIdentifierString pkgId-    case mpkgHtmlDir of-        Nothing -> return ()-        Just (_pkgId, pkgHtmlDir) -> do-            depGhcIds <- findGhcPkgDepends envOverride wc pkgDbs $ packageIdentifierString pkgId-            forM_ depGhcIds $ copyDepWhenNeeded pkgHtmlDir-  where-    copyDepWhenNeeded pkgHtmlDir depGhcId = do-        mDepOrigDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs $ ghcPkgIdString depGhcId-        case mDepOrigDir of-            Nothing -> return ()-            Just (depId, depOrigDir) -> do-                let extraDestDirs' =-                        -- Parent test ensures we don't try to copy docs to global locations-                        if bcoSnapInstallRoot bco `isParentOf` pkgHtmlDir ||-                           bcoLocalInstallRoot bco `isParentOf` pkgHtmlDir-                            then Set.insert (parent pkgHtmlDir) extraDestDirs-                            else extraDestDirs-                copyWhenNeeded extraDestDirs' depId depOrigDir-    copyWhenNeeded destDirs depId depOrigDir = do-        depRelDir <- parseRelDir (packageIdentifierString depId)-        copied <- forM (Set.toList destDirs) $ \destDir -> do-            let depCopyDir = destDir </> depRelDir-            if depCopyDir == depOrigDir-                then return False-                else do-                    needCopy <- getNeedCopy depOrigDir depCopyDir-                    when needCopy $ doCopy depOrigDir depCopyDir-                    return needCopy-        when (or copied) $-            copyDepHaddocks envOverride wc bco pkgDbs depId destDirs-    getNeedCopy depOrigDir depCopyDir = do-        let depOrigIndex = haddockIndexFile depOrigDir-            depCopyIndex = haddockIndexFile depCopyDir-        depOrigExists <- fileExists depOrigIndex-        depCopyExists <- fileExists depCopyIndex-        case (depOrigExists, depCopyExists) of-            (False, _) -> return False-            (True, False) -> return True-            (True, True) -> do-                copyMod <- liftIO $ getModificationTime (toFilePath depCopyIndex)-                origMod <- liftIO $ getModificationTime (toFilePath depOrigIndex)-                return (copyMod <= origMod)-    doCopy depOrigDir depCopyDir = do-        removeTreeIfExists depCopyDir-        createTree depCopyDir-        copyDirectoryRecursive depOrigDir depCopyDir- -- | Generate Haddock index and contents for local packages. generateLocalHaddockIndex     :: (MonadIO m, MonadCatch m, MonadThrow m, MonadLogger m, MonadBaseControl IO m)-    => EnvOverride -> WhichCompiler -> BaseConfigOpts -> [LocalPackage] -> m ()-generateLocalHaddockIndex envOverride wc bco locals = do-    let packageIDs =-            map+    => EnvOverride+    -> WhichCompiler+    -> BaseConfigOpts+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Local package dump+    -> [LocalPackage]+    -> m ()+generateLocalHaddockIndex envOverride wc bco localDumpPkgs locals = do+    let dumpPackages =+            mapMaybe                 (\LocalPackage{lpPackage = Package{..}} ->-                      PackageIdentifier packageName packageVersion)+                    F.find+                        (\dp -> dpPackageIdent dp == PackageIdentifier packageName packageVersion)+                        localDumpPkgs)                 locals     generateHaddockIndex         "local packages"         envOverride         wc-        packageIDs+        dumpPackages         "."         (localDocDir bco)  -- | Generate Haddock index and contents for local packages and their dependencies. generateDepsHaddockIndex     :: (MonadIO m, MonadCatch m, MonadThrow m, MonadLogger m, MonadBaseControl IO m)-    => EnvOverride -> WhichCompiler -> BaseConfigOpts -> [LocalPackage] -> m ()-generateDepsHaddockIndex envOverride wc bco locals = do-    depSets <--        mapM-            (\LocalPackage{lpPackage = Package{..}} ->-                  findTransitiveGhcPkgDepends-                      envOverride-                      wc-                      [bcoSnapDB bco, bcoLocalDB bco]-                      (PackageIdentifier packageName packageVersion))-            locals+    => EnvOverride+    -> WhichCompiler+    -> BaseConfigOpts+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Global dump information+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Snapshot dump information+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Local dump information+    -> [LocalPackage]+    -> m ()+generateDepsHaddockIndex envOverride wc bco globalDumpPkgs snapshotDumpPkgs localDumpPkgs locals = do+    let deps = (mapMaybe (`lookupDumpPackage` allDumpPkgs) . nubOrd . findTransitiveDepends . mapMaybe getGhcPkgId) locals+        depDocDir = localDocDir bco </> $(mkRelDir "all")     generateHaddockIndex         "local packages and dependencies"         envOverride         wc-        (Set.toList (Set.unions depSets))+        deps         ".."-        (localDocDir bco </> $(mkRelDir "all"))+        depDocDir+  where+    getGhcPkgId :: LocalPackage -> Maybe GhcPkgId+    getGhcPkgId LocalPackage{lpPackage = Package{..}} =+        let pkgId = PackageIdentifier packageName packageVersion+            mdpPkg = F.find (\dp -> dpPackageIdent dp == pkgId) localDumpPkgs+        in fmap dpGhcPkgId mdpPkg+    findTransitiveDepends :: [GhcPkgId] -> [GhcPkgId]+    findTransitiveDepends = (`go` HS.empty) . HS.fromList+      where+        go todo checked =+            case HS.toList todo of+                [] -> HS.toList checked+                (ghcPkgId:_) ->+                    let deps =+                            case lookupDumpPackage ghcPkgId allDumpPkgs of+                                Nothing -> HS.empty+                                Just pkgDP -> HS.fromList (dpDepends pkgDP)+                        deps' = deps `HS.difference` checked+                        todo' = HS.delete ghcPkgId (deps' `HS.union` todo)+                        checked' = HS.insert ghcPkgId checked+                    in go todo' checked'+    allDumpPkgs = [localDumpPkgs, snapshotDumpPkgs, globalDumpPkgs]  -- | Generate Haddock index and contents for all snapshot packages. generateSnapHaddockIndex     :: (MonadIO m, MonadCatch m, MonadThrow m, MonadLogger m, MonadBaseControl IO m)-    => EnvOverride -> WhichCompiler -> BaseConfigOpts -> Path Abs Dir -> m ()-generateSnapHaddockIndex envOverride wc bco globalDB = do-    pkgIds <- listGhcPkgDbs envOverride wc [globalDB, bcoSnapDB bco]+    => EnvOverride+    -> WhichCompiler+    -> BaseConfigOpts+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Global package dump+    -> Map GhcPkgId (DumpPackage () ())  -- ^ Snapshot package dump+    -> m ()+generateSnapHaddockIndex envOverride wc bco globalDumpPkgs snapshotDumpPkgs =     generateHaddockIndex         "snapshot packages"         envOverride         wc-        pkgIds+        (Map.elems snapshotDumpPkgs ++ Map.elems globalDumpPkgs)         "."         (snapDocDir bco) @@ -175,56 +156,89 @@     => Text     -> EnvOverride     -> WhichCompiler-    -> [PackageIdentifier]+    -> [DumpPackage () ()]     -> FilePath     -> Path Abs Dir     -> m ()-generateHaddockIndex descr envOverride wc packageIDs docRelDir destDir = do+generateHaddockIndex descr envOverride wc dumpPackages docRelFP destDir = do     createTree destDir-    interfaceOpts <- liftIO $ fmap catMaybes (mapM toInterfaceOpt packageIDs)-    case maximumMay (map snd interfaceOpts) of-        Nothing -> return ()-        Just maxInterfaceModTime -> do-            eindexModTime <--                liftIO $-                tryJust (guard . isDoesNotExistError) $-                getModificationTime (toFilePath (haddockIndexFile destDir))-            let needUpdate =-                    case eindexModTime of-                        Left _ -> True-                        Right indexModTime ->-                            indexModTime < maxInterfaceModTime-            when-                needUpdate $-                do $logInfo-                       ("Updating Haddock index for " <> descr <> " in\n" <>-                        T.pack (toFilePath (haddockIndexFile destDir)))-                   readProcessNull-                       (Just destDir)-                       envOverride-                       (haddockExeName wc)-                       (["--gen-contents", "--gen-index"] ++ concatMap fst interfaceOpts)+    interfaceOpts <- (liftIO . fmap nubOrd . mapMaybeM toInterfaceOpt) dumpPackages+    unless (null interfaceOpts) $ do+        let destIndexFile = toFilePath (haddockIndexFile destDir)+        eindexModTime <- liftIO (tryGetModificationTime destIndexFile)+        let needUpdate =+                case eindexModTime of+                    Left _ -> True+                    Right indexModTime ->+                        or [mt > indexModTime | (_,mt,_,_) <- interfaceOpts]+        when needUpdate $ do+            $logInfo+                (T.concat ["Updating Haddock index for ", descr, " in\n",+                           T.pack destIndexFile])+            liftIO (mapM_ copyPkgDocs interfaceOpts)+            readProcessNull+                (Just destDir)+                envOverride+                (haddockExeName wc)+                (["--gen-contents", "--gen-index"] ++ [x | (xs,_,_,_) <- interfaceOpts, x <- xs])   where-    toInterfaceOpt pid@(PackageIdentifier name _) = do-        let interfaceRelFile =-                docRelDir FP.</> packageIdentifierString pid FP.</>-                packageNameString name FP.<.>-                "haddock"-            interfaceAbsFile = toFilePath destDir FP.</> interfaceRelFile-        einterfaceModTime <--            tryJust (guard . isDoesNotExistError) $-            getModificationTime interfaceAbsFile-        return $-            case einterfaceModTime of-                Left _ -> Nothing-                Right interfaceModTime ->-                    Just-                        ( [ "-i"-                          , concat-                                [ docRelDir FP.</> packageIdentifierString pid-                                , ","-                                , interfaceRelFile]]-                        , interfaceModTime)+    toInterfaceOpt :: DumpPackage a b -> IO (Maybe ([String], UTCTime, Path Abs File, Path Abs File))+    toInterfaceOpt DumpPackage {..} = do+        case dpHaddockInterfaces of+            [] -> return Nothing+            srcInterfaceFP:_ -> do+                srcInterfaceAbsFile <- parseCollapsedAbsFile srcInterfaceFP+                let (PackageIdentifier name _) = dpPackageIdent+                    destInterfaceRelFP =+                        docRelFP FP.</>+                        packageIdentifierString dpPackageIdent FP.</>+                        (packageNameString name FP.<.> "haddock")+                destInterfaceAbsFile <- parseCollapsedAbsFile (toFilePath destDir FP.</> destInterfaceRelFP)+                esrcInterfaceModTime <- tryGetModificationTime (toFilePath srcInterfaceAbsFile)+                return $+                    case esrcInterfaceModTime of+                        Left _ -> Nothing+                        Right srcInterfaceModTime ->+                            Just+                                ( [ "-i"+                                  , concat+                                        [ docRelFP FP.</> packageIdentifierString dpPackageIdent+                                        , ","+                                        , destInterfaceRelFP ]]+                                , srcInterfaceModTime+                                , srcInterfaceAbsFile+                                , destInterfaceAbsFile )+    tryGetModificationTime :: FilePath -> IO (Either () UTCTime)+    tryGetModificationTime = tryJust (guard . isDoesNotExistError) . getModificationTime+    copyPkgDocs :: (a, UTCTime, Path Abs File, Path Abs File) -> IO ()+    copyPkgDocs (_,srcInterfaceModTime,srcInterfaceAbsFile,destInterfaceAbsFile) = do+        -- Copy dependencies' haddocks to documentation directory.  This way, relative @../$pkg-$ver@+        -- links work and it's easy to upload docs to a web server or otherwise view them in a+        -- non-local-filesystem context. We copy instead of symlink for two reasons: (1) symlinks+        -- aren't reliably supported on Windows, and (2) the filesystem containing dependencies'+        -- docs may not be available where viewing the docs (e.g. if building in a Docker+        -- container).+        edestInterfaceModTime <- tryGetModificationTime (toFilePath destInterfaceAbsFile)+        case edestInterfaceModTime of+            Left _ -> doCopy+            Right destInterfaceModTime+                | destInterfaceModTime < srcInterfaceModTime -> doCopy+                | otherwise -> return ()+      where+        doCopy = do+            removeTreeIfExists destHtmlAbsDir+            createTree destHtmlAbsDir+            onException+                (copyDirectoryRecursive (parent srcInterfaceAbsFile) destHtmlAbsDir)+                (removeTreeIfExists destHtmlAbsDir)+        destHtmlAbsDir = parent destInterfaceAbsFile++-- | Find first DumpPackage matching the GhcPkgId+lookupDumpPackage :: GhcPkgId+                  -> [Map GhcPkgId (DumpPackage () ())]+                  -> Maybe (DumpPackage () ())+lookupDumpPackage ghcPkgId dumpPkgs =+    listToMaybe $ mapMaybe (Map.lookup ghcPkgId) dumpPkgs  -- | Path of haddock index file. haddockIndexFile :: Path Abs Dir -> Path Abs File
src/Stack/Build/Installed.hs view
@@ -12,7 +12,6 @@     ) where  import           Control.Applicative-import           Control.Arrow                ((&&&)) import           Control.Monad import           Control.Monad.Catch          (MonadCatch, MonadMask) import           Control.Monad.IO.Class@@ -28,11 +27,15 @@ import qualified Data.Map.Strict              as M import qualified Data.Map.Strict              as Map import           Data.Maybe+import           Data.Maybe.Extra             (mapMaybeM)+import           Data.Monoid+import qualified Data.Text                    as T import           Network.HTTP.Client.Conduit  (HasHttpManager) import           Path import           Prelude                      hiding (FilePath, writeFile) import           Stack.Build.Cache import           Stack.Types.Build+import           Stack.Types.Version import           Stack.Constants import           Stack.GhcPkg import           Stack.PackageDump@@ -41,13 +44,6 @@  type M env m = (MonadIO m,MonadReader env m,HasHttpManager env,HasEnvConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,MonadMask m,HasLogLevel env) -data LoadHelper = LoadHelper-    { lhId   :: !GhcPkgId-    , lhDeps :: ![GhcPkgId]-    , lhPair :: !(PackageName, (Version, InstallLocation, Installed)) -- TODO Version is now redundant and can be gleaned from Installed-    }-    deriving Show- -- | Options for 'getInstalled'. data GetInstalledOpts = GetInstalledOpts     { getInstalledProfiling :: !Bool@@ -63,7 +59,8 @@              -> Map PackageName pii -- ^ does not contain any installed information              -> m ( InstalledMap                   , [DumpPackage () ()] -- globally installed-                  , Map GhcPkgId PackageIdentifier -- locally installed+                  , [DumpPackage () ()] -- snapshot installed+                  , [DumpPackage () ()] -- locally installed                   ) getInstalled menv opts sourceMap = do     snapDBPath <- packageDatabaseDeps@@ -79,14 +76,14 @@      let loadDatabase' = loadDatabase menv opts mcache sourceMap -    (installedLibs0, globalInstalled) <- loadDatabase' Nothing []+    (installedLibs0, globalDumpPkgs) <- loadDatabase' Nothing []     (installedLibs1, _extraInstalled) <--      (foldM (\lhs' pkgdb -> do-        lhs'' <- loadDatabase' (Just (ExtraGlobal, pkgdb)) (fst lhs')-        return lhs'') ((installedLibs0, globalInstalled)) extraDBPaths)-    (installedLibs2, _snapInstalled) <-+      (foldM (\lhs' pkgdb ->+        loadDatabase' (Just (ExtraGlobal, pkgdb)) (fst lhs')+        ) (installedLibs0, globalDumpPkgs) extraDBPaths)+    (installedLibs2, snapshotDumpPkgs) <-         loadDatabase' (Just (InstalledTo Snap, snapDBPath)) installedLibs1-    (installedLibs3, localInstalled) <-+    (installedLibs3, localDumpPkgs) <-         loadDatabase' (Just (InstalledTo Local, localDBPath)) installedLibs2     let installedLibs = M.fromList $ map lhPair installedLibs3 @@ -107,7 +104,7 @@                      | otherwise -> m           where-            m = Map.singleton name (version, loc, Executable $ PackageIdentifier name version)+            m = Map.singleton name (loc, Executable $ PackageIdentifier name version)     exesSnap <- getInstalledExes Snap     exesLocal <- getInstalledExes Local     let installedMap = Map.unions@@ -117,8 +114,9 @@             ]      return ( installedMap-           , globalInstalled-           , Map.fromList $ map (dpGhcPkgId &&& dpPackageIdent) localInstalled+           , globalDumpPkgs+           , snapshotDumpPkgs+           , localDumpPkgs            )  -- | Outputs both the modified InstalledMap and the Set of all installed packages in this database@@ -136,9 +134,10 @@              -> m ([LoadHelper], [DumpPackage () ()]) loadDatabase menv opts mcache sourceMap mdb lhs0 = do     wc <- getWhichCompiler-    (lhs1, dps) <- ghcPkgDump menv wc (fmap snd (maybeToList mdb))+    (lhs1', dps) <- ghcPkgDump menv wc (fmap snd (maybeToList mdb))                 $ conduitDumpPackage =$ sink-+    let ghcjsHack = wc == Ghcjs && isNothing mdb+    lhs1 <- mapMaybeM (processLoadResult mdb ghcjsHack) lhs1'     let lhs = pruneDeps             id             lhId@@ -159,14 +158,65 @@             -- Just an optimization to avoid calculating the haddock             -- values when they aren't necessary             _ -> CL.map (\dp -> dp { dpHaddock = False })+    mloc = fmap fst mdb     sinkDP = conduitProfilingCache           =$ conduitHaddockCache-          =$ CL.mapMaybe (isAllowed opts mcache sourceMap (fmap fst mdb))+          =$ CL.map (\dp -> (isAllowed opts mcache sourceMap mloc dp, toLoadHelper mloc dp))           =$ CL.consume     sink = getZipSink $ (,)         <$> ZipSink sinkDP         <*> ZipSink CL.consume +processLoadResult :: MonadLogger m+                  => Maybe (InstalledPackageLocation, Path Abs Dir)+                  -> Bool+                  -> (Allowed, LoadHelper)+                  -> m (Maybe LoadHelper)+processLoadResult _ _ (Allowed, lh) = return (Just lh)+processLoadResult _ True (WrongVersion actual wanted, lh)+    -- Allow some packages in the ghcjs global DB to have the wrong+    -- versions.  Treat them as wired-ins by setting deps to [].+    | fst (lhPair lh) `HashSet.member` ghcjsBootPackages = do+        $logWarn $ T.concat+            [ "Ignoring that the GHCJS boot package \""+            , packageNameText (fst (lhPair lh))+            , "\" has a different version, "+            , versionText actual+            , ", than the resolver's wanted version, "+            , versionText wanted+            ]+        return (Just lh)+processLoadResult mdb _ (reason, lh) = do+    $logDebug $ T.concat $+        [ "Ignoring package "+        , packageNameText (fst (lhPair lh))+        ] +++        (maybe [] (\db -> [", from ", T.pack (show db), ","]) mdb) +++        [ " due to "+        , case reason of+            Allowed -> " the impossible?!?!"+            NeedsProfiling -> " it needing profiling."+            NeedsHaddock -> " it needing haddocks."+            UnknownPkg -> " it being unknown to the resolver / extra-deps."+            WrongLocation mloc loc -> " wrong location: " <> T.pack (show (mloc, loc))+            WrongVersion actual wanted -> T.concat+                [ " wanting version "+                , versionText wanted+                , " instead of "+                , versionText actual+                ]+        ]+    return Nothing++data Allowed+    = Allowed+    | NeedsProfiling+    | NeedsHaddock+    | UnknownPkg+    | WrongLocation (Maybe InstalledPackageLocation) InstallLocation+    | WrongVersion Version Version+    deriving (Eq, Show)+ -- | Check if a can be included in the set of installed packages or not, based -- on the package selections made by the user. This does not perform any -- dirtiness or flag change checks.@@ -176,54 +226,62 @@           -> Map PackageName pii           -> Maybe InstalledPackageLocation           -> DumpPackage Bool Bool-          -> Maybe LoadHelper+          -> Allowed isAllowed opts mcache sourceMap mloc dp     -- Check that it can do profiling if necessary-    | getInstalledProfiling opts && isJust mcache && not (dpProfiling dp) = Nothing+    | getInstalledProfiling opts && isJust mcache && not (dpProfiling dp) = NeedsProfiling     -- Check that it has haddocks if necessary-    | getInstalledHaddock opts && isJust mcache && not (dpHaddock dp) = Nothing-    | toInclude = Just LoadHelper-        { lhId = gid-        , lhDeps =-            -- We always want to consider the wired in packages as having all-            -- of their dependencies installed, since we have no ability to-            -- reinstall them. This is especially important for using different-            -- minor versions of GHC, where the dependencies of wired-in-            -- packages may change slightly and therefore not match the-            -- snapshot.-            if name `HashSet.member` wiredInPackages-                then []-                else dpDepends dp-        , lhPair = (name, (version, toPackageLocation mloc, Library ident gid))-        }-    | otherwise = Nothing-  where-    toPackageLocation :: Maybe InstalledPackageLocation -> InstallLocation-    toPackageLocation Nothing = Snap-    toPackageLocation (Just ExtraGlobal) = Snap-    toPackageLocation (Just (InstalledTo loc)) = loc--    toInclude =+    | getInstalledHaddock opts && isJust mcache && not (dpHaddock dp) = NeedsHaddock+    | otherwise =         case Map.lookup name sourceMap of             Nothing ->                 case mloc of                     -- The sourceMap has nothing to say about this global                     -- package, so we can use it-                    Nothing -> True-                    Just ExtraGlobal -> True+                    Nothing -> Allowed+                    Just ExtraGlobal -> Allowed                     -- For non-global packages, don't include unknown packages.                     -- See:                     -- https://github.com/commercialhaskell/stack/issues/292-                    Just _ -> False--            Just pii ->-                version == piiVersion pii -- only accept the desired version-                && checkLocation (piiLocation pii)-+                    Just _ -> UnknownPkg+            Just pii+                | not (checkLocation (piiLocation pii)) -> WrongLocation mloc (piiLocation pii)+                | version /= piiVersion pii -> WrongVersion version (piiVersion pii)+                | otherwise -> Allowed+  where+    PackageIdentifier name version = dpPackageIdent dp     -- Ensure that the installed location matches where the sourceMap says it     -- should be installed     checkLocation Snap = mloc /= Just (InstalledTo Local) -- we can allow either global or snap     checkLocation Local = mloc == Just (InstalledTo Local) || mloc == Just ExtraGlobal -- 'locally' installed snapshot packages can come from extra dbs +data LoadHelper = LoadHelper+    { lhId   :: !GhcPkgId+    , lhDeps :: ![GhcPkgId]+    , lhPair :: !(PackageName, (InstallLocation, Installed))+    }+    deriving Show++toLoadHelper :: Maybe InstalledPackageLocation -> DumpPackage Bool Bool -> LoadHelper+toLoadHelper mloc dp = LoadHelper+    { lhId = gid+    , lhDeps =+        -- We always want to consider the wired in packages as having all+        -- of their dependencies installed, since we have no ability to+        -- reinstall them. This is especially important for using different+        -- minor versions of GHC, where the dependencies of wired-in+        -- packages may change slightly and therefore not match the+        -- snapshot.+        if name `HashSet.member` wiredInPackages+            then []+            else dpDepends dp+    , lhPair = (name, (toPackageLocation mloc, Library ident gid))+    }+  where     gid = dpGhcPkgId dp-    ident@(PackageIdentifier name version) = dpPackageIdent dp+    ident@(PackageIdentifier name _) = dpPackageIdent dp++toPackageLocation :: Maybe InstalledPackageLocation -> InstallLocation+toPackageLocation Nothing = Snap+toPackageLocation (Just ExtraGlobal) = Snap+toPackageLocation (Just (InstalledTo loc)) = loc
src/Stack/Build/Source.hs view
@@ -60,7 +60,6 @@                                   parseCustomMiniBuildPlan) import           Stack.Constants (wiredInPackages) import           Stack.Package-import           Stack.PackageIndex import           Stack.Types  import           System.Directory@@ -80,9 +79,10 @@     bconfig <- asks getBuildConfig     rawLocals <- getLocalPackageViews     (mbp0, cliExtraDeps, targets) <- parseTargetsFromBuildOpts needTargets bopts-    menv <- getMinimalEnvOverride-    caches <- getPackageCaches menv-    let latestVersion = Map.fromListWith max $ map toTuple $ Map.keys caches+    let latestVersion =+            Map.fromListWith max $+            map toTuple $+            Map.keys (bcPackageCaches bconfig)      -- Extend extra-deps to encompass targets requested on the command line     -- that are not in the snapshot.@@ -94,6 +94,7 @@      locals <- mapM (loadLocalPackage bopts targets) $ Map.toList rawLocals     checkFlagsUsed bopts locals extraDeps0 (mbpPackages mbp0)+    checkComponentsBuildable locals      let         -- loadLocals returns PackageName (foo) and PackageIdentifier (bar-1.2.3) targets separately;@@ -115,7 +116,7 @@         -- the snapshot         extraDeps2 = Map.union             (Map.map (\v -> (v, Map.empty)) extraDeps0)-            (Map.map (\mpi -> (mpiVersion mpi, mpiFlags mpi)) extraDeps1)+            (Map.map (mpiVersion &&& mpiFlags) extraDeps1)          -- Overwrite any flag settings with those from the config file         extraDeps3 = Map.mapWithKey@@ -230,7 +231,7 @@         (warnings,gpkg) <- readPackageUnresolved cabalfp         mapM_ (printCabalFileWarning cabalfp) warnings         let cabalID = package $ packageDescription gpkg-            name = fromCabalPackageName $ pkgName $ cabalID+            name = fromCabalPackageName $ pkgName cabalID         checkCabalFileName name cabalfp         let lpv = LocalPackageView                 { lpvVersion = fromCabalVersion $ pkgVersion cabalID@@ -314,6 +315,12 @@                 Just STUnknown -> assert False mempty                 Nothing -> mempty +        toComponents e t b = Set.unions+            [ Set.map CExe e+            , Set.map CTest t+            , Set.map CBench b+            ]+         btconfig = config             { packageConfigEnableTests = not $ Set.null tests             , packageConfigEnableBenchmarks = not $ Set.null benches@@ -329,45 +336,46 @@          btpkg             | Set.null tests && Set.null benches = Nothing-            | otherwise = Just $ LocalPackageTB-                { lptbPackage = resolvePackage btconfig gpkg-                , lptbTests = tests-                , lptbBenches = benches-                }+            | otherwise = Just (resolvePackage btconfig gpkg)         testpkg = resolvePackage testconfig gpkg         benchpkg = resolvePackage benchconfig gpkg     mbuildCache <- tryGetBuildCache $ lpvRoot lpv     (files,_) <- getPackageFilesSimple pkg (lpvCabalFP lpv)++    -- Filter out the cabal_macros file to avoid spurious recompilations+    let filteredFiles = Set.filter ((/= $(mkRelFile "cabal_macros.h")) . filename) files+     (dirtyFiles, newBuildCache) <- checkBuildCache         (fromMaybe Map.empty mbuildCache)-        (map toFilePath $ Set.toList files)+        (map toFilePath $ Set.toList filteredFiles)      return LocalPackage         { lpPackage = pkg-        , lpTestDeps = packageDeps $ testpkg-        , lpBenchDeps = packageDeps $ benchpkg-        , lpExeComponents =-            case mtarget of-                Nothing -> Nothing-                Just _ -> Just exes+        , lpTestDeps = packageDeps testpkg+        , lpBenchDeps = packageDeps benchpkg         , lpTestBench = btpkg         , lpFiles = files         , lpDirtyFiles =             if not (Set.null dirtyFiles) || boptsForceDirty bopts                 then let tryStripPrefix y =-                            case stripPrefix (toFilePath $ lpvRoot lpv) y of-                                Nothing -> y-                                Just z -> z+                          fromMaybe y (stripPrefix (toFilePath $ lpvRoot lpv) y)                       in Just $ Set.map tryStripPrefix dirtyFiles                 else Nothing         , lpNewBuildCache = newBuildCache         , lpCabalFile = lpvCabalFP lpv         , lpDir = lpvRoot lpv-        , lpComponents = Set.unions-            [ Set.map CExe exes-            , Set.map CTest tests-            , Set.map CBench benches-            ]+        , lpWanted = isJust mtarget+        , lpComponents = toComponents exes tests benches+        -- TODO: refactor this so that it's easier to be sure that these+        -- components are indeed unbuildable.+        --+        -- The reasoning here is that if the STLocalComps specification+        -- made it through component parsing, but the components aren't+        -- present, then they must not be buildable.+        , lpUnbuildable = toComponents+            (exes `Set.difference` packageExes pkg)+            (tests `Set.difference` packageTests pkg)+            (benches `Set.difference` packageBenchmarks pkg)         }  -- | Ensure that the flags specified in the stack.yaml file and on the command@@ -421,9 +429,9 @@            -> PackageName            -> Map FlagName Bool localFlags boptsflags bconfig name = Map.unions-    [ fromMaybe Map.empty $ Map.lookup (Just name) $ boptsflags-    , fromMaybe Map.empty $ Map.lookup Nothing $ boptsflags-    , fromMaybe Map.empty $ Map.lookup name $ bcFlags bconfig+    [ Map.findWithDefault Map.empty (Just name) boptsflags+    , Map.findWithDefault Map.empty Nothing boptsflags+    , Map.findWithDefault Map.empty name (bcFlags bconfig)     ]  -- | Add in necessary packages to extra dependencies@@ -464,7 +472,7 @@                 -> [FilePath] -- ^ files in package                 -> m (Set FilePath, Map FilePath FileCacheInfo) checkBuildCache oldCache files = liftIO $ do-    (dirtyFiles, m) <- fmap mconcat $ mapM go files+    (dirtyFiles, m) <- mconcat <$> mapM go files     return (dirtyFiles, m)   where     go fp = do@@ -553,3 +561,13 @@             , fciSize = size             , fciHash = toBytes (digest :: Digest SHA256)             }++checkComponentsBuildable :: MonadThrow m => [LocalPackage] -> m ()+checkComponentsBuildable lps =+    unless (null unbuildable) $ throwM $ SomeTargetsNotBuildable unbuildable+  where+    unbuildable =+        [ (packageName (lpPackage lp), c)+        | lp <- lps+        , c <- Set.toList (lpUnbuildable lp)+        ]
src/Stack/Build/Target.hs view
@@ -145,7 +145,7 @@               -> Either Text ((RawInput, RawTarget 'NoIdents), Map PackageName Version) resolveIdents _ _ _ (ri, RTPackageComponent x y) = Right ((ri, RTPackageComponent x y), Map.empty) resolveIdents _ _ _ (ri, RTComponent x) = Right ((ri, RTComponent x), Map.empty)-resolveIdents _ _ _ (ri, RTPackage x) = Right $ ((ri, RTPackage x), Map.empty)+resolveIdents _ _ _ (ri, RTPackage x) = Right ((ri, RTPackage x), Map.empty) resolveIdents snap extras locals (ri, RTPackageIdentifier (PackageIdentifier name version)) =     case mfound of         Just (foundPlace, foundVersion) | foundVersion /= version -> Left $ T.pack $ concat
src/Stack/BuildPlan.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE TemplateHaskell    #-} {-# LANGUAGE TupleSections      #-} - -- | Resolving a build plan for a set of packages in a given Stackage -- snapshot. @@ -76,9 +75,7 @@ import           Prelude -- Fix AMP warning import           Stack.Constants import           Stack.Fetch-import           Stack.GhcPkg import           Stack.Package-import           Stack.PackageIndex import           Stack.Types import           Stack.Types.StackT import           System.Directory (canonicalizePath)@@ -177,21 +174,22 @@ -- -- This may fail if a target package is not present in the @BuildPlan@. resolveBuildPlan :: (MonadThrow m, MonadIO m, MonadReader env m, HasBuildConfig env, MonadLogger m, HasHttpManager env, MonadBaseControl IO m,MonadCatch m)-                 => EnvOverride-                 -> MiniBuildPlan+                 => MiniBuildPlan                  -> (PackageName -> Bool) -- ^ is it shadowed by a local package?                  -> Map PackageName (Set PackageName) -- ^ required packages, and users of it                  -> m ( Map PackageName (Version, Map FlagName Bool)                       , Map PackageName (Set PackageName)                       )-resolveBuildPlan menv mbp isShadowed packages+resolveBuildPlan mbp isShadowed packages     | Map.null (rsUnknown rs) && Map.null (rsShadowed rs) = return (rsToInstall rs, rsUsedBy rs)     | otherwise = do-        cache <- getPackageCaches menv-        let maxVer = Map.fromListWith max $ map toTuple $ Map.keys cache+        bconfig <- asks getBuildConfig+        let maxVer =+                Map.fromListWith max $+                map toTuple $+                Map.keys (bcPackageCaches bconfig)             unknown = flip Map.mapWithKey (rsUnknown rs) $ \ident x ->                 (Map.lookup ident maxVer, x)-        bconfig <- asks getBuildConfig         throwM $ UnknownPackages             (bcStackYaml bconfig)             unknown@@ -434,8 +432,7 @@     -> m MiniBuildPlan loadMiniBuildPlan name = do     path <- configMiniBuildPlanCache name-    let fp = toFilePath path-    taggedDecodeOrLoad fp $ liftM buildPlanFixes $ do+    taggedDecodeOrLoad path $ liftM buildPlanFixes $ do         bp <- loadBuildPlan name         toMiniBuildPlan             (siCompilerVersion $ bpSystemInfo bp)@@ -472,7 +469,7 @@     env <- ask     let stackage = getStackRoot env     file' <- parseRelFile $ T.unpack file-    let fp = stackage </> $(mkRelDir "build-plan") </> file'+    let fp = buildPlanDir stackage </> file'     $logDebug $ "Decoding build plan from: " <> T.pack (toFilePath fp)     eres <- liftIO $ decodeFileEither $ toFilePath fp     case eres of@@ -693,7 +690,7 @@     customPlanDir <- getCustomPlanDir     let binaryFP = customPlanDir </> $(mkRelDir "bin") </> binaryFilename -    taggedDecodeOrLoad (toFilePath binaryFP) $ do+    taggedDecodeOrLoad binaryFP $ do         cs <- either throwM return $ decodeEither' yamlBS         let addFlags :: PackageIdentifier -> (PackageName, (Version, Map FlagName Bool))             addFlags (PackageIdentifier name ver) =
+ src/Stack/Clean.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE DeriveDataTypeable #-}++-- | Clean a project.+module Stack.Clean+    (clean+    ,CleanOpts(..)+    ,StackCleanException(..)+    ) where++import           Control.Exception (Exception)+import           Control.Monad.Catch (MonadThrow,throwM)+import           Control.Monad.IO.Class (MonadIO)+import           Control.Monad.Logger (MonadLogger)+import           Control.Monad.Reader (MonadReader)+import           Data.Foldable (forM_)+import           Data.List ((\\),intercalate)+import qualified Data.Map.Strict as Map+import           Data.Maybe (mapMaybe)+import           Data.Typeable (Typeable)+import           Path.IO (removeTreeIfExists)+import           Stack.Build.Source (getLocalPackageViews)+import           Stack.Build.Target (LocalPackageView(..))+import           Stack.Constants (distDirFromDir)+import           Stack.Types (HasEnvConfig,PackageName)+++-- | Reset the build, i.e. remove the @dist@ directory+-- (for example @.stack-work\/dist\/x84_64-linux\/Cabal-1.22.4.0@)+-- for all targets.+--+-- Throws 'StackCleanException'.+clean+    :: (MonadThrow m, MonadIO m, MonadReader env m, HasEnvConfig env, MonadLogger m)+    => CleanOpts+    -> m ()+clean (CleanOpts targets) = do+    locals <- getLocalPackageViews+    case targets \\ Map.keys locals of+        [] -> do+            let lpvs =+                    if null targets+                        then Map.elems locals -- default to cleaning all local packages+                        else mapMaybe (`Map.lookup` locals) targets+            forM_ lpvs $ \(LocalPackageView{lpvRoot = pkgDir},_) -> do+                distDir <- distDirFromDir pkgDir+                removeTreeIfExists distDir+        pkgs -> throwM (NonLocalPackages pkgs)++-- | Options for cleaning a project.+newtype CleanOpts = CleanOpts+    { cleanOptsTargets :: [PackageName]+    -- ^ Names of the packages to clean.+    -- If the list is empty, every local package should be cleaned.+    }++-- | Exceptions during cleanup.+newtype StackCleanException+    = NonLocalPackages [PackageName]+    deriving (Typeable)++instance Show StackCleanException where+    show (NonLocalPackages pkgs) =+        "The following packages are not part of this project: " +++        intercalate ", " (map show pkgs)++instance Exception StackCleanException
src/Stack/Config.hs view
@@ -34,9 +34,8 @@ import qualified Codec.Compression.GZip as GZip import           Control.Applicative import           Control.Arrow ((***))-import           Control.Exception (IOException) import           Control.Monad-import           Control.Monad.Catch (Handler(..), MonadCatch, MonadThrow, catches, throwM)+import           Control.Monad.Catch (MonadThrow, MonadCatch, catchAll, throwM) import           Control.Monad.IO.Class import           Control.Monad.Logger hiding (Loc) import           Control.Monad.Reader (MonadReader, ask, runReaderT)@@ -62,14 +61,16 @@ import           Network.HTTP.Download (download) import           Options.Applicative (Parser, strOption, long, help) import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO import qualified Paths_stack as Meta import           Safe (headMay) import           Stack.BuildPlan-import           Stack.Constants import           Stack.Config.Docker+import           Stack.Constants import qualified Stack.Image as Image import           Stack.Init+import           Stack.PackageIndex import           Stack.Types import           Stack.Types.Internal import           System.Directory (getAppUserDataDirectory, createDirectoryIfMissing, canonicalizePath)@@ -86,10 +87,7 @@     let mlts = do             (x,y) <- listToMaybe (reverse (IntMap.toList (snapshotsLts snapshots)))             return (LTS x y)-        snap =-            case mlts of-                Nothing -> Nightly (snapshotsNightly snapshots)-                Just lts -> lts+        snap = fromMaybe (Nightly (snapshotsNightly snapshots)) mlts     return (ResolverSnapshot snap)  -- Interprets ConfigMonoid options.@@ -97,7 +95,7 @@     :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader env m, HasHttpManager env)     => Path Abs Dir -- ^ stack root, e.g. ~/.stack     -> Path Abs File -- ^ user config file path, e.g. ~/.stack/config.yaml-    -> Maybe Project+    -> Maybe (Project, Path Abs File)     -> ConfigMonoid     -> m Config configFromConfigMonoid configStackRoot configUserConfigPath mproject configMonoid@ConfigMonoid{..} = do@@ -145,7 +143,7 @@           configCompilerCheck = fromMaybe MatchMinor configMonoidCompilerCheck -     configDocker <- dockerOptsFromMonoid mproject configStackRoot configMonoidDockerOpts+     configDocker <- dockerOptsFromMonoid (fmap fst mproject) configStackRoot configMonoidDockerOpts       rawEnv <- liftIO getEnvironment      origEnv <- mkEnvOverride configPlatform@@ -171,11 +169,17 @@                  localDir <- liftIO (getAppUserDataDirectory "local") >>= parseAbsDir                  return $ localDir </> $(mkRelDir "bin")              Just userPath ->-                 (liftIO $ canonicalizePath userPath >>= parseAbsDir)-                 `catches`-                 [Handler (\(_ :: IOException) -> throwM $ NoSuchDirectory userPath)-                 ,Handler (\(_ :: PathParseException) -> throwM $ NoSuchDirectory userPath)-                 ]+                 (case mproject of+                     -- Not in a project+                     Nothing -> parseRelAsAbsDir userPath+                     -- Resolves to the project dir and appends the user path if it is relative+                     Just (_, configYaml) -> resolveDir (parent configYaml) userPath)+                 -- TODO: Either catch specific exceptions or add a+                 -- parseRelAsAbsDirMaybe utility and use it along with+                 -- resolveDirMaybe.+                 `catchAll`+                 const (throwM (NoSuchDirectory userPath))+      configJobs <-         case configMonoidJobs of             Nothing -> liftIO getNumProcessors@@ -191,6 +195,7 @@          configExplicitSetupDeps = configMonoidExplicitSetupDeps          configRebuildGhcOptions = fromMaybe False configMonoidRebuildGhcOptions          configApplyGhcOptions = fromMaybe AGOLocals configMonoidApplyGhcOptions+         configAllowNewer = fromMaybe False configMonoidAllowNewer       return Config {..} @@ -249,7 +254,7 @@     :: (MonadIO m, HasHttpManager a, MonadReader a m, MonadBaseControl IO m, MonadCatch m, MonadLogger m)     => Config -> m MiniConfig loadMiniConfig config = do-    menv <- liftIO $ (configEnvOverride config) minimalEnvSettings+    menv <- liftIO $ configEnvOverride config minimalEnvSettings     manager <- getHttpManager <$> ask     ghcVariant <-         case configGHCVariant0 config of@@ -276,13 +281,14 @@                               (configMonoidDockerOpts c) {dockerMonoidDefaultEnable = False}})                 extraConfigs0     mproject <- loadProjectConfig mstackYaml-    config <- configFromConfigMonoid stackRoot userConfigPath (fmap (\(proj, _, _) -> proj) mproject) $ mconcat $+    let mproject' = (\(project, stackYaml, _) -> (project, stackYaml)) <$> mproject+    config <- configFromConfigMonoid stackRoot userConfigPath mproject' $ mconcat $         case mproject of             Nothing -> configArgs : extraConfigs             Just (_, _, projectConfig) -> configArgs : projectConfig : extraConfigs     unless (fromCabalVersion Meta.version `withinRange` configRequireStackVersion config)         (throwM (BadStackVersionException (configRequireStackVersion config)))-    return $ LoadConfig+    return LoadConfig         { lcConfig          = config         , lcLoadBuildConfig = loadBuildConfig mproject config         , lcProjectRoot     = fmap (\(_, fp, _) -> parent fp) mproject@@ -294,8 +300,9 @@                 => Maybe (Project, Path Abs File, ConfigMonoid)                 -> Config                 -> Maybe AbstractResolver -- override resolver+                -> Maybe CompilerVersion -- override compiler                 -> m BuildConfig-loadBuildConfig mproject config mresolver = do+loadBuildConfig mproject config mresolver mcompiler = do     env <- ask     miniConfig <- loadMiniConfig config @@ -339,6 +346,7 @@                            , projectExtraDeps = mempty                            , projectFlags = mempty                            , projectResolver = r+                           , projectCompiler = Nothing                            , projectExtraPackageDBs = []                            }                    liftIO $ do@@ -359,22 +367,29 @@     resolver <-         case mresolver of             Nothing -> return $ projectResolver project'-            Just aresolver -> do+            Just aresolver ->                 runReaderT (makeConcreteResolver aresolver) miniConfig-    let project = project' { projectResolver = resolver }+    let project = project'+            { projectResolver = resolver+            , projectCompiler = mcompiler <|> projectCompiler project'+            }      wantedCompiler <--        case projectResolver project of-            ResolverSnapshot snapName -> do-                mbp <- runReaderT (loadMiniBuildPlan snapName) miniConfig-                return $ mbpCompilerVersion mbp-            ResolverCustom _name url -> do-                mbp <- runReaderT (parseCustomMiniBuildPlan stackYamlFP url) miniConfig-                return $ mbpCompilerVersion mbp-            ResolverCompiler wantedCompiler -> return wantedCompiler+        case projectCompiler project of+            Just wantedCompiler -> return wantedCompiler+            Nothing -> case projectResolver project of+                ResolverSnapshot snapName -> do+                    mbp <- runReaderT (loadMiniBuildPlan snapName) miniConfig+                    return $ mbpCompilerVersion mbp+                ResolverCustom _name url -> do+                    mbp <- runReaderT (parseCustomMiniBuildPlan stackYamlFP url) miniConfig+                    return $ mbpCompilerVersion mbp+                ResolverCompiler wantedCompiler -> return wantedCompiler      extraPackageDBs <- mapM parseRelAsAbsDir (projectExtraPackageDBs project) +    packageCaches <- runReaderT (getMinimalEnvOverride >>= getPackageCaches) miniConfig+     return BuildConfig         { bcConfig = config         , bcResolver = projectResolver project@@ -386,6 +401,7 @@         , bcFlags = projectFlags project         , bcImplicitGlobal = isNothing mproject         , bcGHCVariant = getGHCVariant miniConfig+        , bcPackageCaches = packageCaches         }  -- | Resolve a PackageEntry into a list of paths, downloading and cloning as@@ -463,7 +479,7 @@         readInNull (parent dirTmp) "git" menv             [ "clone"             , T.unpack url-            , toFilePath dirTmp+            , toFilePathNoTrailingSep dirTmp             ]             Nothing         readInNull dirTmp "git" menv
src/Stack/Config/Docker.hs view
@@ -10,6 +10,7 @@ import Data.Maybe import qualified Data.Text as T import Data.Typeable (Typeable)+import Distribution.Version (simplifyVersionRange) import Path import Stack.Types @@ -55,6 +56,7 @@         dockerMount = dockerMonoidMount         dockerEnv = dockerMonoidEnv         dockerSetUser = dockerMonoidSetUser+        dockerRequireDockerVersion = simplifyVersionRange dockerMonoidRequireDockerVersion     dockerDatabasePath <-         case dockerMonoidDatabasePath of             Nothing -> return $ stackRoot </> $(mkRelFile "docker.db")
+ src/Stack/ConfigCmd.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Make changes to the stack yaml file++module Stack.ConfigCmd+       (ConfigCmdSet(..)+       ,cfgCmdSet+       ,cfgCmdSetName+       ,cfgCmdName) where++import           Control.Monad.Catch (MonadMask, throwM, MonadThrow)+import           Control.Monad.IO.Class+import           Control.Monad.Logger+import           Control.Monad.Reader (MonadReader, asks)+import           Control.Monad.Trans.Control (MonadBaseControl)+import qualified Data.ByteString as S+import qualified Data.HashMap.Strict as HMap+import qualified Data.Yaml as Yaml+import           Network.HTTP.Client.Conduit (HasHttpManager)+import           Path+import           Stack.BuildPlan+import           Stack.Init+import           Stack.Types++data ConfigCmdSet = ConfigCmdSetResolver AbstractResolver++cfgCmdSet :: ( MonadIO m+             , MonadBaseControl IO m+             , MonadMask m+             , MonadReader env m+             , HasConfig env+             , HasBuildConfig env+             , HasHttpManager env+             , HasGHCVariant env+             , MonadThrow m+             , MonadLogger m)+             => ConfigCmdSet -> m ()+cfgCmdSet (ConfigCmdSetResolver newResolver) = do+    stackYaml <- fmap bcStackYaml (asks getBuildConfig)+    let stackYamlFp =+            toFilePath stackYaml+    -- We don't need to worry about checking for a valid yaml here+    (projectYamlConfig :: Yaml.Object) <-+        liftIO (Yaml.decodeFileEither stackYamlFp) >>=+        either throwM return+    newResolverText <- fmap resolverName (makeConcreteResolver newResolver)+    -- We checking here that the snapshot actually exists+    snap <- parseSnapName newResolverText+    _ <- loadMiniBuildPlan snap++    let projectYamlConfig' =+            HMap.insert+                "resolver"+                (Yaml.String newResolverText)+                projectYamlConfig+    liftIO+        (S.writeFile+             stackYamlFp+             (Yaml.encode projectYamlConfig'))+    return ()++cfgCmdName :: String+cfgCmdName = "config"++cfgCmdSetName :: String+cfgCmdSetName = "set"
src/Stack/Constants.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} @@ -7,6 +6,7 @@ module Stack.Constants     (builtConfigFileFromDir     ,builtFileFromDir+    ,buildPlanDir     ,configuredFileFromDir     ,defaultShakeThreads     ,distDirFromDir@@ -26,12 +26,12 @@     ,benchBuiltFile     ,stackProgName     ,wiredInPackages+    ,ghcjsBootPackages     ,cabalPackageName     ,implicitGlobalProjectDirDeprecated     ,implicitGlobalProjectDir     ,hpcRelativeDir     ,hpcDirFromDir-    ,dotHpc     ,objectInterfaceDir     ,templatesDir     ,defaultUserConfigPathDeprecated@@ -49,16 +49,10 @@ import qualified Data.Text as T import           Path as FL import           Prelude+import           Stack.Types.Compiler import           Stack.Types.Config import           Stack.Types.PackageIdentifier import           Stack.Types.PackageName-#ifdef mingw32_HOST_OS-import qualified Crypto.Hash.SHA1 as SHA1-import qualified Data.ByteString as B-import qualified Data.ByteString.Base16 as Base16-import qualified Data.ByteString.Char8 as B8-import qualified Data.Text.Encoding as T-#endif  -- | Extensions for anything that can be a Haskell module. haskellModuleExts :: [Text]@@ -132,7 +126,7 @@ buildCacheFile :: (MonadThrow m, MonadReader env m, HasPlatform env,HasEnvConfig env)                => Path Abs Dir      -- ^ Package directory.                -> m (Path Abs File)-buildCacheFile dir = do+buildCacheFile dir =     liftM         (</> $(mkRelFile "stack-build-cache"))         (distDirFromDir dir)@@ -168,7 +162,7 @@ configCacheFile :: (MonadThrow m, MonadReader env m, HasPlatform env,HasEnvConfig env)                 => Path Abs Dir      -- ^ Package directory.                 -> m (Path Abs File)-configCacheFile dir = do+configCacheFile dir =     liftM         (</> $(mkRelFile "stack-config-cache"))         (distDirFromDir dir)@@ -177,7 +171,7 @@ configCabalMod :: (MonadThrow m, MonadReader env m, HasPlatform env,HasEnvConfig env)                => Path Abs Dir      -- ^ Package directory.                -> m (Path Abs File)-configCabalMod dir = do+configCabalMod dir =     liftM         (</> $(mkRelFile "stack-cabal-mod"))         (distDirFromDir dir)@@ -213,22 +207,14 @@ distRelativeDir = do     cabalPkgVer <- asks (envConfigCabalVersion . getEnvConfig)     platform <- platformVariantRelDir-    cabal <-+    wc <- getWhichCompiler+    -- Cabal version, suffixed with "_ghcjs" if we're using GHCJS.+    envDir <-         parseRelDir $-        packageIdentifierString-            (PackageIdentifier cabalPackageName cabalPkgVer)--#ifdef mingw32_HOST_OS-    -- This is an attempt to shorten path to stack build artifacts dir on Windows to-    -- decrease our chances of hitting 260 symbol path limit.-    -- The idea is to calculate SHA1 hash from concatenated platform and cabal strings,-    -- encode with base 16 and take first 8 symbols of it.-    let concatenatedText = T.pack . toFilePath $ platform </> cabal-        sha1 = SHA1.hash $ T.encodeUtf8 concatenatedText-    platformAndCabal <- parseRelDir . B8.unpack . B.take 8 $ Base16.encode sha1-#else-    let platformAndCabal = platform </> cabal-#endif+        (if wc == Ghcjs then (++ "_ghcjs") else id) $+        packageIdentifierString $+        PackageIdentifier cabalPackageName cabalPkgVer+    platformAndCabal <- useShaPathOnWindows (platform </> envDir)     return $         workDirRel </>         $(mkRelDir "dist") </>@@ -290,7 +276,7 @@ wiredInPackages =     maybe (error "Parse error in wiredInPackages") HashSet.fromList mparsed   where-    mparsed = sequence $ map parsePackageName+    mparsed = mapM parsePackageName       [ "ghc-prim"       , "integer-gmp"       , "integer-simple"@@ -303,6 +289,53 @@       , "interactive"       ] +-- TODO: Get this unwieldy list out of here and into a datafile+-- generated by GHCJS! See https://github.com/ghcjs/ghcjs/issues/434+ghcjsBootPackages :: HashSet PackageName+ghcjsBootPackages =+    maybe (error "Parse error in ghcjsBootPackages") HashSet.fromList mparsed+  where+    mparsed = mapM parsePackageName+      -- stage1a+      [ "array"+      , "base"+      , "binary"+      , "bytestring"+      , "containers"+      , "deepseq"+      , "integer-gmp"+      , "pretty"+      , "primitive"+      , "integer-gmp"+      , "pretty"+      , "primitive"+      , "template-haskell"+      , "transformers"+      -- stage1b+      , "directory"+      , "filepath"+      , "old-locale"+      , "process"+      , "time"+      -- stage2+      , "async"+      , "aeson"+      , "attoparsec"+      , "case-insensitive"+      , "dlist"+      , "extensible-exceptions"+      , "hashable"+      , "mtl"+      , "old-time"+      , "parallel"+      , "scientific"+      , "stm"+      , "syb"+      , "text"+      , "unordered-containers"+      , "vector"+      ]+ -- | Just to avoid repetition and magic strings. cabalPackageName :: PackageName cabalPackageName =@@ -323,10 +356,6 @@     p </>     $(mkRelDir "global-project") --- | Where .mix files go.-dotHpc :: Path Rel Dir-dotHpc = $(mkRelDir ".hpc")- -- | Deprecated default global config path. defaultUserConfigPathDeprecated :: Path Abs Dir -> Path Abs File defaultUserConfigPathDeprecated = (</> $(mkRelFile "stack.yaml"))@@ -346,3 +375,8 @@ -- Note that this will be @Nothing@ on Windows, which is by design. defaultGlobalConfigPath :: Maybe (Path Abs File) defaultGlobalConfigPath = parseAbsFile "/etc/stack/config.yaml"++-- | Path where build plans are stored.+buildPlanDir :: Path Abs Dir -- ^ Stack root+             -> Path Abs Dir+buildPlanDir = (</> $(mkRelDir "build-plan"))
+ src/Stack/Coverage.hs view
@@ -0,0 +1,400 @@+{-# LANGUAGE ConstraintKinds       #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE TemplateHaskell       #-}+{-# LANGUAGE FlexibleInstances     #-}+-- | Generate HPC (Haskell Program Coverage) reports+module Stack.Coverage+    ( deleteHpcReports+    , updateTixFile+    , generateHpcReport+    , HpcReportOpts(..)+    , generateHpcReportForTargets+    , generateHpcUnifiedReport+    , generateHpcMarkupIndex+    ) where++import           Control.Applicative+import           Control.Exception.Lifted+import           Control.Monad (liftM, when, unless, void)+import           Control.Monad.Catch (MonadCatch)+import           Control.Monad.IO.Class+import           Control.Monad.Logger+import           Control.Monad.Reader (MonadReader, asks)+import           Control.Monad.Trans.Resource+import qualified Data.ByteString.Char8 as S8+import           Data.Foldable (forM_, asum, toList)+import           Data.Function+import           Data.List+import qualified Data.Map.Strict as Map+import           Data.Maybe+import           Data.Maybe.Extra (mapMaybeM)+import           Data.Monoid ((<>))+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.IO as T+import qualified Data.Text.Lazy as LT+import           Data.Traversable (forM)+import           Network.HTTP.Download (HasHttpManager)+import           Path+import           Path.Extra (toFilePathNoTrailingSep)+import           Path.IO+import           Prelude hiding (FilePath, writeFile)+import           Stack.Build.Source (parseTargetsFromBuildOpts)+import           Stack.Build.Target+import           Stack.Constants+import           Stack.Package+import           Stack.Types+import qualified System.Directory as D+import           System.FilePath (dropExtension, isPathSeparator)+import           System.Process.Read+import           Text.Hastache (htmlEscape)+import           Trace.Hpc.Tix++-- | Invoked at the beginning of running with "--coverage"+deleteHpcReports :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env)+                 => m ()+deleteHpcReports = do+    hpcDir <- hpcReportDir+    removeTreeIfExists hpcDir++-- | Move a tix file into a sub-directory of the hpc report directory. Deletes the old one if one is+-- present.+updateTixFile :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+            => PackageName -> Path Abs File -> m ()+updateTixFile pkgName tixSrc = do+    exists <- fileExists tixSrc+    when exists $ do+        tixDest <- tixFilePath pkgName (dropExtension (toFilePath (filename tixSrc)))+        removeFileIfExists tixDest+        createTree (parent tixDest)+        -- Remove exe modules because they are problematic. This could be revisited if there's a GHC+        -- version that fixes https://ghc.haskell.org/trac/ghc/ticket/1853+        mtix <- readTixOrLog tixSrc+        case mtix of+            Nothing -> $logError $ "Failed to read " <> T.pack (toFilePath tixSrc)+            Just tix -> do+                liftIO $ writeTix (toFilePath tixDest) (removeExeModules tix)+                removeFileIfExists tixSrc++-- | Get the directory used for hpc reports for the given pkgId.+hpcPkgPath :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+            => PackageName -> m (Path Abs Dir)+hpcPkgPath pkgName = do+    outputDir <- hpcReportDir+    pkgNameRel <- parseRelDir (packageNameString pkgName)+    return (outputDir </> pkgNameRel)++-- | Get the tix file location, given the name of the file (without extension), and the package+-- identifier string.+tixFilePath :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+            => PackageName -> String ->  m (Path Abs File)+tixFilePath pkgName tixName = do+    pkgPath <- hpcPkgPath pkgName+    tixRel <- parseRelFile (tixName ++ "/" ++ tixName ++ ".tix")+    return (pkgPath </> tixRel)++-- | Generates the HTML coverage report and shows a textual coverage summary for a package.+generateHpcReport :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+                  => Path Abs Dir -> Package -> [Text] -> m ()+generateHpcReport pkgDir package tests = do+    -- If we're using > GHC 7.10, the hpc 'include' parameter must specify a ghc package key. See+    -- https://github.com/commercialhaskell/stack/issues/785+    let pkgName = packageNameText (packageName package)+        pkgId = packageIdentifierString (packageIdentifier package)+    compilerVersion <- asks (envConfigCompilerVersion . getEnvConfig)+    eincludeName <-+        -- Pre-7.8 uses plain PKG-version in tix files.+        if getGhcVersion compilerVersion < $(mkVersion "7.10") then return $ Right $ Just pkgId+        -- We don't expect to find a package key if there is no library.+        else if not (packageHasLibrary package) then return $ Right Nothing+        -- Look in the inplace DB for the package key.+        -- See https://github.com/commercialhaskell/stack/issues/1181#issuecomment-148968986+        else do+            mghcPkgKey <- findPackageKeyForBuiltPackage pkgDir (packageIdentifier package)+            case mghcPkgKey of+                Nothing -> do+                    let msg = "Failed to find GHC package key for " <> pkgName+                    $logError msg+                    return $ Left msg+                Just ghcPkgKey -> return $ Right $ Just $ T.unpack ghcPkgKey+    forM_ tests $ \testName -> do+        tixSrc <- tixFilePath (packageName package) (T.unpack testName)+        let report = "coverage report for " <> pkgName <> "'s test-suite \"" <> testName <> "\""+            reportDir = parent tixSrc+        case eincludeName of+            Left err -> generateHpcErrorReport reportDir (sanitize (T.unpack err))+            -- Restrict to just the current library code, if there is a library in the package (see+            -- #634 - this will likely be customizable in the future)+            Right mincludeName -> do+                let extraArgs = case mincludeName of+                        Just includeName -> ["--include", includeName ++ ":"]+                        Nothing -> []+                generateHpcReportInternal tixSrc reportDir report extraArgs extraArgs++generateHpcReportInternal :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+                          => Path Abs File -> Path Abs Dir -> Text -> [String] -> [String] -> m ()+generateHpcReportInternal tixSrc reportDir report extraMarkupArgs extraReportArgs = do+    -- If a .tix file exists, move it to the HPC output directory and generate a report for it.+    tixFileExists <- fileExists tixSrc+    if not tixFileExists+        then $logError $ T.concat+            [ "Didn't find .tix for "+            , report+            , " - expected to find it at "+            , T.pack (toFilePath tixSrc)+            , "."+            ]+        else (`catch` \err -> do+                 let msg = show (err :: ReadProcessException)+                 $logError (T.pack msg)+                 generateHpcErrorReport reportDir $ sanitize msg) $+             (`onException` $logError ("Error occurred while producing " <> report)) $ do+            -- Directories for .mix files.+            hpcRelDir <- hpcRelativeDir+            -- Compute arguments used for both "hpc markup" and "hpc report".+            pkgDirs <- Map.keys . envConfigPackages <$> asks getEnvConfig+            let args =+                    -- Use index files from all packages (allows cross-package coverage results).+                    concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs +++                    -- Look for index files in the correct dir (relative to each pkgdir).+                    ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"]+            menv <- getMinimalEnvOverride+            $logInfo $ "Generating " <> report+            outputLines <- liftM S8.lines $ readProcessStdout Nothing menv "hpc"+                ( "report"+                : toFilePath tixSrc+                : (args ++ extraReportArgs)+                )+            if all ("(0/0)" `S8.isSuffixOf`) outputLines+                then do+                    let msg html = T.concat+                            [ "Error: The "+                            , report+                            , " did not consider any code. One possible cause of this is"+                            , " if your test-suite builds the library code (see stack "+                            , if html then "<a href='https://github.com/commercialhaskell/stack/issues/1008'>" else ""+                            , "issue #1008"+                            , if html then "</a>" else ""+                            , "). It may also indicate a bug in stack or"+                            , " the hpc program. Please report this issue if you think"+                            , " your coverage report should have meaningful results."+                            ]+                    $logError (msg False)+                    generateHpcErrorReport reportDir (msg True)+                else do+                    -- Print output, stripping @\r@ characters because Windows.+                    forM_ outputLines ($logInfo . T.decodeUtf8 . S8.filter (not . (=='\r')))+                    $logInfo+                        ("The " <> report <> " is available at " <>+                         T.pack (toFilePath (reportDir </> $(mkRelFile "hpc_index.html"))))+                    -- Generate the markup.+                    void $ readProcessStdout Nothing menv "hpc"+                        ( "markup"+                        : toFilePath tixSrc+                        : ("--destdir=" ++ toFilePathNoTrailingSep reportDir)+                        : (args ++ extraMarkupArgs)+                        )++data HpcReportOpts = HpcReportOpts+    { hroptsInputs :: [Text]+    , hroptsAll :: Bool+    , hroptsDestDir :: Maybe String+    } deriving (Show)++generateHpcReportForTargets :: (MonadIO m, HasHttpManager env, MonadReader env m, MonadBaseControl IO m, MonadCatch m, MonadLogger m, HasEnvConfig env)+                            => HpcReportOpts -> m ()+generateHpcReportForTargets opts = do+    let (tixFiles, targetNames) = partition (".tix" `T.isSuffixOf`) (hroptsInputs opts)+    targetTixFiles <-+         -- When there aren't any package component arguments, then+         -- don't default to all package components.+         if not (hroptsAll opts) && null targetNames+         then return []+         else do+             when (hroptsAll opts && not (null targetNames)) $+                 $logWarn $ "Since --all is used, it is redundant to specify these targets: " <> T.pack (show targetNames)+             (_,_,targets) <- parseTargetsFromBuildOpts+                 AllowNoTargets+                 defaultBuildOpts+                 { boptsTargets = if hroptsAll opts then [] else targetNames+                 }+             liftM concat $ forM (Map.toList targets) $ \(name, target) ->+                 case target of+                     STUnknown -> fail $+                         packageNameString name ++ " isn't a known local page"+                     STNonLocal -> fail $+                         "Expected a local package, but " +++                         packageNameString name +++                         " is either an extra-dep or in the snapshot."+                     STLocalComps comps -> do+                         pkgPath <- hpcPkgPath name+                         forM (toList comps) $ \nc ->+                             case nc of+                                 CTest testName ->+                                     liftM (pkgPath </>) $ parseRelFile (T.unpack testName ++ ".tix")+                                 _ -> fail $+                                     "Can't specify anything except test-suites as hpc report targets (" +++                                     packageNameString name +++                                     " is used with a non test-suite target)"+                     STLocalAll -> do+                         pkgPath <- hpcPkgPath name+                         exists <- dirExists pkgPath+                         if exists+                             then do+                                 (_, files) <- listDirectory pkgPath+                                 return (filter ((".tix" `isSuffixOf`) . toFilePath) files)+                             else return []+    tixPaths <- liftM (++ targetTixFiles) $ mapM (parseRelAsAbsFile . T.unpack) tixFiles+    when (null tixPaths) $+        fail "Not generating combined report, because no targets or tix files are specified."+    reportDir <- case hroptsDestDir opts of+        Nothing -> liftM (</> $(mkRelDir "combined/custom")) hpcReportDir+        Just destDir -> do+            liftIO $ D.createDirectoryIfMissing True destDir+            parseRelAsAbsDir destDir+    generateUnionReport "combined report" reportDir tixPaths++generateHpcUnifiedReport :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+                         => m ()+generateHpcUnifiedReport = do+    outputDir <- hpcReportDir+    createTree outputDir+    (dirs, _) <- listDirectory outputDir+    tixFiles <- liftM (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do+        (dirs', _) <- listDirectory dir+        forM dirs' $ \dir' -> do+            (_, files) <- listDirectory dir'+            return (filter ((".tix" `isSuffixOf`) . toFilePath) files)+    let reportDir = outputDir </> $(mkRelDir "combined/all")+    if length tixFiles < 2+        then $logInfo $ T.concat+            [ if null tixFiles then "No tix files" else "Only one tix file"+            , " found in "+            , T.pack (toFilePath outputDir)+            , ", so not generating a unified coverage report."+            ]+        else generateUnionReport "unified report" reportDir tixFiles++generateUnionReport :: (MonadIO m,MonadReader env m,HasConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,HasEnvConfig env)+                    => Text -> Path Abs Dir -> [Path Abs File] -> m ()+generateUnionReport report reportDir tixFiles = do+    (errs, tix) <- fmap (unionTixes . map removeExeModules) (mapMaybeM readTixOrLog tixFiles)+    $logDebug $ "Using the following tix files: " <> T.pack (show tixFiles)+    unless (null errs) $ $logWarn $ T.concat $+        "The following modules are left out of the " : report : " due to version mismatches: " :+        intersperse ", " (map T.pack errs)+    tixDest <- liftM (reportDir </>) $ parseRelFile (dirnameString reportDir ++ ".tix")+    createTree (parent tixDest)+    liftIO $ writeTix (toFilePath tixDest) tix+    generateHpcReportInternal tixDest reportDir report [] []++readTixOrLog :: (MonadLogger m, MonadIO m, MonadBaseControl IO m) => Path b File -> m (Maybe Tix)+readTixOrLog path = do+    mtix <- liftIO (readTix (toFilePath path)) `catch` \(ErrorCall err) -> do+        $logError $ "Error while reading tix: " <> T.pack err+        return Nothing+    when (isNothing mtix) $+        $logError $ "Failed to read tix file " <> T.pack (toFilePath path)+    return mtix++-- | Module names which contain '/' have a package name, and so they weren't built into the+-- executable.+removeExeModules :: Tix -> Tix+removeExeModules (Tix ms) = Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms)++unionTixes :: [Tix] -> ([String], Tix)+unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs))+  where+    (errs, outputs) = Map.mapEither id $ Map.unionsWith merge $ map toMap tixes+    toMap (Tix ms) = Map.fromList (map (\x@(TixModule k _ _ _) -> (k, Right x)) ms)+    merge (Right (TixModule k hash1 len1 tix1))+          (Right (TixModule _ hash2 len2 tix2))+        | hash1 == hash2 && len1 == len2 = Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2))+    merge _ _ = Left ()++generateHpcMarkupIndex :: (MonadIO m,MonadReader env m,MonadLogger m,MonadCatch m,HasEnvConfig env)+                       => m ()+generateHpcMarkupIndex = do+    outputDir <- hpcReportDir+    let outputFile = outputDir </> $(mkRelFile "index.html")+    createTree outputDir+    (dirs, _) <- listDirectory outputDir+    rows <- liftM (catMaybes . concat) $ forM dirs $ \dir -> do+        (subdirs, _) <- listDirectory dir+        forM subdirs $ \subdir -> do+            let indexPath = subdir </> $(mkRelFile "hpc_index.html")+            exists' <- fileExists indexPath+            if not exists' then return Nothing else do+                relPath <- stripDir outputDir indexPath+                let package = dirname dir+                    testsuite = dirname subdir+                return $ Just $ T.concat+                  [ "<tr><td>"+                  , pathToHtml package+                  , "</td><td><a href=\""+                  , pathToHtml relPath+                  , "\">"+                  , pathToHtml testsuite+                  , "</a></td></tr>"+                  ]+    liftIO $ T.writeFile (toFilePath outputFile) $ T.concat $+        [ "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"+        -- Part of the css from HPC's output HTML+        , "<style type=\"text/css\">"+        , "table.dashboard { border-collapse: collapse; border: solid 1px black }"+        , ".dashboard td { border: solid 1px black }"+        , ".dashboard th { border: solid 1px black }"+        , "</style>"+        , "</head>"+        , "<body>"+        ] +++        (if null rows+            then+                [ "<b>No hpc_index.html files found in \""+                , pathToHtml outputDir+                , "\".</b>"+                ]+            else+                [ "<table class=\"dashboard\" width=\"100%\" boder=\"1\"><tbody>"+                , "<p><b>NOTE: This is merely a listing of the html files found in the coverage reports directory.  Some of these reports may be old.</b></p>"+                , "<tr><th>Package</th><th>TestSuite</th><th>Modification Time</th></tr>"+                ] +++                rows +++                ["</tbody></table>"]) +++        ["</body></html>"]+    unless (null rows) $+        $logInfo $ "\nAn index of the generated HTML coverage reports is available at " <>+            T.pack (toFilePath outputFile)++generateHpcErrorReport :: MonadIO m => Path Abs Dir -> Text -> m ()+generateHpcErrorReport dir err = do+    createTree dir+    liftIO $ T.writeFile (toFilePath (dir </> $(mkRelFile "hpc_index.html"))) $ T.concat+        [ "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>"+        , "<h1>HPC Report Generation Error</h1>"+        , "<p>"+        , err+        , "</p>"+        , "</body></html>"+        ]++pathToHtml :: Path b t -> Text+pathToHtml = T.dropWhileEnd (=='/') . sanitize . toFilePath++sanitize :: String -> Text+sanitize = LT.toStrict . htmlEscape . LT.pack++dirnameString :: Path r Dir -> String+dirnameString = dropWhileEnd isPathSeparator . toFilePath . dirname++findPackageKeyForBuiltPackage :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env)+                              => Path Abs Dir -> PackageIdentifier -> m (Maybe Text)+findPackageKeyForBuiltPackage pkgDir pkgId = do+    distDir <- distDirFromDir pkgDir+    path <- liftM (distDir </>) $+        parseRelFile ("package.conf.inplace/" ++ packageIdentifierString pkgId ++ "-inplace.conf")+    contents <- liftIO $ T.readFile (toFilePath path)+    return $ asum (map (T.stripPrefix "key: ") (T.lines contents))
src/Stack/Docker.hs view
@@ -10,7 +10,7 @@   ,dockerCleanupCmdName   ,dockerCmdName   ,dockerPullCmdName-  ,execWithOptionalContainer+  ,entrypoint   ,preventInContainer   ,pull   ,reexecWithOptionalContainer@@ -19,12 +19,13 @@   ) where  import           Control.Applicative+import           Control.Concurrent.MVar.Lifted (MVar,modifyMVar_,newMVar) import           Control.Exception.Lifted import           Control.Monad import           Control.Monad.Catch (MonadThrow,throwM,MonadCatch,MonadMask) import           Control.Monad.IO.Class (MonadIO,liftIO) import           Control.Monad.Logger (MonadLogger,logError,logInfo,logWarn)-import           Control.Monad.Reader (MonadReader,asks)+import           Control.Monad.Reader (MonadReader,asks,runReaderT) import           Control.Monad.Writer (execWriter,runWriter,tell) import           Control.Monad.Trans.Control (MonadBaseControl) import           Data.Aeson.Extended (FromJSON(..),(.:),(.:?),(.!=),eitherDecode)@@ -33,11 +34,12 @@ import qualified Data.ByteString.Lazy.Char8 as LBS import           Data.Char (isSpace,toUpper,isAscii,isDigit) import           Data.Conduit.List (sinkNull)-import           Data.List (dropWhileEnd,intercalate,intersperse,isPrefixOf,isInfixOf,foldl',sortBy)-import           Data.List.Extra (trim)+import           Data.List (dropWhileEnd,intercalate,isPrefixOf,isInfixOf,foldl')+import           Data.List.Extra (trim,nubOrd) import           Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import           Data.Maybe+import           Data.Ord (Down(..)) import           Data.Streaming.Process (ProcessExitedUnsuccessfully(..)) import           Data.Text (Text) import qualified Data.Text as T@@ -45,23 +47,29 @@ import           Data.Time (UTCTime,LocalTime(..),diffDays,utcToLocalTime,getZonedTime,ZonedTime(..)) import           Data.Typeable (Typeable) import           Data.Version (showVersion)-import           Distribution.System (Platform (Platform), Arch (X86_64), OS (Linux))+import           Distribution.System (Platform (Platform),Arch (X86_64),OS (Linux)) import           Distribution.Text (display)+import           GHC.Exts (sortWith) import           Network.HTTP.Client.Conduit (HasHttpManager) import           Path-import           Path.IO (getWorkingDir,listDirectory,createTree,removeFile,removeTree,dirExists)+import           Path.Extra (toFilePathNoTrailingSep)+import           Path.IO import qualified Paths_stack as Meta import           Prelude -- Fix redundant import warnings-import           Stack.Constants (projectDockerSandboxDir,stackProgName,stackRootEnvVar)+import           Stack.Constants (projectDockerSandboxDir,stackProgName,stackRootEnvVar,buildPlanDir) import           Stack.Docker.GlobalDB import           Stack.Types import           Stack.Types.Internal import           Stack.Setup (ensureDockerStackExe)-import           System.Directory (canonicalizePath, getModificationTime)-import           System.Environment (lookupEnv,getProgName, getArgs,getExecutablePath)+import           System.Directory (canonicalizePath,getModificationTime)+import           System.Environment (getEnv,getProgName,getArgs,getExecutablePath,lookupEnv) import           System.Exit (exitSuccess, exitWith)-import           System.FilePath (dropTrailingPathSeparator,takeBaseName)+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as Posix import           System.IO (stderr,stdin,stdout,hIsTerminalDevice)+import           System.IO.Error (isDoesNotExistError)+import           System.IO.Unsafe (unsafePerformIO)+import qualified System.PosixCompat.User as User import           System.Process.PagerEditor (editByteString) import           System.Process.Read import           System.Process.Run@@ -91,24 +99,31 @@ reexecWithOptionalContainer mprojectRoot =     execWithOptionalContainer mprojectRoot getCmdArgs   where-    getCmdArgs envOverride imageInfo = do+    getCmdArgs docker envOverride imageInfo isRemoteDocker = do         config <- asks getConfig+        deUidGid <-+            if fromMaybe (not isRemoteDocker) (dockerSetUser docker)+                then liftIO $ Just <$>+                  ((,) <$> User.getEffectiveUserID <*> User.getEffectiveGroupID)+                else return Nothing         args <-             fmap-                (("--" ++ reExecArgName ++ "=" ++ showVersion Meta.version) :)+                (["--" ++ reExecArgName ++ "=" ++ showVersion Meta.version+                 ,"--" ++ dockerEntrypointArgName+                 ,show DockerEntrypoint{..}] ++)                 (liftIO getArgs)         case dockerStackExe (configDocker config) of             Just DockerStackExeHost-              | configPlatform config == dockerContainerPlatform ->-                  fmap (cmdArgs args) (liftIO getExecutablePath)+              | configPlatform config == dockerContainerPlatform -> do+                  exePath <- liftIO getExecutablePath+                  cmdArgs args exePath               | otherwise -> throwM UnsupportedStackExeHostPlatformException             Just DockerStackExeImage -> do                 progName <- liftIO getProgName-                return (takeBaseName progName, args, [], [])-            Just (DockerStackExePath path) ->-                fmap-                    (cmdArgs args)-                    (liftIO $ canonicalizePath (toFilePath path))+                return (FP.takeBaseName progName, args, [], [])+            Just (DockerStackExePath path) -> do+                exePath <- liftIO $ canonicalizePath (toFilePath path)+                cmdArgs args exePath             Just DockerStackExeDownload -> exeDownload args             Nothing               | configPlatform config == dockerContainerPlatform -> do@@ -125,10 +140,8 @@                                  exeTimestamp                          return (exePath, exeTimestamp, isKnown)                   case misCompatible of-                      Just True -> do-                          return (cmdArgs args exePath)-                      Just False -> do-                          exeDownload args+                      Just True -> cmdArgs args exePath+                      Just False -> exeDownload args                       Nothing -> do                           e <-                               try $@@ -157,18 +170,15 @@                                   exeTimestamp                                   compatible                           if compatible-                              then return (cmdArgs args exePath)+                              then cmdArgs args exePath                               else exeDownload args-            Nothing-              | otherwise -> do-                  exeDownload args-    exeDownload args =-        fmap-            (cmdArgs args . toFilePath)-            (ensureDockerStackExe dockerContainerPlatform)-    cmdArgs args exePath =-        let mountPath = concat ["/opt/host/bin/", takeBaseName exePath]-        in (mountPath, args, [], [Mount exePath mountPath])+            Nothing -> exeDownload args+    exeDownload args = do+        exePath <- ensureDockerStackExe dockerContainerPlatform+        cmdArgs args (toFilePath exePath)+    cmdArgs args exePath = do+        let mountPath = hostBinDir FP.</> FP.takeBaseName exePath+        return (mountPath, args, [], [Mount exePath mountPath])  -- | If Docker is enabled, re-runs the OS command returned by the second argument in a -- Docker container.  Otherwise, runs the inner action.@@ -177,7 +187,7 @@ execWithOptionalContainer     :: M env m     => Maybe (Path Abs Dir)-    -> (EnvOverride -> Inspect -> m (FilePath,[String],[(String,String)],[Mount]))+    -> GetCmdArgs env m     -> Maybe (m ())     -> IO ()     -> Maybe (m ())@@ -222,7 +232,7 @@  -- | Run a command in a new Docker container, then exit the process. runContainerAndExit :: M env m-                    => (EnvOverride -> Inspect -> m (FilePath,[String],[(String,String)],[Mount]))+                    => GetCmdArgs env m                     -> Maybe (Path Abs Dir)                     -> m ()                     -> m ()@@ -234,33 +244,23 @@   do config <- asks getConfig      let docker = configDocker config      envOverride <- getEnvOverride (configPlatform config)-     checkDockerVersion envOverride+     checkDockerVersion envOverride docker      (dockerHost,dockerCertPath,bamboo,jenkins) <-        liftIO ((,,,) <$> lookupEnv "DOCKER_HOST"                      <*> lookupEnv "DOCKER_CERT_PATH"                      <*> lookupEnv "bamboo_buildKey"                      <*> lookupEnv "JENKINS_HOME")      let isRemoteDocker = maybe False (isPrefixOf "tcp://") dockerHost-     userEnvVars <--         if fromMaybe (not isRemoteDocker) (dockerSetUser docker)-             then do-                 uidOut <- readProcessStdout Nothing envOverride "id" ["-u"]-                 gidOut <- readProcessStdout Nothing envOverride "id" ["-g"]-                 return-                     [ "-e","WORK_UID=" ++ dropWhileEnd isSpace (decodeUtf8 uidOut)-                     , "-e","WORK_GID=" ++ dropWhileEnd isSpace (decodeUtf8 gidOut) ]-             else return []      isStdoutTerminal <- asks getTerminal      (isStdinTerminal,isStderrTerminal) <-        liftIO ((,) <$> hIsTerminalDevice stdin                    <*> hIsTerminalDevice stderr)-     pwd <- getWorkingDir      when (isRemoteDocker &&            maybe False (isInfixOf "boot2docker") dockerCertPath)           ($logWarn "Warning: Using boot2docker is NOT supported, and not likely to perform well.")      let image = dockerImage docker      maybeImageInfo <- inspect envOverride image-     imageInfo <- case maybeImageInfo of+     imageInfo@Inspect{..} <- case maybeImageInfo of        Just ii -> return ii        Nothing          | dockerAutoPull docker ->@@ -270,9 +270,10 @@                   Just ii2 -> return ii2                   Nothing -> throwM (InspectFailedException image)          | otherwise -> throwM (NotPulledException image)-     (cmnd,args,envVars,extraMount) <- getCmdArgs envOverride imageInfo-     let imageEnvVars = map (break (== '=')) (icEnv (iiConfig imageInfo))-         sandboxID = fromMaybe "default" (lookupImageEnv sandboxIDEnvVar imageEnvVars)+     let ImageConfig {..} = iiConfig+         imageEnvVars = map (break (== '=')) icEnv+         msandboxID = lookupImageEnv sandboxIDEnvVar imageEnvVars+         sandboxID = fromMaybe "default" msandboxID      sandboxIDDir <- parseRelDir (sandboxID ++ "/")      let stackRoot = configStackRoot config          sandboxDir = projectDockerSandboxDir projectRoot@@ -288,31 +289,41 @@          keepStdinOpen = not (dockerDetach docker) &&                          -- Workaround for https://github.com/docker/docker/issues/12319                          (isTerm || (isNothing bamboo && isNothing jenkins))+         newPathEnv = intercalate [Posix.searchPathSeparator] $+                      nubOrd $+                      [toFilePathNoTrailingSep $ sandboxRepoDir </> $(mkRelDir ".local/bin")+                      ,toFilePathNoTrailingSep $ sandboxRepoDir </> $(mkRelDir ".cabal/bin")+                      ,toFilePathNoTrailingSep $ sandboxRepoDir </> $(mkRelDir "bin")+                      ,hostBinDir] +++                      maybe [] Posix.splitSearchPath (lookupImageEnv "PATH" imageEnvVars)+     (cmnd,args,envVars,extraMount) <- getCmdArgs docker envOverride imageInfo isRemoteDocker+     pwd <- getWorkingDir      liftIO-       (do updateDockerImageLastUsed config-                                     (iiId imageInfo)-                                     (toFilePath projectRoot)-+       (do updateDockerImageLastUsed config iiId (toFilePath projectRoot)            mapM_ createTree-                 (concat [[sandboxHomeDir, sandboxSandboxDir, stackRoot] ++-                          sandboxSubdirs]))+                 ([sandboxHomeDir, sandboxSandboxDir, stackRoot] +++                          sandboxSubdirs))      containerID <- (trim . decodeUtf8) <$> readDockerProcess        envOverride        (concat          [["create"           ,"--net=host"           ,"-e",inContainerEnvVar ++ "=1"-          ,"-e",stackRootEnvVar ++ "=" ++ toFPNoTrailingSep stackRoot-          ,"-e","WORK_WD=" ++ toFPNoTrailingSep pwd-          ,"-e","WORK_HOME=" ++ toFPNoTrailingSep sandboxRepoDir-          ,"-e","WORK_ROOT=" ++ toFPNoTrailingSep projectRoot-          ,"-v",toFPNoTrailingSep stackRoot ++ ":" ++ toFPNoTrailingSep stackRoot-          ,"-v",toFPNoTrailingSep projectRoot ++ ":" ++ toFPNoTrailingSep projectRoot-          ,"-v",toFPNoTrailingSep sandboxSandboxDir ++ ":" ++ toFPNoTrailingSep sandboxDir-          ,"-v",toFPNoTrailingSep sandboxHomeDir ++ ":" ++ toFPNoTrailingSep sandboxRepoDir-          ,"-v",toFPNoTrailingSep stackRoot ++ ":" ++-                toFPNoTrailingSep (sandboxRepoDir </> $(mkRelDir ("." ++ stackProgName ++ "/")))]-         ,userEnvVars+          ,"-e",stackRootEnvVar ++ "=" ++ toFilePathNoTrailingSep stackRoot+          ,"-e","HOME=" ++ toFilePathNoTrailingSep sandboxRepoDir+          ,"-e","PATH=" ++ newPathEnv+          ,"-v",toFilePathNoTrailingSep stackRoot ++ ":" ++ toFilePathNoTrailingSep stackRoot+          ,"-v",toFilePathNoTrailingSep projectRoot ++ ":" ++ toFilePathNoTrailingSep projectRoot+          ,"-v",toFilePathNoTrailingSep sandboxSandboxDir ++ ":" ++ toFilePathNoTrailingSep sandboxDir+          ,"-v",toFilePathNoTrailingSep sandboxHomeDir ++ ":" ++ toFilePathNoTrailingSep sandboxRepoDir+          ,"-v",toFilePathNoTrailingSep stackRoot ++ ":" +++                toFilePathNoTrailingSep (sandboxRepoDir </> $(mkRelDir ("." ++ stackProgName ++ "/")))+          ,"-w",toFilePathNoTrailingSep pwd]+           -- Disable the deprecated entrypoint in FP Complete-generated images+         ,["--entrypoint=/usr/bin/env"+             | isJust msandboxID &&+               (icEntrypoint == ["/usr/local/sbin/docker-entrypoint"] ||+                 icEntrypoint == ["/root/entrypoint.sh"])]          ,concatMap (\(k,v) -> ["-e", k ++ "=" ++ v]) envVars          ,concatMap sandboxSubdirArg sandboxSubdirs          ,concatMap mountArg (extraMount ++ dockerMount docker)@@ -329,12 +340,12 @@      before #ifndef WINDOWS      runInBase <- liftBaseWith $ \run -> return (void . run)-     oldHandlers <- forM (concat [[(sigINT,sigTERM) | not keepStdinOpen]-                                 ,[(sigTERM,sigTERM)]]) $ \(sigIn,sigOut) -> do-       let sigHandler = runInBase (readProcessNull Nothing envOverride "docker"-                                     ["kill","--signal=" ++ show sigOut,containerID])-       oldHandler <- liftIO $ installHandler sigIn (Catch sigHandler) Nothing-       return (sigIn, oldHandler)+     oldHandlers <- forM ([sigINT | not keepStdinOpen] ++ [sigTERM]) $ \sig -> do+       let sigHandler = do+             runInBase (readProcessNull Nothing envOverride "docker"+                                        ["kill","--signal=" ++ show sig,containerID])+       oldHandler <- liftIO $ installHandler sig (Catch sigHandler) Nothing+       return (sig, oldHandler) #endif      e <- try (callProcess'                  (if keepStdinOpen then id else (\cp -> cp { delegate_ctlc = False }))@@ -361,8 +372,7 @@         Just ('=':val) -> Just val         _ -> Nothing     mountArg (Mount host container) = ["-v",host ++ ":" ++ container]-    sandboxSubdirArg subdir = ["-v",toFPNoTrailingSep subdir++ ":" ++ toFPNoTrailingSep subdir]-    toFPNoTrailingSep = dropTrailingPathSeparator . toFilePath+    sandboxSubdirArg subdir = ["-v",toFilePathNoTrailingSep subdir++ ":" ++ toFilePathNoTrailingSep subdir]     projectRoot = fromMaybeProjectRoot mprojectRoot  -- | Clean-up old docker images and containers.@@ -370,8 +380,9 @@         => CleanupOpts -> m () cleanup opts =   do config <- asks getConfig+     let docker = configDocker config      envOverride <- getEnvOverride (configPlatform config)-     checkDockerVersion envOverride+     checkDockerVersion envOverride docker      let runDocker = readDockerProcess envOverride      imagesOut <- runDocker ["images","--no-trunc","-f","dangling=false"]      danglingImagesOut <- runDocker ["images","--no-trunc","-f","dangling=true"]@@ -433,7 +444,7 @@                         | otherwise -> throwM (InvalidCleanupCommandException line)              e <- try (readDockerProcess envOverride args)              case e of-               Left (ReadProcessException _ _ _ _) ->+               Left (ReadProcessException{}) ->                  $logError (concatT ["Could not remove: '",v,"'"])                Left e' -> throwM e'                Right _ -> return ()@@ -513,17 +524,17 @@           case accessor opts of             Just days -> buildStrLn ("#   - " ++ description ++ " at least " ++ showDays days ++ ".")             Nothing -> return ()-        sortCreated l =-          reverse (sortBy (\(_,_,a) (_,_,b) -> compare a b)-                          (catMaybes (map (\(h,r) -> fmap (\ii -> (h,r,iiCreated ii))-                                                          (Map.lookup h inspectMap))-                                          l)))+        sortCreated =+            sortWith (\(_,_,x) -> Down x) .+            (mapMaybe (\(h,r) ->+                case Map.lookup h inspectMap of+                    Nothing -> Nothing+                    Just ii -> Just (h,r,iiCreated ii)))         buildSection sectionHead items itemBuilder =           do let (anyWrote,b) = runWriter (forM items itemBuilder)-             if or anyWrote-               then do buildSectionHead sectionHead-                       tell b-               else return ()+             when (or anyWrote) $+                do buildSectionHead sectionHead+                   tell b         buildKnownImage (imageHash,lastUsedProjects) =           case Map.lookup imageHash imageRepos of             Just repos@(_:_) ->@@ -545,7 +556,7 @@              buildInspect hash              return True         buildContainer removeAge (hash,(image,name),created) =-          do let disp = (name ++ " (image: " ++ image ++ ")")+          do let disp = name ++ " (image: " ++ image ++ ")"              buildTime containerStr removeAge created disp              buildInspect hash              return True@@ -614,7 +625,7 @@          case eitherDecode (LBS.pack (filter isAscii (decodeUtf8 inspectOut))) of            Left msg -> throwM (InvalidInspectOutputException msg)            Right results -> return (Map.fromList (map (\r -> (iiId r,r)) results))-       Left (ReadProcessException _ _ _ _) -> return Map.empty+       Left (ReadProcessException{}) -> return Map.empty        Left e -> throwM e  -- | Pull latest version of configured Docker image from registry.@@ -623,7 +634,7 @@   do config <- asks getConfig      let docker = configDocker config      envOverride <- getEnvOverride (configPlatform config)-     checkDockerVersion envOverride+     checkDockerVersion envOverride docker      pullImage envOverride docker (dockerImage docker)  -- | Pull Docker image from registry.@@ -650,8 +661,8 @@ -- | Check docker version (throws exception if incorrect) checkDockerVersion     :: (MonadIO m, MonadThrow m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-    => EnvOverride -> m ()-checkDockerVersion envOverride =+    => EnvOverride -> DockerOpts -> m ()+checkDockerVersion envOverride docker =   do dockerExists <- doesExecutableExist envOverride "docker"      unless dockerExists (throwM DockerNotInstalledException)      dockerVersionOut <- readDockerProcess envOverride ["--version"]@@ -663,12 +674,14 @@                throwM (DockerTooOldException minimumDockerVersion v')              | v' `elem` prohibitedDockerVersions ->                throwM (DockerVersionProhibitedException prohibitedDockerVersions v')+             | not (v' `withinRange` dockerRequireDockerVersion docker) ->+               throwM (BadDockerVersionException (dockerRequireDockerVersion docker) v')              | otherwise ->                return ()            _ -> throwM InvalidVersionOutputException        _ -> throwM InvalidVersionOutputException-  where minimumDockerVersion = $(mkVersion "1.3.0")-        prohibitedDockerVersions = [$(mkVersion "1.2.0")]+  where minimumDockerVersion = $(mkVersion "1.6.0")+        prohibitedDockerVersions = []  -- | Remove the project's Docker sandbox. reset :: (MonadIO m) => Maybe (Path Abs Dir) -> Bool -> m ()@@ -679,6 +692,94 @@             [])   where projectRoot = fromMaybeProjectRoot maybeProjectRoot +-- | The Docker container "entrypoint": special actions performed when first entering+-- a container, such as switching the UID/GID to the "outside-Docker" user's.+entrypoint :: (MonadIO m, MonadBaseControl IO m, MonadCatch m, MonadLogger m)+           => Config -> DockerEntrypoint -> m ()+entrypoint config@Config{..} DockerEntrypoint{..} = do+  modifyMVar_ entrypointMVar $ \alreadyRan -> do+    -- Only run the entrypoint once+    unless alreadyRan $ do+      envOverride <- getEnvOverride configPlatform+      homeDir <- parseAbsDir =<< liftIO (getEnv "HOME")+      -- Get the UserEntry for the 'stack' user in the image, if it exists+      estackUserEntry0 <- liftIO $ tryJust (guard . isDoesNotExistError) $+        User.getUserEntryForName stackUserName+      -- Switch UID/GID if needed, and update user's home directory+      case deUidGid of+        Nothing -> return ()+        Just (0,_) -> return ()+        Just (uid,gid) -> updateOrCreateStackUser envOverride estackUserEntry0 homeDir uid gid+      case estackUserEntry0 of+        Left _ -> return ()+        Right ue -> do+          -- If the 'stack' user exists in the image, copy any build plans and package indices from+          -- its original home directory to the host's stack root, to avoid needing to download them+          origStackHomeDir <- parseAbsDir (User.homeDirectory ue)+          let origStackRoot = origStackHomeDir </> $(mkRelDir ("." ++ stackProgName))+          buildPlanDirExists <- dirExists (buildPlanDir origStackRoot)+          when buildPlanDirExists $ do+            (_, buildPlans) <- listDirectory (buildPlanDir origStackRoot)+            forM_ buildPlans $ \srcBuildPlan -> do+              let destBuildPlan = buildPlanDir configStackRoot </> filename srcBuildPlan+              exists <- fileExists destBuildPlan+              unless exists $ do+                createTree (parent destBuildPlan)+                copyFile srcBuildPlan destBuildPlan+          forM_ configPackageIndices $ \pkgIdx -> do+            msrcIndex <- flip runReaderT (config{configStackRoot = origStackRoot}) $ do+               srcIndex <- configPackageIndex (indexName pkgIdx)+               exists <- fileExists srcIndex+               return $ if exists+                 then Just srcIndex+                 else Nothing+            case msrcIndex of+              Nothing -> return ()+              Just srcIndex -> do+                flip runReaderT config $ do+                  destIndex <- configPackageIndex (indexName pkgIdx)+                  exists <- fileExists destIndex+                  unless exists $ do+                    createTree (parent destIndex)+                    copyFile srcIndex destIndex+    return True+  where+    updateOrCreateStackUser envOverride estackUserEntry homeDir uid gid = do+      case estackUserEntry of+        Left _ -> do+          -- If no 'stack' user in image, create one with correct UID/GID and home directory+          readProcessNull Nothing envOverride "groupadd"+            ["-o"+            ,"--gid",show gid+            ,stackUserName]+          readProcessNull Nothing envOverride "useradd"+            ["-oN"+            ,"--uid",show uid+            ,"--gid",show gid+            ,"--home",toFilePathNoTrailingSep homeDir+            ,stackUserName]+        Right _ -> do+          -- If there is already a 'stack' user in thr image, adjust its UID/GID and home directory+          readProcessNull Nothing envOverride "usermod"+            ["-o"+            ,"--uid",show uid+            ,"--home",toFilePathNoTrailingSep homeDir+            ,stackUserName]+          readProcessNull Nothing envOverride "groupmod"+            ["-o"+            ,"--gid",show gid+            ,stackUserName]+      -- 'setuid' to the wanted UID and GID+      liftIO $ do+        User.setGroupID gid+        User.setUserID uid+    stackUserName = "stack"::String++-- | MVar used to ensure the Docker entrypoint is performed exactly once+entrypointMVar :: MVar Bool+{-# NOINLINE entrypointMVar #-}+entrypointMVar = unsafePerformIO (newMVar False)+ -- | Remove the contents of a directory, without removing the directory itself. -- This is used instead of 'FS.removeTree' to clear bind-mounted directories, since -- removing the root of the bind-mount won't work.@@ -703,8 +804,7 @@ readDockerProcess     :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)     => EnvOverride -> [String] -> m BS.ByteString-readDockerProcess envOverride args =-  readProcessStdout Nothing envOverride "docker" args+readDockerProcess envOverride = readProcessStdout Nothing envOverride "docker"  -- | Subdirectories of the home directory to sandbox between GHC/Stackage versions. sandboxedHomeSubdirectories :: [Path Rel Dir]@@ -717,9 +817,13 @@ homeDirName :: Path Rel Dir homeDirName = $(mkRelDir "_home/") +-- | Directory where 'stack' executable is bind-mounted in Docker container+hostBinDir :: FilePath+hostBinDir = "/opt/host/bin"+ -- | Convenience function to decode ByteString to String. decodeUtf8 :: BS.ByteString -> String-decodeUtf8 bs = T.unpack (T.decodeUtf8 (bs))+decodeUtf8 bs = T.unpack (T.decodeUtf8 bs)  -- | Convenience function constructing message for @$log*@. concatT :: [String] -> Text@@ -735,7 +839,7 @@  -- | Environment variable used to indicate stack is running in container. inContainerEnvVar :: String-inContainerEnvVar = concat [map toUpper stackProgName,"_IN_CONTAINER"]+inContainerEnvVar = fmap toUpper stackProgName ++ "_IN_CONTAINER"  -- | Command-line argument for "docker" dockerCmdName :: String@@ -749,10 +853,14 @@ dockerCleanupCmdName :: String dockerCleanupCmdName = "cleanup" --- | Command-line option for @--internal-re-exec@.+-- | Command-line option for @--internal-re-exec-version@. reExecArgName :: String reExecArgName = "internal-re-exec-version" +-- | Platform that Docker containers run+dockerContainerPlatform :: Platform+dockerContainerPlatform = Platform X86_64 Linux+ -- | Options for 'cleanup'. data CleanupOpts = CleanupOpts   { dcAction                                :: !CleanupAction@@ -774,28 +882,31 @@   {iiConfig      :: ImageConfig   ,iiCreated     :: UTCTime   ,iiId          :: String-  ,iiVirtualSize :: Maybe Integer }+  ,iiVirtualSize :: Maybe Integer}   deriving (Show)  -- | Parse @docker inspect@ output. instance FromJSON Inspect where   parseJSON v =     do o <- parseJSON v-       (Inspect <$> o .: T.pack "Config"-                <*> o .: T.pack "Created"-                <*> o .: T.pack "Id"-                <*> o .:? T.pack "VirtualSize")+       Inspect <$> o .: "Config"+               <*> o .: "Created"+               <*> o .: "Id"+               <*> o .:? "VirtualSize"  -- | Parsed @Config@ section of @docker inspect@ output. data ImageConfig = ImageConfig-  {icEnv :: [String]}+  {icEnv :: [String]+  ,icEntrypoint :: [String]}   deriving (Show)  -- | Parse @Config@ section of @docker inspect@ output. instance FromJSON ImageConfig where   parseJSON v =     do o <- parseJSON v-       (ImageConfig <$> o .:? T.pack "Env" .!= [])+       ImageConfig+         <$> o .:? "Env" .!= []+         <*> o .:? "Entrypoint" .!= []  -- | Exceptions thrown by Stack.Docker. data StackDockerException@@ -821,6 +932,8 @@     -- ^ Installed version of @docker@ below minimum version.   | DockerVersionProhibitedException [Version] Version     -- ^ Installed version of @docker@ is prohibited.+  | BadDockerVersionException VersionRange Version+    -- ^ Installed version of @docker@ is out of range specified in config file.   | InvalidVersionOutputException     -- ^ Invalid output from @docker --version@.   | HostStackTooOldException Version (Maybe Version)@@ -841,7 +954,7 @@ -- | Show instance for StackDockerException. instance Show StackDockerException where   show DockerMustBeEnabledException =-    concat ["Docker must be enabled in your configuration file to use this command."]+    "Docker must be enabled in your configuration file to use this command."   show OnlyOnHostException =     "This command must be run on host OS (not in a Docker container)."   show (InspectFailedException image) =@@ -869,15 +982,26 @@   show (DockerTooOldException minVersion haveVersion) =     concat ["Minimum docker version '"            ,versionString minVersion-           ,"' is required (you have '"+           ,"' is required by "+           ,stackProgName+           ," (you have '"            ,versionString haveVersion            ,"')."]   show (DockerVersionProhibitedException prohibitedVersions haveVersion) =-    concat ["These Docker versions are prohibited (you have '"+    concat ["These Docker versions are incompatible with "+           ,stackProgName+           ," (you have '"            ,versionString haveVersion            ,"'): "-           ,concat (intersperse ", " (map versionString prohibitedVersions))+           ,intercalate ", " (map versionString prohibitedVersions)            ,"."]+  show (BadDockerVersionException requiredRange haveVersion) =+    concat ["The version of 'docker' you are using ("+           ,show haveVersion+           ,") is outside the required\n"+           ,"version range specified in stack.yaml ("+           ,T.unpack (versionRangeText requiredRange)+           ,")."]   show InvalidVersionOutputException =     "Cannot get Docker version (invalid 'docker --version' output)."   show (HostStackTooOldException minVersion (Just hostVersion)) =@@ -913,9 +1037,15 @@     , display dockerContainerPlatform     , " platform" ] --- | Platform that Docker containers run-dockerContainerPlatform :: Platform-dockerContainerPlatform = Platform X86_64 Linux+-- | Function to get command and arguments to run in Docker container+type GetCmdArgs env m+   = M env m+  => DockerOpts+  -> EnvOverride+  -> Inspect+  -> Bool+  -> m (FilePath,[String],[(String,String)],[Mount])+  type M env m = (MonadIO m,MonadReader env m,MonadLogger m,MonadBaseControl IO m,MonadCatch m                ,HasConfig env,HasTerminal env,HasReExec env,HasHttpManager env,MonadMask m)
src/Stack/Dot.hs view
@@ -92,7 +92,7 @@   (_,_,locals,_,sourceMap) <- loadSourceMap NeedTargets defaultBuildOpts   let graph = Map.fromList (localDependencies dotOpts locals)   menv <- getMinimalEnvOverride-  installedMap <- fmap thrd . fst3 <$> getInstalled menv+  installedMap <- fmap snd . fst4 <$> getInstalled menv                                                    (GetInstalledOpts False False)                                                    sourceMap   withLoadPackage menv (\loader -> do@@ -105,16 +105,8 @@         fmap3 :: Functor f => (d -> e) -> (a -> b -> c -> f d) -> a -> b -> c -> f e         fmap3 f g a b c = f <$> g a b c -        thrd :: (a,b,c) -> c-        thrd (_,_,x) = x--        fst3 :: (a,b,c) -> a-        fst3 (x,_,_) = x---- Given an 'Installed' try to get the 'Version'-libVersionFromInstalled :: Installed -> Maybe Version-libVersionFromInstalled (Library (PackageIdentifier _ v) _) = Just v-libVersionFromInstalled (Executable _) = Nothing+        fst4 :: (a,b,c,d) -> a+        fst4 (x,_,_,_) = x  listDependencies :: (HasEnvConfig env                     ,HasHttpManager env@@ -198,8 +190,7 @@   case Map.lookup pkgName sourceMap of     Just (PSLocal lp) -> pure ((packageAllDeps &&& (Just . packageVersion)) (lpPackage lp))     Just (PSUpstream version _ flags) -> loadPackageDeps pkgName version flags-    Nothing -> pure (Set.empty, do m' <- T.traverse libVersionFromInstalled installed-                                   Map.lookup pkgName m')+    Nothing -> pure (Set.empty, fmap installedVersion (Map.lookup pkgName installed))  -- | Resolve the direct (depth 0) external dependencies of the given local packages localDependencies :: DotOpts -> [LocalPackage] -> [(PackageName,(Set PackageName,Maybe Version))]
src/Stack/Fetch.hs view
@@ -38,8 +38,7 @@ import           Control.Monad.Catch import           Control.Monad.IO.Class import           Control.Monad.Logger-import           Control.Monad.Reader           (asks)-import           Control.Monad.Reader           (runReaderT)+import           Control.Monad.Reader           (asks, runReaderT) import           Control.Monad.Trans.Control import           Crypto.Hash                    (SHA512 (..)) import           Data.ByteString                (ByteString)@@ -50,7 +49,9 @@ import           Data.Function                  (fix) import           Data.IORef                     (newIORef, readIORef,                                                  writeIORef)-import           Data.List                      (intercalate, intersperse)+import           Data.List                      (intercalate)+import           Data.List.NonEmpty             (NonEmpty)+import qualified Data.List.NonEmpty             as NE import           Data.Map                       (Map) import qualified Data.Map                       as Map import           Data.Maybe                     (maybeToList, catMaybes)@@ -78,14 +79,17 @@                                                  SeekMode (AbsoluteSeek), hSeek,                                                  withBinaryFile) import           System.PosixCompat             (setFileMode)+import           Text.EditDistance              as ED +type PackageCaches = Map PackageIdentifier (PackageIndex, PackageCache)+ data FetchException     = Couldn'tReadIndexTarball FilePath Tar.FormatError     | Couldn'tReadPackageTarball FilePath SomeException     | UnpackDirectoryAlreadyExists (Set FilePath)     | CouldNotParsePackageSelectors [String]     | UnknownPackageNames (Set PackageName)-    | UnknownPackageIdentifiers (Set PackageIdentifier)+    | UnknownPackageIdentifiers (Set PackageIdentifier) String     deriving Typeable instance Exception FetchException @@ -111,9 +115,10 @@     show (UnknownPackageNames names) =         "The following packages were not found in your indices: " ++         intercalate ", " (map packageNameString $ Set.toList names)-    show (UnknownPackageIdentifiers idents) =+    show (UnknownPackageIdentifiers idents suggestions) =         "The following package identifiers were not found in your indices: " ++-        intercalate ", " (map packageIdentifierString $ Set.toList idents)+        intercalate ", " (map packageIdentifierString $ Set.toList idents) +++        (if null suggestions then "" else "\n" ++ suggestions)  -- | Fetch packages into the cache without unpacking fetchPackages :: (MonadIO m, MonadBaseControl IO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadLogger m, MonadCatch m)@@ -193,14 +198,11 @@             go >>= either throwM return         Right x -> return x   where-    go = do-        (missingNames, missingIdents, idents) <- resolvePackagesAllowMissing menv idents0 names0-        return $-            case () of-                ()-                    | not $ Set.null missingNames -> Left $ UnknownPackageNames missingNames-                    | not $ Set.null missingIdents -> Left $ UnknownPackageIdentifiers missingIdents-                    | otherwise -> Right idents+    go = r <$> resolvePackagesAllowMissing menv idents0 names0+    r (missingNames, missingIdents, idents)+      | not $ Set.null missingNames  = Left $ UnknownPackageNames       missingNames+      | not $ Set.null missingIdents = Left $ UnknownPackageIdentifiers missingIdents ""+      | otherwise                    = Right idents  resolvePackagesAllowMissing     :: (MonadIO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadLogger m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)@@ -289,37 +291,40 @@                 -- Update the cache and try again                 Nothing -> do                     let fuzzy = fuzzyLookupCandidates ident cachesCurr-                        fuzzyCandidatesText = case fuzzy of-                            Nothing -> ""-                            Just cs -> "Possible candidates: "-                                       <> commaSeparatedIdents cs-                                       <> ". "+                        suggestions = case fuzzy of+                            Nothing ->+                              case typoCorrectionCandidates ident cachesCurr of+                                  Nothing -> ""+                                  Just cs -> "Perhaps you meant " <>+                                    orSeparated cs <> "?"+                            Just cs -> "Possible candidates: " <>+                              commaSeparated (NE.map packageIdentifierString cs)+                              <> "."                     join $ modifyMVar updateRef $ \toUpdate ->                         if toUpdate then do                             runInBase $ do                                 $logInfo $ T.concat                                     [ "Didn't see "                                     , T.pack $ packageIdentifierString ident-                                    , " in your package indices. "-                                    , T.pack fuzzyCandidatesText+                                    , " in your package indices.\n"                                     , "Updating and trying again."                                     ]                                 updateAllIndices menv                                 caches <- getPackageCaches menv                                 liftIO $ writeIORef icaches caches                             return (False, doLookup ident)-                        else return (toUpdate, throwM (unknownIdent ident))+                        else return (toUpdate,+                                     throwM $ UnknownPackageIdentifiers+                                       (Set.singleton ident) suggestions)     inner doLookup-  where-    unknownIdent = UnknownPackageIdentifiers . Set.singleton-    commaSeparatedIdents = F.fold . intersperse ", " . map packageIdentifierString -type PackageCaches = Map PackageIdentifier (PackageIndex, PackageCache)--lookupPackageIdentifierExact :: HasConfig env-                             => PackageIdentifier -> env -> PackageCaches-                             -> IO (Maybe ByteString)-lookupPackageIdentifierExact ident env caches = do+lookupPackageIdentifierExact+  :: HasConfig env+  => PackageIdentifier+  -> env+  -> PackageCaches+  -> IO (Maybe ByteString)+lookupPackageIdentifierExact ident env caches =     case Map.lookup ident caches of         Nothing -> return Nothing         Just (index, cache) -> do@@ -328,23 +333,36 @@                   $ \_ _ bs -> return bs             return $ Just bs --- TODO: use 'Maybe (NonEmpty PackageIdentifier)' return-type-fuzzyLookupCandidates :: PackageIdentifier -> PackageCaches-                      -> Maybe [PackageIdentifier]+-- | Given package identifier and package caches, return list of packages+-- with the same name and the same two first version number components found+-- in the caches.+fuzzyLookupCandidates+  :: PackageIdentifier+  -> PackageCaches+  -> Maybe (NonEmpty PackageIdentifier) fuzzyLookupCandidates (PackageIdentifier name ver) caches =-    if null sameMajor then Nothing else Just (map fst sameMajor)-  where-    sameMajor = filter (\(PackageIdentifier _ v, _) ->-                             toMajorVersion ver == toMajorVersion v)-                       sameIdentCaches-    sameIdentCaches = maybe biggerFiltered-                            (\z -> (zeroIdent, z) : biggerFiltered)-                            zeroVer-    biggerFiltered = takeWhile (\(PackageIdentifier n _, _) -> name == n)-                               (Map.toList bigger)-    zeroIdent = PackageIdentifier name $(mkVersion "0.0")-    (_, zeroVer, bigger) = Map.splitLookup zeroIdent caches+  let (_, zero, bigger) = Map.splitLookup zeroIdent caches+      zeroIdent         = PackageIdentifier name $(mkVersion "0.0")+      sameName  (PackageIdentifier n _) = n == name+      sameMajor (PackageIdentifier _ v) = toMajorVersion v == toMajorVersion ver+  in NE.nonEmpty . filter sameMajor $ maybe [] (pure . const zeroIdent) zero+         <> takeWhile sameName (Map.keys bigger) +-- | Try to come up with typo corrections for given package identifier using+-- package caches. This should be called before giving up, i.e. when+-- 'fuzzyLookupCandidates' cannot return anything.+typoCorrectionCandidates+  :: PackageIdentifier+  -> PackageCaches+  -> Maybe (NonEmpty String)+typoCorrectionCandidates ident =+  let getName = packageNameString . packageIdentifierName+      name    = getName ident+  in  NE.nonEmpty+    . Map.keys+    . Map.filterWithKey (const . (== 1) . damerauLevenshtein name)+    . Map.mapKeys getName+ -- | Figure out where to fetch from. getToFetch :: (MonadThrow m, MonadIO m, MonadReader env m, HasConfig env)            => Maybe (Path Abs Dir) -- ^ directory to unpack into, @Nothing@ means no unpack@@ -433,10 +451,10 @@         let downloadReq = DownloadRequest                 { drRequest = req                 , drHashChecks = map toHashCheck $ maybeToList (tfSHA512 toFetch)-                , drLengthCheck = fmap fromIntegral $ tfSize toFetch+                , drLengthCheck = fromIntegral <$> tfSize toFetch                 , drRetryPolicy = drRetryPolicyDefault                 }-        let progressSink _ = do+        let progressSink _ =                 liftIO $ runInBase $ $logInfo $ packageIdentifierText ident <> ": download"         _ <- verifiedDownload downloadReq destpath progressSink @@ -521,3 +539,15 @@         workers 1 = Concurrently worker         workers i = Concurrently worker *> workers (i - 1)     liftIO $ runConcurrently $ workers cnt++damerauLevenshtein :: String -> String -> Int+damerauLevenshtein = ED.restrictedDamerauLevenshteinDistance ED.defaultEditCosts++orSeparated :: NonEmpty String -> String+orSeparated xs+  | NE.length xs == 1 = NE.head xs+  | NE.length xs == 2 = NE.head xs <> " or " <> NE.last xs+  | otherwise = intercalate ", " (NE.init xs) <> ", or " <> NE.last xs++commaSeparated :: NonEmpty String -> String+commaSeparated = F.fold . NE.intersperse ", "
src/Stack/FileWatch.hs view
@@ -21,7 +21,6 @@ import Data.String (fromString) import Data.Traversable (forM) import GHC.IO.Handle (hIsTerminalDevice)-import Ignore import Path import System.Console.ANSI import System.Exit@@ -33,13 +32,11 @@ printExceptionStderr e =     L.hPut stderr $ toLazyByteString $ fromShow e <> copyByteString "\n" -fileWatch :: IO (Path Abs Dir)-          -> ((Set (Path Abs File) -> IO ()) -> IO ())+fileWatch :: ((Set (Path Abs File) -> IO ()) -> IO ())           -> IO () fileWatch = fileWatchConf defaultConfig -fileWatchPoll :: IO (Path Abs Dir)-               -> ((Set (Path Abs File) -> IO ()) -> IO ())+fileWatchPoll :: ((Set (Path Abs File) -> IO ()) -> IO ())                -> IO () fileWatchPoll = fileWatchConf $ defaultConfig { confUsePolling = True } @@ -48,21 +45,12 @@ -- The action provided takes a callback that is used to set the files to be -- watched. When any of those files are changed, we rerun the action again. fileWatchConf :: WatchConfig-              -> IO (Path Abs Dir)               -> ((Set (Path Abs File) -> IO ()) -> IO ())               -> IO ()-fileWatchConf cfg getProjectRoot inner = withManagerConf cfg $ \manager -> do+fileWatchConf cfg inner = withManagerConf cfg $ \manager -> do     allFiles <- newTVarIO Set.empty     dirtyVar <- newTVarIO True     watchVar <- newTVarIO Map.empty-    projRoot <- getProjectRoot-    mChecker <- findIgnoreFiles [VCSGit, VCSMercurial, VCSDarcs] projRoot >>= buildChecker-    (FileIgnoredChecker isFileIgnored) <--        case mChecker of-          Left err ->-              do putStrLn $ "Failed to parse VCS's ignore file: " ++ err-                 return $ FileIgnoredChecker (const False)-          Right chk -> return chk      let onChange event = atomically $ do             files <- readTVar allFiles@@ -96,7 +84,7 @@                 return Nothing             startListening = Map.mapWithKey $ \dir () -> do                 let dir' = fromString $ toFilePath dir-                listen <- watchDir manager dir' (not . isFileIgnored . eventPath) onChange+                listen <- watchDir manager dir' (const True) onChange                 return $ Just listen      let watchInput = do@@ -108,11 +96,11 @@                         putStrLn "help: display this help"                         putStrLn "quit: exit"                         putStrLn "build: force a rebuild"-                        putStrLn "watched: display watched directories"+                        putStrLn "watched: display watched files"                     "build" -> atomically $ writeTVar dirtyVar True                     "watched" -> do-                        watch <- readTVarIO watchVar-                        mapM_ (putStrLn . toFilePath) (Map.keys watch)+                        watch <- readTVarIO allFiles+                        mapM_ putStrLn (Set.toList watch)                     "" -> atomically $ writeTVar dirtyVar True                     _ -> putStrLn $ concat                         [ "Unknown command: "
src/Stack/GhcPkg.hs view
@@ -10,23 +10,16 @@ -- | Functions for the GHC package database.  module Stack.GhcPkg-  (findGhcPkgId-  ,findGhcPkgKey-  ,getGlobalDB+  (getGlobalDB   ,EnvOverride   ,envHelper   ,createDatabase   ,unregisterGhcPkgId   ,getCabalPkgVer-  ,findGhcPkgHaddockHtml-  ,findGhcPkgDepends-  ,findTransitiveGhcPkgDepends-  ,listGhcPkgDbs   ,ghcPkgExeName   ,mkGhcPackagePath)   where -import           Control.Applicative import           Control.Monad import           Control.Monad.Catch import           Control.Monad.IO.Class@@ -35,20 +28,18 @@ import qualified Data.ByteString.Char8 as S8 import           Data.Either import           Data.List-import qualified Data.Map as Map import           Data.Maybe-import           Data.Set (Set)-import qualified Data.Set as Set import           Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import           Path (Path, Abs, Dir, toFilePath, parent, parseAbsDir)+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO (dirExists, createTree) import           Prelude hiding (FilePath) import           Stack.Constants import           Stack.Types-import           System.Directory (canonicalizePath, doesDirectoryExist)-import           System.FilePath (FilePath, searchPathSeparator, dropTrailingPathSeparator)+import           System.Directory (canonicalizePath)+import           System.FilePath (searchPathSeparator) import           System.Process.Read  -- | Get the global package database@@ -76,12 +67,11 @@        -> m (Either ReadProcessException S8.ByteString) ghcPkg menv wc pkgDbs args = do     eres <- go-    r <- case eres of-            Left _ -> do-                mapM_ (createDatabase menv wc) pkgDbs-                go-            Right _ -> return eres-    return r+    case eres of+          Left _ -> do+              mapM_ (createDatabase menv wc) pkgDbs+              go+          Right _ -> return eres   where     go = tryProcessStdout Nothing menv (ghcPkgExeName wc) args'     args' = packageDbFlags pkgDbs ++ args@@ -108,7 +98,7 @@ packageDbFlags :: [Path Abs Dir] -> [String] packageDbFlags pkgDbs =           "--no-user-package-db"-        : map (\x -> ("--package-db=" ++ toFilePath x)) pkgDbs+        : map (\x -> "--package-db=" ++ toFilePath x) pkgDbs  -- | Get the value of a field of the package. findGhcPkgField@@ -134,31 +124,6 @@   where     stripCR t = fromMaybe t (T.stripSuffix "\r" t) --- | Get the id of the package e.g. @foo-0.0.0-9c293923c0685761dcff6f8c3ad8f8ec@.-findGhcPkgId :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-             => EnvOverride-             -> WhichCompiler-             -> [Path Abs Dir] -- ^ package databases-             -> PackageName-             -> m (Maybe GhcPkgId)-findGhcPkgId menv wc pkgDbs name = do-    mpid <- findGhcPkgField menv wc pkgDbs (packageNameString name) "id"-    case mpid of-        Just !pid -> return (parseGhcPkgId (T.encodeUtf8 pid))-        _ -> return Nothing---- | Get the package key e.g. @foo_9bTCpMF7G4UFWJJvtDrIdB@.------ NOTE: GHC > 7.10 only! Will always yield 'Nothing' otherwise.-findGhcPkgKey :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-             => EnvOverride-             -> WhichCompiler-             -> [Path Abs Dir] -- ^ package databases-             -> PackageName-             -> m (Maybe Text)-findGhcPkgKey menv wc pkgDbs name =-    findGhcPkgField menv wc pkgDbs (packageNameString name) "key"- -- | Get the version of the package findGhcPkgVersion :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)                   => EnvOverride@@ -172,76 +137,6 @@         Just !v -> return (parseVersion (T.encodeUtf8 v))         _ -> return Nothing --- | Get the Haddock HTML documentation path of the package.-findGhcPkgHaddockHtml :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-                      => EnvOverride-                      -> WhichCompiler-                      -> [Path Abs Dir] -- ^ package databases-                      -> String -- ^ PackageIdentifier or GhcPkgId-                      -> m (Maybe (PackageIdentifier, Path Abs Dir))-findGhcPkgHaddockHtml menv wc pkgDbs ghcPkgId = do-    mpath <- findGhcPkgField menv wc pkgDbs ghcPkgId "haddock-html"-    mid <- findGhcPkgField menv wc pkgDbs ghcPkgId "id"-    mversion <- findGhcPkgField menv wc pkgDbs ghcPkgId "version"-    let mpkgId = PackageIdentifier-            <$> (mid >>= parsePackageName . T.encodeUtf8)-            <*> (mversion >>= parseVersion . T.encodeUtf8)-    case (,) <$> mpath <*> mpkgId of-        Just (path0, pkgId) -> do-            let path = T.unpack path0-            exists <- liftIO $ doesDirectoryExist path-            path' <- if exists-                then liftIO $ canonicalizePath path-                else return path--            return $ fmap (pkgId,) (parseAbsDir path')-        _ -> return Nothing---- | Finds dependencies of package, and all their dependencies, etc.-findTransitiveGhcPkgDepends-    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-    => EnvOverride-    -> WhichCompiler-    -> [Path Abs Dir] -- ^ package databases-    -> PackageIdentifier-    -> m (Set PackageIdentifier)-findTransitiveGhcPkgDepends menv wc pkgDbs pkgId0 =-    liftM (Set.fromList . Map.elems)-    (go (packageIdentifierString pkgId0) Map.empty)-  where-    go pkgId res = do-        deps <- findGhcPkgDepends menv wc pkgDbs pkgId-        loop deps res-    loop [] res = return res-    loop (dep:deps) res = do-        if Map.member dep res-            then loop deps res-            else do-                let pkgId = ghcPkgIdString dep-                mname <- findGhcPkgField menv wc pkgDbs pkgId "name"-                mversion <- findGhcPkgField menv wc pkgDbs pkgId "version"-                let mident = do-                        name <- mname >>= parsePackageName . T.encodeUtf8-                        version <- mversion >>= parseVersion . T.encodeUtf8-                        Just $ PackageIdentifier name version-                    res' = maybe id (Map.insert dep) mident res-                res'' <- go pkgId res'-                -- FIXME is the Map.union actually necessary?-                loop deps (Map.union res res'')---- | Get the dependencies of the package.-findGhcPkgDepends :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-                  => EnvOverride-                  -> WhichCompiler-                  -> [Path Abs Dir] -- ^ package databases-                  -> String -- ^ package identifier or GhcPkgId-                  -> m [GhcPkgId]-findGhcPkgDepends menv wc pkgDbs pkgId = do-    mdeps <- findGhcPkgField menv wc pkgDbs pkgId "depends"-    case mdeps of-        Just !deps -> return (mapMaybe (parseGhcPkgId . T.encodeUtf8) (T.words deps))-        _ -> return []- unregisterGhcPkgId :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m, MonadBaseControl IO m)                     => EnvOverride                     -> WhichCompiler@@ -274,30 +169,12 @@         cabalPackageName >>=         maybe (throwM $ Couldn'tFindPkgId cabalPackageName) return -listGhcPkgDbs-    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)-    => EnvOverride -> WhichCompiler -> [Path Abs Dir] -> m [PackageIdentifier]-listGhcPkgDbs menv wc pkgDbs = do-    result <--        ghcPkg-            menv-            wc-            pkgDbs-            ["list", "--simple-output"]-    return $-        case result of-            Left{} -> []-            Right lbs -> mapMaybe parsePackageIdentifier (S8.words lbs)- -- | Get the value for GHC_PACKAGE_PATH-mkGhcPackagePath :: Bool -> Path Abs Dir -> Path Abs Dir -> Path Abs Dir -> Text-mkGhcPackagePath locals localdb deps globaldb =+mkGhcPackagePath :: Bool -> Path Abs Dir -> Path Abs Dir -> [Path Abs Dir] -> Path Abs Dir -> Text+mkGhcPackagePath locals localdb deps extras globaldb =   T.pack $ intercalate [searchPathSeparator] $ concat-    [ [toFilePathNoTrailingSlash localdb | locals]-    , [toFilePathNoTrailingSlash deps]-    , [toFilePathNoTrailingSlash globaldb]+    [ [toFilePathNoTrailingSep localdb | locals]+    , [toFilePathNoTrailingSep deps]+    , [toFilePathNoTrailingSep db | db <- reverse extras]+    , [toFilePathNoTrailingSep globaldb]     ]---- TODO: dedupe with copy in Stack.Setup-toFilePathNoTrailingSlash :: Path loc Dir -> FilePath-toFilePathNoTrailingSlash = dropTrailingPathSeparator . toFilePath
src/Stack/Ghci.hs view
@@ -5,20 +5,29 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RecordWildCards #-} --- | Run a GHCi configured with the user's project(s).+-- | Run a GHCi configured with the user's package(s). -module Stack.Ghci (GhciOpts(..),GhciPkgInfo(..), ghciSetup, ghci) where+module Stack.Ghci+    ( GhciOpts(..)+    , GhciPkgInfo(..)+    , ghciSetup+    , ghci+    ) where +import           Control.Exception.Enclosed (tryAny) import           Control.Monad.Catch import           Control.Monad.IO.Class import           Control.Monad.Logger import           Control.Monad.Reader import           Control.Monad.Trans.Resource+import           Data.Either import           Data.Function import           Data.List+import           Data.List.Extra (nubOrd) import           Data.Map.Strict (Map) import qualified Data.Map.Strict as M import           Data.Maybe+import           Data.Maybe.Extra (forMaybeM) import           Data.Monoid import           Data.Set (Set) import qualified Data.Set as S@@ -28,6 +37,8 @@ import           Distribution.Text (display) import           Network.HTTP.Client.Conduit import           Path+import           Path.Extra (toFilePathNoTrailingSep)+import           Path.IO import           Prelude import           Stack.Build import           Stack.Build.Installed@@ -38,74 +49,112 @@ import           Stack.Package import           Stack.Types import           Stack.Types.Internal+import           System.Directory (getTemporaryDirectory)  -- | Command-line options for GHC. data GhciOpts = GhciOpts-    {ghciTargets            :: ![Text]-    ,ghciArgs               :: ![String]-    ,ghciGhcCommand         :: !(Maybe FilePath)-    ,ghciNoLoadModules      :: !Bool-    ,ghciAdditionalPackages :: ![String]-    ,ghciMainIs             :: !(Maybe Text)-    } deriving (Show,Eq)+    { ghciNoBuild            :: !Bool+    , ghciArgs               :: ![String]+    , ghciGhcCommand         :: !(Maybe FilePath)+    , ghciNoLoadModules      :: !Bool+    , ghciAdditionalPackages :: ![String]+    , ghciMainIs             :: !(Maybe Text)+    , ghciBuildOpts          :: !BuildOpts+    } deriving Show  -- | Necessary information to load a package or its components. data GhciPkgInfo = GhciPkgInfo-  { ghciPkgName :: PackageName-  , ghciPkgOpts :: [String]-  , ghciPkgDir :: Path Abs Dir-  , ghciPkgModules :: Set ModuleName-  , ghciPkgModFiles :: Set (Path Abs File) -- ^ Module file paths.-  , ghciPkgCFiles :: Set (Path Abs File) -- ^ C files.-  , ghciPkgMainIs :: Map NamedComponent (Set (Path Abs File))-  }+    { ghciPkgName :: !PackageName+    , ghciPkgOpts :: ![(NamedComponent, BuildInfoOpts)]+    , ghciPkgDir :: !(Path Abs Dir)+    , ghciPkgModules :: !(Set ModuleName)+    , ghciPkgModFiles :: !(Set (Path Abs File)) -- ^ Module file paths.+    , ghciPkgCFiles :: !(Set (Path Abs File)) -- ^ C files.+    , ghciPkgMainIs :: !(Map NamedComponent (Set (Path Abs File)))+    , ghciPkgPackage :: !Package+    } deriving Show --- | Launch a GHCi session for the given local project targets with the+-- | Launch a GHCi session for the given local package targets with the -- given options and configure it with the load paths and extensions -- of those targets. ghci     :: (HasConfig r, HasBuildConfig r, HasHttpManager r, MonadMask m, HasLogLevel r, HasTerminal r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m, MonadBaseControl IO m)     => GhciOpts -> m () ghci GhciOpts{..} = do-    (targets,mainIsTargets,pkgs) <- ghciSetup ghciMainIs ghciTargets+    let bopts = ghciBuildOpts+            { boptsTestOpts = (boptsTestOpts ghciBuildOpts) { toDisableRun = True }+            , boptsBenchmarkOpts = (boptsBenchmarkOpts ghciBuildOpts) { beoDisableRun = True }+            }+    (targets,mainIsTargets,pkgs) <- ghciSetup bopts ghciNoBuild ghciMainIs+    config <- asks getConfig     bconfig <- asks getBuildConfig-    mainFile <- figureOutMainFile mainIsTargets targets pkgs+    mainFile <- figureOutMainFile bopts mainIsTargets targets pkgs     wc <- getWhichCompiler-    let pkgopts = concatMap ghciPkgOpts pkgs-        srcfiles-          | ghciNoLoadModules = []-          | otherwise =-              nub (maybe [] (return . toFilePath) mainFile <>-                   concatMap (map display . S.toList . ghciPkgModules) pkgs)+    let pkgopts = hidePkgOpt ++ genOpts ++ ghcOpts+        hidePkgOpt = if null pkgs then [] else ["-hide-all-packages"]+        genOpts = nubOrd (concatMap (concatMap (bioGeneratedOpts . snd) . ghciPkgOpts) pkgs)+        (omittedOpts, ghcOpts) = partition badForGhci $+            concatMap (concatMap (bioGhcOpts . snd) . ghciPkgOpts) pkgs +++            getUserOptions Nothing +++            concatMap (getUserOptions . Just . ghciPkgName) pkgs+        getUserOptions mpkg =+            map T.unpack (M.findWithDefault [] mpkg (configGhcOptions config))+        badForGhci x =+            isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky -static")+    unless (null omittedOpts) $+        $logWarn+            ("The following GHC options are incompatible with GHCi and have not been passed to it: " <>+             T.unwords (map T.pack (nubOrd omittedOpts)))+    let modulesToLoad = nubOrd $+            maybe [] (return . toFilePath) mainFile <>+            concatMap (map display . S.toList . ghciPkgModules) pkgs         odir =-            [ "-odir=" <> toFilePath (objectInterfaceDir bconfig)-            , "-hidir=" <> toFilePath (objectInterfaceDir bconfig)]+            [ "-odir=" <> toFilePathNoTrailingSep (objectInterfaceDir bconfig)+            , "-hidir=" <> toFilePathNoTrailingSep (objectInterfaceDir bconfig)]     $logInfo         ("Configuring GHCi with the following packages: " <>          T.intercalate ", " (map (packageNameText . ghciPkgName) pkgs))-    exec-        defaultEnvSettings-        (fromMaybe (compilerExeName wc) ghciGhcCommand)-        ("--interactive" : odir <> pkgopts <> srcfiles <> ghciArgs)+    let execGhci extras =+            exec defaultEnvSettings+                 (fromMaybe (compilerExeName wc) ghciGhcCommand)+                 ("--interactive" :+                 -- This initial "-i" resets the include directories to not+                 -- include CWD.+                  "-i" :+                  odir <> pkgopts <> ghciArgs <> extras)+    case ghciNoLoadModules of+        True -> execGhci []+        False -> do+            tmp <- liftIO getTemporaryDirectory+            withCanonicalizedTempDirectory+                tmp+                "ghci-script"+                (\tmpDir ->+                      do let scriptPath = tmpDir </> $(mkRelFile "ghci-script")+                             fp = toFilePath scriptPath+                             loadModules = ":load " <> unwords modulesToLoad+                             bringIntoScope = ":module + " <> unwords modulesToLoad+                         liftIO (writeFile fp (unlines [loadModules,bringIntoScope]))+                         finally (execGhci ["-ghci-script=" <> fp])+                                 (removeFile scriptPath))  -- | Figure out the main-is file to load based on the targets. Sometimes there -- is none, sometimes it's unambiguous, sometimes it's -- ambiguous. Warns and returns nothing if it's ambiguous. figureOutMainFile     :: (Monad m, MonadLogger m)-    => Maybe (Map PackageName SimpleTarget)+    => BuildOpts+    -> Maybe (Map PackageName SimpleTarget)     -> Map PackageName SimpleTarget     -> [GhciPkgInfo]     -> m (Maybe (Path Abs File))-figureOutMainFile mainIsTargets targets0 packages = do+figureOutMainFile bopts mainIsTargets targets0 packages =     case candidates of         [] -> return Nothing         [c@(_,_,fp)] -> do $logInfo ("Using main module: " <> renderCandidate c)                            return (Just fp)-        candidate:_ -> do-            let border = $logWarn "* * * * * * * *"-            border-            $logWarn ("The main module to load is ambiguous. Candidates are: ")+        candidate:_ -> borderedWarning $ do+            $logWarn "The main module to load is ambiguous. Candidates are: "             forM_ (map renderCandidate candidates) $logWarn             $logWarn                 "None will be loaded. You can specify which one to pick by: "@@ -115,7 +164,6 @@             $logWarn                 (" 2) Specifying what the main is e.g. stack ghci " <>                  sampleMainIsArg candidate)-            border             return Nothing   where     targets = fromMaybe targets0 mainIsTargets@@ -125,15 +173,14 @@             Nothing -> []             Just target -> do                 (component,mains) <--                    M.toList-                        (M.filterWithKey wantedComponent (ghciPkgMainIs pkg))+                    M.toList $+                    M.filterWithKey (\k _ -> k `S.member` wantedComponents)+                                    (ghciPkgMainIs pkg)                 main <- S.toList mains                 return (ghciPkgName pkg, component, main)-                where wantedComponent namedC _ =-                          case target of-                              STLocalAll -> True-                              STLocalComps cs -> S.member namedC cs-                              _ -> False+              where+                wantedComponents =+                    wantedPackageComponents bopts target (ghciPkgPackage pkg)     renderCandidate (pkgName,namedComponent,mainIs) =         "Package `" <> packageNameText pkgName <> "' component " <>         renderComp namedComponent <>@@ -154,32 +201,22 @@ -- information to load that package/components. ghciSetup     :: (HasConfig r, HasHttpManager r, HasBuildConfig r, MonadMask m, HasTerminal r, HasLogLevel r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m, MonadBaseControl IO m)-    => Maybe Text-    -> [Text]+    => BuildOpts+    -> Bool+    -> Maybe Text     -> m (Map PackageName SimpleTarget, Maybe (Map PackageName SimpleTarget), [GhciPkgInfo])-ghciSetup mainIs stringTargets = do-    (_,_,targets) <--        parseTargetsFromBuildOpts-            AllowNoTargets-            defaultBuildOpts-            { boptsTargets = stringTargets-            }+ghciSetup bopts noBuild mainIs = do+    (_,_,targets) <- parseTargetsFromBuildOpts AllowNoTargets bopts     mainIsTargets <-         case mainIs of             Nothing -> return Nothing             Just target -> do-                (_,_,targets') <--                    parseTargetsFromBuildOpts-                        AllowNoTargets-                        defaultBuildOpts-                        { boptsTargets = [target]-                        }+                (_,_,targets') <- parseTargetsFromBuildOpts AllowNoTargets bopts { boptsTargets = [target] }                 return (Just targets')-    let bopts = makeBuildOpts targets     econfig <- asks getEnvConfig     (realTargets,_,_,_,sourceMap) <- loadSourceMap AllowNoTargets bopts     menv <- getMinimalEnvOverride-    (installedMap, _, _) <- getInstalled+    (installedMap, _, _, _) <- getInstalled         menv         GetInstalledOpts             { getInstalledProfiling = False@@ -187,8 +224,7 @@             }         sourceMap     locals <--        liftM catMaybes $-        forM (M.toList (envConfigPackages econfig)) $+        forMaybeM (M.toList (envConfigPackages econfig)) $         \(dir,validWanted) ->              do cabalfp <- getCabalFileName dir                 name <- parsePackageNameFromFilePath cabalfp@@ -198,97 +234,133 @@                                  return (Just (name, (cabalfp, simpleTargets)))                              Nothing -> return Nothing                     else return Nothing+    -- Try to build, but optimistically launch GHCi anyway if it fails (#1065)+    unless noBuild $ do+        eres <- tryAny $ build (const (return ())) Nothing bopts+        case eres of+            Right () -> return ()+            Left err -> do+                $logError $ T.pack (show err)+                $logWarn "Warning: build failed, but optimistically launching GHCi anyway"+    -- Load the list of modules _after_ building, to catch changes in unlisted dependencies (#1180)+    let localLibs = [name | (name, (_, target)) <- locals, hasLocalComp isCLib target]     infos <-         forM locals $-        \(name,(cabalfp,components)) ->-             makeGhciPkgInfo sourceMap installedMap (map fst locals) name cabalfp components-    unless (M.null realTargets) (build (const (return ())) Nothing bopts)+        \(name,(cabalfp,target)) ->+             makeGhciPkgInfo bopts sourceMap installedMap localLibs name cabalfp target+    checkForIssues infos     return (realTargets, mainIsTargets, infos)   where-    makeBuildOpts targets =-        base-        { boptsTargets = stringTargets-        , boptsTests = any (hasLocalComp isCTest) elems-        , boptsBenchmarks = any (hasLocalComp isCBench) elems-        , boptsTestOpts = (boptsTestOpts base)-          { toDisableRun = True-          , toRerunTests = False-          }-        , boptsBenchmarkOpts = (boptsBenchmarkOpts base)-          { beoDisableRun = True-          }-        , boptsBuildSubset = BSOnlyDependencies-        }-      where-        base = defaultBuildOpts-        elems = M.elems targets-        hasLocalComp p t =-            case t of-                STLocalComps s -> any p (S.toList s)-                STLocalAll -> True-                _ -> False-        isCTest nc =-            case nc of-                CTest{} -> True-                _ -> False-        isCBench nc =-            case nc of-                CBench{} -> True-                _ -> False+    hasLocalComp p t =+        case t of+            STLocalComps s -> any p (S.toList s)+            STLocalAll -> True+            _ -> False  -- | Make information necessary to load the given package in GHCi. makeGhciPkgInfo     :: (MonadReader r m, HasEnvConfig r, MonadLogger m, MonadIO m, MonadCatch m)-    => SourceMap+    => BuildOpts+    -> SourceMap     -> InstalledMap     -> [PackageName]     -> PackageName     -> Path Abs File     -> SimpleTarget     -> m GhciPkgInfo-makeGhciPkgInfo sourceMap installedMap locals name cabalfp components = do+makeGhciPkgInfo bopts sourceMap installedMap locals name cabalfp target = do     econfig <- asks getEnvConfig     bconfig <- asks getBuildConfig     let config =             PackageConfig             { packageConfigEnableTests = True             , packageConfigEnableBenchmarks = True-            , packageConfigFlags = localFlags mempty bconfig name+            , packageConfigFlags = localFlags (boptsFlags bopts) bconfig name             , packageConfigCompilerVersion = envConfigCompilerVersion econfig             , packageConfigPlatform = configPlatform (getConfig bconfig)             }     (warnings,pkg) <- readPackage config cabalfp     mapM_ (printCabalFileWarning cabalfp) warnings-    (componentsModules,componentFiles,componentsOpts,generalOpts) <--        getPackageOpts (packageOpts pkg) sourceMap installedMap locals cabalfp-    let filterWithinWantedComponents m =-            M.elems-                (M.filterWithKey-                     (\k _ ->-                           case components of-                               STLocalComps cs -> S.member k cs-                               _ -> True)-                     m)+    (mods,files,opts) <- getPackageOpts (packageOpts pkg) sourceMap installedMap locals cabalfp+    let filteredOpts = filterWanted opts+        filterWanted = M.filterWithKey (\k _ -> k `S.member` allWanted)+        allWanted = wantedPackageComponents bopts target pkg+        setMapMaybe f = S.fromList . mapMaybe f . S.toList     return         GhciPkgInfo         { ghciPkgName = packageName pkg-        , ghciPkgOpts = filter-              (not . badForGhci)-              (generalOpts <>-               concat (filterWithinWantedComponents componentsOpts))+        , ghciPkgOpts = M.toList filteredOpts         , ghciPkgDir = parent cabalfp-        , ghciPkgModules = mconcat-              (filterWithinWantedComponents componentsModules)-        , ghciPkgModFiles = mconcat-              (filterWithinWantedComponents-                   (M.map (setMapMaybe dotCabalModulePath) componentFiles))-        , ghciPkgMainIs = M.map (setMapMaybe dotCabalMainPath) componentFiles-        , ghciPkgCFiles = mconcat-              (filterWithinWantedComponents-                   (M.map (setMapMaybe dotCabalCFilePath) componentFiles))+        , ghciPkgModules = mconcat (M.elems (filterWanted mods))+        , ghciPkgModFiles = mconcat (M.elems (filterWanted (M.map (setMapMaybe dotCabalModulePath) files)))+        , ghciPkgMainIs = M.map (setMapMaybe dotCabalMainPath) files+        , ghciPkgCFiles = mconcat (M.elems (filterWanted (M.map (setMapMaybe dotCabalCFilePath) files)))+        , ghciPkgPackage = pkg         }++-- NOTE: this should make the same choices as the components code in+-- 'loadLocalPackage'. Unfortunately for now we reiterate this logic+-- (differently).+wantedPackageComponents :: BuildOpts -> SimpleTarget -> Package -> Set NamedComponent+wantedPackageComponents _ (STLocalComps cs) _ = cs+wantedPackageComponents bopts STLocalAll pkg = S.fromList $+    (if packageHasLibrary pkg then [CLib] else []) +++    map CExe (S.toList (packageExes pkg)) <>+    (if boptsTests bopts then map CTest (S.toList (packageTests pkg)) else []) <>+    (if boptsBenchmarks bopts then map CBench (S.toList (packageBenchmarks pkg)) else [])+wantedPackageComponents _ _ _ = S.empty++checkForIssues :: (MonadThrow m, MonadLogger m) => [GhciPkgInfo] -> m ()+checkForIssues pkgs = do+    unless (null issues) $ borderedWarning $ do+        $logWarn "There are issues with this project which may prevent GHCi from working properly."+        $logWarn ""+        mapM_ $logWarn $ intercalate [""] issues+        $logWarn ""+        $logWarn "To resolve, remove the flag(s) from the cabal file(s) and instead put them at the top of the haskell files."+        $logWarn ""+        $logWarn "It isn't yet possible to load multiple packages into GHCi in all cases - see"+        $logWarn "https://ghc.haskell.org/trac/ghc/ticket/10827"   where-    badForGhci :: String -> Bool-    badForGhci x =-        isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky")-    setMapMaybe f = S.fromList . mapMaybe f . S.toList+    issues = concat+        [ mixedFlag "-XNoImplicitPrelude"+          [ "-XNoImplicitPrelude will be used, but GHCi will likely fail to build things which depend on the implicit prelude." ]+        , mixedFlag "-XCPP"+          [ "-XCPP will be used, but it can cause issues with multiline strings."+          , "See https://downloads.haskell.org/~ghc/7.10.2/docs/html/users_guide/options-phases.html#cpp-string-gaps"+          ]+        , mixedFlag "-XNoTraditionalRecordSyntax"+          [ "-XNoTraditionalRecordSyntax will be used, but it break modules which use record syntax." ]+        , mixedFlag "-XTemplateHaskell"+          [ "-XTemplateHaskell will be used, but it may cause compilation issues due to different parsing of ($)." ]+        , mixedFlag "-XSafe"+          [ "-XSafe will be used, but it will fail to compile unsafe modules." ]+        ]+    mixedFlag flag msgs =+        let x = partitionComps (== flag) in+        [ msgs ++ showWhich x | mixedSettings x ]+    mixedSettings (xs, ys) = xs /= [] && ys /= []+    showWhich (haveIt, don'tHaveIt) =+        [ "It is specified for:"+        , "    " <> renderPkgComponents haveIt+        , "But not for: "+        , "    " <> renderPkgComponents don'tHaveIt+        ]+    partitionComps f = (map fst xs, map fst ys)+      where+        (xs, ys) = partition (any f . snd) compsWithOpts+    compsWithOpts = map (\(k, bio) -> (k, bioGeneratedOpts bio ++ bioGhcOpts bio)) compsWithBios+    compsWithBios =+        [ ((ghciPkgName pkg, c), bio)+        | pkg <- pkgs+        , (c, bio) <- ghciPkgOpts pkg+        ]++borderedWarning :: MonadLogger m => m a -> m a+borderedWarning f = do+    $logWarn ""+    $logWarn "* * * * * * * *"+    x <- f+    $logWarn "* * * * * * * *"+    $logWarn ""+    return x
src/Stack/Ide.hs view
@@ -1,12 +1,11 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} --- | Run a IDE configured with the user's project(s).+-- | Run a IDE configured with the user's package(s).  module Stack.Ide     (ide, getPackageOptsAndTargetFiles)@@ -25,6 +24,7 @@ import           Distribution.System import           Network.HTTP.Client.Conduit import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO import           Stack.Constants import           Stack.Ghci (GhciPkgInfo(..), ghciSetup)@@ -32,6 +32,7 @@ import           Stack.Types import           Stack.Types.Internal import           System.Environment (lookupEnv)+import           System.FilePath (searchPathSeparator) import           System.Process.Run  -- | Launch a GHCi IDE for the given local project targets with the@@ -43,7 +44,11 @@     -> [String] -- ^ GHC options.     -> m () ide targets useropts = do-    (_realTargets,_,pkgs) <- ghciSetup Nothing targets+    let bopts = defaultBuildOpts+            { boptsTargets = targets+            , boptsBuildSubset = BSOnlyDependencies+            }+    (_realTargets,_,pkgs) <- ghciSetup bopts False Nothing     pwd <- getWorkingDir     (pkgopts,_srcfiles) <-         liftM mconcat $ forM pkgs $ getPackageOptsAndTargetFiles pwd@@ -52,14 +57,14 @@     mpath <- liftIO $ lookupEnv "PATH"     bindirs <- extraBinDirs `ap` return True {- include local bin -}     let pkgdbs =-            ["--package-db=" <> toFilePath depsdb <> ":" <> toFilePath localdb]+            ["--package-db=" <> toFilePathNoTrailingSep depsdb <> [searchPathSeparator] <> toFilePathNoTrailingSep localdb]         paths =             [ "--ide-backend-tools-path=" <>-              intercalate ":" (map toFilePath bindirs) <>-              (maybe "" (':' :) mpath)]+              intercalate [searchPathSeparator] (map toFilePath bindirs) <>+              maybe "" (searchPathSeparator :) mpath]         args =             ["--verbose"] <> ["--include=" <> includeDirs pkgopts] <>-            ["--local-work-dir=" ++ toFilePath pwd] <>+            ["--local-work-dir=" ++ toFilePathNoTrailingSep pwd] <>             map ("--ghc-option=" ++) useropts <>             paths <>             pkgopts <>@@ -69,12 +74,12 @@     when         (os == OSX)         (catch (callProcess (Just pwd) menv "stty" ["cbreak", "-imaxbel"])-               (\(_ :: ProcessExitedUnsuccessfully) -> undefined))+               (\(_ :: ProcessExitedUnsuccessfully) -> return ()))     callProcess (Just pwd) menv "stack-ide" args   where     includeDirs pkgopts =         intercalate-            ":"+            [searchPathSeparator]             (mapMaybe                  (stripPrefix "--ghc-option=-i")                  pkgopts)@@ -85,7 +90,7 @@     => Path Abs Dir -> GhciPkgInfo -> m ([FilePath], [FilePath]) getPackageOptsAndTargetFiles pwd pkg = do     dist <- distDirFromDir (ghciPkgDir pkg)-    autogen <- return (autogenDir dist)+    let autogen = autogenDir dist     paths_foo <-         liftM             (autogen </>)@@ -93,11 +98,9 @@                  ("Paths_" ++ packageNameString (ghciPkgName pkg) ++ ".hs"))     paths_foo_exists <- fileExists paths_foo     return-        ( ["--dist-dir=" <> toFilePath dist] ++-          map ("--ghc-option=" ++) (ghciPkgOpts pkg)+        ( ("--dist-dir=" <> toFilePathNoTrailingSep dist) :+          map ("--ghc-option=" ++) (concatMap (\(_, bio) -> bioGeneratedOpts bio ++ bioGhcOpts bio) (ghciPkgOpts pkg))         , mapMaybe               (fmap toFilePath . stripDir pwd)               (S.toList (ghciPkgCFiles pkg) <> S.toList (ghciPkgModFiles pkg) <>-               if paths_foo_exists-                   then [paths_foo]-                   else []))+               [paths_foo | paths_foo_exists]))
src/Stack/Image.hs view
@@ -30,12 +30,12 @@ import           Data.Typeable import           Options.Applicative import           Path+import           Path.Extra import           Path.IO import           Stack.Constants import           Stack.Types import           Stack.Types.Internal-import           System.FilePath (dropTrailingPathSeparator)-import           System.Process+import           System.Process.Run  type Build e m = (HasBuildConfig e, HasConfig e, HasEnvConfig e, HasTerminal e, MonadBaseControl IO m, MonadCatch m, MonadIO m, MonadLogger m, MonadReader e m) @@ -95,36 +95,40 @@  -- | Derive an image name from the project directory. imageName :: Path Abs Dir -> String-imageName = map toLower . dropTrailingPathSeparator . toFilePath . dirname+imageName = map toLower . toFilePathNoTrailingSep . dirname  -- | Create a general purpose docker image from the temporary -- directory of executables & static content. createDockerImage :: Assemble e m => Path Abs Dir -> m () createDockerImage dir = do+    menv <- getMinimalEnvOverride     config <- asks getConfig     let dockerConfig = imgDocker (configImage config)     case imgDockerBase =<< dockerConfig of         Nothing -> throwM StackImageDockerBaseUnspecifiedException         Just base -> do             liftIO-                (do writeFile-                        (toFilePath-                             (dir </>-                              $(mkRelFile "Dockerfile")))-                        (unlines ["FROM " ++ base, "ADD ./ /"])-                    callProcess-                        "docker"-                        [ "build"-                        , "-t"-                        , fromMaybe-                              (imageName (parent (parent dir)))-                              (imgDockerImageName =<< dockerConfig)-                        , toFilePath dir])+                (writeFile+                     (toFilePath+                          (dir </>+                           $(mkRelFile "Dockerfile")))+                     (unlines ["FROM " ++ base, "ADD ./ /"]))+            callProcess+                Nothing+                menv+                "docker"+                [ "build"+                , "-t"+                , fromMaybe+                      (imageName (parent (parent dir)))+                      (imgDockerImageName =<< dockerConfig)+                , toFilePathNoTrailingSep dir]  -- | Extend the general purpose docker image with entrypoints (if -- specified). extendDockerImageWithEntrypoint :: Assemble e m => Path Abs Dir -> m () extendDockerImageWithEntrypoint dir = do+    menv <- getMinimalEnvOverride     config <- asks getConfig     let dockerConfig = imgDocker (configImage config)     let dockerImageName = fromMaybe@@ -133,26 +137,28 @@     let imgEntrypoints = maybe Nothing imgDockerEntrypoints dockerConfig     case imgEntrypoints of         Nothing -> return ()-        Just eps -> do+        Just eps ->             forM_                 eps-                (\ep ->+                (\ep -> do                       liftIO-                          (do writeFile-                                  (toFilePath-                                       (dir </>-                                        $(mkRelFile "Dockerfile")))-                                  (unlines-                                       [ "FROM " ++ dockerImageName-                                       , "ENTRYPOINT [\"/usr/local/bin/" ++-                                         ep ++ "\"]"-                                       , "CMD []"])-                              callProcess-                                  "docker"-                                  [ "build"-                                  , "-t"-                                  , dockerImageName ++ "-" ++ ep-                                  , toFilePath dir]))+                          (writeFile+                               (toFilePath+                                    (dir </>+                                     $(mkRelFile "Dockerfile")))+                               (unlines+                                    [ "FROM " ++ dockerImageName+                                    , "ENTRYPOINT [\"/usr/local/bin/" +++                                      ep ++ "\"]"+                                    , "CMD []"]))+                      callProcess+                          Nothing+                          menv+                          "docker"+                          [ "build"+                          , "-t"+                          , dockerImageName ++ "-" ++ ep+                          , toFilePathNoTrailingSep dir])  -- | The command name for dealing with images. imgCmdName :: String
src/Stack/Init.hs view
@@ -15,7 +15,7 @@  import           Control.Exception               (assert) import           Control.Exception.Enclosed      (catchAny, handleIO)-import           Control.Monad                   (liftM, when)+import           Control.Monad                   (liftM, when, zipWithM_) import           Control.Monad.Catch             (MonadMask, MonadThrow, throwM) import           Control.Monad.IO.Class import           Control.Monad.Logger@@ -26,7 +26,7 @@ import qualified Data.HashMap.Strict             as HM import qualified Data.IntMap                     as IntMap import qualified Data.Foldable                   as F-import           Data.List                       (isSuffixOf,sort)+import           Data.List                       (isSuffixOf,sortBy) import           Data.List.Extra                 (nubOrd) import           Data.Map                        (Map) import qualified Data.Map                        as Map@@ -86,7 +86,7 @@      when (null cabalfps) $ error "In order to init, you should have an existing .cabal file. Please try \"stack new\" instead"     (warnings,gpds) <- fmap unzip (mapM readPackageUnresolved cabalfps)-    sequence_ (zipWith (mapM_ . printCabalFileWarning) cabalfps warnings)+    zipWithM_ (mapM_ . printCabalFileWarning) cabalfps warnings      (r, flags, extraDeps) <- getDefaultResolver cabalfps gpds initOpts     let p = Project@@ -94,6 +94,7 @@             , projectExtraDeps = extraDeps             , projectFlags = flags             , projectResolver = r+            , projectCompiler = Nothing             , projectExtraPackageDBs = []             }         pkgs = map toPkg cabalfps@@ -226,7 +227,7 @@     -- prefer them over anything else, since odds are high that something     -- already exists for them.     existing <--        liftM (reverse . sort . mapMaybe (parseSnapName . T.pack)) $+        liftM (sortBy (flip compare) . mapMaybe (parseSnapName . T.pack)) $         snapshotsDir >>=         liftIO . handleIO (const $ return [])                . getDirectoryContents . toFilePath@@ -289,7 +290,7 @@                     Nothing -> error $ "No LTS release found with major version " ++ show x                     Just y -> return $ ResolverSnapshot $ LTS x y             ARLatestLTS-                | IntMap.null $ snapshotsLts snapshots -> error $ "No LTS releases found"+                | IntMap.null $ snapshotsLts snapshots -> error "No LTS releases found"                 | otherwise ->                     let (x, y) = IntMap.findMax $ snapshotsLts snapshots                      in return $ ResolverSnapshot $ LTS x y
src/Stack/New.hs view
@@ -30,7 +30,7 @@ import           Data.List import           Data.Map.Strict (Map) import qualified Data.Map.Strict as M-import           Data.Maybe+import           Data.Maybe.Extra (mapMaybeM) import           Data.Monoid import           Data.Set (Set) import qualified Data.Set as S@@ -109,18 +109,38 @@  -- | Download and read in a template's text content. loadTemplate-    :: (HasConfig r, HasHttpManager r, MonadReader r m, MonadIO m, MonadThrow m, MonadCatch m)+    :: forall m r.+       (HasConfig r, HasHttpManager r, MonadReader r m, MonadIO m, MonadThrow m, MonadCatch m)     => TemplateName -> m Text-loadTemplate name = do-    req <--        parseUrl (defaultTemplateUrl <> "/" <> toFilePath (templatePath name))-    config <- asks getConfig-    let path = templatesDir config </> templatePath name-    _ <- catch (redownload req path) (throwM . FailedToDownloadTemplate name)-    exists <- fileExists path-    if exists-        then liftIO (T.readFile (toFilePath path))-        else throwM (FailedToLoadTemplate name path)+loadTemplate name =+    case templatePath name of+        Left absFile -> loadLocalFile absFile+        Right relFile ->+            catch+                (loadLocalFile relFile)+                (\(_ :: NewException) ->+                      downloadTemplate relFile)+  where+    loadLocalFile :: Path b File -> m Text+    loadLocalFile path = do+        exists <- fileExists path+        if exists+            then liftIO (T.readFile (toFilePath path))+            else throwM (FailedToLoadTemplate name (toFilePath path))+    downloadTemplate :: Path Rel File -> m Text+    downloadTemplate rel = do+        config <- asks getConfig+        req <- parseUrl (defaultTemplateUrl <> "/" <> toFilePath rel)+        let path :: Path Abs File+            path = templatesDir config </> rel+        _ <-+            catch+                (redownload req path)+                (throwM . FailedToDownloadTemplate name)+        exists <- fileExists path+        if exists+            then liftIO (T.readFile (toFilePath path))+            else throwM (FailedToLoadTemplate name (toFilePath path))  -- | Apply and unpack a template into a directory. applyTemplate@@ -143,7 +163,7 @@                  defaultConfig                  templateText                  (mkStrContextM (contextFunction context)))-    when (not (S.null missingKeys))+    unless (S.null missingKeys)          ($logInfo (T.pack (show (MissingParameters project template missingKeys (configUserConfigPath config)))))     files :: Map FilePath LB.ByteString <-         execWriterT $@@ -165,7 +185,7 @@         => Map Text Text         -> String         -> WriterT (Set String) m (MuType (WriterT (Set String) m))-    contextFunction context key = do+    contextFunction context key =         case M.lookup (T.pack key) context of             Nothing -> do                 tell (S.singleton key)@@ -176,7 +196,7 @@ writeTemplateFiles     :: MonadIO m     => Map (Path Abs File) LB.ByteString -> m ()-writeTemplateFiles files = do+writeTemplateFiles files =     forM_         (M.toList files)         (\(fp,bytes) ->@@ -193,9 +213,8 @@     case configScmInit config of         Nothing -> return ()         Just Git ->-            do catch-                   (callProcess (Just dir) menv "git" ["init"])-                   (\(_ :: ProcessExitedUnsuccessfully) ->+            catch (callProcess (Just dir) menv "git" ["init"])+                  (\(_ :: ProcessExitedUnsuccessfully) ->                          $logInfo "git init failed to run, ignoring ...")  --------------------------------------------------------------------------------@@ -234,12 +253,12 @@ parseTemplateSet :: Value -> Parser (Set TemplateName) parseTemplateSet a = do     xs <- parseJSON a-    fmap (S.fromList . catMaybes) (mapM parseTemplate xs)+    fmap S.fromList (mapMaybeM parseTemplate xs)   where     parseTemplate v = do         o <- parseJSON v         name <- o .: "name"-        if isSuffixOf ".hsfiles" name+        if ".hsfiles" `isSuffixOf` name             then case parseTemplateNameFromString name of                      Left{} ->                          fail ("Unable to parse template name from " <> name)@@ -269,7 +288,7 @@ -- | Exception that might occur when making a new project. data NewException     = FailedToLoadTemplate !TemplateName-                           !(Path Abs File)+                           !FilePath     | FailedToDownloadTemplate !TemplateName                                !DownloadException     | FailedToDownloadTemplates !HttpException@@ -285,7 +304,7 @@     show (FailedToLoadTemplate name path) =         "Failed to load download template " <> T.unpack (templateName name) <>         " from " <>-        toFilePath path+        path     show (FailedToDownloadTemplate name (RedownloadFailed _ _ resp)) =         case statusCode (responseStatus resp) of             404 ->
src/Stack/Options.hs view
@@ -1,9 +1,11 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings,RecordWildCards #-}  module Stack.Options     (Command(..)     ,benchOptsParser     ,buildOptsParser+    ,cleanOptsParser+    ,configCmdSetParser     ,configOptsParser     ,dockerOptsParser     ,dockerCleanupOptsParser@@ -15,10 +17,11 @@     ,newOptsParser     ,logLevelOptsParser     ,ghciOptsParser-    ,abstractResolverOptsParser     ,solverOptsParser     ,testOptsParser+    ,hpcReportOptsParser     ,pvpBoundsOption+    ,globalOptsFromMonoid     ) where  import           Control.Monad.Logger (LogLevel(..))@@ -33,12 +36,16 @@ import qualified Data.Set as Set import qualified Data.Text as T import           Data.Text.Read (decimal)+import           Distribution.Version (anyVersion)+import           Options.Applicative import           Options.Applicative.Args import           Options.Applicative.Builder.Extra-import           Options.Applicative.Simple-import           Options.Applicative.Types (readerAsk)+import           Options.Applicative.Types (fromM, oneM, readerAsk)+import           Stack.Clean (CleanOpts(..)) import           Stack.Config (packagesParser)+import           Stack.ConfigCmd import           Stack.Constants (stackProgName)+import           Stack.Coverage (HpcReportOpts(..)) import           Stack.Docker import qualified Stack.Docker as Docker import           Stack.Dot@@ -64,10 +71,8 @@                                  metavar "BENCH_ARGS" <>                                  help ("Forward BENCH_ARGS to the benchmark suite. " <>                                        "Supports templates from `cabal bench`")))-        <*> flag False-                 True-                 (long "no-run-benchmarks" <>-                  help "Disable running of benchmarks. (Benchmarks will still be built.)")+        <*> switch (long "no-run-benchmarks" <>+                   help "Disable running of benchmarks. (Benchmarks will still be built.)")  addCoverageFlags :: BuildOpts -> BuildOpts addCoverageFlags bopts@@ -103,7 +108,7 @@         haddock =           boolFlags (cmd == Haddock)                     "haddock"-                    "generating Haddocks the project(s) in this directory/configuration"+                    "generating Haddocks the package(s) in this directory/configuration"                     idm         haddockDeps =              maybeBoolFlags@@ -115,13 +120,17 @@             "copying binaries to the local-bin-path (see 'stack path')"             idm -        dryRun = flag False True (long "dry-run" <>-                                  help "Don't build anything, just prepare to")-        ghcOpts = (++)+        dryRun = switch (long "dry-run" <>+                         help "Don't build anything, just prepare to")+        ghcOpts = (\x y z -> concat [x, y, z])           <$> flag [] ["-Wall", "-Werror"]               ( long "pedantic"-             <> help "Turn on -Wall and -Werror (note: option name may change in the future"+             <> help "Turn on -Wall and -Werror"               )+          <*> flag [] ["-O0"]+              ( long "fast"+             <> help "Turn off optimizations (-O0)"+              )           <*> many (textOption (long "ghc-options" <>                                 metavar "OPTION" <>                                 help "Additional options passed to GHC"))@@ -133,20 +142,20 @@                        help ("Override flags set in stack.yaml " <>                              "(applies to local packages and extra-deps)"))) -        preFetch = flag False True+        preFetch = switch             (long "prefetch" <>              help "Fetch packages necessary for the build immediately, useful with --dry-run")          buildSubset =-            flag' BSOnlySnapshot-                (long "only-snapshot" <>-                 help "Only build packages for the snapshot database, not the local database")-            <|> flag' BSOnlyDependencies-                (long "only-dependencies" <>-                 help "Only build packages that are dependencies of targets on the command line")-            <|> flag' BSOnlyDependencies+            flag' BSOnlyDependencies                 (long "dependencies-only" <>                  help "A synonym for --only-dependencies")+            <|> flag' BSOnlySnapshot+                (long ("only-snapshot") <>+                 help ("Only build packages for the snapshot database, not the local database"))+            <|> flag' BSOnlyDependencies+                (long ("only-dependencies") <>+                 help ("Only build packages that are dependencies of targets on the command line"))             <|> pure BSAll          fileWatch' =@@ -163,18 +172,18 @@             "continue running after a step fails (default: false for build, true for test/bench)"             idm -        forceDirty = flag False True+        forceDirty = switch             (long "force-dirty" <>              help "Force treating all local packages as having dirty files (useful for cases where stack can't detect a file change)")          tests = boolFlags (cmd == Test)             "test"-            "testing the project(s) in this directory/configuration"+            "testing the package(s) in this directory/configuration"             idm          benches = boolFlags (cmd == Bench)             "bench"-            "benchmarking the project(s) in this directory/configuration"+            "benchmarking the package(s) in this directory/configuration"             idm          exec = cmdOption@@ -182,15 +191,15 @@               metavar "CMD [ARGS]" <>               help "Command and arguments to run after a successful build" ) -        onlyConfigure = flag False True+        onlyConfigure = switch             (long "only-configure" <>              help "Only perform the configure step, not any builds. Intended for tool usage, may break when used on multiple packages at once!") -        reconfigure = flag False True+        reconfigure = switch             (long "reconfigure" <>              help "Perform the configure step even if unnecessary. Useful in some corner cases with custom Setup.hs files") -        cabalVerbose = flag False True+        cabalVerbose = switch             (long "cabal-verbose" <>              help "Ask Cabal to be verbose in its output") @@ -217,9 +226,19 @@             return $ Map.singleton pn' $ Map.singleton flagN b         _ -> readerError "Must have a colon" +-- | Command-line parser for the clean command.+cleanOptsParser :: Parser CleanOpts+cleanOptsParser = CleanOpts <$> packages+  where+    packages =+        many+            (packageNameArgument+                 (metavar "PACKAGE" <>+                  help "If none specified, clean all local packages"))+ -- | Command-line arguments parser for configuration. configOptsParser :: Bool -> Parser ConfigMonoid-configOptsParser docker =+configOptsParser hide0 =     (\opts systemGHC installGHC arch os ghcVariant jobs includes libs skipGHCCheck skipMsys localBin modifyCodePage -> mempty         { configMonoidDockerOpts = opts         , configMonoidSystemGHC = systemGHC@@ -235,63 +254,70 @@         , configMonoidLocalBinPath = localBin         , configMonoidModifyCodePage = modifyCodePage         })-    <$> dockerOptsParser docker+    <$> dockerOptsParser True     <*> maybeBoolFlags             "system-ghc"             "using the system installed GHC (on the PATH) if available and a matching version"-            idm+            hide     <*> maybeBoolFlags             "install-ghc"             "downloading and installing GHC if necessary (can be done manually with stack setup)"-            idm+            hide     <*> optional (strOption             ( long "arch"            <> metavar "ARCH"            <> help "System architecture, e.g. i386, x86_64"+           <> hide             ))     <*> optional (strOption             ( long "os"            <> metavar "OS"            <> help "Operating system, e.g. linux, windows"+           <> hide             ))-    <*> optional ghcVariantParser+    <*> optional (ghcVariantParser hide0)     <*> optional (option auto             ( long "jobs"            <> short 'j'            <> metavar "JOBS"            <> help "Number of concurrent jobs to run"+           <> hide             ))     <*> fmap Set.fromList (many (textOption             ( long "extra-include-dirs"            <> metavar "DIR"            <> help "Extra directories to check for C header files"+           <> hide             )))     <*> fmap Set.fromList (many (textOption             ( long "extra-lib-dirs"            <> metavar "DIR"            <> help "Extra directories to check for libraries"+           <> hide             )))     <*> maybeBoolFlags             "skip-ghc-check"             "skipping the GHC version and architecture check"-            idm+            hide     <*> maybeBoolFlags             "skip-msys"             "skipping the local MSYS installation (Windows only)"-            idm+            hide     <*> optional (strOption              ( long "local-bin-path"-             <> metavar "DIR"-             <> help "Install binaries to DIR"-              ))+            <> metavar "DIR"+            <> help "Install binaries to DIR"+            <> hide+             ))     <*> maybeBoolFlags             "modify-code-page"             "setting the codepage to support UTF-8 (Windows only)"-            idm+            hide+  where hide = hideMods hide0  -- | Options parser configuration for Docker. dockerOptsParser :: Bool -> Parser DockerOptsMonoid-dockerOptsParser showOptions =+dockerOptsParser hide0 =     DockerOptsMonoid     <$> pure False     <*> maybeBoolFlags dockerCmdName@@ -363,12 +389,11 @@     <*> maybeBoolFlags (dockerOptName dockerSetUserArgName)                        "setting user in container to match host"                        hide+    <*> pure anyVersion   where     dockerOptName optName = dockerCmdName ++ "-" ++ T.unpack optName     maybeStrOption = optional . option str-    hide = if showOptions-              then idm-              else internal <> hidden+    hide = hideMods hide0  -- | Parser for docker cleanup arguments. dockerCleanupOptsParser :: Parser Docker.CleanupOpts@@ -448,19 +473,15 @@  ghciOptsParser :: Parser GhciOpts ghciOptsParser = GhciOpts-             <$> many (textArgument-                         (metavar "TARGET" <>-                          help ("If none specified, " <>-                                "use all packages defined in current directory")))-             <*> fmap concat (many (argsOption (long "ghc-options" <>+             <$> switch (long "no-build" <> help "Don't build before launching GHCi")+             <*> fmap concat (many (argsOption (long "ghci-options" <>                                        metavar "OPTION" <>                                        help "Additional options passed to GHCi")))              <*> optional                      (strOption (long "with-ghc" <>                                  metavar "GHC" <>-                                 help "Use this command for the GHC to run"))-             <*> flag False True (long "no-load" <>-                   help "Don't load modules on start-up")+                                 help "Use this GHC to run GHCi"))+             <*> (not <$> boolFlags True "load" "load modules on start-up" idm)              <*> packagesParser              <*> optional                      (textOption@@ -469,32 +490,28 @@                             help "Specify which target should contain the main \                                  \module to load, such as for an executable for \                                  \test suite or benchmark."))+             <*> buildOptsParser Build  -- | Parser for exec command-execOptsParser :: Maybe String -- ^ command-               -> Parser ExecOpts+execOptsParser :: Maybe SpecialExecCmd -> Parser ExecOpts execOptsParser mcmd =     ExecOpts-        <$> pure mcmd+        <$> maybe eoCmdParser pure mcmd         <*> eoArgsParser         <*> execOptsExtraParser   where-    eoArgsParser :: Parser [String]-    eoArgsParser = many (strArgument (metavar meta))-      where-        meta = (maybe ("CMD ") (const "") mcmd) ++-               "-- ARGS (e.g. stack ghc -- X.hs -o x)"+    eoCmdParser = ExecCmd <$> strArgument (metavar "CMD")+    eoArgsParser = many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)")) -evalOptsParser :: Maybe String -- ^ metavar+evalOptsParser :: String -- ^ metavar                -> Parser EvalOpts-evalOptsParser mmeta =+evalOptsParser meta =     EvalOpts         <$> eoArgsParser         <*> execOptsExtraParser   where     eoArgsParser :: Parser String     eoArgsParser = strArgument (metavar meta)-    meta = maybe ("CODE") id mmeta  -- | Parser for extra options to exec command execOptsExtraParser :: Parser ExecOptsExtra@@ -524,40 +541,47 @@                           (long "plain" <>                            help "Use an unmodified environment (only useful with Docker)") - -- | Parser for global command-line options.-globalOptsParser :: Bool -> Parser GlobalOpts-globalOptsParser defaultTerminal =-    GlobalOpts <$>-    optional (strOption (long Docker.reExecArgName <>-                         hidden <>-                         internal)) <*>-    logLevelOptsParser <*>-    configOptsParser False <*>-    optional abstractResolverOptsParser <*>-    flag-        defaultTerminal-        False-        (long "no-terminal" <>-         help-             "Override terminal detection in the case of running in a false terminal") <*>+globalOptsParser :: Bool -> Parser GlobalOptsMonoid+globalOptsParser hide0 =+    GlobalOptsMonoid <$>+    optional (strOption (long Docker.reExecArgName <> hidden <> internal)) <*>+    optional (option auto (long dockerEntrypointArgName <> hidden <> internal)) <*>+    logLevelOptsParser hide0 <*>+    configOptsParser hide0 <*>+    optional (abstractResolverOptsParser hide0) <*>+    optional (compilerOptsParser hide0) <*>+    maybeBoolFlags+        "terminal"+        "overriding terminal detection in the case of running in a false terminal"+        hide <*>     optional (strOption (long "stack-yaml" <>                          metavar "STACK-YAML" <>                          help ("Override project stack.yaml file " <>-                               "(overrides any STACK_YAML environment variable)")))+                               "(overrides any STACK_YAML environment variable)") <>+                         hide))+  where hide = hideMods hide0 +-- | Create GlobalOpts from GlobalOptsMonoid.+globalOptsFromMonoid :: Bool -> GlobalOptsMonoid -> GlobalOpts+globalOptsFromMonoid defaultTerminal GlobalOptsMonoid{..} = GlobalOpts+    { globalReExecVersion = globalMonoidReExecVersion+    , globalDockerEntrypoint = globalMonoidDockerEntrypoint+    , globalLogLevel = fromMaybe defaultLogLevel (globalMonoidLogLevel)+    , globalConfigMonoid = globalMonoidConfigMonoid+    , globalResolver = globalMonoidResolver+    , globalCompiler = globalMonoidCompiler+    , globalTerminal = fromMaybe defaultTerminal (globalMonoidTerminal)+    , globalStackYaml = globalMonoidStackYaml }+ initOptsParser :: Parser InitOpts initOptsParser =     InitOpts <$> method <*> overwrite <*> fmap not ignoreSubDirs   where-    ignoreSubDirs = flag False-                         True-                         (long "ignore-subdirs" <>-                         help "Do not search for .cabal files in sub directories")-    overwrite = flag False-                     True-                     (long "force" <>-                      help "Force overwriting of an existing stack.yaml if it exists")+    ignoreSubDirs = switch (long "ignore-subdirs" <>+                           help "Do not search for .cabal files in sub directories")+    overwrite = switch (long "force" <>+                       help "Force overwriting of an existing stack.yaml if it exists")     method = solver          <|> (MethodResolver <$> resolver)          <|> (MethodSnapshot <$> snapPref)@@ -582,16 +606,18 @@          help "Use the given resolver, even if not all dependencies are met")  -- | Parse for a logging level.-logLevelOptsParser :: Parser LogLevel-logLevelOptsParser =-  fmap parse+logLevelOptsParser :: Bool -> Parser (Maybe LogLevel)+logLevelOptsParser hide =+  fmap (Just . parse)        (strOption (long "verbosity" <>                    metavar "VERBOSITY" <>-                   help "Verbosity: silent, error, warn, info, debug")) <|>-  flag defaultLogLevel-       verboseLevel+                   help "Verbosity: silent, error, warn, info, debug" <>+                   hideMods hide)) <|>+  flag' (Just verboseLevel)        (short 'v' <> long "verbose" <>-        help ("Enable verbose mode: verbosity level \"" <> showLevel verboseLevel <> "\""))+        help ("Enable verbose mode: verbosity level \"" <> showLevel verboseLevel <> "\"") <>+        hideMods hide) <|>+  pure Nothing   where verboseLevel = LevelDebug         showLevel l =           case l of@@ -609,12 +635,13 @@             _ -> LevelOther (T.pack s)  -- | Parser for the resolver-abstractResolverOptsParser :: Parser AbstractResolver-abstractResolverOptsParser =+abstractResolverOptsParser :: Bool -> Parser AbstractResolver+abstractResolverOptsParser hide =     option readAbstractResolver         (long "resolver" <>          metavar "RESOLVER" <>-         help "Override resolver in project file")+         help "Override resolver in project file" <>+         hideMods hide)  readAbstractResolver :: ReadM AbstractResolver readAbstractResolver = do@@ -630,14 +657,31 @@                 Left e -> readerError $ show e                 Right x -> return $ ARResolver x +compilerOptsParser :: Bool -> Parser CompilerVersion+compilerOptsParser hide =+    option readCompilerVersion+        (long "compiler" <>+         metavar "COMPILER" <>+         help "Use the specified compiler" <>+         hideMods hide)++readCompilerVersion :: ReadM CompilerVersion+readCompilerVersion = do+    s <- readerAsk+    case parseCompilerVersion (T.pack s) of+        Nothing -> readerError $ "Failed to parse compiler: " ++ s+        Just x -> return x+ -- | GHC variant parser-ghcVariantParser :: Parser GHCVariant-ghcVariantParser =+ghcVariantParser :: Bool -> Parser GHCVariant+ghcVariantParser hide =     option         readGHCVariant         (long "ghc-variant" <> metavar "VARIANT" <>          help-             "Specialized GHC variant, e.g. integersimple (implies --no-system-ghc)")+             "Specialized GHC variant, e.g. integersimple (implies --no-system-ghc)" <>+         hideMods hide+        )   where     readGHCVariant = do         s <- readerAsk@@ -663,14 +707,10 @@                 (optional (argsOption(long "test-arguments" <>                                       metavar "TEST_ARGS" <>                                       help "Arguments passed in to the test suite program")))-      <*> flag False-               True-               (long "coverage" <>-               help "Generate a code coverage report")-      <*> flag False-               True-               (long "no-run-tests" <>-                help "Disable running of tests. (Tests will still be built.)")+      <*> switch (long "coverage" <>+                 help "Generate a code coverage report")+      <*> switch (long "no-run-tests" <>+                 help "Disable running of tests. (Tests will still be built.)")  -- | Parser for @stack new@. newOptsParser :: Parser (NewOpts,InitOpts)@@ -685,7 +725,8 @@              help "Do not create a subdirectory for the project") <*>         templateNameArgument             (metavar "TEMPLATE_NAME" <>-             help "Name of a template, for example: foo or foo.hsfiles" <>+             help "Name of a template or a local template in a subdirectory,\+                  \ for example: foo or foo.hsfiles" <>              value defaultTemplateName) <*>         fmap             M.fromList@@ -693,9 +734,15 @@                  (templateParamArgument                       (short 'p' <> long "param" <> metavar "KEY:VALUE" <>                        help-                           "Parameter for the template in the format key:value"))) <*-        abortOption ShowHelpText (long "help" <> help "Show help text.")+                           "Parameter for the template in the format key:value"))) +-- | Parser for @stack hpc report@.+hpcReportOptsParser :: Parser HpcReportOpts+hpcReportOptsParser = HpcReportOpts+    <$> many (textArgument $ metavar "TARGET_OR_TIX")+    <*> switch (long "all" <> help "Use results from all packages and components")+    <*> optional (strOption (long "destdir" <> help "Output directy for HTML report"))+ pvpBoundsOption :: Parser PvpBounds pvpBoundsOption =     option@@ -707,5 +754,31 @@     readPvpBounds = do         s <- readerAsk         case parsePvpBounds $ T.pack s of-            Left e -> readerError e-            Right v -> return v+            Left e ->+                readerError e+            Right v ->+                return v++configCmdSetParser :: Parser ConfigCmdSet+configCmdSetParser =+    fromM+        (do field <-+                oneM+                    (strArgument+                         (metavar "FIELD VALUE"))+            oneM (fieldToValParser field))+  where+    fieldToValParser :: String -> Parser ConfigCmdSet+    fieldToValParser s =+        case s of+            "resolver" ->+                ConfigCmdSetResolver <$>+                argument+                    readAbstractResolver+                    idm+            _ ->+                error "parse stack config set field: only set resolver is implemented"++-- | If argument is True, hides the option from usage and help+hideMods :: Bool -> Mod f a+hideMods hide = if hide then internal <> hidden else idm
src/Stack/Package.hs view
@@ -36,6 +36,7 @@   ,printCabalFileWarning)   where +import           Control.Arrow ((&&&)) import           Control.Exception hiding (try,catch) import           Control.Monad import           Control.Monad.Catch@@ -70,6 +71,7 @@ import           Distribution.System (OS (..), Arch, Platform (..)) import           Distribution.Text (display, simpleParse) import           Path as FL+import           Path.Extra import           Path.Find import           Path.IO import           Prelude@@ -185,21 +187,21 @@     , packageFlags = packageConfigFlags packageConfig     , packageAllDeps = S.fromList (M.keys deps)     , packageHasLibrary = maybe False (buildable . libBuildInfo) (library pkg)-    , packageTests = S.fromList $+    , packageTests = S.fromList       [T.pack (testName t) | t <- testSuites pkg                            , buildable (testBuildInfo t)]-    , packageBenchmarks = S.fromList $+    , packageBenchmarks = S.fromList       [T.pack (benchmarkName b) | b <- benchmarks pkg                                 , buildable (benchmarkBuildInfo b)]-    , packageExes = S.fromList $+    , packageExes = S.fromList       [T.pack (exeName b) | b <- executables pkg                           , buildable (buildInfo b)]     , packageOpts = GetPackageOpts $-      \sourceMap installedMap locals cabalfp ->+      \sourceMap installedMap omitPkgs cabalfp ->            do (componentsModules,componentFiles,_,_) <- getPackageFiles pkgFiles cabalfp-              (componentsOpts,generalOpts) <--                  generatePkgDescOpts sourceMap installedMap locals cabalfp pkg componentFiles-              return (componentsModules,componentFiles,componentsOpts,generalOpts)+              componentsOpts <-+                  generatePkgDescOpts sourceMap installedMap omitPkgs cabalfp pkg componentFiles+              return (componentsModules,componentFiles,componentsOpts)     , packageHasExposedModules = maybe           False           (not . null . exposedModules)@@ -212,11 +214,11 @@     pkgFiles = GetPackageFiles $         \cabalfp ->              do distDir <- distDirFromDir (parent cabalfp)-                (componentModules,componentFiles,cabalFiles,warnings) <-+                (componentModules,componentFiles,dataFiles',warnings) <-                     runReaderT                         (packageDescModulesAndFiles pkg)                         (cabalfp, buildDir distDir)-                return (componentModules, componentFiles, cabalFiles, warnings)+                return (componentModules, componentFiles, S.insert cabalfp dataFiles', warnings)     pkgId = package (packageDescription gpkg)     name = fromCabalPackageName (pkgName pkgId)     pkg = resolvePackageDescription packageConfig gpkg@@ -229,12 +231,12 @@     :: (HasEnvConfig env, HasPlatform env, MonadThrow m, MonadReader env m, MonadIO m)     => SourceMap     -> InstalledMap-    -> [PackageName]+    -> [PackageName] -- ^ Packages to omit from the "-package" / "-package-id" flags     -> Path Abs File     -> PackageDescription     -> Map NamedComponent (Set DotCabalPath)-    -> m (Map NamedComponent [String],[String])-generatePkgDescOpts sourceMap installedMap locals cabalfp pkg componentPaths = do+    -> m (Map NamedComponent BuildInfoOpts)+generatePkgDescOpts sourceMap installedMap omitPkgs cabalfp pkg componentPaths = do     distDir <- distDirFromDir cabalDir     let cabalmacros = autogenDir distDir </> $(mkRelFile "cabal_macros.h")     exists <- fileExists cabalmacros@@ -250,7 +252,7 @@                   mcabalmacros                   cabalDir                   distDir-                  locals+                  omitPkgs                   binfo                   (fromMaybe mempty (M.lookup namedComponent componentPaths))                   namedComponent)@@ -261,25 +263,24 @@                          []                          (return . generate CLib . libBuildInfo)                          (library pkg)-                   , map+                   , fmap                          (\exe ->-                               (generate+                               generate                                     (CExe (T.pack (exeName exe)))-                                    (buildInfo exe)))+                                    (buildInfo exe))                          (executables pkg)-                   , map+                   , fmap                          (\bench ->-                               (generate+                               generate                                     (CBench (T.pack (benchmarkName bench)))-                                    (benchmarkBuildInfo bench)))+                                    (benchmarkBuildInfo bench))                          (benchmarks pkg)-                   , map+                   , fmap                          (\test ->-                               (generate-                                    (CBench (T.pack (testName test)))-                                    (testBuildInfo test)))-                         (testSuites pkg)])-        , ["-hide-all-packages"])+                               generate+                                    (CTest (T.pack (testName test)))+                                    (testBuildInfo test))+                         (testSuites pkg)]))   where     cabalDir = parent cabalfp @@ -294,9 +295,20 @@     -> BuildInfo     -> Set DotCabalPath     -> NamedComponent-    -> [String]-generateBuildInfoOpts sourceMap installedMap mcabalmacros cabalDir distDir locals b dotCabalPaths componentName =-    nubOrd (concat [ghcOpts b, extOpts b, srcOpts, includeOpts, macros, deps, extra b, extraDirs, fworks b, cObjectFiles])+    -> BuildInfoOpts+generateBuildInfoOpts sourceMap installedMap mcabalmacros cabalDir distDir omitPkgs b dotCabalPaths componentName =+    BuildInfoOpts+        { bioGhcOpts = ghcOpts b+        -- NOTE for future changes: Due to this use of nubOrd (and other uses+        -- downstream), these generated options must not rely on multiple+        -- argument sequences.  For example, ["--main-is", "Foo.hs", "--main-+        -- is", "Bar.hs"] would potentially break due to the duplicate+        -- "--main-is" being removed.+        --+        -- See https://github.com/commercialhaskell/stack/issues/1255+        , bioGeneratedOpts = nubOrd $ concat+            [extOpts b, srcOpts, includeOpts, macros, deps, extra b, extraDirs, fworks b, cObjectFiles]+        }   where     cObjectFiles =         mapMaybe (fmap toFilePath .@@ -306,13 +318,13 @@     deps =         concat             [ case M.lookup (fromCabalPackageName name) installedMap of-                Just (_, _, Stack.Types.Library _ident ipid) -> ["-package-id=" <> ghcPkgIdString ipid]+                Just (_, Stack.Types.Library _ident ipid) -> ["-package-id=" <> ghcPkgIdString ipid]                 _ -> ["-package=" <> display name <>                  maybe "" -- This empty case applies to e.g. base.                      ((("-" <>) . versionString) . sourceVersion)                      (M.lookup (fromCabalPackageName name) sourceMap)]             | Dependency name _ <- targetBuildDepends b-            , not (elem name (map toCabalPackageName locals))]+            , name `notElem` fmap toCabalPackageName omitPkgs]         -- Generates: -package=base -package=base16-bytestring-0.1.1.6 ...     sourceVersion (PSUpstream ver _ _) = ver     sourceVersion (PSLocal localPkg) = packageVersion (lpPackage localPkg)@@ -328,13 +340,15 @@     extOpts = map (("-X" ++) . display) . usedExtensions     srcOpts =         map-            (("-i" <>) . toFilePath)-            (cabalDir :-             map (cabalDir </>) (mapMaybe parseRelDir (hsSourceDirs b)) <>+            (("-i" <>) . toFilePathNoTrailingSep)+            ([cabalDir | null (hsSourceDirs b)] <>+             mapMaybe toIncludeDir (hsSourceDirs b) <>              [autogenDir distDir,buildDir distDir]) ++-        ["-stubdir=" ++ toFilePath (buildDir distDir)]+        ["-stubdir=" ++ toFilePathNoTrailingSep (buildDir distDir)]+    toIncludeDir "." = Just cabalDir+    toIncludeDir x = fmap (cabalDir </>) (parseRelDir x)     includeOpts =-        [ "-I" <> toFilePath absDir+        [ "-I" <> toFilePathNoTrailingSep absDir         | dir <- includeDirs b         , absDir <- case (parseAbsDir dir, parseRelDir dir) of           (Just ab, _       ) -> [ab]@@ -345,7 +359,7 @@         = map ("-l" <>)         . extraLibs     extraDirs =-        [ "-L" <> toFilePath absDir+        [ "-L" <> toFilePathNoTrailingSep absDir         | dir <- extraLibDirs b         , absDir <- case (parseAbsDir dir, parseRelDir dir) of           (Just ab, _       ) -> [ab]@@ -421,7 +435,7 @@ packageDependencies :: PackageDescription -> Map PackageName VersionRange packageDependencies =   M.fromListWith intersectVersionRanges .-  concatMap (map (\dep -> ((depName dep),depRange dep)) .+  concatMap (fmap (depName &&& depRange) .              targetBuildDepends) .   allBuildInfo' @@ -429,7 +443,7 @@ packageToolDependencies :: PackageDescription -> Map BS.ByteString VersionRange packageToolDependencies =   M.fromList .-  concatMap (map (\dep -> ((packageNameByteString $ depName dep),depRange dep)) .+  concatMap (fmap (packageNameByteString . depName &&& depRange) .              buildTools) .   allBuildInfo' @@ -508,7 +522,7 @@     mapM resolve   where     resolve name =-        if any (== '*') name+        if '*' `elem` name             then explode name             else liftM return (resolveFileOrWarn name)     explode name = do@@ -646,7 +660,7 @@     cfiles <- buildOtherSources build     return (modules, files <> cfiles, warnings)   where-    names = concat [bnames, exposed]+    names = bnames ++ exposed     exposed = map DotCabalModule (exposedModules lib)     bnames = map DotCabalModule (otherModules build)     build = libBuildInfo lib@@ -699,8 +713,7 @@          updateLibDeps lib deps =           lib {libBuildInfo =-                 ((libBuildInfo lib) {targetBuildDepends =-                                        deps})}+                 (libBuildInfo lib) {targetBuildDepends = deps}}         updateExeDeps exe deps =           exe {buildInfo =                  (buildInfo exe) {targetBuildDepends = deps}}@@ -750,7 +763,7 @@   where basic = addDeps lib deps         children = mconcat (map apply cs)           where apply (cond,node,mcs) =-                  if (condSatisfied cond)+                  if condSatisfied cond                      then resolveConditions rc addDeps node                      else maybe mempty (resolveConditions rc addDeps) mcs                 condSatisfied c =@@ -760,21 +773,18 @@                     CNot c' ->                       not (condSatisfied c')                     COr cx cy ->-                      or [condSatisfied cx,condSatisfied cy]+                      condSatisfied cx || condSatisfied cy                     CAnd cx cy ->-                      and [condSatisfied cx,condSatisfied cy]+                      condSatisfied cx && condSatisfied cy                 varSatisifed v =                   case v of                     OS os -> os == rcOS rc                     Arch arch -> arch == rcArch rc                     Flag flag ->-                      case M.lookup (fromCabalFlagName flag) (rcFlags rc) of-                        Just x -> x-                        Nothing ->-                          -- NOTE: This should never happen, as all flags-                          -- which are used must be declared. Defaulting-                          -- to False-                          False+                      fromMaybe False $ M.lookup (fromCabalFlagName flag) (rcFlags rc)+                      -- NOTE:  ^^^^^ This should never happen, as all flags+                      -- which are used must be declared. Defaulting to+                      -- False.                     Impl flavor range ->                       case (flavor, rcCompilerVersion rc) of                         (GHC, GhcVersion vghc) -> vghc `withinRange` range@@ -789,11 +799,11 @@  -- | Get the name of a dependency. depName :: Dependency -> PackageName-depName = \(Dependency n _) -> fromCabalPackageName n+depName (Dependency n _) = fromCabalPackageName n  -- | Get the version range of a dependency. depRange :: Dependency -> VersionRange-depRange = \(Dependency _ r) -> r+depRange (Dependency _ r) = r  -- | Try to resolve the list of base names in the given directory by -- looking for unique instances of base names applied with the given@@ -801,10 +811,10 @@ -- dependencies. resolveFilesAndDeps     :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader (Path Abs File, Path Abs Dir) m)-    => Maybe (String) -- ^ Package component name-    -> [Path Abs Dir] -- ^ Directories to look in.+    => Maybe String         -- ^ Package component name+    -> [Path Abs Dir]       -- ^ Directories to look in.     -> [DotCabalDescriptor] -- ^ Base names.-    -> [Text] -- ^ Extentions.+    -> [Text]               -- ^ Extentions.     -> m (Set ModuleName,Set DotCabalPath,[PackageWarning]) resolveFilesAndDeps component dirs names0 exts = do     (dotCabalPaths,foundModules) <- loop names0 S.empty@@ -833,7 +843,7 @@     warnUnlisted foundModules = do         let unlistedModules =                 foundModules `S.difference`-                (S.fromList $ mapMaybe dotCabalModule names0)+                S.fromList (mapMaybe dotCabalModule names0)         cabalfp <- asks fst         return $             if S.null unlistedModules@@ -913,8 +923,8 @@     -> [DotCabalDescriptor] -- ^ Base names.     -> [Text] -- ^ Extentions.     -> m [DotCabalPath]-resolveFiles dirs names exts = do-    liftM catMaybes (forM names (findCandidate dirs exts))+resolveFiles dirs names exts =+    forMaybeM names (findCandidate dirs exts)  -- | Find a candidate for the given module-or-filename from the list -- of directories and given extensions.@@ -932,8 +942,7 @@         [] -> do             case name of                 DotCabalModule mn-                  | not (display mn == paths_pkg pkg) -> do-                      logPossibilities dirs mn+                  | display mn /= paths_pkg pkg -> logPossibilities dirs mn                 _ -> return ()             return Nothing         (candidate:rest) -> do@@ -958,12 +967,10 @@             DotCabalCFile fp -> liftM return (try (resolveFile' dir fp))             DotCabalModule mn ->                 mapM-                    (\ext ->-                          try-                              (resolveFile'-                                   dir-                                   (Cabal.toFilePath mn ++ "." ++ ext)))-                    (map T.unpack exts)+                  ((\ ext ->+                     try (resolveFile' dir (Cabal.toFilePath mn ++ "." ++ ext)))+                   . T.unpack)+                   exts     resolveFile'         :: (MonadIO m, MonadThrow m)         => Path Abs Dir -> FilePath.FilePath -> m (Path Abs File)@@ -983,7 +990,7 @@     $logWarn         ("There were multiple candidates for the Cabal entry \"" <>          showName name <>-         "(" <>+         "\" (" <>          T.intercalate "," (map (T.pack . toFilePath) rest) <>          "), picking " <>          T.pack (toFilePath candidate))@@ -1053,8 +1060,8 @@   env <- ask   let stack = configProjectWorkDir env   fp <- parseRelFile $ concat $-    (packageIdentifierString (packageIdentifier package')) :-    (maybe id (\suffix -> ("-" :) . (suffix :)) msuffix) [".log"]+    packageIdentifierString (packageIdentifier package') :+    maybe id (\suffix -> ("-" :) . (suffix :)) msuffix [".log"]   return $ stack </> $(mkRelDir "logs") </> fp  -- Internal helper to define resolveFileOrWarn and resolveDirOrWarn
src/Stack/PackageDump.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-}@@ -13,6 +12,7 @@     , DumpPackage (..)     , conduitDumpPackage     , ghcPkgDump+    , ghcPkgDescribe     , InstalledCache     , InstalledCacheEntry (..)     , newInstalledCache@@ -45,13 +45,15 @@ import           Data.IORef import           Data.Map (Map) import qualified Data.Map as Map-import           Data.Maybe (catMaybes)+import           Data.Maybe (catMaybes, listToMaybe)+import           Data.Maybe.Extra (mapMaybeM) import qualified Data.Set as Set import qualified Data.Text.Encoding as T import           Data.Typeable (Typeable) import           GHC.Generics (Generic) import           Path import           Path.IO (createTree)+import           Path.Extra (toFilePathNoTrailingSep) import           Prelude -- Fix AMP warning import           Stack.GhcPkg import           Stack.Types@@ -83,18 +85,41 @@     -> [Path Abs Dir] -- ^ if empty, use global     -> Sink ByteString IO a     -> m a-ghcPkgDump menv wc mpkgDbs sink = do+ghcPkgDump = ghcPkgCmdArgs ["dump"]++-- | Call ghc-pkg describe with appropriate flags and stream to the given @Sink@, for a single database+ghcPkgDescribe+    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)+    => PackageName+    -> EnvOverride+    -> WhichCompiler+    -> [Path Abs Dir] -- ^ if empty, use global+    -> Sink ByteString IO a+    -> m a+ghcPkgDescribe pkgName = ghcPkgCmdArgs ["describe", "--simple-output", packageNameString pkgName]++-- | Call ghc-pkg and stream to the given @Sink@, for a single database+ghcPkgCmdArgs+    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)+    => [String]+    -> EnvOverride+    -> WhichCompiler+    -> [Path Abs Dir] -- ^ if empty, use global+    -> Sink ByteString IO a+    -> m a+ghcPkgCmdArgs cmd menv wc mpkgDbs sink = do     case reverse mpkgDbs of-        (pkgDb:_) -> (createDatabase menv wc) pkgDb -- TODO maybe use some retry logic instead?+        (pkgDb:_) -> createDatabase menv wc pkgDb -- TODO maybe use some retry logic instead?         _ -> return ()-    a <- sinkProcessStdout Nothing menv (ghcPkgExeName wc) args sink-    return a+    sinkProcessStdout Nothing menv (ghcPkgExeName wc) args sink   where     args = concat         [ case mpkgDbs of-            [] -> ["--global", "--no-user-package-db"]-            _ -> ["--user", "--no-user-package-db"] ++ concatMap (\pkgDb -> ["--package-db", toFilePath pkgDb]) mpkgDbs-        , ["dump", "--expand-pkgroot"]+              [] -> ["--global", "--no-user-package-db"]+              _ -> ["--user", "--no-user-package-db"] +++                  concatMap (\pkgDb -> ["--package-db", toFilePathNoTrailingSep pkgDb]) mpkgDbs+        , cmd+        , ["--expand-pkgroot"]         ]  -- | Create a new, empty @InstalledCache@@@ -105,14 +130,14 @@ -- empty cache. loadInstalledCache :: (MonadLogger m, MonadIO m) => Path Abs File -> m InstalledCache loadInstalledCache path = do-    m <- taggedDecodeOrLoad (toFilePath path) (return $ InstalledCacheInner Map.empty)-    liftIO $ fmap InstalledCache $ newIORef m+    m <- taggedDecodeOrLoad path (return $ InstalledCacheInner Map.empty)+    liftIO $ InstalledCache <$> newIORef m  -- | Save a @InstalledCache@ to disk saveInstalledCache :: MonadIO m => Path Abs File -> InstalledCache -> m () saveInstalledCache path (InstalledCache ref) = liftIO $ do     createTree (parent path)-    readIORef ref >>= taggedEncodeFile (toFilePath path)+    readIORef ref >>= taggedEncodeFile path  -- | Prune a list of possible packages down to those whose dependencies are met. --@@ -129,7 +154,7 @@     -> Map name item pruneDeps getName getId getDepends chooseBest =       Map.fromList-    . (map $ \item -> (getName $ getId item, item))+    . fmap (getName . getId &&& id)     . loop Set.empty Set.empty []   where     loop foundIds usedNames foundItems dps =@@ -247,6 +272,7 @@     , dpHasExposedModules :: !Bool     , dpDepends :: ![GhcPkgId]     , dpHaddockInterfaces :: ![FilePath]+    , dpHaddockHtml :: !(Maybe FilePath)     , dpProfiling :: !profiling     , dpHaddock :: !haddock     , dpIsExposed :: !Bool@@ -282,10 +308,7 @@                 _ -> throwM $ MissingSingleField k m         -- Can't fail: if not found, same as an empty list. See:         -- https://github.com/fpco/stack/issues/182-        parseM k =-            case Map.lookup k m of-                Just vs -> vs-                Nothing -> []+        parseM k = Map.findWithDefault [] k m          parseDepend :: MonadThrow m => ByteString -> m (Maybe GhcPkgId)         parseDepend "builtin_rts" = return Nothing@@ -311,7 +334,7 @@                 libraries = parseM "hs-libraries"                 exposedModules = parseM "exposed-modules"                 exposed = parseM "exposed"-            depends <- mapM parseDepend $ parseM "depends"+            depends <- mapMaybeM parseDepend $ parseM "depends"              let parseQuoted key =                     case mapM (P.parseOnly (argsParser NoEscaping) . T.decodeUtf8) val of@@ -321,6 +344,7 @@                     val = parseM key             libDirPaths <- parseQuoted libDirKey             haddockInterfaces <- parseQuoted "haddock-interfaces"+            haddockHtml <- parseQuoted "haddock-html"              return $ Just DumpPackage                 { dpGhcPkgId = ghcPkgId@@ -328,8 +352,9 @@                 , dpLibDirs = libDirPaths                 , dpLibraries = S8.words $ S8.unwords libraries                 , dpHasExposedModules = not (null libraries || null exposedModules)-                , dpDepends = catMaybes (depends :: [Maybe GhcPkgId])+                , dpDepends = depends                 , dpHaddockInterfaces = haddockInterfaces+                , dpHaddockHtml = listToMaybe haddockHtml                 , dpProfiling = ()                 , dpHaddock = ()                 , dpIsExposed = exposed == ["True"]
src/Stack/PackageIndex.hs view
@@ -17,15 +17,13 @@ -- | Dealing with the 00-index file and all its cabal files. module Stack.PackageIndex     ( updateAllIndices-    , PackageDownload (..)-    , PackageCache (..)     , getPackageCaches     ) where  import qualified Codec.Archive.Tar as Tar import           Control.Exception (Exception) import           Control.Exception.Enclosed (tryIO)-import           Control.Monad (unless, when, liftM, mzero)+import           Control.Monad (unless, when, liftM) import           Control.Monad.Catch (MonadThrow, throwM, MonadCatch) import qualified Control.Monad.Catch as C import           Control.Monad.IO.Class (MonadIO, liftIO)@@ -35,9 +33,7 @@ import           Control.Monad.Trans.Control  import           Data.Aeson.Extended-import qualified Data.Binary as Binary import           Data.Binary.VersionTagged-import           Data.ByteString (ByteString) import qualified Data.Word8 as Word8 import qualified Data.ByteString as S import qualified Data.ByteString.Unsafe as SU@@ -55,19 +51,11 @@ import           Data.Text (Text) import qualified Data.Text as T --import           Data.Text.Encoding (encodeUtf8)- import           Data.Traversable (forM)  import           Data.Typeable (Typeable) -import           Data.Word (Word64) ---import           GHC.Generics (Generic)- import           Network.HTTP.Download import           Path                                  (mkRelDir, parent,                                                         parseRelDir, toFilePath,@@ -81,24 +69,6 @@                                                         withBinaryFile) import           System.Process.Read (readInNull, EnvOverride, doesExecutableExist) -data PackageCache = PackageCache-    { pcOffset :: !Int64-    -- ^ offset in bytes into the 00-index.tar file for the .cabal file contents-    , pcSize :: !Int64-    -- ^ size in bytes of the .cabal file-    , pcDownload :: !(Maybe PackageDownload)-    }-    deriving (Generic)--instance Binary PackageCache-instance NFData PackageCache-instance HasStructuralInfo PackageCache--newtype PackageCacheMap = PackageCacheMap (Map PackageIdentifier PackageCache)-    deriving (Generic, Binary, NFData)-instance HasStructuralInfo PackageCacheMap-instance HasSemanticVersion PackageCacheMap- -- | Populate the package index caches and return them. populateCache     :: (MonadIO m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m, MonadCatch m)@@ -244,7 +214,7 @@         (True, ILGitHttp url _) -> logUpdate url >> updateIndexGit menv name index url         (_, ILHttp url) -> logUpdate url >> updateIndexHTTP name index url         (False, ILGitHttp _ url) -> logUpdate url >> updateIndexHTTP name index url-        (False, ILGit url) -> logUpdate url >> (throwM $ GitNotAvailable name)+        (False, ILGit url) -> logUpdate url >> throwM (GitNotAvailable name)  -- | Update the index Git repo and the index tarball updateIndexGit :: (MonadIO m,MonadLogger m,MonadThrow m,MonadReader env m,HasConfig env,MonadBaseControl IO m, MonadCatch m)@@ -280,15 +250,15 @@             $logStickyDone "Fetched package index."             removeFileIfExists tarFile             when (indexGpgVerify index)-                 (do readInNull acfDir-                                "git"-                                menv-                                ["tag","-v","current-hackage"]-                                (Just (T.unlines ["Signature verification failed. "-                                                 ,"Please ensure you've set up your"-                                                 ,"GPG keychain to accept the D6CF60FD signing key."-                                                 ,"For more information, see:"-                                                 ,"https://github.com/fpco/stackage-update#readme"])))+                 (readInNull acfDir+                             "git"+                             menv+                             ["tag","-v","current-hackage"]+                             (Just (T.unlines ["Signature verification failed. "+                                              ,"Please ensure you've set up your"+                                              ,"GPG keychain to accept the D6CF60FD signing key."+                                              ,"For more information, see:"+                                              ,"https://github.com/fpco/stackage-update#readme"])))             $logDebug ("Exporting a tarball to " <>                        (T.pack . toFilePath) tarFile)             deleteCache indexName'@@ -357,31 +327,6 @@         Left e -> $logDebug $ "Could not delete cache: " <> T.pack (show e)         Right () -> $logDebug $ "Deleted index cache at " <> T.pack (toFilePath fp) -data PackageDownload = PackageDownload-    { pdSHA512 :: !ByteString-    , pdUrl    :: !ByteString-    , pdSize   :: !Word64-    }-    deriving (Show, Generic)-instance Binary.Binary PackageDownload-instance HasStructuralInfo PackageDownload-instance NFData PackageDownload-instance FromJSON PackageDownload where-    parseJSON = withObject "Package" $ \o -> do-        hashes <- o .: "package-hashes"-        sha512 <- maybe mzero return (Map.lookup ("SHA512" :: Text) hashes)-        locs <- o .: "package-locations"-        url <--            case reverse locs of-                [] -> mzero-                x:_ -> return x-        size <- o .: "package-size"-        return PackageDownload-            { pdSHA512 = encodeUtf8 sha512-            , pdUrl = encodeUtf8 url-            , pdSize = size-            }- -- | Load the cached package URLs, or created the cache if necessary. getPackageCaches :: (MonadIO m, MonadLogger m, MonadReader env m, HasConfig env, MonadThrow m, HasHttpManager env, MonadBaseControl IO m, MonadCatch m)                  => EnvOverride@@ -389,7 +334,7 @@ getPackageCaches menv = do     config <- askConfig     liftM mconcat $ forM (configPackageIndices config) $ \index -> do-        fp <- liftM toFilePath $ configPackageIndexCache (indexName index)+        fp <- configPackageIndexCache (indexName index)         PackageCacheMap pis' <- taggedDecodeOrLoad fp $ liftM PackageCacheMap $ populateCache menv index          return (fmap (index,) pis')
src/Stack/SDist.hs view
@@ -14,7 +14,7 @@ import qualified Codec.Compression.GZip as GZip import           Control.Applicative import           Control.Concurrent.Execute (ActionContext(..))-import           Control.Monad (when, void)+import           Control.Monad (unless, void) import           Control.Monad.Catch (MonadMask) import           Control.Monad.IO.Class import           Control.Monad.Logger@@ -33,6 +33,7 @@ import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TLE+import           Data.Time.Clock.POSIX import           Distribution.Package (Dependency (..)) import           Distribution.PackageDescription.PrettyPrint (showGenericPackageDescription) import           Distribution.Version (simplifyVersionRange, orLaterVersion, earlierVersion)@@ -50,6 +51,7 @@ import           Stack.Package import           Stack.Types import           Stack.Types.Internal+import           System.Directory (getModificationTime, getPermissions, Permissions(..)) import qualified System.FilePath as FP  type M env m = (MonadIO m,MonadReader env m,HasHttpManager env,MonadLogger m,MonadBaseControl IO m,MonadMask m,HasLogLevel env,HasEnvConfig env,HasTerminal env)@@ -80,15 +82,13 @@     -- everything in at once, so that's what we're doing for now:     let tarPath isDir fp = either error id             (Tar.toTarPath isDir (pkgId FP.</> fp))-        packWith f isDir fp =-            liftIO $ f (pkgFp FP.</> fp)-                (tarPath isDir fp)+        packWith f isDir fp = liftIO $ f (pkgFp FP.</> fp) (tarPath isDir fp)         packDir = packWith Tar.packDirectoryEntry True         packFile fp             | tweakCabal && isCabalFp fp = do                 lbs <- getCabalLbs pvpBounds $ toFilePath cabalfp                 return $ Tar.fileEntry (tarPath False fp) lbs-            | otherwise = packWith Tar.packFileEntry False fp+            | otherwise = packWith packFileEntry False fp         isCabalFp fp = toFilePath pkgDir FP.</> fp == toFilePath cabalfp         tarName = pkgId FP.<.> "tar.gz"         pkgId = packageIdentifierString (packageIdentifier (lpPackage lp))@@ -103,7 +103,7 @@     (_warnings, gpd) <- readPackageUnresolvedBS Nothing bs     (_, _, _, _, sourceMap) <- loadSourceMap AllowNoTargets defaultBuildOpts     menv <- getMinimalEnvOverride-    (installedMap, _, _) <- getInstalled menv GetInstalledOpts+    (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts                                 { getInstalledProfiling = False                                 , getInstalledHaddock = False                                 }@@ -126,7 +126,7 @@               Just (PSUpstream version _ _) -> Just version               Nothing ->                   case Map.lookup name installedMap of-                      Just (version, _, _) -> Just version+                      Just (_, installed) -> Just (installedVersion installed)                       Nothing -> Nothing  @@ -171,7 +171,7 @@     mapM_ (printCabalFileWarning cabalfp) warnings     return LocalPackage         { lpPackage = package-        , lpExeComponents = Nothing -- HACK: makes it so that sdist output goes to a log instead of a file.+        , lpWanted = False -- HACK: makes it so that sdist output goes to a log instead of a file.         , lpDir = pkgDir         , lpCabalFile = cabalfp         -- NOTE: these aren't the 'correct values, but aren't used in@@ -183,6 +183,7 @@         , lpNewBuildCache = Map.empty         , lpFiles = Set.empty         , lpComponents = Set.empty+        , lpUnbuildable = Set.empty         }  -- | Returns a newline-separate list of paths, and the absolute path to the .cabal file.@@ -192,11 +193,11 @@         menv <- getMinimalEnvOverride         let bopts = defaultBuildOpts         baseConfigOpts <- mkBaseConfigOpts bopts-        (_, _mbp, locals, _extraToBuild, sourceMap) <- loadSourceMap NeedTargets bopts+        (_, _mbp, locals, _extraToBuild, _sourceMap) <- loadSourceMap NeedTargets bopts         runInBase <- liftBaseWith $ \run -> return (void . run)         withExecuteEnv menv bopts baseConfigOpts locals-            [] -- provide empty list of globals. This is a hack around custom Setup.hs files-            sourceMap $ \ee -> do+            [] [] [] -- provide empty list of globals. This is a hack around custom Setup.hs files+            $ \ee ->             withSingleContext runInBase ac ee task Nothing (Just "sdist") $ \_package cabalfp _pkgDir cabal _announce _console _mlogFile -> do                 let outFile = toFilePath tmpdir FP.</> "source-files-list"                 cabal False ["sdist", "--list-sources", outFile]@@ -213,13 +214,14 @@             , tcoOpts = \_ -> ConfigureOpts [] []             }         , taskPresent = Map.empty+        , taskAllInOne = True         }  normalizeTarballPaths :: M env m => [FilePath] -> m [FilePath] normalizeTarballPaths fps = do     --TODO: consider whether erroring out is better - otherwise the     --user might upload an incomplete tar?-    when (not (null outsideDir)) $+    unless (null outsideDir) $         $logWarn $ T.concat             [ "Warning: These files are outside of the package directory, and will be omitted from the tarball: "             , T.pack (show outsideDir)]@@ -228,7 +230,7 @@     (outsideDir, files) = partitionEithers (map pathToEither fps)     pathToEither fp = maybe (Left fp) Right (normalizePath fp) -normalizePath :: FilePath -> (Maybe FilePath)+normalizePath :: FilePath -> Maybe FilePath normalizePath = fmap FP.joinPath . go . FP.splitDirectories . FP.normalise   where     go [] = Just []@@ -243,3 +245,27 @@     go s x       | Set.member x s = s       | otherwise = go (Set.insert x s) (FP.takeDirectory x)++--------------------------------------------------------------------------------++-- Copy+modified from the tar package to avoid issues with lazy IO ( see+-- https://github.com/commercialhaskell/stack/issues/1344 )++packFileEntry :: FilePath -- ^ Full path to find the file on the local disk+              -> Tar.TarPath  -- ^ Path to use for the tar Entry in the archive+              -> IO Tar.Entry+packFileEntry filepath tarpath = do+  mtime   <- getModTime filepath+  perms   <- getPermissions filepath+  content <- S.readFile filepath+  let size = fromIntegral (S.length content)+  return (Tar.simpleEntry tarpath (Tar.NormalFile (L.fromStrict content) size)) {+    Tar.entryPermissions = if executable perms then Tar.executableFilePermissions+                                               else Tar.ordinaryFilePermissions,+    Tar.entryTime = mtime+  }++getModTime :: FilePath -> IO Tar.EpochTime+getModTime path = do+    t <- getModificationTime path+    return . floor . utcTimeToPOSIXSeconds $ t
src/Stack/Setup.hs view
@@ -42,7 +42,7 @@ import           Data.Foldable hiding (concatMap, or, maximum) import           Data.IORef import           Data.IORef.RunOnce (runOnce)-import           Data.List hiding (concat, elem, maximumBy)+import           Data.List hiding (concat, elem, maximumBy, any) import           Data.Map (Map) import qualified Data.Map as Map import           Data.Maybe@@ -64,16 +64,20 @@ import           Network.HTTP.Client.Conduit import           Network.HTTP.Download.Verified import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO import qualified Paths_stack as Meta-import           Prelude hiding (concat, elem) -- Fix AMP warning+import           Prelude hiding (concat, elem, any) -- Fix AMP warning import           Safe (readMay)-import           Stack.Config (resolvePackageEntry)+import           Stack.Build (build)+import           Stack.Config (resolvePackageEntry, loadConfig) import           Stack.Constants (distRelativeDir, stackProgName)+import           Stack.Exec (defaultEnvSettings) import           Stack.Fetch import           Stack.GhcPkg (createDatabase, getCabalPkgVer, getGlobalDB, mkGhcPackagePath) import           Stack.Setup.Installed import           Stack.Types+import           Stack.Types.Internal (HasTerminal, HasReExec, HasLogLevel) import           Stack.Types.StackT import qualified System.Directory as D import           System.Environment (getExecutablePath)@@ -178,7 +182,7 @@         , "' option to specify a location"]  -- | Modify the environment variables (like PATH) appropriately, possibly doing installation too-setupEnv :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasBuildConfig env, HasHttpManager env, HasGHCVariant env, MonadBaseControl IO m)+setupEnv :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasBuildConfig env, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, HasGHCVariant env, MonadBaseControl IO m)          => Maybe Text -- ^ Message to give user when necessary GHC is not available          -> m EnvConfig setupEnv mResolveMissingGHC = do@@ -235,7 +239,8 @@     localdb <- runReaderT packageDatabaseLocal envConfig0     createDatabase menv wc localdb     globaldb <- getGlobalDB menv wc-    let mkGPP locals = mkGhcPackagePath locals localdb deps globaldb+    extras <- runReaderT packageDatabaseExtra envConfig0+    let mkGPP locals = mkGhcPackagePath locals localdb deps extras globaldb      distDir <- runReaderT distRelativeDir envConfig0 @@ -266,21 +271,20 @@                                 else id)                          -- For reasoning and duplication, see: https://github.com/fpco/stack/issues/70-                        $ Map.insert "HASKELL_PACKAGE_SANDBOX" (T.pack $ toFilePathNoTrailingSlash deps)+                        $ Map.insert "HASKELL_PACKAGE_SANDBOX" (T.pack $ toFilePathNoTrailingSep deps)                         $ Map.insert "HASKELL_PACKAGE_SANDBOXES"                             (T.pack $ if esIncludeLocals es                                 then intercalate [searchPathSeparator]-                                        [ toFilePathNoTrailingSlash localdb-                                        , toFilePathNoTrailingSlash deps+                                        [ toFilePathNoTrailingSep localdb+                                        , toFilePathNoTrailingSep deps                                         , ""                                         ]                                 else intercalate [searchPathSeparator]-                                        [ toFilePathNoTrailingSlash deps+                                        [ toFilePathNoTrailingSep deps                                         , ""                                         ])-                        $ Map.insert "HASKELL_DIST_DIR" (T.pack $ toFilePathNoTrailingSlash distDir)-                        $ env-                    !() <- atomicModifyIORef envRef $ \m' ->+                        $ Map.insert "HASKELL_DIST_DIR" (T.pack $ toFilePathNoTrailingSep distDir) env+                    () <- atomicModifyIORef envRef $ \m' ->                         (Map.insert es eo m', ())                     return eo @@ -307,7 +311,7 @@     }  -- | Ensure compiler (ghc or ghcjs) is installed and provide the PATHs to add if necessary-ensureCompiler :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, HasGHCVariant env, MonadBaseControl IO m)+ensureCompiler :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, HasGHCVariant env, MonadBaseControl IO m)                => SetupOpts                -> m (Maybe ExtraDirs) ensureCompiler sopts = do@@ -336,12 +340,14 @@                 arch /= expectedArch         isWanted = isWantedCompiler (soptsCompilerCheck sopts) (soptsWantedCompiler sopts) -    -- If we need to install a GHC, try to do so+        -- If we need to install a GHC or MSYS, try to do so+        -- Return the additional directory paths of GHC & MSYS.     mtools <- if needLocal         then do             getSetupInfo' <- runOnce (getSetupInfo (soptsStackSetupYaml sopts) =<< asks getHttpManager) -            installed <- listInstalled+            localPrograms <- asks $ configLocalPrograms . getConfig+            installed <- listInstalled localPrograms              -- Install GHC             ghcVariant <- asks getGHCVariant@@ -361,7 +367,7 @@                             (soptsWantedCompiler sopts)                             (soptsCompilerCheck sopts)                             (soptsGHCBindistURL sopts)-                    | otherwise -> do+                    | otherwise ->                         throwM $ CompilerVersionMismatch                             msystem                             (soptsWantedCompiler sopts, expectedArch)@@ -400,6 +406,7 @@     mpaths <- case mtools of         Nothing -> return Nothing         Just (compilerTool, mmsys2Tool) -> do+            -- Add GHC's and MSYS's paths to the config.             let idents = catMaybes [Just compilerTool, mmsys2Tool]             paths <- mapM extraDirs idents             return $ Just $ mconcat paths@@ -501,7 +508,7 @@              dir <-                 case Map.lookup ident m of-                    Nothing -> error $ "upgradeCabal: Invariant violated, dir missing"+                    Nothing -> error "upgradeCabal: Invariant violated, dir missing"                     Just dir -> return dir              runIn dir (compilerExeName wc) menv ["Setup.hs"] Nothing@@ -621,16 +628,16 @@ downloadAndInstallTool programsDir si downloadInfo tool installer = do     (file, at) <- downloadFromInfo programsDir downloadInfo tool     dir <- installDir programsDir tool-    unmarkInstalled tool+    unmarkInstalled programsDir tool     installer si file at dir-    markInstalled tool+    markInstalled programsDir tool     return tool -downloadAndInstallCompiler :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasGHCVariant env, HasHttpManager env, MonadBaseControl IO m)+downloadAndInstallCompiler :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasGHCVariant env, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, MonadBaseControl IO m)                            => SetupInfo                            -> CompilerVersion                            -> VersionCheck-                           -> (Maybe String)+                           -> Maybe String                            -> m Tool downloadAndInstallCompiler si wanted@(GhcVersion{}) versionCheck mbindistURL = do     ghcVariant <- asks getGHCVariant@@ -664,7 +671,7 @@     ghcPkgName <- parsePackageNameFromString ("ghc" ++ ghcVariantSuffix ghcVariant)     let tool = Tool $ PackageIdentifier ghcPkgName selectedVersion     downloadAndInstallTool (configLocalPrograms config) si downloadInfo tool installer-downloadAndInstallCompiler si wanted@(GhcjsVersion version _) versionCheck _mbindistUrl = do+downloadAndInstallCompiler si wanted versionCheck _mbindistUrl = do     config <- asks getConfig     ghcVariant <- asks getGHCVariant     case ghcVariant of@@ -675,7 +682,11 @@         Just pairs -> getWantedCompilerInfo "source" versionCheck wanted id pairs     $logInfo "Preparing to install GHCJS to an isolated location."     $logInfo "This will not interfere with any system-level installation."-    downloadAndInstallTool (configLocalPrograms config) si downloadInfo (ToolGhcjs selectedVersion) (installGHCJS version)+    let tool = ToolGhcjs selectedVersion+        installer = installGHCJS $ case selectedVersion of+            GhcjsVersion version _ -> version+            _ -> error "Invariant violated: expected ghcjs version in downloadAndInstallCompiler."+    downloadAndInstallTool (configLocalPrograms config) si downloadInfo tool installer  getWantedCompilerInfo :: (Ord k, MonadThrow m)                       => Text@@ -684,7 +695,7 @@                       -> (k -> CompilerVersion)                       -> Map k a                       -> m (k, a)-getWantedCompilerInfo key versionCheck wanted toCV pairs = do+getWantedCompilerInfo key versionCheck wanted toCV pairs =     case mpair of         Just pair -> return pair         Nothing -> throwM $ UnknownCompilerVersion key wanted (map toCV (Map.keys pairs))@@ -704,7 +715,7 @@  getOSKey :: (MonadReader env m, MonadThrow m, HasPlatform env, MonadLogger m, MonadIO m, MonadCatch m, MonadBaseControl IO m)          => Platform -> m Text-getOSKey platform = do+getOSKey platform =     case platform of         Platform I386   Cabal.Linux   -> return "linux32"         Platform X86_64 Cabal.Linux   -> return "linux64"@@ -783,7 +794,7 @@             parseRelDir $             "ghc-" ++ versionString version -        $logSticky $ T.concat ["Unpacking GHC into ", (T.pack . toFilePath $ root), " ..."]+        $logSticky $ T.concat ["Unpacking GHC into ", T.pack . toFilePath $ root, " ..."]         $logDebug $ "Unpacking " <> T.pack (toFilePath archiveFile)         readInNull root tarTool menv ["xf", toFilePath archiveFile] Nothing @@ -797,7 +808,7 @@         $logStickyDone $ "Installed GHC."         $logDebug $ "GHC installed to " <> T.pack (toFilePath destDir) -installGHCJS :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, MonadBaseControl IO m)+installGHCJS :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, MonadBaseControl IO m)              => Version              -> SetupInfo              -> Path Abs File@@ -827,7 +838,7 @@     let unpackDir = destDir Path.</> $(mkRelDir "src")     tarComponent <- parseRelDir ("ghcjs-" ++ versionString version)     runUnpack <- case platform of-        Platform _ Cabal.Windows -> return $ do+        Platform _ Cabal.Windows -> return $             withUnpackedTarball7z "GHCJS" si archiveFile archiveType tarComponent unpackDir         _ -> do             zipTool' <-@@ -846,23 +857,34 @@                 readInNull destDir tarTool menv ["xf", toFilePath archiveFile] Nothing                 renameDir (destDir Path.</> tarComponent) unpackDir -    $logSticky $ T.concat ["Unpacking GHCJS into ", (T.pack . toFilePath $ unpackDir), " ..."]+    $logSticky $ T.concat ["Unpacking GHCJS into ", T.pack . toFilePath $ unpackDir, " ..."]     $logDebug $ "Unpacking " <> T.pack (toFilePath archiveFile)     runUnpack -    $logSticky "Installing GHCJS (this will take a long time) ..."-    let destBinDir = destDir Path.</> $(mkRelDir "bin")-    stackPath <- liftIO getExecutablePath+    $logSticky "Setting up GHCJS build environment"+    let stackYaml = unpackDir </> $(mkRelFile "stack.yaml")+        destBinDir = destDir Path.</> $(mkRelDir "bin")     createTree destBinDir-    runAndLog (Just unpackDir) stackPath menv-        [ "--install-ghc"-        , "--stack-yaml"-        , toFilePath (unpackDir </> $(mkRelFile "stack.yaml"))-        , "--local-bin-path"-        , toFilePath destBinDir-        , "install"-        , "-v"-        ]+    envConfig <- loadGhcjsEnvConfig stackYaml destBinDir++    -- On windows we need to copy options files out of the install dir.  Argh!+    -- This is done before the build, so that if it fails, things fail+    -- earlier.+    mwindowsInstallDir <- case platform of+        Platform _ Cabal.Windows ->+            liftM Just $ runInnerStackT envConfig installationRootLocal+        _ -> return Nothing++    $logSticky "Installing GHCJS (this will take a long time) ..."+    runInnerStackT envConfig $+        build (\_ -> return ()) Nothing defaultBuildOpts { boptsInstallExes = True }+    -- Copy over *.options files needed on windows.+    forM_ mwindowsInstallDir $ \dir -> do+        (_, files) <- listDirectory (dir </> $(mkRelDir "bin"))+        forM_ (filter ((".options" `isSuffixOf`). toFilePath) files) $ \optionsFile -> do+            let dest = destDir </> $(mkRelDir "bin") </> filename optionsFile+            removeFileIfExists dest+            copyFile optionsFile dest     $logStickyDone "Installed GHCJS."  -- Install the downloaded stack binary distribution@@ -886,7 +908,7 @@         ["xf", toFilePath archiveFile, "--strip-components", "1"]         Nothing -ensureGhcjsBooted :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, MonadBaseControl IO m)+ensureGhcjsBooted :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, MonadCatch m, HasConfig env, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, MonadReader env m)                   => EnvOverride -> CompilerVersion -> Bool -> m () ensureGhcjsBooted menv cv shouldBoot  = do     eres <- try $ sinkProcessStdout Nothing menv "ghcjs" [] (return ())@@ -915,47 +937,40 @@                             parseRelFile $ "ghcjs-" ++ versionString version ++ "/stack.yaml"                         _ -> fail "ensureGhcjsBooted invoked on non GhcjsVersion"                 actualStackYamlExists <- fileExists actualStackYaml-                when (not actualStackYamlExists) $+                unless actualStackYamlExists $                     fail "Couldn't find GHCJS stack.yaml in old or new location."-                bootGhcjs menv actualStackYaml+                bootGhcjs actualStackYaml destDir         Left err -> throwM err -bootGhcjs :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, MonadCatch m)-          => EnvOverride -> Path Abs File -> m ()-bootGhcjs menv stackYaml  = do-    stackPath <- liftIO getExecutablePath+bootGhcjs :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, MonadCatch m, HasHttpManager env, HasTerminal env, HasReExec env, HasLogLevel env, MonadReader env m)+          => Path Abs File -> Path Abs Dir -> m ()+bootGhcjs stackYaml destDir = do+    envConfig <- loadGhcjsEnvConfig stackYaml (destDir </> $(mkRelDir "bin"))+    menv <- liftIO $ configEnvOverride (getConfig envConfig) defaultEnvSettings     -- Install cabal-install if missing, or if the installed one is old.-    mcabal <- getCabalInstallVersion menv stackYaml+    mcabal <- getCabalInstallVersion menv     shouldInstallCabal <- case mcabal of         Nothing -> do-            $logInfo "No 'cabal' binary found for use with GHCJS.  Installing a local copy of 'cabal' from source."+            $logInfo "No cabal-install binary found for use with GHCJS.  Installing a local copy of cabal-install from source."             return True         Just v             | v < $(mkVersion "1.22.4") -> do                 $logInfo $-                    "'cabal' binary found on PATH is too old to be used for booting GHCJS (version " <>+                    "cabal-install found on PATH is too old to be used for booting GHCJS (version " <>                     versionText v <>-                    ").  Installing a local copy of 'cabal' from source."+                    ").  Installing a local copy of cabal-install from source."                 return True             | otherwise -> return False     when shouldInstallCabal $ do         $logSticky "Building cabal-install for use by ghcjs-boot ... "-        runAndLog Nothing stackPath menv-            [ "--stack-yaml"-            , toFilePath stackYaml-            , "build"-            , "cabal-install"-            ]+        runInnerStackT envConfig $+            build (\_ -> return ())+                  Nothing+                  defaultBuildOpts { boptsTargets = ["cabal-install"] }     $logSticky "Booting GHCJS (this will take a long time) ..."-    runAndLog Nothing stackPath menv-        [ "--stack-yaml"-        , toFilePath stackYaml-        , "exec"-        , "--no-ghc-package-path"-        , "--"-        , "ghcjs-boot"-        , "--clean"-        ]+    let envSettings = defaultEnvSettings { esIncludeGhcPackagePath = False }+    menv' <- liftIO $ configEnvOverride (getConfig envConfig) envSettings+    runAndLog Nothing "ghcjs-boot" menv' ["--clean"]     $logStickyDone "GHCJS booted."  -- TODO: something similar is done in Stack.Build.Execute. Create some utilities@@ -966,16 +981,22 @@     let logLines = CB.lines =$ CL.mapM_ (void . restore . monadLoggerLog $(TH.location >>= liftLoc) "" LevelInfo . toLogStr)     void $ restore $ sinkProcessStderrStdout mdir menv name args logLines logLines +loadGhcjsEnvConfig :: (MonadIO m, HasHttpManager r, MonadReader r m, HasTerminal r, HasReExec r, HasLogLevel r)+                     => Path Abs File -> Path b t -> m EnvConfig+loadGhcjsEnvConfig stackYaml binPath = runInnerStackLoggingT $ do+    lc <- loadConfig+        (mempty+            { configMonoidInstallGHC = Just True+            , configMonoidLocalBinPath = Just (toFilePath binPath)+            })+        (Just stackYaml)+    bconfig <- lcLoadBuildConfig lc Nothing Nothing+    runInnerStackT bconfig $ setupEnv Nothing+ getCabalInstallVersion :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, MonadCatch m)-                       => EnvOverride -> Path Abs File -> m (Maybe Version)-getCabalInstallVersion menv stackYaml = do-    ebs <- tryProcessStdout Nothing menv "stack"-      [ "--stack-yaml"-      , toFilePath stackYaml-      , "exec"-      , "--"-      , "cabal"-      , "--numeric-version"]+                       => EnvOverride -> m (Maybe Version)+getCabalInstallVersion menv = do+    ebs <- tryProcessStdout Nothing menv "cabal" ["--numeric-version"]     case ebs of         Left _ -> return Nothing         Right bs -> Just <$> parseVersion (T.encodeUtf8 (T.dropWhileEnd isSpace (T.decodeUtf8 bs)))@@ -1086,11 +1107,10 @@             Nothing -> error $ "Invalid " ++ name ++ " filename: " ++ show archiveFile             Just x -> parseAbsFile $ T.unpack x     run7z <- setup7z si-    let tmpName = (FP.dropTrailingPathSeparator $ toFilePath $ dirname destDir) ++ "-tmp"+    let tmpName = toFilePathNoTrailingSep (dirname destDir) ++ "-tmp"+    createTree (parent destDir)     withCanonicalizedTempDirectory (toFilePath $ parent destDir) tmpName $ \tmpDir -> do         let absSrcDir = tmpDir </> srcDir-        removeTreeIfExists absSrcDir-        removeFileIfExists tarFile         removeTreeIfExists destDir         run7z (parent archiveFile) archiveFile         run7z tmpDir tarFile@@ -1106,7 +1126,7 @@ -- | Download 7z as necessary, and get a function for unpacking things. -- -- Returned function takes an unpack directory and archive.-setup7z :: (MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadIO m, MonadIO n, MonadLogger m, MonadBaseControl IO m)+setup7z :: (MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadIO m, MonadIO n, MonadLogger m, MonadLogger n, MonadBaseControl IO m)         => SetupInfo         -> m (Path Abs Dir -> Path Abs File -> n ()) setup7z si = do@@ -1117,15 +1137,18 @@         (Just sevenzDll, Just sevenzExe) -> do             chattyDownload "7z.dll" sevenzDll dll             chattyDownload "7z.exe" sevenzExe exe-            return $ \outdir archive -> liftIO $ do-                ec <- rawSystem (toFilePath exe)-                    [ "x"-                    , "-o" ++ toFilePath outdir-                    , "-y"-                    , toFilePath archive-                    ]+            return $ \outdir archive -> do+                let cmd = toFilePath exe+                    args =+                        [ "x"+                        , "-o" ++ toFilePath outdir+                        , "-y"+                        , toFilePath archive+                        ]+                $logProcessRun cmd args+                ec <- liftIO $ rawSystem cmd args                 when (ec /= ExitSuccess)-                    $ throwM (ProblemWhileDecompressing archive)+                    $ liftIO $ throwM (ProblemWhileDecompressing archive)         _ -> throwM SetupInfoMissingSevenz  chattyDownload :: (MonadReader env m, HasHttpManager env, MonadIO m, MonadLogger m, MonadThrow m, MonadBaseControl IO m)@@ -1205,7 +1228,7 @@                  (T.unpack label)                  percentage           where percentage :: Double-                percentage = (fromIntegral totalSoFar / fromIntegral total * 100)+                percentage = fromIntegral totalSoFar / fromIntegral total * 100  -- | Given a printf format string for the decimal part and a number of -- bytes, formats the bytes using an appropiate unit and returns the@@ -1273,9 +1296,6 @@         Left e -> throwM $ GHCSanityCheckCompileFailed e ghc         Right _ -> return () -- TODO check that the output of running the command is correct -toFilePathNoTrailingSlash :: Path loc Dir -> FilePath-toFilePathNoTrailingSlash = FP.dropTrailingPathSeparator . toFilePath- -- Remove potentially confusing environment variables removeHaskellEnvVars :: Map Text Text -> Map Text Text removeHaskellEnvVars =@@ -1307,10 +1327,7 @@                 existingVarNames = Set.unions (map snd checkedVars)                 -- True if a locale is already specified by one of the "global" locale variables.                 hasAnyExisting =-                    or $-                    map-                        (`Set.member` existingVarNames)-                        ["LANG", "LANGUAGE", "LC_ALL"]+                    any (`Set.member` existingVarNames) ["LANG", "LANGUAGE", "LC_ALL"]             if null needChangeVars && hasAnyExisting                 then                      -- If no variables need changes and at least one "global" variable is set, no@@ -1392,7 +1409,7 @@     -- -a@.     getFallbackLocale         :: [Text] -> Maybe Text-    getFallbackLocale utf8Locales = do+    getFallbackLocale utf8Locales =         case concatMap (matchingLocales utf8Locales) fallbackPrefixes of             (v:_) -> Just v             [] ->@@ -1403,17 +1420,10 @@     matchingLocales         :: [Text] -> Text -> [Text]     matchingLocales utf8Locales prefix =-        filter-            (\v ->-                  (T.toLower prefix) `T.isPrefixOf` T.toLower v)-            utf8Locales+        filter (\v -> T.toLower prefix `T.isPrefixOf` T.toLower v) utf8Locales     -- Does the locale have one of the encodings in @utf8Suffixes@ (case-insensitive)?     isUtf8Locale locale =-        or $-        map-            (\v ->-                  T.toLower v `T.isSuffixOf` T.toLower locale)-            utf8Suffixes+      any (\ v -> T.toLower v `T.isSuffixOf` T.toLower locale) utf8Suffixes     -- Prefixes of fallback locales (case-insensitive)     fallbackPrefixes = ["C.", "en_US.", "en_"]     -- Suffixes of UTF-8 locales (case-insensitive)
src/Stack/Setup/Installed.hs view
@@ -31,10 +31,10 @@ import           Distribution.System (Platform (..)) import qualified Distribution.System as Cabal import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO import           Prelude hiding (concat, elem) -- Fix AMP warning import           Stack.Types-import qualified System.FilePath as FP import           System.Process.Read  data Tool@@ -55,27 +55,27 @@ parseToolText _ = Nothing  markInstalled :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m)-              => Tool+              => Path Abs Dir+              -> Tool               -> m ()-markInstalled tool = do-    dir <- asks $ configLocalPrograms . getConfig+markInstalled programsPath tool = do     fpRel <- parseRelFile $ toolString tool ++ ".installed"-    liftIO $ writeFile (toFilePath $ dir </> fpRel) "installed"+    liftIO $ writeFile (toFilePath $ programsPath </> fpRel) "installed"  unmarkInstalled :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m)-                => Tool+                => Path Abs Dir+                -> Tool                 -> m ()-unmarkInstalled tool = do-    dir <- asks $ configLocalPrograms . getConfig+unmarkInstalled programsPath tool = do     fpRel <- parseRelFile $ toolString tool ++ ".installed"-    removeFileIfExists $ dir </> fpRel+    removeFileIfExists $ programsPath </> fpRel  listInstalled :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m)-              => m [Tool]-listInstalled = do-    dir <- asks $ configLocalPrograms . getConfig-    createTree dir-    (_, files) <- listDirectory dir+              => Path Abs Dir+              -> m [Tool]+listInstalled programsPath = do+    createTree programsPath+    (_, files) <- listDirectory programsPath     return $ mapMaybe toTool files   where     toTool fp = do@@ -143,7 +143,7 @@             $logWarn $ "binDirs: unexpected OS/tool combo: " <> T.pack (show (x, toolName))             return mempty   where-    goList = map toFilePathNoTrailingSlash+    goList = map toFilePathNoTrailingSep     isGHC n = "ghc" == n || "ghc-" `isPrefixOf` n     isGHCJS n = "ghcjs" == n @@ -166,6 +166,3 @@ installDir programsDir tool = do     reldir <- parseRelDir $ toolString tool     return $ programsDir </> reldir--toFilePathNoTrailingSlash :: Path loc Dir -> FilePath-toFilePathNoTrailingSlash = FP.dropTrailingPathSeparator . toFilePath
src/Stack/Solver.hs view
@@ -47,6 +47,7 @@             -> [String] -- ^ additional arguments             -> m (CompilerVersion, Map PackageName (Version, Map FlagName Bool)) cabalSolver wc cabalfps constraints userFlags cabalArgs = withSystemTempDirectory "cabal-solver" $ \dir -> do+    when (null cabalfps) $ throwM SolverNoCabalFiles     configLines <- getCabalConfig dir constraints     let configFile = dir FP.</> "cabal.config"     liftIO $ S.writeFile configFile $ encodeUtf8 $ T.unlines configLines@@ -58,7 +59,8 @@         case mghc of             Just _ -> return menv0             Nothing -> do-                tools <- listInstalled+                localPrograms <- asks $ configLocalPrograms . getConfig+                tools <- listInstalled localPrograms                 let ghcName = $(mkPackageName "ghc")                 case [version | Tool (PackageIdentifier name version) <- tools, name == ghcName] of                     [] -> throwM SolverMissingGHC@@ -98,7 +100,7 @@              : "--package-db=global"              : cabalArgs ++                toConstraintArgs userFlags ++-               (map toFilePath cabalfps) +++               fmap toFilePath cabalfps ++                ["--ghcjs" | wc == Ghcjs]      $logInfo "Asking cabal to calculate a build plan, please wait"@@ -177,8 +179,6 @@                => Bool -- ^ modify stack.yaml?                -> m () solveExtraDeps modStackYaml = do-    $logInfo "This command is not guaranteed to give you a perfect build plan"-    $logInfo "It's possible that even with the changes generated below, you will still need to do some manual tweaking"     econfig <- asks getEnvConfig     bconfig <- asks getBuildConfig     snapshot <-@@ -204,11 +204,13 @@     let newDeps = extraDeps `Map.difference` packages         newFlags = Map.filter (not . Map.null) $ fmap snd newDeps +    $logInfo "This command is not guaranteed to give you a perfect build plan"     if Map.null newDeps         then $logInfo "No needed changes found"         else do+            $logInfo "It's possible that even with the changes generated below, you will still need to do some manual tweaking"             let o = object-                    $ ("extra-deps" .= (map fromTuple $ Map.toList $ fmap fst newDeps))+                    $ ("extra-deps" .= map fromTuple (Map.toList $ fmap fst newDeps))                     : (if Map.null newFlags                         then []                         else ["flags" .= newFlags])
src/Stack/Types.hs view
@@ -8,6 +8,7 @@ import Stack.Types.FlagName as X import Stack.Types.GhcPkgId as X import Stack.Types.PackageIdentifier as X+import Stack.Types.PackageIndex as X import Stack.Types.PackageName as X import Stack.Types.Version as X import Stack.Types.Config as X
src/Stack/Types/Build.hs view
@@ -67,6 +67,7 @@ import           Distribution.Text (display) import           GHC.Generics import           Path (Path, Abs, File, Dir, mkRelDir, toFilePath, parseRelDir, (</>))+import           Path.Extra (toFilePathNoTrailingSep) import           Prelude import           Stack.Types.FlagName import           Stack.Types.GhcPkgId@@ -77,7 +78,7 @@ import           Stack.Types.PackageName import           Stack.Types.Version import           System.Exit (ExitCode (ExitFailure))-import           System.FilePath (dropTrailingPathSeparator, pathSeparator)+import           System.FilePath (pathSeparator) import           System.Process.Log (showProcessArgDebug)  ----------------------------------------------@@ -119,6 +120,8 @@   | DuplicateLocalPackageNames [(PackageName, [Path Abs Dir])]   | SolverMissingCabalInstall   | SolverMissingGHC+  | SolverNoCabalFiles+  | SomeTargetsNotBuildable [(PackageName, NamedComponent)]   deriving Typeable  data FlagSource = FSCommandLine | FSStackYaml@@ -325,6 +328,14 @@         [ "Solver requires that GHC be on your PATH"         , "Try running 'stack setup'"         ]+    show SolverNoCabalFiles = unlines+        [ "No cabal files provided.  Maybe this is due to not having a stack.yaml file?"+        , "Try running 'stack init' to create a stack.yaml"+        ]+    show (SomeTargetsNotBuildable xs) =+        "The following components have 'buildable: False' set in the cabal configuration, and so cannot be targets:\n    " +++        T.unpack (renderPkgComponents xs) +++        "\nTo resolve this, either provide flags such that these components are buildable, or only specify buildable targets."  instance Exception StackBuildException @@ -404,7 +415,7 @@     -- ^ Only install packages in the snapshot database, skipping     -- packages intended for the local database.     | BSOnlyDependencies-    deriving Show+    deriving (Show, Eq)  -- | Configuration for building. data BuildOpts =@@ -553,10 +564,15 @@  -- | A task to perform when building data Task = Task-    { taskProvides        :: !PackageIdentifier        -- ^ the package/version to be built-    , taskType            :: !TaskType                 -- ^ the task type, telling us how to build this+    { taskProvides        :: !PackageIdentifier+    -- ^ the package/version to be built+    , taskType            :: !TaskType+    -- ^ the task type, telling us how to build this     , taskConfigOpts      :: !TaskConfigOpts-    , taskPresent         :: !(Map PackageIdentifier GhcPkgId)           -- ^ GhcPkgIds of already-installed dependencies+    , taskPresent         :: !(Map PackageIdentifier GhcPkgId)+    -- ^ GhcPkgIds of already-installed dependencies+    , taskAllInOne        :: !Bool+    -- ^ indicates that the package can be built in one step     }     deriving Show @@ -590,7 +606,7 @@ -- | A complete plan of what needs to be built and how to do it data Plan = Plan     { planTasks :: !(Map PackageName Task)-    , planFinals :: !(Map PackageName (Task, LocalPackageTB))+    , planFinals :: !(Map PackageName Task)     -- ^ Final actions to be taken (test, benchmark, etc)     , planUnregisterLocal :: !(Map GhcPkgId (PackageIdentifier, Maybe Text))     -- ^ Text is reason we're unregistering, for display only@@ -629,20 +645,19 @@                   -> [String] configureOptsDirs bco loc package = concat     [ ["--user", "--package-db=clear", "--package-db=global"]-    , map (("--package-db=" ++) . toFilePath) $ case loc of+    , map (("--package-db=" ++) . toFilePathNoTrailingSep) $ case loc of         Snap -> bcoExtraDBs bco ++ [bcoSnapDB bco]         Local -> bcoExtraDBs bco ++ [bcoSnapDB bco] ++ [bcoLocalDB bco]-    , [ "--libdir=" ++ toFilePathNoTrailingSlash (installRoot </> $(mkRelDir "lib"))-      , "--bindir=" ++ toFilePathNoTrailingSlash (installRoot </> bindirSuffix)-      , "--datadir=" ++ toFilePathNoTrailingSlash (installRoot </> $(mkRelDir "share"))-      , "--libexecdir=" ++ toFilePathNoTrailingSlash (installRoot </> $(mkRelDir "libexec"))-      , "--sysconfdir=" ++ toFilePathNoTrailingSlash (installRoot </> $(mkRelDir "etc"))-      , "--docdir=" ++ toFilePathNoTrailingSlash docDir-      , "--htmldir=" ++ toFilePathNoTrailingSlash docDir-      , "--haddockdir=" ++ toFilePathNoTrailingSlash docDir]+    , [ "--libdir=" ++ toFilePathNoTrailingSep (installRoot </> $(mkRelDir "lib"))+      , "--bindir=" ++ toFilePathNoTrailingSep (installRoot </> bindirSuffix)+      , "--datadir=" ++ toFilePathNoTrailingSep (installRoot </> $(mkRelDir "share"))+      , "--libexecdir=" ++ toFilePathNoTrailingSep (installRoot </> $(mkRelDir "libexec"))+      , "--sysconfdir=" ++ toFilePathNoTrailingSep (installRoot </> $(mkRelDir "etc"))+      , "--docdir=" ++ toFilePathNoTrailingSep docDir+      , "--htmldir=" ++ toFilePathNoTrailingSep docDir+      , "--haddockdir=" ++ toFilePathNoTrailingSep docDir]     ]   where-    toFilePathNoTrailingSlash = dropTrailingPathSeparator . toFilePath     installRoot =         case loc of             Snap -> bcoSnapInstallRoot bco@@ -709,10 +724,9 @@       where         PackageIdentifier name version = ident -    ghcOptionsMap = configGhcOptions $ getConfig econfig     allGhcOptions = concat-        [ fromMaybe [] $ Map.lookup Nothing ghcOptionsMap-        , fromMaybe [] $ Map.lookup (Just $ packageName package) ghcOptionsMap+        [ Map.findWithDefault [] Nothing (configGhcOptions config)+        , Map.findWithDefault [] (Just $ packageName package) (configGhcOptions config)         , if includeExtraOptions             then boptsGhcOptions bopts             else []
src/Stack/Types/Compiler.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ViewPatterns #-}  module Stack.Types.Compiler where 
src/Stack/Types/Config.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-}@@ -12,7 +13,111 @@  -- | The Config type. -module Stack.Types.Config where+module Stack.Types.Config+  (+  -- * Main configuration types and classes+  -- ** HasPlatform & HasStackRoot+   HasPlatform(..)+  ,HasStackRoot(..)+  -- ** Config & HasConfig+  ,Config(..)+  ,HasConfig(..)+  ,askConfig+  ,askLatestSnapshotUrl+  ,explicitSetupDeps+  ,getMinimalEnvOverride+  -- ** BuildConfig & HasBuildConfig+  ,BuildConfig(..)+  ,bcRoot+  ,bcWorkDir+  ,HasBuildConfig(..)+  -- ** GHCVariant & HasGHCVariant+  ,GHCVariant(..)+  ,ghcVariantName+  ,ghcVariantSuffix+  ,parseGHCVariant+  ,HasGHCVariant(..)+  ,snapshotsDir+  -- ** EnvConfig & HasEnvConfig+  ,EnvConfig(..)+  ,HasEnvConfig(..)+  ,getWhichCompiler+  -- * Details+  -- ** ApplyGhcOptions+  ,ApplyGhcOptions(..)+  -- ** ConfigException+  ,ConfigException(..)+  -- ** ConfigMonoid+  ,ConfigMonoid(..)+  -- ** EnvSettings+  ,EnvSettings(..)+  ,minimalEnvSettings+  -- ** GlobalOpts & GlobalOptsMonoid+  ,GlobalOpts(..)+  ,GlobalOptsMonoid(..)+  ,defaultLogLevel+  -- ** LoadConfig+  ,LoadConfig(..)+  -- ** PackageEntry & PackageLocation+  ,PackageEntry(..)+  ,peExtraDep+  ,PackageLocation(..)+  -- ** PackageIndex, IndexName & IndexLocation+  ,PackageIndex(..)+  ,IndexName(..)+  ,configPackageIndex+  ,configPackageIndexCache+  ,configPackageIndexGz+  ,configPackageIndexRoot+  ,configPackageTarball+  ,indexNameText+  ,IndexLocation(..)+  -- ** Project & ProjectAndConfigMonoid+  ,Project(..)+  ,ProjectAndConfigMonoid(..)+  -- ** PvpBounds+  ,PvpBounds(..)+  ,parsePvpBounds+  -- ** Resolver & AbstractResolver+  ,Resolver(..)+  ,parseResolverText+  ,resolverName+  ,AbstractResolver(..)+  -- ** SCM+  ,SCM(..)+  -- * Paths+  ,bindirSuffix+  ,configInstalledCache+  ,configMiniBuildPlanCache+  ,configProjectWorkDir+  ,docDirSuffix+  ,flagCacheLocal+  ,extraBinDirs+  ,hpcReportDir+  ,installationRootDeps+  ,installationRootLocal+  ,packageDatabaseDeps+  ,packageDatabaseExtra+  ,packageDatabaseLocal+  ,platformOnlyRelDir+  ,platformVariantRelDir+  ,useShaPathOnWindows+  ,workDirRel+  -- * Command-specific types+  -- ** Eval+  ,EvalOpts(..)+  -- ** Exec+  ,ExecOpts(..)+  ,SpecialExecCmd(..)+  ,ExecOptsExtra(..)+  -- ** Setup+  ,DownloadInfo(..)+  ,VersionedDownloadInfo(..)+  ,SetupInfo(..)+  ,SetupInfoLocation(..)+  -- ** Docker entrypoint+  ,DockerEntrypoint(..)+  ) where  import           Control.Applicative import           Control.Arrow ((&&&))@@ -35,7 +140,6 @@ import           Data.Hashable (Hashable) import           Data.Map (Map) import qualified Data.Map as Map- import qualified Data.Map.Strict as M import           Data.Maybe import           Data.Monoid@@ -58,9 +162,15 @@ import           Stack.Types.FlagName import           Stack.Types.Image import           Stack.Types.PackageIdentifier+import           Stack.Types.PackageIndex import           Stack.Types.PackageName import           Stack.Types.Version+import           System.PosixCompat.Types (UserID, GroupID) import           System.Process.Read (EnvOverride)+#ifdef mingw32_HOST_OS+import qualified Crypto.Hash.SHA1 as SHA1+import qualified Data.ByteString.Base16 as B16+#endif  -- | The top-level Stackage configuration. data Config =@@ -151,6 +261,9 @@          -- ^ Rebuild on GHC options changes          ,configApplyGhcOptions     :: !ApplyGhcOptions          -- ^ Which packages to ghc-options on the command line apply to?+         ,configAllowNewer          :: !Bool+         -- ^ Ignore version ranges in .cabal files. Funny naming chosen to+         -- match cabal.          }  -- | Which packages to ghc-options on the command line apply to?@@ -236,36 +349,69 @@     deriving (Show, Eq, Ord)  data ExecOpts = ExecOpts-    { eoCmd :: !(Maybe String)-    -- ^ Usage of @Maybe@ here is nothing more than a hack, to avoid some weird-    -- bug in optparse-applicative. See:-    -- https://github.com/commercialhaskell/stack/issues/806+    { eoCmd :: !SpecialExecCmd     , eoArgs :: ![String]     , eoExtra :: !ExecOptsExtra-    }+    } deriving (Show) +data SpecialExecCmd+    = ExecCmd String+    | ExecGhc+    | ExecRunGhc+    deriving (Show, Eq)+ data ExecOptsExtra     = ExecOptsPlain     | ExecOptsEmbellished         { eoEnvSettings :: !EnvSettings         , eoPackages :: ![String]         }+    deriving (Show)  data EvalOpts = EvalOpts     { evalArg :: !String     , evalExtra :: !ExecOptsExtra-    }+    } deriving (Show)  -- | Parsed global command-line options. data GlobalOpts = GlobalOpts     { globalReExecVersion :: !(Maybe String) -- ^ Expected re-exec in container version+    , globalDockerEntrypoint :: !(Maybe DockerEntrypoint)+      -- ^ Data used when stack is acting as a Docker entrypoint (internal use only)     , globalLogLevel     :: !LogLevel -- ^ Log level     , globalConfigMonoid :: !ConfigMonoid -- ^ Config monoid, for passing into 'loadConfig'     , globalResolver     :: !(Maybe AbstractResolver) -- ^ Resolver override+    , globalCompiler     :: !(Maybe CompilerVersion) -- ^ Compiler override     , globalTerminal     :: !Bool -- ^ We're in a terminal?     , globalStackYaml    :: !(Maybe FilePath) -- ^ Override project stack.yaml     } deriving (Show) +-- | Parsed global command-line options monoid.+data GlobalOptsMonoid = GlobalOptsMonoid+    { globalMonoidReExecVersion :: !(Maybe String) -- ^ Expected re-exec in container version+    , globalMonoidDockerEntrypoint :: !(Maybe DockerEntrypoint)+      -- ^ Data used when stack is acting as a Docker entrypoint (internal use only)+    , globalMonoidLogLevel     :: !(Maybe LogLevel) -- ^ Log level+    , globalMonoidConfigMonoid :: !ConfigMonoid -- ^ Config monoid, for passing into 'loadConfig'+    , globalMonoidResolver     :: !(Maybe AbstractResolver) -- ^ Resolver override+    , globalMonoidCompiler     :: !(Maybe CompilerVersion) -- ^ Compiler override+    , globalMonoidTerminal     :: !(Maybe Bool) -- ^ We're in a terminal?+    , globalMonoidStackYaml    :: !(Maybe FilePath) -- ^ Override project stack.yaml+    } deriving (Show)++instance Monoid GlobalOptsMonoid where+    mempty = GlobalOptsMonoid Nothing Nothing Nothing mempty Nothing Nothing Nothing Nothing+    mappend l r = GlobalOptsMonoid+        { globalMonoidReExecVersion = globalMonoidReExecVersion l <|> globalMonoidReExecVersion r+        , globalMonoidDockerEntrypoint =+            globalMonoidDockerEntrypoint l <|> globalMonoidDockerEntrypoint r+        , globalMonoidLogLevel = globalMonoidLogLevel l <|> globalMonoidLogLevel r+        , globalMonoidConfigMonoid = globalMonoidConfigMonoid l <> globalMonoidConfigMonoid r+        , globalMonoidResolver = globalMonoidResolver l <|> globalMonoidResolver r+        , globalMonoidCompiler = globalMonoidCompiler l <|> globalMonoidCompiler r+        , globalMonoidTerminal = globalMonoidTerminal l <|> globalMonoidTerminal r+        , globalMonoidStackYaml = globalMonoidStackYaml l <|> globalMonoidStackYaml r }+ -- | Either an actual resolver value, or an abstract description of one (e.g., -- latest nightly). data AbstractResolver@@ -312,15 +458,17 @@       -- for providing better error messages.     , bcGHCVariant :: !GHCVariant       -- ^ The variant of GHC used to select a GHC bindist.+    , bcPackageCaches :: !(Map PackageIdentifier (PackageIndex, PackageCache))+      -- ^ Shared package cache map     }  -- | Directory containing the project's stack.yaml file bcRoot :: BuildConfig -> Path Abs Dir bcRoot = parent . bcStackYaml --- | Directory containing the project's stack.yaml file+-- | @"'bcRoot'/.stack-work"@ bcWorkDir :: BuildConfig -> Path Abs Dir-bcWorkDir = (</> workDirRel) . parent . bcStackYaml+bcWorkDir = (</> workDirRel) . bcRoot  -- | Configuration after the environment has been setup. data EnvConfig = EnvConfig@@ -343,7 +491,7 @@ data LoadConfig m = LoadConfig     { lcConfig          :: !Config       -- ^ Top-level Stack configuration.-    , lcLoadBuildConfig :: !(Maybe AbstractResolver -> m BuildConfig)+    , lcLoadBuildConfig :: !(Maybe AbstractResolver -> Maybe CompilerVersion -> m BuildConfig)         -- ^ Action to load the remaining 'BuildConfig'.     , lcProjectRoot     :: !(Maybe (Path Abs Dir))         -- ^ The project root directory, if in a project.@@ -435,12 +583,15 @@     -- ^ Per-package flag overrides     , projectResolver :: !Resolver     -- ^ How we resolve which dependencies to use+    , projectCompiler :: !(Maybe CompilerVersion)+    -- ^ When specified, overrides which compiler to use     , projectExtraPackageDBs :: ![FilePath]     }   deriving Show  instance ToJSON Project where-    toJSON p = object+    toJSON p = object $+        (maybe id (\cv -> (("compiler" .= cv) :)) (projectCompiler p))         [ "packages"          .= projectPackages p         , "extra-deps"        .= map fromTuple (Map.toList $ projectExtraDeps p)         , "flags"             .= projectFlags p@@ -605,6 +756,9 @@     ,configMonoidRebuildGhcOptions   :: !(Maybe Bool)     -- ^ See 'configMonoidRebuildGhcOptions'     ,configMonoidApplyGhcOptions     :: !(Maybe ApplyGhcOptions)+    -- ^ See 'configApplyGhcOptions'+    ,configMonoidAllowNewer          :: !(Maybe Bool)+    -- ^ See 'configMonoidAllowNewer'     }   deriving Show @@ -640,6 +794,7 @@     , configMonoidExplicitSetupDeps = mempty     , configMonoidRebuildGhcOptions = Nothing     , configMonoidApplyGhcOptions = Nothing+    , configMonoidAllowNewer = Nothing     }   mappend l r = ConfigMonoid     { configMonoidDockerOpts = configMonoidDockerOpts l <> configMonoidDockerOpts r@@ -673,6 +828,7 @@     , configMonoidExplicitSetupDeps = configMonoidExplicitSetupDeps l <> configMonoidExplicitSetupDeps r     , configMonoidRebuildGhcOptions = configMonoidRebuildGhcOptions l <|> configMonoidRebuildGhcOptions r     , configMonoidApplyGhcOptions = configMonoidApplyGhcOptions l <|> configMonoidApplyGhcOptions r+    , configMonoidAllowNewer = configMonoidAllowNewer l <|> configMonoidAllowNewer r     }  instance FromJSON (ConfigMonoid, [JSONWarning]) where@@ -683,57 +839,57 @@ -- warnings for missing fields. parseConfigMonoidJSON :: Object -> WarningParser ConfigMonoid parseConfigMonoidJSON obj = do-    configMonoidDockerOpts <- jsonSubWarnings (obj ..:? "docker" ..!= mempty)-    configMonoidConnectionCount <- obj ..:? "connection-count"-    configMonoidHideTHLoading <- obj ..:? "hide-th-loading"-    configMonoidLatestSnapshotUrl <- obj ..:? "latest-snapshot-url"-    configMonoidPackageIndices <- jsonSubWarningsTT (obj ..:? "package-indices")-    configMonoidSystemGHC <- obj ..:? "system-ghc"-    configMonoidInstallGHC <- obj ..:? "install-ghc"-    configMonoidSkipGHCCheck <- obj ..:? "skip-ghc-check"-    configMonoidSkipMsys <- obj ..:? "skip-msys"+    configMonoidDockerOpts <- jsonSubWarnings (obj ..:? configMonoidDockerOptsName ..!= mempty)+    configMonoidConnectionCount <- obj ..:? configMonoidConnectionCountName+    configMonoidHideTHLoading <- obj ..:? configMonoidHideTHLoadingName+    configMonoidLatestSnapshotUrl <- obj ..:? configMonoidLatestSnapshotUrlName+    configMonoidPackageIndices <- jsonSubWarningsTT (obj ..:?  configMonoidPackageIndicesName)+    configMonoidSystemGHC <- obj ..:? configMonoidSystemGHCName+    configMonoidInstallGHC <- obj ..:? configMonoidInstallGHCName+    configMonoidSkipGHCCheck <- obj ..:? configMonoidSkipGHCCheckName+    configMonoidSkipMsys <- obj ..:? configMonoidSkipMsysName     configMonoidRequireStackVersion <- unVersionRangeJSON <$>-                                       obj ..:? "require-stack-version"+                                       obj ..:? configMonoidRequireStackVersionName                                            ..!= VersionRangeJSON anyVersion-    configMonoidOS <- obj ..:? "os"-    configMonoidArch <- obj ..:? "arch"-    configMonoidGHCVariant <- obj ..:? "ghc-variant"-    configMonoidJobs <- obj ..:? "jobs"-    configMonoidExtraIncludeDirs <- obj ..:? "extra-include-dirs" ..!= Set.empty-    configMonoidExtraLibDirs <- obj ..:? "extra-lib-dirs" ..!= Set.empty-    configMonoidConcurrentTests <- obj ..:? "concurrent-tests"-    configMonoidLocalBinPath <- obj ..:? "local-bin-path"-    configMonoidImageOpts <- jsonSubWarnings (obj ..:? "image" ..!= mempty)+    configMonoidOS <- obj ..:? configMonoidOSName+    configMonoidArch <- obj ..:? configMonoidArchName+    configMonoidGHCVariant <- obj ..:? configMonoidGHCVariantName+    configMonoidJobs <- obj ..:? configMonoidJobsName+    configMonoidExtraIncludeDirs <- obj ..:?  configMonoidExtraIncludeDirsName ..!= Set.empty+    configMonoidExtraLibDirs <- obj ..:?  configMonoidExtraLibDirsName ..!= Set.empty+    configMonoidConcurrentTests <- obj ..:? configMonoidConcurrentTestsName+    configMonoidLocalBinPath <- obj ..:? configMonoidLocalBinPathName+    configMonoidImageOpts <- jsonSubWarnings (obj ..:?  configMonoidImageOptsName ..!= mempty)     templates <- obj ..:? "templates"     (configMonoidScmInit,configMonoidTemplateParameters) <-       case templates of         Nothing -> return (Nothing,M.empty)         Just tobj -> do-          scmInit <- tobj ..:? "scm-init"-          params <- tobj ..:? "params"+          scmInit <- tobj ..:? configMonoidScmInitName+          params <- tobj ..:? configMonoidTemplateParametersName           return (scmInit,fromMaybe M.empty params)-    configMonoidCompilerCheck <- obj ..:? "compiler-check"+    configMonoidCompilerCheck <- obj ..:? configMonoidCompilerCheckName -    mghcoptions <- obj ..:? "ghc-options"+    mghcoptions <- obj ..:? configMonoidGhcOptionsName     configMonoidGhcOptions <-         case mghcoptions of             Nothing -> return mempty             Just m -> fmap Map.fromList $ mapM handleGhcOptions $ Map.toList m -    extraPath <- obj ..:? "extra-path" ..!= []+    extraPath <- obj ..:? configMonoidExtraPathName ..!= []     configMonoidExtraPath <- forM extraPath $         either (fail . show) return . parseAbsDir . T.unpack      configMonoidSetupInfoLocations <--        maybeToList <$> jsonSubWarningsT (obj ..:? "setup-info")--    configMonoidPvpBounds <- obj ..:? "pvp-bounds"-    configMonoidModifyCodePage <- obj ..:? "modify-code-page"+        maybeToList <$> jsonSubWarningsT (obj ..:?  configMonoidSetupInfoLocationsName)+    configMonoidPvpBounds <- obj ..:? configMonoidPvpBoundsName+    configMonoidModifyCodePage <- obj ..:? configMonoidModifyCodePageName     configMonoidExplicitSetupDeps <--        (obj ..:? "explicit-setup-deps" ..!= mempty)+        (obj ..:? configMonoidExplicitSetupDepsName ..!= mempty)         >>= fmap Map.fromList . mapM handleExplicitSetupDep . Map.toList-    configMonoidRebuildGhcOptions <- obj ..:? "rebuild-ghc-options"-    configMonoidApplyGhcOptions <- obj ..:? "apply-ghc-options"+    configMonoidRebuildGhcOptions <- obj ..:? configMonoidRebuildGhcOptionsName+    configMonoidApplyGhcOptions <- obj ..:? configMonoidApplyGhcOptionsName+    configMonoidAllowNewer <- obj ..:? configMonoidAllowNewerName      return ConfigMonoid {..}   where@@ -760,16 +916,99 @@                         Right x -> return $ Just x         return (name, b) --- | Newtype for non-orphan FromJSON instance.-newtype VersionRangeJSON = VersionRangeJSON { unVersionRangeJSON :: VersionRange }+configMonoidDockerOptsName :: Text+configMonoidDockerOptsName = "docker" --- | Parse VersionRange.-instance FromJSON VersionRangeJSON where-  parseJSON = withText "VersionRange"-                (\s -> maybe (fail ("Invalid cabal-style VersionRange: " ++ T.unpack s))-                             (return . VersionRangeJSON)-                             (Distribution.Text.simpleParse (T.unpack s)))+configMonoidConnectionCountName :: Text+configMonoidConnectionCountName = "connection-count" +configMonoidHideTHLoadingName :: Text+configMonoidHideTHLoadingName = "hide-th-loading"++configMonoidLatestSnapshotUrlName :: Text+configMonoidLatestSnapshotUrlName = "latest-snapshot-url"++configMonoidPackageIndicesName :: Text+configMonoidPackageIndicesName = "package-indices"++configMonoidSystemGHCName :: Text+configMonoidSystemGHCName = "system-ghc"++configMonoidInstallGHCName :: Text+configMonoidInstallGHCName = "install-ghc"++configMonoidSkipGHCCheckName :: Text+configMonoidSkipGHCCheckName = "skip-ghc-check"++configMonoidSkipMsysName :: Text+configMonoidSkipMsysName = "skip-msys"++configMonoidRequireStackVersionName :: Text+configMonoidRequireStackVersionName = "require-stack-version"++configMonoidOSName :: Text+configMonoidOSName = "os"++configMonoidArchName :: Text+configMonoidArchName = "arch"++configMonoidGHCVariantName :: Text+configMonoidGHCVariantName = "ghc-variant"++configMonoidJobsName :: Text+configMonoidJobsName = "jobs"++configMonoidExtraIncludeDirsName :: Text+configMonoidExtraIncludeDirsName = "extra-include-dirs"++configMonoidExtraLibDirsName :: Text+configMonoidExtraLibDirsName = "extra-lib-dirs"++configMonoidConcurrentTestsName :: Text+configMonoidConcurrentTestsName = "concurrent-tests"++configMonoidLocalBinPathName :: Text+configMonoidLocalBinPathName = "local-bin-path"++configMonoidImageOptsName :: Text+configMonoidImageOptsName = "image"++configMonoidScmInitName :: Text+configMonoidScmInitName = "scm-init"++configMonoidTemplateParametersName :: Text+configMonoidTemplateParametersName = "params"++configMonoidCompilerCheckName :: Text+configMonoidCompilerCheckName = "compiler-check"++configMonoidGhcOptionsName :: Text+configMonoidGhcOptionsName = "ghc-options"++configMonoidExtraPathName :: Text+configMonoidExtraPathName = "extra-path"++configMonoidSetupInfoLocationsName :: Text+configMonoidSetupInfoLocationsName = "setup-info"++configMonoidPvpBoundsName :: Text+configMonoidPvpBoundsName = "pvp-bounds"++configMonoidModifyCodePageName :: Text+configMonoidModifyCodePageName = "modify-code-page"++configMonoidExplicitSetupDepsName :: Text+configMonoidExplicitSetupDepsName = "explicit-setup-deps"++configMonoidRebuildGhcOptionsName :: Text+configMonoidRebuildGhcOptionsName = "rebuild-ghc-options"++configMonoidApplyGhcOptionsName :: Text+configMonoidApplyGhcOptionsName = "apply-ghc-options"++configMonoidAllowNewerName :: Text+configMonoidAllowNewerName = "allow-newer"+ data ConfigException   = ParseConfigFileException (Path Abs File) ParseException   | ParseResolverException Text@@ -813,9 +1052,9 @@         [ "The version of stack you are using ("         , show (fromCabalVersion Meta.version)         , ") is outside the required\n"-        ,"version range ("+        ,"version range specified in stack.yaml ("         , T.unpack (versionRangeText requiredRange)-        , ") specified in stack.yaml." ]+        , ")." ]     show (NoMatchingSnapshot names) = concat         [ "There was no snapshot found that matched the package "         , "bounds in your .cabal files.\n"@@ -874,6 +1113,7 @@     base <- parseRelFile $ packageIdentifierString ident ++ ".tar.gz"     return (root </> $(mkRelDir "packages") </> name </> ver </> base) +-- | @".stack-work"@ workDirRel :: Path Rel Dir workDirRel = $(mkRelDir ".stack-work") @@ -895,23 +1135,6 @@     platform <- asks getPlatform     parseRelDir (Distribution.Text.display platform) --- | Relative directory for the platform identifier-platformVariantRelDir-    :: (MonadReader env m, HasPlatform env, HasGHCVariant env, MonadThrow m)-    => m (Path Rel Dir)-platformVariantRelDir = do-    platform <- asks getPlatform-    ghcVariant <- asks getGHCVariant-    parseRelDir (Distribution.Text.display platform <> ghcVariantSuffix ghcVariant)---- | Path to .shake files.-configShakeFilesDir :: (MonadReader env m, HasBuildConfig env) => m (Path Abs Dir)-configShakeFilesDir = liftM (</> $(mkRelDir "shake")) configProjectWorkDir---- | Where to unpack packages for local build-configLocalUnpackDir :: (MonadReader env m, HasBuildConfig env) => m (Path Abs Dir)-configLocalUnpackDir = liftM (</> $(mkRelDir "unpacked")) configProjectWorkDir- -- | Directory containing snapshots snapshotsDir :: (MonadReader env m, HasConfig env, HasGHCVariant env, MonadThrow m) => m (Path Abs Dir) snapshotsDir = do@@ -922,21 +1145,51 @@ -- | Installation root for dependencies installationRootDeps :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) installationRootDeps = do-    snapshots <- snapshotsDir-    bc <- asks getBuildConfig-    name <- parseRelDir $ T.unpack $ resolverName $ bcResolver bc-    ghc <- compilerVersionDir-    return $ snapshots </> name </> ghc+    config <- asks getConfig+    -- TODO: also useShaPathOnWindows here, once #1173 is resolved.+    psc <- platformSnapAndCompilerRel+    return $ configStackRoot config </> $(mkRelDir "snapshots") </> psc  -- | Installation root for locals installationRootLocal :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) installationRootLocal = do     bc <- asks getBuildConfig+    psc <- useShaPathOnWindows =<< platformSnapAndCompilerRel+    return $ configProjectWorkDir bc </> $(mkRelDir "install") </> psc++-- | Path for platform followed by snapshot name followed by compiler+-- name.+platformSnapAndCompilerRel+    :: (MonadReader env m, HasPlatform env, HasEnvConfig env, MonadThrow m)+    => m (Path Rel Dir)+platformSnapAndCompilerRel = do+    bc <- asks getBuildConfig+    platform <- platformVariantRelDir     name <- parseRelDir $ T.unpack $ resolverName $ bcResolver bc     ghc <- compilerVersionDir-    platform <- platformVariantRelDir-    return $ configProjectWorkDir bc </> $(mkRelDir "install") </> platform </> name </> ghc+    useShaPathOnWindows (platform </> name </> ghc) +-- | Relative directory for the platform identifier+platformVariantRelDir+    :: (MonadReader env m, HasPlatform env, HasGHCVariant env, MonadThrow m)+    => m (Path Rel Dir)+platformVariantRelDir = do+    platform <- asks getPlatform+    ghcVariant <- asks getGHCVariant+    parseRelDir (Distribution.Text.display platform <> ghcVariantSuffix ghcVariant)++-- | This is an attempt to shorten stack paths on Windows to decrease our+-- chances of hitting 260 symbol path limit. The idea is to calculate+-- SHA1 hash of the path used on other architectures, encode with base+-- 16 and take first 8 symbols of it.+useShaPathOnWindows :: MonadThrow m => Path Rel Dir -> m (Path Rel Dir)+useShaPathOnWindows =+#ifdef mingw32_HOST_OS+    parseRelDir . S8.unpack . S8.take 8 . B16.encode . SHA1.hash . encodeUtf8 . T.pack . toFilePath+#else+    return+#endif+ compilerVersionDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Rel Dir) compilerVersionDir = do     compilerVersion <- asks (envConfigCompilerVersion . getEnvConfig)@@ -1039,6 +1292,7 @@          flags <- o ..:? "flags" ..!= mempty         resolver <- jsonSubWarnings (o ..: "resolver")+        compiler <- o ..:? "compiler"         config <- parseConfigMonoidJSON o         extraPackageDBs <- o ..:? "extra-package-dbs" ..!= []         let project = Project@@ -1046,6 +1300,7 @@                 , projectExtraDeps = extraDeps                 , projectFlags = flags                 , projectResolver = resolver+                , projectCompiler = compiler                 , projectExtraPackageDBs = extraPackageDBs                 }         return $ ProjectAndConfigMonoid project config@@ -1267,3 +1522,9 @@                 case Map.lookup Nothing m of                     Just b -> b                     Nothing -> False -- default value++-- | Data passed into Docker container for the Docker entrypoint's use+data DockerEntrypoint = DockerEntrypoint+    { deUidGid :: !(Maybe (UserID, GroupID))+      -- ^ UID/GID of host user, if we wish to perform UID/GID switch in container+    } deriving (Read,Show)
src/Stack/Types/Docker.hs view
@@ -10,7 +10,11 @@ import Data.Aeson.Extended import Data.Monoid import Data.Text (Text)+import qualified Data.Text as T+import Distribution.Text (simpleParse)+import Distribution.Version (anyVersion) import Path+import Stack.Types.Version  -- | Docker configuration. data DockerOpts = DockerOpts@@ -46,6 +50,8 @@     -- ^ Location of container-compatible stack executable   ,dockerSetUser :: !(Maybe Bool)    -- ^ Set in-container user to match host's+  ,dockerRequireDockerVersion :: !VersionRange+   -- ^ Require a version of Docker within this range.   }   deriving (Show) @@ -86,6 +92,8 @@     -- ^ Location of container-compatible stack executable   ,dockerMonoidSetUser :: !(Maybe Bool)    -- ^ Set in-container user to match host's+  ,dockerMonoidRequireDockerVersion :: !VersionRange+  -- ^ See: 'dockerRequireDockerVersion'   }   deriving (Show) @@ -110,6 +118,10 @@               dockerMonoidDatabasePath     <- o ..:? dockerDatabasePathArgName               dockerMonoidStackExe         <- o ..:? dockerStackExeArgName               dockerMonoidSetUser          <- o ..:? dockerSetUserArgName+              dockerMonoidRequireDockerVersion+                                           <- unVersionRangeJSON <$>+                                                 o ..:? dockerRequireDockerVersionArgName+                                                   ..!= VersionRangeJSON anyVersion               return DockerOptsMonoid{..})  -- | Left-biased combine Docker options@@ -131,6 +143,7 @@     ,dockerMonoidDatabasePath     = Nothing     ,dockerMonoidStackExe         = Nothing     ,dockerMonoidSetUser          = Nothing+    ,dockerMonoidRequireDockerVersion = anyVersion     }   mappend l r = DockerOptsMonoid     {dockerMonoidDefaultEnable    = dockerMonoidDefaultEnable l || dockerMonoidDefaultEnable r@@ -149,6 +162,9 @@     ,dockerMonoidDatabasePath     = dockerMonoidDatabasePath l <|> dockerMonoidDatabasePath r     ,dockerMonoidStackExe         = dockerMonoidStackExe l <|> dockerMonoidStackExe r     ,dockerMonoidSetUser          = dockerMonoidSetUser l <|> dockerMonoidSetUser r+    ,dockerMonoidRequireDockerVersion+                                  = intersectVersionRanges (dockerMonoidRequireDockerVersion l)+                                                           (dockerMonoidRequireDockerVersion r)     }  -- | Where to get the `stack` executable to run in Docker containers@@ -194,6 +210,16 @@   | DockerMonoidImage String   deriving (Show) +-- | Newtype for non-orphan FromJSON instance.+newtype VersionRangeJSON = VersionRangeJSON { unVersionRangeJSON :: VersionRange }++-- | Parse VersionRange.+instance FromJSON VersionRangeJSON where+  parseJSON = withText "VersionRange"+                (\s -> maybe (fail ("Invalid cabal-style VersionRange: " ++ T.unpack s))+                             (return . VersionRangeJSON)+                             (Distribution.Text.simpleParse (T.unpack s)))+ -- | Docker enable argument name. dockerEnableArgName :: Text dockerEnableArgName = "enable"@@ -269,3 +295,11 @@ -- | Docker @set-user@ argument name dockerSetUserArgName :: Text dockerSetUserArgName = "set-user"++-- | Docker @require-version@ argument name+dockerRequireDockerVersionArgName :: Text+dockerRequireDockerVersionArgName = "require-docker-version"++-- | Argument name used to pass docker entrypoint data (only used internally)+dockerEntrypointArgName :: String+dockerEntrypointArgName = "internal-docker-entrypoint"
src/Stack/Types/FlagName.hs view
@@ -57,7 +57,7 @@   deriving (Typeable,Data,Generic,Hashable,Binary,NFData) instance HasStructuralInfo FlagName instance Eq FlagName where-    x == y = (compare x y) == EQ+    x == y = compare x y == EQ instance Ord FlagName where     compare (FlagName x) (FlagName y) =         compare (S.map Word8.toLower x) (S.map Word8.toLower y)
src/Stack/Types/GhcPkgId.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} 
src/Stack/Types/Package.hs view
@@ -23,8 +23,10 @@ import           Data.Maybe import           Data.Monoid import           Data.Set (Set)+import qualified Data.Set as Set import           Data.Text (Text)-import           Data.Text.Encoding (encodeUtf8)+import qualified Data.Text as T+import           Data.Text.Encoding (encodeUtf8, decodeUtf8) import           Distribution.InstalledPackageInfo (PError) import           Distribution.ModuleName (ModuleName) import           Distribution.Package hiding (Package,PackageName,packageName,packageVersion,PackageIdentifier)@@ -105,11 +107,20 @@                      -> Path Abs File                      -> m (Map NamedComponent (Set ModuleName)                           ,Map NamedComponent (Set DotCabalPath)-                          ,Map NamedComponent [String],[String])+                          ,Map NamedComponent BuildInfoOpts)     } instance Show GetPackageOpts where     show _ = "<GetPackageOpts>" +data BuildInfoOpts = BuildInfoOpts+    { bioGhcOpts :: [String]+    -- ^ Options from the ghc-options cabal field+    , bioGeneratedOpts :: [String]+    -- ^ Other options from cabal information.  These options can safely have+    -- 'nubOrd' applied to them, as there are no multi-word options (see+    -- https://github.com/commercialhaskell/stack/issues/1255)+    } deriving Show+ -- | Files to get for a cabal package. data CabalFileType     = AllFiles@@ -137,18 +148,18 @@         concat             [ "module not listed in "             , toFilePath (filename cabalfp)-            , (case component of+            , case component of                    Nothing -> " for library"-                   Just c -> " for '" ++ c ++ "'")+                   Just c -> " for '" ++ c ++ "'"             , " component (add to other-modules): "             , display unlistedModule]     show (UnlistedModulesWarning cabalfp component unlistedModules) =         concat             [ "modules not listed in "             , toFilePath (filename cabalfp)-            , (case component of+            , case component of                    Nothing -> " for library"-                   Just c -> " for '" ++ c ++ "'")+                   Just c -> " for '" ++ c ++ "'"             , " component (add to other-modules):\n    "             , intercalate "\n    " (map display unlistedModules)] @@ -193,45 +204,41 @@     piiVersion :: a -> Version     piiLocation :: a -> InstallLocation --- | Second-stage build information: tests and benchmarks-data LocalPackageTB = LocalPackageTB-    { lptbPackage :: !Package-    -- ^ Package resolved with dependencies for tests and benchmarks, depending-    -- on which components are active-    , lptbTests   :: !(Set Text)-    -- ^ Test components-    , lptbBenches :: !(Set Text)-    -- ^ Benchmark components-    }-    deriving Show- -- | Information on a locally available package of source code data LocalPackage = LocalPackage-    { lpPackage        :: !Package         -- ^ The @Package@ info itself, after resolution with package flags, not including any tests or benchmarks-    , lpTestDeps       :: !(Map PackageName VersionRange)-    -- ^ Used for determining if we can use --enable-tests in a normal build-    , lpBenchDeps      :: !(Map PackageName VersionRange)-    -- ^ Used for determining if we can use --enable-benchmarks in a normal build-    , lpExeComponents  :: !(Maybe (Set Text)) -- ^ Executable components to build, Nothing if not a target--    , lpTestBench      :: !(Maybe LocalPackageTB)--    , lpDir            :: !(Path Abs Dir)  -- ^ Directory of the package.-    , lpCabalFile      :: !(Path Abs File) -- ^ The .cabal file-    , lpDirtyFiles     :: !(Maybe (Set FilePath))+    { lpPackage       :: !Package+    -- ^ The @Package@ info itself, after resolution with package flags,+    -- with tests and benchmarks disabled+    , lpComponents    :: !(Set NamedComponent)+    -- ^ Components to build, not including the library component.+    , lpUnbuildable   :: !(Set NamedComponent)+    -- ^ Components explicitly requested for build, that are marked+    -- "buildable: false".+    , lpWanted        :: !Bool+    -- ^ Whether this package is wanted as a target.+    , lpTestDeps      :: !(Map PackageName VersionRange)+    -- ^ Used for determining if we can use --enable-tests in a normal build.+    , lpBenchDeps     :: !(Map PackageName VersionRange)+    -- ^ Used for determining if we can use --enable-benchmarks in a normal+    -- build.+    , lpTestBench     :: !(Maybe Package)+    -- ^ This stores the 'Package' with tests and benchmarks enabled, if+    -- either is asked for by the user.+    , lpDir           :: !(Path Abs Dir)+    -- ^ Directory of the package.+    , lpCabalFile     :: !(Path Abs File)+    -- ^ The .cabal file+    , lpDirtyFiles    :: !(Maybe (Set FilePath))     -- ^ Nothing == not dirty, Just == dirty. Note that the Set may be empty if     -- we forced the build to treat packages as dirty. Also, the Set may not     -- include all modified files.-    , lpNewBuildCache  :: !(Map FilePath FileCacheInfo) -- ^ current state of the files-    , lpFiles          :: !(Set (Path Abs File)) -- ^ all files used by this package-    , lpComponents     :: !(Set NamedComponent)+    , lpNewBuildCache :: !(Map FilePath FileCacheInfo)+    -- ^ current state of the files+    , lpFiles         :: !(Set (Path Abs File))+    -- ^ all files used by this package     }     deriving Show --- | Is the given local a target-lpWanted :: LocalPackage -> Bool-lpWanted lp = isJust (lpExeComponents lp) || isJust (lpTestBench lp)- -- | A single, fully resolved component of a package data NamedComponent     = CLib@@ -246,6 +253,46 @@ renderComponent (CTest x) = "test:" <> encodeUtf8 x renderComponent (CBench x) = "bench:" <> encodeUtf8 x +renderPkgComponents :: [(PackageName, NamedComponent)] -> Text+renderPkgComponents = T.intercalate " " . map renderPkgComponent++renderPkgComponent :: (PackageName, NamedComponent) -> Text+renderPkgComponent (pkg, comp) = packageNameText pkg <> ":" <> decodeUtf8 (renderComponent comp)++exeComponents :: Set NamedComponent -> Set Text+exeComponents = Set.fromList . mapMaybe mExeName . Set.toList+  where+    mExeName (CExe name) = Just name+    mExeName _ = Nothing++testComponents :: Set NamedComponent -> Set Text+testComponents = Set.fromList . mapMaybe mTestName . Set.toList+  where+    mTestName (CTest name) = Just name+    mTestName _ = Nothing++benchComponents :: Set NamedComponent -> Set Text+benchComponents = Set.fromList . mapMaybe mBenchName . Set.toList+  where+    mBenchName (CBench name) = Just name+    mBenchName _ = Nothing++isCLib :: NamedComponent -> Bool+isCLib CLib{} = True+isCLib _ = False++isCExe :: NamedComponent -> Bool+isCExe CExe{} = True+isCExe _ = False++isCTest :: NamedComponent -> Bool+isCTest CTest{} = True+isCTest _ = False++isCBench :: NamedComponent -> Bool+isCBench CBench{} = True+isCBench _ = False+ -- | A location to install a package into, either snapshot or local data InstallLocation = Snap | Local     deriving (Show, Eq)@@ -332,7 +379,12 @@         DotCabalFilePath fp -> fp         DotCabalCFilePath fp -> fp -type InstalledMap = Map PackageName (Version, InstallLocation, Installed) -- TODO Version is now redundant and can be gleaned from Installed+type InstalledMap = Map PackageName (InstallLocation, Installed)  data Installed = Library PackageIdentifier GhcPkgId | Executable PackageIdentifier     deriving (Show, Eq, Ord)++-- | Get the installed Version.+installedVersion :: Installed -> Version+installedVersion (Library (PackageIdentifier _ v) _) = v+installedVersion (Executable (PackageIdentifier _ v)) = v
src/Stack/Types/PackageIdentifier.hs view
@@ -1,22 +1,19 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveGeneric #-} {-# OPTIONS -fno-warn-unused-do-bind #-}  -- | Package identifier (name-version).  module Stack.Types.PackageIdentifier-  (PackageIdentifier(..)-  ,toTuple-  ,fromTuple-  ,parsePackageIdentifier-  ,parsePackageIdentifierFromString-  ,packageIdentifierVersion-  ,packageIdentifierName-  ,packageIdentifierParser-  ,packageIdentifierString-  ,packageIdentifierText)+  ( PackageIdentifier(..)+  , toTuple+  , fromTuple+  , parsePackageIdentifier+  , parsePackageIdentifierFromString+  , packageIdentifierParser+  , packageIdentifierString+  , packageIdentifierText )   where  import           Control.Applicative@@ -47,10 +44,12 @@ instance Exception PackageIdentifierParseFail  -- | A pkg-ver combination.-data PackageIdentifier =-  PackageIdentifier !PackageName-                    !Version-  deriving (Eq,Ord,Generic,Data,Typeable)+data PackageIdentifier = PackageIdentifier+  { -- | Get the name part of the identifier.+    packageIdentifierName    :: !PackageName+    -- | Get the version part of the identifier.+  , packageIdentifierVersion :: !Version+  } deriving (Eq,Ord,Generic,Data,Typeable)  instance NFData PackageIdentifier where   rnf (PackageIdentifier !p !v) =@@ -78,14 +77,6 @@ -- | Convert from a tuple to a package identifier. fromTuple :: (PackageName,Version) -> PackageIdentifier fromTuple (n,v) = PackageIdentifier n v---- | Get the version part of the identifier.-packageIdentifierVersion :: PackageIdentifier -> Version-packageIdentifierVersion (PackageIdentifier _ ver) = ver---- | Get the name part of the identifier.-packageIdentifierName :: PackageIdentifier -> PackageName-packageIdentifierName (PackageIdentifier name _) = name  -- | A parser for a package-version pair. packageIdentifierParser :: Parser PackageIdentifier
+ src/Stack/Types/PackageIndex.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}++module Stack.Types.PackageIndex+    ( PackageDownload (..)+    , PackageCache (..)+    , PackageCacheMap (..)+    ) where++import           Control.Monad (mzero)+import           Data.Aeson.Extended+import qualified Data.Binary as Binary+import           Data.Binary.VersionTagged+import           Data.ByteString (ByteString)+import           Data.Int (Int64)+import           Data.Map (Map)+import qualified Data.Map.Strict as Map+import           Data.Text (Text)+import           Data.Text.Encoding (encodeUtf8)+import           Data.Word (Word64)+import           GHC.Generics (Generic)+import           Stack.Types.PackageIdentifier++data PackageCache = PackageCache+    { pcOffset :: !Int64+    -- ^ offset in bytes into the 00-index.tar file for the .cabal file contents+    , pcSize :: !Int64+    -- ^ size in bytes of the .cabal file+    , pcDownload :: !(Maybe PackageDownload)+    }+    deriving (Generic)++instance Binary PackageCache+instance NFData PackageCache+instance HasStructuralInfo PackageCache++newtype PackageCacheMap = PackageCacheMap (Map PackageIdentifier PackageCache)+    deriving (Generic, Binary, NFData)+instance HasStructuralInfo PackageCacheMap+instance HasSemanticVersion PackageCacheMap++data PackageDownload = PackageDownload+    { pdSHA512 :: !ByteString+    , pdUrl    :: !ByteString+    , pdSize   :: !Word64+    }+    deriving (Show, Generic)+instance Binary.Binary PackageDownload+instance HasStructuralInfo PackageDownload+instance NFData PackageDownload+instance FromJSON PackageDownload where+    parseJSON = withObject "Package" $ \o -> do+        hashes <- o .: "package-hashes"+        sha512 <- maybe mzero return (Map.lookup ("SHA512" :: Text) hashes)+        locs <- o .: "package-locations"+        url <-+            case reverse locs of+                [] -> mzero+                x:_ -> return x+        size <- o .: "package-size"+        return PackageDownload+            { pdSHA512 = encodeUtf8 sha512+            , pdUrl = encodeUtf8 url+            , pdSize = size+            }
src/Stack/Types/PackageName.hs view
@@ -37,6 +37,7 @@ import           Data.Char (isLetter) import           Data.Data import           Data.Hashable+import           Data.List (intercalate) import           Data.Map (Map) import qualified Data.Map as Map import           Data.Text (Text)@@ -88,14 +89,13 @@ -- | Attoparsec parser for a package name from bytestring. packageNameParser :: Parser PackageName packageNameParser =-  fmap (PackageName . S8.pack)-       (appending (many1 (satisfy isAlphaNum))-                  (concating (many (alternating-                                      (pured (satisfy isAlphaNum))-                                      (appending (pured (satisfy (== '-')))-                                                 (pured (satisfy isLetter)))))))+  fmap (PackageName . S8.pack . intercalate "-")+       (sepBy1 word (char '-'))   where-    isAlphaNum c = isLetter c || isDigit c+    word = concat <$> sequence [many digit,+                                pured letter,+                                many (alternating letter digit)]+    letter = satisfy isLetter  -- | Make a package name. mkPackageName :: String -> Q Exp
src/Stack/Types/StackT.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -fno-warn-orphans #-}@@ -20,6 +19,8 @@   ,runStackTGlobal   ,runStackLoggingT   ,runStackLoggingTGlobal+  ,runInnerStackT+  ,runInnerStackLoggingT   ,newTLSManager   ,logSticky   ,logStickyDone)@@ -166,6 +167,24 @@ instance HasSupportsUnicode LoggingEnv where     getSupportsUnicode = lenvSupportsUnicode +runInnerStackT :: (HasHttpManager r, HasLogLevel r, HasTerminal r, HasReExec r, MonadReader r m, MonadIO m)+               => config -> StackT config IO a -> m a+runInnerStackT config inner = do+    manager <- asks getHttpManager+    logLevel <- asks getLogLevel+    terminal <- asks getTerminal+    reExec <- asks getReExec+    liftIO $ runStackT manager logLevel config terminal reExec inner++runInnerStackLoggingT :: (HasHttpManager r, HasLogLevel r, HasTerminal r, HasReExec r, MonadReader r m, MonadIO m)+                      => StackLoggingT IO a -> m a+runInnerStackLoggingT inner = do+    manager <- asks getHttpManager+    logLevel <- asks getLogLevel+    terminal <- asks getTerminal+    reExec <- asks getReExec+    liftIO $ runStackLoggingT manager logLevel terminal reExec inner+ -- | Run the logging monad, using global options. runStackLoggingTGlobal :: MonadIO m                        => Manager -> GlobalOpts -> StackLoggingT m a -> m a@@ -275,16 +294,18 @@                       T.hPutStrLn outputChannel out))   where outputChannel = stderr         getOutput maxLogLevel =-          do date <- getDate+          do timestamp <- getTimestamp              l <- getLevel              lc <- getLoc-             return (T.pack date <> T.pack l <> T.decodeUtf8 (fromLogStr (toLogStr msg)) <> T.pack lc)-          where getDate+             return (T.pack timestamp <> T.pack l <> T.decodeUtf8 (fromLogStr (toLogStr msg)) <> T.pack lc)+          where getTimestamp                   | maxLogLevel <= LevelDebug =-                    do now <- getCurrentTime-                       return (formatTime defaultTimeLocale "%Y-%m-%d %T%Q" now ++-                               ": ")+                    do now <- getZonedTime+                       return (formatTime' now ++ ": ")                   | otherwise = return ""+                  where+                    formatTime' =+                        take timestampLength . formatTime defaultTimeLocale "%F %T.%q"                 getLevel                   | maxLogLevel <= LevelDebug =                     return ("[" ++@@ -308,10 +329,16 @@                   where line = show . fst . loc_start                         char = show . snd . loc_start +-- | The length of a timestamp in the format "YYYY-MM-DD hh:mm:ss.μμμμμμ".+-- This definition is top-level in order to avoid multiple reevaluation at runtime.+timestampLength :: Int+timestampLength =+  length (formatTime defaultTimeLocale "%F %T.000000" (UTCTime (ModifiedJulianDay 0) 0))+ -- | With a sticky state, do the thing. withSticky :: (MonadIO m)            => Bool -> (Sticky -> m b) -> m b-withSticky terminal m = do+withSticky terminal m =     if terminal        then do state <- liftIO (newMVar Nothing)                originalMode <- liftIO (hGetBuffering stdout)
src/Stack/Types/TemplateName.hs view
@@ -15,12 +15,12 @@ import           Path import           Path.Internal --- | A template name of the format @foo.hsfiles@.-data TemplateName = TemplateName !Text !(Path Rel File)+-- | A template name.+data TemplateName = TemplateName !Text !(Either (Path Abs File) (Path Rel File))   deriving (Ord,Eq,Show)  -- | An argument which accepts a template name of the format--- @foo.hsfiles@ or @foo@, ultimately normalized to @foo.hsfiles@.+-- @foo.hsfiles@ or @foo@, ultimately normalized to @foo@. templateNameArgument :: O.Mod O.ArgumentFields TemplateName                      -> O.Parser TemplateName templateNameArgument =@@ -51,8 +51,11 @@   where     parseValidFile prefix str =         case parseRelFile str of-            Nothing -> Left expected-            Just fp -> return (TemplateName prefix fp)+            Nothing ->+                case parseAbsFile str of+                    Nothing -> Left expected+                    Just fp -> return (TemplateName prefix (Left fp))+            Just fp -> return (TemplateName prefix (Right fp))     expected = "Expected a template filename like: foo or foo.hsfiles"  -- | Make a template name.@@ -60,13 +63,17 @@ mkTemplateName s =     case parseTemplateNameFromString s of         Left{} -> error ("Invalid template name: " ++ show s)-        Right (TemplateName (T.unpack -> prefix) (Path pn)) ->-            [|TemplateName (T.pack prefix) (Path pn)|]+        Right (TemplateName (T.unpack -> prefix) p) ->+            [|TemplateName (T.pack prefix) $(pn)|]+            where pn =+                      case p of+                          Left (Path fp) -> [|Left (Path fp)|]+                          Right (Path fp) -> [|Right (Path fp)|]  -- | Get a text representation of the template name. templateName :: TemplateName -> Text templateName (TemplateName prefix _) = prefix  -- | Get the path of the template.-templatePath :: TemplateName -> Path Rel File+templatePath :: TemplateName -> Either (Path Abs File) (Path Rel File) templatePath (TemplateName _ fp) = fp
src/Stack/Upgrade.hs view
@@ -16,7 +16,7 @@ import qualified Data.Set                    as Set import qualified Data.Text as T import           Development.GitRev          (gitHash)-import           Network.HTTP.Client.Conduit (HasHttpManager, getHttpManager)+import           Network.HTTP.Client.Conduit (HasHttpManager) import           Path import           Path.IO import qualified Paths_stack as Paths@@ -42,7 +42,7 @@       Just repo -> do         remote <- liftIO $ readProcess "git" ["ls-remote", repo, "master"] []         let latestCommit = head . words $ remote-        if (latestCommit == $gitHash) then do+        if latestCommit == $gitHash then do           $logInfo "Already up-to-date, no upgrade required"           return Nothing         else do $logInfo "Cloning stack"@@ -80,25 +80,20 @@                     Nothing -> error "Stack.Upgrade.upgrade: invariant violated, unpacked directory not found"                     Just path -> return $ Just path -    manager <- asks getHttpManager-    logLevel <- asks getLogLevel-    terminal <- asks getTerminal-    reExec <- asks getReExec     config <- asks getConfig--    forM_ mdir $ \dir -> liftIO $ do-        bconfig <- runStackLoggingT manager logLevel terminal reExec $ do+    forM_ mdir $ \dir -> do+        bconfig <- runInnerStackLoggingT $ do             lc <- loadConfig                 (configConfigMonoid config <> Data.Monoid.mempty                     { configMonoidInstallGHC = Just True                     })                 (Just $ dir </> $(mkRelFile "stack.yaml"))-            lcLoadBuildConfig lc mresolver-        envConfig1 <- runStackT manager logLevel bconfig terminal reExec $ setupEnv $ Just $+            lcLoadBuildConfig lc mresolver Nothing+        envConfig1 <- runInnerStackT bconfig $ setupEnv $ Just $             "Try rerunning with --install-ghc to install the correct GHC into " <>             T.pack (toFilePath (configLocalPrograms config))-        runStackT manager logLevel envConfig1 terminal reExec $-          build (const $ return ()) Nothing defaultBuildOpts-            { boptsTargets = ["stack"]-            , boptsInstallExes = True-            }+        runInnerStackT envConfig1 $+            build (const $ return ()) Nothing defaultBuildOpts+                { boptsTargets = ["stack"]+                , boptsInstallExes = True+                }
src/Stack/Upload.hs view
@@ -30,7 +30,7 @@ import           Control.Applicative import           Control.Exception                     (bracket) import qualified Control.Exception                     as E-import           Control.Monad                         (when)+import           Control.Monad                         (when, unless) import           Data.Aeson                            (FromJSON (..),                                                         ToJSON (..),                                                         eitherDecode', encode,@@ -211,7 +211,7 @@                         putStrLn "authentication failure"                         cfp <- credsFile config                         handleIO (const $ return ()) (removeFile cfp)-                        error $ "Authentication failure uploading to server"+                        error "Authentication failure uploading to server"                     403 -> do                         putStrLn "forbidden upload"                         putStrLn "Usually means: you've already uploaded this package/version combination"@@ -234,7 +234,7 @@   where     loop = do         bs <- brRead $ responseBody res-        when (not $ S.null bs) $ do+        unless (S.null bs) $ do             S.hPut stdout bs             loop 
src/System/Process/Log.hs view
@@ -27,7 +27,7 @@  -- | Show a process arg including speechmarks when necessary. Just for -- debugging purposes, not functionally important.-showProcessArgDebug :: [Char] -> Text+showProcessArgDebug :: String -> Text showProcessArgDebug x     | any special x = T.pack (show x)     | otherwise = T.pack x
src/System/Process/PagerEditor.hs view
@@ -97,11 +97,11 @@  -- | Run editor on a ByteString. editByteString :: String -> ByteString -> IO ByteString-editByteString f s = editReaderWriter f (flip hPut s) Data.ByteString.Lazy.readFile+editByteString f s = editReaderWriter f (`hPut` s) Data.ByteString.Lazy.readFile  -- | Run editor on a String. editString :: String -> String -> IO String-editString f s = editReaderWriter f (flip hPutStr s) System.IO.readFile+editString f s = editReaderWriter f (`hPutStr` s) System.IO.readFile  -- | Short-circuit first Just. orElse :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
src/System/Process/Read.hs view
@@ -53,7 +53,7 @@ import           Data.IORef import           Data.Map (Map) import qualified Data.Map as Map-import           Data.Maybe (fromMaybe, isJust)+import           Data.Maybe (isJust) import           Data.Monoid import           Data.Text (Text) import qualified Data.Text as T@@ -71,25 +71,25 @@ import qualified System.FilePath as FP import           System.Process.Log --- | Override the environment received by a child process+-- | Override the environment received by a child process. data EnvOverride = EnvOverride-    { eoTextMap :: Map Text Text-    , eoStringList :: [(String, String)]-    , eoPath :: [FilePath]+    { eoTextMap :: Map Text Text -- ^ Environment variables as map+    , eoStringList :: [(String, String)] -- ^ Environment variables as association list+    , eoPath :: [FilePath] -- ^ List of directories searched for executables (@PATH@)     , eoExeCache :: IORef (Map FilePath (Either ReadProcessException (Path Abs File)))-    , eoExeExtension :: String+    , eoExeExtension :: String -- ^ @""@ or @".exe"@, depending on the platform     , eoPlatform :: Platform     } --- | Get the environment variables from @EnvOverride@+-- | Get the environment variables from an 'EnvOverride'. unEnvOverride :: EnvOverride -> Map Text Text unEnvOverride = eoTextMap --- | Get the list of directories searched+-- | Get the list of directories searched (@PATH@). envSearchPath :: EnvOverride -> [FilePath] envSearchPath = eoPath --- | Modify an EnvOverride+-- | Modify the environment variables of an 'EnvOverride'. modifyEnvOverride :: MonadIO m                   => EnvOverride                   -> (Map Text Text -> Map Text Text)@@ -98,7 +98,7 @@     (eoPlatform eo)     (f $ eoTextMap eo) --- | Create a new @EnvOverride@+-- | Create a new 'EnvOverride'. mkEnvOverride :: MonadIO m               => Platform               -> Map Text Text@@ -126,16 +126,16 @@             Platform _ Windows -> True             _ -> False --- | Helper conversion function+-- | Helper conversion function. envHelper :: EnvOverride -> Maybe [(String, String)] envHelper = Just . eoStringList  -- | Read from the process, ignoring any output. readProcessNull :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-                => Maybe (Path Abs Dir)+                => Maybe (Path Abs Dir) -- ^ Optional working directory                 -> EnvOverride-                -> String-                -> [String]+                -> String -- ^ Command+                -> [String] -- ^ Command line arguments                 -> m () readProcessNull wd menv name args =     sinkProcessStdout wd menv name args CL.sinkNull@@ -143,11 +143,11 @@ -- | Run the given command in the given directory. If it exits with anything -- but success, prints an error and then calls 'exitWith' to exit the program. readInNull :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-           => Path Abs Dir -- ^ directory to run in-           -> FilePath -- ^ command to run+           => Path Abs Dir -- ^ Directory to run in+           -> FilePath -- ^ Command to run            -> EnvOverride-           -> [String] -- ^ command line arguments-           -> Maybe Text+           -> [String] -- ^ Command line arguments+           -> Maybe Text -- ^ Optional additional error message            -> m () readInNull wd cmd menv args errMsg = do     result <- try (readProcessNull (Just wd) menv cmd args)@@ -169,27 +169,28 @@ -- | Try to produce a strict 'S.ByteString' from the stdout of a -- process. tryProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-                 => Maybe (Path Abs Dir)+                 => Maybe (Path Abs Dir) -- ^ Optional directory to run in                  -> EnvOverride-                 -> String-                 -> [String]+                 -> String -- ^ Command+                 -> [String] -- ^ Command line arguments                  -> m (Either ReadProcessException S.ByteString) tryProcessStdout wd menv name args =     try (readProcessStdout wd menv name args) --- | Produce a strict 'S.ByteString' from the stdout of a--- process. Throws a 'ProcessExitedUnsuccessfully' exception if the--- process fails.+-- | Produce a strict 'S.ByteString' from the stdout of a process.+--+-- Throws a 'ProcessExitedUnsuccessfully' exception if the  process fails. readProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-                  => Maybe (Path Abs Dir)+                  => Maybe (Path Abs Dir) -- ^ Optional directory to run in                   -> EnvOverride-                  -> String-                  -> [String]+                  -> String -- ^ Command+                  -> [String] -- ^ Command line arguments                   -> m S.ByteString readProcessStdout wd menv name args =   sinkProcessStdout wd menv name args CL.consume >>=   liftIO . evaluate . S.concat +-- | An exception while trying to read from process. data ReadProcessException     = ReadProcessException CreateProcess ExitCode L.ByteString L.ByteString     | NoPathFound@@ -230,10 +231,10 @@ -- lots of output; for that use 'sinkProcessStdoutLogStderr'. sinkProcessStdout     :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)-    => Maybe (Path Abs Dir)+    => Maybe (Path Abs Dir) -- ^ Optional directory to run in     -> EnvOverride-    -> String-    -> [String]+    -> String -- ^ Command+    -> [String] -- ^ Command line arguments     -> Sink S.ByteString IO a -- ^ Sink for stdout     -> m a sinkProcessStdout wd menv name args sinkStdout = do@@ -261,10 +262,10 @@  -- | Consume the stdout and stderr of a process feeding strict 'S.ByteString's to the consumers. sinkProcessStderrStdout :: (MonadIO m, MonadLogger m)-                        => Maybe (Path Abs Dir)+                        => Maybe (Path Abs Dir) -- ^ Optional directory to run in                         -> EnvOverride-                        -> String-                        -> [String]+                        -> String -- ^ Command+                        -> [String] -- ^ Command line arguments                         -> Sink S.ByteString IO e -- ^ Sink for stderr                         -> Sink S.ByteString IO o -- ^ Sink for stdout                         -> m (e,o)@@ -283,13 +284,17 @@  -- | Perform pre-call-process tasks.  Ensure the working directory exists and find the -- executable path.-preProcess :: (MonadIO m) => Maybe (Path Abs Dir) -> EnvOverride -> String -> m FilePath+preProcess :: (MonadIO m)+           => Maybe (Path Abs Dir) -- ^ Optional directory to create if necessary+           -> EnvOverride+           -> String -- ^ Command name+           -> m FilePath preProcess wd menv name = do   name' <- liftIO $ liftM toFilePath $ join $ findExecutable menv name   maybe (return ()) createTree wd   return name' --- | Check if the given executable exists on the given PATH+-- | Check if the given executable exists on the given PATH. doesExecutableExist :: MonadIO m => EnvOverride -> String -> m Bool doesExecutableExist menv name = liftM isJust $ findExecutable menv name @@ -304,7 +309,9 @@           | FP.isRelative path = fmap (FP.</> path) getCurrentDirectory           | otherwise          = return path --- | Find the complete path for the executable+-- | Find the complete path for the executable.+--+-- Throws a 'ReadProcessException' if unsuccessful. findExecutable :: (MonadIO m, MonadThrow n) => EnvOverride -> String -> m (n (Path Abs File)) findExecutable _ name | any FP.isPathSeparator name = do     exists <- liftIO $ doesFileExist name@@ -340,7 +347,7 @@             return epath     return $ either throwM return epath --- | Load up an EnvOverride from the standard environment+-- | Load up an 'EnvOverride' from the standard environment. getEnvOverride :: MonadIO m => Platform -> m EnvOverride getEnvOverride platform =     liftIO $@@ -348,17 +355,12 @@           mkEnvOverride platform         . Map.fromList . map (T.pack *** T.pack) --- | Augment the PATH environment variable with the given extra paths+-- | Augment the PATH environment variable with the given extra paths. augmentPath :: [FilePath] -> Maybe Text -> Text augmentPath dirs mpath =     T.intercalate (T.singleton FP.searchPathSeparator)-    $ map (stripTrailingSlashT . T.pack) dirs+    $ map (T.pack . FP.dropTrailingPathSeparator) dirs    ++ maybe [] return mpath--stripTrailingSlashT :: Text -> Text-stripTrailingSlashT t = fromMaybe t $ T.stripSuffix-        (T.singleton FP.pathSeparator)-        t  -- | Apply 'augmentPath' on the PATH value in the given Map. augmentPathMap :: [FilePath] -> Map Text Text -> Map Text Text
src/System/Process/Run.hs view
@@ -6,7 +6,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} --- | Reading from external processes.+-- | Run sub-processes.  module System.Process.Run     (runIn@@ -29,8 +29,9 @@ import qualified System.Process import           System.Process.Read --- | Run the given command in the given directory, inheriting stdout--- and stderr. If it exits with anything but success, prints an error+-- | Run the given command in the given directory, inheriting stdout and stderr.+--+-- If it exits with anything but success, prints an error -- and then calls 'exitWith' to exit the program. runIn :: forall (m :: * -> *).          (MonadLogger m,MonadIO m,MonadBaseControl IO m)@@ -38,7 +39,7 @@       -> FilePath -- ^ command to run       -> EnvOverride       -> [String] -- ^ command line arguments-      -> Maybe Text+      -> Maybe Text -- ^ optional additional error message       -> m () runIn wd cmd menv args errMsg = do     result <- try (callProcess (Just wd) menv cmd args)@@ -57,27 +58,31 @@             liftIO (exitWith ec)         Right () -> return () --- | Like as @System.Process.callProcess@, but takes an optional working directory and--- environment override, and throws ProcessExitedUnsuccessfully if the--- process exits unsuccessfully. Inherits stdout and stderr.+-- | Like 'System.Process.callProcess', but takes an optional working directory and+-- environment override, and throws 'ProcessExitedUnsuccessfully' if the+-- process exits unsuccessfully.+--+-- Inherits stdout and stderr. callProcess :: (MonadIO m, MonadLogger m)-            => Maybe (Path Abs Dir)+            => Maybe (Path Abs Dir) -- ^ optional directory to run in             -> EnvOverride-            -> String-            -> [String]+            -> String -- ^ command to run+            -> [String] -- ^ command line arguments             -> m () callProcess =     callProcess' id --- | Like as @System.Process.callProcess@, but takes an optional working directory and--- environment override, and throws ProcessExitedUnsuccessfully if the--- process exits unsuccessfully. Inherits stdout and stderr.+-- | Like 'System.Process.callProcess', but takes an optional working directory and+-- environment override, and throws 'ProcessExitedUnsuccessfully' if the+-- process exits unsuccessfully.+--+-- Inherits stdout and stderr. callProcess' :: (MonadIO m, MonadLogger m)              => (CreateProcess -> CreateProcess)-             -> Maybe (Path Abs Dir)+             -> Maybe (Path Abs Dir) -- ^ optional directory to run in              -> EnvOverride-             -> String-             -> [String]+             -> String -- ^ command to run+             -> [String] -- ^ command line arguments              -> m () callProcess' modCP wd menv cmd0 args = do     cmd <- preProcess wd menv cmd0
src/main/Main.hs view
@@ -25,6 +25,7 @@ import qualified Data.Map as Map import qualified Data.Map.Strict as M import           Data.Maybe+import           Data.Maybe.Extra (mapMaybeA) import           Data.Monoid import qualified Data.Set as Set import           Data.Text (Text)@@ -33,28 +34,35 @@ import           Data.Traversable import           Data.Typeable (Typeable) import           Data.Version (showVersion)+import           Development.GitRev (gitCommitCount) import           Distribution.System (buildArch) import           Distribution.Text (display)-import           Development.GitRev (gitCommitCount) import           GHC.IO.Encoding (mkTextEncoding, textEncodingName) import           Network.HTTP.Client+import           Options.Applicative import           Options.Applicative.Args import           Options.Applicative.Builder.Extra-import           Options.Applicative.Simple+import           Options.Applicative.Complicated+import           Options.Applicative.Simple (simpleVersion) import           Options.Applicative.Types (readerAsk) import           Path+import           Path.Extra (toFilePathNoTrailingSep) import           Path.IO import qualified Paths_stack as Meta import           Prelude hiding (pi, mapM) import           Stack.Build-import           Stack.Types.Build+import           Stack.Clean (CleanOpts, clean) import           Stack.Config+import           Stack.ConfigCmd as ConfigCmd import           Stack.Constants+import           Stack.Coverage import qualified Stack.Docker as Docker import           Stack.Dot import           Stack.Exec import           Stack.Fetch import           Stack.FileWatch+import           Stack.GhcPkg (getGlobalDB, mkGhcPackagePath)+import           Stack.Ghci import           Stack.Ide import qualified Stack.Image as Image import           Stack.Init@@ -62,8 +70,6 @@ import           Stack.Options import           Stack.Package (getCabalFileName) import qualified Stack.PackageIndex-import           Stack.Ghci-import           Stack.GhcPkg (getGlobalDB, mkGhcPackagePath) import           Stack.SDist (getSDistTarball) import           Stack.Setup import           Stack.Solver (solveExtraDeps)@@ -76,7 +82,7 @@ import           System.Environment (getEnvironment, getProgName) import           System.Exit import           System.FileLock (lockFile, tryLockFile, unlockFile, SharedExclusive(Exclusive), FileLock)-import           System.FilePath (dropTrailingPathSeparator, searchPathSeparator)+import           System.FilePath (searchPathSeparator) import           System.IO (hIsTerminalDevice, stderr, stdin, stdout, hSetBuffering, BufferMode(..), hPutStrLn, Handle, hGetEncoding, hSetEncoding) import           System.Process.Read @@ -106,151 +112,182 @@      isTerminal <- hIsTerminalDevice stdout      execExtraHelp args                    dockerHelpOptName-                   (dockerOptsParser True)+                   (dockerOptsParser False)                    ("Only showing --" ++ Docker.dockerCmdName ++ "* options.")-     let versionString' = concat $ concat+     let commitCount = $gitCommitCount+         versionString' = concat $ concat             [ [$(simpleVersion Meta.version)]               -- Leave out number of commits for --depth=1 clone               -- See https://github.com/commercialhaskell/stack/issues/792-            , [" (" ++ $gitCommitCount ++ " commits)" | $gitCommitCount /= ("1"::String) &&-                                                        $gitCommitCount /= ("UNKNOWN" :: String)]+            , [" (" ++ commitCount ++ " commits)" | commitCount /= ("1"::String) &&+                                                    commitCount /= ("UNKNOWN" :: String)]             , [" ", display buildArch]             ] -     let numericVersion :: Parser (a -> a)-         numericVersion =-          infoOption-            (showVersion Meta.version)-            (long "numeric-version" <>-             help "Show only version number")-+     let globalOpts hide =+             extraHelpOption hide progName (Docker.dockerCmdName ++ "*") dockerHelpOptName <*>+             globalOptsParser hide+         addCommand' cmd title footerStr constr =+             addCommand cmd title footerStr constr (globalOpts True)+         addSubCommands' cmd title footerStr =+             addSubCommands cmd title footerStr (globalOpts True)      eGlobalRun <- try $-       simpleOptions-         versionString'+       complicatedOptions+         Meta.version+         (Just versionString')          "stack - The Haskell Tool Stack"          ""-         (numericVersion <*> extraHelpOption progName (Docker.dockerCmdName ++ "*") dockerHelpOptName <*>-          globalOptsParser isTerminal)-         (do addCommand "build"-                        "Build the project(s) in this directory/configuration"+         (globalOpts False)+         (do addCommand' "build"+                        "Build the package(s) in this directory/configuration"+                        cmdFooter                         buildCmd                         (buildOptsParser Build)-             addCommand "install"+             addCommand' "install"                         "Shortcut for 'build --copy-bins'"+                        cmdFooter                         buildCmd                         (buildOptsParser Install)-             addCommand "uninstall"+             addCommand' "uninstall"                         "DEPRECATED: This command performs no actions, and is present for documentation only"+                        cmdFooter                         uninstallCmd                         (many $ strArgument $ metavar "IGNORED")-             addCommand "test"+             addCommand' "test"                         "Shortcut for 'build --test'"+                        cmdFooter                         buildCmd                         (buildOptsParser Test)-             addCommand "bench"+             addCommand' "bench"                         "Shortcut for 'build --bench'"+                        cmdFooter                         buildCmd                         (buildOptsParser Bench)-             addCommand "haddock"+             addCommand' "haddock"                         "Shortcut for 'build --haddock'"+                        cmdFooter                         buildCmd                         (buildOptsParser Haddock)-             addCommand "new"+             addCommand' "new"                         "Create a new project from a template. Run `stack templates' to see available templates."+                        cmdFooter                         newCmd                         newOptsParser-             addCommand "templates"+             addCommand' "templates"                         "List the templates available for `stack new'."+                        cmdFooter                         templatesCmd                         (pure ())-             addCommand "init"+             addCommand' "init"                         "Initialize a stack project based on one or more cabal packages"+                        cmdFooter                         initCmd                         initOptsParser-             addCommand "solver"+             addCommand' "solver"                         "Use a dependency solver to try and determine missing extra-deps"+                        cmdFooter                         solverCmd                         solverOptsParser-             addCommand "setup"+             addCommand' "setup"                         "Get the appropriate GHC for your project"+                        cmdFooter                         setupCmd                         setupParser-             addCommand "path"+             addCommand' "path"                         "Print out handy path information"+                        cmdFooter                         pathCmd-                        (fmap-                             catMaybes-                             (sequenceA-                                  (map-                                      (\(desc,name,_) ->-                                           flag Nothing-                                                (Just name)-                                                (long (T.unpack name) <>-                                                 help desc))-                                      paths)))-             addCommand "unpack"+                        (mapMaybeA+                            (\(desc,name,_) ->+                                 flag Nothing+                                      (Just name)+                                      (long (T.unpack name) <>+                                       help desc))+                            paths)+             addCommand' "unpack"                         "Unpack one or more packages locally"+                        cmdFooter                         unpackCmd                         (some $ strArgument $ metavar "PACKAGE")-             addCommand "update"+             addCommand' "update"                         "Update the package index"+                        cmdFooter                         updateCmd                         (pure ())-             addCommand "upgrade"+             addCommand' "upgrade"                         "Upgrade to the latest stack (experimental)"+                        cmdFooter                         upgradeCmd-                        ((,) <$> (switch+                        ((,) <$> switch                                   ( long "git"-                                 <> help "Clone from Git instead of downloading from Hackage (more dangerous)"-                                  ))-                             <*> (strOption+                                 <> help "Clone from Git instead of downloading from Hackage (more dangerous)" )+                             <*> strOption                                   ( long "git-repo"                                  <> help "Clone from specified git repository"                                  <> value "https://github.com/commercialhaskell/stack"-                                 <> showDefault-                                  )))-             addCommand "upload"+                                 <> showDefault ))+             addCommand' "upload"                         "Upload a package to Hackage"+                        cmdFooter                         uploadCmd                         ((,)-                         <$> (many $ strArgument $ metavar "TARBALL/DIR")+                         <$> many (strArgument $ metavar "TARBALL/DIR")                          <*> optional pvpBoundsOption)-             addCommand "sdist"+             addCommand' "sdist"                         "Create source distribution tarballs"+                        cmdFooter                         sdistCmd                         ((,)-                         <$> (many $ strArgument $ metavar "DIR")+                         <$> many (strArgument $ metavar "DIR")                          <*> optional pvpBoundsOption)-             addCommand "dot"+             addCommand' "dot"                         "Visualize your project's dependency graph using Graphviz dot"+                        cmdFooter                         dotCmd                         dotOptsParser-             addCommand "exec"+             addCommand' "exec"                         "Execute a command"+                        cmdFooter                         execCmd                         (execOptsParser Nothing)-             addCommand "ghc"+             addCommand' "ghc"                         "Run ghc"+                        cmdFooter                         execCmd-                        (execOptsParser $ Just "ghc")-             addCommand "ghci"-                        "Run ghci in the context of project(s) (experimental)"+                        (execOptsParser $ Just ExecGhc)+             addCommand' "ghci"+                        "Run ghci in the context of package(s) (experimental)"+                        cmdFooter                         ghciCmd                         ghciOptsParser-             addCommand "runghc"+             addCommand' "repl"+                        "Run ghci in the context of package(s) (experimental) (alias for 'ghci')"+                        cmdFooter+                        ghciCmd+                        ghciOptsParser+             addCommand' "runghc"                         "Run runghc"+                        cmdFooter                         execCmd-                        (execOptsParser $ Just "runghc")-             addCommand "eval"+                        (execOptsParser $ Just ExecRunGhc)+             addCommand' "runhaskell"+                        "Run runghc (alias for 'runghc')"+                        cmdFooter+                        execCmd+                        (execOptsParser $ Just ExecRunGhc)+             addCommand' "eval"                         "Evaluate some haskell code inline. Shortcut for 'stack exec ghc -- -e CODE'"+                        cmdFooter                         evalCmd-                        (evalOptsParser $ Just "CODE") -- metavar = "CODE"-             addCommand "clean"+                        (evalOptsParser "CODE")+             addCommand' "clean"                         "Clean the local packages"+                        cmdFooter                         cleanCmd-                        (pure ())-             addCommand "list-dependencies"+                        cleanOptsParser+             addCommand' "list-dependencies"                         "List the dependencies"+                        cmdFooter                         listDependenciesCmd                         (textOption (long "separator" <>                                      metavar "SEP" <>@@ -258,16 +295,19 @@                                            "and package version.") <>                                      value " " <>                                      showDefault))-             addCommand "query"+             addCommand' "query"                         "Query general build information (experimental)"+                        cmdFooter                         queryCmd                         (many $ strArgument $ metavar "SELECTOR...")-             addSubCommands+             addSubCommands'                  "ide"                  "IDE-specific commands"-                 (do addCommand+                 cmdFooter+                 (do addCommand'                          "start"                          "Start the ide-backend service"+                         cmdFooter                          ideCmd                          ((,) <$> many (textArgument                                           (metavar "TARGET" <>@@ -277,40 +317,69 @@                                               metavar "OPTION" <>                                               help "Additional options passed to GHCi" <>                                               value []))-                     addCommand+                     addCommand'                          "packages"                          "List all available local loadable packages"+                         cmdFooter                          packagesCmd                          (pure ())-                     addCommand+                     addCommand'                          "load-targets"                          "List all load targets for a package target"+                         cmdFooter                          targetsCmd                          (textArgument                             (metavar "TARGET")))-             addSubCommands+             addSubCommands'                Docker.dockerCmdName                "Subcommands specific to Docker use"-               (do addCommand Docker.dockerPullCmdName+               cmdFooter+               (do addCommand' Docker.dockerPullCmdName                               "Pull latest version of Docker image from registry"+                              cmdFooter                               dockerPullCmd                               (pure ())-                   addCommand "reset"+                   addCommand' "reset"                               "Reset the Docker sandbox"+                              cmdFooter                               dockerResetCmd-                              (flag False True (long "keep-home" <>-                                               help "Do not delete sandbox's home directory"))-                   addCommand Docker.dockerCleanupCmdName+                              (switch (long "keep-home" <>+                                       help "Do not delete sandbox's home directory"))+                   addCommand' Docker.dockerCleanupCmdName                               "Clean up Docker images and containers"+                              cmdFooter                               dockerCleanupCmd                               dockerCleanupOptsParser)-             addSubCommands+             addSubCommands'+                ConfigCmd.cfgCmdName+                "Subcommands specific to modifying stack.yaml files"+                cmdFooter+                (addCommand' ConfigCmd.cfgCmdSetName+                            "Sets a field in the project's stack.yaml to value"+                            cmdFooter+                            cfgSetCmd+                            configCmdSetParser)+             addSubCommands'                Image.imgCmdName                "Subcommands specific to imaging (EXPERIMENTAL)"-               (addCommand Image.imgDockerCmdName+               cmdFooter+               (addCommand' Image.imgDockerCmdName                 "Build a Docker image for the project"+                cmdFooter                 imgDockerCmd-                (pure ())))+                (boolFlags True+                    "build"+                    "building the project before creating the container"+                    idm))+             addSubCommands'+               "hpc"+               "Subcommands specific to Haskell Program Coverage"+               cmdFooter+               (addCommand' "report"+                            "Generate HPC report a combined HPC report"+                            cmdFooter+                            hpcReportCmd+                            hpcReportOptsParser))      case eGlobalRun of        Left (exitCode :: ExitCode) -> do          when isInterpreter $@@ -322,14 +391,15 @@              , " [options] runghc [options]' comment is required."              , "\nSee https://github.com/commercialhaskell/stack/blob/release/doc/GUIDE.md#ghcrunghc" ]          throwIO exitCode-       Right (global,run) -> do+       Right (globalMonoid,run) -> do+         let global = globalOptsFromMonoid isTerminal globalMonoid          when (globalLogLevel global == LevelDebug) $ hPutStrLn stderr versionString'          case globalReExecVersion global of              Just expectVersion                  | expectVersion /= showVersion Meta.version ->                      throwIO $ InvalidReExecVersion expectVersion (showVersion Meta.version)              _ -> return ()-         run global `catch` \e -> do+         run global `catch` \e ->             -- This special handler stops "stack: " from being printed before the             -- exception             case fromException e of@@ -339,6 +409,7 @@                     exitFailure   where     dockerHelpOptName = Docker.dockerCmdName ++ "-help"+    cmdFooter = "Run 'stack --help' for global options that apply to all subcommands."  -- | Print out useful path information in a human-readable format (and -- support others later).@@ -349,20 +420,31 @@         (do env <- ask             let cfg = envConfig env                 bc = envConfigBuildConfig cfg+            -- This is the modified 'bin-path',+            -- including the local GHC or MSYS if not configured to operate on+            -- global GHC.+            -- It was set up in 'withBuildConfigAndLock -> withBuildConfigExt -> setupEnv'.+            -- So it's not the *minimal* override path.             menv <- getMinimalEnvOverride             snap <- packageDatabaseDeps             local <- packageDatabaseLocal+            extra <- packageDatabaseExtra             global <- getGlobalDB menv =<< getWhichCompiler             snaproot <- installationRootDeps             localroot <- installationRootLocal             distDir <- distRelativeDir+            hpcDir <- hpcReportDir             forM_+                -- filter the chosen paths in flags (keys),+                -- or show all of them if no specific paths chosen.                 (filter                      (\(_,key,_) ->                            null keys || elem key keys)                      paths)                 (\(_,key,path) ->                       liftIO $ T.putStrLn+                          -- If a single path type is requested, output it directly.+                          -- Otherwise, name all the paths.                           ((if length keys == 1                                then ""                                else key <> ": ") <>@@ -375,18 +457,22 @@                                     global                                     snaproot                                     localroot-                                    distDir))))+                                    distDir+                                    hpcDir+                                    extra))))  -- | Passed to all the path printers as a source of info. data PathInfo = PathInfo-    {piBuildConfig :: BuildConfig-    ,piEnvOverride :: EnvOverride-    ,piSnapDb :: Path Abs Dir-    ,piLocalDb :: Path Abs Dir-    ,piGlobalDb :: Path Abs Dir-    ,piSnapRoot :: Path Abs Dir-    ,piLocalRoot :: Path Abs Dir-    ,piDistDir :: Path Rel Dir+    { piBuildConfig :: BuildConfig+    , piEnvOverride :: EnvOverride+    , piSnapDb      :: Path Abs Dir+    , piLocalDb     :: Path Abs Dir+    , piGlobalDb    :: Path Abs Dir+    , piSnapRoot    :: Path Abs Dir+    , piLocalRoot   :: Path Abs Dir+    , piDistDir     :: Path Rel Dir+    , piHpcDir      :: Path Abs Dir+    , piExtraDbs    :: [Path Abs Dir]     }  -- | The paths of interest to a user. The first tuple string is used@@ -402,86 +488,70 @@ paths =     [ ( "Global stack root directory"       , "global-stack-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (configStackRoot (bcConfig (piBuildConfig pi)))))+      , T.pack . toFilePathNoTrailingSep . configStackRoot . bcConfig . piBuildConfig )     , ( "Project root (derived from stack.yaml file)"       , "project-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (bcRoot (piBuildConfig pi))))+      , T.pack . toFilePathNoTrailingSep . bcRoot . piBuildConfig )     , ( "Configuration location (where the stack.yaml file is)"       , "config-location"-      , \pi ->-             T.pack (toFilePathNoTrailing (bcStackYaml (piBuildConfig pi))))+      , T.pack . toFilePath . bcStackYaml . piBuildConfig )     , ( "PATH environment variable"       , "bin-path"-      , \pi ->-             T.pack (intercalate [searchPathSeparator] (eoPath (piEnvOverride pi))))+      , T.pack . intercalate [searchPathSeparator] . eoPath . piEnvOverride )     , ( "Installed GHCs (unpacked and archives)"       , "ghc-paths"-      , \pi ->-             T.pack (toFilePathNoTrailing (configLocalPrograms (bcConfig (piBuildConfig pi)))))+      , T.pack . toFilePathNoTrailingSep . configLocalPrograms . bcConfig . piBuildConfig )     , ( "Local bin path where stack installs executables"       , "local-bin-path"-      , \pi ->-             T.pack (toFilePathNoTrailing (configLocalBin (bcConfig (piBuildConfig pi)))))+      , T.pack . toFilePathNoTrailingSep . configLocalBin . bcConfig . piBuildConfig )     , ( "Extra include directories"       , "extra-include-dirs"-      , \pi ->-             T.intercalate-                 ", "-                 (Set.elems (configExtraIncludeDirs (bcConfig (piBuildConfig pi)))))+      , T.intercalate ", " . Set.elems . configExtraIncludeDirs . bcConfig . piBuildConfig )     , ( "Extra library directories"       , "extra-library-dirs"-      , \pi ->-             T.intercalate ", " (Set.elems (configExtraLibDirs (bcConfig (piBuildConfig pi)))))+      , T.intercalate ", " . Set.elems . configExtraLibDirs . bcConfig . piBuildConfig )     , ( "Snapshot package database"       , "snapshot-pkg-db"-      , \pi ->-             T.pack (toFilePathNoTrailing (piSnapDb pi)))+      , T.pack . toFilePathNoTrailingSep . piSnapDb )     , ( "Local project package database"       , "local-pkg-db"-      , \pi ->-             T.pack (toFilePathNoTrailing (piLocalDb pi)))+      , T.pack . toFilePathNoTrailingSep . piLocalDb )     , ( "Global package database"       , "global-pkg-db"-      , \pi ->-             T.pack (toFilePathNoTrailing (piGlobalDb pi)))+      , T.pack . toFilePathNoTrailingSep . piGlobalDb )     , ( "GHC_PACKAGE_PATH environment variable"       , "ghc-package-path"-      , \pi -> mkGhcPackagePath True (piLocalDb pi) (piSnapDb pi) (piGlobalDb pi))+      , \pi -> mkGhcPackagePath True (piLocalDb pi) (piSnapDb pi) (piExtraDbs pi) (piGlobalDb pi))     , ( "Snapshot installation root"       , "snapshot-install-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (piSnapRoot pi)))+      , T.pack . toFilePathNoTrailingSep . piSnapRoot )     , ( "Local project installation root"       , "local-install-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (piLocalRoot pi)))+      , T.pack . toFilePathNoTrailingSep . piLocalRoot )     , ( "Snapshot documentation root"       , "snapshot-doc-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (piSnapRoot pi </> docDirSuffix)))+      , \pi -> T.pack (toFilePathNoTrailingSep (piSnapRoot pi </> docDirSuffix)))     , ( "Local project documentation root"       , "local-doc-root"-      , \pi ->-             T.pack (toFilePathNoTrailing (piLocalRoot pi </> docDirSuffix)))+      , \pi -> T.pack (toFilePathNoTrailingSep (piLocalRoot pi </> docDirSuffix)))     , ( "Dist work directory"       , "dist-dir"-      , \pi ->-             T.pack (toFilePathNoTrailing (piDistDir pi)))]-  where toFilePathNoTrailing = dropTrailingPathSeparator . toFilePath+      , T.pack . toFilePathNoTrailingSep . piDistDir )+    , ( "Where HPC reports and tix files are stored"+      , "local-hpc-root"+      , T.pack . toFilePathNoTrailingSep . piHpcDir ) ]  data SetupCmdOpts = SetupCmdOpts     { scoCompilerVersion :: !(Maybe CompilerVersion)-    , scoForceReinstall :: !Bool-    , scoUpgradeCabal :: !Bool-    , scoStackSetupYaml :: !String-    , scoGHCBindistURL :: !(Maybe String)+    , scoForceReinstall  :: !Bool+    , scoUpgradeCabal    :: !Bool+    , scoStackSetupYaml  :: !String+    , scoGHCBindistURL   :: !(Maybe String)     }  setupParser :: Parser SetupCmdOpts setupParser = SetupCmdOpts-    <$> (optional $ argument readVersion+    <$> optional (argument readVersion             (metavar "GHC_VERSION" <>              help ("Version of GHC to install, e.g. 7.10.2. " ++                    "The default is to install the version implied by the resolver.")))@@ -497,13 +567,11 @@             ( long "stack-setup-yaml"            <> help "Location of the main stack-setup.yaml file"            <> value defaultStackSetupYaml-           <> showDefault-            )-    <*> (optional $ strOption+           <> showDefault )+    <*> optional (strOption             (long "ghc-bindist"            <> metavar "URL"-           <> help "Alternate GHC binary distribution (requires custom --ghc-variant)"-            ))+           <> help "Alternate GHC binary distribution (requires custom --ghc-variant)"))   where     readVersion = do         s <- readerAsk@@ -527,7 +595,7 @@                   case scoCompilerVersion of                       Just v -> return (v, MatchMinor, Nothing)                       Nothing -> do-                          bc <- lcLoadBuildConfig lc globalResolver+                          bc <- lcLoadBuildConfig lc globalResolver globalCompiler                           return ( bcWantedCompiler bc                                  , configCompilerCheck (lcConfig lc)                                  , Just $ bcStackYaml bc@@ -537,8 +605,7 @@                   ensureCompiler SetupOpts                   { soptsInstallIfMissing = True                   , soptsUseSystem =-                    (configSystemGHC $ lcConfig lc)-                    && not scoForceReinstall+                    configSystemGHC (lcConfig lc) && not scoForceReinstall                   , soptsWantedCompiler = wantedCompiler                   , soptsCompilerCheck = compilerCheck                   , soptsStackYaml = mstack@@ -592,7 +659,7 @@             -- Just in case of asynchronous exceptions, we need to be careful             -- when using tryLockFile here:             EL.bracket (liftIO $ tryLockFile (toFilePath pth) Exclusive)-                       (\fstTry -> maybe (return ()) (liftIO . unlockFile) fstTry)+                       (maybe (return ()) (liftIO . unlockFile))                        (\fstTry ->                         case fstTry of                           Just lk -> EL.finally (act $ Just lk) (liftIO $ unlockFile lk)@@ -625,7 +692,7 @@ -- For now the non-locking version just unlocks immediately. -- That is, there's still a serialization point. withBuildConfig :: GlobalOpts-               -> (StackT EnvConfig IO ())+               -> StackT EnvConfig IO ()                -> IO () withBuildConfig go inner =     withBuildConfigAndLock go (\lk -> do munlockFile lk@@ -672,7 +739,7 @@        let inner'' lk = do               bconfig <- runStackLoggingTGlobal manager go $-                  lcLoadBuildConfig lc globalResolver+                  lcLoadBuildConfig lc globalResolver globalCompiler               envConfig <-                  runStackTGlobal                      manager bconfig go@@ -689,30 +756,24 @@                                              do lk' <- readIORef curLk                                                 munlockFile lk') -cleanCmd :: () -> GlobalOpts -> IO ()-cleanCmd () go = withBuildConfigAndLock go (\_ -> clean)+cleanCmd :: CleanOpts -> GlobalOpts -> IO ()+cleanCmd opts go = withBuildConfigAndLock go (const (clean opts))  -- | Helper for build and install commands buildCmd :: BuildOpts -> GlobalOpts -> IO () buildCmd opts go = do   when (any (("-prof" `elem`) . either (const []) id . parseArgs Escaping) (boptsGhcOptions opts)) $ do     hPutStrLn stderr "When building with stack, you should not use the -prof GHC option"-    hPutStrLn stderr "Instead, please use --enable-library-profiling and --enable-executable-profiling"+    hPutStrLn stderr "Instead, please use --library-profiling and --executable-profiling"     hPutStrLn stderr "See: https://github.com/commercialhaskell/stack/issues/1015"     error "-prof GHC option submitted"   case boptsFileWatch opts of-    FileWatchPoll -> fileWatchPoll getProjectRoot inner-    FileWatch -> fileWatch getProjectRoot inner+    FileWatchPoll -> fileWatchPoll inner+    FileWatch -> fileWatch inner     NoFileWatch -> inner $ const $ return ()   where     inner setLocalFiles = withBuildConfigAndLock go $ \lk ->         Stack.Build.build setLocalFiles lk opts-    getProjectRoot = do-        (manager, lc) <- loadConfigWithOpts go-        bconfig <--            runStackLoggingTGlobal manager go $-            lcLoadBuildConfig lc (globalResolver go)-        return (bcRoot bconfig)  uninstallCmd :: [String] -> GlobalOpts -> IO () uninstallCmd _ go = withConfigAndLock go $ do@@ -746,7 +807,7 @@             return $ if r then (x:as, bs) else (as, x:bs)     (files, nonFiles) <- partitionM doesFileExist args     (dirs, invalid) <- partitionM doesDirectoryExist nonFiles-    when (not (null invalid)) $ error $+    unless (null invalid) $ error $         "stack upload expects a list sdist tarballs or cabal directories.  Can't find " ++         show invalid     let getUploader :: (HasStackRoot config, HasPlatform config, HasConfig config) => StackT config IO Upload.Uploader@@ -754,8 +815,7 @@             config <- asks getConfig             manager <- asks envManager             let uploadSettings =-                    Upload.setGetManager (return manager) $-                    Upload.defaultUploadSettings+                    Upload.setGetManager (return manager) Upload.defaultUploadSettings             liftIO $ Upload.mkUploader config uploadSettings     if null dirs         then withConfigAndLock go $ do@@ -779,35 +839,39 @@         forM_ dirs' $ \dir -> do             (tarName, tarBytes) <- getSDistTarball mpvpBounds dir             distDir <- distDirFromDir dir-            tarPath <- fmap (distDir </>) $ parseRelFile tarName+            tarPath <- (distDir </>) <$> parseRelFile tarName             liftIO $ createTree $ parent tarPath             liftIO $ L.writeFile (toFilePath tarPath) tarBytes             $logInfo $ "Wrote sdist tarball to " <> T.pack (toFilePath tarPath)  -- | Execute a command. execCmd :: ExecOpts -> GlobalOpts -> IO ()-execCmd ExecOpts {..} go@GlobalOpts{..} = do-    (cmd, args) <--        case (eoCmd, eoArgs) of-            (Just cmd, args) -> return (cmd, args)-            (Nothing, cmd:args) -> return (cmd, args)-            (Nothing, []) -> error "You must provide a command to exec, e.g. 'stack exec echo Hello World'"+execCmd ExecOpts {..} go@GlobalOpts{..} =     case eoExtra of         ExecOptsPlain -> do+            (cmd, args) <- case (eoCmd, eoArgs) of+                 (ExecCmd cmd, args) -> return (cmd, args)+                 (ExecGhc, args) -> return ("ghc", args)+                 (ExecRunGhc, args) -> return ("runghc", args)             (manager,lc) <- liftIO $ loadConfigWithOpts go             withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk ->              runStackTGlobal manager (lcConfig lc) go $-                Docker.execWithOptionalContainer+                Docker.reexecWithOptionalContainer                     (lcProjectRoot lc)-                    (\_ _ -> return (cmd, args, [], []))                     -- Unlock before transferring control away, whether using docker or not:                     (Just $ munlockFile lk)-                    (runStackTGlobal manager (lcConfig lc) go $ do+                    (runStackTGlobal manager (lcConfig lc) go $                         exec plainEnvSettings cmd args)                     Nothing                     Nothing -- Unlocked already above.         ExecOptsEmbellished {..} ->            withBuildConfigAndLock go $ \lk -> do+               (cmd, args) <- case (eoCmd, eoArgs) of+                   (ExecCmd cmd, args) -> return (cmd, args)+                   (ExecGhc, args) -> execCompiler "" args+                    -- NOTE: this won't currently work for GHCJS, because it doesn't have+                    -- a runghcjs binary. It probably will someday, though.+                   (ExecRunGhc, args) -> execCompiler "run" args                let targets = concatMap words eoPackages                unless (null targets) $                    Stack.Build.build (const $ return ()) lk defaultBuildOpts@@ -815,13 +879,18 @@                        }                munlockFile lk -- Unlock before transferring control away.                exec eoEnvSettings cmd args+  where+    execCompiler cmdPrefix args = do+        wc <- getWhichCompiler+        let cmd = cmdPrefix ++ compilerExeName wc+        return (cmd, args)  -- | Evaluate some haskell code inline. evalCmd :: EvalOpts -> GlobalOpts -> IO () evalCmd EvalOpts {..} go@GlobalOpts {..} = execCmd execOpts go     where       execOpts =-          ExecOpts { eoCmd = Just "ghc"+          ExecOpts { eoCmd = ExecGhc                    , eoArgs = ["-e", evalArg]                    , eoExtra = evalExtra                    }@@ -860,7 +929,8 @@ targetsCmd :: Text -> GlobalOpts -> IO () targetsCmd target go@GlobalOpts{..} =     withBuildConfig go $-    do (_realTargets,_,pkgs) <- ghciSetup Nothing [target]+    do let bopts = defaultBuildOpts { boptsTargets = [target] }+       (_realTargets,_,pkgs) <- ghciSetup bopts False Nothing        pwd <- getWorkingDir        targets <-            fmap@@ -896,13 +966,22 @@         Docker.preventInContainer $             Docker.cleanup cleanupOpts -imgDockerCmd :: () -> GlobalOpts -> IO ()-imgDockerCmd () go@GlobalOpts{..} = do+cfgSetCmd :: ConfigCmd.ConfigCmdSet -> GlobalOpts -> IO ()+cfgSetCmd co go@GlobalOpts{..} =+    withBuildConfigAndLock+        go+        (\_ -> do env <- ask+                  runReaderT+                      (cfgCmdSet co)+                      env)++imgDockerCmd :: Bool -> GlobalOpts -> IO ()+imgDockerCmd rebuild go@GlobalOpts{..} =     withBuildConfigExt         go         Nothing         (\lk ->-              do Stack.Build.build+              do when rebuild $ Stack.Build.build                          (const (return ()))                          lk                          defaultBuildOpts@@ -920,10 +999,15 @@             Just fp -> do                 path <- canonicalizePath fp >>= parseAbsFile                 return $ Just path-    lc <- runStackLoggingTGlobal-              manager-              go-              (loadConfig globalConfigMonoid mstackYaml)+    lc <- runStackLoggingTGlobal manager go $ do+        lc <- loadConfig globalConfigMonoid mstackYaml+        -- If we have been relaunched in a Docker container, perform in-container initialization+        -- (switch UID, etc.).  We do this after first loading the configuration since it must+        -- happen ASAP but needs a configuration.+        case globalDockerEntrypoint of+            Just de -> Docker.entrypoint (lcConfig lc) de+            Nothing -> return ()+        return lc     return (manager,lc)  -- | Project initialization@@ -967,6 +1051,10 @@ -- | Query build information queryCmd :: [String] -> GlobalOpts -> IO () queryCmd selectors go = withBuildConfig go $ queryBuildInfo $ map T.pack selectors++-- | Generate a combined HPC report+hpcReportCmd :: HpcReportOpts -> GlobalOpts -> IO ()+hpcReportCmd hropts go = withBuildConfig go $ generateHpcReportForTargets hropts  data MainException = InvalidReExecVersion String String      deriving (Typeable)
src/test/Stack/BuildPlanSpec.hs view
@@ -54,12 +54,10 @@         -- github still depends on failure.         writeFile "stack.yaml" "resolver: lts-2.9"         LoadConfig{..} <- loadConfig' manager-        bconfig <- loadBuildConfigRest manager (lcLoadBuildConfig Nothing)+        bconfig <- loadBuildConfigRest manager (lcLoadBuildConfig Nothing Nothing)         runStackT manager logLevel bconfig False False $ do-            menv <- getMinimalEnvOverride             mbp <- loadMiniBuildPlan $ LTS 2 9             eres <- try $ resolveBuildPlan-                menv                 mbp                 (const False)                 (Map.fromList
src/test/Stack/ConfigSpec.hs view
@@ -82,7 +82,7 @@       setCurrentDirectory childDir       LoadConfig{..} <- loadConfig' manager       bc@BuildConfig{..} <- loadBuildConfigRest manager-                            (lcLoadBuildConfig Nothing)+                            (lcLoadBuildConfig Nothing Nothing)       bcRoot bc `shouldBe` parentDir      it "respects the STACK_YAML env variable" $ \T{..} -> inTempDir $ do@@ -92,7 +92,7 @@         withEnvVar "STACK_YAML" stackYamlFp $ do           LoadConfig{..} <- loadConfig' manager           BuildConfig{..} <- loadBuildConfigRest manager-                                (lcLoadBuildConfig Nothing)+                                (lcLoadBuildConfig Nothing Nothing)           bcStackYaml `shouldBe` dir </> stackDotYaml           parent bcStackYaml `shouldBe` dir @@ -106,5 +106,5 @@         withEnvVar "STACK_YAML" (toFilePath yamlRel) $ do             LoadConfig{..} <- loadConfig' manager             BuildConfig{..} <- loadBuildConfigRest manager-                                (lcLoadBuildConfig Nothing)+                                (lcLoadBuildConfig Nothing Nothing)             bcStackYaml `shouldBe` yamlAbs
src/test/Stack/PackageDumpSpec.hs view
@@ -80,6 +80,7 @@                 , dpLibraries = ["HShaskell2010-1.1.2.0"]                 , dpHasExposedModules = True                 , dpHaddockInterfaces = ["/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0/haskell2010.haddock"]+                , dpHaddockHtml = Just "/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0"                 , dpProfiling = ()                 , dpHaddock = ()                 , dpIsExposed = False@@ -113,6 +114,7 @@                 , dpPackageIdent = pkgIdent                 , dpLibDirs = ["/opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY"]                 , dpHaddockInterfaces = ["/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1/ghc.haddock"]+                , dpHaddockHtml = Just "/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1"                 , dpDepends = depends                 , dpLibraries = ["HSghc-7.10.1-EMlWrQ42XY0BNVbSrKixqY"]                 , dpHasExposedModules = True@@ -146,6 +148,7 @@                       , "/usr/local/lib/"                       ,  "C:/Program Files/Example/"]                 , dpHaddockInterfaces = ["/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html/hmatrix.haddock"]+                , dpHaddockHtml = Just "/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html"                 , dpDepends = depends                 , dpLibraries = ["HShmatrix-0.16.1.5"]                 , dpHasExposedModules = True
stack.cabal view
@@ -1,311 +1,326 @@-name:                stack-version:             0.1.6.0-synopsis:            The Haskell Tool Stack-description:         Please see the README.md for usage information, and-                     the wiki on Github for more details.  Also, note that-                     the API for the library is not currently stable, and may-                     change significantly, even between minor releases. It is-                     currently only intended for use by the executable.-license:             BSD3-license-file:        LICENSE-author:              Chris Done-maintainer:          chrisdone@fpcomplete.com-category:            Development-build-type:          Simple-cabal-version:       >=1.10-homepage:            https://github.com/commercialhaskell/stack-extra-source-files:  README.md-                     ChangeLog.md-                     CONTRIBUTING.md-                     doc/GUIDE.md-                     doc/build_command.md-                     doc/install_and_upgrade.md-                     doc/MAINTAINER_GUIDE.md-                     doc/dependency_visualization.md-                     doc/nonstandard_project_init.md-                     doc/SIGNING_KEY.md-                     doc/docker_integration.md-                     doc/shell_autocompletion.md-                     doc/architecture.md-                     doc/faq.md-                     doc/yaml_configuration.md--                     -- Glob would be nice, but apparently Cabal doesn't support it:-                     --     cabal: filepath wildcard 'test/package-dump/*.txt' does not match any files.-                     -- Happened during cabal sdist-                     test/package-dump/ghc-7.8.txt-                     test/package-dump/ghc-7.8.4-osx.txt-                     test/package-dump/ghc-7.10.txt+name: stack+version: 0.1.8.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+maintainer: manny@fpcomplete.com+homepage: https://github.com/commercialhaskell/stack+synopsis: The Haskell Tool Stack+description:+    Please see the README.md for usage information, and+    the wiki on Github for more details.  Also, note that+    the API for the library is not currently stable, and may+    change significantly, even between minor releases. It is+    currently only intended for use by the executable.+category: Development+author: Commercial Haskell SIG+extra-source-files:+    CONTRIBUTING.md+    ChangeLog.md+    README.md+    doc/GUIDE.md+    doc/MAINTAINER_GUIDE.md+    doc/SIGNING_KEY.md+    doc/architecture.md+    doc/build_command.md+    doc/dependency_visualization.md+    doc/docker_integration.md+    doc/faq.md+    doc/install_and_upgrade.md+    doc/nonstandard_project_init.md+    doc/shell_autocompletion.md+    doc/stack_and_ghcjs.md+    doc/yaml_configuration.md+    test/package-dump/ghc-7.8.txt+    test/package-dump/ghc-7.8.4-osx.txt+    test/package-dump/ghc-7.10.txt+    stack.yaml -                     stack.yaml+source-repository head+    type: git+    location: https://github.com/commercialhaskell/stack  flag integration-tests-  manual: True-  default: False-  description: Run the integration test suite+    description:+        Run the integration test suite+    default: False+    manual: True  library-  hs-source-dirs:    src/-  ghc-options:       -Wall-  exposed-modules:   Options.Applicative.Builder.Extra-                     Options.Applicative.Args-                     Stack.BuildPlan-                     Stack.Config-                     Stack.Config.Docker-                     Stack.Constants-                     Stack.Docker-                     Stack.Docker.GlobalDB-                     Stack.Dot-                     Stack.Fetch-                     Stack.Exec-                     Stack.FileWatch-                     Stack.GhcPkg-                     Stack.Init-                     Stack.New-                     Stack.Options-                     Stack.Package-                     Stack.PackageDump-                     Stack.PackageIndex-                     Stack.Ghci-                     Stack.Ide-                     Stack.Image-                     Stack.SDist-                     Stack.Setup-                     Stack.Setup.Installed-                     Stack.Solver-                     Stack.Types-                     Stack.Types.Internal-                     Stack.Types.BuildPlan-                     Stack.Types.Compiler-                     Stack.Types.Config-                     Stack.Types.Docker-                     Stack.Types.FlagName-                     Stack.Types.GhcPkgId-                     Stack.Types.Image-                     Stack.Types.PackageIdentifier-                     Stack.Types.PackageName-                     Stack.Types.TemplateName-                     Stack.Types.Version-                     Stack.Types.StackT-                     Stack.Types.Build-                     Stack.Types.Package-                     Stack.Build-                     Stack.Build.Cache-                     Stack.Build.Coverage-                     Stack.Build.ConstructPlan-                     Stack.Build.Execute-                     Stack.Build.Haddock-                     Stack.Build.Installed-                     Stack.Build.Source-                     Stack.Build.Target-                     Stack.Upgrade-                     Stack.Upload-                     System.Process.Read-                     System.Process.Log-                     System.Process.Run-                     Network.HTTP.Download.Verified-                     Data.Attoparsec.Args-                     Path.IO-  other-modules:     Network.HTTP.Download-                     Control.Concurrent.Execute-                     Path.Find-                     System.Process.PagerEditor-                     Paths_stack-                     Data.Aeson.Extended-                     Data.Attoparsec.Combinators-                     Data.Binary.VersionTagged-                     Data.IORef.RunOnce-                     Data.Maybe.Extra-                     Data.Set.Monad-                     Distribution.Version.Extra-  build-depends:     Cabal >= 1.18.1.5-                   , aeson >= 0.8.0.2-                   , ansi-terminal >= 0.6.2.3-                   , async >= 2.0.2-                   , attoparsec >= 0.12.1.5-                   , base >= 4 && <5-                   , base16-bytestring-                   , base64-bytestring-                   , bifunctors >= 4.2.1-                   , binary >= 0.7-                   , binary-tagged >= 0.1.1-                   , blaze-builder-                   , byteable-                   , bytestring-                   , conduit-combinators >= 0.3.1-                   , conduit >= 1.2.4-                   , conduit-extra >= 1.1.7.1-                   , containers >= 0.5.5.1-                   , cryptohash >= 0.11.6-                   , cryptohash-conduit-                   , directory >= 1.2.1.0-                   , enclosed-exceptions-                   , exceptions >= 0.8.0.2-                   , extra-                   , fast-logger >= 2.3.1-                   , filelock >= 0.1.0.1-                   , filepath >= 1.3.0.2-                   , fsnotify >= 0.2.1-                   , gitrev >= 1.1-                   , hashable >= 1.2.3.2-                   , hpc-                   , http-client >= 0.4.17-                   , http-client-tls >= 0.2.2-                   , http-conduit >= 2.1.7-                   , http-types >= 0.8.6-                   , ignore >= 0.1.1-                   , lifted-base-                   , monad-control-                   , monad-logger >= 0.3.13.1-                   , monad-loops >= 0.4.2.1-                   , mtl >= 2.1.3.1-                   , old-locale >= 1.0.0.6-                   , optparse-applicative-                   , optparse-simple >= 0.0.3-                   , path >= 0.5.1-                   , persistent >= 2.1.2-                   , persistent-sqlite >= 2.1.4-                   , persistent-template >= 2.1.1-                   , pretty-                   , process >= 1.2.0.0-                   , resourcet >= 1.1.4.1-                   , retry >= 0.6-                   , safe >= 0.3-                   , split-                   , stm >= 2.4.4-                   , streaming-commons >= 0.1.10.0-                   , tar >= 0.4.1.0-                   , template-haskell-                   , temporary >= 1.2.0.3-                   , text >= 1.2.0.4-                   , time >= 1.4.2-                   , transformers >= 0.3.0.0-                   , transformers-base >= 0.4.4-                   , unix-compat-                   , unordered-containers >= 0.2.5.1-                   , vector >= 0.10.12.3-                   , vector-binary-instances-                   , void >= 0.7-                   , yaml >= 0.8.10.1-                   , zlib >= 0.5.4.2-                   , deepseq >= 1.3-                   , file-embed-                   , word8-                   , hastache-                   , project-template >= 0.2-  if os(windows)-    cpp-options:     -DWINDOWS-    build-depends:   Win32-  else-    build-depends:   unix >= 2.7.0.1-  default-language:  Haskell2010+    +    if os(windows)+        build-depends:+            Win32 >=2.3.1.0 && <2.4+        cpp-options: -DWINDOWS+    else+        build-depends:+            unix >=2.7.0.1 && <2.8+    exposed-modules:+        Options.Applicative.Builder.Extra+        Options.Applicative.Args+        Options.Applicative.Complicated+        Stack.BuildPlan+        Stack.Clean+        Stack.Config+        Stack.Config.Docker+        Stack.ConfigCmd+        Stack.Constants+        Stack.Coverage+        Stack.Docker+        Stack.Docker.GlobalDB+        Stack.Dot+        Stack.Fetch+        Stack.Exec+        Stack.FileWatch+        Stack.GhcPkg+        Stack.Init+        Stack.New+        Stack.Options+        Stack.Package+        Stack.PackageDump+        Stack.PackageIndex+        Stack.Ghci+        Stack.Ide+        Stack.Image+        Stack.SDist+        Stack.Setup+        Stack.Setup.Installed+        Stack.Solver+        Stack.Types+        Stack.Types.Internal+        Stack.Types.BuildPlan+        Stack.Types.Compiler+        Stack.Types.Config+        Stack.Types.Docker+        Stack.Types.FlagName+        Stack.Types.GhcPkgId+        Stack.Types.Image+        Stack.Types.PackageIdentifier+        Stack.Types.PackageIndex+        Stack.Types.PackageName+        Stack.Types.TemplateName+        Stack.Types.Version+        Stack.Types.StackT+        Stack.Types.Build+        Stack.Types.Package+        Stack.Build+        Stack.Build.Cache+        Stack.Build.ConstructPlan+        Stack.Build.Execute+        Stack.Build.Haddock+        Stack.Build.Installed+        Stack.Build.Source+        Stack.Build.Target+        Stack.Upgrade+        Stack.Upload+        System.Process.Read+        System.Process.Log+        System.Process.Run+        Network.HTTP.Download.Verified+        Data.Attoparsec.Args+        Data.Maybe.Extra+        Path.IO+        Path.Extra+    build-depends:+        Cabal >=1.18.1.5 && <1.23,+        aeson >=0.8.0.2 && <0.10,+        ansi-terminal >=0.6.2.3 && <0.7,+        async >=2.0.2 && <2.1,+        attoparsec >=0.12.1.5 && <0.14,+        base ==4.*,+        base16-bytestring >=0.1.1.6 && <0.2,+        base64-bytestring >=1.0.0.1 && <1.1,+        bifunctors >=4.2.1 && <5.1,+        binary ==0.7.*,+        binary-tagged >=0.1.1 && <0.2,+        blaze-builder >=0.4.0.1 && <0.5,+        byteable >=0.1.1 && <0.2,+        bytestring >=0.10.6.0 && <0.11,+        conduit-combinators >=0.3.1 && <1.1,+        conduit >=1.2.4 && <1.3,+        conduit-extra >=1.1.7.1 && <1.2,+        containers >=0.5.5.1 && <0.6,+        cryptohash >=0.11.6 && <0.12,+        cryptohash-conduit >=0.1.1 && <0.2,+        directory >=1.2.1.0 && <1.3,+        edit-distance ==0.2.*,+        either >=4.4.1 && <4.5,+        enclosed-exceptions >=1.0.1.1 && <1.1,+        exceptions >=0.8.0.2 && <0.9,+        extra >=1.4.2 && <1.5,+        fast-logger >=2.3.1 && <2.5,+        filelock >=0.1.0.1 && <0.2,+        filepath >=1.3.0.2 && <1.5,+        fsnotify >=0.2.1 && <0.3,+        gitrev ==1.1.*,+        hashable >=1.2.3.2 && <1.3,+        hpc >=0.6.0.2 && <0.7,+        http-client >=0.4.17 && <0.5,+        http-client-tls >=0.2.2 && <0.3,+        http-conduit >=2.1.7 && <2.2,+        http-types >=0.8.6 && <0.9,+        lifted-base >=0.2.3.6 && <0.3,+        monad-control >=1.0.0.4 && <1.1,+        monad-logger >=0.3.13.1 && <0.4,+        monad-loops >=0.4.2.1 && <0.5,+        mtl >=2.1.3.1 && <2.3,+        old-locale >=1.0.0.6 && <1.1,+        optparse-applicative >=0.11.0.2 && <0.12,+        path >=0.5.1 && <0.6,+        persistent >=2.1.2 && <2.3,+        persistent-sqlite >=2.1.4 && <2.3,+        persistent-template >=2.1.1 && <2.2,+        pretty >=1.1.2.0 && <1.2,+        process >=1.2.0.0 && <1.3,+        resourcet >=1.1.4.1 && <1.2,+        retry >=0.6 && <0.8,+        safe ==0.3.*,+        semigroups >=0.5 && <0.17,+        split >=0.2.2 && <0.3,+        stm >=2.4.4 && <2.5,+        streaming-commons >=0.1.10.0 && <0.2,+        tar >=0.4.1.0 && <0.5,+        template-haskell >=2.10.0.0 && <2.11,+        temporary >=1.2.0.3 && <1.3,+        text >=1.2.0.4 && <1.3,+        time >=1.4.2 && <1.6,+        transformers >=0.3.0.0 && <0.5,+        transformers-base >=0.4.4 && <0.5,+        unix-compat >=0.4.1.4 && <0.5,+        unordered-containers >=0.2.5.1 && <0.3,+        vector >=0.10.12.3 && <0.12,+        vector-binary-instances >=0.2.1.0 && <0.3,+        void ==0.7.*,+        yaml >=0.8.10.1 && <0.9,+        zlib >=0.5.4.2 && <0.6,+        deepseq ==1.4.*,+        file-embed >=0.0.9 && <0.1,+        word8 >=0.1.2 && <0.2,+        hastache >=0.6.1 && <0.7,+        project-template ==0.2.*+    default-language: Haskell2010+    hs-source-dirs: src/+    other-modules:+        Network.HTTP.Download+        Control.Concurrent.Execute+        Path.Find+        System.Process.PagerEditor+        Paths_stack+        Data.Aeson.Extended+        Data.Attoparsec.Combinators+        Data.Binary.VersionTagged+        Data.IORef.RunOnce+        Data.Set.Monad+        Distribution.Version.Extra+    ghc-options: -Wall  executable stack-  hs-source-dirs: src/main-  main-is:        Main.hs-  ghc-options:    -Wall -threaded -rtsopts-  other-modules:  Paths_stack--  build-depends:  base >=4.7 && < 5-                , bytestring >= 0.10.4.0-                , Cabal-                , containers-                , exceptions-                , filepath-                , filelock >= 0.1.0.1-                , gitrev >= 1.1-                , http-conduit >= 2.1.5-                , lifted-base-                , monad-control-                , monad-logger >= 0.3.13.1-                , mtl >= 2.1.3.1-                , old-locale >= 1.0.0.6-                , optparse-applicative >= 0.11.0.2-                , optparse-simple >= 0.0.3-                , path-                , process-                , resourcet >= 1.1.4.1-                , stack-                , text >= 1.2.0.4-                , either-                , directory-                , split-                , unordered-containers-                , hashable-                , conduit-                , transformers-                , http-client-  default-language:    Haskell2010-  if os(windows)-    build-depends:   Win32-    cpp-options:     -DWINDOWS+    +    if os(windows)+        build-depends:+            Win32 >=2.3.1.0 && <2.4+        cpp-options: -DWINDOWS+    main-is: Main.hs+    build-depends:+        base >=4.7 && <5,+        bytestring >=0.10.4.0 && <0.11,+        Cabal >=1.22.4.0 && <1.23,+        containers >=0.5.6.2 && <0.6,+        exceptions >=0.8.0.2 && <0.9,+        filepath >=1.4.0.0 && <1.5,+        filelock >=0.1.0.1 && <0.2,+        gitrev ==1.1.*,+        http-conduit >=2.1.5 && <2.2,+        lifted-base >=0.2.3.6 && <0.3,+        monad-control >=1.0.0.4 && <1.1,+        monad-logger >=0.3.13.1 && <0.4,+        mtl >=2.1.3.1 && <2.3,+        old-locale >=1.0.0.6 && <1.1,+        optparse-applicative >=0.11.0.2 && <0.12,+        optparse-simple >=0.0.3 && <0.1,+        path >=0.5.2 && <0.6,+        process >=1.2.3.0 && <1.3,+        resourcet >=1.1.4.1 && <1.2,+        stack >=0.1.8.0 && <0.2,+        text >=1.2.0.4 && <1.3,+        either >=4.4.1 && <4.5,+        directory >=1.2.2.0 && <1.3,+        split >=0.2.2 && <0.3,+        unordered-containers >=0.2.5.1 && <0.3,+        hashable >=1.2.3.3 && <1.3,+        conduit >=1.2.5 && <1.3,+        transformers >=0.4.2.0 && <0.5,+        http-client >=0.4.24 && <0.5+    default-language: Haskell2010+    hs-source-dirs: src/main+    other-modules:+        Paths_stack+    ghc-options: -Wall -threaded -rtsopts  test-suite stack-test-  type:           exitcode-stdio-1.0-  hs-source-dirs: src/test-  main-is:        Test.hs-  other-modules:  Spec-                , Stack.BuildPlanSpec-                , Stack.Build.ExecuteSpec-                , Stack.Build.TargetSpec-                , Stack.ConfigSpec-                , Stack.DotSpec-                , Stack.PackageDumpSpec-                , Stack.ArgsSpec-                , Network.HTTP.Download.VerifiedSpec-  ghc-options:    -Wall -threaded-  build-depends:  base >=4.7 && <5-                , hspec-                , containers-                , directory-                , exceptions-                , filepath-                , path-                , temporary-                , stack-                , monad-logger-                , http-conduit-                , cryptohash-                , transformers-                , conduit-                , conduit-extra-                , resourcet-                , Cabal-                , text-                , optparse-applicative-                , bytestring-                , QuickCheck-                , retry >= 0.6-  default-language:    Haskell2010-+    type: exitcode-stdio-1.0+    main-is: Test.hs+    build-depends:+        base >=4.7 && <5,+        hspec >=2.1.10 && <2.2,+        containers >=0.5.6.2 && <0.6,+        directory >=1.2.2.0 && <1.3,+        exceptions >=0.8.0.2 && <0.9,+        filepath >=1.4.0.0 && <1.5,+        path >=0.5.2 && <0.6,+        temporary >=1.2.0.3 && <1.3,+        stack >=0.1.8.0 && <0.2,+        monad-logger >=0.3.13.2 && <0.4,+        http-conduit >=2.1.8 && <2.2,+        cryptohash >=0.11.6 && <0.12,+        transformers >=0.4.2.0 && <0.5,+        conduit >=1.2.5 && <1.3,+        conduit-extra >=1.1.9.1 && <1.2,+        resourcet >=1.1.6 && <1.2,+        Cabal >=1.22.4.0 && <1.23,+        text >=1.2.1.3 && <1.3,+        optparse-applicative >=0.11.0.2 && <0.12,+        bytestring >=0.10.6.0 && <0.11,+        QuickCheck >=2.8.1 && <2.9,+        retry >=0.6 && <0.8+    default-language: Haskell2010+    hs-source-dirs: src/test+    other-modules:+        Spec+        Stack.BuildPlanSpec+        Stack.Build.ExecuteSpec+        Stack.Build.TargetSpec+        Stack.ConfigSpec+        Stack.DotSpec+        Stack.PackageDumpSpec+        Stack.ArgsSpec+        Network.HTTP.Download.VerifiedSpec+    ghc-options: -Wall -threaded test-suite stack-integration-test-  type:           exitcode-stdio-1.0-  hs-source-dirs: test/integration-  main-is:        IntegrationSpec.hs-  ghc-options:    -Wall -threaded -rtsopts -with-rtsopts=-N--  if flag(integration-tests)-    buildable: True-  else-    buildable: False--  build-depends:  base >= 4.7 && < 10-                , temporary-                , hspec-                , process-                , filepath-                , directory-                , text-                , unix-compat-                , containers-                , conduit-                , conduit-extra-                , resourcet-                , async-                , transformers-                , bytestring-  default-language:    Haskell2010--source-repository head-  type:     git-  location: https://github.com/commercialhaskell/stack+    +    if !flag(integration-tests)+        buildable: False+    type: exitcode-stdio-1.0+    main-is: IntegrationSpec.hs+    build-depends:+        base >=4.7 && <10,+        temporary >=1.2.0.3 && <1.3,+        hspec >=2.1.10 && <2.2,+        process >=1.2.3.0 && <1.3,+        filepath >=1.4.0.0 && <1.5,+        directory >=1.2.2.0 && <1.3,+        text >=1.2.1.3 && <1.3,+        unix-compat >=0.4.1.4 && <0.5,+        containers >=0.5.6.2 && <0.6,+        conduit >=1.2.5 && <1.3,+        conduit-extra >=1.1.9.1 && <1.2,+        resourcet >=1.1.6 && <1.2,+        async >=2.0.2 && <2.1,+        transformers >=0.4.2.0 && <0.5,+        bytestring >=0.10.6.0 && <0.11+    default-language: Haskell2010+    hs-source-dirs: test/integration test/integration/lib+    other-modules:+        StackTest+    ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
stack.yaml view
@@ -1,10 +1,5 @@ resolver: lts-3.7-extra-deps:-- ignore-0.1.1.0-- binary-tagged-0.1.1.0-flags:-  ignore:-    without-pcre: true+extra-deps: [] image:   container:     base: "fpco/ubuntu-with-libgmp:14.04"
test/integration/IntegrationSpec.hs view
@@ -15,7 +15,7 @@ import           Data.Conduit.Filesystem      (sourceDirectoryDeep) import qualified Data.Conduit.List            as CL import           Data.Conduit.Process-import           Data.List                    (isSuffixOf, stripPrefix)+import           Data.List                    (isSuffixOf, stripPrefix, sort) import qualified Data.Map                     as Map import           Data.Text.Encoding.Error     (lenientDecode) import qualified Data.Text.Lazy               as TL@@ -43,7 +43,7 @@     stack <- findExe "stack"      let testDir = currDir </> "tests"-    tests <- getDirectoryContents testDir >>= filterM (hasTest testDir)+    tests <- getDirectoryContents testDir >>= filterM (hasTest testDir) . sort      envOrig <- getEnvironment 
+ test/integration/lib/StackTest.hs view
@@ -0,0 +1,104 @@+module StackTest where++import Control.Exception+import Data.List (intercalate)+import System.Environment+import System.FilePath+import System.Directory+import System.IO+import System.Process+import System.Exit++run' :: FilePath -> [String] -> IO ExitCode+run' cmd args = do+    logInfo $ "Running: " ++ cmd ++ " " ++ intercalate " " (map showProcessArgDebug args)+    (Nothing, Nothing, Nothing, ph) <- createProcess (proc cmd args)+    waitForProcess ph++run :: FilePath -> [String] -> IO ()+run cmd args = do+    ec <- run' cmd args+    if ec == ExitSuccess+        then return ()+        else error $ "Exited with exit code: " ++ show ec++stack' :: [String] -> IO ExitCode+stack' args = do+    stack <- getEnv "STACK_EXE"+    run' stack args++stack :: [String] -> IO ()+stack args = do+    ec <- stack' args+    if ec == ExitSuccess+        then return ()+        else error $ "Exited with exit code: " ++ show ec++stackErr :: [String] -> IO ()+stackErr args = do+    ec <- stack' args+    if ec == ExitSuccess+        then error "stack was supposed to fail, but didn't"+        else return ()++doesNotExist :: FilePath -> IO ()+doesNotExist fp = do+    logInfo $ "doesNotExist " ++ fp+    exists <- doesFileOrDirExist fp+    case exists of+      (Right msg) -> error msg+      (Left _) -> return ()++doesExist :: FilePath -> IO ()+doesExist fp = do+    logInfo $ "doesExist " ++ fp+    exists <- doesFileOrDirExist fp+    case exists of+      (Right msg) -> return ()+      (Left _) -> error "No file or directory exists"++doesFileOrDirExist :: FilePath -> IO (Either () String)+doesFileOrDirExist fp = do+    isFile <- doesFileExist fp+    if isFile+        then return (Right ("File exists: " ++ fp))+        else do+            isDir <- doesDirectoryExist fp+            if isDir+                then return (Right ("Directory exists: " ++ fp))+                else return (Left ())++copy :: FilePath -> FilePath -> IO ()+copy src dest = do+    logInfo ("Copy " ++ show src ++ " to " ++ show dest)+    System.Directory.copyFile src dest++fileContentsMatch :: FilePath -> FilePath -> IO ()+fileContentsMatch f1 f2 = do+    doesExist f1+    doesExist f2+    f1Contents <- readFile f1+    f2Contents <- readFile f2+    if f1Contents == f2Contents+          then return ()+          else error+                   ("contents do not match for " +++                    show f1 +++                    " " +++                    show f2)++logInfo :: String -> IO ()+logInfo = hPutStrLn stderr++-- TODO: use stack's process running utilties?  (better logging)+-- for now just copy+modifying this one from System.Process.Log++-- | Show a process arg including speechmarks when necessary. Just for+-- debugging purposes, not functionally important.+showProcessArgDebug :: String -> String+showProcessArgDebug x+    | any special x = show x+    | otherwise = x+  where special '"' = True+        special ' ' = True+        special _ = False