diff --git a/Lib.hs b/Lib.hs
--- a/Lib.hs
+++ b/Lib.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE RecordWildCards #-}
+
 module Lib
        ( putStrErr
        , putStrLnErr
@@ -16,23 +18,33 @@
        )
        where
 
-import           Control.Exception (finally,tryJust)
-import           Control.Monad (when,filterM,guard)
+import           Control.Exception (finally)
+import           Control.Monad (when,filterM,foldM)
+import           Control.Monad.Fail (MonadFail(fail))
 import           Control.Monad.IO.Class (liftIO, MonadIO)
+import           Control.Monad.Trans.Maybe (runMaybeT)
 import           Crypto.Hash (hash,Digest,MD5)
 import qualified Data.ByteString as BS
 import qualified Data.Map as Map (toList,fromList,insert)
-import           Data.Maybe (listToMaybe)
-import           System.Directory (getCurrentDirectory, makeAbsolute, doesFileExist, renameFile, removeFile, createDirectoryIfMissing, getDirectoryContents, removeDirectoryRecursive, withCurrentDirectory)
+import           Data.Maybe (listToMaybe, isJust)
+import qualified Prelude
+import           Prelude hiding (fail,readFile)
+import           System.Directory (getCurrentDirectory, makeAbsolute, doesFileExist, renameFile, removeFile, createDirectoryIfMissing, getDirectoryContents, removeDirectoryRecursive, withCurrentDirectory, doesDirectoryExist)
 import           System.Environment (getEnvironment,lookupEnv)
-import           System.Exit (ExitCode(ExitSuccess))
+import           System.Exit (ExitCode(ExitSuccess),exitFailure)
 import           System.FilePath ((</>), takeBaseName, dropFileName, replaceBaseName, addExtension,splitSearchPath)
 import           System.IO (hPutStrLn,stderr,hPutStr,withFile,IOMode(ReadMode,WriteMode,AppendMode),hFileSize)
-import           System.IO.Error (isDoesNotExistError)
 import qualified System.Process as Proc (env, waitForProcess, createProcess, proc)
 
 import           DepInfo
 
+type Hash = String
+
+readFile :: (MonadFail m, MonadIO m) => FilePath -> m String
+readFile p = ifM (liftIO $ doesFileExist p)
+  (liftIO $ Prelude.readFile p)
+  (fail $ "File "++p++"does not exist")
+
 -- | Print a string to standard error output with a newline character
 -- in the end.
 putStrLnErr :: (MonadIO m) => String -> m ()
@@ -80,7 +92,9 @@
         (_,_,_,ph) <- liftIO $ Proc.createProcess process
         code <- liftIO $ Proc.waitForProcess ph
         if code /= ExitSuccess
-          then putStrLnErr $ "*** redo failed for target "++show target
+          then do
+            putStrLnErr $ "*** redo failed for target "++show target
+            exitFailure
           else do filesize <- getFileSize tempFilePath
                   when (filesize /= 0) $ renameFile tempFilePath target
         return code
@@ -125,87 +139,106 @@
 getDefaultDirs =
   maybe [] splitSearchPath <$> liftIO (lookupEnv "GDO_DEFAULTS")
 
-hashFile :: (MonadIO m) => FilePath -> m (Digest MD5)
+hashFile :: (MonadIO m, MonadFail m) => FilePath -> m Hash
 hashFile target = do
-  bs <- liftIO $ BS.readFile target
-  return $ hash bs
+  targetExists <- liftIO (doesFileExist target)
+  if targetExists
+    then (show :: Digest MD5 -> String) . hash <$> liftIO (BS.readFile target)
+    else fail $ "Files "++target++"does not exist!"
 
 upToDate :: (MonadIO m) =>
             [FilePath]
          -> FilePath
          -> m Bool
 upToDate mDefaultsDir buildTarget = do
