Walk directories in parallel (async)
[barrelfish] / hake / Main.hs
1 import Control.Concurrent.Async
2
3 import Control.Monad.Error
4
5 import Data.Dynamic
6 import Data.List
7 import Data.Maybe
8 import qualified Data.Set as S
9
10 import System.Directory
11 import System.Environment
12 import System.Exit
13 import System.FilePath
14 import System.IO
15 import System.Mem
16
17 import GHC hiding (Target, Ghc, GhcT, runGhc, runGhcT, FunBind, Match)
18 import GHC.Paths (libdir)
19 import Control.Monad.Ghc
20 import DynFlags (defaultFatalMessager, defaultFlushOut,
21                  xopt_set, ExtensionFlag (Opt_DeriveDataTypeable))
22 import GHC.Stats
23
24 import Language.Haskell.Exts
25
26 import RuleDefs
27 import HakeTypes
28 import qualified Args
29 import qualified Config
30 import qualified Path
31
32 data HakeError = HakeError String Int
33 instance Error HakeError
34 type HakeMonad = ErrorT HakeError IO
35
36 --
37 -- Command line options and parsing code
38 --
39 data Opts = Opts { opt_makefilename :: String,
40                    opt_installdir :: String,
41                    opt_sourcedir :: String,
42                    opt_bfsourcedir :: String,
43                    opt_abs_installdir :: String,
44                    opt_abs_sourcedir :: String,
45                    opt_abs_bfsourcedir :: String,
46                    opt_usage_error :: Bool,
47                    opt_architectures :: [String],
48                    opt_verbosity :: Integer
49                  }
50           deriving (Show,Eq)
51                    
52 parse_arguments :: [String] -> Opts
53 parse_arguments [] =
54   Opts { opt_makefilename = "Makefile",
55          opt_installdir = Config.install_dir,
56          opt_sourcedir = Config.source_dir,
57          opt_bfsourcedir = Config.source_dir,
58          opt_abs_installdir = "",
59          opt_abs_sourcedir = "",
60          opt_abs_bfsourcedir = "",
61          opt_usage_error = False, 
62          opt_architectures = [],
63          opt_verbosity = 1 }
64 parse_arguments ("--install-dir" : (s : t)) =
65   (parse_arguments t) { opt_installdir = s }
66 parse_arguments ("--source-dir" : s : t) =  
67   (parse_arguments t) { opt_sourcedir = s }
68 parse_arguments ("--bfsource-dir" : s : t) =  
69   (parse_arguments t) { opt_bfsourcedir = s }
70 parse_arguments ("--output-filename" : s : t) =
71   (parse_arguments t) { opt_makefilename = s }
72 parse_arguments ("--quiet" : t ) = 
73   (parse_arguments t) { opt_verbosity = 0 }
74 parse_arguments ("--verbose" : t ) = 
75   (parse_arguments t) { opt_verbosity = 2 }
76 parse_arguments ("--architecture" : a : t ) = 
77   let 
78     o2 = parse_arguments t
79     arches = (a : opt_architectures o2)
80   in
81     o2 { opt_architectures = arches }
82 parse_arguments _ = 
83   (parse_arguments []) { opt_usage_error = True }
84
85 usage :: String
86 usage = unlines [ "Usage: hake <options>",
87                   "   --source-dir <dir> (required)",
88                   "   --bfsource-dir <dir> (defaults to source dir)",
89                   "   --install-dir <dir> (defaults to source dir)",
90                   "   --quiet",
91                   "   --verbose"
92                 ]
93
94 -- check the configuration options, returning an error string if they're insane
95 configErrors :: Maybe String
96 configErrors
97     | unknownArchs /= [] =
98         Just ("unknown architecture(s) specified: " ++
99         (concat $ intersperse ", " unknownArchs))
100     | Config.architectures == [] =
101         Just "no architectures defined"
102     | Config.lazy_thc && not Config.use_fp =
103         Just "Config.use_fp must be true to use Config.lazy_thc."
104     | otherwise =
105         Nothing
106     where
107         unknownArchs = Config.architectures \\ Args.allArchitectures
108
109 --
110 -- Walk all over a directory tree and build a complete list of pathnames
111 --
112 listFiles :: FilePath -> IO ([FilePath], [(FilePath, String)])
113 listFiles root = do
114     isdir <- doesDirectoryExist root
115     if isdir then do
116         children <- getDirectoryContents root
117         walkchildren children
118     else
119         return ([], [])
120     where
121         walkchildren :: [FilePath] -> IO ([FilePath], [(FilePath, String)])
122         walkchildren children = do
123             children_async <- mapM (async.walkchild) children
124             results <- mapM wait children_async
125             return $ joinResults results
126             where
127                 joinResults :: [([a],[b])] -> ([a],[b])
128                 joinResults [] = ([],[])
129                 joinResults ((as,bs):xs) =
130                     let (as',bs') = joinResults xs in
131                         (as ++ as', bs ++ bs')
132
133         walkchild :: FilePath -> IO ([FilePath], [(FilePath, String)])
134         walkchild child = do
135             if ignore child
136             then return ([], [])
137             else do
138                 (allfiles, hakefiles) <- listFiles (root </> child)
139                 hake <- maybeHake child
140                 return $ ((root </> child) : allfiles,
141                           hake ++ hakefiles)
142             where
143                 maybeHake "Hakefile" = do
144                     contents <- readFile (root </> child)
145                     return [(root </> child, contents)]
146                 maybeHake _ = return []
147
148         ignore :: FilePath -> Bool
149         ignore "."          = True
150         ignore ".."         = True
151         ignore "CMakeFiles" = True
152         ignore ".hg"        = True
153         ignore "build"      = True
154         ignore ".git"       = True
155         ignore _            = False
156
157 instance Show SuccessFlag
158 instance Show RunResult
159
160 driveGhc :: Handle -> Opts -> [FilePath] -> [(FilePath, String)] ->
161             Ghc (S.Set FilePath)
162 driveGhc makefile o allfiles hakefiles = do
163     -- Set the RTS flags
164     dflags <- getSessionDynFlags
165     let dflags' = foldl xopt_set dflags [ Opt_DeriveDataTypeable ]
166     _ <- setSessionDynFlags dflags'{
167         importPaths = module_paths,
168         hiDir = Just "./hake",
169         objectDir = Just "./hake"
170     }
171
172     -- Set compilation targets
173     targets <- mapM (\m -> guessTarget m Nothing) source_modules
174     setTargets targets
175     load LoadAllTargets
176
177     -- Import modules
178     setContext
179         ([IIDecl $ simpleImportDecl $ mkModuleName m |
180             m <- modules] ++
181          [IIDecl $ (simpleImportDecl $ mkModuleName m) {
182                 ideclQualified = True
183           } | m <- qualified_modules])
184
185     buildSections hakefiles
186
187     where
188         module_paths = [ (opt_installdir o) </> "hake", ".", 
189                          (opt_bfsourcedir o) </> "hake" ]
190         source_modules = [ "HakeTypes", "RuleDefs", "Path", "Args", "Config" ]
191         modules = [ "Prelude", "HakeTypes", "RuleDefs", "Path", "Args" ]
192         qualified_modules = [ "Config", "Data.List" ]
193
194         buildSections' :: (S.Set FilePath) -> [(FilePath, String)] ->
195                           Ghc (S.Set FilePath)
196         buildSections' dirs [] = return dirs
197         buildSections' dirs ((abs_hakepath, contents):hs) = do
198             let hakepath = makeRelative (opt_sourcedir o) abs_hakepath
199             rule <- evaluate hakepath contents
200             dirs' <- liftIO $ makefileSection makefile o hakepath rule
201             buildSections' (S.union dirs' dirs) hs
202
203         buildSections :: [(FilePath, String)] -> Ghc (S.Set FilePath)
204         buildSections hs = buildSections' S.empty hs
205
206         evaluate :: FilePath -> String -> Ghc HRule
207         evaluate hakepath hake_raw = do
208             case hake_parse of
209                 Left hake_expr -> do
210                     let hake_wrapped =
211                             prettyPrintWithMode (defaultMode {layout = PPNoLayout}) $
212                                 wrapHake hakepath hake_expr
213
214                     val <- dynCompileExpr $ hake_wrapped ++ " :: [String] -> HRule"
215                     let rule = fromDyn val (\_ -> Error "failed")
216                     let resolved_rule =
217                             resolvePaths o (takeDirectory hakepath)
218                                            (rule allfiles)
219                     return resolved_rule
220                 Right hake_error -> do
221                     return $ Error "failed"
222             where
223                 hake_parse = parseHake hakepath hake_raw
224
225 evalHakeFiles :: Handle -> Opts -> [FilePath] -> [(FilePath, String)] ->
226                  IO (S.Set FilePath)
227 evalHakeFiles makefile o allfiles hakefiles =
228     defaultErrorHandler defaultFatalMessager defaultFlushOut $
229         runGhc (Just libdir) $
230         driveGhc makefile o allfiles hakefiles
231
232 parseHake :: FilePath -> String -> Either Exp HakeError
233 parseHake filename contents =
234     case result of
235         ParseOk e -> Left e
236         ParseFailed loc str ->
237             Right $ HakeError (show loc ++ ": " ++ str) 2
238     where
239         result =
240             parseExpWithMode
241                 (defaultParseMode {
242                     parseFilename = filename,
243                     baseLanguage = Haskell2010 })
244                 contents
245
246 wrapHake :: FilePath -> Exp -> Exp
247 wrapHake hakefile hake_exp =
248     Paren (
249     Lambda dummy_loc [PVar (Ident "allfiles")] (
250     Let (BDecls
251         [FunBind [Match
252             dummy_loc
253             (Ident "find")
254             [PVar (Ident "fn"), PVar (Ident "arg")]
255             Nothing
256             (UnGuardedRhs
257                 (Paren (App (App (App (Var (UnQual (Ident "fn")))
258                                       (Var (UnQual (Ident "allfiles"))))
259                                  (Lit (String hakefile)))
260                        (Var (UnQual (Ident "arg"))))))
261             (BDecls [])],
262
263         FunBind [Match
264             dummy_loc
265             (Ident "build")
266             [PVar (Ident "a")]
267             Nothing
268             (UnGuardedRhs
269                 (App (App (App (Paren (App (Var (UnQual (Ident "buildFunction")))
270                                            (Var (UnQual (Ident "a")))))
271                                (Var (UnQual (Ident "allfiles"))))
272                           (Lit (String hakefile)))
273                      (Var (UnQual (Ident "a")))))
274             (BDecls [])]
275         ])
276         (Paren (App (Con (UnQual (Ident "Rules")))
277                     hake_exp))
278     ))
279     where
280         dummy_loc = SrcLoc { srcFilename = "<hake internal>",
281                                 srcLine = 0, srcColumn = 0 }
282
283 makefilePreamble :: Handle -> Opts -> [String] -> IO ()
284 makefilePreamble h opts args = 
285     mapM_ (hPutStrLn h)
286           ([ "# This Makefile is generated by Hake.  Do not edit!",
287              "# ",
288              "# Hake was invoked with the following command line args:" ] ++
289            [ "#        " ++ a | a <- args ] ++
290            [ "# ",
291              "SRCDIR=" ++ (opt_sourcedir opts),
292              "HAKE_ARCHS=" ++ (concat $ intersperse " " Config.architectures),
293              "include ./symbolic_targets.mk" ])
294
295 arch_list :: S.Set String
296 arch_list = S.fromList (Config.architectures ++
297                         ["", "src", "hake", "root", "tools", "docs"])
298
299 -- a rule is included if it has only "special" architectures and enabled architectures
300 allowedArchs :: [String] -> Bool
301 allowedArchs = all (\a -> a `S.member` arch_list)
302
303 makefileSection :: Handle -> Opts -> FilePath -> HRule -> IO (S.Set FilePath)
304 makefileSection h opts hakepath rule = do
305     hPutStrLn h $ "# From: " ++ hakepath ++ "\n"
306     makefileRule h rule
307
308 makefileRule :: Handle -> HRule -> IO (S.Set FilePath)
309 makefileRule h (Error s) = do
310     hPutStrLn h $ "$(error " ++ s ++ ")\n"
311     return S.empty
312 makefileRule h (Rules rules) = do
313     dir_lists <- mapM (makefileRule h) rules
314     return $! S.unions dir_lists
315 makefileRule h (Include token) = do
316     when (allowedArchs [frArch token]) $
317         mapM_ (hPutStrLn h) [
318             "ifeq ($(MAKECMDGOALS),clean)",
319             "else ifeq ($(MAKECMDGOALS),rehake)",
320             "else ifeq ($(MAKECMDGOALS),Makefile)",
321             "else",
322             "include " ++ (formatToken token),
323             "endif",
324             "" ]
325     return S.empty
326 makefileRule h (HakeTypes.Rule tokens) =
327     if allowedArchs (map frArch tokens)
328         then makefileRuleInner h tokens False
329         else return S.empty
330 makefileRule h (Phony name double_colon tokens) = do
331     hPutStrLn h $ ".PHONY: " ++ name
332     makefileRuleInner h (Target "build" name : tokens) double_colon
333
334 printTokens :: Handle -> S.Set RuleToken -> IO ()
335 printTokens h tokens =
336     S.foldr (\t m -> hPutStr h (formatToken t) >> m) (return ()) tokens
337
338 printDirs :: Handle -> S.Set FilePath -> IO ()
339 printDirs h dirs =
340     S.foldr (\d m -> hPutStr h (d ++ " ") >> m) (return ()) dirs
341
342 makefileRuleInner :: Handle -> [RuleToken] -> Bool -> IO (S.Set FilePath)
343 makefileRuleInner h tokens double_colon = do
344     if S.null (ruleOutputs compiledRule)
345     then do
346         hPutStr h "# hake: omitted rule with no output: "
347         doBody
348     else do
349         printTokens h $ ruleOutputs compiledRule
350         if double_colon then hPutStr h ":: " else hPutStr h ": "
351         printTokens h $ ruleDepends compiledRule
352         printDirs h $ ruleDirs compiledRule
353         when (not (S.null (rulePreDepends compiledRule))) $ do
354             hPutStr h " | "
355             printTokens h $ rulePreDepends compiledRule
356         hPutStrLn h ""
357         doBody
358     where
359         compiledRule = compileRule tokens
360
361         doBody :: IO (S.Set FilePath)
362         doBody = do
363             when (ruleBody compiledRule /= []) $ do
364                 hPutStr h "\t"
365                 mapM_ (hPutStr h . formatToken) $ ruleBody compiledRule
366             hPutStrLn h "\n"
367             return $ ruleDirs compiledRule
368
369 ---
370 --- Functions to resolve relative path names in rules. 
371 ---
372 --- First, the outer function: resolve path names in an HRule. The
373 --- third argument, 'root', is frequently the pathname of the Hakefile
374 --- relative to the source tree - since relative pathnames in
375 --- Hakefiles are interpreted relative to the Hakefile location.
376 ---
377 resolvePaths :: Opts -> FilePath -> HRule -> HRule
378 resolvePaths o hakepath (Rules hrules)
379     = Rules $ map (resolvePaths o hakepath) hrules
380 resolvePaths o hakepath (HakeTypes.Rule tokens)
381     = HakeTypes.Rule $ map (resolveTokenPath o hakepath) tokens
382 resolvePaths o hakepath (Include token)
383     = Include $ resolveTokenPath o hakepath token
384 resolvePaths o hakepath (Error s)
385     = Error s
386 resolvePaths o hakepath (Phony name dbl tokens)
387     = Phony name dbl $ map (resolveTokenPath o hakepath) tokens
388
389 --- Now resolve at the level of individual rule tokens.  At this
390 --- level, we need to take into account the tree (source, build, or
391 --- install).
392 resolveTokenPath :: Opts -> FilePath -> RuleToken -> RuleToken
393 -- An input token specifies which tree it refers to.
394 resolveTokenPath o hakepath (In tree arch path) = 
395     (In tree arch (treePath o tree arch path hakepath))
396 -- An output token implicitly refers to the build tree.
397 resolveTokenPath o hakepath (Out arch path) = 
398     (Out arch (treePath o BuildTree arch path hakepath))
399 -- A dependency token specifies which tree it refers to.
400 resolveTokenPath o hakepath (Dep tree arch path) = 
401     (Dep tree arch (treePath o tree arch path hakepath))
402 -- A non-dependency token specifies which tree it refers to.
403 resolveTokenPath o hakepath (NoDep tree arch path) = 
404     (NoDep tree arch (treePath o tree arch path hakepath))
405 -- A pre-dependency token specifies which tree it refers to.
406 resolveTokenPath o hakepath (PreDep tree arch path) = 
407     (PreDep tree arch (treePath o tree arch path hakepath))
408 -- An target token implicitly refers to the build tree.
409 resolveTokenPath o hakepath (Target arch path) = 
410     (Target arch (treePath o BuildTree arch path hakepath))
411 -- Other tokens don't contain paths to resolve.
412 resolveTokenPath _ _ token = token
413
414 --- Now we get down to the nitty gritty.  We have, in order:
415 ---   o:        The options in force
416 ---   tree:     The tree (source, build, or install)
417 ---   arch:     The architecture (e.g. armv7)
418 ---   path:     The pathname we want to resolve
419 ---   hakepath: The directory containing the Hakefile
420 --- If the tree is SrcTree or the architecture is "root", everything
421 --- is relative to the top-level directory for that tree.  Otherwise,
422 --- it's relative to the top-level directory plus the architecture.
423 treePath :: Opts -> TreeRef -> FilePath -> FilePath -> FilePath -> FilePath
424
425 treePath o SrcTree "root" path hakepath = 
426     relPath (opt_sourcedir o) path hakepath
427 treePath o BuildTree "root" path hakepath = 
428     relPath "." path hakepath
429 treePath o InstallTree "root" path hakepath = 
430     relPath (opt_installdir o) path hakepath
431
432 treePath o SrcTree arch path hakepath =
433     relPath (opt_sourcedir o) path hakepath
434 treePath o BuildTree arch path hakepath =
435     relPath ("." </> arch) path hakepath
436 treePath o InstallTree arch path hakepath =
437     relPath (opt_installdir o </> arch) path hakepath
438
439 --- This is where the work is done: take 'hd' (pathname relative to
440 --- us of the Hakefile) and resolve the filename we're interested in
441 --- relative to this.  This gives us a pathname relative to some root
442 --- of some architecture tree, then return this relative to the actual
443 --- tree we're interested in.  It's troubling that this takes more
444 --- bytes to explain than to code.
445 ---   d:    Pathname of top directory of the tree (source, build, install)
446 ---   f:    Filename we are interested in, relative to 'root' below
447 ---   hd:   Directory containing the Hakefile
448 ---   
449 relPath treeroot path hakepath =
450     treeroot </> stripSlash (hakepath </> path)
451
452 -- Strip any leading slash from the filename.  This is much faster than
453 -- 'makeRelative "/"'.
454 stripSlash :: FilePath -> FilePath
455 stripSlash ('/':cs) = cs
456 stripSlash cs = cs
457
458 makeHakeDeps :: Handle -> Opts -> [String] -> IO ()
459 makeHakeDeps h o l = do
460     makefileRule h rule
461     hPutStrLn h ".DELETE_ON_ERROR:\n" -- this applies to all targets in the Makefile
462     where
463         hake = resolveTokenPath o "" (In InstallTree "root" "/hake/hake")
464         makefile = resolveTokenPath o "/" (Out "root" (opt_makefilename o))
465         rule = HakeTypes.Rule
466                     ( [ hake, 
467                         Str "--source-dir", Str (opt_sourcedir o),
468                         Str "--install-dir", Str (opt_installdir o),
469                         Str "--output-filename", makefile
470                       ] ++
471                       [ Dep SrcTree "root" h | h <- l ]
472                     )
473
474 makeDirectories :: Handle -> S.Set FilePath -> IO ()
475 makeDirectories h dirs = do
476     hPutStrLn h "# Directories follow"
477     mapM_ (makeDir h) (S.toList (S.delete ("." </> ".marker") dirs))
478
479 makeDir :: Handle -> FilePath -> IO ()
480 makeDir h dir = do
481     hPutStrLn h $ "hake_dirs: " ++ dir ++ "\n"
482     hPutStrLn h $ dir ++ ":"
483     hPutStrLn h $ "\tmkdir -p " ++ (takeDirectory dir)
484     hPutStrLn h $ "\ttouch " ++ dir
485     hPutStrLn h ""
486
487 scanTokens :: [RuleToken] -> (S.Set RuleToken, S.Set RuleToken,
488                               S.Set RuleToken, [RuleToken],
489                               S.Set FilePath)
490 scanTokens [] = (S.empty, S.empty, S.empty, [], S.empty)
491 scanTokens (t:ts) =
492     case t of
493         Out _ f      -> (S.insert t outs, deps, predeps, body', dirs' f)
494         Target _ f   -> (S.insert t outs, deps, predeps, body', dirs' f)
495         In _ _ f     -> (outs, S.insert t deps, predeps, body', dirs' f)
496         Dep _ _ f    -> (outs, S.insert t deps, predeps, body', dirs' f)
497         PreDep _ _ f -> (outs, deps, S.insert t predeps, body', dirs' f)
498         NoDep _ _ f  -> (outs, deps, predeps, body', dirs' f)
499         _            -> (outs, deps, predeps, body', dirs)
500     where
501         (outs, deps, predeps, body, dirs) = scanTokens ts
502         body' = if inRule t then t:body else body
503         dirs' f = if Path.isBelow (takeDirectory f) "." &&
504                      takeDirectory f /= "."
505                   then S.insert (dirOf f) dirs else dirs
506
507         dirOf :: FilePath -> FilePath
508         dirOf f = (takeDirectory f) </> ".marker"
509
510
511 data CompiledRule =
512     CompiledRule {
513         ruleOutputs    :: S.Set RuleToken,
514         ruleDepends    :: S.Set RuleToken,
515         rulePreDepends :: S.Set RuleToken,
516         ruleBody       :: [RuleToken],
517         ruleDirs       :: S.Set FilePath
518     }
519
520 compileRule :: [RuleToken] -> CompiledRule
521 compileRule tokens
522     = CompiledRule {
523         ruleOutputs    = outs,
524         ruleDepends    = deps,
525         rulePreDepends = predeps,
526         ruleBody       = body,
527         ruleDirs       = dirs
528         }
529     where
530         (outs, deps, predeps, body, dirs) = scanTokens tokens
531
532 gcStats :: IO ()
533 gcStats = do
534     performGC
535     gc_stats <- getGCStats
536     putStrLn $ show (currentBytesUsed gc_stats) ++ " - " ++
537                show (numGcs gc_stats) ++ " - " ++
538                show (maxBytesUsed gc_stats) ++ " - " ++
539                show (wallSeconds gc_stats)
540
541 body :: HakeMonad ()
542 body =  do
543     -- parse arguments; architectures default to config file
544     args <- liftIO $ System.Environment.getArgs
545     let o1 = parse_arguments args
546         al = if opt_architectures o1 == [] 
547              then Config.architectures 
548              else opt_architectures o1
549         opts' = o1 { opt_architectures = al }
550
551     when (opt_usage_error opts') $
552         throwError (HakeError usage 1)
553
554     -- sanity-check configuration settings
555     -- this is currently known at compile time, but might not always be!
556     when (isJust configErrors) $
557         throwError (HakeError ("Error in configuration: " ++
558                                (fromJust configErrors)) 2)
559
560     -- Canonicalise directories
561     abs_sourcedir   <- liftIO $ canonicalizePath $ opt_sourcedir opts'
562     abs_bfsourcedir <- liftIO $ canonicalizePath $ opt_bfsourcedir opts'
563     abs_installdir  <- liftIO $ canonicalizePath $ opt_installdir opts'
564     let opts = opts' { opt_abs_sourcedir   = abs_sourcedir,
565                        opt_abs_bfsourcedir = abs_bfsourcedir,
566                        opt_abs_installdir  = abs_installdir }
567
568     liftIO $ putStrLn ("Source directory: " ++ opt_sourcedir opts ++
569                        " (" ++ opt_abs_sourcedir opts ++ ")")
570     liftIO $ putStrLn ("BF Source directory: " ++ opt_bfsourcedir opts ++
571                        " (" ++ opt_abs_bfsourcedir opts ++ ")")
572     liftIO $ putStrLn ("Install directory: " ++ opt_installdir opts ++
573                        " (" ++ opt_abs_installdir opts ++ ")")
574
575     liftIO gcStats
576
577     liftIO $ putStrLn "Reading directory tree..."
578     (allfiles, hakefiles) <- liftIO $ listFiles (opt_sourcedir opts)
579     let relfiles = map (makeRelative $ opt_sourcedir opts') allfiles
580
581     liftIO gcStats
582
583     liftIO $ putStrLn $ "Opening " ++ (opt_makefilename opts)
584     makefile <- liftIO $ openFile(opt_makefilename opts) WriteMode
585     liftIO $ makefilePreamble makefile opts args
586     liftIO $ makeHakeDeps makefile opts $ map fst hakefiles
587
588     liftIO gcStats
589
590     liftIO $ putStrLn $ "Evaluating " ++ show (length hakefiles) ++
591                         " Hakefiles..."
592     dirs <- liftIO $ evalHakeFiles makefile opts relfiles hakefiles
593
594     liftIO gcStats
595
596     liftIO $ putStrLn $ show $ S.size dirs
597
598     liftIO $ putStrLn $ "Generating build directory dependencies..."
599     liftIO $ makeDirectories makefile dirs
600
601     liftIO gcStats
602
603     return ()
604
605 main :: IO () 
606 main = do
607     r <- runErrorT $ body `catchError` handleFailure
608     exitWith ExitSuccess
609     where
610         handleFailure (HakeError str n) = do
611             liftIO $ putStrLn str
612             liftIO $ exitWith (ExitFailure n)