diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,22 @@
 Changelog for Shake (* = breaking change)
 
+0.18.4, released 2019-12-15
+    #734, add Forward.cacheActionWith
+    #734, make forward fail if shakeLintInside is empty
+*   #701, make shakeSymlink=False the default
+    #694, embed data files in the binary (use embed-files Cabal flag)
+    #722, don't close file handles in cmd unless CloseFileHandles
+    #727, add actionBracket for bracket-like operations
+    #720, add --share-sanity to sanity check shared cache contents
+    #719, write history key after cached files to ensure it is coherent
+    #703, print rebuild warnings in yellow and errors in red
+    #706, rename Verbosity's constructors and add Warn
+    #443, add getEnvError
+    #662, add Partial to command/cmd
+    #708, rename deprioritize to reschedule
+    #689, allow adding help messages at the end using addHelpSuffix
+    #686, rename Build.hs to Shakefile.hs
+    #690, make Ninja mode use -j0 by default (more like Ninja)
 0.18.3, released 2019-07-01
     Add shakeSymlink to enable/disable symlinks in the shared cache
     Improve cmd on async exceptions with nested processes on Windows
diff --git a/docs/Manual.md b/docs/Manual.md
--- a/docs/Manual.md
+++ b/docs/Manual.md
@@ -14,7 +14,7 @@
         want ["_build/run" <.> exe]
 
         phony "clean" $ do
-            putNormal "Cleaning files in _build"
+            putInfo "Cleaning files in _build"
             removeFilesAfter "_build" ["//*"]
 
         "_build/run" <.> exe %> \out -> do
@@ -27,7 +27,7 @@
             let c = dropDirectory1 $ out -<.> "c"
             let m = out -<.> "m"
             cmd_ "gcc -c" [c] "-o" [out] "-MMD -MF" [m]
-            needMakefileDependencies m
+            neededMakefileDependencies m
 
 This build system builds the executable `_build/run` from all C source files in the current directory. It will rebuild if you add/remove any C files to the directory, if the C files themselves change, or if any headers used by the C files change. All generated files are placed in `_build`, and a `clean` command is provided that will wipe all the generated files. In the rest of this manual we'll explain how the above code works and how to extend it.
 
