diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,13 @@
+## Unreleased
+
+* `--prefetch` [#297](https://github.com/commercialhaskell/stack/issues/297)
+* `upload` command ported from stackage-upload [#225](https://github.com/commercialhaskell/stack/issues/225)
+* `--only-snapshot` [#310](https://github.com/commercialhaskell/stack/issues/310)
+* `--resolver` [#224](https://github.com/commercialhaskell/stack/issues/224)
+* `stack init` [#253](https://github.com/commercialhaskell/stack/issues/253)
+* `--extra-include-dirs` and `--extra-lib-dirs` [#333](https://github.com/commercialhaskell/stack/issues/333)
+* Specify intra-package target [#201](https://github.com/commercialhaskell/stack/issues/201)
+
 ## 0.0.2
 
 * Fix some Windows specific bugs [#216](https://github.com/commercialhaskell/stack/issues/216)
diff --git a/src/Options/Applicative/Args.hs b/src/Options/Applicative/Args.hs
new file mode 100644
--- /dev/null
+++ b/src/Options/Applicative/Args.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Accepting arguments to be passed through to a sub-process.
+
+module Options.Applicative.Args
+    (argsArgument
+    ,argsOption
+    ,parseArgsFromString
+    ,argsParser)
+    where
+
+import           Control.Applicative
+import           Data.Attoparsec.Text ((<?>))
+import qualified Data.Attoparsec.Text as P
+import           Data.Attoparsec.Types (Parser)
+import           Data.Text (Text)
+import qualified Data.Text as T
+import qualified Options.Applicative as O
+
+-- | An argument which accepts a list of arguments e.g. @--ghc-options="-X P.hs \"this\""@.
+argsArgument :: O.Mod O.ArgumentFields [String] -> O.Parser [String]
+argsArgument =
+    O.argument
+        (do string <- O.str
+            either O.readerError return (parseArgsFromString string))
+
+-- | An option which accepts a list of arguments e.g. @--ghc-options="-X P.hs \"this\""@.
+argsOption :: O.Mod O.OptionFields [String] -> O.Parser [String]
+argsOption =
+    O.option
+        (do string <- O.str
+            either O.readerError return (parseArgsFromString string))
+
+-- | Parse from a string.
+parseArgsFromString :: String -> Either String [String]
+parseArgsFromString = P.parseOnly argsParser . T.pack
+
+-- | A basic argument parser. It supports space-separated text, and
+-- string quotation with identity escaping: \x -> x.
+argsParser :: Parser Text [String]
+argsParser = many (P.skipSpace *> (quoted <|> unquoted)) <*
+             P.skipSpace <* (P.endOfInput <?> "unterminated string")
+  where
+    unquoted = P.many1 naked
+    quoted = P.char '"' *> string <* P.char '"'
+    string = many (escaped <|> nonquote)
+    escaped = P.char '\\' *> P.anyChar
+    nonquote = P.satisfy (not . (=='"'))
+    naked = P.satisfy (not . flip elem ("\" " :: String))
diff --git a/src/Path/IO.hs b/src/Path/IO.hs
--- a/src/Path/IO.hs
+++ b/src/Path/IO.hs
@@ -12,7 +12,9 @@
   ,ResolveException(..)
   ,removeFileIfExists
   ,removeTree
-  ,removeTreeIfExists)
+  ,removeTreeIfExists
+  ,fileExists
+  ,dirExists)
   where
 
 import           Control.Exception hiding (catch)
@@ -138,3 +140,13 @@
                   (\e -> if isDoesNotExistError e
                             then return ()
                             else throwIO e))
+
+-- | Does the given file exist?
+fileExists :: MonadIO m => Path b File -> m Bool
+fileExists =
+    liftIO . doesFileExist . toFilePath
+
+-- | Does the given directory exist?
+dirExists :: MonadIO m => Path b Dir -> m Bool
+dirExists =
+    liftIO . doesFileExist . toFilePath
diff --git a/src/Stack/Build.hs b/src/Stack/Build.hs
--- a/src/Stack/Build.hs
+++ b/src/Stack/Build.hs
@@ -47,7 +47,7 @@
 #endif
 --}
 
-type M env m = (MonadIO m,MonadReader env m,HasHttpManager env,HasBuildConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,MonadMask m,HasLogLevel env,HasEnvConfig env)
+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 ()
@@ -60,6 +60,9 @@
     baseConfigOpts <- mkBaseConfigOpts bopts
     plan <- withLoadPackage menv $ \loadPackage ->
         constructPlan mbp baseConfigOpts locals extraToBuild locallyRegistered loadPackage sourceMap installedMap
+
+    when (boptsPreFetch bopts) $
+        preFetch plan
 
     if boptsDryrun bopts
         then printPlan (boptsFinalAction bopts) plan
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
@@ -10,7 +10,9 @@
 
 import           Control.Exception.Lifted
 import           Control.Monad
+import           Control.Monad.Catch (MonadCatch)
 import           Control.Monad.IO.Class
+import           Control.Monad.Logger (MonadLogger)
 import           Control.Monad.RWS.Strict
 import           Control.Monad.Trans.Resource
 import qualified Data.ByteString.Char8 as S8
@@ -28,6 +30,7 @@
 import           Distribution.Package (Dependency (..))
 import           Distribution.Version         (anyVersion,
                                                intersectVersionRanges)
+import           Network.HTTP.Client.Conduit (HasHttpManager)
 import           Prelude hiding (FilePath, pi, writeFile)
 import           Stack.Build.Cache
 import           Stack.Build.Installed
@@ -36,6 +39,7 @@
 import           Stack.BuildPlan
 
 import           Stack.Package
+import           Stack.PackageIndex
 import           Stack.Types
 
 data PackageInfo
@@ -84,6 +88,7 @@
     , ctxBuildConfig :: !BuildConfig
     , callStack      :: ![PackageName]
     , extraToBuild   :: !(Set PackageName)
+    , latestVersions :: !(Map PackageName Version)
     }
 
 instance HasStackRoot Ctx
@@ -93,7 +98,7 @@
     getBuildConfig = ctxBuildConfig
 
 constructPlan :: forall env m.
-                 (MonadThrow m, MonadReader env m, HasBuildConfig env, MonadIO m)
+                 (MonadCatch m, MonadReader env m, HasBuildConfig env, MonadIO m, MonadLogger m, MonadBaseControl IO m, HasHttpManager env)
               => MiniBuildPlan
               -> BaseConfigOpts
               -> [LocalPackage]
@@ -104,11 +109,19 @@
               -> InstalledMap
               -> m Plan
 constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 locallyRegistered loadPackage0 sourceMap installedMap = do
+    menv <- getMinimalEnvOverride
+    caches <- getPackageCaches menv
+    let latest = Map.fromListWith max $ map toTuple $ Map.keys caches
+
     bconfig <- asks getBuildConfig
+    let onWanted =
+            case boptsFinalAction $ bcoBuildOpts baseConfigOpts0 of
+                DoNothing -> void . addDep . packageName . lpPackage
+                _ -> addFinal
     let inner = do
-            mapM_ addFinal $ filter lpWanted locals
+            mapM_ onWanted $ filter lpWanted locals
             mapM_ addDep $ Set.toList extraToBuild0
-    ((), m, (efinals, installExes)) <- liftIO $ runRWST inner (ctx bconfig) M.empty
+    ((), m, (efinals, installExes)) <- liftIO $ runRWST inner (ctx bconfig latest) M.empty
     let toEither (_, Left e)  = Left e
         toEither (k, Right v) = Right (k, v)
         (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m
@@ -119,7 +132,11 @@
             let toTask (_, ADRFound _ _) = Nothing
                 toTask (name, ADRToInstall task) = Just (name, task)
                 tasks = M.fromList $ mapMaybe toTask adrs
-            return Plan
+                maybeStripLocals
+                    | boptsOnlySnapshot $ bcoBuildOpts baseConfigOpts0 =
+                        stripLocals
+                    | otherwise = id
+            return $ maybeStripLocals Plan
                 { planTasks = tasks
                 , planFinals = M.fromList finals
                 , planUnregisterLocal = mkUnregisterLocal tasks locallyRegistered
@@ -128,9 +145,9 @@
                         then installExes
                         else Map.empty
                 }
-        else throwM $ ConstructPlanExceptions errs
+        else throwM $ ConstructPlanExceptions errs (bcStackYaml bconfig)
   where
-    ctx bconfig = Ctx
+    ctx bconfig latest = Ctx
         { mbp = mbp0
         , baseConfigOpts = baseConfigOpts0
         , loadPackage = loadPackage0
@@ -141,6 +158,7 @@
         , ctxBuildConfig = bconfig
         , callStack = []
         , extraToBuild = extraToBuild0
+        , latestVersions = latest
         }
     toolMap = getToolMap mbp0
 
@@ -160,8 +178,6 @@
 
 addFinal :: LocalPackage -> M ()
 addFinal lp = do
-    void $ addDep $ packageName package
-
     depsRes <- addPackageDeps package
     res <- case depsRes of
         Left e -> return $ Left e
@@ -174,6 +190,7 @@
                 , taskConfigOpts = TaskConfigOpts missing $ \missing' ->
                     let allDeps = Set.union present missing'
                      in configureOpts
+                            (getConfig ctx)
                             (baseConfigOpts ctx)
                             allDeps
                             True -- wanted
@@ -263,6 +280,7 @@
                 , taskConfigOpts = TaskConfigOpts missing $ \missing' ->
                     let allDeps = Set.union present missing'
                      in configureOpts
+                            (getConfig ctx)
                             (baseConfigOpts ctx)
                             allDeps
                             (psWanted ps)
@@ -287,6 +305,7 @@
 
 addPackageDeps :: Package -> M (Either ConstructPlanException (Set PackageIdentifier, Set GhcPkgId))
 addPackageDeps package = do
+    ctx <- ask
     deps' <- packageDepsWithTools package
     deps <- forM (Map.toList deps') $ \(depname, range) -> do
         eres <- addDep depname
@@ -294,7 +313,8 @@
             Left e ->
                 let bd =
                         case e of
-                            UnknownPackage _ -> NotInBuildPlan
+                            UnknownPackage name ->
+                                NotInBuildPlan $ Map.lookup name $ latestVersions ctx
                             _ -> Couldn'tResolveItsDependencies
                  in return $ Left (depname, (range, bd))
             Right adr | not $ adrVersion adr `withinRange` range ->
@@ -324,6 +344,7 @@
 checkDirtiness ps installed package present = do
     ctx <- ask
     let configOpts = configureOpts
+            (getConfig ctx)
             (baseConfigOpts ctx)
             present
             (psWanted ps)
@@ -332,6 +353,10 @@
         configCache = ConfigCache
             { configCacheOpts = map encodeUtf8 configOpts
             , configCacheDeps = present
+            , configCacheComponents =
+                case ps of
+                    PSLocal lp -> Set.map encodeUtf8 $ lpComponents lp
+                    PSUpstream _ _ _ -> Set.empty
             }
     moldOpts <- tryGetFlagCache installed
     case moldOpts of
@@ -360,3 +385,18 @@
     return $ Map.unionsWith intersectVersionRanges
            $ packageDeps p
            : map (toolToPackages ctx) (packageTools p)
+
+-- | Strip out anything from the @Plan@ intended for the local database
+stripLocals :: Plan -> Plan
+stripLocals plan = plan
+    { planTasks = Map.filter checkTask $ planTasks plan
+    , planFinals = Map.empty
+    , planUnregisterLocal = Set.empty
+    , planInstallExes = Map.filter (/= Local) $ planInstallExes plan
+    }
+  where
+    checkTask task =
+        case taskType task of
+            TTLocal _ -> False
+            TTUpstream _ Local -> False
+            TTUpstream _ Snap -> True
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
@@ -7,6 +7,7 @@
 -- Perform a build
 module Stack.Build.Execute
     ( printPlan
+    , preFetch
     , executePlan
     ) where
 
@@ -67,9 +68,29 @@
 import           System.IO.Temp                 (withSystemTempDirectory)
 import           System.Process.Internals       (createProcess_)
 import           System.Process.Read
+import           System.Process.Log (showProcessArgDebug)
 
-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)
+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)
 
+preFetch :: M env m => Plan -> m ()
+preFetch plan
+    | Set.null idents = $logDebug "Nothing to fetch"
+    | otherwise = do
+        $logDebug $ T.pack $
+            "Prefetching: " ++
+            intercalate ", " (map packageIdentifierString $ Set.toList idents)
+        menv <- getMinimalEnvOverride
+        fetchPackages menv idents
+  where
+    idents = Set.unions $ map toIdent $ Map.toList $ planTasks plan
+
+    toIdent (name, task) =
+        case taskType task of
+            TTLocal _ -> Set.empty
+            TTUpstream package _ -> Set.singleton $ PackageIdentifier
+                name
+                (packageVersion package)
+
 printPlan :: M env m
           => FinalAction
           -> Plan
@@ -211,19 +232,21 @@
 
         currExe <- liftIO getExecutablePath -- needed for windows, see below
 
-        forM_ (Map.toList $ planInstallExes plan) $ \(name, loc) -> do
+        installed <- forM (Map.toList $ planInstallExes plan) $ \(name, loc) -> do
             let bindir =
                     case loc of
                         Snap -> snapBin
                         Local -> localBin
             mfp <- resolveFileMaybe bindir $ T.unpack name ++ ext
             case mfp of
-                Nothing -> $logWarn $ T.concat
-                    [ "Couldn't find executable "
-                    , name
-                    , " in directory "
-                    , T.pack $ toFilePath bindir
-                    ]
+                Nothing -> do
+                    $logWarn $ T.concat
+                        [ "Couldn't find executable "
+                        , name
+                        , " in directory "
+                        , T.pack $ toFilePath bindir
+                        ]
+                    return Nothing
                 Just file -> do
                     let destFile = destDir' FP.</> T.unpack name ++ ext
                     $logInfo $ T.concat
@@ -237,7 +260,17 @@
                         Platform _ Windows | FP.equalFilePath destFile currExe ->
                             windowsRenameCopy (toFilePath file) destFile
                         _ -> copyFile (toFilePath file) destFile
+                    return $ Just (destDir', [T.append name (T.pack ext)])
 
+        let destToInstalled = Map.fromListWith (++) (catMaybes installed)
+        unless (Map.null destToInstalled) $ $logInfo ""
+        forM_ (Map.toList destToInstalled) $ \(dest, executables) -> do
+            $logInfo $ T.concat
+                [ "Installed executables to "
+                , T.pack dest
+                , ":"]
+            forM_ executables $ \exe -> $logInfo $ T.append "- " exe
+
 -- | Windows can't write over the current executable. Instead, we rename the
 -- current executable to something else and then do the copy.
 windowsRenameCopy :: FilePath -> FilePath -> IO ()
@@ -279,13 +312,15 @@
             (planTasks plan)
             (planFinals plan)
     threads <- asks $ configJobs . getConfig
+    terminal <- asks getTerminal
     errs <- liftIO $ runActions threads actions $ \doneVar -> do
         let total = length actions
             loop prev
                 | prev == total =
                     runInBase $ $logStickyDone ("Completed all " <> T.pack (show total) <> " actions.")
                 | otherwise = do
-                    runInBase $ $logSticky ("Progress: " <> T.pack (show prev) <> "/" <> T.pack (show total))
+                    when terminal $ runInBase $
+                        $logSticky ("Progress: " <> T.pack (show prev) <> "/" <> T.pack (show total))
                     done <- atomically $ do
                         done <- readTVar doneVar
                         check $ done /= prev
@@ -380,6 +415,10 @@
         newConfigCache = ConfigCache
             { configCacheOpts = map encodeUtf8 configOpts
             , configCacheDeps = allDeps
+            , configCacheComponents =
+                case taskType of
+                    TTLocal lp -> Set.map encodeUtf8 $ lpComponents lp
+                    TTUpstream _ _ -> Set.empty
             }
 
     let needConfig = mOldConfigCache /= Just newConfigCache
@@ -388,7 +427,6 @@
         deleteCaches pkgDir
         announce
         cabal False $ "configure" : map T.unpack configOpts
-        $logDebug $ T.pack $ show configOpts
         writeConfigCache pkgDir newConfigCache
         writeCabalMod pkgDir newCabalMod
 
@@ -472,8 +510,24 @@
                                               eeCabalPkgVer))
                     : "-clear-package-db"
                     : "-global-package-db"
-                    -- TODO: Perhaps we want to include the snapshot package database here
-                    -- as well
+
+                    -- This next line is debatable. It adds access to the
+                    -- snapshot package database for Cabal. There are two
+                    -- possible objections:
+                    --
+                    -- 1. This doesn't isolate the build enough; arbitrary
+                    -- other packages available could cause the build to
+                    -- succeed or fail.
+                    --
+                    -- 2. This doesn't provide enough packages: we should also
+                    -- include the local database when building local packages.
+                    --
+                    -- One possible solution to these points would be to use
+                    -- -hide-all-packages and explicitly list which packages
+                    -- can be used by Setup.hs, and have that based on the
+                    -- dependencies of the package itself.
+                    : ("-package-db=" ++ toFilePath (bcoSnapDB eeBaseConfigOpts))
+
                     : toFilePath setuphs
                     : ("--builddir=" ++ toFilePath distRelativeDir')
                     : args
@@ -491,7 +545,7 @@
                             Nothing -> Inherit
                             Just (_, h) -> UseHandle h
                     }
-            $logDebug $ "Running: " <> T.pack (show $ toFilePath exeName : fullArgs)
+            $logProcessRun (toFilePath exeName) fullArgs
 
             -- Use createProcess_ to avoid the log file being closed afterwards
             (Just inH, moutH, Nothing, ph) <- liftIO $ createProcess_ "singleBuild" cp
@@ -534,7 +588,10 @@
 
     announce "build"
     config <- asks getConfig
-    cabal (console && configHideTHLoading config) ["build"]
+    cabal (console && configHideTHLoading config) $
+        case taskType of
+            TTLocal lp -> "build" : map T.unpack (Set.toList $ lpComponents lp)
+            TTUpstream _ _ -> ["build"]
 
     withMVar eeInstallLock $ \() -> do
         announce "install"
@@ -573,8 +630,11 @@
                 (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 (test)"
+            fileModTimes <- getPackageFileModTimes package cabalfp
+            writeBuildCache pkgDir fileModTimes
             cabal (console && configHideTHLoading config) ["build"]
 
         bconfig <- asks getBuildConfig
@@ -589,15 +649,20 @@
             nameDir <- liftIO $ parseRelDir $ T.unpack testName
             nameExe <- liftIO $ parseRelFile $ T.unpack testName ++ exeExtension
             let exeName = buildDir </> $(mkRelDir "build") </> nameDir </> nameExe
-            exists <- liftIO $ doesFileExist $ toFilePath exeName
+            exists <- fileExists exeName
             menv <- liftIO $ configEnvOverride config EnvSettings
                 { esIncludeLocals = taskLocation task == Local
                 , esIncludeGhcPackagePath = True
                 }
             if exists
                 then do
-                    announce $ "test " <> testName
-                    let cp = (proc (toFilePath exeName) [])
+                    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
@@ -637,15 +702,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
             config <- asks getConfig
             cabal (console && configHideTHLoading config) ["build"]
 
@@ -736,11 +804,11 @@
 getSetupHs :: Path Abs Dir -- ^ project directory
            -> IO (Maybe (Path Abs File))
 getSetupHs dir = do
-    exists1 <- doesFileExist (toFilePath fp1)
+    exists1 <- fileExists fp1
     if exists1
         then return $ Just fp1
         else do
-            exists2 <- doesFileExist (toFilePath fp2)
+            exists2 <- fileExists fp2
             if exists2
                 then return $ Just fp2
                 else return Nothing
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
@@ -8,10 +8,11 @@
     ( loadSourceMap
     , SourceMap
     , PackageSource (..)
+    , localFlags
     ) where
 
 import Network.HTTP.Client.Conduit (HasHttpManager)
-import           Control.Applicative          ((<|>))
+import           Control.Applicative          ((<|>), (<$>))
 import           Control.Monad
 import           Control.Monad.Catch          (MonadCatch)
 import           Control.Monad.IO.Class
@@ -28,6 +29,7 @@
 import           Data.Monoid                  ((<>))
 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)
@@ -136,10 +138,10 @@
         case boptsTargets bopts of
             [] -> ["."]
             x -> x
-    (dirs, (names0, idents)) <- case partitionEithers targets of
-        ([], targets') -> return $ fmap partitionEithers $ partitionEithers targets'
-        (bad, _) -> throwM $ Couldn'tParseTargets bad
-    let names = Set.fromList names0
+    (dirs, names, idents) <-
+        case partitionEithers targets of
+            ([], targets') -> return $ partitionTargetSpecs targets'
+            (bad, _) -> throwM $ Couldn'tParseTargets bad
 
     bconfig <- asks getBuildConfig
     lps <- forM (Map.toList $ bcPackages bconfig) $ \(dir, validWanted) -> do
@@ -149,7 +151,7 @@
             config = PackageConfig
                 { packageConfigEnableTests = False
                 , packageConfigEnableBenchmarks = False
-                , packageConfigFlags = localFlags bopts bconfig name
+                , packageConfigFlags = localFlags (boptsFlags bopts) bconfig name
                 , packageConfigGhcVersion = bcGhcVersion bconfig
                 , packageConfigPlatform = configPlatform $ getConfig bconfig
                 }
@@ -173,39 +175,65 @@
                         mbuildCache
             , lpCabalFile = cabalfp
             , lpDir = dir
+            , lpComponents = fromMaybe Set.empty $ Map.lookup name names
             }
 
     let known = Set.fromList $ map (packageName . lpPackage) lps
-        unknown = Set.difference names known
+        unknown = Set.difference (Map.keysSet names) known
 
-    return (lps, unknown, Set.fromList idents)
+    return (lps, unknown, idents)
   where
     parseTarget t = do
         let s = T.unpack t
         isDir <- liftIO $ doesDirectoryExist s
         if isDir
-            then liftM (Right . Left) $ liftIO (canonicalizePath s) >>= parseAbsDir
-            else return $ case parsePackageNameFromString s of
-                     Left _ ->
-                        case parsePackageIdentifierFromString s of
-                            Left _ ->
-                                case T.stripSuffix ":latest" t of
-                                    Just t'
-                                        | Just name <- parsePackageNameFromString $ T.unpack t'
-                                        , Just version <- Map.lookup name latestVersion
-                                        -> Right $ Right $ Right $ PackageIdentifier name version
-                                    _ -> Left t
-                            Right ident -> Right $ Right $ Right ident
-                     Right pname -> Right $ Right $ Left pname
+            then liftM (Right . TSDir) $ liftIO (canonicalizePath s) >>= parseAbsDir
+            else return
+                    $ maybe (Left t) Right
+                    $ (flip TSName Set.empty <$> parsePackageNameFromString s)
+                  <|> (TSIdent <$> parsePackageIdentifierFromString s)
+                  <|> (do
+                        t' <- T.stripSuffix ":latest" t
+                        name <- parsePackageNameFromString $ T.unpack t'
+                        version <- Map.lookup name latestVersion
+                        Just $ TSIdent $ PackageIdentifier name version)
+                  <|> (do
+                        let (name', rest) = T.break (== ':') t
+                        component <- T.stripPrefix ":" rest
+                        name <- parsePackageNameFromString $ T.unpack name'
+                        Just $ TSName name $ Set.singleton component)
     isWanted dirs names dir name =
-        name `Set.member` names ||
+        name `Map.member` names ||
         any (`isParentOf` dir) dirs ||
         any (== dir) dirs
 
+data TargetSpec
+    = TSName PackageName (Set Text)
+    | TSIdent PackageIdentifier
+    | TSDir (Path Abs Dir)
+
+partitionTargetSpecs :: [TargetSpec] -> ([Path Abs Dir], Map PackageName (Set Text), Set PackageIdentifier)
+partitionTargetSpecs =
+    loop id Map.empty Set.empty
+  where
+    loop dirs names idents ts0 =
+        case ts0 of
+            [] -> (dirs [], names, idents)
+            TSName name comps:ts -> loop
+                dirs
+                (Map.insertWith Set.union name comps names)
+                idents
+                ts
+            TSIdent ident:ts -> loop dirs names (Set.insert ident idents) ts
+            TSDir dir:ts -> loop (dirs . (dir:)) names idents ts
+
 -- | All flags for a local package
-localFlags :: BuildOpts -> BuildConfig -> PackageName -> Map FlagName Bool
-localFlags bopts bconfig name = Map.union
-    (fromMaybe Map.empty $ Map.lookup name $ boptsFlags bopts)
+localFlags :: (Map PackageName (Map FlagName Bool))
+           -> BuildConfig
+           -> PackageName
+           -> Map FlagName Bool
+localFlags boptsflags bconfig name = Map.union
+    (fromMaybe Map.empty $ Map.lookup name $ boptsflags)
     (fromMaybe Map.empty $ Map.lookup name $ bcFlags bconfig)
 
 -- | Add in necessary packages to extra dependencies
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
@@ -9,11 +9,30 @@
 
 -- | All data types.
 
-module Stack.Build.Types where
+module Stack.Build.Types
+    (StackBuildException(..)
+    ,Location(..)
+    ,ModTime
+    ,modTime
+    ,Installed(..)
+    ,PackageInstallInfo(..)
+    ,Task(..)
+    ,LocalPackage(..)
+    ,BaseConfigOpts(..)
+    ,Plan(..)
+    ,FinalAction(..)
+    ,BuildOpts(..)
+    ,TaskType(..)
+    ,TaskConfigOpts(..)
+    ,ConfigCache(..)
+    ,ConstructPlanException(..)
+    ,configureOpts
+    ,BadDependency(..))
+    where
 
 import           Control.DeepSeq
 import           Control.Exception
-import           Data.Aeson.Extended
+
 import           Data.Binary (Binary(..))
 import qualified Data.ByteString as S
 import           Data.Char (isSpace)
@@ -32,6 +51,7 @@
 import           Data.Text.Encoding.Error (lenientDecode)
 import           Data.Time.Calendar
 import           Data.Time.Clock
+import           Distribution.System (Arch)
 import           Distribution.Text (display)
 import           GHC.Generics
 import           Path (Path, Abs, File, Dir, mkRelDir, toFilePath, (</>))
@@ -45,7 +65,7 @@
 -- Exceptions
 data StackBuildException
   = Couldn'tFindPkgId PackageName
-  | GHCVersionMismatch (Maybe Version) Version (Maybe (Path Abs File))
+  | GHCVersionMismatch (Maybe (Version, Arch)) (Version, Arch) (Maybe (Path Abs File))
   -- ^ Path to the stack.yaml file
   | Couldn'tParseTargets [Text]
   | UnknownTargets
@@ -53,7 +73,9 @@
     (Map PackageName Version) -- not in snapshot, here's the most recent version in the index
     (Path Abs File) -- stack.yaml
   | TestSuiteFailure PackageIdentifier (Map Text (Maybe ExitCode)) (Maybe (Path Abs File))
-  | ConstructPlanExceptions [ConstructPlanException]
+  | ConstructPlanExceptions
+        [ConstructPlanException]
+        (Path Abs File) -- stack.yaml
   | CabalExitedUnsuccessfully
         ExitCode
         PackageIdentifier
@@ -70,15 +92,21 @@
                ", the package id couldn't be found " <> "(via ghc-pkg describe " <>
                packageNameString name <> "). This shouldn't happen, " <>
                "please report as a bug")
-    show (GHCVersionMismatch mactual expected mstack) = concat
+    show (GHCVersionMismatch mactual (expected, earch) mstack) = concat
                 [ case mactual of
                     Nothing -> "No GHC found, expected version "
-                    Just actual ->
-                        "GHC version mismatched, found " ++
-                        versionString actual ++
-                        ", but expected version "
+                    Just (actual, arch) -> concat
+                        [ "GHC version mismatched, found "
+                        , versionString actual
+                        , " ("
+                        , display arch
+                        , ")"
+                        , ", but expected version "
+                        ]
                 , versionString expected
-                , " (based on "
+                , " ("
+                , display earch
+                , ") (based on "
                 , case mstack of
                     Nothing -> "command line arguments"
                     Just stack -> "resolver setting in " ++ toFilePath stack
@@ -123,12 +151,33 @@
             -- TODO Should we load up the full error output and print it here?
             Just logFile -> "Full log available at " ++ toFilePath logFile
         ]
-    show (ConstructPlanExceptions exceptions) =
+    show (ConstructPlanExceptions exceptions stackYaml) =
         "While constructing the BuildPlan the following exceptions were encountered:" ++
-        appendExceptions (removeDuplicates exceptions)
+        appendExceptions exceptions' ++
+        if Map.null extras then "" else (unlines
+                $ ("\n\nRecommended action: try adding the following to your extra-deps in "
+                    ++ toFilePath stackYaml)
+                : map (\(name, version) -> concat
+                    [ "- "
+                    , packageNameString name
+                    , "-"
+                    , versionString version
+                    ]) (Map.toList extras)
+                )
          where
+             exceptions' = removeDuplicates exceptions
              appendExceptions = foldr (\e -> (++) ("\n\n--" ++ show e)) ""
              removeDuplicates = nub
+             extras = Map.unions $ map getExtras exceptions'
+
+             getExtras (DependencyCycleDetected _) = Map.empty
+             getExtras (UnknownPackage _) = Map.empty
+             getExtras (DependencyPlanFailures _ m) =
+                Map.unions $ map go $ Map.toList m
+              where
+                go (name, (_range, NotInBuildPlan (Just version))) =
+                    Map.singleton name version
+                go _ = Map.empty
      -- Supressing duplicate output
     show (CabalExitedUnsuccessfully exitCode taskProvides' execName fullArgs logFiles bs) =
         let fullCmd = (dropQuotes (show execName) ++ " " ++ (unwords fullArgs))
@@ -158,7 +207,8 @@
 
 -- | Reason why a dependency was not used
 data BadDependency
-    = NotInBuildPlan -- TODO add recommended version so it can be added to extra-deps
+    = NotInBuildPlan
+        (Maybe Version) -- recommended version, for extra-deps output
     | Couldn'tResolveItsDependencies
     | DependencyMismatch Version
     deriving (Typeable, Eq)
@@ -187,7 +237,10 @@
         , display range
         , "), but "
         , case badDep of
-            NotInBuildPlan -> "not present in build plan"
+            NotInBuildPlan mlatest -> "not present in build plan" ++
+                (case mlatest of
+                    Nothing -> ""
+                    Just latest -> ", latest is " ++ versionString latest)
             Couldn'tResolveItsDependencies -> "couldn't resolve its dependencies"
             DependencyMismatch version -> versionString version ++ " found"
         ]
@@ -216,49 +269,16 @@
             ,boptsFlags :: !(Map PackageName (Map FlagName Bool))
             ,boptsInstallExes :: !Bool
             -- ^ Install executables to user path after building?
+            ,boptsPreFetch :: !Bool
+            -- ^ Fetch all packages immediately
+            ,boptsTestArgs :: ![String]
+            -- ^ Arguments to pass to the test suites if we're running them.
+            ,boptsOnlySnapshot :: !Bool
+            -- ^ Only install packages in the snapshot database, skipping
+            -- packages intended for the local database.
             }
   deriving (Show)
 
--- | Configuration for testing.
-data TestConfig =
-  TestConfig {tconfigTargets :: ![Text]
-             }
-  deriving (Show)
-
--- | Configuration for haddocking.
-data HaddockConfig =
-  HaddockConfig {hconfigTargets :: ![Text]
-                }
-  deriving (Show)
-
--- | Configuration for benchmarking.
-data BenchmarkConfig =
-  BenchmarkConfig {benchTargets :: ![Text]
-                  ,benchInDocker :: !Bool}
-  deriving (Show)
-
--- | Generated config for a package build.
-data GenConfig =
-  GenConfig {gconfigOptimize :: !Bool
-            ,gconfigLibProfiling :: !Bool
-            ,gconfigExeProfiling :: !Bool
-            ,gconfigGhcOptions :: ![Text]
-            ,gconfigFlags :: !(Map FlagName Bool)
-            ,gconfigPkgId :: Maybe GhcPkgId}
-  deriving (Generic,Show)
-
-instance FromJSON GenConfig
-instance ToJSON GenConfig
-
-defaultGenConfig :: GenConfig
-defaultGenConfig =
-    GenConfig {gconfigOptimize = False
-              ,gconfigLibProfiling = False
-              ,gconfigExeProfiling = False
-              ,gconfigGhcOptions = []
-              ,gconfigFlags = mempty
-              ,gconfigPkgId = Nothing}
-
 -- | Run a Setup.hs action after building a package, before installing.
 data FinalAction
   = DoTests
@@ -267,19 +287,6 @@
   | DoNothing
   deriving (Eq,Bounded,Enum,Show)
 
-data Dependencies =
-  Dependencies {depsLibraries :: [PackageName]
-               ,depsTools :: [PackageName]}
-  deriving (Show,Typeable,Data)
-
--- | Used for mutex locking on the install step. Beats magic ().
-data InstallLock = InstallLock
-
--- | Mutex for reading/writing .config files in dist/ of
--- packages. Shake works in parallel, without this there are race
--- conditions.
-data ConfigLock = ConfigLock
-
 -- | Package dependency oracle.
 newtype PkgDepsOracle =
     PkgDeps PackageName
@@ -303,6 +310,7 @@
     , 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?
+    , lpComponents     :: !(Set Text)      -- ^ components to build, passed directly to Setup.hs build
     }
     deriving Show
 
@@ -316,6 +324,10 @@
       -- the complete GhcPkgId (only a PackageIdentifier) in the configure
       -- options, just using the previous value is insufficient to know if
       -- dependencies have changed.
+    , configCacheComponents :: !(Set S.ByteString)
+      -- ^ The components to be built. It's a bit of a hack to include this in
+      -- here, as it's not a configure option (just a build option), but this
+      -- is a convenient way to force compilation when the components change.
     }
     deriving (Generic,Eq,Show)
 instance Binary ConfigCache
