diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,15 @@
 Changelog for Shake
 
+0.12
+    #62, move to a ReaderT/IORef for the Action monad
+    Add DEPRECATED pragmas on system' calls
+    Delete Development.Shake.Sys, use command or cmd instead
+    Add a 'console' pool to Ninja
+    Avoid using System.Cmd (deprecated in GHC HEAD)
+    #41, use higher precision file times on POSIX
+    #117, use higher precision times for Unicode files on Windows
+    #118, add support for Ninja -t compdb
+    #119, more test fixes for Linux GHC 7.8
 0.11.7
     #119, test fixes for Linux GHC 7.8
 0.11.6
diff --git a/Development/Make/All.hs b/Development/Make/All.hs
--- a/Development/Make/All.hs
+++ b/Development/Make/All.hs
@@ -14,7 +14,7 @@
 import Data.Maybe
 import Control.Arrow
 import Control.Monad
-import System.Cmd
+import System.Process
 import System.Exit
 import Control.Monad.Trans.State.Strict
 
@@ -85,15 +85,15 @@
                 forM_ cmd $ \c ->
                     case c of
                         Expr c -> (if silent then quietly else id) $
-                            runCommand =<< liftIO (askEnv env c)
+                            execCommand =<< liftIO (askEnv env c)
             return $ if phony then Phony else NotPhony
 
         has auto name target =
             or [(null ws && auto) || target `elem` ws | Ruler t (_,Lit s) _ <- rs, t == name, let ws = words s]
 
 
-runCommand :: String -> Action ()
-runCommand x = do
+execCommand :: String -> Action ()
+execCommand x = do
     res <- if "@" `isPrefixOf` x then sys $ drop 1 x
            else putNormal x >> sys x
     when (res /= ExitSuccess) $
diff --git a/Development/Ninja/All.hs b/Development/Ninja/All.hs
--- a/Development/Ninja/All.hs
+++ b/Development/Ninja/All.hs
@@ -23,17 +23,39 @@
 import Data.Char
 
 
-runNinja :: FilePath -> [String] -> IO (Rules ())
-runNinja file args = do
+runNinja :: FilePath -> [String] -> Maybe String -> IO (Maybe (Rules ()))
+runNinja file args (Just "compdb") = do
+    dir <- getCurrentDirectory
+    Ninja{..} <- parse file =<< newEnv
+    rules <- return $ Map.fromList [r | r <- rules, BS.unpack (fst r) `elem` args]
+    -- the build items are generated in reverse order, hence the reverse
+    let xs = [(a,b,file,rule) | (a,b@Build{..}) <- reverse $ multiples ++ map (first return) singles
+                              , Just rule <- [Map.lookup ruleName rules], file:_ <- [depsNormal]]
+    xs <- forM xs $ \(out,Build{..},file,Rule{..}) -> do
+        -- the order of adding new environment variables matters
+        env <- scopeEnv env
+        addEnv env (BS.pack "out") (BS.unwords $ map quote out)
+        addEnv env (BS.pack "in") (BS.unwords $ map quote depsNormal)
+        addEnv env (BS.pack "in_newline") (BS.unlines depsNormal)
+        addBinds env buildBind
+        addBinds env ruleBind
+        commandline <- fmap BS.unpack $ askVar env $ BS.pack "command"
+        return $ CompDb dir commandline $ BS.unpack $ head depsNormal
+    putStr $ printCompDb xs
+    return Nothing
+
+runNinja file args (Just x) = error $ "Unknown tool argument, expected 'compdb', got " ++ x
+
+runNinja file args tool = do
     addTiming "Ninja parse"
     ninja@Ninja{..} <- parse file =<< newEnv
-    return $ do
+    return $ Just $ do
         needDeps <- return $ needDeps ninja -- partial application
         phonys <- return $ Map.fromList phonys
         singles <- return $ Map.fromList $ map (first normalise) singles
         multiples <- return $ Map.fromList [(x,(xs,b)) | (xs,b) <- map (first $ map normalise) multiples, x <- xs]
         rules <- return $ Map.fromList rules
-        pools <- fmap Map.fromList $ forM pools $ \(name,depth) ->
+        pools <- fmap Map.fromList $ forM ((BS.pack "console",1):pools) $ \(name,depth) ->
             fmap ((,) name) $ newResource (BS.unpack name) depth
 
         action $ needBS $ map normalise $ concatMap (resolvePhony phonys) $
@@ -172,3 +194,23 @@
 bsNote = BS.pack "Note: including file:"
 bsProgFiles = BS.pack "PROGRAM FILES"
 bsVisStudio = BS.pack "MICROSOFT VISUAL STUDIO"
+
+
+data CompDb = CompDb
+    {cdbDirectory :: String
+    ,cdbCommand :: String
+    ,cdbFile :: String
+    }
+    deriving Show
+
+printCompDb :: [CompDb] -> String
+printCompDb xs = unlines $ ["["] ++ concat (zipWith f [1..] xs) ++ ["]"]
+    where
+        n = length xs
+        f i CompDb{..} =
+            ["  {"
+            ,"    \"directory\": " ++ g cdbDirectory ++ ","
+            ,"    \"command\": " ++ g cdbCommand ++ ","
+            ,"    \"file\": " ++ g cdbFile
+            ,"  }" ++ (if i == n then "" else ",")]
+        g = show
diff --git a/Development/Shake/Args.hs b/Development/Shake/Args.hs
--- a/Development/Shake/Args.hs
+++ b/Development/Shake/Args.hs
@@ -239,7 +239,7 @@
     ,yes $ Option "j" ["jobs"] (intArg 0 "jobs" "N" $ \i s -> s{shakeThreads=i}) "Allow N jobs/threads at once."
     ,yes $ Option "k" ["keep-going"] (noArg $ \s -> s{shakeStaunch=True}) "Keep going when some targets can't be made."
     ,yes $ Option "l" ["lint"] (noArg $ \s -> s{shakeLint=Just LintBasic}) "Perform limited validation after the run."
-    ,yes $ Option "t" ["lint-tracker"] (noArg $ \s -> s{shakeLint=Just LintTracker}) "Use tracker.exe to do validation."
+    ,yes $ Option ""  ["lint-tracker"] (noArg $ \s -> s{shakeLint=Just LintTracker}) "Use tracker.exe to do validation."
     ,yes $ Option ""  ["no-lint"] (noArg $ \s -> s{shakeLint=Nothing}) "Turn off --lint."
     ,yes $ Option "m" ["metadata"] (reqArg "PREFIX" $ \x s -> s{shakeFiles=x}) "Prefix for storing metadata files."
     ,no  $ Option "o" ["old-file","assume-old"] (ReqArg (\x -> Right ([AssumeOld x],id)) "FILE") "Consider FILE to be very old and don't remake it."
@@ -258,7 +258,7 @@
     ,yes $ Option "q" ["quiet"] (noArg $ \s -> s{shakeVerbosity=move (shakeVerbosity s) pred}) "Don't print much."
     ,no  $ Option ""  ["no-time"] (NoArg $ Right ([NoTime],id)) "Don't print build time."
     ,yes $ Option ""  ["timings"] (noArg $ \s -> s{shakeTimings=True}) "Print phase timings."
-    ,yes $ Option "t" ["touch"] (noArg $ \s -> s{shakeAssume=Just AssumeClean}) "Assume targets are clean."
+    ,yes $ Option ""  ["touch"] (noArg $ \s -> s{shakeAssume=Just AssumeClean}) "Assume targets are clean."
     ,yes $ Option "V" ["verbose","trace"] (noArg $ \s -> s{shakeVerbosity=move (shakeVerbosity s) succ}) "Print tracing information."
     ,no  $ Option "v" ["version"] (NoArg $ Right ([Version],id)) "Print the version number and exit."
     ,no  $ Option "w" ["print-directory"] (NoArg $ Right ([PrintDirectory True],id)) "Print the current directory."
diff --git a/Development/Shake/Core.hs b/Development/Shake/Core.hs
--- a/Development/Shake/Core.hs
+++ b/Development/Shake/Core.hs
@@ -28,7 +28,6 @@
 import Control.Monad
 import Control.Monad.IO.Class
 import Control.Monad.Trans.Writer.Strict
-import Control.Monad.Trans.State.Strict as State
 import Data.Typeable
 import Data.Function
 import Data.List
@@ -50,6 +49,7 @@
 import General.Timing
 import General.Base
 import General.String
+import General.RAW
 
 
 ---------------------------------------------------------------------
@@ -86,40 +86,40 @@
     storedValue :: ShakeOptions -> key -> IO (Maybe value)
 
 
