diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,24 @@
+## 0.1.2.0
+
+* Add `--prune` flag to `stack dot` [#487](https://github.com/commercialhaskell/stack/issues/487)
+* Add `--[no-]external`,`--[no-]include-base` flags to `stack dot` [#437](https://github.com/commercialhaskell/stack/issues/437)
+* Add `--ignore-subdirs` flag to init command [#435](https://github.com/commercialhaskell/stack/pull/435)
+* Handle attempt to use non-existing resolver [#436](https://github.com/commercialhaskell/stack/pull/436)
+* Add `--force` flag to `init` command
+* exec style commands accept the `--package` option (see [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/))
+* `stack upload` without arguments doesn't do anything [#439](https://github.com/commercialhaskell/stack/issues/439)
+* Print latest version of packages on conflicts [#450](https://github.com/commercialhaskell/stack/issues/450)
+* Flag to avoid rerunning tests that haven't changed [#451](https://github.com/commercialhaskell/stack/issues/451)
+* stack can act as a script interpreter (see [Script interpreter] (https://github.com/commercialhaskell/stack/wiki/Script-interpreter) and [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/))
+* Add the __`--file-watch`__ flag to auto-rebuild on file changes [#113](https://github.com/commercialhaskell/stack/issues/113)
+* Rename `stack docker exec` to `stack exec --plain`
+* Add the `--skip-msys` flag [#377](https://github.com/commercialhaskell/stack/issues/377)
+* `--keep-going`, turned on by default for tests and benchmarks [#478](https://github.com/commercialhaskell/stack/issues/478)
+* `concurrent-tests: BOOL` [#492](https://github.com/commercialhaskell/stack/issues/492)
+* Use hashes to check file dirtiness [#502](https://github.com/commercialhaskell/stack/issues/502)
+* Install correct GHC build on systems with libgmp.so.3 [#465](https://github.com/commercialhaskell/stack/issues/465)
+* `stack upgrade` checks version before upgrading [#447](https://github.com/commercialhaskell/stack/issues/447)
+
 ## 0.1.1.0
 
 * Remove GHC uncompressed tar file after installation [#376](https://github.com/commercialhaskell/stack/issues/376)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 ## The Haskell Tool Stack
 
 [![Build Status](https://travis-ci.org/commercialhaskell/stack.svg?branch=master)](https://travis-ci.org/commercialhaskell/stack)
+![Release](https://img.shields.io/github/release/commercialhaskell/stack.svg)
 
 `stack` is a cross-platform program for developing Haskell
 projects. It is aimed at Haskellers both new and experienced.
diff --git a/src/Control/Concurrent/Execute.hs b/src/Control/Concurrent/Execute.hs
--- a/src/Control/Concurrent/Execute.hs
+++ b/src/Control/Concurrent/Execute.hs
@@ -14,7 +14,7 @@
 import           Control.Concurrent.Async (Concurrently (..), async)
 import           Control.Concurrent.STM
 import           Control.Exception
-import           Control.Monad            (join)
+import           Control.Monad            (join, unless)
 import           Data.Foldable            (sequenceA_)
 import           Data.Set                 (Set)
 import qualified Data.Set                 as Set
@@ -45,6 +45,8 @@
     , esExceptions :: TVar [SomeException]
     , esInAction   :: TVar (Set ActionId)
     , esCompleted  :: TVar Int
+    , esFinalLock  :: Maybe (TMVar ())
+    , esKeepGoing  :: Bool
     }
 
 data ExecuteException
@@ -57,15 +59,21 @@
         "Inconsistent dependencies were discovered while executing your build plan. This should never happen, please report it as a bug to the stack team."
 
 runActions :: Int -- ^ threads
+           -> Bool -- ^ keep going after one task has failed
+           -> Bool -- ^ run final actions concurrently?
            -> [Action]
            -> (TVar Int -> IO ()) -- ^ progress updated
            -> IO [SomeException]
-runActions threads actions0 withProgress = do
+runActions threads keepGoing concurrentFinal actions0 withProgress = do
     es <- ExecuteState
         <$> newTVarIO actions0
         <*> newTVarIO []
         <*> newTVarIO Set.empty
         <*> newTVarIO 0
+        <*> (if concurrentFinal
+                then pure Nothing
+                else Just <$> atomically (newTMVar ()))
+        <*> pure keepGoing
     _ <- async $ withProgress $ esCompleted es
     if threads <= 1
         then runActions' es
@@ -78,7 +86,7 @@
   where
     breakOnErrs inner = do
         errs <- readTVar esExceptions
-        if null errs
+        if null errs || esKeepGoing
             then inner
             else return $ return ()
     withActions inner = do
@@ -92,10 +100,18 @@
                 inAction <- readTVar esInAction
                 if Set.null inAction
                     then do
-                        modifyTVar esExceptions (toException InconsistentDependencies:)
+                        unless esKeepGoing $
+                            modifyTVar esExceptions (toException InconsistentDependencies:)
                         return $ return ()
                     else retry
             (xs, action:ys) -> do
+                unlock <-
+                    case (actionId action, esFinalLock) of
+                        (ActionId _ ATFinal, Just lock) -> do
+                            takeTMVar lock
+                            return $ putTMVar lock ()
+                        _ -> return $ return ()
+
                 let as' = xs ++ ys
                 inAction <- readTVar esInAction
                 let remaining = Set.union
@@ -107,15 +123,13 @@
                     eres <- try $ restore $ actionDo action ActionContext
                         { acRemaining = remaining
                         }
-                    case eres of
-                        Left err -> atomically $ do
-                            modifyTVar esExceptions (err:)
-                            modifyTVar esInAction (Set.delete $ actionId action)
-                            modifyTVar esCompleted (+1)
-                        Right () -> do
-                            atomically $ do
-                                modifyTVar esInAction (Set.delete $ actionId action)
-                                modifyTVar esCompleted (+1)
+                    atomically $ do
+                        unlock
+                        modifyTVar esInAction (Set.delete $ actionId action)
+                        modifyTVar esCompleted (+1)
+                        case eres of
+                            Left err -> modifyTVar esExceptions (err:)
+                            Right () ->
                                 let dropDep a = a { actionDeps = Set.delete (actionId action) $ actionDeps a }
-                                modifyTVar esActions $ map dropDep
-                            restore loop
+                                 in modifyTVar esActions $ map dropDep
+                    restore loop
diff --git a/src/Data/Attoparsec/Args.hs b/src/Data/Attoparsec/Args.hs
--- a/src/Data/Attoparsec/Args.hs
+++ b/src/Data/Attoparsec/Args.hs
@@ -1,12 +1,27 @@
+{-# LANGUAGE OverloadedStrings #-}
 -- | Parsing argument-like things.
 
-module Data.Attoparsec.Args (EscapingMode(..), argsParser) where
+module Data.Attoparsec.Args
+    ( EscapingMode(..)
+    , argsParser
+    , parseArgs
+    , withInterpreterArgs
+    ) where
 
 import           Control.Applicative
 import           Data.Attoparsec.Text ((<?>))
 import qualified Data.Attoparsec.Text as P
 import           Data.Attoparsec.Types (Parser)
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString as S
+import           Data.Conduit
+import qualified Data.Conduit.Binary as CB
+import qualified Data.Conduit.List as CL
 import           Data.Text (Text)
+import           Data.Text.Encoding (decodeUtf8')
+import           System.Directory (doesFileExist)
+import           System.Environment (getArgs, withArgs)
+import           System.IO (IOMode (ReadMode), withBinaryFile)
 
 -- | Mode for parsing escape characters.
 data EscapingMode
@@ -14,6 +29,10 @@
     | NoEscaping
     deriving (Show,Eq,Enum)
 
+-- | Parse arguments using 'argsParser'.
+parseArgs :: EscapingMode -> Text -> Either String [String]
+parseArgs mode t = P.parseOnly (argsParser mode) t
+
 -- | A basic argument parser. It supports space-separated text, and
 -- string quotation with identity escaping: \x -> x.
 argsParser :: EscapingMode -> Parser Text [String]
@@ -28,3 +47,52 @@
     escaped = P.char '\\' *> P.anyChar
     nonquote = P.satisfy (not . (=='"'))
     naked = P.satisfy (not . flip elem ("\" " :: String))
+
+-- | Use 'withArgs' on result of 'getInterpreterArgs'.
+withInterpreterArgs :: String -> ([String] -> Bool -> IO a) -> IO a
+withInterpreterArgs progName inner = do
+    (args, isInterpreter) <- getInterpreterArgs progName
+    withArgs args $ inner args isInterpreter
+
+-- | Check if command-line looks like it's being used as a script interpreter,
+-- and if so look for a @-- progName ...@ comment that contains additional
+-- arguments.
+getInterpreterArgs :: String -> IO ([String], Bool)
+getInterpreterArgs progName = do
+    args0 <- getArgs
+    case args0 of
+        (x:_) -> do
+            isFile <- doesFileExist x
+            if isFile
+                then do
+                    margs <-
+                        withBinaryFile x ReadMode $ \h ->
+                        CB.sourceHandle h
+                            $= CB.lines
+                            $= CL.map killCR
+                            $$ sinkInterpreterArgs progName
+                    return $ case margs of
+                        Nothing -> (args0, True)
+                        Just args -> (args ++ "--" : args0, True)
+                else return (args0, False)
+        _ -> return (args0, False)
+  where
+    killCR bs
+        | S.null bs || S.last bs /= 13 = bs
+        | otherwise = S.init bs
+
+sinkInterpreterArgs :: Monad m => String -> Sink ByteString m (Maybe [String])
+sinkInterpreterArgs progName =
+    await >>= maybe (return Nothing) checkShebang
+  where
+    checkShebang bs
+        | "#!" `S.isPrefixOf` bs = fmap (maybe Nothing parseArgs') await
+        | otherwise = return (parseArgs' bs)
+
+    parseArgs' bs =
+        case decodeUtf8' bs of
+            Left _ -> Nothing
+            Right t ->
+                case P.parseOnly (argsParser Escaping) t of
+                    Right ("--":progName':rest) | progName' == progName -> Just rest
+                    _ -> Nothing
diff --git a/src/Network/HTTP/Download/Verified.hs b/src/Network/HTTP/Download/Verified.hs
--- a/src/Network/HTTP/Download/Verified.hs
+++ b/src/Network/HTTP/Download/Verified.hs
@@ -33,7 +33,7 @@
 import Data.ByteString (ByteString)
 import Data.Conduit
 import Data.Conduit.Binary (sourceHandle, sinkHandle)
-import Data.Foldable (traverse_)
+import Data.Foldable (traverse_,for_)
 import Data.Monoid
 import Data.String
 import Data.Typeable (Typeable)
@@ -217,12 +217,8 @@
           `catch` \(_ :: VerifyFileException) -> return False
           `catch` \(_ :: VerifiedDownloadException) -> return False
 
-    whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
-    whenJust (Just a) f = f a
-    whenJust _ _ = return ()
-
     checkExpectations = bracket (openFile fp ReadMode) hClose $ \h -> do
-        whenJust drLengthCheck $ checkFileSizeExpectations h
+        for_ drLengthCheck $ checkFileSizeExpectations h
         sourceHandle h $$ getZipSink (hashChecksToZipSink drRequest drHashChecks)
 
     -- doesn't move the handle
@@ -244,7 +240,7 @@
 
     go h res = do
         let headers = responseHeaders res
-        whenJust drLengthCheck $ checkContentLengthHeader headers
+        for_ drLengthCheck $ checkContentLengthHeader headers
         let hashChecks = (case List.lookup hContentMD5 headers of
                 Just md5BS ->
                     [ HashCheck
diff --git a/src/Path/Find.hs b/src/Path/Find.hs
--- a/src/Path/Find.hs
+++ b/src/Path/Find.hs
@@ -12,6 +12,7 @@
 import Control.Monad
 import Control.Monad.Catch
 import Control.Monad.IO.Class
+import System.IO.Error (isPermissionError)
 import Data.List
 import Path
 import Path.IO
@@ -60,7 +61,11 @@
           -> (Path Abs Dir -> Bool)  -- ^ Predicate for which directories to traverse.
           -> IO [Path Abs File]      -- ^ List of matching files.
 findFiles dir p traversep =
-  do (dirs,files) <- listDirectory dir
+  do (dirs,files) <- catchJust (\ e -> if isPermissionError e
+                                         then Just ()
+                                         else Nothing)
+                               (listDirectory dir)
+                               (\ _ -> return ([], []))
      subResults <-
        forM dirs
             (\entry ->
diff --git a/src/Stack/Build.hs b/src/Stack/Build.hs
--- a/src/Stack/Build.hs
+++ b/src/Stack/Build.hs
@@ -14,7 +14,8 @@
 
 module Stack.Build
   (build
-  ,clean)
+  ,clean
+  ,withLoadPackage)
   where
 
 import           Control.Monad
@@ -26,7 +27,10 @@
 import           Data.Function
 import           Data.Map.Strict (Map)
 import qualified Data.Map as Map
+import           Data.Set (Set)
+import qualified Data.Set as Set
 import           Network.HTTP.Client.Conduit (HasHttpManager)
+import           Path
 import           Path.IO
 import           Prelude hiding (FilePath, writeFile)
 import           Stack.Build.ConstructPlan
@@ -45,11 +49,22 @@
 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)
 
 -- | Build
-build :: M env m => BuildOpts -> m ()
-build bopts = do
+build :: M env m
+      => (Set (Path Abs File) -> IO ()) -- ^ callback after discovering all local files
+      -> BuildOpts
+      -> m ()
+build setLocalFiles bopts = do
     menv <- getMinimalEnvOverride
 
     (mbp, locals, extraToBuild, sourceMap) <- loadSourceMap bopts
+
+    -- Set local files, necessary for file watching
+    stackYaml <- asks $ bcStackYaml . getBuildConfig
+    liftIO $ setLocalFiles
+           $ Set.insert stackYaml
+           $ Set.unions
+           $ map lpFiles locals
+
     (installedMap, locallyRegistered) <-
         getInstalled menv
                      GetInstalledOpts
@@ -87,7 +102,13 @@
         }
 
 -- | Provide a function for loading package information from the package index
-withLoadPackage :: M env m
+withLoadPackage :: ( MonadIO m
+                   , HasHttpManager env
+                   , MonadReader env m
+                   , MonadBaseControl IO m
+                   , MonadCatch m
+                   , MonadLogger m
+                   , HasEnvConfig env)
                 => EnvOverride
                 -> ((PackageName -> Version -> Map FlagName Bool -> IO Package) -> m a)
                 -> m a
diff --git a/src/Stack/Build/Cache.hs b/src/Stack/Build/Cache.hs
--- a/src/Stack/Build/Cache.hs
+++ b/src/Stack/Build/Cache.hs
@@ -7,7 +7,6 @@
     ( tryGetBuildCache
     , tryGetConfigCache
     , tryGetCabalMod
-    , getPackageFileModTimes
     , getInstalledExes
     , buildCacheTimes
     , tryGetFlagCache
@@ -18,11 +17,13 @@
     , writeBuildCache
     , writeConfigCache
     , writeCabalMod
+    , setTestSuccess
+    , unsetTestSuccess
+    , checkTestSuccess
     ) where
 
 import           Control.Exception.Enclosed (catchIO, handleIO, tryIO)
-import           Control.Monad.Catch        (MonadCatch, MonadThrow, catch,
-                                             throwM)
+import           Control.Monad.Catch        (MonadThrow, catch, throwM)
 import           Control.Monad.IO.Class
 import           Control.Monad.Logger (MonadLogger)
 import           Control.Monad.Reader
@@ -31,19 +32,15 @@
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as L
 import           Data.Map (Map)
-import qualified Data.Map as Map
-import           Data.Maybe (catMaybes, mapMaybe)
-import qualified Data.Set as Set
+import           Data.Maybe (fromMaybe, mapMaybe)
 import           GHC.Generics (Generic)
 import           Path
 import           Path.IO
 import           Stack.Build.Types
 import           Stack.Constants
-import           Stack.Package
 import           Stack.Types
 import           System.Directory           (createDirectoryIfMissing,
                                              getDirectoryContents,
-                                             getModificationTime,
                                              removeFile)
 import           System.IO.Error (isDoesNotExistError)
 
@@ -86,16 +83,16 @@
 -- | Stored on disk to know whether the flags have changed or any
 -- files have changed.
 data BuildCache = BuildCache
-    { buildCacheTimes :: !(Map FilePath ModTime)
+    { buildCacheTimes :: !(Map FilePath FileCacheInfo)
       -- ^ Modification times of files.
     }
-    deriving (Generic,Eq)
+    deriving (Generic)
 instance Binary BuildCache
 
 -- | Try to read the dirtiness cache for the given package directory.
 tryGetBuildCache :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m, MonadLogger m, HasEnvConfig env)
-                 => Path Abs Dir -> m (Maybe BuildCache)
-tryGetBuildCache = tryGetCache buildCacheFile
+                 => Path Abs Dir -> m (Maybe (Map FilePath FileCacheInfo))
+tryGetBuildCache = liftM (fmap buildCacheTimes) . tryGetCache buildCacheFile
 
 -- | Try to read the dirtiness cache for the given package directory.
 tryGetConfigCache :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m, MonadLogger m, HasEnvConfig env)
@@ -126,7 +123,7 @@
 
 -- | Write the dirtiness cache for this package's files.
 writeBuildCache :: (MonadIO m, MonadReader env m, HasConfig env, MonadThrow m, MonadLogger m, HasEnvConfig env)
-                => Path Abs Dir -> Map FilePath ModTime -> m ()
+                => Path Abs Dir -> Map FilePath FileCacheInfo -> m ()
 writeBuildCache dir times =
     writeCache
         dir
@@ -206,25 +203,31 @@
 
         Binary.encodeFile (toFilePath file) cache
 
--- | Get the modified times of all known files in the package,
--- including the package's cabal file itself.
-getPackageFileModTimes :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m)
-                       => Package
-                       -> Path Abs File -- ^ cabal file
-                       -> m (Map FilePath ModTime)
-getPackageFileModTimes pkg cabalfp = do
-    files <- getPackageFiles (packageFiles pkg) AllFiles cabalfp
-    liftM (Map.fromList . catMaybes)
-        $ mapM getModTimeMaybe
-        $ Set.toList files
-  where
-    getModTimeMaybe fp =
-        liftIO
-            (catch
-                 (liftM
-                      (Just . (toFilePath fp,) . modTime)
-                      (getModificationTime (toFilePath fp)))
-                 (\e ->
-                       if isDoesNotExistError e
-                           then return Nothing
-                           else throwM e))
+-- | Mark a test suite as having succeeded
+setTestSuccess :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)
+               => Path Abs Dir
+               -> m ()
+setTestSuccess dir =
+    writeCache
+        dir
+        testSuccessFile
+        True
+
+-- | Mark a test suite as not having succeeded
+unsetTestSuccess :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)
+                 => Path Abs Dir
+                 -> m ()
+unsetTestSuccess dir =
+    writeCache
+        dir
+        testSuccessFile
+        False
+
+-- | Check if the test suite already passed
+checkTestSuccess :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env, HasEnvConfig env)
+                 => Path Abs Dir
+                 -> m Bool
+checkTestSuccess dir =
+    liftM
+        (fromMaybe False)
+        (tryGetCache testSuccessFile dir)
diff --git a/src/Stack/Build/ConstructPlan.hs b/src/Stack/Build/ConstructPlan.hs
--- a/src/Stack/Build/ConstructPlan.hs
+++ b/src/Stack/Build/ConstructPlan.hs
@@ -336,16 +336,16 @@
     deps' <- packageDepsWithTools package
     deps <- forM (Map.toList deps') $ \(depname, range) -> do
         eres <- addDep depname
+        let mlatest = Map.lookup depname $ latestVersions ctx
         case eres of
             Left e ->
                 let bd =
                         case e of
-                            UnknownPackage name ->
-                                NotInBuildPlan $ Map.lookup name $ latestVersions ctx
+                            UnknownPackage name -> assert (name == depname) NotInBuildPlan
                             _ -> Couldn'tResolveItsDependencies
-                 in return $ Left (depname, (range, bd))
+                 in return $ Left (depname, (range, mlatest, bd))
             Right adr | not $ adrVersion adr `withinRange` range ->
-                return $ Left (depname, (range, DependencyMismatch $ adrVersion adr))
+                return $ Left (depname, (range, mlatest, DependencyMismatch $ adrVersion adr))
             Right (ADRToInstall task) -> return $ Right
                 (Set.singleton $ taskProvides task, Set.empty, taskLocation task)
             Right (ADRFound loc _ (Executable _)) -> return $ Right
diff --git a/src/Stack/Build/Execute.hs b/src/Stack/Build/Execute.hs
--- a/src/Stack/Build/Execute.hs
+++ b/src/Stack/Build/Execute.hs
@@ -124,7 +124,7 @@
             case finalAction of
                 DoNothing -> Nothing
                 DoBenchmarks -> Just "benchmark"
-                DoTests -> Just "test"
+                DoTests _ -> Just "test"
     case mfinalLabel of
         Nothing -> return ()
         Just finalLabel -> do
@@ -333,8 +333,20 @@
             (planTasks plan)
             (planFinals plan)
     threads <- asks $ configJobs . getConfig
+    concurrentTests <- asks $ configConcurrentTests . getConfig
+    let keepGoing =
+            case boptsKeepGoing eeBuildOpts of
+                Just kg -> kg
+                Nothing ->
+                    case boptsFinalAction eeBuildOpts of
+                        DoNothing -> False
+                        _ -> True
+        concurrentFinal =
+            case boptsFinalAction eeBuildOpts of
+                DoTests _ -> concurrentTests
+                _ -> True
     terminal <- asks getTerminal
-    errs <- liftIO $ runActions threads actions $ \doneVar -> do
+    errs <- liftIO $ runActions threads keepGoing concurrentFinal actions $ \doneVar -> do
         let total = length actions
             loop prev
                 | prev == total =
@@ -393,7 +405,7 @@
     mfunc =
         case boptsFinalAction $ eeBuildOpts ee of
             DoNothing -> Nothing
-            DoTests -> Just (singleTest, checkTest)
+            DoTests rerunTests -> Just (singleTest rerunTests, checkTest)
             DoBenchmarks -> Just (singleBench, checkBench)
 
     checkTest task =
@@ -618,9 +630,10 @@
   withSingleContext ac ee task $ \package cabalfp pkgDir cabal announce console _mlogFile -> do
     (cache, _neededConfig) <- ensureConfig pkgDir ee task (announce "configure") cabal cabalfp []
 
-    fileModTimes <- getPackageFileModTimes package cabalfp
     markExeNotInstalled (taskLocation task) taskProvides
-    writeBuildCache pkgDir fileModTimes
+    case taskType of
+        TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp
+        TTUpstream _ _ -> return ()
 
     announce "build"
     config <- asks getConfig
@@ -665,16 +678,18 @@
     when (doHaddock && shouldHaddockDeps eeBuildOpts) $
         copyDepHaddocks
             eeEnvOverride
+            eeBaseConfigOpts
             (pkgDbs ++ [eeGlobalDB])
             (PackageIdentifier (packageName package) (packageVersion package))
             Set.empty
 
 singleTest :: M env m
-           => ActionContext
+           => Bool -- ^ rerun tests?
+           -> ActionContext
            -> ExecuteEnv
            -> Task
            -> m ()
-singleTest ac ee task =
+singleTest rerunTests ac ee task =
     withSingleContext ac ee task $ \package cabalfp pkgDir cabal announce console mlogFile -> do
         (_cache, neededConfig) <- ensureConfig pkgDir ee task (announce "configure (test)") cabal cabalfp ["--enable-tests"]
         config <- asks getConfig
@@ -683,7 +698,6 @@
                 (case taskType task of
                     TTLocal lp -> lpDirtyFiles lp
                     _ -> assert False True)
-                || True -- FIXME above logic is incorrect, see: https://github.com/commercialhaskell/stack/issues/319
             needHpc = boptsCoverage (eeBuildOpts ee)
 
             componentsRaw =
@@ -695,103 +709,119 @@
 
         when needBuild $ do
             announce "build (test)"
-            fileModTimes <- getPackageFileModTimes package cabalfp
-            writeBuildCache pkgDir fileModTimes
+            unsetTestSuccess pkgDir
+            case taskType task of
+                TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp
+                TTUpstream _ _ -> assert False $ return ()
             cabal (console && configHideTHLoading config) $ "build" : components
 
-        bconfig <- asks getBuildConfig
-        buildDir <- distDirFromDir pkgDir
-        hpcDir <- hpcDirFromDir pkgDir
-        when needHpc (createTree hpcDir)
-        let dotHpcDir = pkgDir </> dotHpc
-            exeExtension =
-                case configPlatform $ getConfig bconfig of
-                    Platform _ Windows -> ".exe"
-                    _ -> ""
+        toRun <-
+            if rerunTests
+                then return True
+                else do
+                    success <- checkTestSuccess pkgDir
+                    if success
+                        then do
+                            unless (null testsToRun) $ announce "skipping already passed test"
+                            return False
+                        else return True
 
-        errs <- liftM Map.unions $ forM testsToRun $ \testName -> do
-            nameDir <- parseRelDir $ T.unpack testName
-            nameExe <- parseRelFile $ T.unpack testName ++ exeExtension
-            nameTix <- liftM (pkgDir </>) $ parseRelFile $ T.unpack testName ++ ".tix"
-            let exeName = buildDir </> $(mkRelDir "build") </> nameDir </> nameExe
-            exists <- fileExists exeName
-            menv <- liftIO $ configEnvOverride config EnvSettings
-                { esIncludeLocals = taskLocation task == Local
-                , esIncludeGhcPackagePath = True
-                , esStackExe = True
-                }
-            if exists
-                then do
-                    -- We clear out the .tix files before doing a run.
-                    when needHpc $ do
-                        tixexists <- fileExists nameTix
-                        when tixexists $
-                            $logWarn ("Removing HPC file " <> T.pack (toFilePath nameTix))
-                        removeFileIfExists nameTix
+        when toRun $ do
+            bconfig <- asks getBuildConfig
+            buildDir <- distDirFromDir pkgDir
+            hpcDir <- hpcDirFromDir pkgDir
+            when needHpc (createTree hpcDir)
+            let dotHpcDir = pkgDir </> dotHpc
+                exeExtension =
+                    case configPlatform $ getConfig bconfig of
+                        Platform _ Windows -> ".exe"
+                        _ -> ""
 
-                    let args = boptsTestArgs (eeBuildOpts ee)
-                        argsDisplay =
-                            case args of
-                              [] -> ""
-                              _ -> ", args: " <> T.intercalate " " (map showProcessArgDebug args)
-                    announce $ "test (suite: " <> testName <> argsDisplay <> ")"
-                    let cp = (proc (toFilePath exeName) args)
-                            { cwd = Just $ toFilePath pkgDir
-                            , Process.env = envHelper menv
-                            , std_in = CreatePipe
-                            , std_out =
-                                case mlogFile of
-                                    Nothing -> Inherit
-                                    Just (_, h) -> UseHandle h
-                            , std_err =
-                                case mlogFile of
-                                    Nothing -> Inherit
-                                    Just (_, h) -> UseHandle h
-                            }
+            errs <- liftM Map.unions $ forM testsToRun $ \testName -> do
+                nameDir <- parseRelDir $ T.unpack testName
+                nameExe <- parseRelFile $ T.unpack testName ++ exeExtension
+                nameTix <- liftM (pkgDir </>) $ parseRelFile $ T.unpack testName ++ ".tix"
+                let exeName = buildDir </> $(mkRelDir "build") </> nameDir </> nameExe
+                exists <- fileExists exeName
+                menv <- liftIO $ configEnvOverride config EnvSettings
+                    { esIncludeLocals = taskLocation task == Local
+                    , esIncludeGhcPackagePath = True
+                    , esStackExe = True
+                    }
+                if exists
+                    then do
+                        -- We clear out the .tix files before doing a run.
+                        when needHpc $ do
+                            tixexists <- fileExists nameTix
+                            when tixexists $
+                                $logWarn ("Removing HPC file " <> T.pack (toFilePath nameTix))
+                            removeFileIfExists nameTix
 
-                    -- Use createProcess_ to avoid the log file being closed afterwards
-                    (Just inH, Nothing, Nothing, ph) <- liftIO $ createProcess_ "singleBuild.runTests" cp
-                    liftIO $ hClose inH
-                    ec <- liftIO $ waitForProcess ph
-                    -- Move the .tix file out of the package directory
-                    -- into the hpc work dir, for tidiness.
-                    when needHpc $
-                        moveFileIfExists nameTix hpcDir
-                    return $ case ec of
-                        ExitSuccess -> Map.empty
-                        _ -> Map.singleton testName $ Just ec
-                else do
-                    $logError $ T.concat
-                        [ "Test suite "
-                        , testName
-                        , " executable not found for "
-                        , T.pack $ packageNameString $ packageName package
-                        ]
-                    return $ Map.singleton testName Nothing
-        when needHpc $ do
-            createTree (hpcDir </> dotHpc)
-            exists <- dirExists dotHpcDir
-            when exists $ do
-                copyDirectoryRecursive dotHpcDir (hpcDir </> dotHpc)
-                removeTree dotHpcDir
-            (_,files) <- listDirectory hpcDir
-            let tixes =
-                    filter (isSuffixOf ".tix" . toFilePath . filename) files
-            generateHpcReport pkgDir hpcDir (hpcDir </> dotHpc) tixes
+                        let args = boptsTestArgs (eeBuildOpts ee)
+                            argsDisplay =
+                                case args of
+                                  [] -> ""
+                                  _ -> ", args: " <> T.intercalate " " (map showProcessArgDebug args)
+                        announce $ "test (suite: " <> testName <> argsDisplay <> ")"
+                        let cp = (proc (toFilePath exeName) args)
+                                { cwd = Just $ toFilePath pkgDir
+                                , Process.env = envHelper menv
+                                , std_in = CreatePipe
+                                , std_out =
+                                    case mlogFile of
+                                        Nothing -> Inherit
+                                        Just (_, h) -> UseHandle h
+                                , std_err =
+                                    case mlogFile of
+                                        Nothing -> Inherit
+                                        Just (_, h) -> UseHandle h
+                                }
 
-        bs <- liftIO $
-            case mlogFile of
-                Nothing -> return ""
-                Just (logFile, h) -> do
-                    hClose h
-                    S.readFile $ toFilePath logFile
+                        -- Use createProcess_ to avoid the log file being closed afterwards
+                        (Just inH, Nothing, Nothing, ph) <- liftIO $ createProcess_ "singleBuild.runTests" cp
+                        liftIO $ hClose inH
+                        ec <- liftIO $ waitForProcess ph
+                        -- Move the .tix file out of the package directory
+                        -- into the hpc work dir, for tidiness.
+                        when needHpc $
+                            moveFileIfExists nameTix hpcDir
+                        return $ case ec of
+                            ExitSuccess -> Map.empty
+                            _ -> Map.singleton testName $ Just ec
+                    else do
+                        $logError $ T.concat
+                            [ "Test suite "
+                            , testName
+                            , " executable not found for "
+                            , T.pack $ packageNameString $ packageName package
+                            ]
+                        return $ Map.singleton testName Nothing
+            when needHpc $ do
+                createTree (hpcDir </> dotHpc)
+                exists <- dirExists dotHpcDir
+                when exists $ do
+                    copyDirectoryRecursive dotHpcDir (hpcDir </> dotHpc)
+                    removeTree dotHpcDir
+                (_,files) <- listDirectory hpcDir
+                let tixes =
+                        filter (isSuffixOf ".tix" . toFilePath . filename) files
+                generateHpcReport pkgDir hpcDir (hpcDir </> dotHpc) tixes
 
-        unless (Map.null errs) $ throwM $ TestSuiteFailure
-            (taskProvides task)
-            errs
-            (fmap fst mlogFile)
-            bs
+            bs <- liftIO $
+                case mlogFile of
+                    Nothing -> return ""
+                    Just (logFile, h) -> do
+                        hClose h
+                        S.readFile $ toFilePath logFile
 
+            unless (Map.null errs) $ throwM $ TestSuiteFailure
+                (taskProvides task)
+                errs
+                (fmap fst mlogFile)
+                bs
+
+            setTestSuccess pkgDir
+
 -- | Determine the tests to be run based on the list of components.
 compareTestsComponents :: [Text] -- ^ components
                        -> [Text] -- ^ all test names
@@ -838,18 +868,18 @@
             -> Task
             -> m ()
 singleBench ac ee task =
-    withSingleContext ac ee task $ \package cabalfp pkgDir cabal announce console _mlogFile -> do
+    withSingleContext ac ee task $ \_package cabalfp pkgDir cabal announce console _mlogFile -> do
         (_cache, neededConfig) <- ensureConfig pkgDir ee task (announce "configure (benchmarks)") cabal cabalfp ["--enable-benchmarks"]
 
         let needBuild = neededConfig ||
                 (case taskType task of
                     TTLocal lp -> lpDirtyFiles lp
                     _ -> assert False True)
-                || True -- FIXME above logic is incorrect, see: https://github.com/commercialhaskell/stack/issues/319
         when needBuild $ do
             announce "build (benchmarks)"
-            fileModTimes <- getPackageFileModTimes package cabalfp
-            writeBuildCache pkgDir fileModTimes
+            case taskType task of
+                TTLocal lp -> writeBuildCache pkgDir $ lpNewBuildCache lp
+                TTUpstream _ _ -> assert False $ return ()
             config <- asks getConfig
             cabal (console && configHideTHLoading config) ["build"]
 
diff --git a/src/Stack/Build/Haddock.hs b/src/Stack/Build/Haddock.hs
--- a/src/Stack/Build/Haddock.hs
+++ b/src/Stack/Build/Haddock.hs
@@ -55,11 +55,12 @@
 -- 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
+                -> BaseConfigOpts
                 -> [Path Abs Dir]
                 -> PackageIdentifier
                 -> Set (Path Abs Dir)
                 -> m ()
-copyDepHaddocks envOverride pkgDbs pkgId extraDestDirs = do
+copyDepHaddocks envOverride bco pkgDbs pkgId extraDestDirs = do
     mpkgHtmlDir <- findGhcPkgHaddockHtml envOverride pkgDbs pkgId
     case mpkgHtmlDir of
         Nothing -> return ()
@@ -72,9 +73,14 @@
         mDepOrigDir <- findGhcPkgHaddockHtml envOverride pkgDbs depId
         case mDepOrigDir of
             Nothing -> return ()
-            Just depOrigDir ->
-                copyWhenNeeded (Set.insert (parent pkgHtmlDir) extraDestDirs)
-                               depId depOrigDir
+            Just 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
@@ -86,7 +92,7 @@
                     when needCopy $ doCopy depOrigDir depCopyDir
                     return needCopy
         when (or copied) $
-            copyDepHaddocks envOverride pkgDbs depId destDirs
+            copyDepHaddocks envOverride bco pkgDbs depId destDirs
     getNeedCopy depOrigDir depCopyDir = do
         let depOrigIndex = haddockIndexFile depOrigDir
             depCopyIndex = haddockIndexFile depCopyDir
diff --git a/src/Stack/Build/Source.hs b/src/Stack/Build/Source.hs
--- a/src/Stack/Build/Source.hs
+++ b/src/Stack/Build/Source.hs
@@ -14,13 +14,21 @@
     ) where
 
 import Network.HTTP.Client.Conduit (HasHttpManager)
-import           Control.Applicative          ((<|>), (<$>))
+import           Control.Applicative          ((<|>), (<$>), (<*>))
+import           Control.Exception            (catch)
 import           Control.Monad
 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           Crypto.Hash                  (Digest, SHA256)
+import           Crypto.Hash.Conduit          (sinkHash)
+import           Data.Byteable                (toBytes)
+import qualified Data.ByteString              as S
+import           Data.Conduit                 (($$), ZipSink (..))
+import qualified Data.Conduit.Binary          as CB
+import qualified Data.Conduit.List            as CL
 import           Data.Either
 import qualified Data.Foldable                as F
 import           Data.Function
@@ -29,13 +37,13 @@
 import qualified Data.Map                     as Map
 import           Data.Map.Strict              (Map)
 import           Data.Maybe
-import           Data.Monoid                  ((<>))
+import           Data.Monoid                  ((<>), Any (..), mconcat)
 import           Data.Set                     (Set)
 import qualified Data.Set                     as Set
 import           Data.Text                    (Text)
 import qualified Data.Text                    as T
 import           Path
-import           Prelude                      hiding (FilePath, writeFile)
+import           Prelude                      hiding (writeFile)
 import           Stack.Build.Cache
 import           Stack.Build.Types
 import           Stack.BuildPlan              (loadMiniBuildPlan,
@@ -45,6 +53,8 @@
 import           Stack.PackageIndex
 import           Stack.Types
 import           System.Directory             hiding (findExecutable, findFiles)
+import           System.IO                    (withBinaryFile, IOMode (ReadMode))
+import           System.IO.Error              (isDoesNotExistError)
 
 type SourceMap = Map PackageName PackageSource
 
@@ -190,7 +200,10 @@
                 , packageConfigPlatform = configPlatform $ getConfig bconfig
                 }
             configFinal = config
-                { packageConfigEnableTests = wanted && boptsFinalAction bopts == DoTests
+                { packageConfigEnableTests =
+                    case boptsFinalAction bopts of
+                        DoTests _ -> wanted
+                        _ -> False
                 , packageConfigEnableBenchmarks = wanted && boptsFinalAction bopts == DoBenchmarks
                 }
         pkg <- readPackage config cabalfp
@@ -198,15 +211,17 @@
         when (packageName pkg /= name) $ throwM
             $ MismatchedCabalName cabalfp (packageName pkg)
         mbuildCache <- tryGetBuildCache dir
-        fileModTimes <- getPackageFileModTimes pkg cabalfp
+        files <- getPackageFiles (packageFiles pkg) AllFiles cabalfp
+        (isDirty, newBuildCache) <- checkBuildCache
+            (fromMaybe Map.empty mbuildCache)
+            (map toFilePath $ Set.toList files)
         return LocalPackage
             { lpPackage = pkg
             , lpPackageFinal = pkgFinal
             , lpWanted = wanted
-            , lpDirtyFiles =
-                  maybe True
-                        ((/= fileModTimes) . buildCacheTimes)
-                        mbuildCache
+            , lpFiles = files
+            , lpDirtyFiles = isDirty
+            , lpNewBuildCache = newBuildCache
             , lpCabalFile = cabalfp
             , lpDir = dir
             , lpComponents = fromMaybe Set.empty $ Map.lookup name names
@@ -309,3 +324,58 @@
             -- the version matches what's in the snapshot, so just use the snapshot version
             Just version' | version == version' -> m
             _ -> Map.insert name version m
+
+-- | Compare the current filesystem state to the cached information, and
+-- determine (1) if the files are dirty, and (2) the new cache values.
+checkBuildCache :: MonadIO m
+                => Map FilePath FileCacheInfo -- ^ old cache
+                -> [FilePath] -- ^ files in package
+                -> m (Bool, Map FilePath FileCacheInfo)
+checkBuildCache oldCache files = liftIO $ do
+    (Any isDirty, m) <- fmap mconcat $ mapM go files
+    return (isDirty, m)
+  where
+    go fp = do
+        mmodTime <- getModTimeMaybe fp
+        case mmodTime of
+            Nothing -> return (Any False, Map.empty)
+            Just modTime' -> do
+                (isDirty, newFci) <-
+                    case Map.lookup fp oldCache of
+                        Just fci
+                            | fciModTime fci == modTime' -> return (False, fci)
+                            | otherwise -> do
+                                newFci <- calcFci modTime' fp
+                                let isDirty =
+                                        fciSize fci /= fciSize newFci ||
+                                        fciHash fci /= fciHash newFci
+                                return (isDirty, newFci)
+                        Nothing -> do
+                            newFci <- calcFci modTime' fp
+                            return (True, newFci)
+                return (Any isDirty, Map.singleton fp newFci)
+
+    getModTimeMaybe fp =
+        liftIO
+            (catch
+                 (liftM
+                      (Just . modTime)
+                      (getModificationTime fp))
+                 (\e ->
+                       if isDoesNotExistError e
+                           then return Nothing
+                           else throwM e))
+
+    calcFci modTime' fp =
+        withBinaryFile fp ReadMode $ \h -> do
+            (size, digest) <- CB.sourceHandle h $$ getZipSink
+                ((,)
+                    <$> ZipSink (CL.fold
+                        (\x y -> x + fromIntegral (S.length y))
+                        0)
+                    <*> ZipSink sinkHash)
+            return FileCacheInfo
+                { fciModTime = modTime'
+                , fciSize = size
+                , fciHash = toBytes (digest :: Digest SHA256)
+                }
diff --git a/src/Stack/Build/Types.hs b/src/Stack/Build/Types.hs
--- a/src/Stack/Build/Types.hs
+++ b/src/Stack/Build/Types.hs
@@ -22,13 +22,15 @@
     ,Plan(..)
     ,FinalAction(..)
     ,BuildOpts(..)
+    ,defaultBuildOpts
     ,TaskType(..)
     ,TaskConfigOpts(..)
     ,ConfigCache(..)
     ,ConstructPlanException(..)
     ,configureOpts
     ,BadDependency(..)
-    ,wantedLocalPackages)
+    ,wantedLocalPackages
+    ,FileCacheInfo (..))
     where
 
 import           Control.DeepSeq
@@ -52,6 +54,7 @@
 import           Data.Text.Encoding.Error (lenientDecode)
 import           Data.Time.Calendar
 import           Data.Time.Clock
+import           Data.Word (Word64)
 import           Distribution.System (Arch)
 import           Distribution.Text (display)
 import           GHC.Generics
@@ -183,7 +186,7 @@
              getExtras (DependencyPlanFailures _ m) =
                 Map.unions $ map go $ Map.toList m
               where
-                go (name, (_range, NotInBuildPlan (Just version))) =
+                go (name, (_range, Just version, NotInBuildPlan)) =
                     Map.singleton name version
                 go _ = Map.empty
      -- Supressing duplicate output
@@ -208,15 +211,17 @@
 
 data ConstructPlanException
     = DependencyCycleDetected [PackageName]
-    | DependencyPlanFailures PackageIdentifier (Map PackageName (VersionRange, BadDependency))
+    | DependencyPlanFailures PackageIdentifier (Map PackageName (VersionRange, LatestVersion, BadDependency))
     | UnknownPackage PackageName -- TODO perhaps this constructor will be removed, and BadDependency will handle it all
     -- ^ Recommend adding to extra-deps, give a helpful version number?
     deriving (Typeable, Eq)
 
+-- | For display purposes only, Nothing if package not found
+type LatestVersion = Maybe Version
+
 -- | Reason why a dependency was not used
 data BadDependency
     = NotInBuildPlan
-        (Maybe Version) -- recommended version, for extra-deps output
     | Couldn'tResolveItsDependencies
     | DependencyMismatch Version
     deriving (Typeable, Eq)
@@ -239,16 +244,17 @@
       indent = dropWhileEnd isSpace . unlines . fmap (\line -> "  " ++ line) . lines
       doubleIndent = indent . indent
       appendDeps = foldr (\dep-> (++) ("\n" ++ showDep dep)) ""
-      showDep (name, (range, badDep)) = concat
+      showDep (name, (range, mlatest, badDep)) = concat
         [ show name
         , ": needed ("
         , display range
-        , "), but "
+        , ")"
+        , case mlatest of
+            Nothing -> ""
+            Just latest -> ", latest is " ++ versionString latest
+        , ", but "
         , case badDep of
-            NotInBuildPlan mlatest -> "not present in build plan" ++
-                (case mlatest of
-                    Nothing -> ""
-                    Just latest -> ", latest is " ++ versionString latest)
+            NotInBuildPlan -> "not present in build plan"
             Couldn'tResolveItsDependencies -> "couldn't resolve its dependencies"
             DependencyMismatch version -> versionString version ++ " found"
         ]
@@ -291,15 +297,41 @@
             ,boptsCoverage :: !Bool
             -- ^ Enable code coverage report generation for test
             -- suites.
+            ,boptsFileWatch :: !Bool
+            -- ^ Watch files for changes and automatically rebuild
+            ,boptsKeepGoing :: !(Maybe Bool)
+            -- ^ Keep building/running after failure
             }
   deriving (Show)
 
+defaultBuildOpts :: BuildOpts
+defaultBuildOpts = BuildOpts
+    { boptsTargets = []
+    , boptsLibProfile = False
+    , boptsExeProfile = False
+    , boptsEnableOptimizations = Nothing
+    , boptsHaddock = False
+    , boptsHaddockDeps = Nothing
+    , boptsFinalAction = DoNothing
+    , boptsDryrun = False
+    , boptsGhcOptions = []
+    , boptsFlags = Map.empty
+    , boptsInstallExes = False
+    , boptsPreFetch = False
+    , boptsTestArgs = []
+    , boptsOnlySnapshot = False
+    , boptsCoverage = False
+    , boptsFileWatch = False
+    , boptsKeepGoing = Nothing
+    }
+
 -- | Run a Setup.hs action after building a package, before installing.
 data FinalAction
   = DoTests
+      Bool -- rerun tests which already passed?
   | DoBenchmarks
   | DoNothing
-  deriving (Eq,Bounded,Enum,Show)
+  deriving (Eq,Show)
 
 -- | Package dependency oracle.
 newtype PkgDepsOracle =
@@ -329,6 +361,8 @@
     , lpDir            :: !(Path Abs Dir)  -- ^ Directory of the package.
     , lpCabalFile      :: !(Path Abs File) -- ^ The .cabal file
     , lpDirtyFiles     :: !Bool            -- ^ are there files that have changed since the last build?
+    , lpNewBuildCache  :: !(Map FilePath FileCacheInfo) -- ^ current state of the files
+    , lpFiles          :: !(Set (Path Abs File)) -- ^ all files used by this package
     , lpComponents     :: !(Set Text)      -- ^ components to build, passed directly to Setup.hs build
     }
     deriving Show
@@ -500,3 +534,11 @@
 
 data Installed = Library GhcPkgId | Executable PackageIdentifier
     deriving (Show, Eq, Ord)
+
+data FileCacheInfo = FileCacheInfo
+    { fciModTime :: !ModTime
+    , fciSize :: !Word64
+    , fciHash :: !S.ByteString
+    }
+    deriving (Generic, Show)
+instance Binary FileCacheInfo
diff --git a/src/Stack/BuildPlan.hs b/src/Stack/BuildPlan.hs
--- a/src/Stack/BuildPlan.hs
+++ b/src/Stack/BuildPlan.hs
@@ -64,6 +64,8 @@
 import qualified Distribution.Version as C
 import           Distribution.Text (display)
 import           Network.HTTP.Download
+import           Network.HTTP.Types (Status(..))
+import           Network.HTTP.Client (checkStatus)
 import           Path
 import           Prelude -- Fix AMP warning
 import           Stack.Constants
@@ -81,9 +83,16 @@
         (Path Abs File) -- stack.yaml file
         (Map PackageName (Maybe Version, (Set PackageName))) -- truly unknown
         (Map PackageName (Set PackageIdentifier)) -- shadowed
+    | SnapshotNotFound SnapName
     deriving (Typeable)
 instance Exception BuildPlanException
 instance Show BuildPlanException where
+    show (SnapshotNotFound snapName) = unlines
+        [ "SnapshotNotFound " ++ snapName'
+        , "Non existing resolver: " ++ snapName' ++ "."
+        , "For a complete list of available snapshots see https://www.stackage.org/snapshots"
+        ]
+        where snapName' = show $ renderSnapName snapName
     show (UnknownPackages stackYaml unknown shadowed) =
         unlines $ unknown' ++ shadowed'
       where
@@ -462,7 +471,7 @@
             req <- parseUrl $ T.unpack url
             $logSticky $ "Downloading " <> renderSnapName name <> " build plan ..."
             $logDebug $ "Downloading build plan from: " <> url
-            _ <- download req fp
+            _ <- download req { checkStatus = handle404 } fp
             $logStickyDone $ "Downloaded " <> renderSnapName name <> " build plan."
             liftIO (decodeFileEither $ toFilePath fp) >>= either throwM return
 
@@ -473,6 +482,8 @@
             LTS _ _ -> "lts-haskell"
             Nightly _ -> "stackage-nightly"
     url = rawGithubUrl "fpco" reponame "master" file
+    handle404 (Status 404 _) _ _ = Just $ SomeException $ SnapshotNotFound name
+    handle404 _ _ _              = Nothing
 
 -- | Find the set of @FlagName@s necessary to get the given
 -- @GenericPackageDescription@ to compile against the given @BuildPlan@. Will
diff --git a/src/Stack/Config.hs b/src/Stack/Config.hs
--- a/src/Stack/Config.hs
+++ b/src/Stack/Config.hs
@@ -47,7 +47,7 @@
 import qualified Data.Text as T
 import           Data.Text.Encoding (encodeUtf8, decodeUtf8)
 import qualified Data.Yaml as Yaml
-import           Distribution.System (OS (Windows), Platform (..), buildPlatform)
+import           Distribution.System (OS (..), Platform (..), buildPlatform)
 import qualified Distribution.Text
 import           Distribution.Version (simplifyVersionRange)
 import           Network.HTTP.Client.Conduit (HasHttpManager, getHttpManager, Manager, parseUrl)
@@ -116,6 +116,7 @@
          configSystemGHC = fromMaybe True configMonoidSystemGHC
          configInstallGHC = fromMaybe False configMonoidInstallGHC
          configSkipGHCCheck = fromMaybe False configMonoidSkipGHCCheck
+         configSkipMsys = fromMaybe False configMonoidSkipMsys
 
          configExtraIncludeDirs = configMonoidExtraIncludeDirs
          configExtraLibDirs = configMonoidExtraLibDirs
@@ -153,13 +154,14 @@
         case configMonoidJobs of
             Nothing -> liftIO getNumCapabilities
             Just i -> return i
+     let configConcurrentTests = fromMaybe True configMonoidConcurrentTests
 
      return Config {..}
 
 -- | Command-line arguments parser for configuration.
 configOptsParser :: Bool -> Parser ConfigMonoid
 configOptsParser docker =
-    (\opts systemGHC installGHC arch os jobs includes libs skipGHCCheck -> mempty
+    (\opts systemGHC installGHC arch os jobs includes libs skipGHCCheck skipMsys -> mempty
         { configMonoidDockerOpts = opts
         , configMonoidSystemGHC = systemGHC
         , configMonoidInstallGHC = installGHC
@@ -169,6 +171,7 @@
         , configMonoidJobs = jobs
         , configMonoidExtraIncludeDirs = includes
         , configMonoidExtraLibDirs = libs
+        , configMonoidSkipMsys = skipMsys
         })
     <$> Docker.dockerOptsParser docker
     <*> maybeBoolFlags
@@ -209,6 +212,10 @@
             "skip-ghc-check"
             "skipping the GHC version and architecture check"
             idm
+    <*> maybeBoolFlags
+            "skip-msys"
+            "skipping the local MSYS installation (Windows only)"
+            idm
 
 -- | Get the directory on Windows where we should install extra programs. For
 -- more information, see discussion at:
@@ -275,7 +282,7 @@
       Nothing -> case noConfigStrat of
         ThrowException -> do
             currDir <- getWorkingDir
-            cabalFiles <- findCabalFiles currDir
+            cabalFiles <- findCabalFiles True currDir
             throwM $ NoProjectConfigFound currDir
                 $ Just $ if null cabalFiles then "new" else "init"
         ExecStrategy -> do
diff --git a/src/Stack/Constants.hs b/src/Stack/Constants.hs
--- a/src/Stack/Constants.hs
+++ b/src/Stack/Constants.hs
@@ -19,6 +19,7 @@
     ,configCacheFile
     ,configCabalMod
     ,buildCacheFile
+    ,testSuccessFile
     ,stackProgName
     ,wiredInPackages
     ,cabalPackageName
@@ -106,6 +107,15 @@
 buildCacheFile dir = do
     liftM
         (</> $(mkRelFile "stack-build-cache"))
+        (distDirFromDir dir)
+
+-- | The filename used to mark tests as having succeeded
+testSuccessFile :: (MonadThrow m, MonadReader env m, HasPlatform env,HasEnvConfig env)
+                => Path Abs Dir -- ^ Package directory
+                -> m (Path Abs File)
+testSuccessFile dir =
+    liftM
+        (</> $(mkRelFile "stack-test-success"))
         (distDirFromDir dir)
 
 -- | The filename used for dirtiness check of config.
diff --git a/src/Stack/Docker.hs b/src/Stack/Docker.hs
--- a/src/Stack/Docker.hs
+++ b/src/Stack/Docker.hs
@@ -11,11 +11,11 @@
   ,dockerOptsParser
   ,dockerOptsFromMonoid
   ,dockerPullCmdName
+  ,execWithOptionalContainer
+  ,execWithRequiredContainer
   ,preventInContainer
   ,pull
-  ,rerunCmdWithOptionalContainer
-  ,rerunCmdWithRequiredContainer
-  ,rerunWithOptionalContainer
+  ,reexecWithOptionalContainer
   ,reset
   ) where
 
@@ -29,6 +29,7 @@
 import           Control.Monad.Writer (execWriter,runWriter,tell)
 import           Control.Monad.Trans.Control (MonadBaseControl)
 import           Data.Aeson.Extended (FromJSON(..),(.:),(.:?),(.!=),eitherDecode)
+import           Data.Attoparsec.Args (EscapingMode (Escaping), parseArgs)
 import           Data.ByteString.Builder (stringUtf8,charUtf8,toLazyByteString)
 import qualified Data.ByteString.Char8 as BS
 import qualified Data.ByteString.Lazy.Char8 as LBS
@@ -49,35 +50,30 @@
 import           Path
 import           Path.IO (getWorkingDir,listDirectory)
 import           Stack.Constants (projectDockerSandboxDir,stackProgName,stackDotYaml,stackRootEnvVar)
+import           Stack.Exec
 import           Stack.Types
 import           Stack.Types.Internal
 import           Stack.Docker.GlobalDB
 import           System.Directory (createDirectoryIfMissing,removeDirectoryRecursive,removeFile)
 import           System.Directory (doesDirectoryExist)
 import           System.Environment (lookupEnv,getProgName,getArgs,getExecutablePath)
-import           System.Exit (ExitCode(ExitSuccess),exitWith)
 import           System.FilePath (dropTrailingPathSeparator,takeBaseName)
 import           System.Info (arch,os)
 import           System.IO (stderr,stdin,stdout,hIsTerminalDevice)
-import qualified System.Process as Proc
 import           System.Process.PagerEditor (editByteString)
 import           System.Process.Read
 import           System.Process.Run
 import           Text.Printf (printf)
 
-#ifndef mingw32_HOST_OS
-import           System.Posix.Signals (installHandler,sigTERM,Handler(Catch))
-#endif
-
 -- | If Docker is enabled, re-runs the currently running OS command in a Docker container.
 -- Otherwise, runs the inner action.
-rerunWithOptionalContainer
+reexecWithOptionalContainer
     :: M env m
     => Maybe (Path Abs Dir)
     -> IO ()
     -> m ()
-rerunWithOptionalContainer mprojectRoot =
-  rerunCmdWithOptionalContainer mprojectRoot getCmdArgs
+reexecWithOptionalContainer mprojectRoot =
+  execWithOptionalContainer mprojectRoot getCmdArgs
   where
     getCmdArgs =
       do args <- getArgs
@@ -94,33 +90,33 @@
 
 -- | If Docker is enabled, re-runs the OS command returned by the second argument in a
 -- Docker container.  Otherwise, runs the inner action.
-rerunCmdWithOptionalContainer
+execWithOptionalContainer
     :: M env m
     => Maybe (Path Abs Dir)
     -> IO (FilePath,[String],Config -> Config)
     -> IO ()
     -> m ()
-rerunCmdWithOptionalContainer mprojectRoot getCmdArgs inner =
+execWithOptionalContainer mprojectRoot getCmdArgs inner =
   do config <- asks getConfig
      inContainer <- getInContainer
      if inContainer || not (dockerEnable (configDocker config))
         then liftIO inner
         else do (cmd_,args,modConfig) <- liftIO getCmdArgs
-                runContainerAndExit modConfig mprojectRoot cmd_ args [] (return ())
+                runContainerAndExit modConfig mprojectRoot cmd_ args []
 
 -- | If Docker is enabled, re-runs the OS command returned by the second argument in a
--- Docker container.  Otherwise, runs the inner action.
-rerunCmdWithRequiredContainer
+-- Docker container.  Otherwise, throws 'DockerMustBeEnabledException'.
+execWithRequiredContainer
     :: M env m
     => Maybe (Path Abs Dir)
     -> IO (FilePath,[String],Config -> Config)
     -> m ()
-rerunCmdWithRequiredContainer mprojectRoot getCmdArgs =
+execWithRequiredContainer mprojectRoot getCmdArgs =
   do config <- asks getConfig
-     when (not (dockerEnable (configDocker config)))
+     unless (dockerEnable (configDocker config))
           (throwM DockerMustBeEnabledException)
      (cmd_,args,modConfig) <- liftIO getCmdArgs
-     runContainerAndExit modConfig mprojectRoot cmd_ args [] (return ())
+     runContainerAndExit modConfig mprojectRoot cmd_ args []
 
 -- | Error if running in a container.
 preventInContainer :: (MonadIO m,MonadThrow m) => m () -> m ()
@@ -145,14 +141,12 @@
                     -> FilePath
                     -> [String]
                     -> [(String, String)]
-                    -> IO ()
                     -> m ()
 runContainerAndExit modConfig
                     mprojectRoot
                     cmnd
                     args
-                    envVars
-                    successPostAction =
+                    envVars =
   do config <- fmap modConfig (asks getConfig)
      let docker = configDocker config
      envOverride <- getEnvOverride (configPlatform config)
@@ -209,69 +203,68 @@
          sandboxSubdirs = map (\d -> sandboxRepoDir </> d)
                               sandboxedHomeSubdirectories
          isTerm = isStdinTerminal && isStdoutTerminal && isStderrTerminal
-         execDockerProcess =
-           do mapM_ (createDirectoryIfMissing True)
-                    (concat [[toFilePath sandboxHomeDir
-                             ,toFilePath sandboxSandboxDir
-                             ,toFilePath stackRoot] ++
-                             map toFilePath sandboxSubdirs])
-              execProcessAndExit
-                envOverride
-                "docker"
-                (concat
-                  [["run"
-                   ,"--net=host"
-                   ,"-e",inContainerEnvVar ++ "=1"
-                   ,"-e",stackRootEnvVar ++ "=" ++ toFPNoTrailingSep stackRoot
-                   ,"-e","WORK_UID=" ++ uid
-                   ,"-e","WORK_GID=" ++ gid
-                   ,"-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 ++ "/")))]
-                  ,if oldImage
-                     then ["-e",sandboxIDEnvVar ++ "=" ++ sandboxID
-                          ,"--entrypoint=/root/entrypoint.sh"]
-                     else []
-                  ,case (dockerPassHost docker,dockerHost) of
-                     (True,Just x@('u':'n':'i':'x':':':'/':'/':s)) -> ["-e","DOCKER_HOST=" ++ x
-                                                                      ,"-v",s ++ ":" ++ s]
-                     (True,Just x) -> ["-e","DOCKER_HOST=" ++ x]
-                     (True,Nothing) -> ["-v","/var/run/docker.sock:/var/run/docker.sock"]
-                     (False,_) -> []
-                  ,case (dockerPassHost docker,dockerCertPath) of
-                     (True,Just x) -> ["-e","DOCKER_CERT_PATH=" ++ x
-                                      ,"-v",x ++ ":" ++ x]
-                     _ -> []
-                  ,case (dockerPassHost docker,dockerTlsVerify) of
-                     (True,Just x )-> ["-e","DOCKER_TLS_VERIFY=" ++ x]
-                     _ -> []
-                  ,concatMap sandboxSubdirArg sandboxSubdirs
-                  ,concatMap mountArg (dockerMount docker)
-                  ,case dockerContainerName docker of
-                     Just name -> ["--name=" ++ name]
-                     Nothing -> []
-                  ,if dockerDetach docker
-                      then ["-d"]
-                      else concat [["--rm" | not (dockerPersist docker)]
-                                  ,["-t" | isTerm]
-                                  ,["-i" | isTerm]]
-                  ,dockerRunArgs docker
-                  ,[image]
-                  ,map (\(k,v) -> k ++ "=" ++ v) envVars
-                  ,[cmnd]
-                  ,args])
-                successPostAction
-     liftIO (do updateDockerImageLastUsed config
-                                          (iiId imageInfo)
-                                          (toFilePath projectRoot)
-                execDockerProcess)
+     liftIO
+       (do updateDockerImageLastUsed config
+                                     (iiId imageInfo)
+                                     (toFilePath projectRoot)
 
+           mapM_ (createDirectoryIfMissing True)
+                 (concat [[toFilePath sandboxHomeDir
+                          ,toFilePath sandboxSandboxDir
+                          ,toFilePath stackRoot] ++
+                          map toFilePath sandboxSubdirs]))
+     exec
+       plainEnvSettings
+       "docker"
+       (concat
+         [["run"
+          ,"--net=host"
+          ,"-e",inContainerEnvVar ++ "=1"
+          ,"-e",stackRootEnvVar ++ "=" ++ toFPNoTrailingSep stackRoot
+          ,"-e","WORK_UID=" ++ uid
+          ,"-e","WORK_GID=" ++ gid
+          ,"-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 ++ "/")))]
+         ,if oldImage
+            then ["-e",sandboxIDEnvVar ++ "=" ++ sandboxID
+                 ,"--entrypoint=/root/entrypoint.sh"]
+            else []
+         ,case (dockerPassHost docker,dockerHost) of
+            (True,Just x@('u':'n':'i':'x':':':'/':'/':s)) -> ["-e","DOCKER_HOST=" ++ x
+                                                             ,"-v",s ++ ":" ++ s]
+            (True,Just x) -> ["-e","DOCKER_HOST=" ++ x]
+            (True,Nothing) -> ["-v","/var/run/docker.sock:/var/run/docker.sock"]
+            (False,_) -> []
+         ,case (dockerPassHost docker,dockerCertPath) of
+            (True,Just x) -> ["-e","DOCKER_CERT_PATH=" ++ x
+                             ,"-v",x ++ ":" ++ x]
+            _ -> []
+         ,case (dockerPassHost docker,dockerTlsVerify) of
+            (True,Just x )-> ["-e","DOCKER_TLS_VERIFY=" ++ x]
+            _ -> []
+         ,concatMap sandboxSubdirArg sandboxSubdirs
+         ,concatMap mountArg (dockerMount docker)
+         ,case dockerContainerName docker of
+            Just name -> ["--name=" ++ name]
+            Nothing -> []
+         ,if dockerDetach docker
+             then ["-d"]
+             else concat [["--rm" | not (dockerPersist docker)]
+                         ,["-t" | isTerm]
+                         ,["-i"]]
+         ,dockerRunArgs docker
+         ,[image]
+         ,map (\(k,v) -> k ++ "=" ++ v) envVars
+         ,[cmnd]
+         ,args])
+
   where
     lookupImageEnv name vars =
       case lookup name vars of
@@ -587,19 +580,6 @@
   where minimumDockerVersion = $(mkVersion "1.3.0")
         prohibitedDockerVersions = [$(mkVersion "1.2.0")]
 
--- | Run a process, then exit with the same exit code.
-execProcessAndExit :: EnvOverride -> FilePath -> [String] -> IO () -> IO ()
-execProcessAndExit envOverride cmnd args successPostAction =
-  do (_, _, _, h) <- Proc.createProcess (Proc.proc cmnd args){Proc.delegate_ctlc = True
-                                                             ,Proc.env = envHelper envOverride}
-#ifndef mingw32_HOST_OS
-     _ <- installHandler sigTERM (Catch (Proc.terminateProcess h)) Nothing
-#endif
-     exitCode <- Proc.waitForProcess h
-     when (exitCode == ExitSuccess)
-          successPostAction
-     exitWith exitCode
-
 -- | Remove the project's Docker sandbox.
 reset :: (MonadIO m) => Maybe (Path Abs Dir) -> Bool -> m ()
 reset maybeProjectRoot keepHome =
@@ -688,7 +668,7 @@
                         hide <>
                         metavar "NAME" <>
                         help "Docker container name")
-    <*> wordsStrOption (long (dockerOptName dockerRunArgsArgName) <>
+    <*> argsStrOption (long (dockerOptName dockerRunArgsArgName) <>
                         hide <>
                         value [] <>
                         metavar "'ARG1 [ARG2 ...]'" <>
@@ -708,7 +688,7 @@
   where
     dockerOptName optName = dockerCmdName ++ "-" ++ T.unpack optName
     maybeStrOption = optional . option str
-    wordsStrOption = option (fmap words str)
+    argsStrOption = option (fmap (either error id . parseArgs Escaping . T.pack) str)
     hide = if showOptions
               then idm
               else internal <> hidden
diff --git a/src/Stack/Dot.hs b/src/Stack/Dot.hs
--- a/src/Stack/Dot.hs
+++ b/src/Stack/Dot.hs
@@ -1,69 +1,235 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TemplateHaskell #-}
-module Stack.Dot where
-
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Stack.Dot (dot
+                 ,DotOpts(..)
+                 ,dotOptsParser
+                 ,resolveDependencies
+                 ,printGraph
+                 ,pruneGraph
+                 ) where
 
-import           Control.Monad (when)
+import           Control.Monad (void)
 import           Control.Monad.Catch (MonadCatch)
-import           Control.Monad.IO.Class (MonadIO)
+import           Control.Monad.IO.Class
 import           Control.Monad.Logger (MonadLogger, logInfo)
 import           Control.Monad.Reader (MonadReader)
+import           Control.Monad.Trans.Control (MonadBaseControl)
+import           Data.Char (isSpace)
 import qualified Data.Foldable as F
-import           Data.Monoid ((<>))
+import qualified Data.HashSet as HashSet
+import           Data.List.Split (splitOn)
+import           Data.Map (Map)
 import qualified Data.Map as Map
+import           Data.Set (Set)
 import qualified Data.Set as Set
-import qualified Data.Text as T
+import           Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Traversable as T
+import           Network.HTTP.Client.Conduit (HasHttpManager)
+import           Options.Applicative
+import           Options.Applicative.Builder.Extra (boolFlags)
+import           Stack.Build (withLoadPackage)
 import           Stack.Build.Source
 import           Stack.Build.Types
+import           Stack.Constants
 import           Stack.Package
 import           Stack.Types
 
--- | Convert a package name to a graph node name.
-nodeName :: PackageName -> T.Text
-nodeName name = "\"" <> T.pack (packageNameString name) <> "\""
+-- | Options record for `stack dot`
+data DotOpts = DotOpts
+    { dotIncludeExternal :: Bool
+    -- ^ Include external dependencies
+    , dotIncludeBase :: Bool
+    -- ^ Include dependencies on base
+    , dotDependencyDepth :: Maybe Int
+    -- ^ Limit the depth of dependency resolution to (Just n) or continue until fixpoint
+    , dotPrune :: Set String
+    -- ^ Package names to prune from the graph
+    }
 
-dot :: (MonadReader env m, HasBuildConfig env, MonadIO m, MonadLogger m, MonadCatch m,HasEnvConfig env)
-    => m ()
-dot = do
-    (locals, _names, _idents) <- loadLocals
-        BuildOpts
-            { boptsTargets = []
-            , boptsLibProfile = False
-            , boptsExeProfile = False
-            , boptsEnableOptimizations = Nothing
-            , boptsHaddock = False
-            , boptsHaddockDeps = Nothing
-            , boptsFinalAction = DoNothing
-            , boptsDryrun = False
-            , boptsGhcOptions = []
-            , boptsFlags = Map.empty
-            , boptsInstallExes = False
-            , boptsPreFetch = False
-            , boptsTestArgs = []
-            , boptsOnlySnapshot = False
-            , boptsCoverage = False
-            }
-        Map.empty
-    let localNames = Set.fromList $ map (packageName . lpPackage) locals
+-- | Parser for arguments to `stack dot`
+dotOptsParser :: Parser DotOpts
+dotOptsParser = DotOpts
+            <$> includeExternal
+            <*> includeBase
+            <*> depthLimit
+            <*> fmap (maybe Set.empty Set.fromList . fmap splitNames) prunedPkgs
+  where includeExternal = boolFlags False
+                                    "external"
+                                    "inclusion of external dependencies"
+                                    idm
+        includeBase = boolFlags True
+                                "include-base"
+                                "inclusion of dependencies on base"
+                                idm
+        depthLimit =
+            optional (option auto
+                             (long "depth" <>
+                              metavar "DEPTH" <>
+                              help ("Limit the depth of dependency resolution " <>
+                                    "(Default: No limit)")))
+        prunedPkgs = optional (strOption
+                                   (long "prune" <>
+                                    metavar "PACKAGES" <>
+                                    help ("Prune each package name " <>
+                                          "from the comma separated list " <>
+                                          "of package names PACKAGES")))
 
-    $logInfo "digraph deps {"
-    $logInfo "splines=polyline;"
+        splitNames :: String -> [String]
+        splitNames = map (takeWhile (not . isSpace) . dropWhile isSpace) . splitOn ","
 
-    F.forM_ locals $ \lp -> do
-        let deps = Set.intersection localNames $ packageAllDeps $ lpPackage lp
-        F.forM_ deps $ \dep ->
-            $logInfo $ T.concat
-                [ nodeName $ packageName $ lpPackage lp
-                , " -> "
-                , nodeName dep
-                , ";"
-                ]
-        when (Set.null deps) $
-            $logInfo $ T.concat
-                [ "{rank=max; "
-                , nodeName $ packageName $ lpPackage lp
-                , "}"
-                ]
+-- | Visualize the project's dependencies as a graphviz graph
+dot :: (HasEnvConfig env
+       ,HasHttpManager env
+       ,MonadBaseControl IO m
+       ,MonadCatch m
+       ,MonadIO m
+       ,MonadLogger m
+       ,MonadReader env m
+       )
+    => DotOpts
+    -> m ()
+dot dotOpts = do
+    (locals,_,_) <- loadLocals defaultBuildOpts Map.empty
+    (_,_,_,sourceMap) <- loadSourceMap defaultBuildOpts
+    let graph = Map.fromList (localDependencies dotOpts locals)
+    menv <- getMinimalEnvOverride
+    resultGraph <- withLoadPackage menv (\loader -> do
+      let depLoader = createDepLoader sourceMap (fmap3 packageAllDeps loader)
+      liftIO $ resolveDependencies (dotDependencyDepth dotOpts) graph depLoader)
+    let pkgsToPrune = if dotIncludeBase dotOpts
+                         then dotPrune dotOpts
+                         else Set.insert "base" (dotPrune dotOpts)
+        localNames = Set.fromList (map (packageName . lpPackage) locals)
+        prunedGraph = pruneGraph localNames pkgsToPrune resultGraph
+    printGraph dotOpts locals prunedGraph
+  where -- fmap a function over the result of a function with 3 arguments
+        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
 
-    $logInfo "}"
+-- | `pruneGraph dontPrune toPrune graph` prunes all packages in
+-- `graph` with a name in `toPrune` and removes resulting orphans
+-- unless they are in `dontPrune`
+pruneGraph :: (F.Foldable f, F.Foldable g)
+           => f PackageName
+           -> g String
+           -> Map PackageName (Set PackageName)
+           -> Map PackageName (Set PackageName)
+pruneGraph dontPrune names =
+  pruneUnreachable dontPrune . Map.mapMaybeWithKey (\pkg pkgDeps ->
+    if show pkg `F.elem` names
+      then Nothing
+      else let filtered = Set.filter (\n -> show n `F.notElem` names) pkgDeps
+           in if Set.null filtered && not (Set.null pkgDeps)
+                then Nothing
+                else Just filtered)
+
+-- | Make sure that all unreachable nodes (orphans) are pruned
+pruneUnreachable :: F.Foldable f
+                 => f PackageName
+                 -> Map PackageName (Set PackageName)
+                 -> Map PackageName (Set PackageName)
+pruneUnreachable dontPrune = fixpoint prune
+  where fixpoint :: Eq a => (a -> a) -> a -> a
+        fixpoint f v = if f v == v then v else fixpoint f (f v)
+        prune graph' = Map.filterWithKey (\k _ -> reachable k) graph'
+          where reachable k = k `F.elem` dontPrune || k `Set.member` reachables
+                reachables = F.fold graph'
+
+
+-- | Resolve the dependency graph up to (Just depth) or until fixpoint is reached
+resolveDependencies :: (Applicative m, Monad m)
+                    => Maybe Int
+                    -> Map PackageName (Set PackageName)
+                    -> (PackageName -> m (Set PackageName))
+                    -> m (Map PackageName (Set PackageName))
+resolveDependencies (Just 0) graph _ = return graph
+resolveDependencies limit graph loadPackageDeps = do
+  let values = Set.unions (Map.elems graph)
+      keys = Map.keysSet graph
+      next = Set.difference values keys
+  if Set.null next
+     then return graph
+     else do
+       x <- T.traverse (\name -> (name,) <$> loadPackageDeps name) (F.toList next)
+       resolveDependencies (subtract 1 <$> limit)
+                      (Map.unionWith Set.union graph (Map.fromList x))
+                      loadPackageDeps
+
+-- | Given a SourceMap and a dependency loader, load the set of dependencies for a package
+createDepLoader :: Applicative m
+                => Map PackageName PackageSource
+                -> (PackageName -> Version -> Map FlagName Bool -> m (Set PackageName))
+                -> PackageName
+                -> m (Set PackageName)
+createDepLoader sourceMap loadPackageDeps pkgName =
+  case Map.lookup pkgName sourceMap of
+    Just (PSLocal lp) -> pure (packageAllDeps (lpPackage lp))
+    Just (PSUpstream version _ flags) -> loadPackageDeps pkgName version flags
+    Nothing -> pure Set.empty
+
+-- | Resolve the direct (depth 0) external dependencies of the given local packages
+localDependencies :: DotOpts -> [LocalPackage] -> [(PackageName,Set PackageName)]
+localDependencies dotOpts locals = map (\lp -> (packageName (lpPackage lp), deps lp)) locals
+  where deps lp = if dotIncludeExternal dotOpts
+                then Set.delete (lpName lp) (packageAllDeps (lpPackage lp))
+                else Set.intersection localNames (packageAllDeps (lpPackage lp))
+        lpName lp = packageName (lpPackage lp)
+        localNames = Set.fromList $ map (packageName . lpPackage) locals
+
+-- | Print a graphviz graph of the edges in the Map and highlight the given local packages
+printGraph :: (Applicative m, MonadLogger m)
+           => DotOpts
+           -> [LocalPackage]
+           -> Map PackageName (Set PackageName)
+           -> m ()
+printGraph dotOpts locals graph = do
+  $logInfo "strict digraph deps {"
+  printLocalNodes dotOpts filteredLocals
+  printLeaves graph
+  void (Map.traverseWithKey printEdges graph)
+  $logInfo "}"
+  where filteredLocals = filter (\local ->
+          show (packageName (lpPackage local)) `Set.notMember` dotPrune dotOpts) locals
+
+-- | Print the local nodes with a different style depending on options
+printLocalNodes :: (F.Foldable t, MonadLogger m)
+                => DotOpts
+                -> t LocalPackage
+                -> m ()
+printLocalNodes dotOpts locals = $logInfo (Text.intercalate "\n" lpNodes)
+  where applyStyle :: Text -> Text
+        applyStyle n = if dotIncludeExternal dotOpts
+                         then n <> " [style=dashed];"
+                         else n <> " [style=solid];"
+        lpNodes :: [Text]
+        lpNodes = map (applyStyle . nodeName . packageName . lpPackage) (F.toList locals)
+
+-- | Print nodes without dependencies
+printLeaves :: (Applicative m, MonadLogger m) => Map PackageName (Set PackageName) -> m ()
+printLeaves = F.traverse_ printLeaf . Map.keysSet . Map.filter Set.null
+
+-- | `printDedges p ps` prints an edge from p to every ps
+printEdges :: (Applicative m, MonadLogger m) => PackageName -> Set PackageName -> m ()
+printEdges package deps = F.for_ deps (printEdge package)
+
+-- | Print an edge between the two package names
+printEdge :: MonadLogger m => PackageName -> PackageName -> m ()
+printEdge from to = $logInfo (Text.concat [ nodeName from, " -> ", nodeName to, ";"])
+
+-- | Convert a package name to a graph node name.
+nodeName :: PackageName -> Text
+nodeName name = "\"" <> Text.pack (packageNameString name) <> "\""
+
+-- | Print a node with no dependencies
+printLeaf :: MonadLogger m => PackageName -> m ()
+printLeaf package = $logInfo . Text.concat $
+  if isWiredIn package
+    then ["{rank=max; ", nodeName package, " [shape=box]; };"]
+    else ["{rank=max; ", nodeName package, "; };"]
+
+-- | Check if the package is wired in (shipped with) ghc
+isWiredIn :: PackageName -> Bool
+isWiredIn = (`HashSet.member` wiredInPackages)
diff --git a/src/Stack/Exec.hs b/src/Stack/Exec.hs
--- a/src/Stack/Exec.hs
+++ b/src/Stack/Exec.hs
@@ -26,6 +26,14 @@
     , esStackExe = True
     }
 
+-- | Environment settings which do not embellish the environment
+plainEnvSettings :: EnvSettings
+plainEnvSettings = EnvSettings
+    { esIncludeLocals = False
+    , esIncludeGhcPackagePath = False
+    , esStackExe = False
+    }
+
 -- | Execute a process within the Stack configured environment.
 exec :: (HasConfig r, MonadReader r m, MonadIO m, MonadLogger m, MonadThrow m)
         => EnvSettings -> String -> [String] -> m b
diff --git a/src/Stack/Fetch.hs b/src/Stack/Fetch.hs
--- a/src/Stack/Fetch.hs
+++ b/src/Stack/Fetch.hs
@@ -398,7 +398,7 @@
                 , drLengthCheck = fmap fromIntegral $ tfSize toFetch
                 }
         let progressSink = do
-                liftIO $ runInBase $ $logInfo $ packageIdentifierText ident <> ": downloading"
+                liftIO $ runInBase $ $logInfo $ packageIdentifierText ident <> ": download"
         _ <- verifiedDownload downloadReq destpath progressSink
 
         let fp = toFilePath destpath
diff --git a/src/Stack/FileWatch.hs b/src/Stack/FileWatch.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/FileWatch.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+module Stack.FileWatch
+    ( fileWatch
+    , printExceptionStderr
+    ) where
+
+import Blaze.ByteString.Builder (toLazyByteString, copyByteString)
+import Blaze.ByteString.Builder.Char.Utf8 (fromShow)
+import Control.Concurrent.Async (race_)
+import Control.Concurrent.STM
+import Control.Exception (Exception)
+import Control.Exception.Enclosed (tryAny)
+import Control.Monad (forever, unless)
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Map.Strict as Map
+import Data.Monoid ((<>))
+import Data.Set (Set)
+import qualified Data.Set as Set
+import Data.String (fromString)
+import Data.Traversable (forM)
+import Path
+import System.FSNotify
+import System.IO (stderr)
+
+-- | Print an exception to stderr
+printExceptionStderr :: Exception e => e -> IO ()
+printExceptionStderr e =
+    L.hPut stderr $ toLazyByteString $ fromShow e <> copyByteString "\n"
+
+-- | Run an action, watching for file changes
+--
+-- 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.
+fileWatch :: ((Set (Path Abs File) -> IO ()) -> IO ())
+          -> IO ()
+fileWatch inner = withManager $ \manager -> do
+    dirtyVar <- newTVarIO True
+    watchVar <- newTVarIO Map.empty
+
+    let onChange = atomically $ writeTVar dirtyVar True
+
+        setWatched :: Set (Path Abs File) -> IO ()
+        setWatched files = do
+            watch0 <- readTVarIO watchVar
+            let actions = Map.mergeWithKey
+                    keepListening
+                    stopListening
+                    startListening
+                    watch0
+                    newDirs
+            watch1 <- forM (Map.toList actions) $ \(k, mmv) -> do
+                mv <- mmv
+                return $
+                    case mv of
+                        Nothing -> Map.empty
+                        Just v -> Map.singleton k v
+            atomically $ writeTVar watchVar $ Map.unions watch1
+          where
+            newDirs = Map.fromList $ map (, ())
+                    $ Set.toList
+                    $ Set.map parent files
+
+            keepListening _dir listen () = Just $ return $ Just listen
+            stopListening = Map.map $ \f -> do
+                () <- f
+                return Nothing
+            startListening = Map.mapWithKey $ \dir () -> do
+                let dir' = fromString $ toFilePath dir
+                listen <- watchDir manager dir' (const True) (const onChange)
+                return $ Just listen
+
+    let watchInput = do
+            line <- getLine
+            unless (line == "quit") $ do
+                case line of
+                    "help" -> do
+                        putStrLn ""
+                        putStrLn "help: display this help"
+                        putStrLn "quit: exit"
+                        putStrLn "build: force a rebuild"
+                        putStrLn "watched: display watched directories"
+                    "build" -> onChange
+                    "watched" -> do
+                        watch <- readTVarIO watchVar
+                        mapM_ (putStrLn . toFilePath) (Map.keys watch)
+                    _ -> putStrLn $ "Unknown command: " ++ show line
+
+                watchInput
+
+    race_ watchInput $ forever $ do
+        atomically $ do
+            dirty <- readTVar dirtyVar
+            check dirty
+            writeTVar dirtyVar False
+
+        eres <- tryAny $ inner setWatched
+        case eres of
+            Left e -> printExceptionStderr e
+            Right () -> putStrLn "Success! Waiting for next file change."
+
+        putStrLn "Type help for available commands"
diff --git a/src/Stack/Ide.hs b/src/Stack/Ide.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Ide.hs
@@ -0,0 +1,169 @@
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+-- | Run a IDE configured with the user's project(s).
+
+module Stack.Ide (ide) where
+
+import           Control.Concurrent
+import           Control.Monad.Catch
+import           Control.Monad.IO.Class
+import           Control.Monad.Logger
+import           Control.Monad.Reader
+import           Data.Aeson
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as S8
+import           Data.ByteString.Lazy (ByteString)
+import qualified Data.ByteString.Lazy as L
+import           Data.List
+import qualified Data.Map.Strict as M
+import           Data.Maybe
+import           Data.Monoid
+import qualified Data.Set as S
+import           Data.Text (Text)
+import           Path
+import           Path.IO
+import           Stack.Build.Source
+import           Stack.Constants
+import           Stack.Exec (defaultEnvSettings)
+import           Stack.Package
+import           Stack.Types
+import           System.Directory (doesFileExist)
+import           System.Exit
+import           System.IO
+import qualified System.Process as P
+import           System.Process.Read
+
+-- | Launch a GHCi IDE for the given local project targets with the
+-- given options and configure it with the load paths and extensions
+-- of those targets.
+ide
+    :: (HasConfig r, HasBuildConfig r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m)
+    => [Text] -- ^ Targets.
+    -> [String] -- ^ GHC options.
+    -> m ()
+ide targets useropts = do
+    econfig <- asks getEnvConfig
+    bconfig <- asks getBuildConfig
+    pwd <- getWorkingDir
+    pkgs <-
+        liftM catMaybes $
+        forM (M.toList (bcPackages bconfig)) $
+        \(dir,validWanted) ->
+             do cabalfp <- getCabalFileName dir
+                name <- parsePackageNameFromFilePath cabalfp
+                let config =
+                        PackageConfig
+                        { packageConfigEnableTests = True
+                        , packageConfigEnableBenchmarks = True
+                        , packageConfigFlags = localFlags mempty bconfig name
+                        , packageConfigGhcVersion = envConfigGhcVersion econfig
+                        , packageConfigPlatform = configPlatform
+                              (getConfig bconfig)
+                        }
+                pkg <- readPackage config cabalfp
+                if validWanted && wanted pwd cabalfp pkg
+                    then do
+                        pkgOpts <- getPackageOpts (packageOpts pkg) cabalfp
+                        srcfiles <-
+                            getPackageFiles (packageFiles pkg) Modules cabalfp
+                        dist <- distDirFromDir dir
+                        autogen <- return (autogenDir dist)
+                        paths_foo <-
+                            liftM
+                                (autogen </>)
+                                (parseRelFile
+                                     ("Paths_" ++
+                                      packageNameString name ++ ".hs"))
+                        paths_foo_exists <- fileExists paths_foo
+                        return
+                            (Just
+                                 ( packageName pkg
+                                 , ["--dist-dir=" <> toFilePath dist] ++
+                                   map ("--ghc-option=" ++) (filter (not . badForGhci) pkgOpts)
+                                 , mapMaybe
+                                       (stripDir pwd)
+                                       (S.toList srcfiles <>
+                                        if paths_foo_exists
+                                            then [paths_foo]
+                                            else [])))
+                    else return Nothing
+    localdb <- packageDatabaseLocal
+    depsdb <- packageDatabaseDeps
+    let pkgopts = concat (map _2 pkgs)
+        srcfiles = concatMap (map toFilePath . _3) pkgs
+        pkgdbs =
+            ["--package-db=" <> toFilePath depsdb <> ":" <> toFilePath localdb]
+    exec
+        "stack-ide"
+        (["--local-work-dir="++toFilePath pwd] ++
+         map ("--ghc-option=" ++) (filter (not . badForGhci) useropts) <> pkgopts <> pkgdbs)
+        (encode (initialRequest srcfiles))
+  where
+    wanted pwd cabalfp pkg = isInWantedList || targetsEmptyAndInDir
+      where
+        isInWantedList = elem (packageNameText (packageName pkg)) targets
+        targetsEmptyAndInDir = null targets || isParentOf (parent cabalfp) pwd
+    badForGhci x =
+        isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky")
+    _1 (x,_,_) = x
+    _2 (_,x,_) = x
+    _3 (_,_,x) = x
+
+-- | Make the initial request.
+initialRequest :: [FilePath] -> Value
+initialRequest srcfiles =
+    object
+        [ "tag" .= "RequestUpdateSession"
+        , "contents" .=
+            [ object
+                [ "tag" .= "RequestUpdateTargets"
+                , "contents" .= object
+                    [ "tag" .= "TargetsInclude"
+                    , "contents" .= srcfiles ]
+                ]
+            ]
+        ]
+
+-- | Execute a process within the Stack configured environment.
+exec :: (HasConfig r, MonadReader r m, MonadIO m, MonadLogger m, MonadThrow m)
+        => String -> [String] -> ByteString -> m b
+exec cmd args input = do
+    config <- asks getConfig
+    menv <-
+        liftIO
+            (configEnvOverride
+                 config
+                 defaultEnvSettings
+                 { esIncludeGhcPackagePath = False
+                 })
+    exists <- liftIO $ doesFileExist cmd
+    cmd' <-
+        if exists
+            then return cmd
+            else liftM toFilePath $
+                 join $ System.Process.Read.findExecutable menv cmd
+    let cp =
+            (P.proc cmd' args)
+            { P.env = envHelper menv
+            , P.delegate_ctlc = True
+            , P.std_in = P.CreatePipe
+            }
+    $logProcessRun cmd' args
+    (Just procin,Nothing,Nothing,ph) <- liftIO (P.createProcess cp)
+    liftIO
+        (do hSetBuffering stdin LineBuffering
+            hSetBuffering procin LineBuffering)
+    liftIO (do {-S8.hPutStrLn stdout (L.toStrict input)-}
+               S8.hPutStrLn procin (L.toStrict input))
+    _tid <-
+        liftIO
+            (forkIO
+                 (forever
+                      (do bytes <- S.getLine
+                          S.hPutStr procin bytes)))
+    ec <- liftIO (P.waitForProcess ph)
+    liftIO (exitWith ec)
diff --git a/src/Stack/Init.hs b/src/Stack/Init.hs
--- a/src/Stack/Init.hs
+++ b/src/Stack/Init.hs
@@ -43,9 +43,9 @@
 import           Stack.Types
 import           System.Directory                (getDirectoryContents)
 
-findCabalFiles :: MonadIO m => Path Abs Dir -> m [Path Abs File]
-findCabalFiles dir =
-    liftIO $ findFiles dir isCabal (not . isIgnored)
+findCabalFiles :: MonadIO m => Bool -> Path Abs Dir -> m [Path Abs File]
+findCabalFiles recurse dir =
+    liftIO $ findFiles dir isCabal (\subdir -> recurse && not (isIgnored subdir))
   where
     isCabal path = ".cabal" `isSuffixOf` toFilePath path
 
@@ -68,9 +68,12 @@
     let dest = currDir </> stackDotYaml
         dest' = toFilePath dest
     exists <- fileExists dest
-    when exists $ error "Refusing to overwrite existing stack.yaml, please delete before running stack init"
+    when (not (forceOverwrite initOpts) && exists) $
+      error ("Refusing to overwrite existing stack.yaml, " <>
+             "please delete before running stack init " <>
+             "or if you are sure use \"--force\"")
 
-    cabalfps <- findCabalFiles currDir
+    cabalfps <- findCabalFiles (includeSubDirs initOpts) currDir
     $logInfo $ "Writing default config file to: " <> T.pack dest'
     $logInfo $ "Basing on cabal files:"
     mapM_ (\path -> $logInfo $ "- " <> T.pack (toFilePath path)) cabalfps
@@ -128,7 +131,7 @@
                    -> [C.GenericPackageDescription] -- ^ cabal descriptions
                    -> InitOpts
                    -> m (Resolver, Map PackageName (Map FlagName Bool), Map PackageName Version)
-getDefaultResolver cabalfps gpds initOpts = do
+getDefaultResolver cabalfps gpds initOpts =
     case ioMethod initOpts of
         MethodSnapshot snapPref -> do
             msnapshots <- getSnapshots'
@@ -195,6 +198,10 @@
 data InitOpts = InitOpts
     { ioMethod :: !Method
     -- ^ Preferred snapshots
+    , forceOverwrite :: Bool
+    -- ^ Force overwrite of existing stack.yaml
+    , includeSubDirs :: Bool
+    -- ^ If True, include all .cabal files found in any sub directories
     }
 
 data SnapPref = PrefNone | PrefLTS | PrefNightly
@@ -204,8 +211,16 @@
 
 initOptsParser :: Parser InitOpts
 initOptsParser =
-    InitOpts <$> method
+    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")
     method = solver
          <|> (MethodResolver <$> resolver)
          <|> (MethodSnapshot <$> snapPref)
diff --git a/src/Stack/Package.hs b/src/Stack/Package.hs
--- a/src/Stack/Package.hs
+++ b/src/Stack/Package.hs
@@ -29,7 +29,8 @@
   ,packageToolDependencies
   ,packageDependencies
   ,packageIdentifier
-  ,CabalFileType(..))
+  ,CabalFileType(..)
+  ,autogenDir)
   where
 
 import           Control.Exception hiding (try,catch)
@@ -62,7 +63,7 @@
 import           Distribution.PackageDescription hiding (FlagName)
 import           Distribution.PackageDescription.Parse
 import           Distribution.Simple.Utils
-import           Distribution.System (OS, Arch, Platform (..))
+import           Distribution.System (OS (..), Arch, Platform (..))
 import           Distribution.Text (display)
 import           Distribution.Version (intersectVersionRanges)
 import           Path as FL
@@ -614,7 +615,7 @@
 mkResolveConditions ghcVersion (Platform arch os) flags = ResolveConditions
     { rcFlags = flags
     , rcGhcVersion = ghcVersion
-    , rcOS = os
+    , rcOS = if isWindows os then Windows else os
     , rcArch = arch
     }
 
diff --git a/src/Stack/PackageDump.hs b/src/Stack/PackageDump.hs
--- a/src/Stack/PackageDump.hs
+++ b/src/Stack/PackageDump.hs
@@ -378,7 +378,7 @@
     start' bs1 =
         toConsumer (valSrc =$= inner key) >>= yield >> start
       where
-        (key, bs2) = S.breakByte _colon bs1
+        (key, bs2) = S.break (== _colon) bs1
         (spaces, bs3) = S.span (== _space) $ S.drop 1 bs2
         indent = S.length key + 1 + S.length spaces
 
diff --git a/src/Stack/Setup.hs b/src/Stack/Setup.hs
--- a/src/Stack/Setup.hs
+++ b/src/Stack/Setup.hs
@@ -39,6 +39,8 @@
 import qualified Data.Set as Set
 import           Data.Text (Text)
 import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.Encoding.Error as T
 import           Data.Time.Clock (NominalDiffTime, diffUTCTime, getCurrentTime)
 import           Data.Typeable (Typeable)
 import qualified Data.Yaml as Yaml
@@ -49,13 +51,13 @@
 import           Path
 import           Path.IO
 import           Prelude -- Fix AMP warning
-import           Safe (readMay)
+import           Safe (headMay, readMay)
 import           Stack.Build.Types
-import           Stack.GhcPkg (getCabalPkgVer, getGlobalDB)
+import           Stack.GhcPkg (createDatabase, getCabalPkgVer, getGlobalDB)
 import           Stack.Solver (getGhcVersion)
 import           Stack.Types
 import           Stack.Types.StackT
-import           System.Directory (doesDirectoryExist, createDirectoryIfMissing, removeFile)
+import           System.Directory (createDirectoryIfMissing, removeFile)
 import           System.Environment (getExecutablePath)
 import           System.Exit (ExitCode (ExitSuccess))
 import           System.FilePath (searchPathSeparator)
@@ -75,11 +77,13 @@
     -- ^ Run a sanity check on the selected GHC
     , soptsSkipGhcCheck :: !Bool
     -- ^ Don't check for a compatible GHC version/architecture
+    , soptsSkipMsys :: !Bool
+    -- ^ Do not use a custom msys installation on Windows
     }
     deriving Show
 data SetupException = UnsupportedSetupCombo OS Arch
                     | MissingDependencies [String]
-                    | UnknownGHCVersion Version (Set MajorVersion)
+                    | UnknownGHCVersion Text Version (Set MajorVersion)
                     | UnknownOSKey Text
                     | GHCSanityCheckCompileFailed ReadProcessException (Path Abs File)
     deriving Typeable
@@ -93,10 +97,10 @@
     show (MissingDependencies tools) =
         "The following executables are missing and must be installed: " ++
         intercalate ", " tools
-    show (UnknownGHCVersion version known) = concat
+    show (UnknownGHCVersion oskey version known) = concat
         [ "No information found for GHC version "
         , versionString version
-        , ". Known GHC major versions: "
+        , ".\nSupported GHC major versions for OS key '" ++ T.unpack oskey ++ "': "
         , intercalate ", " (map show $ Set.toList known)
         ]
     show (UnknownOSKey oskey) =
@@ -125,6 +129,7 @@
             , soptsForceReinstall = False
             , soptsSanityCheck = False
             , soptsSkipGhcCheck = configSkipGHCCheck $ bcConfig bconfig
+            , soptsSkipMsys = configSkipMsys $ bcConfig bconfig
             }
     mghcBin <- ensureGHC sopts
     menv0 <- getMinimalEnvOverride
@@ -164,13 +169,13 @@
         localsPath = augmentPath (mkDirs' True) mpath
 
     deps <- runReaderT packageDatabaseDeps envConfig0
-    depsExists <- liftIO $ doesDirectoryExist $ toFilePath deps
+    createDatabase menv1 deps
     localdb <- runReaderT packageDatabaseLocal envConfig0
-    localdbExists <- liftIO $ doesDirectoryExist $ toFilePath localdb
+    createDatabase menv1 localdb
     globalDB <- mkEnvOverride platform env1 >>= getGlobalDB
     let mkGPP locals = T.pack $ intercalate [searchPathSeparator] $ concat
-            [ [toFilePathNoTrailingSlash localdb | locals && localdbExists]
-            , [toFilePathNoTrailingSlash deps | depsExists]
+            [ [toFilePathNoTrailingSlash localdb | locals]
+            , [toFilePathNoTrailingSlash deps]
             , [toFilePathNoTrailingSlash globalDB]
             ]
 
@@ -261,10 +266,11 @@
             config <- asks getConfig
             let tools =
                     case configPlatform config of
-                        Platform _ Windows ->
-                            [ ($(mkPackageName "ghc"), Just expected)
-                            , ($(mkPackageName "git"), Nothing)
-                            ]
+                        Platform _ os | isWindows os ->
+                              ($(mkPackageName "ghc"), Just expected)
+                            : (if soptsSkipMsys sopts
+                                then []
+                                else [($(mkPackageName "git"), Nothing)])
                         _ ->
                             [ ($(mkPackageName "ghc"), Just expected)
                             ]
@@ -282,7 +288,7 @@
                             return si
 
             installed <- runReaderT listInstalled config
-            idents <- mapM (ensureTool sopts installed getSetupInfo' msystem) tools
+            idents <- mapM (ensureTool menv0 sopts installed getSetupInfo' msystem) tools
             paths <- runReaderT (mapM binDirs $ catMaybes idents) config
             return $ Just $ map toFilePathNoTrailingSlash $ concat paths
         else return Nothing
@@ -394,11 +400,11 @@
     config <- asks getConfig
     dir <- installDir ident
     case (configPlatform config, packageNameString $ packageIdentifierName ident) of
-        (Platform _ Windows, "ghc") -> return
+        (Platform _ (isWindows -> True), "ghc") -> return
             [ dir </> $(mkRelDir "bin")
             , dir </> $(mkRelDir "mingw") </> $(mkRelDir "bin")
             ]
-        (Platform _ Windows, "git") -> return
+        (Platform _ (isWindows -> True), "git") -> return
             [ dir </> $(mkRelDir "cmd")
             , dir </> $(mkRelDir "usr") </> $(mkRelDir "bin")
             ]
@@ -410,13 +416,14 @@
             return []
 
 ensureTool :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, MonadBaseControl IO m)
-           => SetupOpts
+           => EnvOverride
+           -> SetupOpts
            -> [PackageIdentifier] -- ^ already installed
            -> m SetupInfo
            -> Maybe (Version, Arch) -- ^ installed GHC
            -> (PackageName, Maybe Version)
            -> m (Maybe PackageIdentifier)
-ensureTool sopts installed getSetupInfo' msystem (name, mversion)
+ensureTool menv sopts installed getSetupInfo' msystem (name, mversion)
     | not $ null available = return $ Just $ PackageIdentifier name $ maximum available
     | not $ soptsInstallIfMissing sopts =
         if name == $(mkPackageName "ghc")
@@ -434,7 +441,7 @@
                     let pair = siPortableGit si
                     return (pair, installGitWindows)
                 "ghc" -> do
-                    osKey <- getOSKey
+                    osKey <- getOSKey menv
                     pairs <-
                         case Map.lookup osKey $ siGHCs si of
                             Nothing -> throwM $ UnknownOSKey osKey
@@ -445,12 +452,12 @@
                             Just version -> return version
                     pair <-
                         case Map.lookup (getMajorVersion version) pairs of
-                            Nothing -> throwM $ UnknownGHCVersion version (Map.keysSet pairs)
+                            Nothing -> throwM $ UnknownGHCVersion osKey version (Map.keysSet pairs)
                             Just pair -> return pair
                     platform <- asks $ configPlatform . getConfig
                     let installer =
                             case platform of
-                                Platform _ Windows -> installGHCWindows
+                                Platform _ os | isWindows os -> installGHCWindows
                                 _ -> installGHCPosix
                     return (pair, installer)
                 x -> error $ "Invariant violated: ensureTool on " ++ x
@@ -477,12 +484,13 @@
                 getMajorVersion expected == getMajorVersion actual &&
                 actual >= expected
 
-getOSKey :: (MonadReader env m, MonadThrow m, HasConfig env) => m Text
-getOSKey = do
+getOSKey :: (MonadReader env m, MonadThrow m, HasConfig env, MonadLogger m, MonadIO m, MonadCatch m, MonadBaseControl IO m)
+         => EnvOverride -> m Text
+getOSKey menv = do
     platform <- asks $ configPlatform . getConfig
     case platform of
-        Platform I386 Linux -> return "linux32"
-        Platform X86_64 Linux -> return "linux64"
+        Platform I386 Linux -> ("linux32" <>) <$> getLinuxSuffix
+        Platform X86_64 Linux -> ("linux64" <>) <$> getLinuxSuffix
         Platform I386 OSX -> return "macosx"
         Platform X86_64 OSX -> return "macosx"
         Platform I386 FreeBSD -> return "freebsd32"
@@ -491,7 +499,20 @@
         Platform X86_64 OpenBSD -> return "openbsd64"
         Platform I386 Windows -> return "windows32"
         Platform X86_64 Windows -> return "windows64"
+
+        Platform I386 (OtherOS "windowsintegersimple") -> return "windowsintegersimple32"
+        Platform X86_64 (OtherOS "windowsintegersimple") -> return "windowsintegersimple64"
+
         Platform arch os -> throwM $ UnsupportedSetupCombo os arch
+  where
+    getLinuxSuffix = do
+        executablePath <- liftIO getExecutablePath
+        elddOut <- tryProcessStdout Nothing menv "ldd" [executablePath]
+        return $ case elddOut of
+            Left _ -> ""
+            Right lddOut -> if hasLineWithFirstWord "libgmp.so.3" lddOut then "-gmp4" else ""
+    hasLineWithFirstWord w =
+      elem (Just w) . map (headMay . T.words) . T.lines . T.decodeUtf8With T.lenientDecode
 
 downloadPair :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, MonadBaseControl IO m)
              => DownloadPair
@@ -581,11 +602,13 @@
                   -> PackageIdentifier
                   -> m ()
 installGHCWindows si archiveFile archiveType destDir _ = do
-    case archiveType of
-        TarXz -> return ()
-        _ -> error $ "GHC on Windows must be a .tar.xz file"
+    suffix <-
+        case archiveType of
+            TarXz -> return ".xz"
+            TarBz2 -> return ".bz2"
+            _ -> error $ "GHC on Windows must be a tarball file"
     tarFile <-
-        case T.stripSuffix ".xz" $ T.pack $ toFilePath archiveFile of
+        case T.stripSuffix suffix $ T.pack $ toFilePath archiveFile of
             Nothing -> error $ "Invalid GHC filename: " ++ show archiveFile
             Just x -> parseAbsFile $ T.unpack x
 
@@ -729,7 +752,10 @@
         ]
     ghc <- join $ findExecutable menv "ghc"
     $logDebug $ "Performing a sanity check on: " <> T.pack (toFilePath ghc)
-    eres <- tryProcessStdout (Just dir') menv "ghc" [fp]
+    eres <- tryProcessStdout (Just dir') menv "ghc"
+        [ fp
+        , "-no-user-package-db"
+        ]
     case eres of
         Left e -> throwM $ GHCSanityCheckCompileFailed e ghc
         Right _ -> return () -- TODO check that the output of running the command is correct
diff --git a/src/Stack/Types/BuildPlan.hs b/src/Stack/Types/BuildPlan.hs
--- a/src/Stack/Types/BuildPlan.hs
+++ b/src/Stack/Types/BuildPlan.hs
@@ -22,6 +22,7 @@
     , MiniPackageInfo (..)
     , renderSnapName
     , parseSnapName
+    , isWindows
     ) where
 
 import           Control.Applicative
@@ -51,7 +52,7 @@
 import qualified Data.Traversable                as T
 import           Data.Typeable                   (TypeRep, Typeable, typeOf)
 import           Data.Vector                     (Vector)
-import           Distribution.System             (Arch, OS)
+import           Distribution.System             (Arch, OS (..))
 import qualified Distribution.Text               as DT
 import qualified Distribution.Version            as C
 import           GHC.Generics                    (Generic)
@@ -386,3 +387,9 @@
     }
     deriving (Generic, Show, Eq)
 instance Binary.Binary MiniPackageInfo
+
+
+isWindows :: OS -> Bool
+isWindows Windows = True
+isWindows (OtherOS "windowsintegersimple") = True
+isWindows _ = False
diff --git a/src/Stack/Types/Config.hs b/src/Stack/Types/Config.hs
--- a/src/Stack/Types/Config.hs
+++ b/src/Stack/Types/Config.hs
@@ -87,6 +87,8 @@
          -- version is available? Can be overridden by command line options.
          ,configSkipGHCCheck        :: !Bool
          -- ^ Don't bother checking the GHC version or architecture.
+         ,configSkipMsys            :: !Bool
+         -- ^ On Windows: don't use a locally installed MSYS
          ,configLocalBin            :: !(Path Abs Dir)
          -- ^ Directory we should install executables into
          ,configRequireStackVersion :: !VersionRange
@@ -99,6 +101,8 @@
          -- ^ --extra-lib-dirs arguments
          ,configConfigMonoid        :: !ConfigMonoid
          -- ^ @ConfigMonoid@ used to generate this
+         ,configConcurrentTests     :: !Bool
+         -- ^ Run test suites concurrently
          }
 
 -- | Information on a single package index
@@ -416,6 +420,8 @@
     -- ^ See: 'configInstallGHC'
     ,configMonoidSkipGHCCheck        :: !(Maybe Bool)
     -- ^ See: 'configSkipGHCCheck'
+    ,configMonoidSkipMsys            :: !(Maybe Bool)
+    -- ^ See: 'configSkipMsys'
     ,configMonoidRequireStackVersion :: !VersionRange
     -- ^ See: 'configRequireStackVersion'
     ,configMonoidOS                  :: !(Maybe String)
@@ -428,6 +434,8 @@
     -- ^ See: 'configExtraIncludeDirs'
     ,configMonoidExtraLibDirs        :: !(Set Text)
     -- ^ See: 'configExtraLibDirs'
+    ,configMonoidConcurrentTests     :: !(Maybe Bool)
+    -- ^ See: 'configConcurrentTests'
     }
   deriving Show
 
@@ -441,12 +449,14 @@
     , configMonoidSystemGHC = Nothing
     , configMonoidInstallGHC = Nothing
     , configMonoidSkipGHCCheck = Nothing
+    , configMonoidSkipMsys = Nothing
     , configMonoidRequireStackVersion = anyVersion
     , configMonoidOS = Nothing
     , configMonoidArch = Nothing
     , configMonoidJobs = Nothing
     , configMonoidExtraIncludeDirs = Set.empty
     , configMonoidExtraLibDirs = Set.empty
+    , configMonoidConcurrentTests = Nothing
     }
   mappend l r = ConfigMonoid
     { configMonoidDockerOpts = configMonoidDockerOpts l <> configMonoidDockerOpts r
@@ -457,6 +467,7 @@
     , configMonoidSystemGHC = configMonoidSystemGHC l <|> configMonoidSystemGHC r
     , configMonoidInstallGHC = configMonoidInstallGHC l <|> configMonoidInstallGHC r
     , configMonoidSkipGHCCheck = configMonoidSkipGHCCheck l <|> configMonoidSkipGHCCheck r
+    , configMonoidSkipMsys = configMonoidSkipMsys l <|> configMonoidSkipMsys r
     , configMonoidRequireStackVersion = intersectVersionRanges (configMonoidRequireStackVersion l)
                                                                (configMonoidRequireStackVersion r)
     , configMonoidOS = configMonoidOS l <|> configMonoidOS r
@@ -464,6 +475,7 @@
     , configMonoidJobs = configMonoidJobs l <|> configMonoidJobs r
     , configMonoidExtraIncludeDirs = Set.union (configMonoidExtraIncludeDirs l) (configMonoidExtraIncludeDirs r)
     , configMonoidExtraLibDirs = Set.union (configMonoidExtraLibDirs l) (configMonoidExtraLibDirs r)
+    , configMonoidConcurrentTests = configMonoidConcurrentTests l <|> configMonoidConcurrentTests r
     }
 
 instance FromJSON ConfigMonoid where
@@ -478,6 +490,7 @@
          configMonoidSystemGHC <- obj .:? "system-ghc"
          configMonoidInstallGHC <- obj .:? "install-ghc"
          configMonoidSkipGHCCheck <- obj .:? "skip-ghc-check"
+         configMonoidSkipMsys <- obj .:? "skip-msys"
          configMonoidRequireStackVersion <- unVersionRangeJSON <$>
                                             obj .:? "require-stack-version"
                                                 .!= VersionRangeJSON anyVersion
@@ -486,6 +499,7 @@
          configMonoidJobs <- obj .:? "jobs"
          configMonoidExtraIncludeDirs <- obj .:? "extra-include-dirs" .!= Set.empty
          configMonoidExtraLibDirs <- obj .:? "extra-lib-dirs" .!= Set.empty
+         configMonoidConcurrentTests <- obj .:? "concurrent-tests"
          return ConfigMonoid {..}
 
 -- | Newtype for non-orphan FromJSON instance.
diff --git a/src/Stack/Upgrade.hs b/src/Stack/Upgrade.hs
--- a/src/Stack/Upgrade.hs
+++ b/src/Stack/Upgrade.hs
@@ -7,12 +7,14 @@
 import           Control.Monad.Catch
 import           Control.Monad.IO.Class
 import           Control.Monad.Logger
-import           Control.Monad.Reader
+import           Control.Monad.Reader        (MonadReader, asks)
 import           Control.Monad.Trans.Control
+import           Data.Foldable               (forM_)
 import qualified Data.Map                    as Map
 import qualified Data.Set                    as Set
 import           Network.HTTP.Client.Conduit (HasHttpManager, getHttpManager)
 import           Path
+import qualified Paths_stack as Paths
 import           Stack.Build
 import           Stack.Build.Types
 import           Stack.Config
@@ -32,7 +34,7 @@
 upgrade fromGit mresolver = withSystemTempDirectory "stack-upgrade" $ \tmp' -> do
     menv <- getMinimalEnvOverride
     tmp <- parseAbsDir tmp'
-    dir <-
+    mdir <-
         if fromGit
             then do
                 $logInfo "Cloning stack"
@@ -44,48 +46,45 @@
                     , "1"
                     ]
                     Nothing
-                return $ tmp </> $(mkRelDir "stack")
+                return $ Just $ tmp </> $(mkRelDir "stack")
             else do
                 updateAllIndices menv
                 caches <- getPackageCaches menv
                 let latest = Map.fromListWith max
                            $ map toTuple
-                           $ Map.keys caches
+                           $ Map.keys
+
+                           -- Mistaken upload to Hackage, just ignore it
+                           $ Map.delete (PackageIdentifier
+                                $(mkPackageName "stack")
+                                $(mkVersion "9.9.9"))
+
+                             caches
                 case Map.lookup $(mkPackageName "stack") latest of
                     Nothing -> error "No stack found in package indices"
+                    Just version | version <= fromCabalVersion Paths.version -> do
+                        $logInfo "Already at latest version, no upgrade required"
+                        return Nothing
                     Just version -> do
                         let ident = PackageIdentifier $(mkPackageName "stack") version
                         paths <- unpackPackageIdents menv tmp Nothing $ Set.singleton ident
                         case Map.lookup ident paths of
                             Nothing -> error "Stack.Upgrade.upgrade: invariant violated, unpacked directory not found"
-                            Just path -> return path
+                            Just path -> return $ Just path
 
     manager <- asks getHttpManager
     logLevel <- asks getLogLevel
     terminal <- asks getTerminal
     configMonoid <- asks $ configConfigMonoid . getConfig
 
-    liftIO $ do
+    forM_ mdir $ \dir -> liftIO $ do
         bconfig <- runStackLoggingT manager logLevel terminal $ do
             lc <- loadConfig
                 configMonoid
                 (Just $ dir </> $(mkRelFile "stack.yaml"))
             lcLoadBuildConfig lc mresolver ThrowException
         envConfig1 <- runStackT manager logLevel bconfig terminal setupEnv
-        runStackT manager logLevel envConfig1 terminal $ build BuildOpts
+        runStackT manager logLevel envConfig1 terminal $ build (const $ return ()) defaultBuildOpts
             { boptsTargets = ["stack"]
-            , boptsLibProfile = False
-            , boptsExeProfile = False
-            , boptsEnableOptimizations = Nothing
-            , boptsHaddock = False
-            , boptsHaddockDeps = Nothing
-            , boptsFinalAction = DoNothing
-            , boptsDryrun = False
-            , boptsGhcOptions = []
-            , boptsFlags = Map.empty
             , boptsInstallExes = True
-            , boptsPreFetch = False
-            , boptsTestArgs = []
-            , boptsOnlySnapshot = False
-            , boptsCoverage = False
             }
diff --git a/src/System/Process/Read.hs b/src/System/Process/Read.hs
--- a/src/System/Process/Read.hs
+++ b/src/System/Process/Read.hs
@@ -57,7 +57,7 @@
 import qualified Data.Text.Lazy.Encoding as LT
 import qualified Data.Text.Lazy as LT
 import           Data.Typeable (Typeable)
-import           Distribution.System (OS (Windows), Platform (Platform))
+import           Distribution.System (OS (Windows, OtherOS), Platform (Platform))
 import           Path (Path, Abs, Dir, toFilePath, File, parseAbsFile)
 import           Prelude -- Fix AMP warning
 import           System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory)
@@ -108,6 +108,7 @@
     isWindows =
         case platform of
             Platform _ Windows -> True
+            Platform _ (OtherOS "windowsintegersimple") -> True
             _ -> False
 
 -- | Helper conversion function
diff --git a/src/main/Main.hs b/src/main/Main.hs
--- a/src/main/Main.hs
+++ b/src/main/Main.hs
@@ -9,14 +9,12 @@
 
 module Main where
 
-import           Blaze.ByteString.Builder (toLazyByteString, copyByteString)
-import           Blaze.ByteString.Builder.Char.Utf8 (fromShow)
 import           Control.Exception
 import           Control.Monad
 import           Control.Monad.IO.Class
 import           Control.Monad.Logger
 import           Control.Monad.Reader (ask)
-import qualified Data.ByteString.Lazy as L
+import           Data.Attoparsec.Args (withInterpreterArgs)
 import           Data.Char (toLower)
 import           Data.List
 import qualified Data.List as List
@@ -46,10 +44,12 @@
 import           Stack.Dot
 import           Stack.Exec
 import           Stack.Fetch
+import           Stack.FileWatch
 import           Stack.Init
 import           Stack.New
 import qualified Stack.PackageIndex
 import           Stack.Repl
+import           Stack.Ide
 import           Stack.Setup
 import           Stack.Solver (solveExtraDeps)
 import           Stack.Types
@@ -60,13 +60,13 @@
 import           System.Directory (canonicalizePath)
 import           System.Environment (getArgs, getProgName)
 import           System.Exit
-import           System.FilePath (searchPathSeparator)
+import           System.FilePath (searchPathSeparator,dropTrailingPathSeparator)
 import           System.IO (hIsTerminalDevice, stderr, stdin, stdout, hSetBuffering, BufferMode(..))
 import           System.Process.Read
 
 -- | Commandline dispatcher.
 main :: IO ()
-main =
+main = withInterpreterArgs stackProgName $ \args isInterpreter ->
   do -- Line buffer the output by default, particularly for non-terminal runs.
      -- See https://github.com/commercialhaskell/stack/pull/360
      hSetBuffering stdout LineBuffering
@@ -76,14 +76,13 @@
        plugins <- findPlugins (T.pack stackProgName)
        tryRunPlugin plugins
      progName <- getProgName
-     args <- getArgs
      isTerminal <- hIsTerminalDevice stdout
      execExtraHelp args
                    dockerHelpOptName
                    (Docker.dockerOptsParser True)
                    ("Only showing --" ++ Docker.dockerCmdName ++ "* options.")
      let versionString' = $(simpleVersion Meta.version)
-     (level,run) <-
+     eGlobalRun <- try $
        simpleOptions
          versionString'
          "stack - The Haskell Tool Stack"
@@ -100,8 +99,13 @@
                         (buildOpts Build)
              addCommand "test"
                         "Build and test the project(s) in this directory/configuration"
-                        (buildCmd DoTests)
-                        (buildOpts Test)
+                        (\(rerun, bopts) -> buildCmd (DoTests rerun) bopts)
+                        ((,)
+                            <$> boolFlags True
+                                "rerun-tests"
+                                "running already successful tests"
+                                idm
+                            <*> (buildOpts Test))
              addCommand "bench"
                         "Build and benchmark the project(s) in this directory/configuration"
                         (buildCmd DoBenchmarks)
@@ -161,30 +165,15 @@
              addCommand "dot"
                         "Visualize your project's dependency graph using Graphviz dot"
                         dotCmd
-                        (pure ())
+                        dotOptsParser
              addCommand "exec"
                         "Execute a command"
                         execCmd
-                        ((,,)
-                            <$> strArgument (metavar "CMD")
-                            <*> many (strArgument (metavar "-- ARGS (e.g. stack exec -- ghc --version)"))
-                            <*> (EnvSettings
-                                    <$> pure True
-                                    <*> boolFlags True
-                                            "ghc-package-path"
-                                            "setting the GHC_PACKAGE_PATH variable for the subprocess"
-                                            idm
-                                    <*> boolFlags True
-                                            "stack-exe"
-                                            "setting the STACK_EXE environment variable to the path for the stack executable"
-                                            idm))
+                        (execOptsParser Nothing)
              addCommand "ghc"
                         "Run ghc"
                         execCmd
-                        ((,,)
-                            <$> pure "ghc"
-                            <*> many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)"))
-                            <*> pure defaultEnvSettings)
+                        (execOptsParser $ Just "ghc")
              addCommand "ghci"
                         "Run ghci in the context of project(s)"
                         replCmd
@@ -203,13 +192,22 @@
                                                     help "Use this command for the GHC to run"))) <*>
                          flag False True (long "no-load" <>
                                          help "Don't load modules on start-up"))
+             addCommand "ide"
+                        "Run ide-backend-client with the correct arguments"
+                        ideCmd
+                        ((,) <$>
+                         fmap (map T.pack)
+                              (many (strArgument
+                                       (metavar "TARGET" <>
+                                        help "If none specified, use all packages defined in current directory"))) <*>
+                         fmap (fromMaybe [])
+                              (optional (argsOption (long "ghc-options" <>
+                                                     metavar "OPTION" <>
+                                                     help "Additional options passed to GHCi"))))
              addCommand "runghc"
                         "Run runghc"
                         execCmd
-                        ((,,)
-                            <$> pure "runghc"
-                            <*> many (strArgument (metavar "-- ARGS (e.g. stack runghc -- X.hs)"))
-                            <*> pure defaultEnvSettings)
+                        (execOptsParser $ Just "runghc")
              addCommand "clean"
                         "Clean the local packages"
                         cleanCmd
@@ -229,23 +227,30 @@
                    addCommand Docker.dockerCleanupCmdName
                               "Clean up Docker images and containers"
                               dockerCleanupCmd
-                              dockerCleanupOpts
-                   addCommand "exec"
-                              "Execute a command in a Docker container without setting up Haskell environment first"
-                              dockerExecCmd
-                              ((,) <$> strArgument (metavar "[--] CMD")
-                                   <*> many (strArgument (metavar "ARGS"))))
+                              dockerCleanupOpts)
              )
              -- commandsFromPlugins plugins pluginShouldHaveRun) https://github.com/commercialhaskell/stack/issues/322