@@ -37,7 +37,7 @@
 
 1. Install the [Haskell Stack](https://haskellstack.org/), which provides a Haskell compiler and package manager.
 2. Type `stack install shake`, to build and install Shake and all its dependencies.
-3. Type `stack exec -- shake --demo`, which will create a directory containing a sample project, the above Shake script (named `Build.hs`), and execute it (which can be done by `runhaskell Build.hs`). For more details see a [trace of `shake --demo`](Demo.md).
+3. Type `stack exec -- shake --demo`, which will create a directory containing a sample project, the above Shake script (named `Shakefile.hs`), and execute it (which can be done by `runhaskell Shakefile.hs`). For more details see a [trace of `shake --demo`](Demo.md).
 
 ## Basic syntax
 
@@ -224,7 +224,7 @@
 
 That will compile `main.c` to `main.o`, and also produce a file `main.m` containing the dependencies. To add these dependencies as dependencies of this rule we can call:
 
-    needMakefileDependencies "main.m"
+    neededMakefileDependencies "main.m"
 
 Now, if either `main.c` or any headers transitively imported by `main.c` change, the file will be rebuilt. In the initial example the complete rule is:
 
@@ -232,9 +232,9 @@
         let c = dropDirectory1 $ out -<.> "c"
         let m = out -<.> "m"
         cmd_ "gcc -c" [c] "-o" [out] "-MMD -MF" [m]
-        needMakefileDependencies m
+        neededMakefileDependencies m
 
-We first compute the source file `c` (e.g. `"main.c"`) that is associated with the `out` file (e.g. `"_build/main.o"`). We then compute a temporary file `m` to write the dependencies to (e.g. `"_build/main.m"`). We then call `gcc` using the `-MMD -MF` flags and then finally call `needMakefileDependencies`.
+We first compute the source file `c` (e.g. `"main.c"`) that is associated with the `out` file (e.g. `"_build/main.o"`). We then compute a temporary file `m` to write the dependencies to (e.g. `"_build/main.m"`). We then call `gcc` using the `-MMD -MF` flags and then finally call `neededMakefileDependencies`.
 
 #### Top-level variables
 
@@ -258,10 +258,10 @@
 A standard clean command is defined as:
 
     phony "clean" $ do
-        putNormal "Cleaning files in _build"
+        putInfo "Cleaning files in _build"
         removeFilesAfter "_build" ["//*"]
 
-Running the build system with the `clean` argument, e.g. `runhaskell Build.hs clean` will remove all files under the `_build` directory. This clean command is formed from two separate pieces. Firstly, we can define `phony` commands as:
+Running the build system with the `clean` argument, e.g. `runhaskell Shakefile.hs clean` will remove all files under the `_build` directory. This clean command is formed from two separate pieces. Firstly, we can define `phony` commands as:
 
 <pre>
 phony "<i>name</i>" $ do
@@ -270,7 +270,7 @@
 
 Where <tt><i>name</i></tt> is the name used on the command line to invoke the actions, and <tt><i>actions</i></tt> are the list of things to do in response. These names are not dependency tracked and are run afresh each time they are requested.
 
-The <tt><i>actions</i></tt> can be any standard build actions, although for a `clean` rule, `removeFilesAfter` is typical. This function waits until after any files have finished building (which will be none, if you do `runhaskell Build.hs clean`) then deletes all files matching `//*` in the `_build` directory. The `putNormal` function writes out a message to the console, as long as `--quiet` was not passed.
+The <tt><i>actions</i></tt> can be any standard build actions, although for a `clean` rule, `removeFilesAfter` is typical. This function waits until after any files have finished building (which will be none, if you do `runhaskell Shakefile.hs clean`) then deletes all files matching `//*` in the `_build` directory. The `putInfo` function writes out a message to the console, as long as `--quiet` was not passed.
 
 ## Running
 
@@ -278,13 +278,13 @@
 
 #### Compiling the build system
 
-As shown before, we can use `runhaskell Build.hs` to execute our build system, but doing so causes the build script to be compiled afresh each time. A more common approach is to add a shell script that compiles the build system and runs it. In the example directory you will find `build.sh` (Linux) and `build.bat` (Windows), both of which execute the same interesting commands. Looking at `build.sh`:
+As shown before, we can use `runhaskell Shakefile.hs` to execute our build system, but doing so causes the build script to be compiled afresh each time. A more common approach is to add a shell script that compiles the build system and runs it. In the example directory you will find `build.sh` (Linux) and `build.bat` (Windows), both of which execute the same interesting commands. Looking at `build.sh`:
 
     #!/bin/sh
     mkdir -p _shake
-    ghc --make Build.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake/build "$@"
+    ghc --make Shakefile.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake/build "$@"
 
-This script creates a folder named `_shake` for the build system objects to live in, then runs `ghc --make Build.hs` to produce `_shake/build`, then executes `_shake/build` with all arguments it was given. The `-with-rtsopts` flag instructs the Haskell compiler to disable "idle garbage collection", making more CPU available for the commands you are running, as [explained here](https://stackoverflow.com/questions/34588057/why-does-shake-recommend-disabling-idle-garbage-collection/).
+This script creates a folder named `_shake` for the build system objects to live in, then runs `ghc --make Shakefile.hs` to produce `_shake/build`, then executes `_shake/build` with all arguments it was given. The `-with-rtsopts` flag instructs the Haskell compiler to disable "idle garbage collection", making more CPU available for the commands you are running, as [explained here](https://stackoverflow.com/questions/34588057/why-does-shake-recommend-disabling-idle-garbage-collection/).
 
 Now you can run a build by typing `stack exec ./build.sh` on Linux, or `stack exec build.bat` on Windows. On Linux you may want to alias `build` to `stack exec ./build.sh`. For the rest of this document we will assume `build` runs the build system.
 
@@ -342,8 +342,8 @@
 * Run in single-threaded mode (`-j1`) to make any output clearer by not interleaving commands.
 * By default a Shake build system prints out a message every time it runs a command. Use verbose mode (`--verbose`) to print more information to the screen, such as which rule is being run. Additional `--verbose` flags increase the verbosity. Three verbosity flags produce output intended for someone debugging the Shake library itself, rather than a build system based on it.
 * To raise a build error call `error "error message"`. Shake will abort, showing the error message.
-* To output additional information use `putNormal "output message"`. This message will be printed to the console when it is reached.
-* To show additional information with either `error` or `putNormal`, use `error $ show ("message", myVariable)`. This allows you to show any local variables.
+* To output additional information use `putInfo "output message"`. This message will be printed to the console when it is reached.
+* To show additional information with either `error` or `putInfo`, use `error $ show ("message", myVariable)`. This allows you to show any local variables.
 
 ## Extensions
 
diff --git a/docs/manual/Build.hs b/docs/manual/Build.hs
deleted file mode 100644
--- a/docs/manual/Build.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-import Development.Shake
-import Development.Shake.Command
-import Development.Shake.FilePath
-import Development.Shake.Util
-
-main :: IO ()
-main = shakeArgs shakeOptions{shakeFiles="_build"} $ do
-    want ["_build/run" <.> exe]
-
-    phony "clean" $ do
-        putNormal "Cleaning files in _build"
-        removeFilesAfter "_build" ["//*"]
-
-    "_build/run" <.> exe %> \out -> do
-        cs <- getDirectoryFiles "" ["//*.c"]
-        let os = ["_build" </> c -<.> "o" | c <- cs]
-        need os
-        cmd "gcc -o" [out] os
-
-    "_build//*.o" %> \out -> do
-        let c = dropDirectory1 $ out -<.> "c"
-        let m = out -<.> "m"
-        () <- cmd "gcc -c" [c] "-o" [out] "-MMD -MF" [m]
-        needMakefileDependencies m
diff --git a/docs/manual/Shakefile.hs b/docs/manual/Shakefile.hs
new file mode 100644
--- /dev/null
+++ b/docs/manual/Shakefile.hs
@@ -0,0 +1,24 @@
+import Development.Shake
+import Development.Shake.Command
+import Development.Shake.FilePath
+import Development.Shake.Util
+
+main :: IO ()
+main = shakeArgs shakeOptions{shakeFiles="_build"} $ do
+    want ["_build/run" <.> exe]
+
+    phony "clean" $ do
+        putInfo "Cleaning files in _build"
+        removeFilesAfter "_build" ["//*"]
+
+    "_build/run" <.> exe %> \out -> do
+        cs <- getDirectoryFiles "" ["//*.c"]
+        let os = ["_build" </> c -<.> "o" | c <- cs]
+        need os
+        cmd_ "gcc -o" [out] os
+
+    "_build//*.o" %> \out -> do
+        let c = dropDirectory1 $ out -<.> "c"
+        let m = out -<.> "m"
+        cmd_ "gcc -c" [c] "-o" [out] "-MMD -MF" [m]
+        neededMakefileDependencies m
diff --git a/docs/manual/build.bat b/docs/manual/build.bat
--- a/docs/manual/build.bat
+++ b/docs/manual/build.bat
@@ -1,2 +1,2 @@
 @mkdir _shake 2> nul
-@ghc --make Build.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake\build %*
+@ghc --make Shakefile.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake\build %*
diff --git a/docs/manual/build.sh b/docs/manual/build.sh
--- a/docs/manual/build.sh
+++ b/docs/manual/build.sh
@@ -1,3 +1,3 @@
 #!/bin/sh
 mkdir -p _shake
-ghc --make Build.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake/build "$@"
+ghc --make Shakefile.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake/build "$@"
diff --git a/shake.cabal b/shake.cabal
--- a/shake.cabal
+++ b/shake.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               shake
-version:            0.18.3
+version:            0.18.4
 license:            BSD3
 license-file:       LICENSE
 category:           Development, Shake
@@ -30,7 +30,7 @@
     (e.g. compiler version).
 homepage:           https://shakebuild.com
 bug-reports:        https://github.com/ndmitchell/shake/issues
-tested-with:        GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3
+tested-with:        GHC==8.8.1, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3
 extra-doc-files:
     CHANGES.txt
     README.md
@@ -53,7 +53,7 @@
     src/Test/Tup/root.cfg
 data-files:
     docs/manual/build.bat
-    docs/manual/Build.hs
+    docs/manual/Shakefile.hs
     docs/manual/build.sh
     docs/manual/constants.c
     docs/manual/constants.h
@@ -76,6 +76,11 @@
     manual: True
     description: Enable cloud build features
 
+flag embed-files
+    default: False
+    manual: True
+    description: Embed data files into the shake library
+
 library
     default-language: Haskell2010
     hs-source-dirs:   src
@@ -101,6 +106,12 @@
         unordered-containers >= 0.2.7,
         utf8-string >= 0.3
 
+    if flag(embed-files)
+        cpp-options: -DFILE_EMBED
+        build-depends:
+            file-embed >= 0.0.11,
+            template-haskell
+
     if flag(portable)
         cpp-options: -DPORTABLE
     else
@@ -208,6 +219,7 @@
         extra >= 1.6.14,
         filepath,
         filepattern,
+        file-embed >= 0.0.11,
         hashable >= 1.1.2.3,
         heaps >= 0.3.6.1,
         js-dgtable,
@@ -216,11 +228,14 @@
         primitive,
         process >= 1.1,
         random,
+        template-haskell,
         time,
         transformers >= 0.2,
         unordered-containers >= 0.2.7,
         utf8-string >= 0.3
 
+    cpp-options: -DFILE_EMBED
+
     if flag(portable)
         cpp-options: -DPORTABLE
     else
@@ -339,6 +354,12 @@
         unordered-containers >= 0.2.7,
         utf8-string >= 0.3
 
+    if flag(embed-files)
+        cpp-options: -DFILE_EMBED
+        build-depends:
+            file-embed >= 0.0.11,
+            template-haskell
+
     if flag(portable)
         cpp-options: -DPORTABLE
     else
@@ -437,10 +458,10 @@
         Test.C
         Test.Cache
         Test.Cleanup
+        Test.CloseFileHandles
         Test.Command
         Test.Config
         Test.Database
-        Test.Deprioritize
         Test.Digest
         Test.Directory
         Test.Docs
@@ -466,6 +487,7 @@
         Test.Progress
         Test.Random
         Test.Rebuild
+        Test.Reschedule
         Test.Resources
         Test.Self
         Test.SelfMake
diff --git a/src/Development/Ninja/All.hs b/src/Development/Ninja/All.hs
--- a/src/Development/Ninja/All.hs
+++ b/src/Development/Ninja/All.hs
@@ -132,7 +132,7 @@
                         Nothing -> liftIO $ errorIO $ "Ninja pool named " ++ BS.unpack pool ++ " not found, required to build " ++ BS.unpack (BS.unwords out)
                         Just r -> withResource r 1 act
 
-                when (description /= "") $ putNormal description
+                when (description /= "") $ putInfo description
                 let (cmdOpts, cmdProg, cmdArgs) = toCommand commandline
                 if deps == "msvc" then do
                     Stdout stdout <- withPool $ command cmdOpts cmdProg cmdArgs
diff --git a/src/Development/Shake.hs b/src/Development/Shake.hs
--- a/src/Development/Shake.hs
+++ b/src/Development/Shake.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TypeFamilies, ConstraintKinds #-}
+{-# LANGUAGE TypeFamilies, ConstraintKinds, PatternSynonyms #-}
 
 -- | This module is used for defining Shake build systems. As a simple example of a Shake build system,
 --   let us build the file @result.tar@ from the files listed by @result.txt@:
@@ -54,20 +54,20 @@
     shakeOptions,
     Rules, action, withoutActions, alternatives, priority, versioned,
     Action, traced,
-    liftIO, actionOnException, actionFinally, actionCatch, actionRetry, runAfter,
+    liftIO, actionOnException, actionFinally, actionBracket, actionCatch, actionRetry, runAfter,
     ShakeException(..),
     -- * Configuration
     ShakeOptions(..), Rebuild(..), Lint(..), Change(..),
     getShakeOptions, getShakeOptionsRules, getHashedShakeVersion,
     getShakeExtra, getShakeExtraRules, addShakeExtra,
     -- ** Command line
-    shakeArgs, shakeArgsWith, shakeArgsOptionsWith, shakeOptDescrs,
+    shakeArgs, shakeArgsWith, shakeArgsOptionsWith, shakeOptDescrs, addHelpSuffix,
     -- ** Targets
     getTargets, addTarget, withTargetDocs, withoutTargets,
     -- ** Progress reporting
     Progress(..), progressSimple, progressDisplay, progressTitlebar, progressProgram, getProgress,
     -- ** Verbosity
-    Verbosity(..), getVerbosity, putLoud, putNormal, putQuiet, withVerbosity, quietly,
+    Verbosity(..), getVerbosity, putVerbose, putInfo, putWarn, putError, withVerbosity, quietly,
     -- * Running commands
     command, command_, cmd, cmd_, unit,
     Stdout(..), StdoutTrim(..), Stderr(..), Stdouterr(..), Exit(..), Process(..), CmdTime(..), CmdLine(..), FSATrace(..),
@@ -92,7 +92,7 @@
     doesFileExist, doesDirectoryExist, getDirectoryContents, getDirectoryFiles, getDirectoryDirs,
     getDirectoryFilesIO,
     -- * Environment rules
-    getEnv, getEnvWithDefault,
+    getEnv, getEnvWithDefault, getEnvError,
     -- * Oracle rules
     ShakeValue, RuleResult, addOracle, addOracleCache, addOracleHash, askOracle, askOracles,
     -- * Special rules
@@ -108,14 +108,17 @@
     needHasChanged,
     resultHasChanged,
     batch,
-    deprioritize,
+    reschedule,
     -- * Deprecated
     (*>), (|*>), (&*>),
     (**>), (*>>), (?>>),
-    askOracleWith
+    askOracleWith,
+    deprioritize,
+    pattern Quiet, pattern Normal, pattern Loud, pattern Chatty,
+    putLoud, putNormal, putQuiet
     ) where
 
-import Prelude(Maybe, FilePath) -- Since GHC 7.10 duplicates *>
+import Prelude(Maybe, FilePath, Double, String) -- Since GHC 7.10 duplicates *>
 
 -- I would love to use module export in the above export list, but alas Haddock
 -- then shows all the things that are hidden in the docs, which is terrible.
@@ -267,3 +270,31 @@
 --   since the 'RuleResult' type family now fixes the result type.
 askOracleWith :: (RuleResult q ~ a, ShakeValue q, ShakeValue a) => q -> a -> Action a
 askOracleWith question _ = askOracle question
+
+---------------------------------------------------------------------
+-- DEPRECATED SINCE 0.18.4, JUL 2019
+
+-- | /Deprecated:/ Alias for 'reschedule'.
+deprioritize :: Double -> Action ()
+deprioritize = reschedule
+
+-- | /Deprecated:/ A bidirectional pattern synonym for 'Error'.
+pattern Quiet :: Verbosity
+pattern Quiet  = Error
+-- | /Deprecated:/ A bidirectional pattern synonym for 'Info'.
+pattern Normal :: Verbosity
+pattern Normal = Info
+-- | /Deprecated:/ A bidirectional pattern synonym for 'Verbose'.
+pattern Loud :: Verbosity
+pattern Loud   = Verbose
+-- | /Deprecated:/ A bidirectional pattern synonym for 'Verbose'.
+pattern Chatty :: Verbosity
+pattern Chatty = Verbose
+
+putLoud, putNormal, putQuiet :: String -> Action ()
+-- | /Deprecated:/ Alias for 'putVerbose'.
+putLoud = putVerbose
+-- | /Deprecated:/ Alias for 'putInfo'.
+putNormal = putInfo
+-- | /Deprecated:/ Alias for 'putError'.
+putQuiet = putError
diff --git a/src/Development/Shake/Command.hs b/src/Development/Shake/Command.hs
--- a/src/Development/Shake/Command.hs
+++ b/src/Development/Shake/Command.hs
@@ -254,7 +254,7 @@
 -- ACTION EXPLICIT OPERATION
 
 -- | Given explicit operations, apply the Action ones, like skip/trace/track/autodep
-commandExplicitAction :: Params -> Action [Result]
+commandExplicitAction :: Partial => Params -> Action [Result]
 commandExplicitAction oparams = do
     ShakeOptions{shakeCommandOptions,shakeRunCommands,shakeLint,shakeLintInside} <- getShakeOptions
     params@Params{..} <- return $ oparams{opts = shakeCommandOptions ++ opts oparams}
@@ -263,12 +263,12 @@
 
     let verboser act = do
             let cwd = listToMaybe $ reverse [x | Cwd x <- opts]
-            putLoud $
+            putVerbose $
                 maybe "" (\x -> "cd " ++ x ++ "; ") cwd ++
                 last (showCommandForUser2 prog args : [x | UserCommand x <- opts])
             verb <- getVerbosity
-            -- run quietly to supress the tracer (don't want to print twice)
-            (if verb >= Loud then quietly else id) act
+            -- run quietly to suppress the tracer (don't want to print twice)
+            (if verb >= Verbose then quietly else id) act
 
     let tracer act = do
             -- note: use the oparams - find a good tracing before munging it for shell stuff
@@ -277,7 +277,7 @@
 
     let async = ResultProcess PID0 `elem` results
     let tracker act
-            | AutoDeps `elem` opts = if async then fail "Can't use AutoDeps and asyncronous execution" else autodeps act
+            | AutoDeps `elem` opts = if async then liftIO $ errorIO "Can't use AutoDeps and asyncronous execution" else autodeps act
             | shakeLint == Just LintFSATrace && not async = fsalint act
             | otherwise = act params
 
@@ -285,7 +285,9 @@
             ResultFSATrace pxs : res <- act params{opts = addFSAOptions "r" opts, results = ResultFSATrace [] : results}
             xs <- liftIO $ filterM doesFileExist [x | FSARead x <- pxs]
             cwd <- liftIO getCurrentDirectory
-            unsafeAllowApply . need =<< fixPaths cwd xs
+            temp <- fixPaths cwd xs
+            liftIO $ print ("AutoDeps", pxs, cwd, xs, temp) -- DEBUGGING
+            unsafeAllowApply $ need temp
             return res
 
         fixPaths cwd xs = liftIO $ do
@@ -314,7 +316,7 @@
 -- IO EXPLICIT OPERATION
 
 -- | Given a very explicit set of CmdOption, translate them to a General.Process structure
-commandExplicitIO :: Params -> IO [Result]
+commandExplicitIO :: Partial => Params -> IO [Result]
 commandExplicitIO params = removeOptionShell params $ \params -> removeOptionFSATrace params $ \Params{..} -> do
     let (grabStdout, grabStderr) = both or $ unzip $ flip map results $ \r -> case r of
             ResultStdout{} -> (True, False)
@@ -340,6 +342,7 @@
     let optEchoStderr = last $ (not grabStderr && null optFileStderr) : [x | EchoStderr x <- opts]
     let optRealCommand = showCommandForUser2 prog args
     let optUserCommand = last $ optRealCommand : [x | UserCommand x <- opts]
+    let optCloseFds = CloseFileHandles `elem` opts
 
     let bufLBS f = do (a,b) <- buf $ LBS LBS.empty; return (a, (\(LBS x) -> f x) <$> b)
         buf Str{} | optBinary = bufLBS (Str . LBS.unpack)
@@ -366,6 +369,7 @@
         ,poStdout = [DestEcho | optEchoStdout] ++ map DestFile optFileStdout ++ [DestString exceptionBuffer | optWithStdout && not optAsync] ++ concat dStdout
         ,poStderr = [DestEcho | optEchoStderr] ++ map DestFile optFileStderr ++ [DestString exceptionBuffer | optWithStderr && not optAsync] ++ concat dStderr
         ,poAsync = optAsync
+        ,poCloseFds = optCloseFds
         }
     (dur,(pid,exit)) <- duration $ process po
     if exit == ExitSuccess || ResultCode ExitSuccess `elem` results then
@@ -378,7 +382,8 @@
             Just v -> do
                 v <- canonicalizePath v `catchIO` const (return v)
                 return $ "Current directory: " ++ v ++ "\n"
-        fail $
+        -- FIXME: switch to errorIO once extra-1.6.18 is available everywhere
+        liftIO $ error $
             "Development.Shake." ++ funcName ++ ", system command failed\n" ++
             "Command line: " ++ optRealCommand ++ "\n" ++
             (if optRealCommand /= optUserCommand then "Original command line: " ++ optUserCommand ++ "\n" else "") ++
@@ -602,14 +607,14 @@
 --   By default the @stderr@ stream will be captured for use in error messages, and also echoed. To only echo
 --   pass @'WithStderr' 'False'@, which causes no streams to be captured by Shake, and certain programs (e.g. @gcc@)
 --   to detect they are running in a terminal.
-command :: CmdResult r => [CmdOption] -> String -> [String] -> Action r
-command opts x xs = b <$> commandExplicitAction (Params "command" opts a x xs)
+command :: (Partial, CmdResult r) => [CmdOption] -> String -> [String] -> Action r
+command opts x xs = withFrozenCallStack $ b <$> commandExplicitAction (Params "command" opts a x xs)
     where (a,b) = cmdResult
 
 -- | A version of 'command' where you do not require any results, used to avoid errors about being unable
 --   to deduce 'CmdResult'.
-command_ :: [CmdOption] -> String -> [String] -> Action ()
-command_ opts x xs = void $ commandExplicitAction (Params "command_" opts [] x xs)
+command_ :: Partial => [CmdOption] -> String -> [String] -> Action ()
+command_ opts x xs = withFrozenCallStack $ void $ commandExplicitAction (Params "command_" opts [] x xs)
 
 
 ---------------------------------------------------------------------
@@ -662,13 +667,13 @@
 -- @
 -- 'cmd' ('Cwd' \"generated\") 'Shell' \"gcc -c myfile.c\" :: IO ()
 -- @
-cmd :: CmdArguments args => args :-> Action r
-cmd = cmdArguments mempty
+cmd :: (Partial, CmdArguments args) => args :-> Action r
+cmd = withFrozenCallStack $ cmdArguments mempty
 
 -- | See 'cmd'. Same as 'cmd' except with a unit result.
 -- 'cmd' is to 'cmd_' as 'command' is to 'command_'.
-cmd_ :: (CmdArguments args, Unit args) => args :-> Action ()
-cmd_ = cmd
+cmd_ :: (Partial, CmdArguments args, Unit args) => args :-> Action ()
+cmd_ = withFrozenCallStack cmd
 
 -- | The arguments to 'cmd' - see 'cmd' for examples and semantics.
 newtype CmdArgument = CmdArgument [Either CmdOption String]
@@ -677,7 +682,7 @@
 -- | The arguments to 'cmd' - see 'cmd' for examples and semantics.
 class CmdArguments t where
     -- | Arguments to cmd
-    cmdArguments :: CmdArgument -> t
+    cmdArguments :: Partial => CmdArgument -> t
 instance (IsCmdArgument a, CmdArguments r) => CmdArguments (a -> r) where
     cmdArguments xs x = cmdArguments $ xs `mappend` toCmdArgument x
 instance CmdResult r => CmdArguments (Action r) where
diff --git a/src/Development/Shake/Forward.hs b/src/Development/Shake/Forward.hs
--- a/src/Development/Shake/Forward.hs
+++ b/src/Development/Shake/Forward.hs
@@ -31,16 +31,25 @@
 -- * Where Haskell performs real computation, if zero-build performance is insufficient, use 'cacheAction'.
 --
 --   All forward-defined systems use 'AutoDeps', which requires @fsatrace@ to be on the @$PATH@.
---   You can obtain @fsatrace@ from <https://github.com/jacereda/fsatrace>.
+--   You can obtain @fsatrace@ from <https://github.com/jacereda/fsatrace>. You must set
+--   'shakeLintInside' to specify where 'AutoDeps' will look for dependencies - if you want all dependencies
+--   everywhere use @[\"\"]@.
 --
---   This module is considered experimental - it has not been battle tested.
---   A possible alternative is available at <http://hackage.haskell.org/package/pier/docs/Pier-Core-Artifact.html>.
+--   This module is considered experimental - it has not been battle tested. There are now a few possible
+--   alternatives in this space:
+--
+-- * Pier <http://hackage.haskell.org/package/pier/docs/Pier-Core-Artifact.html> (built on Shake).
+--
+-- * Rattle <https://github.com/ndmitchell/rattle> (by the same author as Shake).
+--
+-- * Stroll <https://github.com/snowleopard/stroll>.
 module Development.Shake.Forward(
     shakeForward, shakeArgsForward,
     forwardOptions, forwardRule,
-    cache, cacheAction
+    cache, cacheAction, cacheActionWith,
     ) where
 
+import Control.Monad
 import Development.Shake
 import Development.Shake.Rule
 import Development.Shake.Command
@@ -70,7 +79,7 @@
 mkForward :: (Typeable a, Show a, Binary a) => a -> Forward
 mkForward x = Forward (show $ typeOf x, show x, encode' x)
 
-unForward :: forall a . (Typeable a, Show a, Binary a) => Forward -> a
+unForward :: forall a . (Typeable a, Binary a) => Forward -> a
 unForward (Forward (got,_,x))
     | got /= want = error $ "Failed to match forward type, wanted " ++ show want ++ ", got " ++ show got
     | otherwise = decode' x
@@ -98,6 +107,9 @@
 -- | Given an 'Action', turn it into a 'Rules' structure which runs in forward mode.
 forwardRule :: Action () -> Rules ()
 forwardRule act = do
+    opts <- getShakeOptionsRules
+    when (null $ shakeLintInside opts) $
+        fail "When running in forward mode you must set shakeLintInside to specify where to detect dependencies"
     addBuiltinRule noLint noIdentity $ \k old mode ->
         case old of
             Just old | mode == RunDependenciesSame -> return $ RunResult ChangedNothing old (decode' old)
@@ -115,7 +127,10 @@
 forwardOptions opts = opts{shakeCommandOptions=[AutoDeps]}
 
 
--- | Cache an action. The name of the action must be unique for all different actions.
+-- | Cache an action, given a key and an 'Action'. Each call in your program should specify a different
+--   key, but the key should remain consistent between runs. Ideally, the 'Action' will gather all its dependencies
+--   with tracked operations, e.g. 'readFile\''. However, if information is accessed from the environment
+--   (e.g. the action is a closure), you should call 'cacheActionWith' being explicit about what is captured.
 cacheAction :: (Typeable a, Binary a, Show a, Typeable b, Binary b, Show b) => a -> Action b -> Action b
 cacheAction (mkForward -> key) (action :: Action b) = do
     liftIO $ atomicModifyIORef forwards $ \mp -> (Map.insert key (mkForward <$> action) mp, ())
@@ -123,7 +138,27 @@
     liftIO $ atomicModifyIORef forwards $ \mp -> (Map.delete key mp, ())
     return $ unForward res
 
--- | Apply caching to an external command.
+newtype With a = With a
+    deriving (Typeable, Binary, Show)
+
+-- | Like 'cacheAction', but also specify which information is captured by the closure of the 'Action'. If that
+--   information changes, the 'Action' will be rerun.
+cacheActionWith :: (Typeable a, Binary a, Show a, Typeable b, Binary b, Show b, Typeable c, Binary c, Show c) => a -> b ->  Action c -> Action c
+cacheActionWith key argument action = do
+    cacheAction (With argument) $ do
+        alwaysRerun
+        return argument
+    cacheAction key $ do
+        apply1 $ mkForward $ With argument
+        action
+
+-- | Apply caching to an external command using the same arguments as 'cmd'.
+--
+-- > cache $ cmd "gcc -c" ["foo.c"] "-o" ["foo.o"]
+--
+--   This command will be cached, with the inputs/outputs traced. If any of the
+--   files used by this command (e.g. @foo.c@ or header files it imports) then
+--   the command will rerun.
 cache :: (forall r . CmdArguments r => r) -> Action ()
 cache cmd = do
     let CmdArgument args = cmd
diff --git a/src/Development/Shake/Internal/Args.hs b/src/Development/Shake/Internal/Args.hs
--- a/src/Development/Shake/Internal/Args.hs
+++ b/src/Development/Shake/Internal/Args.hs
@@ -160,38 +160,40 @@
     let putWhenLn v msg = putWhen v $ msg ++ "\n"
     let showHelp long = do
             progName <- getProgName
-            targets <- if not long then return [] else
-                handleSynchronous (\e -> do putWhenLn Normal $ "Failure to collect targets: " ++ show e; return []) $ do
+            (targets, helpSuffix) <- if not long then return ([], []) else
+                handleSynchronous (\e -> do putWhenLn Info $ "Failure to collect targets: " ++ show e; return ([], [])) $ do
                     -- run the rules as simply as we can
                     rs <- rules shakeOpts [] []
                     case rs of
                         Just (_, rs) -> do
                             xs <- getTargets shakeOpts rs
-                            evaluate $ force ["  - " ++ a ++ maybe "" (" - " ++) b | (a,b) <- xs]
-                        _ -> return []
+                            helpSuffix <- getHelpSuffix shakeOpts rs
+                            evaluate $ force (["  - " ++ a ++ maybe "" (" - " ++) b | (a,b) <- xs], helpSuffix)
+                        _ -> return ([], [])
             changes <- return $
                 let as = shakeOptionsFields baseOpts
                     bs = shakeOptionsFields oshakeOpts
                 in ["  - " ++ lbl ++ ": " ++ v1 ++ " => " ++ v2 | long, ((lbl, v1), (_, v2)) <- zip as bs, v1 /= v2]
 
-            putWhen Quiet $ unlines $
+            putWhen Error $ unlines $
                 ("Usage: " ++ progName ++ " [options] [target] ...") :
                 (if null baseOpts2 then [] else "" : (if null userOptions then "Options:" else "Standard options:") : showOptDescr baseOpts2) ++
                 (if null userOptions then [] else "" : "Extra options:" : showOptDescr userOptions) ++
                 (if null changes then [] else "" : "Changed ShakeOptions:" : changes) ++
-                (if null targets then [] else "" : "Targets:" : targets)
+                (if null targets then [] else "" : "Targets:" : targets) ++
+                (if null helpSuffix then [] else "" : helpSuffix)
 
     when (errs /= []) $ do
-        putWhen Quiet $ unlines $ map ("shake: " ++) $ filter (not . null) $ lines $ unlines errs
+        putWhen Error $ unlines $ map ("shake: " ++) $ filter (not . null) $ lines $ unlines errs
         showHelp False
         exitFailure
 
     if Help `elem` flagsExtra then
         showHelp True
      else if Version `elem` flagsExtra then
-        putWhenLn Normal $ "Shake build system, version " ++ shakeVersionString
+        putWhenLn Info $ "Shake build system, version " ++ shakeVersionString
      else if NumericVersion `elem` flagsExtra then
-        putWhenLn Normal shakeVersionString
+        putWhenLn Info shakeVersionString
      else if Demo `elem` flagsExtra then
         demo $ shakeStaunch shakeOpts
      else if not $ null progressReplays then do
@@ -199,7 +201,7 @@
             src <- readFile file
             return (file, map read $ lines src)
         forM_ (if null $ shakeReport shakeOpts then ["-"] else shakeReport shakeOpts) $ \file -> do
-            putWhenLn Normal $ "Writing report to " ++ file
+            putWhenLn Info $ "Writing report to " ++ file
             writeProgressReport file dat
      else do
         when (Sleep `elem` flagsExtra) $ sleep 1
@@ -220,7 +222,7 @@
         (ran,shakeOpts,res) <- redir $ do
             when printDirectory $ do
                 curdir <- getCurrentDirectory
-                putWhenLn Normal $ "shake: In directory `" ++ curdir ++ "'"
+                putWhenLn Info $ "shake: In directory `" ++ curdir ++ "'"
             (shakeOpts, ui) <- do
                 let compact = last $ No : [x | Compact x <- flagsExtra]
                 use <- if compact == Auto then checkEscCodes else return $ compact == Yes
@@ -234,18 +236,22 @@
                     res <- try_ $ shake shakeOpts $
                         if NoBuild `elem` flagsExtra then
                             withoutActions rules
-                        else if ShareList `elem` flagsExtra || not (null shareRemoves) then do
+                        else if ShareList `elem` flagsExtra ||
+                                not (null shareRemoves) ||
+                                ShareSanity `elem` flagsExtra then do
                             action $ do
                                 unless (null shareRemoves) $
                                     actionShareRemove shareRemoves
                                 when (ShareList `elem` flagsExtra)
                                     actionShareList
+                                when (ShareSanity `elem` flagsExtra)
+                                    actionShareSanity
                             withoutActions rules
                         else
                             rules
                     return (True, shakeOpts, res)
 
-        if not ran || shakeVerbosity shakeOpts < Normal || NoTime `elem` flagsExtra then
+        if not ran || shakeVerbosity shakeOpts < Info || NoTime `elem` flagsExtra then
             either throwIO return res
          else
             let esc = if shakeColor shakeOpts then escape else flip const
@@ -254,11 +260,11 @@
                     if Exception `elem` flagsExtra then
                         throwIO err
                     else do
-                        putWhenLn Quiet $ esc Red $ show err
+                        putWhenLn Error $ esc Red $ show err
                         exitFailure
                 Right () -> do
                     tot <- start
-                    putWhenLn Normal $ esc Green $ "Build completed in " ++ showDuration tot
+                    putWhenLn Info $ esc Green $ "Build completed in " ++ showDuration tot
 
 
 -- | A list of command line options that can be used to modify 'ShakeOptions'. Each option returns
@@ -281,6 +287,7 @@
            | ProgressReplay FilePath
            | Demo
            | ShareList
+           | ShareSanity
            | ShareRemove String
            | Compact Auto
              deriving Eq
@@ -292,7 +299,12 @@
 escape color x = escForeground color ++ x ++ escNormal
 
 outputColor :: (Verbosity -> String -> IO ()) -> Verbosity -> String -> IO ()
-outputColor output v msg = output v $ escape Blue msg
+outputColor output v msg = output v $ color msg
+  where color = case v of
+            Silent -> id
+            Error  -> escape Red
+            Warn   -> escape Yellow
+            _      -> escape Blue
 
 -- | True if it has a potential effect on ShakeOptions
 shakeOptsEx :: [(Bool, OptDescr (Either String ([Extra], ShakeOptions -> ShakeOptions)))]
@@ -336,6 +348,7 @@
     ,opts $ Option ""  ["no-rule-version"] (noArg $ \s -> s{shakeVersionIgnore=True}) "Ignore the build rules version."
     ,opts $ Option ""  ["share"] (optArg "DIRECTORY" $ \x s -> s{shakeShare=Just $ fromMaybe "" x, shakeChange=ensureHash $ shakeChange s}) "Shared cache location."
     ,hide $ Option ""  ["share-list"] (noArg ([ShareList], ensureShare)) "List the shared cache files."
+    ,hide $ Option ""  ["share-sanity"] (noArg ([ShareSanity], ensureShare)) "Sanity check the shared cache files."
     ,hide $ Option ""  ["share-remove"] (OptArg (\x -> Right ([ShareRemove $ fromMaybe "**" x], ensureShare)) "SUBSTRING") "Remove the shared cache keys."
     ,opts $ Option ""  ["share-copy"] (noArg $ \s -> s{shakeSymlink=False}) "Copy files into the cache."
     ,opts $ Option ""  ["share-symlink"] (noArg $ \s -> s{shakeSymlink=True}) "Symlink files into the cache."
diff --git a/src/Development/Shake/Internal/CmdOption.hs b/src/Development/Shake/Internal/CmdOption.hs
--- a/src/Development/Shake/Internal/CmdOption.hs
+++ b/src/Development/Shake/Internal/CmdOption.hs
@@ -25,7 +25,8 @@
     | EchoStderr Bool -- ^ Should I echo the @stderr@? Defaults to 'True' unless a 'Stderr' result is required or you use 'FileStderr'.
     | FileStdout FilePath -- ^ Should I put the @stdout@ to a file.
     | FileStderr FilePath -- ^ Should I put the @stderr@ to a file.
-    | AutoDeps -- ^ Compute dependencies automatically.
+    | AutoDeps -- ^ Compute dependencies automatically. Only works if 'shakeLintInside' has been set to the files where autodeps might live.
     | UserCommand String -- ^ The command the user thinks about, before any munging. Defaults to the actual command.
     | FSAOptions String -- ^ Options to @fsatrace@, a list of strings with characters such as @\"r\"@ (reads) @\"w\"@ (writes). Defaults to @\"rwmdqt\"@ if the output of @fsatrace@ is required.
+    | CloseFileHandles -- ^ Before starting the command in the child process, close all file handles except stdin, stdout, stderr in the child process. Uses @close_fds@ from package process and comes with the same caveats, i.e. runtime is linear with the maximum number of open file handles (@RLIMIT_NOFILE@, see @man 2 getrlimit@ on Linux).
       deriving (Eq,Ord,Show,Data,Typeable)
diff --git a/src/Development/Shake/Internal/CompactUI.hs b/src/Development/Shake/Internal/CompactUI.hs
--- a/src/Development/Shake/Internal/CompactUI.hs
+++ b/src/Development/Shake/Internal/CompactUI.hs
@@ -71,7 +71,7 @@
         ,shakeOutput = \a b -> tweak (addOutput a b)
         ,shakeProgress = \x -> void $ progressDisplay 1 (tweak . addProgress) x `withThreadsBoth` shakeProgress opts x
         ,shakeCommandOptions = [EchoStdout False, EchoStderr False] ++ shakeCommandOptions opts
-        ,shakeVerbosity = Quiet
+        ,shakeVerbosity = Error
         }
     let tick = do t <- time; mask_ $ putStr =<< atomicModifyIORef ref (display t)
     return (opts, forever (tick >> sleep 0.4) `finally` tick)
diff --git a/src/Development/Shake/Internal/Core/Action.hs b/src/Development/Shake/Internal/Core/Action.hs
--- a/src/Development/Shake/Internal/Core/Action.hs
+++ b/src/Development/Shake/Internal/Core/Action.hs
@@ -1,23 +1,23 @@
-{-# LANGUAGE RecordWildCards, NamedFieldPuns, ScopedTypeVariables, ConstraintKinds, TupleSections, ViewPatterns #-}
+{-# LANGUAGE RecordWildCards, NamedFieldPuns, ScopedTypeVariables, ConstraintKinds, TupleSections #-}
 
 module Development.Shake.Internal.Core.Action(
-    actionOnException, actionFinally, actionCatch, actionRetry,
+    actionOnException, actionFinally, actionBracket, actionCatch, actionRetry,
     getShakeOptions, getProgress, runAfter,
     lintTrackRead, lintTrackWrite, lintTrackAllow,
-    getVerbosity, putWhen, putLoud, putNormal, putQuiet, withVerbosity, quietly,
+    getVerbosity, putWhen, putVerbose, putInfo, putWarn, putError, withVerbosity, quietly,
     orderOnlyAction,
     newCacheIO,
     unsafeExtraThread,
     parallel,
     batch,
-    deprioritize,
+    reschedule,
     historyDisable,
     traced,
     -- Internal only
     producesChecked, producesUnchecked, producesCheck, lintCurrentDirectory, lintWatch,
     blockApply, unsafeAllowApply, shakeException, lintTrackFinished,
     getCurrentKey, getLocal,
-    actionShareList, actionShareRemove
+    actionShareList, actionShareRemove, actionShareSanity
     ) where
 
 import Control.Exception
@@ -60,8 +60,8 @@
 
 -- | Apply a modification, run an action, then run an undo action after.
 --   Doesn't actually require exception handling because we don't have the ability to catch exceptions to the user.
-actionBracket :: (Local -> (Local, Local -> Local)) -> Action a -> Action a
-actionBracket f m = Action $ do
+actionThenUndoLocal :: (Local -> (Local, Local -> Local)) -> Action a -> Action a
+actionThenUndoLocal f m = Action $ do
     s <- getRW
     let (s2,undo) = f s
     putRW s2
@@ -81,28 +81,36 @@
     Just (e :: ShakeException) -> return e
     Nothing -> do
         e <- return $ exceptionStack stk e
-        when (shakeStaunch && shakeVerbosity >= Quiet) $
-            globalOutput Quiet $ show e ++ "Continuing due to staunch mode"
+        when (shakeStaunch && shakeVerbosity >= Error) $
+            globalOutput Error $ show e ++ "Continuing due to staunch mode"
         return e
 
 
-actionBoom :: Bool -> Action a -> IO b -> Action a
-actionBoom runOnSuccess act (void -> clean) = do
+actionBracketEx :: Bool -> IO a -> (a -> IO b) -> (a -> Action c) -> Action c
+actionBracketEx runOnSuccess alloc free act = do
     Global{..} <- Action getRO
-    key <- liftIO $ register globalCleanup clean
-    -- important to mask_ the undo/clean combo so either both happen or neither
-    res <- Action $ catchRAW (fromAction act) $ \e -> liftIO (release key) >> throwRAW e
+    (v, key) <- liftIO $ mask_ $ do
+        v <- alloc
+        key <- liftIO $ register globalCleanup $ void $ free v
+        return (v, key)
+    res <- Action $ catchRAW (fromAction $ act v) $ \e -> liftIO (release key) >> throwRAW e
     liftIO $ if runOnSuccess then release key else unprotect key
     return res
 
 -- | If an exception is raised by the 'Action', perform some 'IO' then reraise the exception.
 actionOnException :: Action a -> IO b -> Action a
-actionOnException = actionBoom False
+actionOnException act free = actionBracketEx False (return ()) (const free) (const act)
 
 -- | After an 'Action', perform some 'IO', even if there is an exception.
 actionFinally :: Action a -> IO b -> Action a
-actionFinally = actionBoom True
+actionFinally act free = actionBracket (return ()) (const free) (const act)
 
+-- | Like `bracket`, but where the inner operation is of type 'Action'. Usually used as
+--   @'actionBracket' alloc free use@.
+actionBracket :: IO a -> (a -> IO b) -> (a -> Action c) -> Action c
+actionBracket = actionBracketEx True
+
+
 -- | If a syncronous exception is raised by the 'Action', perform some handler.
 --   Note that there is no guarantee that the handler will run on shutdown (use 'actionFinally' for that),
 --   and that 'actionCatch' /cannot/ catch exceptions thrown by dependencies, e.g. raised by 'need'
@@ -158,25 +166,30 @@
         liftIO $ globalOutput v msg
 
 
--- | Write an unimportant message to the output, only shown when 'shakeVerbosity' is higher than normal ('Loud' or above).
+-- | Write an unimportant message to the output, only shown when 'shakeVerbosity' is higher than normal ('Verbose' or above).
 --   The output will not be interleaved with any other Shake messages (other than those generated by system commands).
-putLoud :: String -> Action ()
-putLoud = putWhen Loud
+putVerbose :: String -> Action ()
+putVerbose = putWhen Verbose
 
--- | Write a normal priority message to the output, only supressed when 'shakeVerbosity' is 'Quiet' or 'Silent'.
+-- | Write a normal priority message to the output, only suppressed when 'shakeVerbosity' is 'Error', 'Warn' or 'Silent'.
 --   The output will not be interleaved with any other Shake messages (other than those generated by system commands).
-putNormal :: String -> Action ()
-putNormal = putWhen Normal
+putInfo :: String -> Action ()
+putInfo = putWhen Info
 
--- | Write an important message to the output, only supressed when 'shakeVerbosity' is 'Silent'.
+-- | Write a semi important message to the output, only suppressed when 'shakeVerbosity' is 'Error' or 'Silent'.
 --   The output will not be interleaved with any other Shake messages (other than those generated by system commands).
-putQuiet :: String -> Action ()
-putQuiet = putWhen Quiet
+putWarn :: String -> Action ()
+putWarn = putWhen Warn
 
+-- | Write an important message to the output, only suppressed when 'shakeVerbosity' is 'Silent'.
+--   The output will not be interleaved with any other Shake messages (other than those generated by system commands).
+putError :: String -> Action ()
+putError = putWhen Error
 
+
 -- | Get the current verbosity level, originally set by 'shakeVerbosity'. If you
 --   want to output information to the console, you are recommended to use
---   'putLoud' \/ 'putNormal' \/ 'putQuiet', which ensures multiple messages are
+--   'putVerbose' \/ 'putInfo' \/ 'putError', which ensures multiple messages are
 --   not interleaved. The verbosity can be modified locally by 'withVerbosity'.
 getVerbosity :: Action Verbosity
 getVerbosity = Action $ localVerbosity <$> getRW
@@ -186,16 +199,16 @@
 --   Will not update the 'shakeVerbosity' returned by 'getShakeOptions' and will
 --   not have any impact on 'Diagnostic' tracing.
 withVerbosity :: Verbosity -> Action a -> Action a
-withVerbosity new = actionBracket $ \s0 ->
+withVerbosity new = actionThenUndoLocal $ \s0 ->
     (s0{localVerbosity=new}, \s -> s{localVerbosity=localVerbosity s0})
 
 
--- | Run an action with 'Quiet' verbosity, in particular messages produced by 'traced'
+-- | Run an action with 'Error' verbosity, in particular messages produced by 'traced'
 --   (including from 'Development.Shake.cmd' or 'Development.Shake.command') will not be printed to the screen.
 --   Will not update the 'shakeVerbosity' returned by 'getShakeOptions' and will
 --   not turn off any 'Diagnostic' tracing.
 quietly :: Action a -> Action a
-quietly = withVerbosity Quiet
+quietly = withVerbosity Error
 
 
 ---------------------------------------------------------------------
@@ -208,7 +221,7 @@
 blockApply = applyBlockedBy . Just
 
 applyBlockedBy :: Maybe String -> Action a -> Action a
-applyBlockedBy reason = actionBracket $ \s0 ->
+applyBlockedBy reason = actionThenUndoLocal $ \s0 ->
     (s0{localBlockApply=reason}, \s -> s{localBlockApply=localBlockApply s0})
 
 
@@ -225,7 +238,7 @@
 -- > # traced message (for myobject.o)
 --
 --   To suppress the output of 'traced' (for example you want more control
---   over the message using 'putNormal'), use the 'quietly' combinator.
+--   over the message using 'putInfo'), use the 'quietly' combinator.
 --
 --   It is recommended that the string passed to 'traced' is short and that only a small number of unique strings
 --   are used (makes profiling work better).
@@ -236,7 +249,7 @@
     Local{localStack} <- Action getRW
     start <- liftIO globalTimestamp
     let key = showTopStack localStack
-    putNormal $ "# " ++ msg ++ " (for " ++ key ++ ")"
+    putInfo $ "# " ++ msg ++ " (for " ++ key ++ ")"
     res <- liftIO $
         (shakeTrace globalOptions key msg True >> act)
             `finally` shakeTrace globalOptions key msg False
@@ -250,7 +263,7 @@
 ---------------------------------------------------------------------
 -- TRACKING
 
--- | Track that a key has been used/read by the action preceeding it when 'shakeLint' is active.
+-- | Track that a key has been used/read by the action preceding it when 'shakeLint' is active.
 lintTrackRead :: ShakeValue key => [key] -> Action ()
 -- One of the following must be true:
 -- 1) you are the one building this key (e.g. key == topStack)
@@ -561,12 +574,12 @@
                 liftIO $ mapM_ (flip signalFence res . thd3) now
 
 
--- | Given a running task, deprioritize so it only continues after all other pending tasks,
---   and all deprioritized tasks with a higher priority. Note that due to parallelism there is no guarantee
---   that all actions of a higher priority will have /completed/ before the action resumes.
+-- | Given a running task, reschedule so it only continues after all other pending tasks,
+--   and all rescheduled tasks with a higher pool priority. Note that due to parallelism there is no guarantee
+--   that all actions of a higher pool priority will have /completed/ before the action resumes.
 --   Only useful if the results are being interactively reported or consumed.
-deprioritize :: Double -> Action ()
-deprioritize x = do
+reschedule :: Double -> Action ()
+reschedule x = do
     (wait, _) <- actionAlwaysRequeuePriority (PoolDeprioritize $ negate x) $ return ()
     Action $ modifyRW $ addDiscount wait
 
@@ -592,3 +605,11 @@
     case globalShared of
         Nothing -> throwM $ errorInternal "actionShareList with no shared"
         Just x -> liftIO $ listShared x
+
+-- | Hooked up to --share-sanity
+actionShareSanity :: Action ()
+actionShareSanity = do
+    Global{..} <- Action getRO
+    case globalShared of
+        Nothing -> throwM $ errorInternal "actionShareSanity with no shared"
+        Just x -> liftIO $ sanityShared x
diff --git a/src/Development/Shake/Internal/Core/Build.hs b/src/Development/Shake/Internal/Core/Build.hs
--- a/src/Development/Shake/Internal/Core/Build.hs
+++ b/src/Development/Shake/Internal/Core/Build.hs
@@ -78,13 +78,13 @@
         Nothing -> Now $ Left $ errorStructured "Shake Id no longer exists" [("Id", Just $ show i)] ""
         Just (k, s) -> case s of
             Ready r -> Now $ Right r
-            Error e _ -> Now $ Left e
+            Failed e _ -> Now $ Left e
             Running{} | Left e <- addStack i k stack -> Now $ Left e
             _ -> Later $ \continue -> do
                 Just (_, s) <- liftIO $ getKeyValueFromId database i
                 case s of
                     Ready r -> continue $ Right r
-                    Error e _ -> continue $ Left e
+                    Failed e _ -> continue $ Left e
                     Running (NoShow w) r -> do
                         let w2 v = w v >> continue v
                         setMem database i k $ Running (NoShow w2) r
@@ -117,7 +117,7 @@
                     Right RunResult{..} | runChanged /= ChangedNothing -> setDisk database i k $ Loaded runValue{result=runStore}
                     _ -> return ()
     where
-        mkError e = if globalOneShot then Error e Nothing else Error e r
+        mkError e = Failed e $ if globalOneShot then Nothing else r
 
 
 -- | Compute the value for a given RunMode and a restore function to run
diff --git a/src/Development/Shake/Internal/Core/Database.hs b/src/Development/Shake/Internal/Core/Database.hs
--- a/src/Development/Shake/Internal/Core/Database.hs
+++ b/src/Development/Shake/Internal/Core/Database.hs
@@ -9,6 +9,7 @@
     setMem, setDisk, modifyAllMem
     ) where
 
+import Data.Tuple.Extra
 import Data.IORef.Extra
 import General.Intern(Id, Intern)
 import Development.Shake.Classes
@@ -18,7 +19,7 @@
 import Control.Monad.IO.Class
 import qualified General.Ids as Ids
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 import Control.Monad.Fail
 #endif
 
@@ -107,7 +108,7 @@
 setMem Database{..} i k v = liftIO $ Ids.insert status i (k,v)
 
 modifyAllMem :: DatabasePoly k v -> (v -> v) -> Locked ()
-modifyAllMem Database{..} f = liftIO $ Ids.forMutate status $ \(k, s) -> (k, f s)
+modifyAllMem Database{..} f = liftIO $ Ids.forMutate status $ second f
 
 setDisk :: DatabasePoly k v -> Id -> k -> v -> IO ()
 setDisk = journal
diff --git a/src/Development/Shake/Internal/Core/Monad.hs b/src/Development/Shake/Internal/Core/Monad.hs
--- a/src/Development/Shake/Internal/Core/Monad.hs
+++ b/src/Development/Shake/Internal/Core/Monad.hs
@@ -18,7 +18,7 @@
 import Data.Semigroup
 import Prelude
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 import Control.Monad.Fail
 #endif
 
@@ -54,9 +54,13 @@
 instance MonadIO (RAW k v ro rw) where
     liftIO = LiftIO
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 instance MonadFail (RAW k v ro rw) where
     fail = liftIO . Control.Monad.Fail.fail
+#endif
+#if __GLASGOW_HASKELL__ >= 808
+instance MonadFail (RAW k v ro rw) where
+    fail = liftIO . Prelude.fail
 #endif
 
 instance Semigroup a => Semigroup (RAW k v ro rw a) where
diff --git a/src/Development/Shake/Internal/Core/Rules.hs b/src/Development/Shake/Internal/Core/Rules.hs
--- a/src/Development/Shake/Internal/Core/Rules.hs
+++ b/src/Development/Shake/Internal/Core/Rules.hs
@@ -1,17 +1,19 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE RecordWildCards, ScopedTypeVariables #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving, ConstraintKinds #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving, ConstraintKinds, NamedFieldPuns #-}
 {-# LANGUAGE ExistentialQuantification, RankNTypes #-}
 {-# LANGUAGE TypeFamilies, DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleInstances #-}
 
 module Development.Shake.Internal.Core.Rules(
-    Rules, runRules,
+    Rules, SRules(..), runRules,
     RuleResult, addBuiltinRule, addBuiltinRuleEx,
     noLint, noIdentity,
     getShakeOptionsRules,
     getUserRuleInternal, getUserRuleOne, getUserRuleList, getUserRuleMaybe,
     addUserRule, alternatives, priority, versioned,
     getTargets, addTarget, withTargetDocs, withoutTargets,
+    addHelpSuffix, getHelpSuffix,
     action, withoutActions
     ) where
 
@@ -40,7 +42,7 @@
 import General.ListBuilder
 import Prelude
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 import Control.Monad.Fail
 #endif
 
@@ -113,17 +115,17 @@
 -- | Define a set of rules. Rules can be created with calls to functions such as 'Development.Shake.%>' or 'action'.
 --   Rules are combined with either the 'Monoid' instance, or (more commonly) the 'Monad' instance and @do@ notation.
 --   To define your own custom types of rule, see "Development.Shake.Rule".
-newtype Rules a = Rules (ReaderT (ShakeOptions, IORef SRules) IO a) -- All IO must be associative/commutative (e.g. creating IORef/MVars)
+newtype Rules a = Rules (ReaderT (ShakeOptions, IORef (SRules ListBuilder)) IO a) -- All IO must be associative/commutative (e.g. creating IORef/MVars)
     deriving (Functor, Applicative, Monad, MonadIO, MonadFix
 #if __GLASGOW_HASKELL__ >= 800
              ,MonadFail
 #endif
         )
 
-newRules :: SRules -> Rules ()
+newRules :: SRules ListBuilder -> Rules ()
 newRules x = Rules $ liftIO . flip modifyIORef' (<> x) =<< asks snd
 
-modifyRulesScoped :: (SRules -> SRules) -> Rules a -> Rules a
+modifyRulesScoped :: (SRules ListBuilder -> SRules ListBuilder) -> Rules a -> Rules a
 modifyRulesScoped f (Rules r) = Rules $ do
     (opts, refOld) <- ask
     liftIO $ do
@@ -133,12 +135,12 @@
         modifyIORef' refOld (<> f rules)
         return res
 
-runRules :: ShakeOptions -> Rules () -> IO ([(Stack, Action ())], Map.HashMap TypeRep BuiltinRule, TMap.Map UserRuleVersioned, [Target])
+runRules :: ShakeOptions -> Rules () -> IO (SRules [])
 runRules opts (Rules r) = do
     ref <- newIORef mempty
     runReaderT r (opts, ref)
     SRules{..} <- readIORef ref
-    return (runListBuilder actions, builtinRules, userRules, runListBuilder targets)
+    return $ SRules (runListBuilder actions) builtinRules userRules (runListBuilder targets) (runListBuilder helpSuffix)
 
 -- | Get all targets registered in the given rules. The names in
 --   'Development.Shake.phony' and 'Development.Shake.~>' as well as the file patterns
@@ -147,27 +149,33 @@
 --   Returns the command, paired with the documentation (if any).
 getTargets :: ShakeOptions -> Rules () -> IO [(String, Maybe String)]
 getTargets opts rs = do
-    (_actions, _ruleinfo, _userRules, targets) <- runRules opts rs
+    SRules{targets} <- runRules opts rs
     return [(target, documentation) | Target{..} <- targets]
 
+getHelpSuffix :: ShakeOptions -> Rules () -> IO [String]
+getHelpSuffix opts rs = do
+    SRules{helpSuffix} <- runRules opts rs
+    return helpSuffix
+
 data Target = Target
     {target :: !String
     ,documentation :: !(Maybe String)
     } deriving (Eq,Ord,Show,Read,Data,Typeable)
 
-data SRules = SRules
-    {actions :: !(ListBuilder (Stack, Action ()))
+data SRules list = SRules
+    {actions :: !(list (Stack, Action ()))
     ,builtinRules :: !(Map.HashMap TypeRep{-k-} BuiltinRule)
     ,userRules :: !(TMap.Map UserRuleVersioned)
-    ,targets :: !(ListBuilder Target)
+    ,targets :: !(list Target)
+    ,helpSuffix :: !(list String)
     }
 
-instance Semigroup SRules where
-    (SRules x1 x2 x3 x4) <> (SRules y1 y2 y3 y4) = SRules (mappend x1 y1) (Map.unionWithKey f x2 y2) (TMap.unionWith (<>) x3 y3) (mappend x4 y4)
+instance Semigroup (SRules ListBuilder) where
+    (SRules x1 x2 x3 x4 x5) <> (SRules y1 y2 y3 y4 y5) = SRules (mappend x1 y1) (Map.unionWithKey f x2 y2) (TMap.unionWith (<>) x3 y3) (mappend x4 y4) (mappend x5 y5)
         where f k a b = throwImpure $ errorRuleDefinedMultipleTimes k [builtinLocation a, builtinLocation b]
 
-instance Monoid SRules where
-    mempty = SRules mempty Map.empty TMap.empty mempty
+instance Monoid (SRules ListBuilder) where
+    mempty = SRules mempty Map.empty TMap.empty mempty mempty
     mappend = (<>)
 
 instance Semigroup a => Semigroup (Rules a) where
@@ -190,7 +198,7 @@
 addTarget :: String -> Rules ()
 addTarget t = newRules mempty{targets = newListBuilder $ Target t Nothing}
 
--- | For all 'addTarget' targets within the 'Rules' prodivde the specified documentation, if they
+-- | For all 'addTarget' targets within the 'Rules' provide the specified documentation, if they
 --   don't already have documentation.
 withTargetDocs :: String -> Rules () -> Rules ()
 withTargetDocs d = modifyRulesScoped $ \x -> x{targets = f <$> targets x}
@@ -201,6 +209,9 @@
 withoutTargets :: Rules a -> Rules a
 withoutTargets = modifyRulesScoped $ \x -> x{targets=mempty}
 
+-- | Adds some extra information at the end of @--help@.
+addHelpSuffix :: String -> Rules ()
+addHelpSuffix s = newRules mempty{helpSuffix = newListBuilder s}
 
 -- | A suitable 'BuiltinLint' that always succeeds.
 noLint :: BuiltinLint key value
@@ -209,16 +220,16 @@
 -- | A suitable 'BuiltinIdentity' that always fails with a runtime error, incompatible with 'shakeShare'.
 --   Use this function if you don't care about 'shakeShare', or if your rule provides a dependency that can
 --   never be cached (in which case you should also call 'Development.Shake.historyDisable').
-noIdentity :: Typeable key => BuiltinIdentity key value
+noIdentity :: BuiltinIdentity key value
 noIdentity _ _ = Nothing
 
 
 -- | The type mapping between the @key@ or a rule and the resulting @value@.
---   See 'addBuiltinRule' and 'apply'.
+--   See 'addBuiltinRule' and 'Development.Shake.Rule.apply'.
 type family RuleResult key -- = value
 
 -- | Define a builtin rule, passing the functions to run in the right circumstances.
---   The @key@ and @value@ types will be what is used by 'Development.Shake.apply'.
+--   The @key@ and @value@ types will be what is used by 'Development.Shake.Rule.apply'.
 --   As a start, you can use 'noLint' and 'noIdentity' as the first two functions,
 --   but are required to supply a suitable 'BuiltinRun'.
 --
@@ -249,7 +260,7 @@
     newRules mempty{builtinRules = Map.singleton (typeRep k) $ BuiltinRule lint_ check_ run_ binary_ (Ver 0) callStackTop}
 
 
--- | Change the priority of a given set of rules, where higher priorities take precedence.
+-- | Change the priority of a given set of rules, where higher values take precedence.
 --   All matching rules at a given priority must be disjoint, or an error is raised.
 --   All builtin Shake rules have priority between 0 and 1.
 --   Excessive use of 'priority' is discouraged. As an example:
diff --git a/src/Development/Shake/Internal/Core/Run.hs b/src/Development/Shake/Internal/Core/Run.hs
--- a/src/Development/Shake/Internal/Core/Run.hs
+++ b/src/Development/Shake/Internal/Core/Run.hs
@@ -1,6 +1,6 @@
 {-# LANGUAGE RecordWildCards, ScopedTypeVariables, PatternGuards #-}
 {-# LANGUAGE ConstraintKinds, TupleSections, ViewPatterns #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeFamilies, NamedFieldPuns #-}
 
 module Development.Shake.Internal.Core.Run(
     RunState,
@@ -60,7 +60,7 @@
 
 data RunState = RunState
     {opts :: ShakeOptions
-    ,ruleinfo :: Map.HashMap TypeRep BuiltinRule
+    ,builtinRules :: Map.HashMap TypeRep BuiltinRule
     ,userRules :: TMap.Map UserRuleVersioned
     ,database :: Database
     ,curdir :: FilePath
@@ -73,18 +73,18 @@
 open :: Cleanup -> ShakeOptions -> Rules () -> IO RunState
 open cleanup opts rs = withInit opts $ \opts@ShakeOptions{..} diagnostic _ -> do
     diagnostic $ return "Starting run"
-    (actions, ruleinfo, userRules, _targets) <- runRules opts rs
+    SRules{actions, builtinRules, userRules} <- runRules opts rs
 
     diagnostic $ return $ "Number of actions = " ++ show (length actions)
-    diagnostic $ return $ "Number of builtin rules = " ++ show (Map.size ruleinfo) ++ " " ++ show (Map.keys ruleinfo)
+    diagnostic $ return $ "Number of builtin rules = " ++ show (Map.size builtinRules) ++ " " ++ show (Map.keys builtinRules)
     diagnostic $ return $ "Number of user rule types = " ++ show (TMap.size userRules)
     diagnostic $ return $ "Number of user rules = " ++ show (sum (TMap.toList (userRuleSize . userRuleContents) userRules))
 
     checkShakeExtra shakeExtra
     curdir <- getCurrentDirectory
 
-    database <- usingDatabase cleanup opts diagnostic ruleinfo
-    (shared, cloud) <- loadSharedCloud database opts ruleinfo
+    database <- usingDatabase cleanup opts diagnostic builtinRules
+    (shared, cloud) <- loadSharedCloud database opts builtinRules
     return RunState{..}
 
 
@@ -94,7 +94,7 @@
     modifyAllMem database f
     where
         f (Ready r) = Loaded (snd <$> r)
-        f (Error _ x) = maybe Missing Loaded x
+        f (Failed _ x) = maybe Missing Loaded x
         f (Running _ x) = maybe Missing Loaded x -- shouldn't ever happen, but Loaded is least worst
         f x = x
 
@@ -110,7 +110,7 @@
 
         res <- withCleanup $ \cleanup -> do
             register cleanup $ do
-                when (shakeTimings && shakeVerbosity >= Normal) $
+                when (shakeTimings && shakeVerbosity >= Info) $
                     writeIORef timingsToShow . Just =<< getTimings
                 resetTimings
 
@@ -141,7 +141,7 @@
             addTiming "Running rules"
             locals <- newIORef []
             runPool (shakeThreads == 1) shakeThreads $ \pool -> do
-                let global = Global applyKeyValue database pool cleanup start ruleinfo output opts diagnostic ruleFinished after absent getProgress userRules shared cloud step oneshot
+                let global = Global applyKeyValue database pool cleanup start builtinRules output opts diagnostic ruleFinished after absent getProgress userRules shared cloud step oneshot
                 -- give each action a stack to start with!
                 forM_ (actions ++ map (emptyStack,) actions2) $ \(stack, act) -> do
                     let local = newLocal stack shakeVerbosity
@@ -156,26 +156,26 @@
             locals <- readIORef locals
             end <- start
             if null actions && null actions2 then
-                putWhen Normal "Warning: No want/action statements, nothing to do"
+                putWhen Info "Warning: No want/action statements, nothing to do"
              else
                 recordRoot step locals end database
 
             when (isJust shakeLint) $ do
                 addTiming "Lint checking"
                 lintCurrentDirectory curdir "After completion"
-                checkValid diagnostic database (runLint ruleinfo) =<< readIORef absent
-                putWhen Loud "Lint checking succeeded"
+                checkValid diagnostic database (runLint builtinRules) =<< readIORef absent
+                putWhen Verbose "Lint checking succeeded"
             when (shakeReport /= []) $ do
                 addTiming "Profile report"
                 forM_ shakeReport $ \file -> do
-                    putWhen Normal $ "Writing report to " ++ file
+                    putWhen Info $ "Writing report to " ++ file
                     writeProfile file database
             when (shakeLiveFiles /= []) $ do
                 addTiming "Listing live"
                 diagnostic $ return "Listing live keys"
                 xs <- liveFiles database
                 forM_ shakeLiveFiles $ \file -> do
-                    putWhen Normal $ "Writing live list to " ++ file
+                    putWhen Info $ "Writing live list to " ++ file
                     (if file == "-" then putStr else writeFile file) $ unlines xs
 
             res <- readIORef after
@@ -196,7 +196,7 @@
     let n = show $ length after
     diagnostic $ return $ "Running " ++ n ++ " after actions"
     (time, _) <- duration $ sequence_ $ reverse after
-    when (shakeTimings && shakeVerbosity >= Normal) $
+    when (shakeTimings && shakeVerbosity >= Info) $
         putStrLn $ "(+ running " ++ show n ++ " after actions in " ++ showDuration time ++ ")"
 
 
@@ -274,7 +274,7 @@
 errorsState :: RunState -> IO [(String, SomeException)]
 errorsState RunState{..} = do
     status <- getKeyValues database
-    return [(show k, e) | (k, Error e _) <- status]
+    return [(show k, e) | (k, Failed e _) <- status]
 
 
 checkValid :: (IO String -> IO ()) -> Database -> (Key -> Value -> IO (Maybe String)) -> [(Key, Key)] -> IO ()
@@ -348,7 +348,7 @@
 recordRoot :: Step -> [Local] -> Seconds -> Database -> IO ()
 recordRoot step locals (doubleToFloat -> end) db = runLocked db $ do
     rootId <- mkId db rootKey
-    let local = localMergeMutable (newLocal emptyStack Normal) locals
+    let local = localMergeMutable (newLocal emptyStack Info) locals
     let rootRes = Result
             {result = (newValue (), BS.empty)
             ,changed = step
diff --git a/src/Development/Shake/Internal/Core/Storage.hs b/src/Development/Shake/Internal/Core/Storage.hs
--- a/src/Development/Shake/Internal/Core/Storage.hs
+++ b/src/Development/Shake/Internal/Core/Storage.hs
@@ -196,7 +196,7 @@
             t <- getCurrentTime
             appendFile (shakeFiles </> ".shake.storage.log") $ "\n[" ++ show t ++ "]: " ++ trimEnd x ++ "\n"
         outputErr x = do
-            when (shakeVerbosity >= Quiet) $ shakeOutput Quiet $ unlines x
+            when (shakeVerbosity >= Warn) $ shakeOutput Warn $ unlines x
             unexpected $ unlines x
 
 
diff --git a/src/Development/Shake/Internal/Core/Types.hs b/src/Development/Shake/Internal/Core/Types.hs
--- a/src/Development/Shake/Internal/Core/Types.hs
+++ b/src/Development/Shake/Internal/Core/Types.hs
@@ -49,7 +49,7 @@
 import General.Cleanup
 import Prelude
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 import Control.Monad.Fail
 #endif
 
@@ -216,7 +216,7 @@
 
 data Status
     = Ready (Result (Value, OneShot BS_Store)) -- ^ I have a value
-    | Error SomeException (OneShot (Maybe (Result BS_Store))) -- ^ I have been run and raised an error
+    | Failed SomeException (OneShot (Maybe (Result BS_Store))) -- ^ I have been run and raised an error
     | Loaded (Result BS_Store) -- ^ Loaded from the database
     | Running (NoShow (Either SomeException (Result (Value, BS_Store)) -> Locked ())) (Maybe (Result BS_Store)) -- ^ Currently in the process of being checked or built
     | Missing -- ^ I am only here because I got into the Intern table
@@ -225,7 +225,7 @@
 instance NFData Status where
     rnf x = case x of
         Ready x -> rnf x
-        Error x y -> rnfException x `seq` rnf y
+        Failed x y -> rnfException x `seq` rnf y
         Loaded x -> rnf x
         Running _ x -> rnf x -- Can't RNF a waiting, but also unnecessary
         Missing -> ()
@@ -248,7 +248,7 @@
     rnf (Result a _ _ b _ c) = rnf a `seq` rnf b `seq` rnf c
 
 statusType Ready{} = "Ready"
-statusType Error{} = "Error"
+statusType Failed{} = "Failed"
 statusType Loaded{} = "Loaded"
 statusType Running{} = "Running"
 statusType Missing{} = "Missing"
@@ -411,7 +411,7 @@
     ,globalShared :: Maybe Shared -- ^ The active shared state, if any
     ,globalCloud :: Maybe Cloud
     ,globalStep :: {-# UNPACK #-} !Step
-    ,globalOneShot :: Bool -- ^ I am running in one-shot mode so don't need to store BS's for Result/Error
+    ,globalOneShot :: Bool -- ^ I am running in one-shot mode so don't need to store BS's for Result/Failed
     }
 
 -- local variables of Action
diff --git a/src/Development/Shake/Internal/Derived.hs b/src/Development/Shake/Internal/Derived.hs
--- a/src/Development/Shake/Internal/Derived.hs
+++ b/src/Development/Shake/Internal/Derived.hs
@@ -90,7 +90,7 @@
 copyFile' :: Partial => FilePath -> FilePath -> Action ()
 copyFile' old new = do
     need [old]
-    putLoud $ "Copying from " ++ old ++ " to " ++ new
+    putVerbose $ "Copying from " ++ old ++ " to " ++ new
     liftIO $ do
         createDirectoryRecursive $ takeDirectory new
         removeFile_ new -- symlink safety
@@ -105,7 +105,7 @@
     -- in newer versions of the directory package we can use copyFileWithMetadata which (we think) updates
     -- the timestamp as well and thus no need to read the source file twice.
     unlessM (liftIO $ doesFileExist new &&^ IO.fileEq old new) $ do
-        putLoud $ "Copying from " ++ old ++ " to " ++ new
+        putVerbose $ "Copying from " ++ old ++ " to " ++ new
         liftIO $ do
             createDirectoryRecursive $ takeDirectory new
             -- copyFile does a lot of clever stuff with permissions etc, so make sure we just reuse it
@@ -172,7 +172,7 @@
 --
 -- @
 -- 'withTempDir' $ \\mydir -> do
---    'putNormal' $ \"Temp directory is \" ++ mydir
+--    'putInfo' $ \"Temp directory is \" ++ mydir
 --    'writeFile'' (mydir \</\> \"test.txt\") \"writing out a temp file\"
 -- @
 withTempDir :: (FilePath -> Action a) -> Action a
diff --git a/src/Development/Shake/Internal/Errors.hs b/src/Development/Shake/Internal/Errors.hs
--- a/src/Development/Shake/Internal/Errors.hs
+++ b/src/Development/Shake/Internal/Errors.hs
@@ -128,7 +128,7 @@
 -- | Error representing all expected exceptions thrown by Shake.
 --   Problems when executing rules will be raising using this exception type.
 data ShakeException = ShakeException
-    {shakeExceptionTarget :: String -- ^ The target that was being built when the exception occured.
+    {shakeExceptionTarget :: String -- ^ The target that was being built when the exception occurred.
     ,shakeExceptionStack :: [String]  -- ^ A description of the call stack, one entry per line.
     ,shakeExceptionInner :: SomeException -- ^ The underlying exception that was raised.
     }
diff --git a/src/Development/Shake/Internal/History/Cloud.hs b/src/Development/Shake/Internal/History/Cloud.hs
--- a/src/Development/Shake/Internal/History/Cloud.hs
+++ b/src/Development/Shake/Internal/History/Cloud.hs
@@ -38,7 +38,7 @@
         _ -> def
     return fence
 
-laterFence :: (Applicative m, MonadIO m) => Fence m a -> Wait m a
+laterFence :: MonadIO m => Fence m a -> Wait m a
 laterFence fence = do
     res <- liftIO $ testFence fence
     case res of
diff --git a/src/Development/Shake/Internal/History/Shared.hs b/src/Development/Shake/Internal/History/Shared.hs
--- a/src/Development/Shake/Internal/History/Shared.hs
+++ b/src/Development/Shake/Internal/History/Shared.hs
@@ -3,7 +3,8 @@
 module Development.Shake.Internal.History.Shared(
     Shared, newShared,
     addShared, lookupShared,
-    removeShared, listShared
+    removeShared, listShared,
+    sanityShared
     ) where
 
 import Control.Exception
@@ -105,7 +106,7 @@
             return $ if valid then Just e else Nothing
 
 
--- | Given a way to get the identity, see if you can a stored cloud version
+-- | Given a way to get the identity, see if you can find a stored cloud version
 lookupShared :: Shared -> (Key -> Wait Locked (Maybe BS_Identity)) -> Key -> Ver -> Ver -> Wait Locked (Maybe (BS_Store, [[Key]], IO ()))
 lookupShared shared ask key builtinVersion userVersion = do
     ents <- liftIO $ loadSharedEntry shared key builtinVersion userVersion
@@ -130,12 +131,13 @@
 saveSharedEntry shared entry = do
     let dir = sharedFileDir shared (entryKey entry)
     createDirectoryRecursive dir
-    let v = runBuilder $ putEntry (keyOp shared) entry
-    createDirectoryRecursive $ dir </> "_key"
-    BS.writeFile (dir </> "_key" </> hexed v) v
     forM_ (entryFiles entry) $ \(file, hash) ->
         unlessM (doesFileExist_ $ dir </> show hash) $
             copyFileLink (useSymlink shared) file (dir </> show hash)
+    -- Write key after files to make sure cache is always useable
+    let v = runBuilder $ putEntry (keyOp shared) entry
+    createDirectoryRecursive $ dir </> "_key"
+    BS.writeFile (dir </> "_key" </> hexed v) v
 
 
 addShared :: Shared -> Key -> Ver -> Ver -> [[(Key, BS_Identity)]] -> BS_Store -> [FilePath] -> IO ()
@@ -167,3 +169,27 @@
                 putStrLn $ "  Key: " ++ show entryKey
                 forM_ entryFiles $ \(file,_) ->
                     putStrLn $ "    File: " ++ file
+
+sanityShared :: Shared -> IO ()
+sanityShared Shared{..} = do
+    dirs <- listDirectories $ sharedRoot </> ".shake.cache"
+    forM_ dirs $ \dir -> do
+        putStrLn $ "Directory: " ++ dir
+        keys <- sharedFileKeys dir
+        forM_ keys $ \key ->
+            handleSynchronous (\e -> putStrLn $ "Warning: " ++ show e) $ do
+                Entry{..} <- getEntry keyOp <$> BS.readFile key
+                putStrLn $ "  Key: " ++ show entryKey
+                putStrLn $ "  Key file: " ++ key
+                forM_ entryFiles $ \(file,hash) ->
+                    checkFile file dir hash
+    where
+      checkFile filename dir keyHash = do
+          let cachefile = dir </> show keyHash
+          putStrLn $ "    File: " ++ filename
+          putStrLn $ "    Cache file: " ++ cachefile
+          ifM (not <$> doesFileExist_ cachefile)
+              (putStrLn "      Error: cache file does not exist") $
+              ifM ((/= keyHash) <$> getFileHash (fileNameFromString cachefile))
+                  (putStrLn "      Error: cache file hash does not match stored hash")
+                  (putStrLn "      OK")
diff --git a/src/Development/Shake/Internal/History/Symlink.hs b/src/Development/Shake/Internal/History/Symlink.hs
--- a/src/Development/Shake/Internal/History/Symlink.hs
+++ b/src/Development/Shake/Internal/History/Symlink.hs
@@ -49,7 +49,7 @@
         b <- createLinkMaybe from to
         whenJust b $ \_ ->
             copyFile from to
-        -- making files read only stops them from inadvertantly mutating the cache
+        -- making files read only stops them from inadvertently mutating the cache
         forM_ [from, to] $ \x -> do
             perm <- getPermissions x
             setPermissions x perm{writable=False}
diff --git a/src/Development/Shake/Internal/Options.hs b/src/Development/Shake/Internal/Options.hs
--- a/src/Development/Shake/Internal/Options.hs
+++ b/src/Development/Shake/Internal/Options.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveDataTypeable, PatternGuards #-}
+{-# LANGUAGE CPP, DeriveDataTypeable, PatternGuards #-}
 
 -- | Types exposed to the user
 module Development.Shake.Internal.Options(
@@ -150,7 +150,7 @@
         --   significant changes to the rules that require a wipe. The version number should be
         --   set in the source code, and not passed on the command line.
     ,shakeVerbosity :: Verbosity
-        -- ^ Defaults to 'Normal'. What level of messages should be printed out.
+        -- ^ Defaults to 'Info'. What level of messages should be printed out.
     ,shakeStaunch :: Bool
         -- ^ Defaults to 'False'. Operate in staunch mode, where building continues even after errors,
         --   similar to @make --keep-going@.
@@ -206,7 +206,10 @@
     ,shakeCloud :: [String]
         -- ^ Defaults to @[]@. Cloud servers to talk to forming a shared cache.
     ,shakeSymlink :: Bool
-        -- ^ Defaults to @True@. Use symlinks if they are available.
+        -- ^ Defaults to @False@. Use symlinks for 'shakeShare' if they are available.
+        --   If this setting is @True@ (even if symlinks are not available) then files will be
+        --   made read-only to avoid inadvertantly poisoning the shared cache.
+        --   Note the links are actually hard links, not symlinks.
     ,shakeProgress :: IO Progress -> IO ()
         -- ^ Defaults to no action. A function called when the build starts, allowing progress to be reported.
         --   The function is called on a separate thread, and that thread is killed when the build completes.
@@ -230,8 +233,8 @@
 -- | The default set of 'ShakeOptions'.
 shakeOptions :: ShakeOptions
 shakeOptions = ShakeOptions
-    ".shake" 1 "1" Normal False [] Nothing [] [] [] [] (Just 10) [] [] False True False
-    True ChangeModtime True [] False False Nothing [] True
+    ".shake" 1 "1" Info False [] Nothing [] [] [] [] (Just 10) [] [] False True False
+    True ChangeModtime True [] False False Nothing [] False
     (const $ return ())
     (const $ BS.putStrLn . UTF8.fromString) -- try and output atomically using BS
     (\_ _ _ -> return ())
@@ -301,11 +304,12 @@
 
 -- | The verbosity data type, used by 'shakeVerbosity'.
 data Verbosity
-    = Silent -- ^ Don't print any messages.
-    | Quiet  -- ^ Only print essential messages, typically errors.
-    | Normal -- ^ Print errors and @# /command-name/ (for /file-name/)@ when running a 'Development.Shake.traced' command.
-    | Loud   -- ^ Print errors and full command lines when running a 'Development.Shake.command' or 'Development.Shake.cmd' command.
-    | Chatty -- ^ Print errors, full command line and status messages when starting a rule.
+    = Silent  -- ^ Don't print any messages.
+    | Error     -- ^ Only print error messages.
+    | Warn    -- ^ Print errors and warnings.
+    | Info    -- ^ Print errors, warnings and @# /command-name/ (for /file-name/)@ when running a 'Development.Shake.traced' command.
+    | Verbose -- ^ Print errors, warnings, full command lines when running a 'Development.Shake.command' or
+              --   'Development.Shake.cmd' command and status messages when starting a rule.
     | Diagnostic -- ^ Print messages for virtually everything (mostly for debugging).
       deriving (Eq,Ord,Show,Read,Typeable,Data,Enum,Bounded)
 
diff --git a/src/Development/Shake/Internal/Paths.hs b/src/Development/Shake/Internal/Paths.hs
--- a/src/Development/Shake/Internal/Paths.hs
+++ b/src/Development/Shake/Internal/Paths.hs
@@ -1,4 +1,9 @@
+{-# LANGUAGE CPP #-}
 
+#ifdef FILE_EMBED
+{-# LANGUAGE TemplateHaskell #-}
+#endif
+
 -- | The information from Paths_shake cleaned up
 module Development.Shake.Internal.Paths(
     shakeVersionString,
@@ -7,23 +12,58 @@
     readDataFileHTML
     ) where
 
-import Paths_shake
-import Control.Exception
 import Control.Monad.Extra
 import Data.Version
-import System.Directory
 import System.FilePath
-import System.Info.Extra
-import System.IO.Unsafe
-import System.Environment
 import General.Extra
 import qualified Data.ByteString.Lazy as LBS
+import Paths_shake
 
+#ifdef FILE_EMBED
+import qualified Data.ByteString as BS
+import Data.FileEmbed
+#else
+import Control.Exception
+import System.Directory
+import System.Info.Extra
+import System.IO.Unsafe
+import System.Environment
+#endif
 
 shakeVersionString :: String
 shakeVersionString = showVersion version
 
+#ifdef FILE_EMBED
 
+initDataDirectory :: IO ()
+initDataDirectory = return ()
+
+htmlDataFiles :: [(FilePath, BS.ByteString)]
+htmlDataFiles =
+  [ ("profile.html",  $(embedFile "html/profile.html"))
+  , ("progress.html", $(embedFile "html/progress.html"))
+  , ("shake.js",      $(embedFile "html/shake.js"))
+  ]
+
+readDataFileHTML :: FilePath -> IO LBS.ByteString
+readDataFileHTML file = do
+    case lookup file htmlDataFiles of
+      Nothing -> fail $ "Could not find data file " ++ file ++ " in embedded data files!"
+      Just x  -> return (LBS.fromStrict x)
+
+manualDirData :: [(FilePath, BS.ByteString)]
+manualDirData = $(embedDir "docs/manual")
+
+hasManualData :: IO Bool
+hasManualData = return True
+
+copyManualData :: FilePath -> IO ()
+copyManualData dest = do
+    createDirectoryRecursive dest
+    forM_ manualDirData $ \(file, bs) -> do
+        BS.writeFile (dest </> file) bs
+
+#else
 -- We want getDataFileName to be relative to the current directory on program startup,
 -- even if we issue a change directory command. Therefore, first call caches, future ones read.
 {-# NOINLINE dataDirs #-}
@@ -34,12 +74,10 @@
     curdir <- getCurrentDirectory
     return $ [datdir] ++ [exedir | exedir /= ""] ++ [curdir]
 
-
 -- The data files may be located relative to the current directory, if so cache it in advance
 initDataDirectory :: IO ()
 initDataDirectory = void $ evaluate dataDirs
 
-
 getDataFile :: FilePath -> IO FilePath
 getDataFile file = do
     let poss = map (</> file) dataDirs
@@ -51,13 +89,11 @@
 hasDataFile :: FilePath -> IO Bool
 hasDataFile file = anyM (\dir -> doesFileExist_ $ dir </> file) dataDirs
 
-
 readDataFileHTML :: FilePath -> IO LBS.ByteString
 readDataFileHTML file = LBS.readFile =<< getDataFile ("html" </> file)
 
-
 manualFiles :: [FilePath]
-manualFiles = map ("docs/manual" </>) ["Build.hs","main.c","constants.c","constants.h","build" <.> if isWindows then "bat" else "sh"]
+manualFiles = map ("docs/manual" </>) ["Shakefile.hs","main.c","constants.c","constants.h","build" <.> if isWindows then "bat" else "sh"]
 
 hasManualData :: IO Bool
 hasManualData = allM hasDataFile manualFiles
@@ -68,3 +104,4 @@
     forM_ manualFiles $ \file -> do
         src <- getDataFile file
         copyFile src (dest </> takeFileName file)
+#endif
diff --git a/src/Development/Shake/Internal/Rules/Directory.hs b/src/Development/Shake/Internal/Rules/Directory.hs
--- a/src/Development/Shake/Internal/Rules/Directory.hs
+++ b/src/Development/Shake/Internal/Rules/Directory.hs
@@ -5,12 +5,13 @@
 module Development.Shake.Internal.Rules.Directory(
     doesFileExist, doesDirectoryExist,
     getDirectoryContents, getDirectoryFiles, getDirectoryDirs,
-    getEnv, getEnvWithDefault,
+    getEnv, getEnvWithDefault, getEnvError,
     removeFiles, removeFilesAfter,
     getDirectoryFilesIO,
     defaultRuleDirectory
     ) where
 
+import Control.Exception.Extra
 import Control.Monad.Extra
 import Control.Monad.IO.Class
 import Data.Maybe
@@ -181,6 +182,10 @@
 getEnvWithDefault :: String -> String -> Action String
 getEnvWithDefault def var = fromMaybe def <$> getEnv var
 
+-- | A partial variant of 'getEnv' that returns the environment variable variable or fails.
+getEnvError :: Partial => String -> Action String
+getEnvError name = getEnvWithDefault (error $ "getEnvError: Environment variable " ++ name ++ " is undefined") name
+
 -- | Get the contents of a directory. The result will be sorted, and will not contain
 --   the entries @.@ or @..@ (unlike the standard Haskell version).
 --   The resulting paths will be relative to the first argument.
@@ -322,5 +327,5 @@
 --   Where possible, delete the files as a normal part of the build, e.g. using @'liftIO' $ 'removeFiles' dir pats@.
 removeFilesAfter :: FilePath -> [FilePattern] -> Action ()
 removeFilesAfter a b = do
-    putLoud $ "Will remove " ++ unwords b ++ " from " ++ a
+    putVerbose $ "Will remove " ++ unwords b ++ " from " ++ a
     runAfter $ removeFiles a b
diff --git a/src/Development/Shake/Internal/Rules/File.hs b/src/Development/Shake/Internal/Rules/File.hs
--- a/src/Development/Shake/Internal/Rules/File.hs
+++ b/src/Development/Shake/Internal/Rules/File.hs
@@ -215,7 +215,7 @@
     (ruleVer, ruleAct, ruleErr) <- getUserRuleInternal o (\(FileRule s _) -> Just s) $ \(FileRule _ f) -> f xStr
     let verEq v = Just v == ruleVer || case ruleAct of [] -> v == Ver 0; [(v2,_)] -> v == Ver v2; _ -> False
     let rebuild = do
-            putWhen Chatty $ "# " ++ show o
+            putWhen Verbose $ "# " ++ show o
             case ruleAct of
                 [] -> rebuildWith Nothing
                 [x] -> rebuildWith $ Just x
@@ -457,14 +457,14 @@
         tracker $ map (FileQ . fileNameFromString) ys
 
 
--- | Track that a file was read by the action preceeding it. If 'shakeLint' is activated
+-- | Track that a file was read by the action preceding it. If 'shakeLint' is activated
 --   then these files must be dependencies of this rule. Calls to 'trackRead' are
 --   automatically inserted in 'LintFSATrace' mode.
 trackRead :: [FilePath] -> Action ()
 trackRead = track lintTrackRead
 
 
--- | Track that a file was written by the action preceeding it. If 'shakeLint' is activated
+-- | Track that a file was written by the action preceding it. If 'shakeLint' is activated
 --   then these files must either be the target of this rule, or never referred to by the build system.
 --   Calls to 'trackWrite' are automatically inserted in 'LintFSATrace' mode.
 trackWrite :: [FilePath] -> Action ()
diff --git a/src/Development/Shake/Internal/Rules/Files.hs b/src/Development/Shake/Internal/Rules/Files.hs
--- a/src/Development/Shake/Internal/Rules/Files.hs
+++ b/src/Development/Shake/Internal/Rules/Files.hs
@@ -96,7 +96,7 @@
     (ruleVer, ruleAct, ruleErr) <- getUserRuleInternal k (\(FilesRule s _) -> Just s) $ \(FilesRule _ f) -> f k
     let verEq v = Just v == ruleVer || map (Ver . fst) ruleAct == [v]
     let rebuild = do
-            putWhen Chatty $ "# " ++ show k
+            putWhen Verbose $ "# " ++ show k
             case ruleAct of
                 [x] -> rebuildWith x
                 _ -> throwM ruleErr
diff --git a/src/Development/Shake/Internal/Rules/Oracle.hs b/src/Development/Shake/Internal/Rules/Oracle.hs
--- a/src/Development/Shake/Internal/Rules/Oracle.hs
+++ b/src/Development/Shake/Internal/Rules/Oracle.hs
@@ -82,7 +82,7 @@
 -- newtype GhcVersion = GhcVersion () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
 -- type instance RuleResult GhcVersion = String
 -- rules = do
---     'addOracle' $ \\(GhcVersion _) -> fmap 'Development.Shake.fromStdout' $ 'Development.Shake.cmd' \"ghc --numeric-version\" :: Action String
+--     'addOracle' $ \\(GhcVersion _) -> 'Development.Shake.fromStdout' \<$\> 'Development.Shake.cmd' \"ghc --numeric-version\" :: Action String
 --     ... rules ...
 -- @
 --
diff --git a/src/General/Binary.hs b/src/General/Binary.hs
--- a/src/General/Binary.hs
+++ b/src/General/Binary.hs
@@ -35,7 +35,7 @@
     ,getOp :: BS.ByteString -> v
     }
 
-binaryOpMap :: (Eq a, Hashable a, BinaryEx a) => (a -> BinaryOp b) -> BinaryOp (a, b)
+binaryOpMap :: BinaryEx a => (a -> BinaryOp b) -> BinaryOp (a, b)
 binaryOpMap mp = BinaryOp
     {putOp = \(a, b) -> putExN (putEx a) <> putOp (mp a) b
     ,getOp = \bs -> let (bs1,bs2) = getExN bs; a = getEx bs1 in (a, getOp (mp a) bs2)
diff --git a/src/General/Process.hs b/src/General/Process.hs
--- a/src/General/Process.hs
+++ b/src/General/Process.hs
@@ -71,6 +71,7 @@
     ,poStdout :: [Destination]
     ,poStderr :: [Destination]
     ,poAsync :: Bool
+    ,poCloseFds :: Bool
     }
 
 
@@ -152,7 +153,7 @@
     let outFiles = nubOrd [x | DestFile x <- poStdout ++ poStderr]
     let inFiles = nubOrd [x | SrcFile x <- poStdin]
     withFiles WriteMode outFiles $ \outHandle -> withFiles ReadMode inFiles $ \inHandle -> do
-        let cp = (cmdSpec poCommand){cwd = poCwd, env = poEnv, create_group = True, close_fds = True
+        let cp = (cmdSpec poCommand){cwd = poCwd, env = poEnv, create_group = True, close_fds = poCloseFds
                  ,std_in = fst $ stdIn inHandle poStdin
                  ,std_out = stdStream outHandle poStdout poStderr, std_err = stdStream outHandle poStderr poStdout}
         withCreateProcessCompat cp $ \inh outh errh pid ->
diff --git a/src/General/Template.hs b/src/General/Template.hs
--- a/src/General/Template.hs
+++ b/src/General/Template.hs
@@ -1,5 +1,10 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE ViewPatterns #-}
 
+#ifdef FILE_EMBED
+{-# LANGUAGE TemplateHaskell #-}
+#endif
+
 module General.Template(runTemplate) where
 
 import System.FilePath.Posix
@@ -13,12 +18,26 @@
 import qualified Language.Javascript.Flot as Flot
 import qualified Language.Javascript.JQuery as JQuery
 
+#ifdef FILE_EMBED
+import Data.FileEmbed
+import Language.Haskell.TH.Syntax ( runIO )
+#endif
 
+{- HLINT ignore "Redundant bracket" -} -- a result of CPP expansion
+
+-- Very hard to abstract over TH, so we do it with CPP
+#ifdef FILE_EMBED
+#define FILE(x) (return (LBS.fromStrict $(embedFile =<< runIO (x))))
+#else
+#define FILE(x) (LBS.readFile =<< (x))
+#endif
+
+libraries :: [(String, IO LBS.ByteString)]
 libraries =
-    [("jquery.js", JQuery.file)
-    ,("jquery.dgtable.js", DGTable.file)
-    ,("jquery.flot.js", Flot.file Flot.Flot)
-    ,("jquery.flot.stack.js", Flot.file Flot.FlotStack)
+    [("jquery.js",            FILE(JQuery.file))
+    ,("jquery.dgtable.js",    FILE(DGTable.file))
+    ,("jquery.flot.js",       FILE(Flot.file Flot.Flot))
+    ,("jquery.flot.stack.js", FILE(Flot.file Flot.FlotStack))
     ]
 
 
@@ -40,9 +59,11 @@
                 y = LBS.dropWhile isSpace x
                 grab = asker . takeWhile (/= '\"') . LBS.unpack
 
-        asker o@(splitFileName -> ("lib/",x)) = case lookup x libraries of
-            Just act -> LBS.readFile =<< act
-            Nothing -> errorIO $ "Template library, unknown library: " ++ o
+        asker o@(splitFileName -> ("lib/",x)) =
+            case lookup x libraries of
+                Nothing -> errorIO $ "Template library, unknown library: " ++ o
+                Just act -> act
+
         asker "shake.js" = readDataFileHTML "shake.js"
         asker "data/metadata.js" = do
             time <- getCurrentTime
@@ -50,7 +71,6 @@
                 "var version = " ++ show shakeVersionString ++
                 "\nvar generated = " ++ show (formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) time)
         asker x = ask x
-
 
 -- Perform a mapM on each line and put the result back together again
 lbsMapLinesIO :: (LBS.ByteString -> IO LBS.ByteString) -> LBS.ByteString -> IO LBS.ByteString
diff --git a/src/General/Wait.hs b/src/General/Wait.hs
--- a/src/General/Wait.hs
+++ b/src/General/Wait.hs
@@ -13,7 +13,7 @@
 import Data.Primitive.Array
 import GHC.Exts(RealWorld)
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 import Control.Monad.Fail
 #endif
 
@@ -58,10 +58,14 @@
 instance (MonadIO m,  Applicative m) => MonadIO (Wait m) where
     liftIO = Lift . liftIO . fmap Now
 
-#if __GLASGOW_HASKELL__ >= 800
+#if __GLASGOW_HASKELL__ >= 800 && __GLASGOW_HASKELL__ < 808
 instance MonadFail m => MonadFail (Wait m) where
     fail = Lift . Control.Monad.Fail.fail
 #endif
+#if __GLASGOW_HASKELL__ >= 808
+instance MonadFail m => MonadFail (Wait m) where
+    fail = Lift . Prelude.fail
+#endif
 
 
 firstJustWaitUnordered :: MonadIO m => (a -> Wait m (Maybe b)) -> [a] -> Wait m (Maybe b)
@@ -91,7 +95,7 @@
                         when (old == 1) $ callback Nothing
 
 
-firstLeftWaitUnordered :: (Applicative m, MonadIO m) => (a -> Wait m (Either e b)) -> [a] -> Wait m (Either e [b])
+firstLeftWaitUnordered :: MonadIO m => (a -> Wait m (Either e b)) -> [a] -> Wait m (Either e [b])
 firstLeftWaitUnordered f xs = do
         let n = length xs
         mut <- liftIO $ newArray n undefined
@@ -101,7 +105,7 @@
             Nothing -> liftIO $ Right <$> mapM (readArray mut) [0..n-1]
     where
         -- keep a list of those things we might visit later, and ask for each we see in turn
-        go :: (Applicative m, MonadIO m) => MutableArray RealWorld b -> [(Int, (Either e b -> m ()) -> m ())] -> [(Int, Wait m (Either e b))] -> Wait m (Maybe e)
+        go :: MonadIO m => MutableArray RealWorld b -> [(Int, (Either e b -> m ()) -> m ())] -> [(Int, Wait m (Either e b))] -> Wait m (Maybe e)
         go mut later ((i,x):xs) = case x of
             Now (Left e) -> Now $ Just e
             Now (Right b) -> do
diff --git a/src/Run.hs b/src/Run.hs
--- a/src/Run.hs
+++ b/src/Run.hs
@@ -30,7 +30,7 @@
             e <- rawSystem prog args
             when (e /= ExitSuccess) $ exitWith e
         Nothing -> do
-            let go = shakeArgsWith shakeOptions{shakeCreationCheck=False} flags $ \opts targets -> do
+            let go = shakeArgsWith shakeOptions{shakeThreads=0,shakeCreationCheck=False} flags $ \opts targets -> do
                         let tool = listToMaybe [x | Tool x <- opts]
                         makefile <- case reverse [x | UseMakefile x <- opts] of
                             x:_ -> return x
diff --git a/src/Test.hs b/src/Test.hs
--- a/src/Test.hs
+++ b/src/Test.hs
@@ -20,6 +20,7 @@
 import qualified Test.C
 import qualified Test.Cache
 import qualified Test.Cleanup
+import qualified Test.CloseFileHandles
 import qualified Test.Command
 import qualified Test.Config
 import qualified Test.Database
@@ -48,7 +49,7 @@
 import qualified Test.Progress
 import qualified Test.Random
 import qualified Test.Rebuild
-import qualified Test.Deprioritize
+import qualified Test.Reschedule
 import qualified Test.Resources
 import qualified Test.Self
 import qualified Test.SelfMake
@@ -75,10 +76,10 @@
     ,"c" * Test.C.main
     ,"cache" * Test.Cache.main
     ,"cleanup" * Test.Cleanup.main
+    ,"closefilehandles" * Test.CloseFileHandles.main
     ,"command" * Test.Command.main
     ,"config" * Test.Config.main
     ,"database" * Test.Database.main
-    ,"deprioritize" * Test.Deprioritize.main
     ,"digest" * Test.Digest.main
     ,"directory" * Test.Directory.main
     ,"docs" * Test.Docs.main
@@ -104,6 +105,7 @@
     ,"progress" * Test.Progress.main
     ,"random" * Test.Random.main
     ,"rebuild" * Test.Rebuild.main
+    ,"reschedule" * Test.Reschedule.main
     ,"resources" * Test.Resources.main
     ,"self" * Test.Self.main
     ,"selfmake" * Test.SelfMake.main
diff --git a/src/Test/Basic.hs b/src/Test/Basic.hs
--- a/src/Test/Basic.hs
+++ b/src/Test/Basic.hs
@@ -58,7 +58,7 @@
 
     phony "options" $ do
         opts <- getShakeOptions
-        putNormal $ show opts
+        putInfo $ show opts
 
     "dummer.txt" %> \out -> do
         need ["dummy","dummy"]
diff --git a/src/Test/Batch.hs b/src/Test/Batch.hs
--- a/src/Test/Batch.hs
+++ b/src/Test/Batch.hs
@@ -15,7 +15,7 @@
     batch 3 ("*.out" %>) (\out -> do need [inp out]; return out) $ \outs -> do
         liftIO $ assertBool (length outs <= 3) "length outs <= 3"
         withResource file 1 $ liftIO $ appendFile "log.txt" $ show (length outs) ++ "\n"
-        putNormal $ "Building batch: " ++ unwords outs
+        putInfo $ "Building batch: " ++ unwords outs
         forM_ outs $ \out -> liftIO $ copyFile (inp out) out
     want [show i <.> "out" | i <- [1..6]]
 
diff --git a/src/Test/CloseFileHandles.hs b/src/Test/CloseFileHandles.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/CloseFileHandles.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE CPP #-}
+module Test.CloseFileHandles(main) where
+
+import Test.Type
+
+#ifdef mingw32_HOST_OS
+
+main = testNone -- don't know how to do this on windows
+
+#else
+
+import Development.Shake
+import Development.Shake.FilePath
+import System.Posix.IO
+import Control.Monad.Extra
+import System.Exit
+import System.IO
+
+main = testBuild test $ do
+    let helper = toNative $ "helper/close_file_handles_helper" <.> exe
+    let name !> test = do want [name]
+                          name ~> do need ["helper/close_file_handles_helper" <.> exe]; test
+
+    let helper_source = unlines
+            ["import System.Environment"
+            ,"import System.Posix.IO"
+            ,"import System.IO"
+            ,"import System.Exit"
+            ,""
+            ,"main = do"
+            ,"  args <- getArgs"
+            ,"  case args of"
+            ,"    [fdString] -> do"
+            ,"       handle <- fdToHandle (read fdString)"
+            ,"       hClose handle"
+            ,"       exitSuccess"
+            ,"    _ -> do"
+            ,"      progName <- getProgName"
+            ,"      hPutStrLn stderr (\"usage: \" ++ progName ++ \" <file descriptor number>\\n    tries closing the file descriptor number\\n    exits successful, if the file descriptor was open\")"
+            ,"      exitWith (ExitFailure 3)"]
+
+    "close_file_handles_helper.hs" %> \out -> do
+        need ["../../src/Test/CloseFileHandles.hs"]
+        writeFileChanged out helper_source
+
+    ["helper/close_file_handles_helper"<.>exe, "close_file_handles_helper.hi", "close_file_handles_helper.o"] &%> \_ -> do
+        need ["close_file_handles_helper.hs"]
+        cmd "ghc --make" "close_file_handles_helper.hs -o helper/close_file_handles_helper"
+
+    let callWithOpenFile cmdWithOpts = withTempFile $
+            \file -> actionBracket (openFile file AppendMode) hClose $
+                \h -> do fd <- liftIO $ handleToFd h
+                         (Exit c, Stdout _, Stderr _) <- cmdWithOpts helper (show fd) :: Action (Exit, Stdout String, Stderr String)
+                         return c
+
+    "defaultbehaviour" !> do
+        c <- callWithOpenFile cmd
+        liftIO $ assertBool (c == ExitSuccess) "handle closed without option CloseFileHandles"
+
+    "closing" !> do
+        c <- callWithOpenFile (cmd CloseFileHandles)
+        liftIO $ assertBool (c /= ExitSuccess) "handle not closed with option CloseFileHandles"
+
+test build = do
+    whenM hasTracker $
+        build ["-j4", "--no-lint"]
+    build ["-j4"]
+
+#endif
diff --git a/src/Test/Command.hs b/src/Test/Command.hs
--- a/src/Test/Command.hs
+++ b/src/Test/Command.hs
@@ -89,7 +89,7 @@
             offset <- liftIO offsetTime
             act
             t <- liftIO offset
-            putNormal $ "Timed out in " ++ showDuration t
+            putInfo $ "Timed out in " ++ showDuration t
             when (t < 2 || t > 8) $ error $ "failed to timeout, took " ++ show t
 
     "timeout1" !> checkTimeout (do
diff --git a/src/Test/Deprioritize.hs b/src/Test/Deprioritize.hs
deleted file mode 100644
--- a/src/Test/Deprioritize.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-
-module Test.Deprioritize(main) where
-
-import Development.Shake
-import Test.Type
-
-
-main = testBuild test $ do
-    file <- newResource "log.txt" 1
-    let log x = withResource file 1 $ liftIO $ appendFile "log.txt" x
-    "*.p0" %> \out -> do
-        log "0"
-        writeFile' out ""
-    "*.p1" %> \out -> do
-        deprioritize 1
-        log "1"
-        writeFile' out ""
-    "*.p2" %> \out -> do
-        deprioritize 2
-        log "2"
-        writeFile' out ""
-
-
-test build = do
-    build ["clean"]
-    build ["foo.p1","bar.p1","baz.p0","qux.p2"]
-    assertContents "log.txt" "0211"
diff --git a/src/Test/Docs.hs b/src/Test/Docs.hs
--- a/src/Test/Docs.hs
+++ b/src/Test/Docs.hs
@@ -9,18 +9,19 @@
 import Test.Type
 import Control.Monad
 import Data.Char
-import General.Extra
 import Data.List.Extra
 import Data.Maybe
 import System.Info
 import Data.Version.Extra
 
 
--- Older versions of Haddock garbage the --@ markup and have ambiguity errors
-brokenHaddock = compilerVersion < makeVersion [8]
+-- Older versions of Haddock (GHC 7.10 and below) garbage the --@ markup and have ambiguity errors
+-- GHC 8.0 has a segfault when linking Setup
+brokenHaddock = compilerVersion < makeVersion [8,2]
 
 main = testBuild (unless brokenHaddock . defaultTest) $ do
     let index = "dist/doc/html/shake/index.html"
+    let setup = "dist/setup.exe"
     let config = "dist/setup-config"
     want ["Success.txt"]
     let trackIgnore = trackAllow ["dist/**"]
@@ -28,18 +29,33 @@
     let needSource = need =<< getDirectoryFiles "." (map (shakeRoot </>)
             ["src/Development/Shake.hs","src/Development/Shake//*.hs","src/Development/Ninja/*.hs","src/General//*.hs"])
 
-    config %> \_ -> do
+    let runSetup :: [String] -> Action ()
+        runSetup args = do
+            trackIgnore
+            need [setup]
+            -- Make Cabal and Stack play nicely with GHC_PACKAGE_PATH
+            setup <- liftIO $ canonicalizePath setup
+            cmd_ (RemEnv "GHC_PACKAGE_PATH") (Cwd shakeRoot) setup args
+
+    setup %> \_ -> do
+        -- Important to compile the setup binary, or we run foul of
+        -- https://gitlab.haskell.org/ghc/ghc/issues/17575
         trackIgnore
-        need $ map (shakeRoot </>) ["shake.cabal","Setup.hs"]
-        -- Make Cabal and Stack play nicely
+        need [shakeRoot </> "Setup.hs"]
+        setup <- liftIO $ canonicalizePath setup
+        curdir <- liftIO $ canonicalizePath "dist"
+        cmd_ (Cwd shakeRoot) "ghc -package=Cabal Setup.hs -o" [setup] "-outputdir" [curdir]
+
+    config %> \_ -> do
         path <- getEnv "GHC_PACKAGE_PATH"
-        liftIO $ createDirectoryRecursive "dist"
         dist <- liftIO $ canonicalizePath "dist" -- make sure it works even if we cwd
-        cmd_ (RemEnv "GHC_PACKAGE_PATH") (Cwd shakeRoot) "runhaskell -package=Cabal Setup.hs configure"
-            ["--builddir=" ++ dist,"--user"]
+        need [shakeRoot </> "shake.cabal"]
+        runSetup $
+            ["configure","--builddir=" ++ dist,"--user"] ++
             -- package-db is very sensitive, see #267
             -- note that the reverse ensures the behaviour is consistent between the flags and the env variable
             ["--package-db=" ++ x | x <- maybe [] (reverse . filter (`notElem` [".",""]) . splitSearchPath) path]
+
         -- Paths_shake is only created by "Setup build" (which we want to skip), and required by "Setup haddock", so we fake it
         copyFile' (shakeRoot </> "src/Paths.hs") "dist/build/autogen/Paths_shake.hs"
         copyFile' (shakeRoot </> "src/Paths.hs") "dist/build/shake/autogen/Paths_shake.hs"
@@ -51,7 +67,7 @@
         needSource
         trackIgnore
         dist <- liftIO $ canonicalizePath "dist"
-        cmd (RemEnv "GHC_PACKAGE_PATH") (Cwd shakeRoot) "runhaskell -package=Cabal Setup.hs haddock" ["--builddir=" ++ dist]
+        runSetup ["haddock", "--builddir=" ++ dist]
 
     "Part_*.hs" %> \out -> do
         need [shakeRoot </> "src/Test/Docs.hs"] -- so much of the generator is in this module
@@ -161,7 +177,7 @@
         writeFileLines out $ ["module Main(main) where"] ++ ["import " ++ m ++ "()" | m <- mods] ++ ["main = return ()"]
 
     "Success.txt" %> \out -> do
-        putNormal . ("Checking documentation for:\n" ++) =<< readFile' "Files.lst"
+        putInfo . ("Checking documentation for:\n" ++) =<< readFile' "Files.lst"
         needModules
         need ["Main.hs"]
         trackIgnore
@@ -347,7 +363,7 @@
     "@ndm_haskell file-name .PHONY filepath trim base stack extra #include " ++
     "*> BuiltinRun BuiltinLint BuiltinIdentity RuleResult " ++
     "oldStore mode node_modules llbuild Makefile " ++
-    "RebuildNever"
+    "RebuildNever RLIMIT_NOFILE "
     = True
 whitelist x = x `elem`
     ["[Foo.hi, Foo.o]"
@@ -371,6 +387,7 @@
     ,"$(LitE . StringL . loc_filename <$> location)"
     ,"-d[ FILE], --debug[=FILE]"
     ,"-r[ FILE], --report[=FILE], --profile[=FILE]"
+    ,"man 2 getrlimit"
     ]
 
 blacklist :: [String]
diff --git a/src/Test/Errors.hs b/src/Test/Errors.hs
--- a/src/Test/Errors.hs
+++ b/src/Test/Errors.hs
@@ -179,6 +179,8 @@
         liftIO $ putStrLn $ let x = x in x
 
 test build = do
+    let hasLocations = compilerVersion >= readVersion "8.0" -- when GHC got support for locations
+
     -- on Windows, file paths may end up with \ separators, make sure we can still match them
     let crash args parts = assertExceptionAfter (replace "\\" "/") parts (build $ "--quiet" : args)
     build ["clean"]
@@ -193,7 +195,7 @@
     crash ["failcreates"] ["failcreates"]
     crash ["recursive_"] ["recursive_","intermediate_","recursive"]
     crash ["rec1","rec2"] ["rec1","rec2","indirect recursion","recursive"]
-    crash ["systemcmd"] ["systemcmd","random_missing_command"]
+    crash ["systemcmd"] $ ["systemcmd","random_missing_command"] ++ ["at cmd, called at" | hasLocations]
     crash ["stack1"] ["stack1","stack2","stack3","crash"]
 
     b <- IO.doesFileExist "staunch1"
@@ -230,15 +232,14 @@
     assertContents "overlap.txt" "overlap.txt"
     crash ["overlap.txx"] $
         ["key matches multiple rules","matched:  4","overlap.txx","overlap.t*","overlap.*","*.tox"] ++
-        ["Test/Errors.hs" | compilerVersion >= readVersion "8.0"] -- when GHC got support for locations
+        ["Test/Errors.hs" | hasLocations]
 
     crash ["tempfile"] ["tempfile-died"]
     src <- readFile "tempfile"
     assertMissing src
     build ["tempdir"]
 
-    crash ["--die"] $ ["Shake","death error"] ++
-        ["Test/Errors.hs" | compilerVersion >= readVersion "8.0"] -- when GHC got support for locations
+    crash ["--die"] $ ["Shake","death error"] ++ ["Test/Errors.hs" | hasLocations]
 
     putStrLn "## BUILD errors"
     (out,_) <- IO.captureOutput $ build []
@@ -294,6 +295,6 @@
     assertContents "finalfinal" "XY"
 
     build ["catch1"]
-    assertContents "catch1" "magic1"
+    assertContentsInfix "catch1" "magic1"
     crash ["catch2"] [show ThreadKilled]
     crash ["catch3.2"] ["magic3"]
diff --git a/src/Test/FileLock.hs b/src/Test/FileLock.hs
--- a/src/Test/FileLock.hs
+++ b/src/Test/FileLock.hs
@@ -13,12 +13,13 @@
 
 main = testBuild test $
     action $ do
-        putNormal "Starting sleep"
+        putInfo "Starting sleep"
         liftIO $ sleep 5
-        putNormal "Finished sleep"
+        putInfo "Finished sleep"
 
 
 -- Disabled under Mac because it fails, see #560
+-- Reported as working locally under APFS, so may just be the older HFS+ as used by Travis CI
 test build = unless isMac $ do
     -- check it fails exactly once
     time <- offsetTime
diff --git a/src/Test/Forward.hs b/src/Test/Forward.hs
--- a/src/Test/Forward.hs
+++ b/src/Test/Forward.hs
@@ -1,21 +1,65 @@
 
 module Test.Forward(main) where
 
+import Data.Char
+import Data.List.Extra
 import Development.Shake
+import System.Info.Extra
 import Development.Shake.Forward
 import Development.Shake.FilePath
 import Test.Type
+import System.IO.Extra as IO
 
+
 main = testBuild test $ forwardRule $ do
-    let src = shakeRoot </> "src/Test/C"
-    cs <- getDirectoryFiles src ["*.c"]
+    cs <- getDirectoryFiles "" ["*.c"]
     os <- forP cs $ \c -> do
         let o = c <.> "o"
-        cache $ cmd "gcc -c" [src </> c] "-o" [o]
+        cache $ cmd "gcc -c" [c] "-o" [o]
         return o
     cache $ cmd "gcc -o" ["Main" <.> exe] os
+    cache $ cmd ["." </> "Main" <.> exe] (FileStdout "output.txt")
 
-test build = do
+    -- Doing this way to test cacheAction with arguments
+    -- any real code should use a tracked readFile and avoid passing arguments to the closure
+    src <- liftIO $ IO.readFile' "output.txt"
+    cacheActionWith "reducer" src $ writeFile' "out.txt" $ filter isUpper src
+
+
+checkVaild act = do
     b <- hasTracker
-    build $ "--clean" : ["--forward" | b]
-    build $ "-j2" : ["--forward" | b]
+    if not b then
+        putStrLn "Warning: Not running forward test (no tracker)"
+     else if isMac then
+        putStrLn "Warning: Not running forward test (doesn't work on Mac)"
+     else
+        act
+
+test build = checkVaild $ do
+    -- first clean then copy the source files over
+    build ["clean"]
+    copyDirectoryChanged (shakeRoot </> "src/Test/C") "."
+
+    -- build and rebuild
+    build ["--forward"]
+    assertContents "output.txt" "Hello Shake Users!\n"
+    assertContents "out.txt" "HSU"
+
+    -- check that cacheAction doesn't rerun when it shouldn't
+    writeFile "out.txt" "HHH"
+    build ["-j2","--forward"]
+    assertContents "output.txt" "Hello Shake Users!\n"
+    assertContents "out.txt" "HHH"
+
+    -- modify the constants
+    orig <- IO.readFile' "constants.c"
+    writeFile "constants.c" $ replace "Shake" "Rattle" orig
+    build ["-j2","--forward"]
+    assertContents "output.txt" "Hello Rattle Users!\n"
+    assertContents "out.txt" "HRU"
+
+    -- put it back
+    writeFile "constants.c" orig
+    build ["-j2","--forward"]
+    assertContents "output.txt" "Hello Shake Users!\n"
+    assertContents "out.txt" "HSU"
diff --git a/src/Test/Reschedule.hs b/src/Test/Reschedule.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Reschedule.hs
@@ -0,0 +1,27 @@
+
+module Test.Reschedule(main) where
+
+import Development.Shake
+import Test.Type
+
+
+main = testBuild test $ do
+    file <- newResource "log.txt" 1
+    let log x = withResource file 1 $ liftIO $ appendFile "log.txt" x
+    "*.p0" %> \out -> do
+        log "0"
+        writeFile' out ""
+    "*.p1" %> \out -> do
+        reschedule 1
+        log "1"
+        writeFile' out ""
+    "*.p2" %> \out -> do
+        reschedule 2
+        log "2"
+        writeFile' out ""
+
+
+test build = do
+    build ["clean"]
+    build ["foo.p1","bar.p1","baz.p0","qux.p2"]
+    assertContents "log.txt" "0211"
diff --git a/src/Test/Targets.hs b/src/Test/Targets.hs
--- a/src/Test/Targets.hs
+++ b/src/Test/Targets.hs
@@ -1,12 +1,15 @@
 module Test.Targets(main) where
 
 import Development.Shake
+import Development.Shake.Internal.Core.Rules (getHelpSuffix)
 import Test.Type
 
 main :: IO () -> IO ()
 main _sleeper = do
     targets <- getTargets shakeOptions rules
     targets === expected
+    helpSuffix <- getHelpSuffix shakeOptions rules
+    helpSuffix === ["Don't Panic", "Know where your towel is"]
 
 rules :: Rules ()
 rules = do
@@ -26,6 +29,9 @@
         withTargetDocs "awesome files" $ ["file12", "file13"] &%> \_ -> return ()
         phony "Foo" $ return ()
         withoutTargets $ phony "Bar" $ return ()
+
+    addHelpSuffix "Don't Panic"
+    addHelpSuffix "Know where your towel is"
 
 
 expected :: [(String, Maybe String)]
diff --git a/src/Test/Type.hs b/src/Test/Type.hs
--- a/src/Test/Type.hs
+++ b/src/Test/Type.hs
@@ -2,13 +2,13 @@
 
 module Test.Type(
     sleep, sleepFileTime, sleepFileTimeCalibrate,
-    testBuildArgs, testBuild, testSimple,
+    testBuildArgs, testBuild, testSimple, testNone,
     shakeRoot,
     defaultTest, hasTracker,
     copyDirectoryChanged, copyFileChangedIO,
     assertWithin,
     assertBool, assertBoolIO, assertException, assertExceptionAfter,
-    assertContents, assertContentsUnordered, assertContentsWords,
+    assertContents, assertContentsUnordered, assertContentsWords, assertContentsInfix,
     assertExists, assertMissing,
     (===),
     (&?%>),
@@ -58,6 +58,8 @@
 testSimple :: IO () -> IO () -> IO ()
 testSimple act = testBuild (const act) (return ())
 
+testNone :: IO () -> IO ()
+testNone _ = return ()
 
 shakenEx
     :: Bool
@@ -97,13 +99,13 @@
             del <- removeFilesRandom out
             threads <- randomRIO (1,4)
             putStrLn $ "## TESTING PERTURBATION (" ++ show del ++ " files, " ++ show threads ++ " threads)"
-            shake shakeOptions{shakeFiles=out, shakeThreads=threads, shakeVerbosity=Quiet} $ rules [] args
+            shake shakeOptions{shakeFiles=out, shakeThreads=threads, shakeVerbosity=Error} $ rules [] args
 
         args -> change $ do
             t <- tracker
             opts <- return shakeOptions{shakeFiles = "."}
             cwd <- getCurrentDirectory
-            opts <- return $ if forward then forwardOptions opts else opts
+            opts <- return $ if forward then forwardOptions opts{shakeLintInside=[""]} else opts
                 {shakeLint = Just t
                 ,shakeLintInside = [cwd </> ".." </> ".."]
                 ,shakeLintIgnore = [".cabal-sandbox/**",".stack-work/**","../../.stack-work/**"]}
@@ -121,7 +123,7 @@
                     if "clean" `elem` files then
                         clean >> return Nothing
                     else return $ Just $ (,) so $ do
-                        -- if you have passed sleep, supress the "no actions" warning
+                        -- if you have passed sleep, suppress the "no actions" warning
                         when (Sleep `elem` extra1) $ action $ return ()
                         rules extra2 files
 
@@ -190,6 +192,11 @@
 assertContents file want = do
     got <- IO.readFile' file
     assertBool (want == got) $ "File contents are wrong: " ++ file ++ "\nWANT: " ++ want ++ "\nGOT: " ++ got
+
+assertContentsInfix :: FilePath -> String -> IO ()
+assertContentsInfix file want = do
+    got <- IO.readFile' file
+    assertBool (want `isInfixOf` got) $ "File contents are wrong: " ++ file ++ "\nWANT (infix): " ++ want ++ "\nGOT: " ++ got
 
 assertContentsOn :: (String -> String) -> FilePath -> String -> IO ()
 assertContentsOn f file want = do
diff --git a/src/Test/Unicode.hs b/src/Test/Unicode.hs
--- a/src/Test/Unicode.hs
+++ b/src/Test/Unicode.hs
@@ -5,8 +5,8 @@
 import Development.Shake.FilePath
 import Test.Type
 import General.GetOpt
-import Control.Exception.Extra
 import Control.Monad
+import GHC.IO.Encoding
 
 
 -- | Decode a dull ASCII string to certain unicode points, necessary because
@@ -47,12 +47,14 @@
     -- IO.hSetEncoding IO.stdout IO.char8
     -- IO.hSetEncoding IO.stderr IO.char8
     forM_ ["normal","e^",":)","e^-:)"] $ \pre -> do
-        let ext x = decode pre <.> x
-        res <- try_ $ writeFile (ext "source") "x"
-        case res of
-            Left _ ->
-                putStrLn $ "WARNING: Failed to write file " ++ pre ++ ", skipping unicode test (LANG=C ?)"
-            Right _ -> do
+        -- If you aren't on UTF-8 file encoding it goes wrong, see
+        -- https://github.com/ndmitchell/shake/pull/681
+        enc <- liftIO getFileSystemEncoding
+        if textEncodingName enc /= "UTF-8"
+        then putStrLn "WARNING: filesystem encoding is not UTF-8, skipping unicode test (LANG=C ?)"
+        else do
+                let ext x = decode pre <.> x
+                writeFile (ext "source") "x"
                 build ["--prefix=" ++ pre, "--want=" ++ pre <.> "out", "--sleep"]
                 assertContents (ext "out") $ "x" ++ "False"
                 writeFile (ext "source") "y"
diff --git a/src/Test/Verbosity.hs b/src/Test/Verbosity.hs
--- a/src/Test/Verbosity.hs
+++ b/src/Test/Verbosity.hs
@@ -8,12 +8,12 @@
 main = testBuild test $ do
     "in.txt" %> \out -> do
         a <- getVerbosity
-        b <- withVerbosity Normal getVerbosity
+        b <- withVerbosity Info getVerbosity
         writeFile' out $ unwords $ map show [a,b]
 
     "out.txt" %> \out -> do
         x <- getVerbosity
-        ys <- withVerbosity Loud $ do
+        ys <- withVerbosity Verbose $ do
             a <- getVerbosity
             need ["in.txt"] -- make sure the inherited verbosity does not get passed along
             b <- getVerbosity
@@ -25,13 +25,13 @@
 
 test build = do
     build ["out.txt","--clean"]
-    assertContents "in.txt" "Normal Normal"
-    assertContents "out.txt" "Normal Loud Loud Quiet Normal Normal"
+    assertContents "in.txt" "Info Info"
+    assertContents "out.txt" "Info Verbose Verbose Error Info Info"
 
     build ["out.txt","--clean","--verbose"]
-    assertContents "in.txt" "Loud Normal"
-    assertContents "out.txt" "Loud Loud Loud Quiet Loud Loud"
+    assertContents "in.txt" "Verbose Info"
+    assertContents "out.txt" "Verbose Verbose Verbose Error Verbose Verbose"
 
     build ["out.txt","--clean","--quiet"]
-    assertContents "in.txt" "Quiet Normal"
-    assertContents "out.txt" "Quiet Loud Loud Quiet Quiet Quiet"
+    assertContents "in.txt" "Warn Info"
+    assertContents "out.txt" "Warn Verbose Verbose Error Warn Warn"