@@ -371,13 +383,14 @@
     }
 
 -- | Render a @BaseConfigOpts@ to an actual list of options
-configureOpts :: BaseConfigOpts
+configureOpts :: Config
+              -> BaseConfigOpts
               -> Set GhcPkgId -- ^ dependencies
               -> Bool -- ^ wanted?
               -> Location
               -> Map FlagName Bool
               -> [Text]
-configureOpts bco deps wanted loc flags = map T.pack $ concat
+configureOpts config bco deps wanted loc flags = map T.pack $ concat
     [ ["--user", "--package-db=clear", "--package-db=global"]
     , map (("--package-db=" ++) . toFilePath) $ case loc of
         Snap -> [bcoSnapDB bco]
@@ -401,6 +414,8 @@
     , if wanted
         then concatMap (\x -> ["--ghc-options", T.unpack x]) (boptsGhcOptions bopts)
         else []
+    , map (("--extra-include-dirs=" ++) . T.unpack) (Set.toList (configExtraIncludeDirs config))
+    , map (("--extra-lib-dirs=" ++) . T.unpack) (Set.toList (configExtraLibDirs config))
     ]
   where
     bopts = bcoBuildOpts bco
diff --git a/src/Stack/BuildPlan.hs b/src/Stack/BuildPlan.hs
--- a/src/Stack/BuildPlan.hs
+++ b/src/Stack/BuildPlan.hs
@@ -25,9 +25,7 @@
     ) where
 
 import           Control.Applicative
-import           Control.Arrow ((&&&))
 import           Control.Exception (assert)
-import           Control.Exception.Enclosed (handleIO)
 import           Control.Monad (liftM, forM)
 import           Control.Monad.Catch
 import           Control.Monad.IO.Class
@@ -45,7 +43,7 @@
 import qualified Data.HashMap.Strict as HM
 import           Data.IntMap (IntMap)
 import qualified Data.IntMap as IntMap
-import           Data.List (intercalate, sort)
+import           Data.List (intercalate)
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import           Data.Maybe (mapMaybe)
@@ -62,6 +60,8 @@
                                                   flagDefault, flagManual,
                                                   flagName, genPackageFlags,
                                                   executables, exeName, library, libBuildInfo, buildable)
+import qualified Distribution.Package as C
+import qualified Distribution.PackageDescription as C
 import           Network.HTTP.Download
 import           Path
 import           Prelude -- Fix AMP warning
@@ -72,7 +72,7 @@
 import           Stack.PackageIndex
 import           Stack.Types
 import           Stack.Types.StackT
-import           System.Directory (createDirectoryIfMissing, getDirectoryContents)
+import           System.Directory (createDirectoryIfMissing)
 import           System.FilePath (takeDirectory)
 
 data BuildPlanException
@@ -160,7 +160,7 @@
 -- This function will not provide test suite and benchmark dependencies.
 --
 -- This may fail if a target package is not present in the @BuildPlan@.
-resolveBuildPlan :: (MonadThrow m, MonadIO m, MonadReader env m, HasBuildConfig env, MonadLogger m, HasHttpManager env)
+resolveBuildPlan :: (MonadThrow m, MonadIO m, MonadReader env m, HasBuildConfig env, MonadLogger m, HasHttpManager env, MonadBaseControl IO m,MonadCatch m)
                  => EnvOverride
                  -> MiniBuildPlan
                  -> (PackageName -> Bool) -- ^ is it shadowed by a local package?
@@ -191,7 +191,7 @@
     , rsUsedBy    :: Map PackageName (Set PackageName)
     }
 
-toMiniBuildPlan :: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m)
+toMiniBuildPlan :: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m, MonadCatch m)
                 => BuildPlan -> m MiniBuildPlan
 toMiniBuildPlan bp = do
     extras <- addDeps ghcVersion $ fmap goPP $ bpPackages bp
@@ -216,7 +216,7 @@
         )
 
 -- | Add in the resolved dependencies from the package index
-addDeps :: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m)
+addDeps :: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m, MonadCatch m)
         => Version -- ^ GHC version
         -> Map PackageName (Version, Map FlagName Bool)
         -> m (Map PackageName MiniPackageInfo)
@@ -329,12 +329,20 @@
 
 -- | Map from tool name to package providing it
 getToolMap :: MiniBuildPlan -> Map ByteString (Set PackageName)
-getToolMap mbp = Map.unionsWith Set.union
+getToolMap mbp =
+      Map.unionsWith Set.union
+
+    {- We no longer do this, following discussion at:
+
+        https://github.com/commercialhaskell/stack/issues/308#issuecomment-112076704
+
     -- First grab all of the package names, for times where a build tool is
     -- identified by package name
     $ Map.fromList (map (packageNameByteString &&& Set.singleton) (Map.keys ps))
+    -}
+
     -- And then get all of the explicit executable names
-    : concatMap goPair (Map.toList ps)
+    $ concatMap goPair (Map.toList ps)
   where
     ps = mbpPackages mbp
 
@@ -379,7 +387,7 @@
 
 -- | Load up a 'MiniBuildPlan', preferably from cache
 loadMiniBuildPlan
-    :: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, MonadBaseControl IO m)
+    :: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, MonadBaseControl IO m, MonadCatch m)
     => SnapName
     -> m MiniBuildPlan
 loadMiniBuildPlan name = do
@@ -441,12 +449,10 @@
 -- only modify non-manual flags, and will prefer default values for flags.
 -- Returns @Nothing@ if no combination exists.
 checkBuildPlan :: (MonadLogger m, MonadThrow m, MonadIO m, MonadReader env m, HasConfig env, MonadCatch m)
-               => SnapName -- ^ used only for debugging purposes
-               -> MiniBuildPlan
+               => MiniBuildPlan
                -> GenericPackageDescription
                -> m (Maybe (Map FlagName Bool))
-checkBuildPlan name mbp gpd = do
-    $logInfo $ "Checking against build plan " <> renderSnapName name
+checkBuildPlan mbp gpd = do
     platform <- asks (configPlatform . getConfig)
     loop platform flagOptions
   where
@@ -516,48 +522,29 @@
 -- | Find a snapshot and set of flags that is compatible with the given
 -- 'GenericPackageDescription'. Returns 'Nothing' if no such snapshot is found.
 findBuildPlan :: (MonadIO m, MonadCatch m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, MonadBaseControl IO m)