-     when (globalLogLevel level == LevelDebug) $ putStrLn versionString'
-     run level `catch` \e -> do
-        -- This special handler stops "stack: " from being printed before the
-        -- exception
-        case fromException e of
-            Just ec -> exitWith ec
-            Nothing -> do
-                L.hPut stderr $ toLazyByteString $ fromShow e <> copyByteString "\n"
-                exitFailure
+     case eGlobalRun of
+       Left (exitCode :: ExitCode) -> do
+         when isInterpreter $
+           putStrLn $ concat
+             [ "\nIf you are trying to use "
+             , stackProgName
+             , " as a script interpreter, a\n'-- "
+             , stackProgName
+             , " [options] runghc [options]' comment is required."
+             , "\nSee https://github.com/commercialhaskell/stack/wiki/Script-interpreter" ]
+         throwIO exitCode
+       Right (global,run) -> do
+         when (globalLogLevel global == LevelDebug) $ putStrLn versionString'
+         run global `catch` \e -> do
+            -- This special handler stops "stack: " from being printed before the
+            -- exception
+            case fromException e of
+                Just ec -> exitWith ec
+                Nothing -> do
+                    printExceptionStderr e
+                    exitFailure
   where
     dockerHelpOptName = Docker.dockerCmdName ++ "-help"
 