-data ARule = forall key value . Rule key value => ARule (key -> Maybe (Action value))
+data ARule m = forall key value . Rule key value => ARule (key -> Maybe (m value))
 
-ruleKey :: Rule key value => (key -> Maybe (Action value)) -> key
+ruleKey :: Rule key value => (key -> Maybe (m value)) -> key
 ruleKey = err "ruleKey"
 
-ruleValue :: Rule key value => (key -> Maybe (Action value)) -> value
+ruleValue :: Rule key value => (key -> Maybe (m value)) -> value
 ruleValue = err "ruleValue"
 
 
 -- | 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 (WriterT SRules IO a) -- All IO must be associative/commutative (e.g. creating IORef/MVars)
+newtype Rules a = Rules (WriterT (SRules Action) IO a) -- All IO must be associative/commutative (e.g. creating IORef/MVars)
     deriving (Monad, Functor, Applicative)
 
 rulesIO :: IO a -> Rules a
 rulesIO = Rules . liftIO
 
-newRules :: SRules -> Rules ()
+newRules :: SRules Action -> Rules ()
 newRules = Rules . tell
 
-modifyRules :: (SRules -> SRules) -> Rules () -> Rules ()
+modifyRules :: (SRules Action -> SRules Action) -> Rules () -> Rules ()
 modifyRules f (Rules r) = Rules $ censor f r
 
-getRules :: Rules () -> IO SRules
+getRules :: Rules () -> IO (SRules Action)
 getRules (Rules r) = execWriterT r
 
 
-data SRules = SRules
-    {actions :: [Action ()]
-    ,rules :: Map.HashMap TypeRep{-k-} (TypeRep{-k-},TypeRep{-v-},[(Int,ARule)]) -- higher fst is higher priority
+data SRules m = SRules
+    {actions :: [m ()]
+    ,rules :: Map.HashMap TypeRep{-k-} (TypeRep{-k-},TypeRep{-v-},[(Int,ARule m)]) -- higher fst is higher priority
     }
 
-instance Monoid SRules where
+instance Monoid (SRules m) where
     mempty = SRules [] (Map.fromList [])
     mappend (SRules x1 x2) (SRules y1 y2) = SRules (x1++y1) (Map.unionWith f x2 y2)
         where f (k, v1, xs) (_, v2, ys)
@@ -128,7 +128,7 @@
 
 instance Monoid a => Monoid (Rules a) where
     mempty = return mempty
-    mappend a b = do a <- a; b <- b; return $ mappend a b
+    mappend = liftA2 mappend
 
 
 -- | Like 'rule', but lower priority, if no 'rule' exists then 'defaultRule' is checked.
@@ -168,101 +168,6 @@
             where b2 = fmap (fmap (fromJust . cast)) . b . fromJust . cast
 
 
--- | Track that a key has been used by the action preceeding it.
-trackUse ::
-#if __GLASGOW_HASKELL__ >= 704
-    ShakeValue key
-#else
-    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
-#endif
-    => key -> Action ()
--- One of the following must be true:
--- 1) you are the one building this key (e.g. key == topStack)
--- 2) you have already been used by apply, and are on the dependency list
--- 3) someone explicitly gave you permission with trackAllow
--- 4) at the end of the rule, a) you are now on the dependency list, and b) this key itself has no dependencies (is source file)
-trackUse key = do
-    let k = newKey key
-    s <- Action State.get
-    deps <- liftIO $ concatMapM (listDepends $ database s) (depends s)
-    let top = topStack $ stack s
-    if top == Just k then
-        return () -- condition 1
-     else if k `elem` deps then
-        return () -- condition 2
-     else if any ($ k) $ trackAllows s then
-        return () -- condition 3
-     else
-        Action $ State.modify $ \s -> s{trackUsed = k : trackUsed s} -- condition 4
-
-
-trackCheckUsed :: Action ()
-trackCheckUsed = do
-    s <- Action State.get
-    deps <- liftIO $ concatMapM (listDepends $ database s) (depends s)
-
-    -- check 3a
-    bad <- return $ trackUsed s \\ deps
-    unless (null bad) $ do
-        let n = length bad
-        errorStructured
-            ("Link checking error - " ++ (if n == 1 then "value was" else show n ++ " values were") ++ " used but not depended upon")
-            [("Used", Just $ show x) | x <- bad]
-            ""
-
-    -- check 3b
-    bad <- liftIO $ flip filterM (trackUsed s) $ \k -> fmap (not . null) $ lookupDependencies (database s) k
-    unless (null bad) $ do
-        let n = length bad
-        errorStructured
-            ("Link checking error - " ++ (if n == 1 then "value was" else show n ++ " values were") ++ " depended upon after being used")
-            [("Used", Just $ show x) | x <- bad]
-            ""
-
-
--- | Track that a key has been changed by the action preceeding it.
-trackChange ::
-#if __GLASGOW_HASKELL__ >= 704
-    ShakeValue key
-#else
-    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
-#endif
-    => key -> Action ()
--- One of the following must be true:
--- 1) you are the one building this key (e.g. key == topStack)
--- 2) someone explicitly gave you permission with trackAllow
--- 3) this file is never known to the build system, at the end it is not in the database
-trackChange key = do
-    let k = newKey key
-    s <- Action State.get
-    let top = topStack $ stack s
-    if top == Just k then
-        return () -- condition 1
-     else if any ($ k) $ trackAllows s then
-        return () -- condition 2
-     else
-        -- condition 3
-        liftIO $ atomicModifyIORef (trackAbsent s) $ \ks -> ((fromMaybe k top, k):ks, ())
-
-
--- | Allow any matching key to violate the tracking rules.
-trackAllow ::
-#if __GLASGOW_HASKELL__ >= 704
-    ShakeValue key
-#else
-    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
-#endif
-    => (key -> Bool) -> Action ()
-trackAllow test = Action $ State.modify $ \s -> s{trackAllows = f : trackAllows s}
-    where
-        -- We don't want the forall in the Haddock docs
-        arrow1Type :: forall a b . Typeable a => (a -> b) -> TypeRep
-        arrow1Type _ = typeOf (err "trackAllow" :: a)
-
-        ty = arrow1Type test
-        f k = typeKey k == ty && test (fromKey k)
-
-
 -- | Run an action, usually used for specifying top-level requirements.
 --
 -- @
@@ -288,52 +193,88 @@
 withoutActions = modifyRules $ \x -> x{actions=[]}
 
 
----------------------------------------------------------------------
--- MAKE
+registerWitnesses :: SRules m -> IO ()
+registerWitnesses SRules{..} =
+    forM_ (Map.elems rules) $ \(_, _, (_,ARule r):_) -> do
+        registerWitness $ ruleKey r
+        registerWitness $ ruleValue r
 
-data RuleInfo = RuleInfo
+
+data RuleInfo m = RuleInfo
     {stored :: Key -> IO (Maybe Value)
-    ,execute :: Key -> Action Value
+    ,execute :: Key -> m Value
     ,resultType :: TypeRep
     }
 