-              => GenericPackageDescription
-              -> Snapshots
-              -> m (Maybe (SnapName, Map FlagName Bool))
-findBuildPlan gpd snapshots = do
-    -- Get the most recent LTS and Nightly in the snapshots directory and
-    -- prefer them over anything else, since odds are high that something
-    -- already exists for them.
-    existing <-
-        liftM (reverse . sort . mapMaybe (parseSnapName . T.pack)) $
-        snapshotsDir >>=
-        liftIO . handleIO (const $ return [])
-               . getDirectoryContents . toFilePath
-    let isLTS LTS{} = True
-        isLTS Nightly{} = False
-        isNightly Nightly{} = True
-        isNightly LTS{} = False
-
-    let names = nubOrd $ concat
-            [ take 2 $ filter isLTS existing
-            , take 2 $ filter isNightly existing
-            , map (uncurry LTS)
-                (take 2 $ reverse $ IntMap.toList $ snapshotsLts snapshots)
-            , [Nightly $ snapshotsNightly snapshots]
-            ]
-        loop [] = return Nothing
-        loop (name:names') = do
-            mbp <- loadMiniBuildPlan name
-            mflags <- checkBuildPlan name mbp gpd
-            case mflags of
-                Nothing -> loop names'
-                Just flags -> return $ Just (name, flags)
-    loop names
-
--- | Same semantics as @nub@, but more efficient by using the @Ord@ constraint.
-nubOrd :: Ord a => [a] -> [a]
-nubOrd =
-    go Set.empty
+              => [GenericPackageDescription]
+              -> [SnapName]
+              -> m (Maybe (SnapName, Map PackageName (Map FlagName Bool)))
+findBuildPlan gpds0 =
+    loop
   where
-    go _ [] = []
-    go s (x:xs)
-        | x `Set.member` s = go s xs
-        | otherwise = x : go (Set.insert x s) xs
+    loop [] = return Nothing
+    loop (name:names') = do
+        mbp <- loadMiniBuildPlan name
+        $logInfo $ "Checking against build plan " <> renderSnapName name
+        let checkGPDs flags [] = return $ Just (name, flags)
+            checkGPDs flags (gpd:gpds) = do
+                let C.PackageIdentifier pname' _ = C.package $ C.packageDescription gpd
+                    pname = fromCabalPackageName pname'
+                mflags <- checkBuildPlan mbp gpd
+                case mflags of
+                    Nothing -> loop names'
+                    Just flags' -> checkGPDs
+                        (if Map.null flags'
+                            then flags
+                            else Map.insert pname flags' flags)
+                        gpds
+        checkGPDs Map.empty gpds0
 
 shadowMiniBuildPlan :: MiniBuildPlan
                     -> Set PackageName
diff --git a/src/Stack/Config.hs b/src/Stack/Config.hs
--- a/src/Stack/Config.hs
+++ b/src/Stack/Config.hs
@@ -40,13 +40,8 @@
 import qualified Data.ByteString.Base16 as B16
 import qualified Data.ByteString.Lazy as L
 import           Data.Either (partitionEithers)
-import           Data.Map (Map)
 import qualified Data.IntMap as IntMap
 import qualified Data.Map as Map
-import qualified Distribution.Package as C
-import qualified Distribution.PackageDescription as C
-import qualified Distribution.Text
-import           Distribution.Version (simplifyVersionRange)
 import           Data.Maybe
 import           Data.Monoid
 import qualified Data.Set as S
@@ -54,6 +49,8 @@
 import           Data.Text.Encoding (encodeUtf8, decodeUtf8)
 import qualified Data.Yaml as Yaml
 import           Distribution.System (OS (Windows), Platform (..), buildPlatform)
+import qualified Distribution.Text
+import           Distribution.Version (simplifyVersionRange)
 import           Network.HTTP.Client.Conduit (HasHttpManager, getHttpManager, Manager, parseUrl)
 import           Network.HTTP.Download (download)
 import           Options.Applicative (Parser, idm, strOption, long, short, metavar, help, option, auto)
@@ -62,57 +59,29 @@
 import           Path.IO
 import qualified Paths_stack as Meta
 import           Stack.BuildPlan
-import           Stack.Types.Config
 import           Stack.Constants
 import qualified Stack.Docker as Docker
-import           Stack.Package
+import           Stack.Init
 import           Stack.Types
 import           System.Directory
 import           System.Environment
-import           System.IO (IOMode (ReadMode), withBinaryFile)
+import           System.IO
 import           System.Process.Read (getEnvOverride, EnvOverride, unEnvOverride, readInNull)
 
--- | Get the default resolver value
-getDefaultResolver :: (MonadIO m, MonadCatch m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m)
-                   => Path Abs Dir
-                   -> m (Resolver, Map PackageName (Map FlagName Bool))
-getDefaultResolver dir = do
-    cabalfp <- getCabalFileName dir
-    gpd <- readPackageUnresolved cabalfp
-    snapshots <- getSnapshots `catch` \e -> do
-        $logError $
-            "Unable to download snapshot list, and therefore could " <>
-            "not generate a stack.yaml file automatically"
-        $logError $
-            "This sometimes happens due to missing Certificate Authorities " <>
-            "on your system. For more information, see:"
-        $logError ""
-        $logError "    https://github.com/commercialhaskell/stack/issues/234"
-        $logError ""
-        $logError "You can try again, or create your stack.yaml file by hand. See:"
-        $logError ""
-        $logError "    https://github.com/commercialhaskell/stack/wiki/stack.yaml"
-        $logError ""
-        throwM (e :: SomeException)
-    mpair <- findBuildPlan gpd snapshots
-    let name =
-            case C.package $ C.packageDescription gpd of
-                C.PackageIdentifier cname _ ->
-                    fromCabalPackageName cname
-    case mpair of
-        Just (snap, flags) ->
-            return (ResolverSnapshot snap, Map.singleton name flags)
-        Nothing -> do
-            let snap = case IntMap.maxViewWithKey (snapshotsLts snapshots) of
-                    Just ((x, y), _) -> LTS x y
-                    Nothing -> Nightly $ snapshotsNightly snapshots
-            $logWarn $ T.concat
-                [ "No matching snapshot was found for your package, "
-                , "falling back to: "
-                , renderSnapName snap
-                ]
-            $logWarn "This behavior will improve in the future, please see: https://github.com/commercialhaskell/stack/issues/253"
-            return (ResolverSnapshot snap, Map.empty)
+-- | Get the latest snapshot resolver available.
+getLatestResolver
+    :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m)
+    => m Resolver
+getLatestResolver = do
+    snapshots <- getSnapshots
+    let mlts = do
+            (x,y) <- listToMaybe (reverse (IntMap.toList (snapshotsLts snapshots)))
+            return (LTS x y)
+        snap =
+            case mlts of
+                Nothing -> Nightly (snapshotsNightly snapshots)
+                Just lts -> lts
+    return (ResolverSnapshot snap)
 
 data ProjectAndConfigMonoid
   = ProjectAndConfigMonoid !Project !ConfigMonoid
@@ -187,6 +156,9 @@
          configSystemGHC = fromMaybe True configMonoidSystemGHC
          configInstallGHC = fromMaybe False configMonoidInstallGHC
 
+         configExtraIncludeDirs = configMonoidExtraIncludeDirs
+         configExtraLibDirs = configMonoidExtraLibDirs
+
          -- Only place in the codebase where platform is hard-coded. In theory
          -- in the future, allow it to be configured.
          (Platform defArch defOS) = buildPlatform
@@ -224,13 +196,15 @@
 -- | Command-line arguments parser for configuration.
 configOptsParser :: Bool -> Parser ConfigMonoid
 configOptsParser docker =
-    (\opts systemGHC installGHC arch os jobs -> mempty
+    (\opts systemGHC installGHC arch os jobs includes libs -> mempty
         { configMonoidDockerOpts = opts
         , configMonoidSystemGHC = systemGHC
         , configMonoidInstallGHC = installGHC
         , configMonoidArch = arch
         , configMonoidOS = os
         , configMonoidJobs = jobs
+        , configMonoidExtraIncludeDirs = includes
+        , configMonoidExtraLibDirs = libs
         })
     <$> Docker.dockerOptsParser docker
     <*> maybeBoolFlags
@@ -257,6 +231,16 @@
            <> metavar "JOBS"
            <> help "Number of concurrent jobs to run"
             ))
+    <*> fmap (S.fromList . map T.pack) (many $ strOption
+            ( long "extra-include-dirs"
+           <> metavar "DIR"
+           <> help "Extra directories to check for C header files"
+            ))
+    <*> fmap (S.fromList . map T.pack) (many $ strOption
+            ( long "extra-lib-dirs"
+           <> metavar "DIR"
+           <> help "Extra directories to check for libraries"
+            ))
 
 -- | Get the directory on Windows where we should install extra programs. For
 -- more information, see discussion at:
@@ -299,7 +283,7 @@
     menv <- runReaderT getMinimalEnvOverride config
     return $ LoadConfig
         { lcConfig          = config
-        , lcLoadBuildConfig = loadBuildConfig menv mproject config
+        , lcLoadBuildConfig = loadBuildConfig menv mproject config stackRoot
         , lcProjectRoot     = fmap (\(_, fp, _) -> parent fp) mproject
         }
 
@@ -313,39 +297,62 @@
 
 -- | Load the build configuration, adds build-specific values to config loaded by @loadConfig@.
 -- values.
-loadBuildConfig :: (MonadLogger m,MonadIO m,MonadCatch m,MonadReader env m,HasHttpManager env,MonadBaseControl IO m)
+loadBuildConfig :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader env m, HasHttpManager env, MonadBaseControl IO m)
                 => EnvOverride
                 -> Maybe (Project, Path Abs File, ConfigMonoid)
                 -> Config
+                -> Path Abs Dir
+                -> Maybe Resolver -- override resolver
                 -> NoBuildConfigStrategy
                 -> m BuildConfig
-loadBuildConfig menv mproject config noConfigStrat = do
+loadBuildConfig menv mproject config stackRoot mresolver noConfigStrat = do
     env <- ask
     let miniConfig = MiniConfig (getHttpManager env) config