@@ -326,21 +331,22 @@
 -- really it's mainly for the documentation aspect.
 --
 -- When printing output we generate @PathInfo@ and pass it to the
--- function to generate an appropriate string.
+-- function to generate an appropriate string.  Trailing slashes are
+-- removed, see #506
 paths :: [(String, Text, PathInfo -> Text)]
 paths =
     [ ( "Global stack root directory"
       , "global-stack-root"
       , \pi ->
-             T.pack (toFilePath (configStackRoot (bcConfig (piBuildConfig pi)))))
+             T.pack (toFilePathNoTrailing (configStackRoot (bcConfig (piBuildConfig pi)))))
     , ( "Project root (derived from stack.yaml file)"
       , "project-root"
       , \pi ->
-             T.pack (toFilePath (bcRoot (piBuildConfig pi))))
+             T.pack (toFilePathNoTrailing (bcRoot (piBuildConfig pi))))
     , ( "Configuration location (where the stack.yaml file is)"
       , "config-location"
       , \pi ->
-             T.pack (toFilePath (bcStackYaml (piBuildConfig pi))))
+             T.pack (toFilePathNoTrailing (bcStackYaml (piBuildConfig pi))))
     , ( "PATH environment variable"
       , "bin-path"
       , \pi ->
@@ -348,11 +354,11 @@
     , ( "Installed GHCs (unpacked and archives)"
       , "ghc-paths"
       , \pi ->
-             T.pack (toFilePath (configLocalPrograms (bcConfig (piBuildConfig pi)))))
+             T.pack (toFilePathNoTrailing (configLocalPrograms (bcConfig (piBuildConfig pi)))))
     , ( "Local bin path where stack installs executables"
       , "local-bin-path"
       , \pi ->
-             T.pack (toFilePath (configLocalBin (bcConfig (piBuildConfig pi)))))
+             T.pack (toFilePathNoTrailing (configLocalBin (bcConfig (piBuildConfig pi)))))
     , ( "Extra include directories"
       , "extra-include-dirs"
       , \pi ->
@@ -366,23 +372,24 @@
     , ( "Snapshot package database"
       , "snapshot-pkg-db"
       , \pi ->
-             T.pack (toFilePath (piSnapDb pi)))
+             T.pack (toFilePathNoTrailing (piSnapDb pi)))
     , ( "Local project package database"
       , "local-pkg-db"
       , \pi ->
-             T.pack (toFilePath (piLocalDb pi)))
+             T.pack (toFilePathNoTrailing (piLocalDb pi)))
     , ( "Snapshot installation root"
       , "snapshot-install-root"
       , \pi ->
-             T.pack (toFilePath (piSnapRoot pi)))
+             T.pack (toFilePathNoTrailing (piSnapRoot pi)))
     , ( "Local project installation root"
       , "local-install-root"
       , \pi ->
-             T.pack (toFilePath (piLocalRoot pi)))
+             T.pack (toFilePathNoTrailing (piLocalRoot pi)))
     , ( "Dist work directory"
       , "dist-dir"
       , \pi ->
-             T.pack (toFilePath (piDistDir pi)))]
+             T.pack (toFilePathNoTrailing (piDistDir pi)))]
+  where toFilePathNoTrailing = dropTrailingPathSeparator . toFilePath
 
 data SetupCmdOpts = SetupCmdOpts
     { scoGhcVersion :: !(Maybe Version)
@@ -407,7 +414,7 @@
 setupCmd SetupCmdOpts{..} go@GlobalOpts{..} = do
   (manager,lc) <- loadConfigWithOpts go
   runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
-      Docker.rerunWithOptionalContainer
+      Docker.reexecWithOptionalContainer
           (lcProjectRoot lc)
           (runStackLoggingT manager globalLogLevel globalTerminal $ do
               (ghc, mstack) <-
@@ -426,6 +433,7 @@
                   , soptsForceReinstall = scoForceReinstall
                   , soptsSanityCheck = True
                   , soptsSkipGhcCheck = False
+                  , soptsSkipMsys = configSkipMsys $ lcConfig lc
                   }
               case mpaths of
                   Nothing -> $logInfo "GHC on PATH would be used"
@@ -439,7 +447,7 @@
 withConfig go@GlobalOpts{..} inner = do
     (manager, lc) <- loadConfigWithOpts go
     runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
-        Docker.rerunWithOptionalContainer (lcProjectRoot lc) $
+        Docker.reexecWithOptionalContainer (lcProjectRoot lc) $
             runStackT manager globalLogLevel (lcConfig lc) globalTerminal
                 inner
 
@@ -450,7 +458,7 @@
 withBuildConfig go@GlobalOpts{..} strat inner = do
     (manager, lc) <- loadConfigWithOpts go
     runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
-        Docker.rerunWithOptionalContainer (lcProjectRoot lc) $ do
+        Docker.reexecWithOptionalContainer (lcProjectRoot lc) $ do
             bconfig <- runStackLoggingT manager globalLogLevel globalTerminal $
                 lcLoadBuildConfig lc globalResolver strat
             envConfig <-
@@ -498,15 +506,23 @@
             return $ Map.singleton pn' $ Map.singleton flagN b
         _ -> readerError "Must have a colon"
 
+-- | Helper for build and install commands
+buildCmdHelper :: NoBuildConfigStrategy -> FinalAction -> BuildOpts -> GlobalOpts -> IO ()
+buildCmdHelper strat finalAction opts go
+    | boptsFileWatch opts = fileWatch inner
+    | otherwise = inner $ const $ return ()
+  where
+    inner setLocalFiles =
+        withBuildConfig go strat $
+        Stack.Build.build setLocalFiles opts { boptsFinalAction = finalAction }
+
 -- | Build the project.
 buildCmd :: FinalAction -> BuildOpts -> GlobalOpts -> IO ()
-buildCmd finalAction opts go@GlobalOpts{..} = withBuildConfig go ThrowException $
-    Stack.Build.build opts { boptsFinalAction = finalAction }
+buildCmd = buildCmdHelper ThrowException
 
 -- | Install
 installCmd :: BuildOpts -> GlobalOpts -> IO ()
-installCmd opts go@GlobalOpts{..} = withBuildConfig go ExecStrategy $
-    Stack.Build.build opts { boptsInstallExes = True }
+installCmd opts = buildCmdHelper ExecStrategy DoNothing opts { boptsInstallExes = True }
 
 -- | Unpack packages to the filesystem
 unpackCmd :: [String] -> GlobalOpts -> IO ()
@@ -525,28 +541,99 @@
 
 -- | Upload to Hackage
 uploadCmd :: [String] -> GlobalOpts -> IO ()
-uploadCmd args0 go = do
+uploadCmd args go = do
     (manager,lc) <- loadConfigWithOpts go
     let config = lcConfig lc
-        args = if null args0 then ["."] else args0
-    liftIO $ do
-        uploader <- Upload.mkUploader
-              config
-            $ Upload.setGetManager (return manager)
-              Upload.defaultUploadSettings
-        mapM_ (Upload.upload uploader) args
+    if null args
+        then error "To upload the current project, please run 'stack upload .'"
+        else liftIO $ do
+            uploader <- Upload.mkUploader
+                  config
+                $ Upload.setGetManager (return manager)
+                  Upload.defaultUploadSettings
+            mapM_ (Upload.upload uploader) args
 
+data ExecOpts = ExecOpts
+    { eoCmd :: !String
+    , eoArgs :: ![String]
+    , eoExtra :: !ExecOptsExtra
+    }
+
+data ExecOptsExtra
+    = ExecOptsPlain
+    | ExecOptsEmbellished
+        { eoEnvSettings :: !EnvSettings
+        , eoPackages :: ![String]
+        }
+
+execOptsParser :: Maybe String -- ^ command
+               -> Parser ExecOpts
+execOptsParser mcmd =
+    ExecOpts
+        <$> maybe eoCmdParser pure mcmd
+        <*> eoArgsParser
+        <*> (eoPlainParser <|>
+             ExecOptsEmbellished
+                <$> eoEnvSettingsParser
+                <*> eoPackagesParser)
+  where
+    eoCmdParser :: Parser String
+    eoCmdParser = strArgument (metavar "CMD")
+
+    eoArgsParser :: Parser [String]
+    eoArgsParser = many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)"))
+
+    eoEnvSettingsParser :: Parser EnvSettings
+    eoEnvSettingsParser = EnvSettings
+        <$> pure True
+        <*> boolFlags True
+                "ghc-package-path"
+                "setting the GHC_PACKAGE_PATH variable for the subprocess"
+                idm
+        <*> boolFlags True
+                "stack-exe"
+                "setting the STACK_EXE environment variable to the path for the stack executable"
+                idm
+
+    eoPackagesParser :: Parser [String]
+    eoPackagesParser = many (strOption (long "package" <> help "Additional packages that must be installed"))
+
+    eoPlainParser :: Parser ExecOptsExtra
+    eoPlainParser = flag' ExecOptsPlain
+                          (long "plain" <>
+                           help "Use an unmodified environment (only useful with Docker)")
+
 -- | Execute a command.