-  metaDepDir <- getMetaDir buildTarget
-  liftIO $
-    maybe
-      -- When no recipe exists, we assume the target is up to date.
-      (return True)
-      ( const $ do
-           r <- tryJust
-                (guard . isDoesNotExistError)
-                ( do md5s <- Prelude.filter (not . (`elem` metaIgnoreFiles)) <$>
-                             getDirectoryContents metaDepDir
-                     depsNotChanged <- and <$> mapM upToDate' md5s
-                     noNewFiles <- not <$> newFilesCreated
-                     return $ depsNotChanged && noNewFiles
-                )
-           return $ fromRight (const False) r
-      ) =<< getRecipeFile mDefaultsDir buildTarget
+  recipeExists <- isJust <$> liftIO (getRecipeFile mDefaultsDir buildTarget)
+  if recipeExists
+    then do
+    mDepInfos <- runMaybeT (getHashesAndDepsForTarget buildTarget)
+    case mDepInfos of
+      Nothing -> return False
+      Just depInfos -> do
+        directDepsUpToDate <- if recipeExists
+          then do
+          return $ and . map depInfoUpToDate $ depInfos
+          else return True
+        depsUpToDate <-
+          mapM (upToDate mDefaultsDir . depInfoFilePath) depInfos
+        return (and $ directDepsUpToDate:depsUpToDate)
+    else return True
+
+ifM :: (Monad m) => m Bool -> m a -> m a -> m a
+ifM condM consequence alternative = do
+  cond <- condM
+  if cond
+    then consequence
+    else alternative
+
+getOldHashes :: (MonadIO m, MonadFail m) => String -> m [(String, FilePath)]
+getOldHashes target = do
+  metaDir <- liftIO $ getMetaDir target
+  ifM (liftIO $ doesDirectoryExist metaDir)
+    (liftIO $ withCurrentDirectory metaDir
+      ( foldM putHashInMap mempty =<<
+        (filter (not . (`elem` metaIgnoreFiles)) <$>
+         (getDirectoryContents metaDir)))
+    )
+    ( fail "Metadata directory does not exist" )
   where
-    fromRight :: (a -> b) -> Either a b -> b
-    fromRight f = either f id
-    upToDate' :: String -> IO Bool
-    upToDate' oldHash = do
-      metaDepDir <- getMetaDir buildTarget
-      r <- tryJust (guard . isDoesNotExistError) $ do
-        dep <- readFile $ metaDepDir </> oldHash
-        newHash <- show <$> hashFile dep
-        depUpToDate <- upToDate mDefaultsDir dep
-        let allUpToDate = oldHash == newHash && depUpToDate
-        return allUpToDate
-      case r of
-       Right b -> return b
-       Left _ -> return False
-    newFilesCreated :: IO Bool
-    newFilesCreated = do
-      missingDepsFile <- getMissingDepsFile buildTarget
-      r <- tryJust (guard . isDoesNotExistError) $
+    putHashInMap hashmap filepath =
+      (:) <$> ((,) <$> readFile filepath <*> pure filepath) <*> pure hashmap
+
+getHashesAndDepsForTarget :: (MonadIO m, MonadFail m) =>
+                             String -> m [DepInfo]
+getHashesAndDepsForTarget target = do
+  existingDeps <- mapM (liftIO . getDepInfo) =<< getOldHashes target
+  nonExistingDeps <- futureDependencies
+  return (existingDeps ++ nonExistingDeps)
+  where
+    getDepInfo (depInfoFilePath, oldHash) = do
+      let
+        depInfoSavedMD5 = Just oldHash
+      depInfoCurrentMD5 <- runMaybeT $ hashFile depInfoFilePath
+      return DepInfo {..}
+    futureDependencies = do
+      missingDepsFile <- getMissingDepsFile target
+      r <- runMaybeT $
            Prelude.filter (\e -> (e `notElem` metaIgnoreFiles) ||
                                  Prelude.null e) .
            lines <$>
            readFile missingDepsFile
       case r of