-    (project, stackYamlFP) <- case mproject of
+    (project', stackYamlFP) <- case mproject of
       Just (project, fp, _) -> return (project, fp)
       Nothing -> case noConfigStrat of
         ThrowException -> do
             currDir <- getWorkingDir
+            cabalFiles <- findCabalFiles currDir
             throwM $ NoProjectConfigFound currDir
-        ExecStrategy ->
-            error "You do not have a stack.yaml. This will be handled in the future, see https://github.com/fpco/stack/issues/59"
-        CreateConfig -> do
-            currDir <- getWorkingDir
-            (r, flags) <- runReaderT (getDefaultResolver currDir) miniConfig
-            let dest = currDir </> stackDotYaml
+                $ Just $ if null cabalFiles then "new" else "init"
+        ExecStrategy -> do
+            let dest :: Path Abs File
+                dest = destDir </> stackDotYaml
+                destDir = implicitGlobalDir stackRoot
+                dest' :: FilePath
                 dest' = toFilePath dest
-            exists <- liftIO $ doesFileExist dest'
-            when exists $ error "Invariant violated: in toBuildConfig's Nothing branch, and the stack.yaml file exists"
-            $logInfo $ "Writing default config file to: " <> T.pack dest'
-            let p = Project
-                    { projectPackages = [packageEntryCurrDir]
-                    , projectExtraDeps = Map.empty
-                    , projectFlags = flags
-                    , projectResolver = r
-                    }
-            liftIO $ Yaml.encodeFile dest' p
-            return (p, dest)
+            liftIO (createDirectoryIfMissing True (toFilePath destDir))
+            exists <- fileExists dest
+            if exists
+               then do
+                   inTerminal <- liftIO (hIsTerminalDevice stdout)
+                   ProjectAndConfigMonoid project _ <- loadYaml dest
+                   when inTerminal $ do
+                       case mresolver of
+                           Nothing ->
+                               $logInfo ("Using resolver: " <> renderResolver (projectResolver project) <>
+                                         " from global config file: " <> T.pack dest')
+                           Just resolver ->
+                               $logInfo ("Using resolver: " <> renderResolver resolver <>
+                                         " specified on command line")
+                   return (project, dest)
+               else do
+                   r <- runReaderT getLatestResolver miniConfig
+                   $logInfo ("Using latest snapshot resolver: " <> renderResolver r)
+                   $logInfo ("Writing global (non-project-specific) config file to: " <> T.pack dest')
+                   $logInfo "Note: You can change the snapshot via the resolver field there."
+                   let p = Project
+                           { projectPackages = mempty
+                           , projectExtraDeps = mempty
+                           , projectFlags = mempty
+                           , projectResolver = r
+                           }
+                   liftIO $ Yaml.encodeFile dest' p
+                   return (p, dest)
+    let project = project'
+            { projectResolver = fromMaybe (projectResolver project') mresolver
+            }
 
     ghcVersion <-
         case projectResolver project of
@@ -371,11 +378,13 @@
 
 -- | Resolve a PackageEntry into a list of paths, downloading and cloning as
 -- necessary.
-resolvePackageEntry :: (MonadIO m, MonadThrow m, MonadReader env m, HasHttpManager env, MonadLogger m)
-                    => EnvOverride
-                    -> Path Abs Dir -- ^ project root
-                    -> PackageEntry
-                    -> m [(Path Abs Dir, Bool)]
+resolvePackageEntry
+    :: (MonadIO m, MonadThrow m, MonadReader env m, HasHttpManager env, MonadLogger m, MonadCatch m
+       ,MonadBaseControl IO m)
+    => EnvOverride
+    -> Path Abs Dir -- ^ project root
+    -> PackageEntry
+    -> m [(Path Abs Dir, Bool)]
 resolvePackageEntry menv projRoot pe = do
     entryRoot <- resolvePackageLocation menv projRoot (peLocation pe)
     paths <-
@@ -386,11 +395,13 @@
 
 -- | Resolve a PackageLocation into a path, downloading and cloning as
 -- necessary.
-resolvePackageLocation :: (MonadIO m, MonadThrow m, MonadReader env m, HasHttpManager env, MonadLogger m)
-                       => EnvOverride
-                       -> Path Abs Dir -- ^ project root
-                       -> PackageLocation
-                       -> m (Path Abs Dir)
+resolvePackageLocation
+    :: (MonadIO m, MonadThrow m, MonadReader env m, HasHttpManager env, MonadLogger m, MonadCatch m
+       ,MonadBaseControl IO m)
+    => EnvOverride
+    -> Path Abs Dir -- ^ project root
+    -> PackageLocation
+    -> m (Path Abs Dir)
 resolvePackageLocation _ projRoot (PLFilePath fp) = resolveDir projRoot fp
 resolvePackageLocation _ projRoot (PLHttpTarball url) = do
     let name = T.unpack $ decodeUtf8 $ B16.encode $ SHA256.hash $ encodeUtf8 url
@@ -478,7 +489,7 @@
     mstackGlobalConfig <-
         maybe (return Nothing) (fmap Just . parseAbsFile)
       $ lookup "STACK_GLOBAL_CONFIG" env
-    filterM (liftIO . doesFileExist . toFilePath)
+    filterM fileExists
         $ fromMaybe (stackRoot </> stackDotYaml) mstackConfig
         : maybe [] return (mstackGlobalConfig <|> defaultStackGlobalConfig)
 
@@ -488,7 +499,7 @@
     liftIO $ Yaml.decodeFileEither (toFilePath path)
          >>= either throwM return
 
--- | Get the location of the project config file, if it exists
+-- | Get the location of the project config file, if it exists.
 getProjectConfig :: (MonadIO m, MonadThrow m, MonadLogger m)
                  => m (Maybe (Path Abs File))
 getProjectConfig = do
@@ -509,7 +520,7 @@
         let fp = dir </> stackDotYaml
             fp' = toFilePath fp
         $logDebug $ "Checking for project config at: " <> T.pack fp'
-        exists <- liftIO $ doesFileExist fp'
+        exists <- fileExists fp
         if exists
             then return $ Just fp
             else do
diff --git a/src/Stack/Constants.hs b/src/Stack/Constants.hs
--- a/src/Stack/Constants.hs
+++ b/src/Stack/Constants.hs
@@ -22,6 +22,7 @@
     ,stackProgName
     ,wiredInPackages
     ,cabalPackageName
+    ,implicitGlobalDir
     )
     where
 
@@ -214,3 +215,10 @@
 cabalPackageName :: PackageName
 cabalPackageName =
     $(mkPackageName "Cabal")
+
+-- | Implicit global directory used when outside of a project.
+implicitGlobalDir :: Path Abs Dir -- ^ Stack root.
+                  -> Path Abs Dir
+implicitGlobalDir p =
+    p </>
+    $(mkRelDir "global")
diff --git a/src/Stack/Docker.hs b/src/Stack/Docker.hs
--- a/src/Stack/Docker.hs
+++ b/src/Stack/Docker.hs
@@ -22,7 +22,7 @@
 import           Control.Applicative
 import           Control.Exception.Lifted
 import           Control.Monad
-import           Control.Monad.Catch (MonadThrow, throwM)
+import           Control.Monad.Catch (MonadThrow, throwM, MonadCatch)
 import           Control.Monad.IO.Class (MonadIO,liftIO)
 import           Control.Monad.Logger (MonadLogger,logError,logInfo,logWarn)
 import           Control.Monad.Writer (execWriter,runWriter,tell)
@@ -69,8 +69,9 @@
 
 -- | If Docker is enabled, re-runs the currently running OS command in a Docker container.
 -- Otherwise, runs the inner action.
-rerunWithOptionalContainer :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
-                           => Config -> Maybe (Path Abs Dir) -> IO () -> m ()
+rerunWithOptionalContainer
+    :: (MonadLogger m, MonadIO m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)
+    => Config -> Maybe (Path Abs Dir) -> IO () -> m ()
 rerunWithOptionalContainer config mprojectRoot =
   rerunCmdWithOptionalContainer config mprojectRoot getCmdArgs
   where
@@ -89,12 +90,13 @@
 
 -- | If Docker is enabled, re-runs the OS command returned by the second argument in a
 -- Docker container.  Otherwise, runs the inner action.
-rerunCmdWithOptionalContainer :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
-                              => Config
-                              -> Maybe (Path Abs Dir)
-                              -> IO (FilePath,[String],Config)
-                              -> IO ()
-                              -> m ()
+rerunCmdWithOptionalContainer
+    :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m, MonadCatch m)
+    => Config
+    -> Maybe (Path Abs Dir)
+    -> IO (FilePath,[String],Config)
+    -> IO ()
+    -> m ()
 rerunCmdWithOptionalContainer config mprojectRoot getCmdArgs inner =
   do inContainer <- getInContainer
      if inContainer || not (dockerEnable (configDocker config))
@@ -104,11 +106,12 @@
 
 -- | If Docker is enabled, re-runs the OS command returned by the second argument in a
 -- Docker container.  Otherwise, runs the inner action.
-rerunCmdWithRequiredContainer :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
-                              => Config
-                              -> Maybe (Path Abs Dir)
-                              -> IO (FilePath,[String],Config)
-                              -> m ()
+rerunCmdWithRequiredContainer
+    :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m, MonadCatch m)
+    => Config
+    -> Maybe (Path Abs Dir)
+    -> IO (FilePath,[String],Config)
+    -> m ()
 rerunCmdWithRequiredContainer config mprojectRoot getCmdArgs =
   do when (not (dockerEnable (configDocker config)))
           (throwM DockerMustBeEnabledException)
@@ -132,12 +135,12 @@
        Just _ -> return True
 
 -- | Run a command in a new Docker container, then exit the process.
-runContainerAndExit :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
+runContainerAndExit :: (MonadLogger m, MonadIO m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)
                     => Config
                     -> Maybe (Path Abs Dir)
                     -> FilePath
                     -> [String]
-                    -> [(String,String)]
+                    -> [(String, String)]
                     -> IO ()
                     -> m ()
 runContainerAndExit config
@@ -272,7 +275,7 @@
     docker = configDocker config
 
 -- | Clean-up old docker images and containers.
-cleanup :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
+cleanup :: (MonadLogger m, MonadIO m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)
         => Config -> CleanupOpts -> m ()
 cleanup config opts =
   do envOverride <- getEnvOverride (configPlatform config)
@@ -338,8 +341,9 @@
                         | otherwise -> throwM (InvalidCleanupCommandException line)
              e <- try (readDockerProcess envOverride args)
              case e of
-               Left (ProcessExitedUnsuccessfully _ _) ->
+               Left (ReadProcessException _ _ _ _) ->
                  $logError (concatT ["Could not remove: '",v,"'"])
+               Left e' -> throwM e'
                Right _ -> return ()
         _ -> throwM (InvalidCleanupCommandException line)
     parseImagesOut = Map.fromListWith (++) . map parseImageRepo . drop 1 . lines . decodeUtf8
@@ -496,7 +500,7 @@
     containerStr = "container"
 
 -- | Inspect Docker image or container.
-inspect :: (MonadIO m,MonadThrow m,MonadLogger m,MonadBaseControl IO m)
+inspect :: (MonadIO m,MonadThrow m,MonadLogger m,MonadBaseControl IO m,MonadCatch m)
         => EnvOverride -> String -> m (Maybe Inspect)
 inspect envOverride image =
   do results <- inspects envOverride [image]
@@ -506,7 +510,7 @@
        _ -> throwM (InvalidInspectOutputException "expect a single result")
 
 -- | Inspect multiple Docker images and/or containers.
-inspects :: (MonadIO m,MonadThrow m,MonadLogger m,MonadBaseControl IO m)
+inspects :: (MonadIO m, MonadThrow m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
          => EnvOverride -> [String] -> m (Map String Inspect)
 inspects _ [] = return Map.empty
 inspects envOverride images =
@@ -518,10 +522,11 @@
          case eitherDecode (LBS.pack (filter isAscii (decodeUtf8 inspectOut))) of
            Left msg -> throwM (InvalidInspectOutputException msg)
            Right results -> return (Map.fromList (map (\r -> (iiId r,r)) results))
-       Left (ProcessExitedUnsuccessfully _ _) -> return Map.empty
+       Left (ReadProcessException _ _ _ _) -> return Map.empty
+       Left e -> throwM e
 
 -- | Pull latest version of configured Docker image from registry.
-pull :: (MonadLogger m,MonadIO m,MonadThrow m,MonadBaseControl IO m)
+pull :: (MonadLogger m, MonadIO m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)
      => Config -> m ()
 pull config =
   do envOverride <- getEnvOverride (configPlatform config)
@@ -551,8 +556,9 @@
        Right () -> return ()
 
 -- | Check docker version (throws exception if incorrect)
-checkDockerVersion :: (MonadIO m,MonadThrow m,MonadLogger m,MonadBaseControl IO m)
-                   => EnvOverride -> m ()
+checkDockerVersion
+    :: (MonadIO m, MonadThrow m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+    => EnvOverride -> m ()
 checkDockerVersion envOverride =
   do dockerExists <- doesExecutableExist envOverride "docker"
      unless dockerExists (throwM DockerNotInstalledException)
@@ -613,14 +619,13 @@
                                   (removeFile (toFilePath f))))
 
 -- | Produce a strict 'S.ByteString' from the stdout of a
--- process. Throws a 'ProcessExitedUnsuccessfully' exception if the
+-- process. Throws a 'ReadProcessException' exception if the
 -- process fails.  Logs process's stderr using @$logError@.
-readDockerProcess :: (MonadIO m,MonadLogger m,MonadBaseControl IO m)
-                  => EnvOverride
-                  -> [String]
-                  -> m BS.ByteString
+readDockerProcess
+    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+    => EnvOverride -> [String] -> m BS.ByteString
 readDockerProcess envOverride args =
-  readProcessStdoutLogStderr "docker: " Nothing envOverride "docker" args
+  readProcessStdout Nothing envOverride "docker" args
 
 -- | Subdirectories of the home directory to sandbox between GHC/Stackage versions.
 sandboxedHomeSubdirectories :: [Path Rel Dir]
diff --git a/src/Stack/Exec.hs b/src/Stack/Exec.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Exec.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Execute commands within the properly configured Stack
+-- environment.
+
+module Stack.Exec where
+
+import           Control.Monad.Reader
+import           Control.Monad.Logger
+import           Control.Monad.Catch
+
+
+import           Path
+import           Stack.Types
+import           System.Exit
+import qualified System.Process as P
+import           System.Process.Read
+
+
+-- | Execute a process within the Stack configured environment.
+exec :: (HasConfig r, MonadReader r m, MonadIO m, MonadLogger m, MonadThrow m)
+        => String -> [String] -> m b
+exec cmd args = do
+    config <- asks getConfig
+    menv <- liftIO (configEnvOverride config
+                            EnvSettings
+                                { esIncludeLocals = True
+                                , esIncludeGhcPackagePath = True
+                                })
+    cmd' <- join $ System.Process.Read.findExecutable menv cmd
+    let cp = (P.proc (toFilePath cmd') args)
+            { P.env = envHelper menv
+            , P.delegate_ctlc = True
+            }
+    $logProcessRun (toFilePath cmd') args
+    (Nothing, Nothing, Nothing, ph) <- liftIO (P.createProcess cp)
+    ec <- liftIO (P.waitForProcess ph)
+    liftIO (exitWith ec)
diff --git a/src/Stack/Fetch.hs b/src/Stack/Fetch.hs
--- a/src/Stack/Fetch.hs
+++ b/src/Stack/Fetch.hs
@@ -14,6 +14,7 @@
 module Stack.Fetch
     ( unpackPackages
     , unpackPackageIdents
+    , fetchPackages
     , resolvePackages
     , ResolvedPackage (..)
     , withCabalFiles
@@ -29,6 +30,7 @@
 import           Control.Concurrent.STM          (TVar, atomically, modifyTVar,
                                                   newTVarIO, readTVar,
                                                   readTVarIO, writeTVar)
+import           Control.Exception (assert)
 import           Control.Monad (liftM, when, join, unless, void)
 import           Control.Monad.Catch
 import           Control.Monad.IO.Class
@@ -107,8 +109,20 @@
         "The following package identifiers were not found in your indices: " ++
         intercalate ", " (map packageIdentifierString $ Set.toList idents)
 
+-- | Fetch packages into the cache without unpacking
+fetchPackages :: (MonadIO m, MonadBaseControl IO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadLogger m, MonadCatch m)
+              => EnvOverride
+              -> Set PackageIdentifier
+              -> m ()
+fetchPackages menv idents = do
+    resolved <- resolvePackages menv idents Set.empty
+    ToFetchResult toFetch alreadyUnpacked <- getToFetch Nothing resolved
+    assert (Map.null alreadyUnpacked) (return ())
+    nowUnpacked <- fetchPackages' Nothing toFetch
+    assert (Map.null nowUnpacked) (return ())
+
 -- | Intended to work for the command line command.
-unpackPackages :: (MonadIO m,MonadBaseControl IO m,MonadReader env m,HasHttpManager env,HasConfig env,MonadThrow m,MonadLogger m)
+unpackPackages :: (MonadIO m, MonadBaseControl IO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadLogger m, MonadCatch m)
                => EnvOverride
                -> FilePath -- ^ destination
                -> [String] -- ^ names or identifiers
@@ -119,10 +133,10 @@
         ([], x) -> return $ partitionEithers x
         (errs, _) -> throwM $ CouldNotParsePackageSelectors errs
     resolved <- resolvePackages menv (Set.fromList idents) (Set.fromList names)
-    ToFetchResult toFetch alreadyUnpacked <- getToFetch dest' resolved
+    ToFetchResult toFetch alreadyUnpacked <- getToFetch (Just dest') resolved
     unless (Map.null alreadyUnpacked) $
         throwM $ UnpackDirectoryAlreadyExists $ Set.fromList $ map toFilePath $ Map.elems alreadyUnpacked
-    unpacked <- fetchPackages Nothing toFetch
+    unpacked <- fetchPackages' Nothing toFetch
     F.forM_ (Map.toList unpacked) $ \(ident, dest'') -> $logInfo $ T.pack $ concat
         [ "Unpacked "
         , packageIdentifierString ident
@@ -142,7 +156,7 @@
 -- | Ensure that all of the given package idents are unpacked into the build
 -- unpack directory, and return the paths to all of the subdirectories.
 unpackPackageIdents
-    :: (MonadBaseControl IO m, MonadIO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadLogger m)
+    :: (MonadBaseControl IO m, MonadIO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadThrow m, MonadLogger m, MonadCatch m)
     => EnvOverride
     -> Path Abs Dir -- ^ unpack directory
     -> Maybe (Path Rel Dir) -- ^ the dist rename directory, see: https://github.com/fpco/stack/issues/157
@@ -150,8 +164,8 @@
     -> m (Map PackageIdentifier (Path Abs Dir))
 unpackPackageIdents menv unpackDir mdistDir idents = do
     resolved <- resolvePackages menv idents Set.empty
-    ToFetchResult toFetch alreadyUnpacked <- getToFetch unpackDir resolved
-    nowUnpacked <- fetchPackages mdistDir toFetch
+    ToFetchResult toFetch alreadyUnpacked <- getToFetch (Just unpackDir) resolved
+    nowUnpacked <- fetchPackages' mdistDir toFetch
     return $ alreadyUnpacked <> nowUnpacked
 
 data ResolvedPackage = ResolvedPackage
@@ -160,7 +174,7 @@
     }
 
 -- | Resolve a set of package names and identifiers into @FetchPackage@ values.
-resolvePackages :: (MonadIO m,MonadReader env m,HasHttpManager env,HasConfig env,MonadLogger m,MonadThrow m,MonadBaseControl IO m)
+resolvePackages :: (MonadIO m, MonadReader env m, HasHttpManager env, HasConfig env, MonadLogger m, MonadThrow m, MonadBaseControl IO m, MonadCatch m)
                 => EnvOverride
                 -> Set PackageIdentifier
                 -> Set PackageName
@@ -199,7 +213,7 @@
 
 data ToFetch = ToFetch
     { tfTarball :: !(Path Abs File)
-    , tfDestDir :: !(Path Abs Dir)
+    , tfDestDir :: !(Maybe (Path Abs Dir))
     , tfUrl     :: !T.Text
     , tfSize    :: !(Maybe Word64)
     , tfSHA512  :: !(Maybe ByteString)
@@ -232,7 +246,7 @@
 -- | Provide a function which will load up a cabal @ByteString@ from the
 -- package indices.
 withCabalLoader
-    :: (MonadThrow m, MonadIO m, MonadReader env m, HasConfig env, MonadLogger m, HasHttpManager env, MonadBaseControl IO m)
+    :: (MonadThrow m, MonadIO m, MonadReader env m, HasConfig env, MonadLogger m, HasHttpManager env, MonadBaseControl IO m, MonadCatch m)
     => EnvOverride
     -> ((PackageIdentifier -> IO ByteString) -> m a)
     -> m a
@@ -283,10 +297,10 @@
 
 -- | Figure out where to fetch from.
 getToFetch :: (MonadThrow m, MonadIO m, MonadReader env m, HasConfig env)
-           => Path Abs Dir -- ^ directory to unpack into
+           => Maybe (Path Abs Dir) -- ^ directory to unpack into, @Nothing@ means no unpack
            -> Map PackageIdentifier ResolvedPackage
            -> m ToFetchResult
-getToFetch dest resolvedAll = do
+getToFetch mdest resolvedAll = do
     (toFetch0, unpacked) <- liftM partitionEithers $ mapM checkUnpacked $ Map.toList resolvedAll
     toFetch1 <- mapM goIndex $ Map.toList $ Map.fromListWith (++) toFetch0
     return ToFetchResult
@@ -296,18 +310,23 @@
   where
     checkUnpacked (ident, resolved) = do
         dirRel <- parseRelDir $ packageIdentifierString ident
-        let destDir = dest </> dirRel
-        exists <- liftIO $ doesDirectoryExist $ toFilePath destDir
-        if exists
-            then return $ Right (ident, destDir)
-            else do
+        let mdestDir = (</> dirRel) <$> mdest
+        mexists <-
+            case mdestDir of
+                Nothing -> return Nothing
+                Just destDir -> do
+                    exists <- liftIO $ doesDirectoryExist $ toFilePath destDir
+                    return $ if exists then Just destDir else Nothing
+        case mexists of
+            Just destDir -> return $ Right (ident, destDir)
+            Nothing -> do
                 let index = rpIndex resolved
                     d = pcDownload $ rpCache resolved
                     targz = T.pack $ packageIdentifierString ident ++ ".tar.gz"
                 tarball <- configPackageTarball (indexName index) ident
                 return $ Left (indexName index, [(ident, rpCache resolved, ToFetch
                     { tfTarball = tarball
-                    , tfDestDir = destDir
+                    , tfDestDir = mdestDir
                     , tfUrl = case d of
                         Just d' -> decodeUtf8 $ pdUrl d'
                         Nothing -> indexDownloadPrefix index <> targz
@@ -335,11 +354,11 @@
 -- @
 --
 -- Since 0.1.0.0
-fetchPackages :: (MonadIO m,MonadReader env m,HasHttpManager env,HasConfig env,MonadLogger m,MonadThrow m,MonadBaseControl IO m)
-              => Maybe (Path Rel Dir) -- ^ the dist rename directory, see: https://github.com/fpco/stack/issues/157
-              -> Map PackageIdentifier ToFetch
-              -> m (Map PackageIdentifier (Path Abs Dir))
-fetchPackages mdistDir toFetchAll = do
+fetchPackages' :: (MonadIO m,MonadReader env m,HasHttpManager env,HasConfig env,MonadLogger m,MonadThrow m,MonadBaseControl IO m)
+               => Maybe (Path Rel Dir) -- ^ the dist rename directory, see: https://github.com/fpco/stack/issues/157
+               -> Map PackageIdentifier ToFetch
+               -> m (Map PackageIdentifier (Path Abs Dir))
+fetchPackages' mdistDir toFetchAll = do
     connCount <- asks $ configConnectionCount . getConfig
     outputVar <- liftIO $ newTVarIO Map.empty
 
@@ -371,49 +390,51 @@
         _ <- verifiedDownload downloadReq destpath progressSink
 
         let fp = toFilePath destpath
-        let dest = toFilePath $ parent $ tfDestDir toFetch
-            innerDest = toFilePath $ tfDestDir toFetch
 
-        liftIO $ createDirectoryIfMissing True dest
+        F.forM_ (tfDestDir toFetch) $ \destDir -> do
+            let dest = toFilePath $ parent destDir
+                innerDest = toFilePath destDir
 
-        liftIO $ withBinaryFile fp ReadMode $ \h -> do
-            -- Avoid using L.readFile, which is more likely to leak
-            -- resources
-            lbs <- L.hGetContents h
-            let entries = fmap (either wrap wrap)
-                        $ Tar.checkTarbomb identStr
-                        $ Tar.read $ decompress lbs
-                wrap :: Exception e => e -> FetchException
-                wrap = Couldn'tReadPackageTarball fp . toException
-                identStr = packageIdentifierString ident
-            Tar.unpack dest entries
+            liftIO $ createDirectoryIfMissing True dest
 
-            case mdistDir of
-                Nothing -> return ()
-                -- See: https://github.com/fpco/stack/issues/157
-                Just distDir -> do
-                    let inner = dest FP.</> identStr
-                        oldDist = inner FP.</> "dist"
-                        newDist = inner FP.</> toFilePath distDir
-                    exists <- doesDirectoryExist oldDist
-                    when exists $ do
-                        -- Previously used takeDirectory, but that got confused
-                        -- by trailing slashes, see:
-                        -- https://github.com/commercialhaskell/stack/issues/216
-                        --
-                        -- Instead, use Path which is a bit more resilient
-                        newDist' <- parseAbsDir newDist
-                        createDirectoryIfMissing True
-                            $ toFilePath $ parent newDist'
-                        renameDirectory oldDist newDist
+            liftIO $ withBinaryFile fp ReadMode $ \h -> do
+                -- Avoid using L.readFile, which is more likely to leak
+                -- resources
+                lbs <- L.hGetContents h
+                let entries = fmap (either wrap wrap)
+                            $ Tar.checkTarbomb identStr
+                            $ Tar.read $ decompress lbs
+                    wrap :: Exception e => e -> FetchException
+                    wrap = Couldn'tReadPackageTarball fp . toException
+                    identStr = packageIdentifierString ident
+                Tar.unpack dest entries
 
-            let cabalFP =
-                    innerDest FP.</>
-                    packageNameString (packageIdentifierName ident)
-                    <.> "cabal"
-            S.writeFile cabalFP $ tfCabal toFetch
+                case mdistDir of
+                    Nothing -> return ()
+                    -- See: https://github.com/fpco/stack/issues/157
+                    Just distDir -> do
+                        let inner = dest FP.</> identStr
+                            oldDist = inner FP.</> "dist"
+                            newDist = inner FP.</> toFilePath distDir
+                        exists <- doesDirectoryExist oldDist
+                        when exists $ do
+                            -- Previously used takeDirectory, but that got confused
+                            -- by trailing slashes, see:
+                            -- https://github.com/commercialhaskell/stack/issues/216
+                            --
+                            -- Instead, use Path which is a bit more resilient
+                            newDist' <- parseAbsDir newDist
+                            createDirectoryIfMissing True
+                                $ toFilePath $ parent newDist'
+                            renameDirectory oldDist newDist
 
-            atomically $ modifyTVar outputVar $ Map.insert ident $ tfDestDir toFetch
+                let cabalFP =
+                        innerDest FP.</>
+                        packageNameString (packageIdentifierName ident)
+                        <.> "cabal"
+                S.writeFile cabalFP $ tfCabal toFetch
+
+                atomically $ modifyTVar outputVar $ Map.insert ident destDir
 
 parMapM_ :: (F.Foldable f,MonadIO m,MonadBaseControl IO m)
          => Int
diff --git a/src/Stack/GhcPkg.hs b/src/Stack/GhcPkg.hs
--- a/src/Stack/GhcPkg.hs
+++ b/src/Stack/GhcPkg.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE DeriveDataTypeable #-}
@@ -20,14 +21,13 @@
 import           Control.Monad.Catch
 import           Control.Monad.IO.Class
 import           Control.Monad.Logger
+import           Control.Monad.Trans.Control
 import qualified Data.ByteString.Char8 as S8
 import           Data.Either
 import           Data.List
 import           Data.Maybe
-import           Data.Streaming.Process
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
-import           Data.Time.Clock
 import           Path (Path, Abs, Dir, toFilePath, parent, parseAbsDir)
 import           Prelude hiding (FilePath)
 import           Stack.Build.Types (StackBuildException (Couldn'tFindPkgId))
@@ -37,9 +37,8 @@
 import           System.Process.Read
 
 -- | Get the global package database
-getGlobalDB :: (MonadIO m, MonadLogger m, MonadThrow m)
-            => EnvOverride
-            -> m (Path Abs Dir)
+getGlobalDB :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)
+            => EnvOverride -> m (Path Abs Dir)
 getGlobalDB menv = do
     -- This seems like a strange way to get the global package database
     -- location, but I don't know of a better one
@@ -54,33 +53,26 @@
     firstLine = S8.takeWhile (\c -> c /= '\r' && c /= '\n')
 
 -- | Run the ghc-pkg executable
-ghcPkg :: (MonadIO m, MonadLogger m)
+ghcPkg :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)
        => EnvOverride
        -> [Path Abs Dir]
        -> [String]
-       -> m (Either ProcessExitedUnsuccessfully S8.ByteString)
+       -> m (Either ReadProcessException S8.ByteString)
 ghcPkg menv pkgDbs args = do
-    start <- liftIO getCurrentTime
     eres <- go
     r <- case eres of
             Left _ -> do
                 mapM_ (createDatabase menv) pkgDbs
                 go
             Right _ -> return eres
-    end <- liftIO getCurrentTime
-    $logDebug $ T.concat
-        [ "ghc-pkg ("
-        , T.pack $ show $ diffUTCTime end start
-        , "s) with args "
-        , T.pack $ show args'
-        ]
     return r
   where
     go = tryProcessStdout Nothing menv "ghc-pkg" args'
     args' = packageDbFlags pkgDbs ++ args
 
 -- | Create a package database in the given directory, if it doesn't exist.
-createDatabase :: (MonadIO m, MonadLogger m) => EnvOverride -> Path Abs Dir -> m ()
+createDatabase :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)
+               => EnvOverride -> Path Abs Dir -> m ()
 createDatabase menv db = do
     let db' = toFilePath db
     exists <- liftIO $ doesDirectoryExist db'
@@ -99,7 +91,7 @@
         : map (\x -> ("--package-db=" ++ toFilePath x)) pkgDbs
 
 -- | Get the id of the package e.g. @foo-0.0.0-9c293923c0685761dcff6f8c3ad8f8ec@.
-findGhcPkgId :: (MonadIO m, MonadLogger m)
+findGhcPkgId :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)
              => EnvOverride
              -> [Path Abs Dir] -- ^ package databases
              -> PackageName
@@ -128,7 +120,7 @@
     stripCR t =
         fromMaybe t (T.stripSuffix "\r" t)
 
-unregisterGhcPkgId :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m)
+unregisterGhcPkgId :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m, MonadBaseControl IO m)
                     => EnvOverride
                     -> Path Abs Dir -- ^ package database
                     -> GhcPkgId
@@ -143,7 +135,7 @@
     args = ["unregister", "--user", "--force", packageIdentifierString $ ghcPkgIdPackageIdentifier gid]
 
 -- | Get the version of Cabal from the global package database.
-getCabalPkgVer :: (MonadThrow m,MonadIO m,MonadLogger m)
+getCabalPkgVer :: (MonadThrow m, MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
                => EnvOverride -> m Version
 getCabalPkgVer menv = do
     db <- getGlobalDB menv -- FIXME shouldn't be necessary, just tell ghc-pkg to look in the global DB
diff --git a/src/Stack/Init.hs b/src/Stack/Init.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Init.hs
@@ -0,0 +1,229 @@
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE TemplateHaskell       #-}
+module Stack.Init
+    ( findCabalFiles
+    , initProject
+    , InitOpts (..)
+    , initOptsParser
+    , readResolver
+    ) where
+
+import           Control.Exception               (assert)
+import           Control.Exception.Enclosed      (handleIO)
+import           Control.Monad                   (liftM, when)
+import           Control.Monad.Catch             (MonadCatch, SomeException,
+                                                  catch, throwM)
+import           Control.Monad.IO.Class
+import           Control.Monad.Logger
+import           Control.Monad.Reader            (MonadReader)
+import           Control.Monad.Trans.Control     (MonadBaseControl)
+import qualified Data.IntMap                     as IntMap
+import           Data.List                       (sort)
+import           Data.List                       (isSuffixOf)
+import           Data.Map                        (Map)
+import qualified Data.Map                        as Map
+import           Data.Maybe                      (mapMaybe)
+import           Data.Monoid
+import           Data.Set                        (Set)
+import qualified Data.Set                        as Set
+import qualified Data.Text                       as T
+import qualified Data.Yaml                       as Yaml
+import qualified Distribution.PackageDescription as C
+import           Network.HTTP.Client.Conduit     (HasHttpManager)
+import           Options.Applicative
+import           Options.Applicative.Types       (readerAsk)
+import           Path
+import           Path.Find
+import           Path.IO
+import           Stack.BuildPlan
+import           Stack.Constants
+import           Stack.Package
+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)
+  where
+    isCabal path = ".cabal" `isSuffixOf` toFilePath path
+
+    isIgnored path = toFilePath (dirname path) `Set.member` ignoredDirs
+
+-- | Special directories that we don't want to traverse for .cabal files
+ignoredDirs :: Set FilePath
+ignoredDirs = Set.fromList
+    [ ".git"
+    , "dist"
+    , ".stack-work"
+    ]
+
+-- | Generate stack.yaml
+initProject :: (MonadIO m, MonadCatch m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m)
+            => Maybe Resolver -- ^ force this resolver to be used
+            -> InitOpts
+            -> m ()
+initProject mresolver initOpts = do
+    currDir <- getWorkingDir
+    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"
+
+    cabalfps <- findCabalFiles currDir
+    $logInfo $ "Writing default config file to: " <> T.pack dest'
+    $logInfo $ "Basing on cabal files:"
+    mapM_ (\path -> $logInfo $ "- " <> T.pack (toFilePath path)) cabalfps
+    $logInfo ""
+
+    when (null cabalfps) $ error "In order to init, you should have an existing .cabal file. Please try \"stack new\" instead"
+    gpds <- mapM readPackageUnresolved cabalfps
+
+    (r, flags) <- getDefaultResolver gpds mresolver initOpts
+    let p = Project
+            { projectPackages = pkgs
+            , projectExtraDeps = Map.empty
+            , projectFlags = flags
+            , projectResolver = r
+            }
+        pkgs = map toPkg cabalfps
+        toPkg fp = PackageEntry
+            { peValidWanted = True
+            , peLocation = PLFilePath $
+                case stripDir currDir $ parent fp of
+                    Nothing
+                        | currDir == parent fp -> "."
+                        | otherwise -> assert False $ toFilePath $ parent fp
+                    Just rel -> toFilePath rel
+            , peSubdirs = []
+            }
+    $logInfo $ "Selected resolver: " <> renderResolver r
+    liftIO $ Yaml.encodeFile dest' p
+    $logInfo $ "Wrote project config to: " <> T.pack dest'
+
+getSnapshots' :: (MonadIO m, MonadCatch m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m)
+              => m Snapshots
+getSnapshots' =
+    getSnapshots `catch` \e -> do
+        $logError $
+            "Unable to download snapshot list, and therefore could " <>
+            "not generate a stack.yaml file automatically"
+        $logError $
+            "This sometimes happens due to missing Certificate Authorities " <>
+            "on your system. For more information, see:"
+        $logError ""
+        $logError "    https://github.com/commercialhaskell/stack/issues/234"
+        $logError ""
+        $logError "You can try again, or create your stack.yaml file by hand. See:"
+        $logError ""
+        $logError "    https://github.com/commercialhaskell/stack/wiki/stack.yaml"
+        $logError ""
+        throwM (e :: SomeException)
+
+-- | Get the default resolver value
+getDefaultResolver :: (MonadIO m, MonadCatch m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m)
+                   => [C.GenericPackageDescription] -- ^ cabal files
+                   -> Maybe Resolver -- ^ resolver override
+                   -> InitOpts
+                   -> m (Resolver, Map PackageName (Map FlagName Bool))
+getDefaultResolver gpds mresolver initOpts = do
+    names <-
+        case mresolver of
+            Nothing -> do
+                snapshots <- getSnapshots'
+                getRecommendedSnapshots snapshots initOpts
+            Just resolver ->
+                return $
+                    case resolver of
+                        ResolverSnapshot name -> [name]
+                        ResolverGhc _ -> []
+    mpair <- findBuildPlan gpds names
+    case mpair of
+        Just (snap, flags) ->
+            return (ResolverSnapshot snap, flags)
+        Nothing ->
+            case mresolver of
+                Nothing ->
+                    case ioFallback initOpts of
+                        Nothing -> throwM $ NoMatchingSnapshot names
+                        Just resolver -> return (resolver, Map.empty)
+                Just resolver -> return (resolver, Map.empty)
+
+getRecommendedSnapshots :: (MonadIO m, MonadCatch m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m)
+                        => Snapshots
+                        -> InitOpts
+                        -> m [SnapName]
+getRecommendedSnapshots snapshots initOpts = do
+    -- Get the most recent LTS and Nightly in the snapshots directory and
+    -- prefer them over anything else, since odds are high that something
+    -- already exists for them.
+    existing <-
+        liftM (reverse . sort . mapMaybe (parseSnapName . T.pack)) $
+        snapshotsDir >>=
+        liftIO . handleIO (const $ return [])
+               . getDirectoryContents . toFilePath
+    let isLTS LTS{} = True
+        isLTS Nightly{} = False
+        isNightly Nightly{} = True
+        isNightly LTS{} = False
+
+        names = nubOrd $ concat
+            [ take 2 $ filter isLTS existing
+            , take 2 $ filter isNightly existing
+            , map (uncurry LTS)
+                (take 2 $ reverse $ IntMap.toList $ snapshotsLts snapshots)
+            , [Nightly $ snapshotsNightly snapshots]
+            ]
+
+        namesLTS = filter isLTS names
+        namesNightly = filter isNightly names
+
+    case ioPref initOpts of
+        PrefNone -> return names
+        PrefLTS -> return $ namesLTS ++ namesNightly
+        PrefNightly -> return $ namesNightly ++ namesLTS
+
+data InitOpts = InitOpts
+    { ioPref     :: !SnapPref
+    -- ^ Preferred snapshots
+    , ioFallback :: !(Maybe Resolver)
+    }
+
+data SnapPref = PrefNone | PrefLTS | PrefNightly
+
+initOptsParser :: Parser InitOpts
+initOptsParser = InitOpts
+    <$> pref
+    <*> optional fallback
+  where
+    pref =
+        flag' PrefLTS
+            (long "prefer-lts" <>
+             help "Prefer LTS snapshots over Nightly snapshots") <|>
+        flag' PrefNightly
+            (long "prefer-nightly" <>
+             help "Prefer Nightly snapshots over LTS snapshots") <|>
+        pure PrefNone
+
+    fallback = option readResolver
+        (long "fallback" <>
+         metavar "RESOLVER" <>
+         help "Fallback resolver if none of the tested snapshots work")
+
+readResolver :: ReadM Resolver
+readResolver = do
+    s <- readerAsk
+    case parseResolver $ T.pack s of
+        Left e -> readerError $ show e
+        Right x -> return x
+
+-- | Same semantics as @nub@, but more efficient by using the @Ord@ constraint.
+nubOrd :: Ord a => [a] -> [a]
+nubOrd =
+    go Set.empty
+  where
+    go _ [] = []
+    go s (x:xs)
+        | x `Set.member` s = go s xs
+        | otherwise = x : go (Set.insert x s) xs
diff --git a/src/Stack/New.hs b/src/Stack/New.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/New.hs
@@ -0,0 +1,8 @@
+module Stack.New
+    ( newProject
+    ) where
+
+import Control.Monad.IO.Class
+
+newProject :: MonadIO m => m ()
+newProject = error "new command not yet implemented, check out https://github.com/commercialhaskell/stack/issues/137 for status and to get involved"
diff --git a/src/Stack/Package.hs b/src/Stack/Package.hs
--- a/src/Stack/Package.hs
+++ b/src/Stack/Package.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE DataKinds #-}
@@ -20,6 +21,7 @@
   ,getCabalFileName
   ,Package(..)
   ,GetPackageFiles(..)
+  ,GetPackageOpts(..)
   ,PackageConfig(..)
   ,buildLogPath
   ,PackageException (..)
@@ -53,13 +55,14 @@
 import           Data.Text.Encoding.Error (lenientDecode)
 import           Distribution.Compiler
 import           Distribution.InstalledPackageInfo (PError)
-import qualified Distribution.ModuleName as Cabal
 import           Distribution.ModuleName (ModuleName)
+import qualified Distribution.ModuleName as Cabal
 import           Distribution.Package hiding (Package,PackageName,packageName,packageVersion,PackageIdentifier)
 import           Distribution.PackageDescription hiding (FlagName)
 import           Distribution.PackageDescription.Parse
 import           Distribution.Simple.Utils
 import           Distribution.System (OS, Arch, Platform (..))
+import           Distribution.Text (display)
 import           Distribution.Version (intersectVersionRanges)
 import           Path as FL
 import           Path.Find
@@ -117,11 +120,22 @@
           ,packageTests :: !(Set Text)                    -- ^ names of test suites
           ,packageBenchmarks :: !(Set Text)               -- ^ names of benchmarks
           ,packageExes :: !(Set Text)                     -- ^ names of executables
+          ,packageOpts :: !GetPackageOpts              -- ^ Args to pass to GHC.
           }
  deriving (Show,Typeable)
 
 -- | Files that the package depends on, relative to package directory.
 -- Argument is the location of the .cabal file
+newtype GetPackageOpts = GetPackageOpts
+    { getPackageOpts :: forall env m. (MonadIO m,HasEnvConfig env, HasPlatform env, MonadThrow m, MonadReader env m)
+                     => Path Abs File
+                     -> m [String]
+    }
+instance Show GetPackageOpts where
+    show _ = "<GetPackageOpts>"
+
+-- | Files that the package depends on, relative to package directory.
+-- Argument is the location of the .cabal file
 newtype GetPackageFiles = GetPackageFiles
     { getPackageFiles :: forall m. (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m)
                       => Path Abs File
@@ -227,6 +241,8 @@
     , packageTests = S.fromList $ [ T.pack (testName t) | t <- testSuites pkg, buildable (testBuildInfo t)]
     , packageBenchmarks = S.fromList $ [ T.pack (benchmarkName b) | b <- benchmarks pkg, buildable (benchmarkBuildInfo b)]
     , packageExes = S.fromList $ [ T.pack (exeName b) | b <- executables pkg, buildable (buildInfo b)]
+    , packageOpts = GetPackageOpts $ \cabalfp ->
+        generatePkgDescOpts cabalfp pkg
     }
 
   where
@@ -235,6 +251,66 @@
     pkg = resolvePackageDescription packageConfig gpkg
     deps = M.filterWithKey (const . (/= name)) (packageDependencies pkg)
 
+-- | Generate GHC options for the package.
+generatePkgDescOpts :: (HasEnvConfig env, HasPlatform env, MonadThrow m, MonadReader env m, MonadIO m)
+                    => Path Abs File -> PackageDescription -> m [String]
+generatePkgDescOpts cabalfp pkg =
+    do distDir <- distDirFromDir cabalDir
+       let cabalmacros = autogenDir distDir </> $(mkRelFile "cabal_macros.h")
+       exists <- fileExists cabalmacros
+       let mcabalmacros =
+               if exists
+                  then Just cabalmacros
+                  else Nothing
+       return (nub
+                   (concatMap
+                        (concatMap (generateBuildInfoOpts mcabalmacros cabalDir distDir))
+                        [ maybe
+                              []
+                              (return . libBuildInfo)
+                              (library pkg)
+                        , map buildInfo (executables pkg)
+                        , map benchmarkBuildInfo (benchmarks pkg)
+                        , map testBuildInfo (testSuites pkg)]))
+  where cabalDir =
+            parent cabalfp
+
+-- | Generate GHC options for the target.
+generateBuildInfoOpts :: Maybe (Path Abs File) -> Path Abs Dir -> Path Abs Dir -> BuildInfo -> [String]
+generateBuildInfoOpts mcabalmacros cabalDir distDir b =
+    nub (concat [ghcOpts b, extOpts b, srcOpts, macros])
+  where
+    macros =
+        case mcabalmacros of
+            Nothing ->
+                []
+            Just cabalmacros ->
+                ["-optP-include", "-optP" <> toFilePath cabalmacros]
+    ghcOpts =
+        concatMap snd .
+        filter (isGhc . fst) .
+        options
+      where
+        isGhc GHC =
+            True
+        isGhc _ =
+            False
+    extOpts =
+        map (("-X" ++) . display) .
+        allExtensions
+    srcOpts =
+        map
+            (("-i" <>) . toFilePath)
+            (cabalDir :
+             map (cabalDir </>) (mapMaybe parseRelDir (hsSourceDirs b)) <>
+             [autogenDir distDir])
+
+-- | Make the autogen dir.
+autogenDir :: Path Abs Dir -> Path Abs Dir
+autogenDir distDir =
+        distDir </>
+        $(mkRelDir "build/autogen")
+
 -- | Get all dependencies of the package (buildable targets only).
 packageDependencies :: PackageDescription -> Map PackageName VersionRange
 packageDependencies =
@@ -286,6 +362,12 @@
        liftM concat
              (mapM executableFiles
                    (executables pkg))
+     benchfiles <-
+         liftM concat
+               (mapM benchmarkFiles (benchmarks pkg))
+     testfiles <-
+         liftM concat
+               (mapM testFiles (testSuites pkg))
      dfiles <-
        resolveGlobFiles (map (dataDir pkg FilePath.</>) (dataFiles pkg))
      srcfiles <-
@@ -295,7 +377,7 @@
      -- don't error out if not present
      docfiles <-
        resolveGlobFiles (extraDocFiles pkg)
-     return (concat [libfiles,exefiles,dfiles,srcfiles,docfiles])
+     return (concat [libfiles,exefiles,dfiles,srcfiles,docfiles,benchfiles,testfiles])
 
 -- | Resolve globbing of files (e.g. data files) to absolute paths.
 resolveGlobFiles :: (MonadLogger m,MonadIO m,MonadThrow m,MonadReader (Path Abs File) m,MonadCatch m)
@@ -358,6 +440,48 @@
       []      -> die $ "filepath wildcard '" ++ filepath
                     ++ "' does not match any files."
       matches -> return matches
+
+-- | Get all files referenced by the benchmark.
+benchmarkFiles :: (MonadLogger m, MonadIO m, MonadThrow m, MonadReader (Path Abs File) m)
+               => Benchmark -> m [Path Abs File]
+benchmarkFiles bench = do
+    dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build)
+    dir <- asks parent
+    exposed <-
+        resolveFiles
+            (dirs ++ [dir])
+            (case benchmarkInterface bench of
+                 BenchmarkExeV10 _ fp ->
+                     [Right fp]
+                 BenchmarkUnsupported _ ->
+                     [])
+            haskellFileExts
+    bfiles <- buildFiles dir build
+    return (mconcat [bfiles, exposed])
+  where
+    build = benchmarkBuildInfo bench
+
+-- | Get all files referenced by the test.
+testFiles :: (MonadLogger m, MonadIO m, MonadThrow m, MonadReader (Path Abs File) m)
+          => TestSuite -> m [Path Abs File]
+testFiles test = do
+    dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build)
+    dir <- asks parent
+    exposed <-
+        resolveFiles
+            (dirs ++ [dir])
+            (case testInterface test of
+                 TestSuiteExeV10 _ fp ->
+                     [Right fp]
+                 TestSuiteLibV09 _ mn ->
+                     [Left mn]
+                 TestSuiteUnsupported _ ->
+                     [])
+            haskellFileExts
+    bfiles <- buildFiles dir build
+    return (mconcat [bfiles, exposed])
+  where
+    build = testBuildInfo test
 
 -- | Get all files referenced by the executable.
 executableFiles :: (MonadLogger m,MonadIO m,MonadThrow m,MonadReader (Path Abs File) m)
diff --git a/src/Stack/PackageDump.hs b/src/Stack/PackageDump.hs
--- a/src/Stack/PackageDump.hs
+++ b/src/Stack/PackageDump.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE OverloadedStrings #-}
@@ -21,9 +22,10 @@
 
 import           Control.Applicative
 import           Control.Monad (when, liftM)
-import           Control.Monad.Catch (MonadThrow, Exception, throwM)
+import           Control.Monad.Catch
 import           Control.Monad.IO.Class
-import           Control.Monad.Logger (MonadLogger, logDebug)
+import           Control.Monad.Logger (MonadLogger)
+import           Control.Monad.Trans.Control
 import           Data.Binary.VersionTagged (taggedDecodeOrLoad, taggedEncodeFile)
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as S
@@ -37,12 +39,7 @@
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import           Data.Maybe (catMaybes)
-
 import qualified Data.Set as Set
-import qualified Data.Text as T
-
-
-import           Data.Time.Clock
 import           Data.Typeable (Typeable)
 import           Path
 import           Prelude -- Fix AMP warning
@@ -56,22 +53,14 @@
 
 -- | Call ghc-pkg dump with appropriate flags and stream to the given @Sink@, for a single database
 ghcPkgDump
-    :: (MonadIO m, MonadLogger m)
+    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m, MonadThrow m)
     => EnvOverride
     -> Maybe (Path Abs Dir) -- ^ if Nothing, use global
     -> Sink ByteString IO a
     -> m a
 ghcPkgDump menv mpkgDb sink = do
     F.mapM_ (createDatabase menv) mpkgDb -- TODO maybe use some retry logic instead?
-    start <- liftIO getCurrentTime
     a <- sinkProcessStdout Nothing menv "ghc-pkg" args sink
-    end <- liftIO getCurrentTime
-    $logDebug $ T.concat
-        [ "ghc-pkg ("
-        , T.pack $ show $ diffUTCTime end start
-        , "s) with args "
-        , T.pack $ show args
-        ]
     return a
   where
     args = concat
diff --git a/src/Stack/PackageIndex.hs b/src/Stack/PackageIndex.hs
--- a/src/Stack/PackageIndex.hs
+++ b/src/Stack/PackageIndex.hs
@@ -27,11 +27,12 @@
 import           Control.Exception (Exception)
 import           Control.Exception.Enclosed (tryIO)
 import           Control.Monad (unless, when, liftM, mzero)
-import           Control.Monad.Catch (MonadThrow, throwM)
+import           Control.Monad.Catch (MonadThrow, throwM, MonadCatch)
 import           Control.Monad.IO.Class (MonadIO, liftIO)
 import           Control.Monad.Logger                  (MonadLogger, logDebug,
                                                         logInfo, logWarn)
 import           Control.Monad.Reader (asks)
+import           Control.Monad.Trans.Control
 
 import           Data.Aeson.Extended
 import qualified Data.Binary as Binary
@@ -67,6 +68,7 @@
 import           Path                                  (mkRelDir, parent,
                                                         parseRelDir, toFilePath,
                                                         (</>))
+import           Path.IO
 import           Prelude -- Fix AMP warning
 import           Stack.Types
 import           Stack.Types.StackT
@@ -99,17 +101,20 @@
 instance Binary.Binary PackageCache
 
 -- | Stream all of the cabal files from the 00-index tar file.
-sourcePackageIndex :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m)
-                   => EnvOverride
-                   -> PackageIndex
-                   -> Producer m (Either UnparsedCabalFile (PackageIdentifier, L.ByteString))
-sourcePackageIndex menv index = do
+withSourcePackageIndex
+    :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env, HasHttpManager env, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+    => EnvOverride
+    -> PackageIndex
+    -> (Producer m (Either UnparsedCabalFile (PackageIdentifier, L.ByteString)) -> m a)
+    -> m a
+withSourcePackageIndex menv index cont = do
     requireIndex menv index
     -- This uses full on lazy I/O instead of ResourceT to provide some
     -- protections. Caveat emptor
-    path <- configPackageIndex (indexName index)
-    lbs <- liftIO $ L.readFile $ Path.toFilePath path
-    loop 0 (Tar.read lbs)
+    cont $ do
+        path <- configPackageIndex (indexName index)
+        lbs <- liftIO $ L.readFile $ Path.toFilePath path
+        loop 0 (Tar.read lbs)
   where
     loop blockNo (Tar.Next e es) = do
         goE blockNo e
@@ -171,20 +176,20 @@
 -- | Require that an index be present, updating if it isn't.
 requireIndex :: (MonadIO m,MonadLogger m
                 ,MonadThrow m,MonadReader env m,HasHttpManager env
-                ,HasConfig env)
+                ,HasConfig env,MonadBaseControl IO m,MonadCatch m)
              => EnvOverride
              -> PackageIndex
              -> m ()
 requireIndex menv index = do
     tarFile <- configPackageIndex $ indexName index
-    exists <- liftIO $ doesFileExist $ toFilePath tarFile
+    exists <- fileExists tarFile
     unless exists $ updateIndex menv index
 
 -- | Update all of the package indices
 updateAllIndices
     :: (MonadIO m,MonadLogger m
        ,MonadThrow m,MonadReader env m,HasHttpManager env
-       ,HasConfig env)
+       ,HasConfig env,MonadBaseControl IO m, MonadCatch m)
     => EnvOverride
     -> m ()
 updateAllIndices menv =