-execCmd :: (String, [String],EnvSettings) -> GlobalOpts -> IO ()
-execCmd (cmd,args,envSettings) go@GlobalOpts{..} =
-    withBuildConfig go ExecStrategy $
-    exec envSettings cmd args
+execCmd :: ExecOpts -> GlobalOpts -> IO ()
+execCmd ExecOpts {..} go@GlobalOpts{..} =
+    case eoExtra of
+        ExecOptsPlain -> do
+            (manager,lc) <- liftIO $ loadConfigWithOpts go
+            runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
+                Docker.execWithOptionalContainer
+                    (lcProjectRoot lc)
+                    (return (eoCmd, eoArgs, id)) $
+                    runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
+                        exec plainEnvSettings eoCmd eoArgs
+        ExecOptsEmbellished {..} ->
+           withBuildConfig go ExecStrategy $ do
+               let targets = concatMap words eoPackages
+               unless (null targets) $
+                   Stack.Build.build (const $ return ()) defaultBuildOpts
+                       { boptsTargets = map T.pack targets
+                       }
+               exec eoEnvSettings eoCmd eoArgs
 
--- | Run the REPL in the context of a project, with
+-- | Run the REPL in the context of a project.
 replCmd :: ([Text], [String], FilePath, Bool) -> GlobalOpts -> IO ()
 replCmd (targets,args,path,noload) go@GlobalOpts{..} = withBuildConfig go ExecStrategy $ do
       repl targets args path noload
 
