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