@@ -193,7 +198,7 @@
 -- | Update the index tarball
 updateIndex :: (MonadIO m,MonadLogger m
                ,MonadThrow m,MonadReader env m,HasHttpManager env
-               ,HasConfig env)
+               ,HasConfig env,MonadBaseControl IO m, MonadCatch m)
             => EnvOverride
             -> PackageIndex
             -> m ()
@@ -209,7 +214,7 @@
         (False, ILGit url) -> logUpdate url >> (throwM $ GitNotAvailable name)
 
 -- | Update the index Git repo and the index tarball
-updateIndexGit :: (MonadIO m,MonadLogger m,MonadThrow m,MonadReader env m,HasConfig env)
+updateIndexGit :: (MonadIO m,MonadLogger m,MonadThrow m,MonadReader env m,HasConfig env,MonadBaseControl IO m, MonadCatch m)
                => EnvOverride
                -> IndexName
                -> PackageIndex
@@ -256,15 +261,17 @@
             $logDebug ("Exporting a tarball to " <>
                        (T.pack . toFilePath) tarFile)
             deleteCache indexName'
+            let tarFileTmp = toFilePath tarFile ++ ".tmp"
             readInNull acfDir
                        "git"
                        menv
                        ["archive"
                        ,"--format=tar"
                        ,"-o"
-                       ,toFilePath tarFile
+                       ,tarFileTmp
                        ,"current-hackage"]
                        Nothing
+            liftIO $ renameFile tarFileTmp (toFilePath tarFile)
 
 -- | Update the index tarball via HTTP
 updateIndexHTTP :: (MonadIO m,MonadLogger m
@@ -282,7 +289,7 @@
     toUnpack <-
         if wasDownloaded
             then return True
-            else liftIO $ fmap not $ doesFileExist $ toFilePath tar
+            else liftM not $ fileExists tar
 
     when toUnpack $ do
         let tmp = toFilePath tar <.> "tmp"
@@ -341,7 +348,7 @@
             }
 
 -- | Load the cached package URLs, or created the cache if necessary.
-getPackageCaches :: (MonadIO m, MonadLogger m, MonadReader env m, HasConfig env, MonadThrow m, HasHttpManager env)
+getPackageCaches :: (MonadIO m, MonadLogger m, MonadReader env m, HasConfig env, MonadThrow m, HasHttpManager env, MonadBaseControl IO m, MonadCatch m)
                  => EnvOverride
                  -> m (Map PackageIdentifier (PackageIndex, PackageCache))
 getPackageCaches menv = do
@@ -353,7 +360,7 @@
         return (fmap (index,) pis')
 
 -- | Populate the package index caches and return them.
-populateCache :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env, MonadLogger m, HasHttpManager env)
+populateCache :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env, MonadLogger m, HasHttpManager env, MonadBaseControl IO m, MonadCatch m)
               => EnvOverride
               -> PackageIndex
               -> m (Map PackageIdentifier PackageCache)
@@ -375,20 +382,21 @@
                 Nothing -> Nothing
                 Just pd -> Just (ident, pd)
 
-    (pis, pds) <- sourcePackageIndex menv index $$ getZipSink ((,)
-        <$> ZipSink (CL.mapMaybe toIdent =$ CL.consume)
-        <*> ZipSink (Map.fromList <$> (CL.mapMaybe parseDownload =$ CL.consume)))
+    withSourcePackageIndex menv index $ \source -> do
+        (pis, pds) <- source $$ getZipSink ((,)
+            <$> ZipSink (CL.mapMaybe toIdent =$ CL.consume)
+            <*> ZipSink (Map.fromList <$> (CL.mapMaybe parseDownload =$ CL.consume)))
 
-    pis' <- liftM Map.fromList $ forM pis $ \(ident, pc) ->
-        case Map.lookup ident pds of
-            Just d -> return (ident, pc { pcDownload = Just d })
-            Nothing
-                | indexRequireHashes index -> throwM $ MissingRequiredHashes (indexName index) ident
-                | otherwise -> return (ident, pc)
+        pis' <- liftM Map.fromList $ forM pis $ \(ident, pc) ->
+            case Map.lookup ident pds of
+                Just d -> return (ident, pc { pcDownload = Just d })
+                Nothing
+                    | indexRequireHashes index -> throwM $ MissingRequiredHashes (indexName index) ident
+                    | otherwise -> return (ident, pc)
 
-    $logStickyDone "Populated index cache."
+        $logStickyDone "Populated index cache."
 
-    return pis'
+        return pis'
 
 --------------- Lifted from cabal-install, Distribution.Client.Tar:
 -- | Return the number of blocks in an entry.
diff --git a/src/Stack/Repl.hs b/src/Stack/Repl.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Repl.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+
+-- | Run a REPL configured with the user's project(s).
+
+module Stack.Repl where
+
+import           Control.Monad.Catch
+import           Control.Monad.IO.Class
+import           Control.Monad.Logger
+import           Control.Monad.Reader
+import           Data.List
+import qualified Data.Map.Strict as M
+import           Data.Maybe
+import           Data.Monoid
+import           Data.Text (Text)
+import qualified Data.Text as T
+import           Path
+import           Path.IO
+import           Stack.Build.Source
+import           Stack.Exec
+import           Stack.Package
+import           Stack.Types
+
+-- | Launch a GHCi REPL for the given local project targets with the
+-- given options and configure it with the load paths and extensions
+-- of those targets.
+repl :: (HasConfig r, HasBuildConfig r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m)
+     => [Text] -- ^ Targets.
+     -> [String] -- ^ GHC options.
+     -> m ()
+repl targets opts = do
+    bconfig <- asks getBuildConfig
+    pwd <- getWorkingDir
+    pkgOpts <-
+        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 = bcGhcVersion bconfig
+                        , packageConfigPlatform = configPlatform
+                              (getConfig bconfig)
+                        }
+                pkg <- readPackage config cabalfp
+                if validWanted &&
+                   wanted pwd cabalfp pkg
+                    then do
+                        pkgOpts <-
+                            getPackageOpts
+                                (packageOpts pkg)
+                                cabalfp
+                        return (Just (packageName pkg, pkgOpts))
+                    else return Nothing
+    $logInfo
+        ("Configuring GHCi with the following packages: " <>
+         T.intercalate
+             ", "
+             (map packageNameText (map fst pkgOpts)))
+    exec
+        "ghc"
+        ("--interactive" :
+         filter (not . badForGhci) (concat (map snd pkgOpts)) <>
+         opts)
+  where
+    wanted pwd cabalfp pkg =
+        isInWantedList || targetsEmptyAndInDir
+      where
+        isInWantedList =
+            elem
+                (packageNameText
+                     (packageName pkg))
+                targets
+        targetsEmptyAndInDir =
+            null targets ||
+            isParentOf
+                (parent cabalfp)
+                pwd
+    badForGhci = isPrefixOf "-O"
diff --git a/src/Stack/Setup.hs b/src/Stack/Setup.hs
--- a/src/Stack/Setup.hs
+++ b/src/Stack/Setup.hs
@@ -15,21 +15,18 @@
   ) where
 
 import           Control.Applicative