+-- | Run ide-backend in the context of a project.
+ideCmd :: ([Text], [String]) -> GlobalOpts -> IO ()
+ideCmd (targets,args) go@GlobalOpts{..} = withBuildConfig go ExecStrategy $ do
+      ide targets args
+
 -- | Pull the current Docker image.
 dockerPullCmd :: () -> GlobalOpts -> IO ()
 dockerPullCmd _ go@GlobalOpts{..} = do
@@ -569,15 +656,6 @@
         Docker.preventInContainer $
             Docker.cleanup cleanupOpts
 
--- | Execute a command
-dockerExecCmd :: (String, [String]) -> GlobalOpts -> IO ()
-dockerExecCmd (cmd,args) go@GlobalOpts{..} = do
-    (manager,lc) <- liftIO $ loadConfigWithOpts go
-    runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
-        Docker.preventInContainer $
-            Docker.rerunCmdWithRequiredContainer (lcProjectRoot lc)
-                                                 (return (cmd,args,id))
-
 -- | Command sum type for conditional arguments.
 data Command
     = Build
@@ -590,7 +668,8 @@
 buildOpts cmd = fmap process $
             BuildOpts <$> target <*> libProfiling <*> exeProfiling <*>
             optimize <*> haddock <*> haddockDeps <*> finalAction <*> dryRun <*> ghcOpts <*>