-data SAction = SAction
-    -- global constants
-    {database :: Database
-    ,pool :: Pool
-    ,timestamp :: IO Time
-    ,ruleinfo :: Map.HashMap TypeRep RuleInfo
-    ,output :: Verbosity -> String -> IO ()
-    ,opts :: ShakeOptions
-    ,diagnostic :: String -> IO ()
-    ,lint :: String -> IO ()
-    ,after :: IORef [IO ()]
-    ,trackAbsent :: IORef [(Key, Key)] -- in rule fst, snd must be absent
-    -- stack variables
-    ,stack :: Stack
-    -- local variables
-    ,verbosity :: Verbosity
-    ,depends :: [Depends] -- built up in reverse
-    ,discount :: !Duration
-    ,traces :: [Trace] -- in reverse
-    ,blockapply ::  Maybe String -- reason to block apply, or Nothing to allow
-    ,trackAllows :: [Key -> Bool]
-    ,trackUsed :: [Key]
+createRuleinfo :: ShakeOptions -> SRules Action -> Map.HashMap TypeRep (RuleInfo Action)
+createRuleinfo opt SRules{..} = flip Map.map rules $ \(_,tv,rs) -> RuleInfo (stored rs) (execute rs) tv
+    where
+        stored ((_,ARule r):_) = fmap (fmap newValue) . f r . fromKey
+            where f :: Rule key value => (key -> Maybe (m value)) -> (key -> IO (Maybe value))
+                  f _ = storedValue opt
+
+        execute rs = \k -> case filter (not . null) $ map (mapMaybe ($ k)) rs2 of
+               [r]:_ -> r
+               rs -> errorMultipleRulesMatch (typeKey k) (show k) (length rs)
+            where rs2 = sets [(i, \k -> fmap (fmap newValue) $ r (fromKey k)) | (i,ARule r) <- rs] 
+
+        sets :: Ord a => [(a, b)] -> [[b]] -- highest to lowest
+        sets = map (map snd) . reverse . groupBy ((==) `on` fst) . sortBy (compare `on` fst)
+
+runStored :: Map.HashMap TypeRep (RuleInfo m) -> Key -> IO (Maybe Value)
+runStored mp k = case Map.lookup (typeKey k) mp of
+    Nothing -> return Nothing
+    Just RuleInfo{..} -> stored k
+
+runExecute :: Map.HashMap TypeRep (RuleInfo m) -> Key -> m Value
+runExecute mp k = let tk = typeKey k in case Map.lookup tk mp of
+    Nothing -> errorNoRuleToBuildType tk (Just $ show k) Nothing -- Not sure if this is even possible, but best be safe
+    Just RuleInfo{..} -> execute k
+
+
+---------------------------------------------------------------------
+-- MAKE
+
+-- global constants of Action
+data Global = Global
+    {globalDatabase :: Database
+    ,globalPool :: Pool
+    ,globalTimestamp :: IO Time
+    ,globalRules :: Map.HashMap TypeRep (RuleInfo Action)
+    ,globalOutput :: Verbosity -> String -> IO ()
+    ,globalOptions  :: ShakeOptions
+    ,globalDiagnostic :: String -> IO ()
+    ,globalLint :: String -> IO ()
+    ,globalAfter :: IORef [IO ()]
+    ,globalTrackAbsent :: IORef [(Key, Key)] -- in rule fst, snd must be absent
     }
 
+
+-- local variables of Action
+data Local = Local
+    -- constants
+    {localStack :: Stack
+    -- stack scoped local variables
+    ,localVerbosity :: Verbosity
+    ,localBlockApply ::  Maybe String -- reason to block apply, or Nothing to allow
+    -- mutable local variables
+    ,localDepends :: [Depends] -- built up in reverse
+    ,localDiscount :: !Duration
+    ,localTraces :: [Trace] -- in reverse
+    ,localTrackAllows :: [Key -> Bool]
+    ,localTrackUsed :: [Key]
+    }
+
 -- | The 'Action' monad, use 'liftIO' to raise 'IO' actions into it, and 'Development.Shake.need' to execute files.
 --   Action values are used by 'rule' and 'action'. The 'Action' monad tracks the dependencies of a 'Rule'.
-newtype Action a = Action (StateT SAction IO a)
-    deriving (Monad, MonadIO, Functor, Applicative)
+newtype Action a = Action {fromAction :: RAW Global Local a}
+    deriving (Functor, Applicative, Monad, MonadIO)
 
 
 -- | If an exception is raised by the 'Action', perform some 'IO'.
 actionOnException :: Action a -> IO b -> Action a
-actionOnException act clean = do
-    s <- Action State.get
-    (res,s) <- liftIO $ onException (runAction s act) clean
-    Action $ State.put s
-    return res
+actionOnException act clean = Action $
+    catchRAW (fromAction act) (\(e :: SomeException) -> liftIO clean >> throwRAW e)
 
 
 -- | After an 'Action', perform some 'IO', even if there is an exception.
@@ -400,8 +341,9 @@
                 let ruleinfo = createRuleinfo opts rs
                 addTiming "Running rules"
                 runPool (shakeThreads == 1) shakeThreads $ \pool -> do
-                    let s0 = SAction database pool start ruleinfo output opts diagnostic lint after absent emptyStack shakeVerbosity [] 0 [] Nothing [] []
-                    mapM_ (addPool pool . staunch . runAction s0) (actions rs)
+                    let s0 = Global database pool start ruleinfo output opts diagnostic lint after absent
+                    let s1 = Local emptyStack shakeVerbosity Nothing [] 0 [] [] []
+                    mapM_ (addPool pool . staunch . runAction s0 s1) (actions rs)
 
                 when (isJust shakeLint) $ do
                     addTiming "Lint checking"
@@ -419,22 +361,8 @@
             sequence_ . reverse =<< readIORef after
 
 
-withCapabilities :: Int -> IO a -> IO a
-#if __GLASGOW_HASKELL__ >= 706
-withCapabilities new act | rtsSupportsBoundThreads = do
-    old <- getNumCapabilities
-    if old == new then act else
-        bracket_ (setNumCapabilities new) (setNumCapabilities old) act
-#endif
-withCapabilities new act = act
-
 lineBuffering :: IO a -> IO a
-lineBuffering = f stdout . f stderr
-    where
-        f h act = do
-            bracket (hGetBuffering h) (hSetBuffering h) $ const $ do
-                hSetBuffering h LineBuffering
-                act
+lineBuffering = withBufferMode stdout LineBuffering . withBufferMode stderr LineBuffering
 
 
 abbreviate :: [(String,String)] -> String -> String
@@ -458,64 +386,31 @@
          else throwIO $ ShakeException (last stk) stk $ SomeException e
 
 
-registerWitnesses :: SRules -> IO ()
-registerWitnesses SRules{..} =
-    forM_ (Map.elems rules) $ \(_, _, (_,ARule r):_) -> do
-        registerWitness $ ruleKey r
-        registerWitness $ ruleValue r
-
-
-createRuleinfo :: ShakeOptions -> SRules -> Map.HashMap TypeRep RuleInfo
-createRuleinfo opt SRules{..} = flip Map.map rules $ \(_,tv,rs) -> RuleInfo (stored rs) (execute rs) tv
-    where
-        stored ((_,ARule r):_) = fmap (fmap newValue) . f r . fromKey
-            where f :: Rule key value => (key -> Maybe (Action value)) -> (key -> IO (Maybe value))
-                  f _ = storedValue opt
-
-        execute rs = \k -> case filter (not . null) $ map (mapMaybe ($ k)) rs2 of
-               [r]:_ -> r
-               rs -> errorMultipleRulesMatch (typeKey k) (show k) (length rs)
-            where rs2 = sets [(i, \k -> fmap (fmap newValue) $ r (fromKey k)) | (i,ARule r) <- rs] 
-
-        sets :: Ord a => [(a, b)] -> [[b]] -- highest to lowest
-        sets = map (map snd) . reverse . groupBy ((==) `on` fst) . sortBy (compare `on` fst)
-
-runStored :: Map.HashMap TypeRep RuleInfo -> Key -> IO (Maybe Value)
-runStored mp k = case Map.lookup (typeKey k) mp of
-    Nothing -> return Nothing
-    Just RuleInfo{..} -> stored k
-
-runExecute :: Map.HashMap TypeRep RuleInfo -> Key -> Action Value
-runExecute mp k = let tk = typeKey k in case Map.lookup tk mp of
-    Nothing -> errorNoRuleToBuildType tk (Just $ show k) Nothing -- Not sure if this is even possible, but best be safe
-    Just RuleInfo{..} -> execute k
-
-
-runAction :: SAction -> Action a -> IO (a, SAction)
-runAction s (Action x) = runStateT x s
+runAction :: Global -> Local -> Action a -> IO a
+runAction g l (Action x) = runRAW g l x
 
 
 runAfter :: IO () -> Action ()
 runAfter op = do
-    s <- Action State.get
-    liftIO $ atomicModifyIORef (after s) $ \ops -> (op:ops, ())
+    Global{..} <- Action getRO
+    liftIO $ atomicModifyIORef globalAfter $ \ops -> (op:ops, ())
 
 
 -- | Execute a rule, returning the associated values. If possible, the rules will be run in parallel.
 --   This function requires that appropriate rules have been added with 'rule' or 'defaultRule'.
 --   All @key@ values passed to 'apply' become dependencies of the 'Action'.
 apply :: Rule key value => [key] -> Action [value]
-apply = f -- Don't short-circuit as we still want error messages
+apply = f -- Don't short-circuit [] as we still want error messages
     where
         -- We don't want the forall in the Haddock docs
         f :: forall key value . Rule key value => [key] -> Action [value]
         f ks = do
             let tk = typeOf (err "apply key" :: key)
                 tv = typeOf (err "apply type" :: value)
-            ruleinfo <- Action $ State.gets ruleinfo
-            block <- Action $ State.gets blockapply
+            Global{..} <- Action getRO
+            block <- Action $ getsRW localBlockApply
             whenJust block $ errorNoApply tk (fmap show $ listToMaybe ks)
-            case Map.lookup tk ruleinfo of
+            case Map.lookup tk globalRules of
                 Nothing -> errorNoRuleToBuildType tk (fmap show $ listToMaybe ks) (Just tv)
                 Just RuleInfo{resultType=tv2} | tv /= tv2 -> errorRuleTypeMismatch tk (fmap show $ listToMaybe ks) tv2 tv
                 _ -> fmap (map fromValue) $ applyKeyValue $ map newKey ks
@@ -524,27 +419,29 @@
 applyKeyValue :: [Key] -> Action [Value]
 applyKeyValue [] = return []
 applyKeyValue ks = do
-    s <- Action State.get
-    let exec stack k = try $ wrapStack (showStack (database s) stack) $ do
+    global@Global{..} <- Action getRO
+    let exec stack k = try $ wrapStack (showStack globalDatabase stack) $ do
             evaluate $ rnf k
-            let s2 = s{verbosity=shakeVerbosity $ opts s, depends=[], stack=stack, discount=0, traces=[], trackAllows=[], trackUsed=[]}
+            let s = Local {localVerbosity=shakeVerbosity globalOptions, localDepends=[], localStack=stack, localBlockApply=Nothing
+                          ,localDiscount=0, localTraces=[], localTrackAllows=[], localTrackUsed=[]}
             let top = showTopStack stack
-            lint s $ "before building " ++ top
-            (dur,(res,s2)) <- duration $ runAction s2 $ do
+            globalLint $ "before building " ++ top
+            (dur,(res,Local{..})) <- duration $ runAction global s $ do
                 putWhen Chatty $ "# " ++ show k
-                res <- runExecute (ruleinfo s) k
-                when (shakeLint (opts s) == Just LintTracker)
+                res <- runExecute globalRules k
+                when (shakeLint globalOptions == Just LintTracker)
                     trackCheckUsed
-                return res
-            lint s $ "after building " ++ top
-            let ans = (res, reverse $ depends s2, dur - discount s2, reverse $ traces s2)
+                Action $ fmap ((,) res) getRW
+            globalLint $ "after building " ++ top
+            let ans = (res, reverse localDepends, dur - localDiscount, reverse localTraces)
             evaluate $ rnf ans
             return ans
-    res <- liftIO $ build (pool s) (database s) (Ops (runStored (ruleinfo s)) exec) (stack s) ks
+    stack <- Action $ getsRW localStack
+    res <- liftIO $ build globalPool globalDatabase (Ops (runStored globalRules) exec) stack ks
     case res of
         Left err -> throw err
         Right (dur, dep, vs) -> do
-            Action $ State.modify $ \s -> s{discount=discount s + dur, depends=dep : depends s}
+            Action $ modifyRW $ \s -> s{localDiscount=localDiscount s + dur, localDepends=dep : localDepends s}
             return vs
 
 
@@ -556,7 +453,7 @@
 
 -- | Get the initial 'ShakeOptions', these will not change during the build process.
 getShakeOptions :: Action ShakeOptions
-getShakeOptions = Action $ gets opts
+getShakeOptions = Action $ getsRO globalOptions
 
 
 -- | Write an action to the trace list, along with the start/end time of running the IO action.
@@ -564,20 +461,22 @@
 --   The trace list is used for profile reports (see 'shakeReport').
 traced :: String -> IO a -> Action a
 traced msg act = do
-    s <- Action State.get
-    start <- liftIO $ timestamp s
-    putNormal $ "# " ++ msg ++ " " ++ showTopStack (stack s)
+    Global{..} <- Action getRO
+    stack <- Action $ getsRW localStack
+    start <- liftIO globalTimestamp
+    putNormal $ "# " ++ msg ++ " " ++ showTopStack stack
     res <- liftIO act
-    stop <- liftIO $ timestamp s
-    Action $ State.modify $ \s -> s{traces = Trace (pack msg) start stop : traces s}
+    stop <- liftIO globalTimestamp
+    Action $ modifyRW $ \s -> s{localTraces = Trace (pack msg) start stop : localTraces s}
     return res
 
 
 putWhen :: Verbosity -> String -> Action ()
 putWhen v msg = do
-    s <- Action State.get
-    when (verbosity s >= v) $
-        liftIO $ output s v msg
+    Global{..} <- Action getRO
+    verb <- getVerbosity
+    when (verb >= v) $
+        liftIO $ globalOutput v msg
 
 
 -- | Write a message to the output when the verbosity ('shakeVerbosity') is appropriate.
@@ -594,19 +493,15 @@
 --   'putLoud' \/ 'putNormal' \/ 'putQuiet', which ensures multiple messages are
 --   not interleaved. The verbosity can be modified locally by 'withVerbosity'.
 getVerbosity :: Action Verbosity
-getVerbosity = Action $ gets verbosity
+getVerbosity = Action $ getsRW localVerbosity
 
 
 -- | Run an action with a particular verbosity level.
 --   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 act = do
-    old <- Action $ State.gets verbosity
-    Action $ State.modify $ \s -> s{verbosity=new}
-    res <- act
-    Action $ State.modify $ \s -> s{verbosity=old}
-    return res
+withVerbosity new = Action . unmodifyRW f . fromAction
+    where f s0 = (s0{localVerbosity=new}, \s -> s{localVerbosity=localVerbosity s0})
 
 
 -- | Run an action with 'Quiet' verbosity, in particular messages produced by 'traced'
@@ -617,6 +512,112 @@
 quietly = withVerbosity Quiet
 
 
+---------------------------------------------------------------------
+-- TRACKING
+
+-- | Track that a key has been used by the action preceeding it.
+trackUse ::
+#if __GLASGOW_HASKELL__ >= 704
+    ShakeValue key
+#else
+    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
+#endif
+    => key -> Action ()
+-- One of the following must be true:
+-- 1) you are the one building this key (e.g. key == topStack)
+-- 2) you have already been used by apply, and are on the dependency list
+-- 3) someone explicitly gave you permission with trackAllow
+-- 4) at the end of the rule, a) you are now on the dependency list, and b) this key itself has no dependencies (is source file)
+trackUse key = do
+    let k = newKey key
+    Global{..} <- Action getRO
+    l@Local{..} <- Action getRW
+    deps <- liftIO $ concatMapM (listDepends globalDatabase) localDepends
+    let top = topStack localStack
+    if top == Just k then
+        return () -- condition 1
+     else if k `elem` deps then
+        return () -- condition 2
+     else if any ($ k) localTrackAllows then
+        return () -- condition 3
+     else
+        Action $ putRW l{localTrackUsed = k : localTrackUsed} -- condition 4
+
+
+trackCheckUsed :: Action ()
+trackCheckUsed = do
+    Global{..} <- Action getRO
+    Local{..} <- Action getRW
+    liftIO $ do
+        deps <- concatMapM (listDepends globalDatabase) localDepends
+
+        -- check 3a
+        bad <- return $ localTrackUsed \\ deps
+        unless (null bad) $ do
+            let n = length bad
+            errorStructured
+                ("Link checking error - " ++ (if n == 1 then "value was" else show n ++ " values were") ++ " used but not depended upon")
+                [("Used", Just $ show x) | x <- bad]
+                ""
+
+        -- check 3b
+        bad <- flip filterM localTrackUsed $ \k -> fmap (not . null) $ lookupDependencies globalDatabase k
+        unless (null bad) $ do
+            let n = length bad
+            errorStructured
+                ("Link checking error - " ++ (if n == 1 then "value was" else show n ++ " values were") ++ " depended upon after being used")
+                [("Used", Just $ show x) | x <- bad]
+                ""
+
+
+-- | Track that a key has been changed by the action preceeding it.
+trackChange ::
+#if __GLASGOW_HASKELL__ >= 704
+    ShakeValue key
+#else
+    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
+#endif
+    => key -> Action ()
+-- One of the following must be true:
+-- 1) you are the one building this key (e.g. key == topStack)
+-- 2) someone explicitly gave you permission with trackAllow
+-- 3) this file is never known to the build system, at the end it is not in the database
+trackChange key = do
+    let k = newKey key
+    Global{..} <- Action getRO
+    Local{..} <- Action getRW
+    liftIO $ do
+        let top = topStack localStack
+        if top == Just k then
+            return () -- condition 1
+         else if any ($ k) localTrackAllows then
+            return () -- condition 2
+         else
+            -- condition 3
+            atomicModifyIORef globalTrackAbsent $ \ks -> ((fromMaybe k top, k):ks, ())
+
+
+-- | Allow any matching key to violate the tracking rules.
+trackAllow ::
+#if __GLASGOW_HASKELL__ >= 704
+    ShakeValue key
+#else
+    (Show key, Typeable key, Eq key, Hashable key, Binary key, NFData key)
+#endif
+    => (key -> Bool) -> Action ()
+trackAllow test = Action $ modifyRW $ \s -> s{localTrackAllows = f : localTrackAllows s}
+    where
+        -- We don't want the forall in the Haddock docs
+        arrow1Type :: forall a b . Typeable a => (a -> b) -> TypeRep
+        arrow1Type _ = typeOf (err "trackAllow" :: a)
+
+        ty = arrow1Type test
+        f k = typeKey k == ty && test (fromKey k)
+
+
+---------------------------------------------------------------------
+-- RESOURCES
+
 -- | Create a finite resource, given a name (for error messages) and a quantity of the resource that exists.
 --   Shake will ensure that actions using the same finite resource do not execute in parallel.
 --   As an example, only one set of calls to the Excel API can occur at one time, therefore