-import           Control.Exception (Exception)
 import           Control.Monad (liftM, when, join, void)
-import           Control.Monad.Catch (MonadThrow, throwM, MonadMask)
+import           Control.Monad.Catch
 import           Control.Monad.IO.Class (MonadIO, liftIO)
 import           Control.Monad.Logger
 import           Control.Monad.Reader (MonadReader, ReaderT (..), asks)
 import           Control.Monad.State (get, put, modify)
 import           Control.Monad.Trans.Control
-
 import           Data.Aeson.Extended
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Char8 as S8
 import           Data.Conduit (Conduit, ($$), (=$), await, yield, awaitForever)
 import           Data.Conduit.Lift (evalStateC)
-import           Data.Conduit.Process (ProcessExitedUnsuccessfully)
 import qualified Data.Conduit.List as CL
 import           Data.IORef
 import           Data.List (intercalate)
@@ -45,11 +42,13 @@
 import           Data.Typeable (Typeable)
 import qualified Data.Yaml as Yaml
 import           Distribution.System (OS (..), Arch (..), Platform (..))
+import           Distribution.Text (simpleParse)
 import           Network.HTTP.Client.Conduit
 import           Network.HTTP.Download (verifiedDownload, DownloadRequest(..))
 import           Path
 import           Path.IO
 import           Prelude -- Fix AMP warning
+import           Safe (readMay)
 import           Stack.Build.Types
 import           Stack.GhcPkg (getGlobalDB)
 import           Stack.Types
@@ -77,7 +76,7 @@
                     | MissingDependencies [String]
                     | UnknownGHCVersion Version (Set MajorVersion)
                     | UnknownOSKey Text
-                    | GHCSanityCheckCompileFailed ProcessExitedUnsuccessfully (Path Abs File)
+                    | GHCSanityCheckCompileFailed ReadProcessException (Path Abs File)
     deriving Typeable
 instance Exception SetupException
 instance Show SetupException where
@@ -87,7 +86,7 @@
         , ", please install manually"
         ]
     show (MissingDependencies tools) =
-        "The following executables are missing and must be installed:" ++
+        "The following executables are missing and must be installed: " ++
         intercalate ", " tools
     show (UnknownGHCVersion version known) = concat
         [ "No information found for GHC version "
@@ -218,12 +217,15 @@
             then getSystemGHC menv0
             else return Nothing
 
+    Platform expectedArch _ <- asks getPlatform
+
     let needLocal = case msystem of
             Nothing -> True
-            Just system ->
+            Just (system, arch) ->
                 -- we allow a newer version of GHC within the same major series
                 getMajorVersion system /= getMajorVersion expected ||
-                expected > system
+                expected > system ||
+                arch /= expectedArch
 
     -- If we need to install a GHC, try to do so
     mpaths <- if needLocal
@@ -254,7 +256,6 @@
             installed <- runReaderT listInstalled config
             idents <- mapM (ensureTool sopts installed getSetupInfo' msystem) tools
             paths <- runReaderT (mapM binDirs $ catMaybes idents) config
-            -- TODO: strip the trailing slash for prettier PATH output
             return $ Just $ map toFilePath $ concat paths
         else return Nothing
 
@@ -276,19 +277,19 @@
     expected = soptsExpected sopts
 
 -- | Get the major version of the system GHC, if available
-getSystemGHC :: (MonadIO m) => EnvOverride -> m (Maybe Version)
+getSystemGHC :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> m (Maybe (Version, Arch))
 getSystemGHC menv = do
     exists <- doesExecutableExist menv "ghc"
     if exists
         then do
-            eres <- liftIO $ tryProcessStdout Nothing menv "ghc" ["--numeric-version"]
+            eres <- tryProcessStdout Nothing menv "ghc" ["--info"]
             return $ do
                 Right bs <- Just eres
-                parseVersion $ S8.takeWhile isValidChar bs
+                pairs <- readMay $ S8.unpack bs :: Maybe [(String, String)]
+                version <- lookup "Project version" pairs >>= parseVersionFromString
+                arch <- lookup "Target platform" pairs >>= simpleParse . takeWhile (/= '-')
+                Just (version, arch)
         else return Nothing
-  where
-    isValidChar '.' = True
-    isValidChar c = '0' <= c && c <= '9'
 
 data DownloadPair = DownloadPair Version Text
     deriving Show
@@ -384,14 +385,16 @@
            => SetupOpts
            -> [PackageIdentifier] -- ^ already installed
            -> m SetupInfo
-           -> Maybe Version -- ^ installed GHC
+           -> Maybe (Version, Arch) -- ^ installed GHC
            -> (PackageName, Maybe Version)
            -> m (Maybe PackageIdentifier)
 ensureTool sopts installed getSetupInfo' msystem (name, mversion)
     | not $ null available = return $ Just $ PackageIdentifier name $ maximum available
     | not $ soptsInstallIfMissing sopts =
         if name == $(mkPackageName "ghc")
-            then throwM $ GHCVersionMismatch msystem (soptsExpected sopts) (soptsStackYaml sopts)
+            then do
+                Platform arch _ <- asks getPlatform
+                throwM $ GHCVersionMismatch msystem (soptsExpected sopts, arch) (soptsStackYaml sopts)
             else do
                 $logWarn $ "Continuing despite missing tool: " <> T.pack (packageNameString name)
                 return Nothing
@@ -491,7 +494,7 @@
     | TarXz
     | SevenZ
 
-installGHCPosix :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env)
+installGHCPosix :: (MonadIO m, MonadMask m, MonadLogger m, MonadReader env m, HasConfig env, HasHttpManager env, MonadBaseControl IO m)
                 => SetupInfo
                 -> Path Abs File
                 -> ArchiveType
@@ -677,7 +680,7 @@
 
 
 -- | Perform a basic sanity check of GHC
-sanityCheck :: (MonadIO m, MonadMask m, MonadLogger m)
+sanityCheck :: (MonadIO m, MonadMask m, MonadLogger m, MonadBaseControl IO m)
             => EnvOverride
             -> m ()
 sanityCheck menv = withSystemTempDirectory "stack-sanity-check" $ \dir -> do
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
@@ -22,6 +22,8 @@
 import           Data.Hashable (Hashable)
 import           Data.Map (Map)
 import qualified Data.Map as Map
+import           Data.Set (Set)
+import qualified Data.Set as Set
 import           Data.Monoid
 import           Data.Text (Text)
 import qualified Data.Text as T
@@ -87,6 +89,10 @@
          -- ^ Require a version of stack within this range.
          ,configJobs                :: !Int
          -- ^ How many concurrent jobs to run, defaults to number of capabilities
+         ,configExtraIncludeDirs    :: !(Set Text)
+         -- ^ --extra-include-dirs arguments
+         ,configExtraLibDirs        :: !(Set Text)
+         -- ^ --extra-lib-dirs arguments
          }
 
 -- | Information on a single package index
@@ -201,7 +207,7 @@
 data LoadConfig m = LoadConfig
     { lcConfig          :: !Config
       -- ^ Top-level Stack configuration.
-    , lcLoadBuildConfig :: !(NoBuildConfigStrategy -> m BuildConfig)
+    , lcLoadBuildConfig :: !(Maybe Resolver -> NoBuildConfigStrategy -> m BuildConfig)
         -- ^ Action to load the remaining 'BuildConfig'.
     , lcProjectRoot     :: !(Maybe (Path Abs Dir))
         -- ^ The project root directory, if in a project.
@@ -209,7 +215,6 @@
 
 data NoBuildConfigStrategy
     = ThrowException
-    | CreateConfig
     | ExecStrategy
     deriving (Show, Eq, Ord)
 
@@ -389,6 +394,10 @@
     -- ^ Used for overriding the platform
     ,configMonoidJobs                :: !(Maybe Int)
     -- ^ See: 'configJobs'
+    ,configMonoidExtraIncludeDirs    :: !(Set Text)
+    -- ^ See: 'configExtraIncludeDirs'
+    ,configMonoidExtraLibDirs        :: !(Set Text)
+    -- ^ See: 'configExtraLibDirs'
     }
   deriving Show
 
@@ -405,6 +414,8 @@
     , configMonoidOS = Nothing
     , configMonoidArch = Nothing
     , configMonoidJobs = Nothing
+    , configMonoidExtraIncludeDirs = Set.empty
+    , configMonoidExtraLibDirs = Set.empty
     }
   mappend l r = ConfigMonoid
     { configMonoidDockerOpts = configMonoidDockerOpts l <> configMonoidDockerOpts r
@@ -412,13 +423,14 @@
     , configMonoidHideTHLoading = configMonoidHideTHLoading l <|> configMonoidHideTHLoading r
     , configMonoidLatestSnapshotUrl = configMonoidLatestSnapshotUrl l <|> configMonoidLatestSnapshotUrl r
     , configMonoidPackageIndices = configMonoidPackageIndices l <|> configMonoidPackageIndices r
-    , configMonoidSystemGHC = configMonoidSystemGHC l <|> configMonoidSystemGHC r
-    , configMonoidInstallGHC = configMonoidInstallGHC l <|> configMonoidInstallGHC r
+    , configMonoidSystemGHC = configMonoidSystemGHC l <|> configMonoidSystemGHC r , configMonoidInstallGHC = configMonoidInstallGHC l <|> configMonoidInstallGHC r
     , configMonoidRequireStackVersion = intersectVersionRanges (configMonoidRequireStackVersion l)
                                                                (configMonoidRequireStackVersion r)
     , configMonoidOS = configMonoidOS l <|> configMonoidOS r
     , configMonoidArch = configMonoidArch l <|> configMonoidArch r
     , configMonoidJobs = configMonoidJobs l <|> configMonoidJobs r
+    , configMonoidExtraIncludeDirs = Set.union (configMonoidExtraIncludeDirs l) (configMonoidExtraIncludeDirs r)
+    , configMonoidExtraLibDirs = Set.union (configMonoidExtraLibDirs l) (configMonoidExtraLibDirs r)
     }
 
 instance FromJSON ConfigMonoid where
@@ -438,6 +450,8 @@
          configMonoidOS <- obj .:? "os"
          configMonoidArch <- obj .:? "arch"
          configMonoidJobs <- obj .:? "jobs"
+         configMonoidExtraIncludeDirs <- obj .:? "extra-include-dirs" .!= Set.empty
+         configMonoidExtraLibDirs <- obj .:? "extra-lib-dirs" .!= Set.empty
          return ConfigMonoid {..}
 
 -- | Newtype for non-orphan FromJSON instance.
@@ -452,9 +466,10 @@
 
 data ConfigException
   = ParseResolverException Text
-  | NoProjectConfigFound (Path Abs Dir)
+  | NoProjectConfigFound (Path Abs Dir) (Maybe Text)
   | UnexpectedTarballContents [Path Abs Dir] [Path Abs File]
   | BadStackVersionException VersionRange
+  | NoMatchingSnapshot [SnapName]
   deriving Typeable
 instance Show ConfigException where
     show (ParseResolverException t) = concat
@@ -463,10 +478,13 @@
         , ". Possible valid values include lts-2.12, nightly-YYYY-MM-DD, and ghc-7.10. "
         , "See https://www.stackage.org/snapshots for a complete list."
         ]
-    show (NoProjectConfigFound dir) = concat
+    show (NoProjectConfigFound dir mcmd) = concat
         [ "Unable to find a stack.yaml file in the current directory ("
         , toFilePath dir
         , ") or its ancestors"
+        , case mcmd of
+            Nothing -> ""
+            Just cmd -> "\nRecommended action: stack " ++ T.unpack cmd
         ]
     show (UnexpectedTarballContents dirs files) = concat
         [ "When unpacking a tarball specified in your stack.yaml file, "
@@ -482,6 +500,18 @@
         ,"version range ("
         , T.unpack (versionRangeText requiredRange)
         , ") specified in stack.yaml." ]
+    show (NoMatchingSnapshot names) = concat
+        [ "There was no snapshot found that matched the package "
+        , "bounds in your .cabal files.\n"
+        , "Please choose one of the following commands to get started.\n\n"
+        , unlines $ map
+            (\name -> "    stack init --resolver " ++ T.unpack (renderSnapName name))
+            names
+        , "\nYou'll then need to add some extra-deps. See:\n\n"
+        , "    https://github.com/commercialhaskell/stack/wiki/stack.yaml#extra-deps"
+        , "\n\nNote that this will be improved in the future, see:\n\n"
+        , "    https://github.com/commercialhaskell/stack/issues/116"
+        ]
 instance Exception ConfigException
 
 -- | Helper function to ask the environment and apply getConfig
diff --git a/src/Stack/Types/Internal.hs b/src/Stack/Types/Internal.hs
--- a/src/Stack/Types/Internal.hs
+++ b/src/Stack/Types/Internal.hs
@@ -12,6 +12,7 @@
 data Env config =
   Env {envConfig :: !config
       ,envLogLevel :: !LogLevel
+      ,envTerminal :: !Bool
       ,envManager :: !Manager
       ,envSticky :: !Sticky}
 
@@ -37,6 +38,12 @@
 
 instance HasLogLevel LogLevel where
   getLogLevel = id