-            flags <*> installExes <*> preFetch <*> testArgs <*> onlySnapshot <*> coverage
+            flags <*> installExes <*> preFetch <*> testArgs <*> onlySnapshot <*> coverage <*>
+            fileWatch' <*> keepGoing
   where process bopts =
             if boptsCoverage bopts
                then bopts { boptsExeProfile = True
@@ -670,6 +749,15 @@
                          help "Generate a code coverage report")
                else pure False
 
+        fileWatch' = flag False True
+            (long "file-watch" <>
+             help "Watch for changes in local files and automatically rebuild")
+
+        keepGoing = maybeBoolFlags
+            "keep-going"
+            "continue running after a step fails (default: false for build, true for test/bench)"
+            idm
+
 -- | Parser for docker cleanup arguments.
 dockerCleanupOpts :: Parser Docker.CleanupOpts
 dockerCleanupOpts =
@@ -822,5 +910,5 @@
     idm
 
 -- | Visualize dependencies
-dotCmd :: () -> GlobalOpts -> IO ()
-dotCmd () go = withBuildConfig go ThrowException dot
+dotCmd :: DotOpts -> GlobalOpts -> IO ()
+dotCmd dotOpts go = withBuildConfig go ThrowException (dot dotOpts)
diff --git a/src/test/Network/HTTP/Download/VerifiedSpec.hs b/src/test/Network/HTTP/Download/VerifiedSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/test/Network/HTTP/Download/VerifiedSpec.hs
@@ -0,0 +1,162 @@
+{-# LANGUAGE RecordWildCards #-}
+module Network.HTTP.Download.VerifiedSpec where
+
+import Crypto.Hash
+import Control.Monad (unless)
+import Control.Monad.Trans.Reader
+import Data.Maybe
+import Network.HTTP.Client.Conduit
+import Network.HTTP.Download.Verified
+import Path
+import System.Directory
+import System.IO.Temp
+import Test.Hspec hiding (shouldNotBe, shouldNotReturn)
+
+
+-- TODO: share across test files
+withTempDir :: (Path Abs Dir -> IO a) -> IO a
+withTempDir f = withSystemTempDirectory "NHD_VerifiedSpec" $ \dirFp -> do
+  dir <- parseAbsDir dirFp
+  f dir
+
+
+-- | An example path to download the exampleReq.
+getExamplePath :: Path Abs Dir -> IO (Path Abs File)
+getExamplePath dir = do
+    file <- parseRelFile "cabal-install-1.22.4.0.tar.gz"
+    return (dir </> file)
+
+-- | An example DownloadRequest that uses a SHA1
+exampleReq :: DownloadRequest
+exampleReq = fromMaybe (error "exampleReq") $ do
+    req <- parseUrl "http://download.fpcomplete.com/stackage-cli/linux64/cabal-install-1.22.4.0.tar.gz"
+    return DownloadRequest
+        { drRequest = req
+        , drHashChecks = [exampleHashCheck]
+        , drLengthCheck = Just exampleLengthCheck
+        }
+
+exampleHashCheck :: HashCheck
+exampleHashCheck = HashCheck
+    { hashCheckAlgorithm = SHA1
+    , hashCheckHexDigest = CheckHexDigestString "b98eea96d321cdeed83a201c192dac116e786ec2"
+    }
+
+exampleLengthCheck :: LengthCheck
+exampleLengthCheck = 302513
+
+-- | The wrong ContentLength for exampleReq
+exampleWrongContentLength :: Int
+exampleWrongContentLength = 302512
+
+-- | The wrong SHA1 digest for exampleReq
+exampleWrongDigest :: CheckHexDigest
+exampleWrongDigest = CheckHexDigestString "b98eea96d321cdeed83a201c192dac116e786ec3"
+
+exampleWrongContent :: String
+exampleWrongContent = "example wrong content"
+
+isWrongContentLength :: VerifiedDownloadException -> Bool
+isWrongContentLength WrongContentLength{} = True
+isWrongContentLength _ = False
+
+isWrongDigest :: VerifiedDownloadException -> Bool
+isWrongDigest WrongDigest{} = True
+isWrongDigest _ = False
+
+data T = T
+  { manager :: Manager
+  }
+
+runWith :: Manager -> ReaderT Manager m r -> m r
+runWith = flip runReaderT
+
+setup :: IO T
+setup = do
+  manager <- newManager
+  return T{..}
+
+teardown :: T -> IO ()
+teardown _ = return ()
+
+shouldNotBe :: (Show a, Eq a) => a -> a -> Expectation
+actual `shouldNotBe` expected =
+    unless (actual /= expected) (expectationFailure msg)
+  where
+    msg = "Value was exactly what it shouldn't be: " ++ show expected
+
+shouldNotReturn :: (Show a, Eq a) => IO a -> a -> Expectation
+action `shouldNotReturn` unexpected = action >>= (`shouldNotBe` unexpected)
+
+spec :: Spec
+spec = beforeAll setup $ afterAll teardown $ do
+  let exampleProgressHook = return ()
+
+  describe "verifiedDownload" $ do
+    -- Preconditions:
+    -- * the exampleReq server is running
+    -- * the test runner has working internet access to it
+    it "downloads the file correctly" $ \T{..} -> withTempDir $ \dir -> do
+      examplePath <- getExamplePath dir
+      let exampleFilePath = toFilePath examplePath
+      doesFileExist exampleFilePath `shouldReturn` False
+      let go = runWith manager $ verifiedDownload exampleReq examplePath exampleProgressHook
+      go `shouldReturn` True
+      doesFileExist exampleFilePath `shouldReturn` True
+
+    it "is idempotent, and doesn't redownload unnecessarily" $ \T{..} -> withTempDir $ \dir -> do
+      examplePath <- getExamplePath dir
+      let exampleFilePath = toFilePath examplePath
+      doesFileExist exampleFilePath `shouldReturn` False
+      let go = runWith manager $ verifiedDownload exampleReq examplePath exampleProgressHook
+      go `shouldReturn` True
+      doesFileExist exampleFilePath `shouldReturn` True
+      go `shouldReturn` False
+      doesFileExist exampleFilePath `shouldReturn` True
+
+    -- https://github.com/commercialhaskell/stack/issues/372
+    it "does redownload when the destination file is wrong" $ \T{..} -> withTempDir $ \dir -> do
+      examplePath <- getExamplePath dir
+      let exampleFilePath = toFilePath examplePath
+      writeFile exampleFilePath exampleWrongContent
+      doesFileExist exampleFilePath `shouldReturn` True
+      readFile exampleFilePath `shouldReturn` exampleWrongContent
+      let go = runWith manager $ verifiedDownload exampleReq examplePath exampleProgressHook
+      go `shouldReturn` True
+      doesFileExist exampleFilePath `shouldReturn` True
+      readFile exampleFilePath `shouldNotReturn` exampleWrongContent
+
+    it "rejects incorrect content length" $ \T{..} -> withTempDir $ \dir -> do
+      examplePath <- getExamplePath dir
+      let exampleFilePath = toFilePath examplePath
+      let wrongContentLengthReq = exampleReq
+            { drLengthCheck = Just exampleWrongContentLength
+            }
+      let go = runWith manager $ verifiedDownload wrongContentLengthReq examplePath exampleProgressHook
+      go `shouldThrow` isWrongContentLength
+      doesFileExist exampleFilePath `shouldReturn` False
+
+    it "rejects incorrect digest" $ \T{..} -> withTempDir $ \dir -> do
+      examplePath <- getExamplePath dir
+      let exampleFilePath = toFilePath examplePath
+      let wrongHashCheck = exampleHashCheck { hashCheckHexDigest = exampleWrongDigest }
+      let wrongDigestReq = exampleReq { drHashChecks = [wrongHashCheck] }
+      let go = runWith manager $ verifiedDownload wrongDigestReq examplePath exampleProgressHook
+      go `shouldThrow` isWrongDigest
+      doesFileExist exampleFilePath `shouldReturn` False
+
+    -- https://github.com/commercialhaskell/stack/issues/240
+    it "can download hackage tarballs" $ \T{..} -> withTempDir $ \dir -> do
+      dest <- fmap (dir </>) $ parseRelFile "acme-missiles-0.3.tar.gz"
+      let destFp = toFilePath dest
+      req <- parseUrl "http://hackage.haskell.org/package/acme-missiles-0.3/acme-missiles-0.3.tar.gz"
+      let dReq = DownloadRequest
+            { drRequest = req
+            , drHashChecks = []
+            , drLengthCheck = Nothing
+            }
+      let progressHook = return ()
+      let go = runWith manager $ verifiedDownload dReq dest progressHook
+      doesFileExist destFp `shouldReturn` False
+      go `shouldReturn` True
+      doesFileExist destFp `shouldReturn` True
diff --git a/src/test/Stack/DotSpec.hs b/src/test/Stack/DotSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/test/Stack/DotSpec.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- | Test suite for Stack.Dot
+module Stack.DotSpec where
+
+import           Data.ByteString.Char8 (ByteString)
+import qualified Data.Foldable as F
+import           Data.Functor.Identity
+import           Data.List ((\\))
+import qualified Data.Map as Map
+import           Data.Maybe (fromMaybe)
+import           Data.Set (Set)
+import qualified Data.Set as Set
+import           Options.Applicative (execParserPure,idm,prefs,info,getParseResult)
+import           Stack.Types
+import           Test.Hspec
+import           Test.Hspec.QuickCheck (prop)
+#if MIN_VERSION_QuickCheck(2,8,0)
+import           Test.QuickCheck (forAll,sublistOf)
+#endif
+
+import           Stack.Dot
+
+spec :: Spec
+spec = do
+  let graph =
+         Map.mapKeys pkgName
+       . fmap (Set.map pkgName)
+       . Map.fromList $ [("one",Set.fromList ["base","free"])
+                        ,("two",Set.fromList ["base","free","mtl","transformers","one"])
+                        ]
+  describe "Stack.Dot" $ do
+    it "does nothing if depth is 0" $
+      resolveDependencies (Just 0) graph stubLoader `shouldBe` return graph
+
+    it "with depth 1, more dependencies are resolved" $ do
+      let graph' = Map.insert (pkgName "cycle") (Set.singleton (pkgName "cycle")) graph
+          resultGraph = runIdentity (resolveDependencies (Just 0) graph stubLoader)
+          resultGraph' = runIdentity (resolveDependencies (Just 1) graph' stubLoader)
+      Map.size resultGraph < Map.size resultGraph' `shouldBe` True
+
+    it "cycles are ignored" $ do
+       let graph' = Map.insert (pkgName "cycle") (Set.singleton (pkgName "cycle")) graph
+           resultGraph = resolveDependencies Nothing graph stubLoader
+           resultGraph' = resolveDependencies Nothing graph' stubLoader
+       fmap Map.size resultGraph' `shouldBe` fmap ((+1) . Map.size) resultGraph
+
+#if MIN_VERSION_QuickCheck(2,8,0)
+    prop "requested packages are pruned" $ do
+      let resolvedGraph = runIdentity (resolveDependencies Nothing graph stubLoader)
+          allPackages g = Set.map show (Map.keysSet g `Set.union` F.fold g)
+      forAll (sublistOf (Set.toList (allPackages resolvedGraph))) $ \toPrune ->
+        let pruned = pruneGraph [pkgName "one", pkgName "two"] toPrune resolvedGraph
+        in Set.null (allPackages pruned `Set.intersection` Set.fromList toPrune)
+
+    prop "pruning removes orhpans" $ do
+      let resolvedGraph = runIdentity (resolveDependencies Nothing graph stubLoader)
+          allPackages g = Set.map show (Map.keysSet g `Set.union` F.fold g)
+          orphans g = Map.filterWithKey (\k _ -> not (graphElem k g)) g
+      forAll (sublistOf (Set.toList (allPackages resolvedGraph))) $ \toPrune ->
+        let pruned = pruneGraph [pkgName "one", pkgName "two"] toPrune resolvedGraph
+        in null (Map.keys (orphans pruned) \\ [pkgName "one", pkgName "two"])
+#endif
+
+  where graphElem e graph = Set.member e . Set.unions . Map.elems $ graph
+
+{- Helper functions below -}
+
+-- Unsafe internal helper to create a package name
+pkgName :: ByteString -> PackageName
+pkgName = fromMaybe failure . parsePackageName
+  where
+   failure = error "Internal error during package name creation in DotSpec.pkgName"
+
+-- Stub, simulates the function to load package dependecies
+stubLoader :: PackageName -> Identity (Set PackageName)
+stubLoader name = return . Set.fromList . map pkgName $ case show name of
+  "StateVar" -> ["stm","transformers"]
+  "array" -> []
+  "bifunctors" -> ["semigroupoids","semigroups","tagged"]
+  "binary" -> ["array","bytestring","containers"]
+  "bytestring" -> ["deepseq","ghc-prim","integer-gmp"]
+  "comonad" -> ["containers","contravariant","distributive"
+               ,"semigroups","tagged","transformers","transformers-compat"
+               ]
+  "cont" -> ["StateVar","semigroups","transformers","transformers-compat","void"]
+  "containers" -> ["array","deepseq","ghc-prim"]
+  "deepseq" -> ["array"]
+  "distributive" -> ["ghc-prim","tagged","transformers","transformers-compat"]
+  "free" -> ["bifunctors","comonad","distributive","mtl"
+            ,"prelude-extras","profunctors","semigroupoids"
+            ,"semigroups","template-haskell","transformers"
+            ]
+  "ghc" -> []
+  "hashable" -> ["bytestring","ghc-prim","integer-gmp","text"]
+  "integer" -> []
+  "mtl" -> ["transformers"]
+  "nats" -> []
+  "one" -> ["free"]
+  "prelude" -> []
+  "profunctors" -> ["comonad","distributive","semigroupoids","tagged","transformers"]
+  "semigroupoids" -> ["comonad","containers","contravariant","distributive"
+                     ,"semigroups","transformers","transformers-compat"
+                     ]
+  "semigroups" -> ["bytestring","containers","deepseq","hashable"
+                  ,"nats","text","unordered-containers"
+                  ]
+  "stm" -> ["array"]
+  "tagged" -> ["template-haskell"]
+  "template" -> []
+  "text" -> ["array","binary","bytestring","deepseq","ghc-prim","integer-gmp"]
+  "transformers" -> []
+  "two" -> ["free","mtl","one","transformers"]
+  "unordered" -> ["deepseq","hashable"]
+  "void" -> ["ghc-prim","hashable","semigroups"]
+  _ -> []
diff --git a/stack.cabal b/stack.cabal
--- a/stack.cabal
+++ b/stack.cabal
@@ -1,5 +1,5 @@
 name:                stack