@@ -684,32 +685,27 @@
 
 
 blockApply :: String -> Action a -> Action a
-blockApply msg act = do
-    s0 <- Action State.get
-    Action $ State.put s0{blockapply=Just msg}
-    res <- act
-    Action $ State.modify $ \s -> s{blockapply=blockapply s0}
-    return res
+blockApply msg = Action . unmodifyRW f . fromAction
+    where f s0 = (s0{localBlockApply=Just msg}, \s -> s{localBlockApply=localBlockApply s0})
 
 
 -- | Run an action which uses part of a finite resource. For more details see 'Resource'.
 --   You cannot depend on a rule (e.g. 'need') while a resource is held.
 withResource :: Resource -> Int -> Action a -> Action a
-withResource r i act = do
-    s <- Action State.get
-    (res,s) <- liftIO $ bracket_
+withResource r i act = Action $ do
+    Global{..} <- getRO
+    act <- evalRAW $ fromAction $ blockApply ("Within withResource using " ++ show r) act
+    join $ liftIO $ bracket_
         (do res <- acquireResource r i
             case res of
-                Nothing -> diagnostic s $ show r ++ " acquired " ++ show i ++ " with no wait"
+                Nothing -> globalDiagnostic $ show r ++ " acquired " ++ show i ++ " with no wait"
                 Just wait -> do
-                    diagnostic s $ show r ++ " waiting to acquire " ++ show i
-                    blockPool (pool s) $ fmap ((,) False) wait
-                    diagnostic s $ show r ++ " acquired " ++ show i ++ " after waiting")
+                    globalDiagnostic $ show r ++ " waiting to acquire " ++ show i
+                    blockPool globalPool $ fmap ((,) False) wait
+                    globalDiagnostic $ show r ++ " acquired " ++ show i ++ " after waiting")
         (do releaseResource r i
-            diagnostic s $ show r ++ " released " ++ show i)
-        (runAction s $ blockApply ("Within withResource using " ++ show r) act)
-    Action $ State.put s
-    return res
+            globalDiagnostic $ show r ++ " released " ++ show i)
+        act
 
 
 -- | Run an action which uses part of several finite resources. Acquires the resources in a stable