+
+class HasTerminal r where
+  getTerminal :: r -> Bool
+
+instance HasTerminal (Env config) where
+  getTerminal = envTerminal
 
 newtype Sticky = Sticky
     { unSticky :: Maybe (MVar (Maybe Text))
diff --git a/src/Stack/Types/StackT.hs b/src/Stack/Types/StackT.hs
--- a/src/Stack/Types/StackT.hs
+++ b/src/Stack/Types/StackT.hs
@@ -81,10 +81,14 @@
 
 -- | Run a Stack action.
 runStackT :: (MonadIO m,MonadBaseControl IO m)
-          => Manager -> LogLevel -> config -> StackT config m a -> m a
-runStackT manager logLevel config m =
-     withSticky (\sticky -> runReaderT (unStackT m)
-                                       (Env config logLevel manager sticky))
+          => Manager -> LogLevel -> config -> Bool -> StackT config m a -> m a
+runStackT manager logLevel config terminal m =
+    withSticky
+        terminal
+        (\sticky ->
+              runReaderT
+                  (unStackT m)
+                  (Env config logLevel terminal manager sticky))
 
 --------------------------------------------------------------------------------
 -- Logging only StackLoggingT monad transformer
@@ -122,11 +126,14 @@
 
 -- | Run the logging monad.
 runStackLoggingT :: MonadIO m
-                 => Manager -> LogLevel -> StackLoggingT m a -> m a
-runStackLoggingT manager logLevel m =
-     withSticky (\sticky ->
-                     runReaderT (unStackLoggingT m)
-                                (logLevel,manager,sticky))
+                 => Manager -> LogLevel -> Bool -> StackLoggingT m a -> m a
+runStackLoggingT manager logLevel terminal m =
+    withSticky
+        terminal
+        (\sticky ->
+              runReaderT
+                  (unStackLoggingT m)
+                  (logLevel, manager, sticky))
 
 -- | Convenience for getting a 'Manager'
 newTLSManager :: MonadIO m => m Manager
@@ -237,10 +244,9 @@
                         char = show . snd . loc_start
 
 -- | With a sticky state, do the thing.
-withSticky :: MonadIO m
-           => (Sticky -> m b) -> m b
-withSticky m = do
-    terminal <- liftIO (hIsTerminalDevice stdout)
+withSticky :: (MonadIO m)
+           => Bool -> (Sticky -> m b) -> m b
+withSticky terminal m = do
     if terminal
        then do state <- liftIO (newMVar Nothing)
                originalMode <- liftIO (hGetBuffering stdout)
diff --git a/src/Stack/Upload.hs b/src/Stack/Upload.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Upload.hs
@@ -0,0 +1,351 @@
+{-# LANGUAGE DeriveDataTypeable  #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- | Provide ability to upload tarballs to Hackage.
+module Stack.Upload
+    ( -- * Upload
+      mkUploader
+    , Uploader
+    , upload
+    , UploadSettings
+    , defaultUploadSettings
+    , setUploadUrl
+    , setGetManager
+    , setCredsSource
+    , setSaveCreds
+      -- * Credentials
+    , HackageCreds
+    , loadCreds
+    , saveCreds
+    , FromFile
+      -- ** Credentials source
+    , HackageCredsSource
+    , fromAnywhere
+    , fromPrompt
+    , fromFile
+    , fromMemory
+    ) where
+
+import           Control.Applicative                   ((<$>), (<*>))
+import           Control.Exception                     (bracket)
+import qualified Control.Exception                     as E
+import           Control.Monad                         (when)
+import           Data.Aeson                            (FromJSON (..),
+                                                        ToJSON (..),
+                                                        eitherDecode', encode,
+                                                        object, withObject,
+                                                        (.:), (.=))
+import qualified Data.ByteString.Char8                 as S
+import qualified Data.ByteString.Lazy                  as L
+import           Data.Text                             (Text)
+import qualified Data.Text                             as T
+import           Data.Text.Encoding                    (encodeUtf8)
+import qualified Data.Text.IO                          as TIO
+import           Data.Typeable                         (Typeable)
+import           Network.HTTP.Client                   (BodyReader, Manager,
+                                                        Response,
+                                                        applyBasicAuth, brRead,
+                                                        checkStatus, newManager,
+                                                        parseUrl,
+                                                        requestHeaders,
+                                                        responseBody,
+                                                        responseStatus,
+                                                        withResponse)
+import           Network.HTTP.Client.MultipartFormData (formDataBody, partFile)
+import           Network.HTTP.Client.TLS               (tlsManagerSettings)
+import           Network.HTTP.Types                    (statusCode)
+import           Path                                  (toFilePath)
+import           Stack.Types
+import           System.Directory                      (createDirectoryIfMissing,
+                                                        doesDirectoryExist,
+                                                        doesFileExist,
+                                                        getDirectoryContents,
+                                                        removeFile)
+import           System.Exit                           (ExitCode (ExitSuccess))
+import           System.FilePath                       (takeExtension, (</>))
+import           System.IO                             (hClose, hFlush,
+                                                        hGetEcho, hSetEcho,
+                                                        stdin, stdout)
+import           System.IO.Temp                        (withSystemTempDirectory)
+import           System.Process                        (StdStream (CreatePipe),
+                                                        createProcess, cwd,
+                                                        proc, std_in,
+                                                        waitForProcess)
+
+-- | Username and password to log into Hackage.
+--
+-- Since 0.1.0.0
+data HackageCreds = HackageCreds
+    { hcUsername :: !Text
+    , hcPassword :: !Text
+    }
+    deriving Show
+
+instance ToJSON HackageCreds where
+    toJSON (HackageCreds u p) = object
+        [ "username" .= u
+        , "password" .= p
+        ]
+instance FromJSON HackageCreds where
+    parseJSON = withObject "HackageCreds" $ \o -> HackageCreds
+        <$> o .: "username"
+        <*> o .: "password"
+
+-- | A source for getting Hackage credentials.
+--
+-- Since 0.1.0.0
+newtype HackageCredsSource = HackageCredsSource
+    { getCreds :: IO (HackageCreds, FromFile)
+    }
+
+-- | Whether the Hackage credentials were loaded from a file.
+--
+-- This information is useful since, typically, you only want to save the
+-- credentials to a file if it wasn't already loaded from there.
+--
+-- Since 0.1.0.0
+type FromFile = Bool
+
+-- | Load Hackage credentials from the given source.
+--
+-- Since 0.1.0.0
+loadCreds :: HackageCredsSource -> IO (HackageCreds, FromFile)
+loadCreds = getCreds
+
+-- | Save the given credentials to the credentials file.
+--
+-- Since 0.1.0.0
+saveCreds :: Config -> HackageCreds -> IO ()
+saveCreds config creds = do
+    fp <- credsFile config
+    L.writeFile fp $ encode creds
+
+-- | Load the Hackage credentials from the prompt, asking the user to type them
+-- in.
+--
+-- Since 0.1.0.0
+fromPrompt :: HackageCredsSource
+fromPrompt = HackageCredsSource $ do
+    putStr "Hackage username: "
+    hFlush stdout
+    username <- TIO.getLine
+    password <- promptPassword
+    return (HackageCreds
+        { hcUsername = username
+        , hcPassword = password
+        }, False)
+
+credsFile :: Config -> IO FilePath
+credsFile config = do
+    let dir = toFilePath (configStackRoot config) </> "upload"
+    createDirectoryIfMissing True dir
+    return $ dir </> "credentials.json"
+
+-- | Load the Hackage credentials from the JSON config file.
+--
+-- Since 0.1.0.0
+fromFile :: Config -> HackageCredsSource
+fromFile config = HackageCredsSource $ do
+    fp <- credsFile config
+    lbs <- L.readFile fp
+    case eitherDecode' lbs of
+        Left e -> E.throwIO $ Couldn'tParseJSON fp e
+        Right creds -> return (creds, True)
+
+-- | Load the Hackage credentials from the given arguments.
+--
+-- Since 0.1.0.0
+fromMemory :: Text -> Text -> HackageCredsSource
+fromMemory u p = HackageCredsSource $ return (HackageCreds
+    { hcUsername = u
+    , hcPassword = p
+    }, False)
+
+data HackageCredsExceptions = Couldn'tParseJSON FilePath String
+    deriving (Show, Typeable)
+instance E.Exception HackageCredsExceptions
+
+-- | Try to load the credentials from the config file. If that fails, ask the
+-- user to enter them.
+--
+-- Since 0.1.0.0
+fromAnywhere :: Config -> HackageCredsSource
+fromAnywhere config = HackageCredsSource $
+    getCreds (fromFile config) `E.catches`
+        [ E.Handler $ \(_ :: E.IOException) -> getCreds fromPrompt
+        , E.Handler $ \(_ :: HackageCredsExceptions) -> getCreds fromPrompt
+        ]
+
+-- | Lifted from cabal-install, Distribution.Client.Upload
+promptPassword :: IO Text
+promptPassword = do
+  putStr "Hackage password: "
+  hFlush stdout
+  -- save/restore the terminal echoing status
+  passwd <- bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do
+    hSetEcho stdin False  -- no echoing for entering the password
+    fmap T.pack getLine
+  putStrLn ""
+  return passwd
+
+-- | Turn the given settings into an @Uploader@.
+--
+-- Since 0.1.0.0
+mkUploader :: FilePath -- ^ runghc
+           -> Config -> UploadSettings -> IO Uploader
+mkUploader runghc config us = do
+    manager <- usGetManager us
+    (creds, fromFile') <- loadCreds $ usCredsSource us config
+    when (not fromFile' && usSaveCreds us) $ saveCreds config creds
+    req0 <- parseUrl $ usUploadUrl us
+    let req1 = req0
+            { requestHeaders = [("Accept", "text/plain")]
+            , checkStatus = \_ _ _ -> Nothing
+            }
+    return Uploader
+        { upload_ = \fp0 -> withTarball runghc fp0 $ \fp -> do
+            let formData = [partFile "package" fp]
+            req2 <- formDataBody formData req1
+            let req3 = applyBasicAuth
+                    (encodeUtf8 $ hcUsername creds)
+                    (encodeUtf8 $ hcPassword creds)
+                    req2
+            putStr $ "Uploading " ++ fp ++ "... "
+            hFlush stdout
+            withResponse req3 manager $ \res ->
+                case statusCode $ responseStatus res of
+                    200 -> putStrLn "done!"
+                    401 -> do
+                        putStrLn "authentication failure"
+                        cfp <- credsFile config
+                        handleIO (const $ return ()) (removeFile cfp)
+                        error $ "Authentication failure uploading to server"
+                    403 -> do
+                        putStrLn "forbidden upload"
+                        putStrLn "Usually means: you've already uploaded this package/version combination"
+                        putStrLn "Ignoring error and continuing, full message from Hackage below:\n"
+                        printBody res
+                    503 -> do
+                        putStrLn "service unavailable"
+                        putStrLn "This error some times gets sent even though the upload succeeded"
+                        putStrLn "Check on Hackage to see if your pacakge is present"
+                        printBody res
+                    code -> do
+                        putStrLn $ "unhandled status code: " ++ show code
+                        printBody res
+                        error $ "Upload failed on " ++ fp
+        }
+
+-- | Given either a file, return it. Given a directory, run @cabal sdist@ and
+-- get the resulting tarball.
+withTarball :: FilePath -- ^ runghc
+            -> FilePath -> (FilePath -> IO a) -> IO a
+withTarball _runghc fp0 inner = do
+    isFile <- doesFileExist fp0
+    if isFile then inner fp0 else withSystemTempDirectory "stackage-upload-tarball" $ \dir -> do
+        isDir <- doesDirectoryExist fp0
+        when (not isDir) $ error $ "Invalid argument: " ++ fp0
+
+        (Just h, Nothing, Nothing, ph) <-
+        -- The insanity: the Cabal library seems to sometimes generate tarballs
+        -- in the wrong format. For now, just falling back to cabal-install.
+        -- Sigh.
+
+            createProcess $ (proc "cabal"
+                    [ "sdist"
+                    , "--builddir=" ++ dir
+                    ])
+                { cwd = Just fp0
+                , std_in = CreatePipe
+                }
+        hClose h
+        ec <- waitForProcess ph
+        when (ec /= ExitSuccess) $
+            error $ "Could not create tarball for " ++ fp0
+        contents <- getDirectoryContents dir
+        case filter ((== ".gz") . takeExtension) contents of
+            [x] -> inner (dir </> x)
+            _ -> error $ "Unexpected directory contents after cabal sdist: " ++ show contents
+
+printBody :: Response BodyReader -> IO ()
+printBody res =
+    loop
+  where
+    loop = do
+        bs <- brRead $ responseBody res
+        when (not $ S.null bs) $ do
+            S.hPut stdout bs
+            loop
+
+-- | The computed value from a @UploadSettings@.
+--
+-- Typically, you want to use this with 'upload'.
+--
+-- Since 0.1.0.0
+data Uploader = Uploader
+    { upload_ :: !(FilePath -> IO ())
+    }
+
+-- | Upload a single tarball with the given @Uploader@.
+--
+-- Since 0.1.0.0
+upload :: Uploader -> FilePath -> IO ()
+upload = upload_
+
+-- | Settings for creating an @Uploader@.
+--
+-- Since 0.1.0.0
+data UploadSettings = UploadSettings
+    { usUploadUrl   :: !String
+    , usGetManager  :: !(IO Manager)
+    , usCredsSource :: !(Config -> HackageCredsSource)
+    , usSaveCreds   :: !Bool
+    }
+
+-- | Default value for @UploadSettings@.
+--
+-- Use setter functions to change defaults.
+--
+-- Since 0.1.0.0
+defaultUploadSettings :: UploadSettings
+defaultUploadSettings = UploadSettings
+    { usUploadUrl = "https://hackage.haskell.org/packages/"
+    , usGetManager = newManager tlsManagerSettings
+    , usCredsSource = fromAnywhere
+    , usSaveCreds = True
+    }
+
+-- | Change the upload URL.
+--
+-- Default: "https://hackage.haskell.org/packages/"
+--
+-- Since 0.1.0.0
+setUploadUrl :: String -> UploadSettings -> UploadSettings
+setUploadUrl x us = us { usUploadUrl = x }
+
+-- | How to get an HTTP connection manager.
+--
+-- Default: @newManager tlsManagerSettings@
+--
+-- Since 0.1.0.0
+setGetManager :: IO Manager -> UploadSettings -> UploadSettings
+setGetManager x us = us { usGetManager = x }
+
+-- | How to get the Hackage credentials.
+--
+-- Default: @fromAnywhere@
+--
+-- Since 0.1.0.0
+setCredsSource :: (Config -> HackageCredsSource) -> UploadSettings -> UploadSettings
+setCredsSource x us = us { usCredsSource = x }
+
+-- | Save new credentials to the config file.
+--
+-- Default: @True@
+--
+-- Since 0.1.0.0
+setSaveCreds :: Bool -> UploadSettings -> UploadSettings
+setSaveCreds x us = us { usSaveCreds = x }
+
+handleIO :: (E.IOException -> IO a) -> IO a -> IO a
+handleIO = E.handle
diff --git a/src/System/Process/Log.hs b/src/System/Process/Log.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Process/Log.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+-- | Separate module because TH.
+
+module System.Process.Log
+    (logProcessRun
+    ,showProcessArgDebug)
+    where
+
+import           Control.Monad.Logger
+import           Data.Monoid
+import           Data.Text (Text)
+import qualified Data.Text as T
+import           Language.Haskell.TH
+
+-- | Log running a process with its arguments, for debugging (-v).
+logProcessRun :: Q Exp
+logProcessRun =
+    [|let f :: MonadLogger m => String -> [String] -> m ()
+          f name args =
+              $logDebug
+                  ("Run process: " <> T.pack name <> " " <>
+                   T.intercalate
+                       " "
+                       (map showProcessArgDebug args))
+      in f|]
+
+-- | Show a process arg including speechmarks when necessary. Just for
+-- debugging purposes, not functionally important.
+showProcessArgDebug :: [Char] -> Text
+showProcessArgDebug x
+    | any special x = T.pack (show x)
+    | otherwise = T.pack x
+  where special '"' = True
+        special ' ' = True
+        special _ = 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
@@ -1,3 +1,4 @@
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE FlexibleContexts #-}
@@ -9,12 +10,10 @@
 -- | Reading from external processes.
 
 module System.Process.Read
-  (readProcessStdoutLogStderr
-  ,readProcessStdout
+  (readProcessStdout
   ,tryProcessStdout
-  ,sinkProcessStdoutLogStderr
   ,sinkProcessStdout
-  ,sinkProcessStderrStdout
+  ,readProcess
   ,EnvOverride
   ,unEnvOverride
   ,mkEnvOverride
@@ -25,21 +24,25 @@
   ,envSearchPath
   ,preProcess
   ,readProcessNull
-  ,readInNull)
+  ,readInNull
+  ,logProcessRun
+  ,ReadProcessException (..)
+  )
   where
 
 import           Control.Applicative
 import           Control.Arrow ((***), first)
 import           Control.Concurrent.Async (Concurrently (..))
-import           Control.Exception
-import           Control.Monad (join, liftM, void)
-import           Control.Monad.Catch (MonadThrow, throwM)
+import           Control.Exception hiding (try, catch)
+import           Control.Monad (join, liftM)
+import           Control.Monad.Catch (MonadThrow, MonadCatch, throwM, try, catch)
 import           Control.Monad.IO.Class (MonadIO, liftIO)
 import           Control.Monad.Logger (MonadLogger, logError)
-import           Control.Monad.Trans.Control (MonadBaseControl,liftBaseWith)
+import           Control.Monad.Trans.Control (MonadBaseControl)
 import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as L
+import           Data.ByteString.Builder
 import           Data.Conduit
-import qualified Data.Conduit.Combinators as CC
 import qualified Data.Conduit.List as CL
 import           Data.Conduit.Process hiding (callProcess)
 import           Data.Foldable (forM_)
@@ -47,8 +50,12 @@
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import           Data.Maybe (isJust)
+import           Data.Monoid
 import           Data.Text (Text)
 import qualified Data.Text as T
+import           Data.Text.Encoding.Error (lenientDecode)
+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           Path (Path, Abs, Dir, toFilePath, File, parseAbsFile)
@@ -57,13 +64,14 @@
 import           System.Environment (getEnvironment)
 import           System.Exit
 import qualified System.FilePath as FP
+import           System.Process.Log
 
 -- | Override the environment received by a child process
 data EnvOverride = EnvOverride
     { eoTextMap :: Map Text Text
     , eoStringList :: [(String, String)]
     , eoPath :: [FilePath]
-    , eoExeCache :: IORef (Map FilePath (Either FindExecutableException (Path Abs File)))
+    , eoExeCache :: IORef (Map FilePath (Either ReadProcessException (Path Abs File)))
     , eoExeExtension :: String
     }
 
@@ -107,28 +115,26 @@
 envHelper = Just . eoStringList
 
 -- | Read from the process, ignoring any output.
-readProcessNull
-    :: (MonadIO m)
-    => Maybe (Path Abs Dir)
-    -> EnvOverride
-    -> String
-    -> [String]
-    -> m ()
+readProcessNull :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+                => Maybe (Path Abs Dir)
+                -> EnvOverride
+                -> String
+                -> [String]
+                -> m ()
 readProcessNull wd menv name args =
     sinkProcessStdout wd menv name args CL.sinkNull
 
 -- | Run the given command in the given directory. If it exits with anything
 -- but success, prints an error and then calls 'exitWith' to exit the program.
-readInNull :: forall (m :: * -> *).
-               (MonadLogger m,MonadIO m)
-            => Path Abs Dir -- ^ directory to run in
-            -> FilePath -- ^ command to run
-            -> EnvOverride
-            -> [String] -- ^ command line arguments
-            -> Maybe Text
-            -> m ()
+readInNull :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+           => Path Abs Dir -- ^ directory to run in
+           -> FilePath -- ^ command to run
+           -> EnvOverride
+           -> [String] -- ^ command line arguments
+           -> Maybe Text
+           -> m ()
 readInNull wd cmd menv args errMsg = do
-    result <- liftIO (try (readProcessNull (Just wd) menv cmd args))
+    result <- try (readProcessNull (Just wd) menv cmd args)
     case result of
         Left (ProcessExitedUnsuccessfully _ ec) -> do
             $logError $
@@ -146,19 +152,19 @@
 
 -- | Try to produce a strict 'S.ByteString' from the stdout of a
 -- process.
-tryProcessStdout :: (MonadIO m)
+tryProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
                  => Maybe (Path Abs Dir)
                  -> EnvOverride
                  -> String
                  -> [String]
-                 -> m (Either ProcessExitedUnsuccessfully S.ByteString)
-tryProcessStdout wd menv name args = do
-  liftIO (try (readProcessStdout wd menv name args))
+                 -> m (Either ReadProcessException S.ByteString)
+tryProcessStdout wd menv name args =
+    try (readProcessStdout wd menv name args)
 
 -- | Produce a strict 'S.ByteString' from the stdout of a
 -- process. Throws a 'ProcessExitedUnsuccessfully' exception if the
 -- process fails.
-readProcessStdout :: (MonadIO m)
+readProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
                   => Maybe (Path Abs Dir)
                   -> EnvOverride
                   -> String
@@ -168,51 +174,74 @@
   sinkProcessStdout wd menv name args CL.consume >>=
   liftIO . evaluate . S.concat
 
--- | Produce a strict 'S.ByteString' from the stdout of a
--- process. Throws a 'ProcessExitedUnsuccessfully' exception if the
--- process fails.  Logs process's stderr using @$logError@.
-readProcessStdoutLogStderr :: (MonadIO m,MonadLogger m,MonadBaseControl IO m)
-                           => Text
-                           -> Maybe (Path Abs Dir)
-                           -> EnvOverride
-                           -> String
-                           -> [String]
-                           -> m S.ByteString
-readProcessStdoutLogStderr stderrPrefix wd menv name args = do
-  stdout <- sinkProcessStdoutLogStderr stderrPrefix wd menv name args CL.consume
-  liftIO (evaluate (S.concat stdout))
+data ReadProcessException
+    = ReadProcessException CreateProcess ExitCode L.ByteString L.ByteString
+    | NoPathFound
+    | ExecutableNotFound String [FilePath]
+    deriving Typeable
+instance Show ReadProcessException where
+    show (ReadProcessException cp ec out err) = concat
+        [ "Running "
+        , showSpec $ cmdspec cp
+        , " exited with "
+        , show ec
+        , "\n"
+        , toStr out
+        , "\n"
+        , toStr err
+        ]
+      where
+        toStr = LT.unpack . LT.decodeUtf8With lenientDecode
 
--- | Consume the stdout of a process feeding strict 'S.ByteString's to a consumer.
--- Logs process's stderr using @$logError@.
-sinkProcessStdoutLogStderr :: (MonadIO m,MonadLogger m,MonadBaseControl IO m)
-                           => Text -- ^ Prefix for any logged stderr message
-                           -> Maybe (Path Abs Dir)
-                           -> EnvOverride
-                           -> String
-                           -> [String]
-                           -> Sink S.ByteString IO a -- ^ Sink for stdout
-                           -> m a
-sinkProcessStdoutLogStderr stderrPrefix wd menv name args sinkStdout = do
-  runInBase <- liftBaseWith $ \run -> return (void . run)
-  let logSink = CC.mapM_ (liftIO . runInBase . $logError . T.append stderrPrefix)
-      sinkStderr = CC.decodeUtf8 =$= CC.line logSink
-  (_,stdout) <- sinkProcessStderrStdout wd menv name args sinkStderr sinkStdout
-  return stdout
+        showSpec (ShellCommand str) = str
+        showSpec (RawCommand cmd args) =
+            unwords $ cmd : map (T.unpack . showProcessArgDebug) args
+    show NoPathFound = "PATH not found in EnvOverride"
+    show (ExecutableNotFound name path) = concat
+        [ "Executable named "
+        , name
+        , " not found on path: "
+        , show path
+        ]
+instance Exception ReadProcessException
 
 -- | Consume the stdout of a process feeding strict 'S.ByteString's to a consumer.
-sinkProcessStdout :: (MonadIO m)
-                  => Maybe (Path Abs Dir)
-                  -> EnvOverride
-                  -> String
-                  -> [String]
-                  -> Sink S.ByteString IO a
-                  -> m a
-sinkProcessStdout wd menv name args sink = do
-  (_,stdout) <- sinkProcessStderrStdout wd menv name args CL.sinkNull sink
-  return stdout
+-- If the process fails, spits out stdout and stderr as error log
+-- level. Should not be used for long-running processes or ones with
+-- lots of output; for that use 'sinkProcessStdoutLogStderr'.
+sinkProcessStdout
+    :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m)
+    => Maybe (Path Abs Dir)
+    -> EnvOverride
+    -> String
+    -> [String]
+    -> Sink S.ByteString IO a -- ^ Sink for stdout
+    -> m a
+sinkProcessStdout wd menv name args sinkStdout = do
+  stderrBuffer <- liftIO (newIORef mempty)
+  stdoutBuffer <- liftIO (newIORef mempty)
+  (_,sinkRet) <-
+      catch
+          (sinkProcessStderrStdout
+               wd
+               menv
+               name
+               args
+               (CL.mapM_ (\bytes -> liftIO (modifyIORef' stdoutBuffer (<> byteString bytes))))
+               (CL.iterM (\bytes -> liftIO (modifyIORef' stdoutBuffer (<> byteString bytes))) $=
+                sinkStdout))
+          (\(ProcessExitedUnsuccessfully cp ec) ->
+               do stderrBuilder <- liftIO (readIORef stderrBuffer)
+                  stdoutBuilder <- liftIO (readIORef stdoutBuffer)
+                  throwM $ ReadProcessException
+                    cp
+                    ec
+                    (toLazyByteString stdoutBuilder)
+                    (toLazyByteString stderrBuilder))
+  return sinkRet
 
 -- | Consume the stdout and stderr of a process feeding strict 'S.ByteString's to the consumers.
-sinkProcessStderrStdout :: (MonadIO m)
+sinkProcessStderrStdout :: (MonadIO m, MonadLogger m)
                         => Maybe (Path Abs Dir)
                         -> EnvOverride
                         -> String
@@ -221,6 +250,7 @@
                         -> Sink S.ByteString IO o -- ^ Sink for stdout
                         -> m (e,o)
 sinkProcessStderrStdout wd menv name args sinkStderr sinkStdout = do
+  $logProcessRun name args
   name' <- preProcess wd menv name
   liftIO (withCheckedProcess
             (proc name' args) { env = envHelper menv, cwd = fmap toFilePath wd }
@@ -232,7 +262,6 @@
   where asBSSource :: Source m S.ByteString -> Source m S.ByteString
         asBSSource = id
 
-
 -- | Perform pre-call-process tasks.  Ensure the working directory exists and find the
 -- executable path.
 preProcess :: (MonadIO m) => Maybe (Path Abs Dir) -> EnvOverride -> String -> m FilePath
@@ -285,17 +314,3 @@
     getEnvironment >>=
           mkEnvOverride platform
         . Map.fromList . map (T.pack *** T.pack)
-
-data FindExecutableException
-    = NoPathFound
-    | ExecutableNotFound String [FilePath]
-    deriving Typeable
-instance Exception FindExecutableException
-instance Show FindExecutableException where
-    show NoPathFound = "PATH not found in EnvOverride"
-    show (ExecutableNotFound name path) = concat
-        [ "Executable named "
-        , name
-        , " not found on path: "
-        , show path
-        ]
diff --git a/src/main/Main.hs b/src/main/Main.hs
--- a/src/main/Main.hs
+++ b/src/main/Main.hs
@@ -10,7 +10,7 @@
 module Main where
 
 import           Control.Exception
-import           Control.Monad (join)
+import           Control.Monad (join, when)
 import           Control.Monad.IO.Class (liftIO)
 import           Control.Monad.Logger
 import           Control.Monad.Reader (asks, runReaderT)
@@ -19,42 +19,49 @@
 import qualified Data.List as List
 import           Data.Map (Map)
 import qualified Data.Map as Map
-import           Data.Maybe (isJust)
+import           Data.Maybe
 import           Data.Monoid
+import           Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.IO as T
 import           Network.HTTP.Client
+import           Network.HTTP.Client.Conduit (getHttpManager)
+import           Options.Applicative.Args
 import           Options.Applicative.Builder.Extra
 import           Options.Applicative.Simple
 import           Options.Applicative.Types (readerAsk)
 import           Path (toFilePath)
 import qualified Paths_stack as Meta
 import           Plugins
-import           Plugins.Commands
 import           Stack.Build
 import           Stack.Build.Types
 import           Stack.Config
 import           Stack.Constants
 import qualified Stack.Docker as Docker
+import           Stack.Exec
 import           Stack.Fetch
-import           Stack.GhcPkg (envHelper,getCabalPkgVer)
+import           Stack.GhcPkg (getCabalPkgVer)
+import           Stack.Init
+import           Stack.New
 import qualified Stack.PackageIndex
 import           Stack.Path
+import           Stack.Repl
 import           Stack.Setup
 import           Stack.Types
 import           Stack.Types.StackT
+import qualified Stack.Upload as Upload
 import           System.Environment (getArgs, getProgName)
 import           System.Exit
 import           System.FilePath (searchPathSeparator)
 import           System.IO (stderr)
-import qualified System.Process as P
 import qualified System.Process.Read
 
 -- | Commandline dispatcher.
 main :: IO ()
 main =
-  do plugins <- findPlugins (T.pack stackProgName)
-     tryRunPlugin plugins
+  do when False $ do -- https://github.com/commercialhaskell/stack/issues/322
+       plugins <- findPlugins (T.pack stackProgName)
+       tryRunPlugin plugins
      progName <- getProgName
      args <- getArgs
      execExtraHelp args
@@ -87,6 +94,14 @@
                         "Generate haddocks for the project(s) in this directory/configuration"
                         (buildCmd DoHaddock)
                         buildOpts
+             addCommand "new"
+                        "Create a brand new project"
+                        (\_ _ -> newProject)
+                        (pure ())
+             addCommand "init"
+                        "Initialize a stack project based on one or more cabal packages"
+                        initCmd
+                        initOptsParser
              addCommand "setup"
                         "Get the appropriate ghc for your project"
                         setupCmd
@@ -99,30 +114,39 @@
                         "Update the package index"
                         updateCmd
                         (pure ())
+             addCommand "upload"
+                        "Upload a package to Hackage"
+                        uploadCmd
+                        (many $ strArgument $ metavar "TARBALL/DIR")
              addCommand "exec"
                         "Execute a command"
                         execCmd
                         ((,)
-                            <$> strArgument (metavar "[--] CMD")
-                            <*> many (strArgument (metavar "ARGS")))
+                            <$> strArgument (metavar "CMD")
+                            <*> many (strArgument (metavar "-- ARGS (e.g. stack exec -- ghc --version)")))
              addCommand "ghc"
                         "Run ghc"
                         execCmd
                         ((,)
                             <$> pure "ghc"
-                            <*> many (strArgument (metavar "ARGS")))
+                            <*> many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)")))
              addCommand "ghci"
-                        "Run ghci"
-                        execCmd
-                        ((,)
-                            <$> pure "ghci"
-                            <*> many (strArgument (metavar "ARGS")))
+                        "Run ghci in the context of project(s)"
+                        replCmd
+                        ((,) <$>
+                         fmap (map T.pack)
+                              (many (strArgument
+                                       (metavar "TARGET" <>
+                                        help "If none specified, use all packages defined in current directory"))) <*>
+                         many (strOption (long "ghc-options" <>
+                                          metavar "OPTION" <>
+                                          help "Additional options passed to GHCi")))
              addCommand "runghc"
                         "Run runghc"
                         execCmd
                         ((,)
                             <$> pure "runghc"
-                            <*> many (strArgument (metavar "ARGS")))
+                            <*> many (strArgument (metavar "-- ARGS (e.g. stack runghc -- X.hs)")))
              addCommand "clean"
                         "Clean the local packages"
                         cleanCmd
@@ -163,7 +187,8 @@
                               dockerExecCmd
                               ((,) <$> strArgument (metavar "[--] CMD")
                                    <*> many (strArgument (metavar "ARGS"))))
-             commandsFromPlugins plugins pluginShouldHaveRun)
+             )
+             -- commandsFromPlugins plugins pluginShouldHaveRun) https://github.com/commercialhaskell/stack/issues/322
      run level `catch` \e -> do
         -- This special handler stops "stack: " from being printed before the
         -- exception
@@ -179,9 +204,9 @@
 pathCmd :: PathArg -> GlobalOpts -> IO ()
 pathCmd pathArg go@GlobalOpts{..} = do
   (manager,lc) <- loadConfigWithOpts go
-  buildConfig <- runStackLoggingT manager globalLogLevel (lcLoadBuildConfig lc ExecStrategy)
-  runStackT manager globalLogLevel buildConfig (pathString pathArg) >>= putStrLn
-
+  buildConfig <- runStackLoggingT manager globalLogLevel globalTerminal
+    (lcLoadBuildConfig lc globalResolver ExecStrategy)
+  runStackT manager globalLogLevel buildConfig globalTerminal (pathString pathArg) >>= putStrLn
 
 -- Try to run a plugin
 tryRunPlugin :: Plugins -> IO ()
@@ -229,18 +254,18 @@
 setupCmd :: SetupCmdOpts -> GlobalOpts -> IO ()
 setupCmd SetupCmdOpts{..} go@GlobalOpts{..} = do
   (manager,lc) <- loadConfigWithOpts go
-  runStackLoggingT manager globalLogLevel $
+  runStackLoggingT manager globalLogLevel globalTerminal $
       Docker.rerunWithOptionalContainer
           (lcConfig lc)
           (lcProjectRoot lc)
-          (runStackLoggingT manager globalLogLevel $ do
+          (runStackLoggingT manager globalLogLevel globalTerminal $ do
               (ghc, mstack) <-
                   case scoGhcVersion of
                       Just v -> return (v, Nothing)
                       Nothing -> do
-                          bc <- lcLoadBuildConfig lc ThrowException
+                          bc <- lcLoadBuildConfig lc globalResolver ThrowException
                           return (bcGhcVersion bc, Just $ bcStackYaml bc)
-              mpaths <- runStackT manager globalLogLevel (lcConfig lc) $ ensureGHC SetupOpts
+              mpaths <- runStackT manager globalLogLevel (lcConfig lc) globalTerminal $ ensureGHC SetupOpts
                   { soptsInstallIfMissing = True
                   , soptsUseSystem =
                     (configSystemGHC $ lcConfig lc)
@@ -262,13 +287,13 @@
                 -> IO ()
 withBuildConfig go@GlobalOpts{..} strat inner = do
     (manager, lc) <- loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $
+    runStackLoggingT manager globalLogLevel globalTerminal $
         Docker.rerunWithOptionalContainer (lcConfig lc) (lcProjectRoot lc) $ do
-            bconfig1 <- runStackLoggingT manager globalLogLevel $
-                lcLoadBuildConfig lc strat
+            bconfig1 <- runStackLoggingT manager globalLogLevel globalTerminal $
+                lcLoadBuildConfig lc globalResolver strat
             (bconfig2,cabalVer) <-
                 runStackT
-                    manager globalLogLevel bconfig1
+                    manager globalLogLevel bconfig1 globalTerminal
                     (do cfg <- setupEnv
                         menv <- runReaderT getMinimalEnvOverride cfg
                         cabalVer <- getCabalPkgVer menv
@@ -277,6 +302,7 @@
                 manager
                 globalLogLevel
                 (EnvConfig bconfig2 cabalVer)
+                globalTerminal
                 inner
 
 cleanCmd :: () -> GlobalOpts -> IO ()
@@ -313,7 +339,7 @@
 
 -- | Build the project.
 buildCmd :: FinalAction -> BuildOpts -> GlobalOpts -> IO ()
-buildCmd finalAction opts go@GlobalOpts{..} = withBuildConfig go CreateConfig $
+buildCmd finalAction opts go@GlobalOpts{..} = withBuildConfig go ThrowException $
     Stack.Build.build opts { boptsFinalAction = finalAction }
 
 -- | Install
@@ -325,9 +351,9 @@
 unpackCmd :: [String] -> GlobalOpts -> IO ()
 unpackCmd names go@GlobalOpts{..} = do
     (manager,lc) <- loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $
+    runStackLoggingT manager globalLogLevel globalTerminal $
         Docker.rerunWithOptionalContainer (lcConfig lc) (lcProjectRoot lc) $
-            runStackT manager globalLogLevel (lcConfig lc) $ do
+            runStackT manager globalLogLevel (lcConfig lc) globalTerminal $ do
                 menv <- getMinimalEnvOverride
                 Stack.Fetch.unpackPackages menv "." names
 
@@ -335,57 +361,64 @@
 updateCmd :: () -> GlobalOpts -> IO ()
 updateCmd () go@GlobalOpts{..} = do
     (manager,lc) <- loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $
+    runStackLoggingT manager globalLogLevel globalTerminal $
         Docker.rerunWithOptionalContainer (lcConfig lc) (lcProjectRoot lc) $
-            runStackT manager globalLogLevel (lcConfig lc) $
+            runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
                 getMinimalEnvOverride >>= Stack.PackageIndex.updateAllIndices
 
--- | Execute a command
+-- | Upload to Hackage
+uploadCmd :: [String] -> GlobalOpts -> IO ()
+uploadCmd args0 go = withBuildConfig go ExecStrategy $ do
+    let args = if null args0 then ["."] else args0
+    config <- asks getConfig
+    manager <- asks getHttpManager
+    menv <- getMinimalEnvOverride
+    runghc <- join $ System.Process.Read.findExecutable menv "runghc"
+    liftIO $ do
+        uploader <- Upload.mkUploader
+              (toFilePath runghc)
+              config
+            $ Upload.setGetManager (return manager)
+              Upload.defaultUploadSettings
+        mapM_ (Upload.upload uploader) args
+
+-- | Execute a command.
 execCmd :: (String, [String]) -> GlobalOpts -> IO ()
-execCmd (cmd, args) go@GlobalOpts{..} = withBuildConfig go ExecStrategy $ do
-      config <- asks getConfig
-      liftIO $ do
-          menv <- configEnvOverride config
-                          EnvSettings
-                              { esIncludeLocals = True
-                              , esIncludeGhcPackagePath = True
-                              }
-          cmd' <- join $ System.Process.Read.findExecutable menv cmd
-          let cp = (P.proc (toFilePath cmd') args)
-                  { P.env = envHelper menv
-                  , P.delegate_ctlc = True
-                  }
+execCmd (cmd,args) go@GlobalOpts{..} =
+    withBuildConfig go ExecStrategy $
+    exec cmd args
 
-          (Nothing, Nothing, Nothing, ph) <- P.createProcess cp
-          ec <- P.waitForProcess ph
-          exitWith ec
+-- | Run the REPL in the context of a project, with
+replCmd :: ([Text], [String]) -> GlobalOpts -> IO ()
+replCmd (targets,args) go@GlobalOpts{..} = withBuildConfig go ExecStrategy $ do
+      repl targets args
 
 -- | Pull the current Docker image.
 dockerPullCmd :: () -> GlobalOpts -> IO ()
 dockerPullCmd _ go@GlobalOpts{..} = do
     (manager,lc) <- liftIO $ loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $ Docker.preventInContainer $
+    runStackLoggingT manager globalLogLevel globalTerminal $ Docker.preventInContainer $
         Docker.pull (lcConfig lc)
 
 -- | Reset the Docker sandbox.
 dockerResetCmd :: Bool -> GlobalOpts -> IO ()
 dockerResetCmd keepHome go@GlobalOpts{..} = do
     (manager,lc) <- liftIO (loadConfigWithOpts go)
-    runStackLoggingT manager globalLogLevel $ Docker.preventInContainer $
+    runStackLoggingT manager globalLogLevel globalTerminal$ Docker.preventInContainer $
         Docker.reset (lcProjectRoot lc) keepHome
 
 -- | Cleanup Docker images and containers.
 dockerCleanupCmd :: Docker.CleanupOpts -> GlobalOpts -> IO ()
 dockerCleanupCmd cleanupOpts go@GlobalOpts{..} = do
     (manager,lc) <- liftIO $ loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $ Docker.preventInContainer $
+    runStackLoggingT manager globalLogLevel globalTerminal$ Docker.preventInContainer $
         Docker.cleanup (lcConfig lc) cleanupOpts
 
 -- | Execute a command
 dockerExecCmd :: (String, [String]) -> GlobalOpts -> IO ()
 dockerExecCmd (cmd,args) go@GlobalOpts{..} = do
     (manager,lc) <- liftIO $ loadConfigWithOpts go
-    runStackLoggingT manager globalLogLevel $ Docker.preventInContainer $
+    runStackLoggingT manager globalLogLevel globalTerminal$ Docker.preventInContainer $
         Docker.rerunCmdWithRequiredContainer (lcConfig lc)
                                              (lcProjectRoot lc)
                                              (return (cmd,args,lcConfig lc))
@@ -395,7 +428,7 @@
 buildOpts =
             BuildOpts <$> target <*> libProfiling <*> exeProfiling <*>
             optimize <*> finalAction <*> dryRun <*> ghcOpts <*> flags <*>
-            installExes
+            installExes <*> preFetch <*> testArgs <*> onlySnapshot
   where optimize =
           maybeBoolFlags "optimizations" "optimizations for TARGETs and all its dependencies" idm
         target =
@@ -435,6 +468,20 @@
                <> help "Override flags set in stack.yaml (applies to local packages and extra-deps)"
                 ))
 
+        preFetch = flag False True
+            (long "prefetch" <>
+             help "Fetch packages necessary for the build immediately, useful with --dry-run")
+        testArgs =
+             fmap (fromMaybe [])
+                  (optional
+                       (argsOption
+                            (long "test-arguments" <> metavar "TEST_ARGS" <>
+                             help "Arguments passed in to the test suite program")))
+
+        onlySnapshot = flag False True
+            (long "only-snapshot" <>
+             help "Only build packages for the snapshot database, not the local database")
+
 -- | Parser for docker cleanup arguments.
 dockerCleanupOpts :: Parser Docker.CleanupOpts
 dockerCleanupOpts =
@@ -483,9 +530,15 @@
 -- | Parser for global command-line options.
 globalOpts :: Parser GlobalOpts
 globalOpts =
-    GlobalOpts
-    <$> logLevelOpt
-    <*> configOptsParser False
+    GlobalOpts <$> logLevelOpt <*>
+    configOptsParser False <*>
+    optional resolverParser <*>
+    flag
+        True
+        False
+        (long "no-terminal" <>
+         help
+             "Override terminal detection in the case of running in a false terminal")
 
 -- | Parse for a logging level.
 logLevelOpt :: Parser LogLevel
@@ -514,6 +567,13 @@
             "error" -> LevelError
             _ -> LevelOther (T.pack s)
 
+resolverParser :: Parser Resolver
+resolverParser =
+    option readResolver
+        (long "resolver" <>
+         metavar "RESOLVER" <>
+         help "Override resolver in project file")
+
 -- | Default logging level should be something useful but not crazy.
 defaultLogLevel :: LogLevel
 defaultLogLevel = LevelInfo
@@ -522,6 +582,8 @@
 data GlobalOpts = GlobalOpts
     { globalLogLevel     :: LogLevel -- ^ Log level
     , globalConfigMonoid :: ConfigMonoid -- ^ Config monoid, for passing into 'loadConfig'
+    , globalResolver     :: Maybe Resolver -- ^ Resolver override
+    , globalTerminal     :: Bool -- ^ We're in a terminal?
     } deriving (Show)
 
 -- | Load the configuration with a manager. Convenience function used
@@ -532,5 +594,15 @@
     lc <- runStackLoggingT
               manager
               globalLogLevel
+              globalTerminal
               (loadConfig globalConfigMonoid)
     return (manager,lc)
+
+-- | Project initialization
+initCmd :: InitOpts -> GlobalOpts -> IO ()
+initCmd initOpts go@GlobalOpts{..} = do
+  (manager,lc) <- loadConfigWithOpts go
+  runStackLoggingT manager globalLogLevel globalTerminal $
+        Docker.rerunWithOptionalContainer (lcConfig lc) (lcProjectRoot lc) $
+            runStackT manager globalLogLevel (lcConfig lc) globalTerminal $
+                initProject globalResolver initOpts
diff --git a/src/main/Plugins.hs b/src/main/Plugins.hs
--- a/src/main/Plugins.hs
+++ b/src/main/Plugins.hs
@@ -112,10 +112,20 @@
 
 
 -- | Things that can go wrong when using `callPlugin`.
+-- Note: it may be wiser to catch PluginExitFailure, rather than display it.
 data PluginException
   = PluginNotFound !Plugins !Text
   | PluginExitFailure !Plugin !Int
-  deriving (Show, Typeable)
+  deriving (Typeable)
+instance Show PluginException where
+  show (PluginNotFound plugins requestedPlugin)
+     = "Plugin not found for '" ++ unpack (_pluginsPrefix plugins)
+    ++ "': '" ++ unpack requestedPlugin ++ "'"
+  show (PluginExitFailure plugin exitCode)
+     = "Plugin '" ++ unpack (_pluginName plugin)
+    ++ "' for '" ++ unpack (_pluginPrefix plugin)
+    ++ "' failed with exit code: "
+    ++ show exitCode
 instance Exception PluginException
 
 -- | Look up a particular plugin by name.
diff --git a/src/test/Stack/ArgsSpec.hs b/src/test/Stack/ArgsSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/test/Stack/ArgsSpec.hs
@@ -0,0 +1,31 @@
+-- | Args parser test suite.
+
+module Stack.ArgsSpec where
+
+import Control.Monad
+import Options.Applicative.Args
+import Test.Hspec
+
+-- | Test spec.
+spec :: Spec
+spec =
+    forM_
+        tests
+        (\(input,output) ->
+              it input (parseArgsFromString input == output))
+
+-- | Fairly comprehensive checks.
+tests :: [(String, Either String [String])]
+tests =
+    [ ("x", Right ["x"])
+    , ("x y z", Right ["x", "y", "z"])
+    , ("aaa bbb ccc", Right ["aaa", "bbb", "ccc"])
+    , ("    aaa    bbb    ccc   ", Right ["aaa", "bbb", "ccc"])
+    , ("aaa\"", Left "unterminated string: endOfInput")
+    , ("\"", Left "unterminated string: endOfInput")
+    , ("\"\"", Right [""])
+    , ("\"aaa", Left "unterminated string: endOfInput")
+    , ("\"aaa\" bbb ccc \"ddd\"", Right ["aaa", "bbb", "ccc", "ddd"])
+    , ("\"aa\\\"a\" bbb ccc \"ddd\"", Right ["aa\"a", "bbb", "ccc", "ddd"])
+    , ("\"aa\\\"a\" bb\\b ccc \"ddd\"", Right ["aa\"a", "bb\\b", "ccc", "ddd"])
+    , ("\"\" \"\" c", Right ["","","c"])]
diff --git a/src/test/Stack/BuildPlanSpec.hs b/src/test/Stack/BuildPlanSpec.hs
--- a/src/test/Stack/BuildPlanSpec.hs
+++ b/src/test/Stack/BuildPlanSpec.hs
@@ -39,8 +39,8 @@
 spec :: Spec
 spec = beforeAll setup $ afterAll teardown $ do
     let logLevel = LevelDebug
-    let loadConfig' m = runStackLoggingT m logLevel (loadConfig mempty)
-    let loadBuildConfigRest m = runStackLoggingT m logLevel
+    let loadConfig' m = runStackLoggingT m logLevel False (loadConfig mempty)
+    let loadBuildConfigRest m = runStackLoggingT m logLevel False
     let inTempDir action = do
             currentDirectory <- getCurrentDirectory
             withSystemTempDirectory "Stack_BuildPlanSpec" $ \tempDir -> do
@@ -53,8 +53,8 @@
         -- github still depends on failure.
         writeFile "stack.yaml" "resolver: lts-2.9"
         LoadConfig{..} <- loadConfig' manager
-        bconfig <- loadBuildConfigRest manager (lcLoadBuildConfig ThrowException)
-        runStackT manager logLevel bconfig $ do
+        bconfig <- loadBuildConfigRest manager (lcLoadBuildConfig Nothing ThrowException)
+        runStackT manager logLevel bconfig False $ do
             menv <- getMinimalEnvOverride
             mbp <- loadMiniBuildPlan $ LTS 2 9
             eres <- try $ resolveBuildPlan
diff --git a/src/test/Stack/ConfigSpec.hs b/src/test/Stack/ConfigSpec.hs
--- a/src/test/Stack/ConfigSpec.hs
+++ b/src/test/Stack/ConfigSpec.hs
@@ -60,8 +60,8 @@
 
 
   describe "loadConfig" $ do
-    let loadConfig' m = runStackLoggingT m logLevel (loadConfig mempty)
-    let loadBuildConfigRest m = runStackLoggingT m logLevel
+    let loadConfig' m = runStackLoggingT m logLevel False (loadConfig mempty)
+    let loadBuildConfigRest m = runStackLoggingT m logLevel False
     -- TODO(danburton): make sure parent dirs also don't have config file
     it "works even if no config file exists" $ \T{..} -> example $ do
       _config <- loadConfig' manager
@@ -80,7 +80,7 @@
       setCurrentDirectory childDir
       LoadConfig{..} <- loadConfig' manager
       BuildConfig{..} <- loadBuildConfigRest manager
-                            (lcLoadBuildConfig ThrowException)
+                            (lcLoadBuildConfig Nothing ThrowException)
       bcRoot `shouldBe` parentDir
 
     it "respects the STACK_YAML env variable" $ \T{..} -> inTempDir $ do
@@ -91,7 +91,7 @@
         withEnvVar "STACK_YAML" stackYamlFp $ do
           LoadConfig{..} <- loadConfig' manager
           BuildConfig{..} <- loadBuildConfigRest manager
-                                (lcLoadBuildConfig ThrowException)
+                                (lcLoadBuildConfig Nothing ThrowException)
           bcRoot `shouldBe` dir
 
     it "STACK_YAML can be relative" $ \T{..} -> inTempDir $ do
@@ -104,5 +104,5 @@
         withEnvVar "STACK_YAML" (toFilePath yamlRel) $ do
             LoadConfig{..} <- loadConfig' manager
             BuildConfig{..} <- loadBuildConfigRest manager
-                                (lcLoadBuildConfig ThrowException)
+                                (lcLoadBuildConfig Nothing ThrowException)
             bcStackYaml `shouldBe` yamlAbs
diff --git a/stack.cabal b/stack.cabal
--- a/stack.cabal
+++ b/stack.cabal
@@ -1,5 +1,5 @@
 name:                stack
-version:             0.0.2.1
+version:             0.0.3
 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
@@ -26,17 +26,22 @@
   hs-source-dirs:    src/
   ghc-options:       -Wall
   exposed-modules:   Options.Applicative.Builder.Extra
+                     Options.Applicative.Args
                      Stack.BuildPlan
                      Stack.Config
                      Stack.Constants
                      Stack.Docker
                      Stack.Docker.GlobalDB
                      Stack.Fetch
+                     Stack.Exec
                      Stack.GhcPkg
+                     Stack.Init
+                     Stack.New
                      Stack.Package
                      Stack.PackageDump
                      Stack.PackageIndex
                      Stack.Path
+                     Stack.Repl
                      Stack.Setup
                      Stack.Types
                      Stack.Types.Internal
@@ -57,7 +62,9 @@
                      Stack.Build.Source
                      Stack.Build.Types
                      Stack.Build.Doc
+                     Stack.Upload
                      System.Process.Read
+                     System.Process.Log
                      System.Process.Run
                      Network.HTTP.Download.Verified
   other-modules:     Network.HTTP.Download
@@ -104,7 +111,7 @@
                    , mtl >= 2.1.3.1
                    , old-locale >= 1.0.0.6
                    , optparse-applicative
-                   , path >= 0.5.0
+                   , path >= 0.5.1
                    , persistent >= 2.1.2
                    , persistent-sqlite >= 2.1.4
                    , persistent-template >= 2.1.1
@@ -173,6 +180,7 @@
                 , Stack.BuildPlanSpec
                 , Stack.ConfigSpec
                 , Stack.PackageDumpSpec
+                , Stack.ArgsSpec
   ghc-options:    -Wall -threaded
   build-depends:  base >=4.7 && <5
                 , hspec