-version:             0.1.1.0
+version:             0.1.2.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
@@ -50,6 +50,7 @@
                      Stack.Dot
                      Stack.Fetch
                      Stack.Exec
+                     Stack.FileWatch
                      Stack.GhcPkg
                      Stack.Init
                      Stack.New
@@ -57,6 +58,7 @@
                      Stack.PackageDump
                      Stack.PackageIndex
                      Stack.Repl
+                     Stack.Ide
                      Stack.Setup
                      Stack.Solver
                      Stack.Types
@@ -84,6 +86,7 @@
                      System.Process.Log
                      System.Process.Run
                      Network.HTTP.Download.Verified
+                     Data.Attoparsec.Args
   other-modules:     Network.HTTP.Download
                      Control.Concurrent.Execute
                      Path.Find
@@ -95,16 +98,17 @@
                      Data.Binary.VersionTagged
                      Data.Set.Monad
                      Data.Maybe.Extra
-                     Data.Attoparsec.Args
   build-depends:     Cabal >= 1.18.1.5
                    , aeson >= 0.8.0.2
                    , async >= 2.0.2
                    , attoparsec >= 0.12.1.5
                    , base >= 4 && <5
                    , base16-bytestring
+                   , base64-bytestring
                    , bifunctors >= 4.2.1
                    , binary >= 0.7