@@ -734,31 +730,28 @@
             Just bar -> return $ (,) mp $ do
                 res <- liftIO $ waitBarrierMaybe bar
                 res <- case res of
-                    Nothing -> do pool <- Action $ gets pool; liftIO $ blockPool pool $ fmap ((,) False) $ waitBarrier bar
+                    Nothing -> do pool <- Action $ getsRO globalPool; liftIO $ blockPool pool $ fmap ((,) False) $ waitBarrier bar
                     Just res -> return res
                 case res of
-                    Left err -> liftIO $ throwIO err
+                    Left err -> Action $ throwRAW err
                     Right (deps,v) -> do
-                        Action $ modify $ \s -> s{depends = deps ++ depends s}
+                        Action $ modifyRW $ \s -> s{localDepends = deps ++ localDepends s}
                         return v
             Nothing -> do
                 bar <- newBarrier
                 return $ (,) (Map.insert key bar mp) $ do
-                    s <- Action State.get
-                    let pre = depends s
-                    res <- liftIO $ try $ runAction s $ act key
+                    pre <- Action $ getsRW localDepends
+                    res <- Action $ tryRAW $ fromAction $ act key
                     case res of
-                        Left err -> liftIO $ do
-                            signalBarrier bar $ Left (err :: SomeException)
-                            throwIO err
-                        Right (v,s) -> do
-                            Action $ State.put s
-                            let post = depends s
+                        Left err -> do
+                            liftIO $ signalBarrier bar $ Left (err :: SomeException)
+                            Action $ throwRAW err
+                        Right v -> do
+                            post <- Action $ getsRW localDepends
                             let deps = take (length post - length pre) post
                             liftIO $ signalBarrier bar (Right (deps, v))
                             return v
 
-
 -- | Given an action on a key, produce a cached version that will execute the action at most once per key.
 --   Using the cached result will still result include any dependencies that the action requires.
 --   Each call to 'newCache' creates a separate cache that is independent of all other calls to 'newCache'.
@@ -787,8 +780,7 @@
 --   on remote machines using barely any local CPU resources. Unsafe as it allows the 'shakeThreads' limit to be exceeded.
 --   You cannot depend on a rule (e.g. 'need') while the extra thread is executing.
 unsafeExtraThread :: Action a -> Action a
-unsafeExtraThread act = do
-    s <- Action State.get
-    (res,s) <- liftIO $ blockPool (pool s) $ fmap ((,) False) $ runAction s act
-    Action $ State.put s
-    return res
+unsafeExtraThread act = Action $ do
+    Global{..} <- getRO
+    act <- evalRAW $ fromAction act
+    join $ liftIO $ blockPool globalPool $ fmap ((,) False) act
diff --git a/Development/Shake/Derived.hs b/Development/Shake/Derived.hs
--- a/Development/Shake/Derived.hs
+++ b/Development/Shake/Derived.hs
@@ -23,6 +23,10 @@
 checkExitCode cmd ExitSuccess = return ()
 checkExitCode cmd (ExitFailure i) = error $ "System command failed (code " ++ show i ++ "):\n" ++ cmd
 