-       Left _ -> return False
-       Right watchlist -> or <$> mapM
-                          doesFileExist watchlist
-
-getHashesAndDepsForTarget :: FilePath -> IO [DepInfo]
-getHashesAndDepsForTarget target = do
-  metaDir <- getMetaDir target
-  (filenames, oldMd5s) <- withCurrentDirectory metaDir $ do
-    hashes <- filter (not . (`elem` metaIgnoreFiles)) <$>
-              getDirectoryContents metaDir
-    (,) <$> mapM readFile hashes <*> pure hashes
-  md5s <- mapM hashFromPath filenames
-  return (zipWith3 DepInfo
-          filenames
-          (map Just oldMd5s)
-          (map (Just . show)  md5s))
-  where
-    hashFromPath :: FilePath -> IO (Digest MD5)
-    hashFromPath f = hash <$> BS.readFile f
+       Nothing -> return []
+       Just watchlist ->
+         mapM (\ depInfoFilePath -> do
+                  let
+                    depInfoSavedMD5 = Nothing
+                  depInfoCurrentMD5 <- runMaybeT (hashFile depInfoFilePath)
+                  return DepInfo {..}
+              )
+         watchlist
 
+-- | `recordDependency target dep` records the existing file `dep` as
+-- a dependency of `target`.
+--
+-- `target` can be absolute paths or paths relative to the current
+-- working directory.
 recordDependency :: FilePath -- ^ target name
                  -> FilePath -- ^ dependency name
                  -> IO ()
 recordDependency target dep = do
-  newHash <- show <$> hashFile dep
+  newHash <- hashFile dep
   metaDepDir <- getMetaDir target
   let md5file = metaDepDir </> newHash
   liftIO $ createDirectoryIfMissing True metaDepDir
   liftIO $ withFile md5file WriteMode
     (`hPutStr` dep)
 
+-- | `recordMissingDependency target dep` records `dep` as a
+-- dependency of `target` if it should be created.
+--
+-- `target` can be absolute paths or paths relative to the current
+-- working directory.
 recordMissingDependency :: FilePath -> FilePath -> IO ()
 recordMissingDependency target dep = do
   metaDepsDir <- getMetaDir target
@@ -216,6 +249,8 @@
     AppendMode
     (`hPutStrLn` dep)
 
+-- | Remove all recorded dependencies for a target.  This makes the
+-- target stale by definition.
 purgeRecordedDependencies :: FilePath -> IO ()
 purgeRecordedDependencies target =
   removeDirectoryRecursive =<< getMetaDir target
diff --git a/gdo.cabal b/gdo.cabal
--- a/gdo.cabal
+++ b/gdo.cabal
@@ -2,7 +2,7 @@
 -- see http://haskell.org/cabal/users-guide/
 
 name:                gdo
-version:             0.1.2
+version:             0.1.3
 synopsis:            recursive atomic build system
 description:
   *gdo* is a build system similar to **GNU Make**. It builds files from
@@ -40,7 +40,7 @@
   main-is:             Main.hs
   other-modules:       Lib
                      , DepInfo
-  build-depends:       base >=4.8 && <4.10,
+  build-depends:       base >=4.8 && < 5,
                        process >= 1.1,
                        filepath >= 1.0,
                        directory >= 1.2,
diff --git a/gdo.do b/gdo.do
--- a/gdo.do
+++ b/gdo.do
@@ -1,14 +1,27 @@
 #!/bin/sh
 
 # build the gdo executable
+set -e
 
 HC="ghc -v0"
 
-gdo --if Changed Main.hs Lib.hs DepInfo.hs
+gdo --if Changed build_util.sh Main.hs
 gdo --if Created environment
 
+source ./build_util.sh
+
 if [ -e environment ]; then
     source ./environment
 fi
 
-${HC} Main.hs -o ${3}
+TEMPMAKEFILE=$(mktemp)
+
+trap "rm ${TEMPMAKEFILE} -rf" EXIT SIGTERM SIGINT
+
+${HC} Main.hs -M -dep-suffix '' -dep-makefile ${TEMPMAKEFILE}
+OBJECTS=$(cat ${TEMPMAKEFILE} | filter_comments | extract_exec_deps)
+
+
+gdo --if Changed ${OBJECTS}
+
+${HC} ${OBJECTS} ${GHC_PACKAGES} -o ${3}