-                   , base64-bytestring
+                   , blaze-builder
+                   , byteable
                    , bytestring
                    , conduit-combinators >= 0.3.1
                    , conduit >= 1.2.4
@@ -117,6 +121,7 @@
                    , exceptions >= 0.8.0.2
                    , fast-logger >= 2.3.1
                    , filepath >= 1.3.0.2
+                   , fsnotify
                    , hashable >= 1.2.3.2
                    , http-client >= 0.4.9
                    , http-client-tls >= 0.2.2
@@ -137,6 +142,7 @@
                    , process >= 1.2.0.0
                    , resourcet >= 1.1.4.1
                    , safe >= 0.3
+                   , split
                    , stm >= 2.4.4
                    , streaming-commons >= 0.1.10.0
                    , tar >= 0.4.1.0
@@ -167,7 +173,6 @@
                      Plugins.Commands
 
   build-depends:  base >=4.7 && < 5
-                , blaze-builder
                 , bytestring >= 0.10.4.0
                 , containers
                 , exceptions
@@ -201,8 +206,10 @@
                 , Stack.BuildPlanSpec
                 , Stack.Build.ExecuteSpec
                 , Stack.ConfigSpec
+                , Stack.DotSpec
                 , Stack.PackageDumpSpec
                 , Stack.ArgsSpec
+                , Network.HTTP.Download.VerifiedSpec
   ghc-options:    -Wall -threaded
   build-depends:  base >=4.7 && <5
                 , hspec
@@ -222,6 +229,9 @@
                 , resourcet
                 , Cabal
                 , text
+                , optparse-applicative
+                , bytestring
+                , QuickCheck
   default-language:    Haskell2010
 
 test-suite stack-integration-test
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,7 +1,7 @@
 packages:
 - .
 extra-deps:
-- optparse-simple-0.0.3
-- path-0.5.1
-- monad-unlift-0.1.1.0
-resolver: lts-2.9
+- path-0.5.2
+- Win32-notify-0.3.0.1
+- hfsevents-0.1.5
+resolver: lts-2.17