+{-# DEPRECATED system' "Use 'command' or 'cmd'" #-}
+{-# DEPRECATED systemCwd "Use 'command' or 'cmd' with 'Cwd'" #-}
+{-# DEPRECATED systemOutput "Use 'command' or 'cmd' with 'Stdout' or 'Stderr'" #-}
+
 -- | /Deprecated:/ Please use 'command' or 'cmd' instead.
 --   This function will be removed in a future version.
 --
diff --git a/Development/Shake/FileTime.hs b/Development/Shake/FileTime.hs
--- a/Development/Shake/FileTime.hs
+++ b/Development/Shake/FileTime.hs
@@ -10,29 +10,24 @@
 import Data.Char
 import Data.Int
 import Data.Word
-import qualified Data.ByteString.Char8 as BS
-import System.IO.Error
-import Control.Exception
 import Numeric
 
--- Required for Portable
+#if defined(PORTABLE)
+import System.IO.Error
+import Control.Exception
 import System.Directory
 import Data.Time
 import System.Time
 
--- Required for non-portable Windows
-#if defined(mingw32_HOST_OS)
+#elif defined(mingw32_HOST_OS)
+import qualified Data.ByteString.Char8 as BS
 import Foreign
 import Foreign.C.Types
-type WIN32_FILE_ATTRIBUTE_DATA = Ptr ()
-type LPCSTR = Ptr CChar
-foreign import stdcall unsafe "Windows.h GetFileAttributesExA" c_getFileAttributesEx :: LPCSTR -> Int32 -> WIN32_FILE_ATTRIBUTE_DATA -> IO Bool
-size_WIN32_FILE_ATTRIBUTE_DATA = 36
-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20
-#endif
+import Foreign.C.String
 
--- Required for non-portable Unix (since it requires a non-standard library, only require if not portable)
-#if !defined(PORTABLE) && !defined(mingw32_HOST_OS)
+#else
+import System.IO.Error
+import Control.Exception
 import System.Posix.Files.ByteString
 #endif
 
@@ -64,19 +59,10 @@
 
 
 getModTimeMaybe :: BSU -> IO (Maybe FileTime)
-#if defined(PORTABLE)
-getModTimeMaybe x = getModTimeMaybePortable x
-#elif defined(mingw32_HOST_OS)
-getModTimeMaybe x = getModTimeMaybeWindows x
-#else
-getModTimeMaybe x = getModTimeMaybeUnix x
-#endif
 
-
-
+#if defined(PORTABLE)
 -- Portable fallback
-getModTimeMaybePortable :: BSU -> IO (Maybe FileTime)
-getModTimeMaybePortable x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
+getModTimeMaybe x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
     time <- getModificationTime $ unpackU x
     return $ Just $ extractFileTime time
 
@@ -86,27 +72,47 @@
 instance ExtractFileTime UTCTime where extractFileTime = fileTime . floor . fromRational . toRational . utctDayTime
 
 
+#elif defined(mingw32_HOST_OS)
 -- Directly against the Win32 API, twice as fast as the portable version
-#if defined(mingw32_HOST_OS)
-getModTimeMaybeWindows :: BSU -> IO (Maybe FileTime)
-getModTimeMaybeWindows x = BS.useAsCString (unpackU_ x) $ \file ->
-    allocaBytes size_WIN32_FILE_ATTRIBUTE_DATA $ \info -> do
-        res <- c_getFileAttributesEx file 0 info
-        if res then do
-            -- Technically a Word32, but we can treak it as an Int32 for peek
-            dword <- peekByteOff info index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime :: IO Int32
-            return $ Just $ fileTime dword
-         else if requireU x then
-            getModTimeMaybePortable x
+getModTimeMaybe x = BS.useAsCString (unpackU_ x) $ \file ->
+    alloca_WIN32_FILE_ATTRIBUTE_DATA $ \fad -> do
+        res <- c_getFileAttributesExA file 0 fad
+        if res then
+            fmap (Just . fileTime) $ peekLastWriteTimeLow fad
+         else if requireU x then withCWString (unpackU x) $ \file -> do
+            res <- c_getFileAttributesExW file 0 fad
+            if res then fmap (Just . fileTime) $ peekLastWriteTimeLow fad else return Nothing
          else
             return Nothing
-#endif
 
+foreign import stdcall unsafe "Windows.h GetFileAttributesExA" c_getFileAttributesExA :: Ptr CChar  -> Int32 -> Ptr WIN32_FILE_ATTRIBUTE_DATA -> IO Bool
+foreign import stdcall unsafe "Windows.h GetFileAttributesExW" c_getFileAttributesExW :: Ptr CWchar -> Int32 -> Ptr WIN32_FILE_ATTRIBUTE_DATA -> IO Bool
 
+data WIN32_FILE_ATTRIBUTE_DATA
+
+alloca_WIN32_FILE_ATTRIBUTE_DATA :: (Ptr WIN32_FILE_ATTRIBUTE_DATA -> IO a) -> IO a
+alloca_WIN32_FILE_ATTRIBUTE_DATA act = allocaBytes size_WIN32_FILE_ATTRIBUTE_DATA act
+    where size_WIN32_FILE_ATTRIBUTE_DATA = 36
+
+peekLastWriteTimeLow :: Ptr WIN32_FILE_ATTRIBUTE_DATA -> IO Int32
+peekLastWriteTimeLow p = peekByteOff p index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
+    where index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20
+
+
+#else
 -- Unix version
-#if !defined(PORTABLE) && !defined(mingw32_HOST_OS)
-getModTimeMaybeUnix :: BSU -> IO (Maybe FileTime)
-getModTimeMaybeUnix x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
-    t <- fmap modificationTime $ getFileStatus $ unpackU_ x
-    return $ Just $ fileTime $ fromIntegral $ fromEnum t
+getModTimeMaybe x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
+    s <- getFileStatus $ unpackU_ x
+    return $ Just $ fileTime $ extractFileTime s
+
+extractFileTime :: FileStatus -> Int32
+#ifndef MIN_VERSION_unix
+#define MIN_VERSION_unix(a,b,c) 0
+#endif
+#if MIN_VERSION_unix(2,6,0)
+extractFileTime x = ceiling $ modificationTimeHiRes x * 1e4 -- precision of 0.1ms
+#else
+extractFileTime x = fromIntegral $ fromEnum $ modificationTime x
+#endif
+
 #endif
diff --git a/Development/Shake/Sys.hs b/Development/Shake/Sys.hs
deleted file mode 100644
--- a/Development/Shake/Sys.hs
+++ /dev/null
@@ -1,82 +0,0 @@
-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, TypeOperators #-}
-
--- | /Deprecated: Please use command or cmd instead. This module will be removed in a future version./
---
---   This module provides versions of the 'Development.Shake.Derived.system'' family of functions
---   which take a variable number of arguments.
---
---   All these functions take a variable number of arguments.
---
--- * @String@ arguments are treated as whitespace separated arguments.
---
--- * @[String]@ arguments are treated as literal arguments.
---
---   As an example, to run @ghc --make -O2 inputs -o output@:
---
--- @
---   'sys' \"ghc --make -O2\" inputs \"-o\" [output]
--- @
---
---   Note that we enclose @output@ as a list so that if the output name contains spaces they are
---   appropriately escaped.
-module Development.Shake.Sys {-# DEPRECATED "Use 'command' or 'cmd' instead" #-} (
-    sys, sysCwd, sysOutput, args
-    ) where
-    
-import Development.Shake.Core
-import Development.Shake.Derived
-
-
-type a :-> t = a
-
-
--- | A variable arity version of 'system''.
-sys :: SysArguments v => String -> v :-> Action ()
-sys x = sys_ [] x
-
-class SysArguments t where sys_ :: [String] -> t
-instance (Arg a, SysArguments r) => SysArguments (a -> r) where
-    sys_ xs x = sys_ $ xs ++ arg x
-instance SysArguments (Action ()) where
-    sys_ (x:xs) = system' x xs
-    sys_ [] = error "No executable or arguments given to sys"
-
-
--- | A variable arity version of 'systemCwd'.
-sysCwd :: SysCwdArguments v => FilePath -> String -> v :-> Action ()
-sysCwd dir x = sysCwd_ dir [] x
-
-class SysCwdArguments t where sysCwd_ :: FilePath -> [String] -> t
-instance (Arg a, SysCwdArguments r) => SysCwdArguments (a -> r) where
-    sysCwd_ dir xs x = sysCwd_ dir $ xs ++ arg x
-instance SysCwdArguments (Action ()) where
-    sysCwd_ dir (x:xs) = systemCwd dir x xs
-    sysCwd_ dir [] = error "No executable or arguments given to sysCwd"
-
-
--- | A variable arity version of 'systemOutput'.
-sysOutput :: SysOutputArguments v => String -> v :-> Action (String, String)
-sysOutput x = sysOutput_ [] x
-
-class SysOutputArguments t where sysOutput_ :: [String] -> t
-instance (Arg a, SysOutputArguments r) => SysOutputArguments (a -> r) where
-    sysOutput_ xs x = sysOutput_ $ xs ++ arg x
-instance SysOutputArguments (Action (String, String)) where
-    sysOutput_ (x:xs) = systemOutput x xs
-    sysOutput_ [] = error "No executable or arguments given to sys"
-
-
--- | A variable arity function to accumulate a list of arguments.
-args :: ArgsArguments v => v :-> [String]
-args = args_ []
-
-class ArgsArguments t where args_ :: [String] -> t
-instance (Arg a, ArgsArguments r) => ArgsArguments (a -> r) where
-    args_ xs x = args_ $ xs ++ arg x
-instance ArgsArguments [String] where
-    args_ = id
-
-
-class Arg a where arg :: a -> [String]
-instance Arg String where arg = words
-instance Arg [String] where arg = id
diff --git a/Examples/Ninja/Main.hs b/Examples/Ninja/Main.hs
--- a/Examples/Ninja/Main.hs
+++ b/Examples/Ninja/Main.hs
@@ -4,6 +4,8 @@
 import Development.Shake
 import Development.Shake.FilePath
 import System.Directory(copyFile)
+import Control.Monad
+import General.Base
 import Examples.Util
 import Data.List
 import qualified Start
@@ -53,3 +55,10 @@
     runFail "-f../../Examples/Ninja/lint.ninja bad --lint" "'needed' file required rebuilding"
     run "-f../../Examples/Ninja/lint.ninja good --lint"
     runFail "-f../../Examples/Ninja/lint.ninja bad --lint" "not a pre-dependency"
+
+    res <- fmap (reverse . drop 2 . reverse . drop 1 . lines) $ captureOutput $ run "-f../../Examples/Ninja/compdb.ninja -t compdb cxx"
+    want <- fmap lines $ readFile "Examples/Ninja/compdb.output"
+    let eq a b | (a1,'*':a2) <- break (== '*') a = unless (a1 `isPrefixOf` b && a2 `isSuffixOf` b) $ a === b
+               | otherwise = a === b
+    length want === length res
+    sequence_ $ zipWith eq want res
diff --git a/Examples/Ninja/compdb.ninja b/Examples/Ninja/compdb.ninja
new file mode 100644
--- /dev/null
+++ b/Examples/Ninja/compdb.ninja
@@ -0,0 +1,45 @@
+# Copied from the Ninja repo
+
+ninja_required_version = 1.3
+
+builddir = build
+cxx = g++
+ar = ar
+cflags = -g -Wall -Wextra -Wno-deprecated -Wno-unused-parameter -fno-rtti $
+    -fno-exceptions -pipe -Wno-missing-field-initializers $
+    -DNINJA_PYTHON="python.exe" -O2 -DNDEBUG -D_WIN32_WINNT=0x0501
+ldflags = -L$builddir -static
+
+rule cxx
+  command = $cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule ar
+  command = cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out
+  description = AR $out
+
+rule link
+  command = $cxx $ldflags -o $out $in $libs
+  description = LINK $out
+
+# the depfile parser and ninja lexers are generated using re2c.
+
+# Core source files all build into ninja library.
+build $builddir\build.o: cxx src\build.cc
+build $builddir\build_log.o: cxx src\build_log.cc
+build $builddir\clean.o: cxx src\clean.cc
+
+rule doxygen
+  command = doxygen $in
+  description = DOXYGEN $in
+doxygen_mainpage_generator = src\gen_doxygen_mainpage.sh
+rule doxygen_mainpage
+  command = $doxygen_mainpage_generator $in > $out
+  description = DOXYGEN_MAINPAGE $out
+build $builddir\doxygen_mainpage: doxygen_mainpage README COPYING | $
+    $doxygen_mainpage_generator
+build doxygen: doxygen doc\doxygen.config | $builddir\doxygen_mainpage
+
+default ninja.exe
diff --git a/Examples/Ninja/compdb.output b/Examples/Ninja/compdb.output
new file mode 100644
--- /dev/null
+++ b/Examples/Ninja/compdb.output
@@ -0,0 +1,17 @@
+[
+  {
+    "directory": "*",
+    "command": "g++ -MMD -MT build\\build.o -MF build\\build.o.d -g -Wall -Wextra -Wno-deprecated -Wno-unused-parameter -fno-rtti -fno-exceptions -pipe -Wno-missing-field-initializers -DNINJA_PYTHON=\"python.exe\" -O2 -DNDEBUG -D_WIN32_WINNT=0x0501 -c src\\build.cc -o build\\build.o",
+    "file": "src\\build.cc"
+  },
+  {
+    "directory": "*",
+    "command": "g++ -MMD -MT build\\build_log.o -MF build\\build_log.o.d -g -Wall -Wextra -Wno-deprecated -Wno-unused-parameter -fno-rtti -fno-exceptions -pipe -Wno-missing-field-initializers -DNINJA_PYTHON=\"python.exe\" -O2 -DNDEBUG -D_WIN32_WINNT=0x0501 -c src\\build_log.cc -o build\\build_log.o",
+    "file": "src\\build_log.cc"
+  },
+  {
+    "directory": "*",
+    "command": "g++ -MMD -MT build\\clean.o -MF build\\clean.o.d -g -Wall -Wextra -Wno-deprecated -Wno-unused-parameter -fno-rtti -fno-exceptions -pipe -Wno-missing-field-initializers -DNINJA_PYTHON=\"python.exe\" -O2 -DNDEBUG -D_WIN32_WINNT=0x0501 -c src\\clean.cc -o build\\clean.o",
+    "file": "src\\clean.cc"
+  }
+]
diff --git a/Examples/Self/Main.hs b/Examples/Self/Main.hs
--- a/Examples/Self/Main.hs
+++ b/Examples/Self/Main.hs
@@ -61,7 +61,8 @@
         deps <- readFileLines $ out -<.> "deps"
         let hs = fixPaths $ unobj $ out -<.> "hs"
         need $ hs : map (obj . moduleToFile "hi") deps
-        ghc ["-c",hs,"-hide-all-packages","-odir=output/self","-hidir=output/self","-i=output/self"]
+        ghc $ ["-c",hs,"-hide-all-packages","-odir=output/self","-hidir=output/self","-i=output/self"] ++
+              ["-DPORTABLE","-fwarn-unused-imports","-Werror"] -- to test one CPP branch
 
     obj ".pkgs" *> \out -> do
         src <- readFile' "shake.cabal"
diff --git a/Examples/Test/Command.hs b/Examples/Test/Command.hs
--- a/Examples/Test/Command.hs
+++ b/Examples/Test/Command.hs
@@ -46,7 +46,8 @@
     assertContentsInfix (obj "ghc-version") "The Glorious Glasgow Haskell Compilation System"
 
     build ["ghc-random"]
-    assertContentsInfix (obj "ghc-random") "unrecognised flags: --random"
+    assertContentsInfix (obj "ghc-random") "unrecognised flag"
+    assertContentsInfix (obj "ghc-random") "--random"
 
     crash ["ghc-random2"] ["unrecognised flag","--random"]
 
diff --git a/Examples/Util.hs b/Examples/Util.hs
--- a/Examples/Util.hs
+++ b/Examples/Util.hs
@@ -143,16 +143,14 @@
 sleepFileTimeCalibrate = do
     let file = "output/calibrate"
     createDirectoryIfMissing True $ takeDirectory file
-    mtimes <- forM [1..3] $ \i -> fmap fst $ duration $ do
+    mtimes <- forM [1..10] $ \i -> fmap fst $ duration $ do
         writeFile file $ show i
-        -- important to benchmark both modtimes, since #117 means unicode files
-        -- fall back to getModificationTime on Windows
-        let time = liftM2 (,) (getModificationTime file) (getModTimeError "File is missing" $ packU file)
+        let time = getModTimeError "File is missing" $ packU file
         t1 <- time
         flip loop 0 $ \j -> do
             writeFile file $ show (i,j)
             t2 <- time
-            return $ if fst t1 == fst t2 || snd t1 == snd t2 then Left $ j+1 else Right ()
+            return $ if t1 == t2 then Left $ j+1 else Right ()
     putStrLn $ "Longest file modification time lag was " ++ show (ceiling (maximum mtimes * 1000)) ++ "ms"
     return $ sleep $ min 1 $ maximum mtimes * 2
 
diff --git a/General/Base.hs b/General/Base.hs
--- a/General/Base.hs
+++ b/General/Base.hs
@@ -6,10 +6,11 @@
     Barrier, newBarrier, signalBarrier, waitBarrier, waitBarrierMaybe,
     Duration, duration, Time, offsetTime, sleep,
     isWindows, getProcessorCount,
-    readFileUCS2, getEnvMaybe,
+    readFileUCS2, getEnvMaybe, captureOutput,
     modifyIORef'', writeIORef'',
     whenJust, loop, whileM, partitionM, concatMapM, mapMaybeM,
     fastNub, showQuote,
+    withBufferMode, withCapabilities
     ) where
 
 import Control.Concurrent
@@ -21,10 +22,12 @@
 import Data.Maybe
 import Data.Time
 import qualified Data.HashSet as Set
+import System.Directory
 import System.Environment
 import System.IO
 import System.IO.Error
 import System.IO.Unsafe
+import GHC.IO.Handle(hDuplicate,hDuplicateTo)
 import Development.Shake.Classes
 
 
@@ -220,3 +223,34 @@
 
 getEnvMaybe :: String -> IO (Maybe String)
 getEnvMaybe x = catchJust (\x -> if isDoesNotExistError x then Just x else Nothing) (fmap Just $ getEnv x) (const $ return Nothing)
+
+captureOutput :: IO () -> IO String
+captureOutput act = do
+    tmp <- getTemporaryDirectory
+    (f,h) <- openTempFile tmp "hlint"
+    sto <- hDuplicate stdout
+    ste <- hDuplicate stderr
+    hDuplicateTo h stdout
+    hDuplicateTo h stderr
+    hClose h
+    act
+    hDuplicateTo sto stdout
+    hDuplicateTo ste stderr
+    res <- readFile f
+    evaluate $ length res
+    removeFile f
+    return res
+
+withCapabilities :: Int -> IO a -> IO a
+#if __GLASGOW_HASKELL__ >= 706
+withCapabilities new act | rtsSupportsBoundThreads = do
+    old <- getNumCapabilities
+    if old == new then act else
+        bracket_ (setNumCapabilities new) (setNumCapabilities old) act
+#endif
+withCapabilities new act = act
+
+withBufferMode :: Handle -> BufferMode -> IO a -> IO a
+withBufferMode h b act = bracket (hGetBuffering h) (hSetBuffering h) $ const $ do
+    hSetBuffering h LineBuffering
+    act
diff --git a/General/RAW.hs b/General/RAW.hs
new file mode 100644
--- /dev/null
+++ b/General/RAW.hs
@@ -0,0 +1,96 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module General.RAW(
+    RAW, runRAW,
+    getRO, getRW, getsRO, getsRW, putRW, modifyRW,
+    withRO, withRW,
+    catchRAW, tryRAW, throwRAW,
+    evalRAW, unmodifyRW
+    ) where
+
+import Control.Exception as E
+import Control.Applicative
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Reader
+import Data.IORef
+
+
+newtype RAW ro rw a = RAW {fromRAW :: ReaderT (ro, IORef rw) IO a}
+    deriving (Functor, Applicative, Monad, MonadIO)
+
+runRAW :: ro -> rw -> RAW ro rw a -> IO a
+runRAW ro rw (RAW m) = do
+    ref <- newIORef rw
+    runReaderT m (ro, ref)
+
+
+---------------------------------------------------------------------
+-- STANDARD
+
+getRO :: RAW ro rw ro
+getRO = RAW $ asks fst
+
+getRW :: RAW ro rw rw
+getRW = RAW $ liftIO . readIORef =<< asks snd
+
+getsRO :: (ro -> a) -> RAW ro rw a
+getsRO f = fmap f getRO
+
+getsRW :: (rw -> a) -> RAW ro rw a
+getsRW f = fmap f getRW
+
+-- | Strict version
+putRW :: rw -> RAW ro rw ()
+putRW rw = rw `seq` RAW $ liftIO . flip writeIORef rw =<< asks snd
+
+modifyRW :: (rw -> rw) -> RAW ro rw ()
+modifyRW f = do x <- getRW; putRW $ f x
+
+withRO :: (ro -> ro2) -> RAW ro2 rw a -> RAW ro rw a
+withRO f m = RAW $ withReaderT (\(ro,rw) -> (f ro, rw)) $ fromRAW m
+
+withRW :: (rw -> rw2) -> RAW ro rw2 a -> RAW ro rw a
+withRW f m = RAW $ do
+    rw <- asks snd
+    ref <- liftIO $ newIORef . f =<< readIORef rw
+    withReaderT (\(ro,_) -> (ro,ref)) $ fromRAW m
+
+
+---------------------------------------------------------------------
+-- EXCEPTIONS
+
+catchRAW :: Exception e => RAW ro rw a -> (e -> RAW ro rw a) -> RAW ro rw a
+catchRAW m handle = RAW $ liftCatch E.catch (fromRAW m) (fromRAW . handle)
+
+tryRAW :: Exception e => RAW ro rw a -> RAW ro rw (Either e a)
+tryRAW m = catchRAW (fmap Right m) (return . Left)
+
+throwRAW :: Exception e => e -> RAW ro rw a
+throwRAW = liftIO . throwIO
+
+
+---------------------------------------------------------------------
+-- WEIRD STUFF
+
+-- | Given an action, produce a 'RAW' that runs fast, containing
+--   an 'IO' that runs slowly (the bulk of the work) and a 'RAW'
+--   that runs fast.
+evalRAW :: RAW ro rw a -> RAW ro rw (IO (RAW ro rw a))
+evalRAW m = RAW $ do
+    (ro,rw) <- ask
+    return $ do
+        ref <- newIORef =<< readIORef rw
+        res <- runReaderT (fromRAW m) (ro,ref)
+        return $ RAW $ do
+            (ro,rw) <- ask
+            liftIO $ writeIORef rw =<< readIORef ref
+            return res
+
+-- | Apply a modification, run an action, then undo the changes after.
+unmodifyRW :: (rw -> (rw, rw -> rw)) -> RAW ro rw a -> RAW ro rw a
+unmodifyRW f m = do
+    (s2,undo) <- fmap f getRW
+    putRW s2
+    res <- m
+    modifyRW undo
+    return res
diff --git a/Start.hs b/Start.hs
--- a/Start.hs
+++ b/Start.hs
@@ -7,6 +7,7 @@
 import Development.Shake
 import Development.Shake.FilePath
 import General.Timing
+import Data.Maybe
 import qualified System.Directory as IO
 import System.Console.GetOpt
 
@@ -17,18 +18,24 @@
     args <- getArgs
     withArgs ("--no-time":args) $
         shakeArgsWith shakeOptions flags $ \opts targets -> do
+            let tool = listToMaybe [x | Tool x <- opts]
             makefile <- case reverse [x | UseMakefile x <- opts] of
                 x:_ -> return x
                 _ -> findMakefile
             if takeExtension makefile == ".ninja" then
-                fmap Just $ runNinja makefile targets
+                runNinja makefile targets tool
+             else if isJust tool then
+                error "--tool flag is not supported without a .ninja Makefile"
              else
                 fmap Just $ runMakefile makefile targets
 
 
 data Flag = UseMakefile FilePath
+          | Tool String
 
-flags = [Option "f" ["file","makefile"] (ReqArg (Right . UseMakefile) "FILE") "Read FILE as a makefile."]
+flags = [Option "f" ["file","makefile"] (ReqArg (Right . UseMakefile) "FILE") "Read FILE as a makefile."
+        ,Option "t" ["tool"] (ReqArg (Right . Tool) "TOOL") "Ninja-compatible tools."
+        ]
 
 
 findMakefile :: IO FilePath
diff --git a/Test.hs b/Test.hs
--- a/Test.hs
+++ b/Test.hs
@@ -4,15 +4,13 @@
 
 import Control.Exception
 import Control.Monad
-import Data.List
 import Data.Maybe
 import System.Environment
-import Development.Shake.Pool
 import General.Timing
 import Development.Shake.FileTime
 import General.String
 import qualified Data.ByteString.Char8 as BS
-import Examples.Util(sleepFileTime, sleepFileTimeCalibrate)
+import Examples.Util(sleepFileTimeCalibrate)
 import Control.Concurrent
 
 import qualified Examples.Tar.Main as Tar
@@ -92,7 +90,7 @@
             ,unwords ["  ", exePath, "self",  "--jobs=2", "--trace"]
             ,""
             ,"Which will build Shake, using Shake, on 2 threads."]
-        Just main -> main sleepFileTime
+        Just main -> main =<< sleepFileTimeCalibrate
 
 
 makefile :: IO () -> IO ()
@@ -128,18 +126,7 @@
 
 
 test :: IO () -> IO ()
-test _ = do
+test yield = do
     args <- getArgs
-    let tests = filter ((/= "random") . fst) mains
-    let (priority,normal) = partition (flip elem ["assume","journal"] . fst) tests
-    yield <- sleepFileTimeCalibrate
     flip onException (putStrLn "TESTS FAILED") $
-        execute yield [\pause -> withArgs (name:"test":drop 1 args) $ test pause | (name,test) <- priority ++ normal]
-
-
--- | Execute each item in the list. They may yield (call the first parameter) in which case
---   you must execute yield for each one of them.
-execute :: IO () -> [IO () -> IO ()] -> IO ()
-execute yield acts = runPool True 1 $ \pool -> do
-    let pause = blockPool pool $ yield >> return (False,())
-    forM_ acts $ \act -> addPool pool $ act pause
+        sequence_ [withArgs (name:"test":drop 1 args) $ test yield | (name,test) <- mains, name /= "random"]
diff --git a/shake.cabal b/shake.cabal
--- a/shake.cabal
+++ b/shake.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.10
 build-type:         Simple
 name:               shake
-version:            0.11.7
+version:            0.12
 license:            BSD3
 license-file:       LICENSE
 category:           Development
@@ -42,6 +42,7 @@
     Examples/MakeTutor/hellomake.h
     Examples/Tar/list.txt
     Examples/Ninja/*.ninja
+    Examples/Ninja/*.output
     Paths.hs
     CHANGES.txt
     README.md
@@ -107,7 +108,6 @@
         Development.Shake.Config
         Development.Shake.FilePath
         Development.Shake.Rule
-        Development.Shake.Sys
         Development.Shake.Util
 
     other-modules:
@@ -141,6 +141,7 @@
         General.Base
         General.Binary
         General.Intern
+        General.RAW
         General.String
         General.Timing
         Paths_shake
