packages feed

cake3 0.3.0.1 → 0.4.0.0

raw patch · 8 files changed

+210/−131 lines, 8 files

Files

CHANGELOG view
@@ -1,3 +1,8 @@+Version 0.4+-----------+Autobuild 'clean' rule. genFile and genTmpFile functions allowing to embed text+files into the Makefile.+ Version 0.3 ----------- API changes, improve documentation, improve UrWeb extension
README.md view
@@ -72,8 +72,8 @@ ----------------  Cake3 allows user to write Cakefile.hs in plain Haskell to define rules, targets-and other stuff as usual. After that, `cake3` compiles it into ./Cakegen-application which builds your Makefile (ghc is required for that). GNU Make+and other things as usual. `cake3` executable compiles it into ./Cakegen+application which outputs your Makefile (ghc is required for that). GNU Make knows how to do the rest.  ### Example@@ -102,32 +102,34 @@         shell [cmd| gcc -o @(file "main.elf") $os |]        rule $ do-        phony "clean"-        unsafeShell [cmd|rm $elf $os $d|]--      rule $ do         phony "all"         depend elf        includeMakefile d -   * Cakefile\_P is an autogenerated module. It defines `file :: String -> File`     function plus some others.   * Main building blocks - `rule` functions - build Makefile rules one-to-one.     The prerequisites are computed based on it's actions.   * All actions live in Action monad (`A` Monad). `shell` is the most important     operation of this monad.-  * Quasy-quotation is used to simplify the shell code. `[cmd|..|]` takes a string-    as an argument. It's syntax includes:-    *  $name untiquotes hasell expressions `name` of type File, Variable plus few-       others. They define a prerequisites of a rule-    *  @name untiquotes hasell expressions `name` of type File. They define-       rule's targets-    *  complex expressions are also supported with $(foo bar) and @(bar baz).-    *  $$ and @@ expands to $ and @+  * Quasy-quotation is used to simplify writing of the shell code. `[cmd|..|]`+    takes a string as an argument. The following antiquotations are supported:+    *  $name antiquotes Hasell expressions `name` of type File, Variable, few+       others. The name will be placed to the set of prerequisites of the rule.+    *  @name antiquotes Hasell expressions `name` of type File. The name will be+       placed to the set of rule's targets.+    *  complex Haskell expressions inside antiquotations are supported with+       $(foo bar) and @(bar baz) syntax.+    *  $$ and @@ expands to $ and @.   * Rules appears in the Makefile in the reversed order. Normally, you want-    'all' rule to be at the bottom.+    'all' rule to be defined at the bottom of Cakefile.hs.+  * Starting from 0.4, cake3 outputs rule named 'clean' automatically. This rule+    contains recipe which deletes all intermediate files with 'rm' command.+  * selfUpdate call includes the self-updating dependencies. That means, that+    Makefile will depend on Cakefile.hs and thus will require ghc to present in+    the system. Removing selfUpdate call will make the Makefile fully+    Haskell-independent.   Features and limitations@@ -147,20 +149,23 @@  ### Features -  * *Allows spaces inside the filenames.*+  * *Cake3 generates the 'clean' rule automatically.*+    But if you define your own 'clean', cake3 will take it as is.++  * *Cake3 takes care of spaces inside the filenames.*        Everyone knows that Makefiles don't like spaces in filenames. Cake3     carefully inserts '\ ' to make make happy. -  * *Rebuilds a rule when variable changes.*+  * *Cake3 rebuilds a rule's target when variable changes.*        Consider following antipattern:          # You often write rules like this, don't you?-        out : in-             foo $(FLAGS) -o $@ $^+        program : program.c+             gcc $(FLAGS) -o $@ $^ -    Unfortunately, changes in FLAGS don't lead to rebuilding of out.+    Unfortunately, changes in FLAGS don't lead to rebuilding of the program.     Hardly-trackable bugs may appear if one part of a project was built with one     set of optimisation flags and another part was build with another set by     mistake.@@ -170,20 +175,21 @@     detect changes in variables and rebuild targets when nessesary.          rule $ do-          shell [cmd|foo $(extvar "FLAGS") -o @out $in |]+          shell [cmd|gcc $(extvar "FLAGS") -o @program $program_c |] -    will rebuild `out` on FLAGS change+    will rebuild `program` every time the FLAGS change  -  * *Supports rules with more than one target.*+  * *Rules may have more than one target.*     -    It is not that simple to write a rule which has more than one target. Really,+    It is not that simple to write a rule which has more than one target in+    Makefile. Indeed,                  out1 out2 : in1 in2             foo in1 in2 -o out1 -o out2      is not corret. Read this [Automake     article](http://www.gnu.org/software/automake/manual/html_node/Multiple-Outputs.html#Multiple-Outputs)-    if you are surprised. Thirdcake implements [.INTERMEDIATE+    if you are surprised. Cake3 implements [.INTERMEDIATE     pattern](http://stackoverflow.com/a/10609434/1133157) to deal with this     problem so `rule` like this @@ -192,13 +198,13 @@      will always notice inputs changes and rebuild both outputs -  * *Supports global prebuild\postbuild actions*+  * *Cake3 supports global prebuild\postbuild actions*      Common human-made Makefile with prebuild commands would support them for one     rule, typically, "all". Other targets often stay uncovered. Cake3 makes sure     that actions are executed for any target you call. -  * *Lets user organize build hierarchy.*+  * *Cake3 lets user organize build hierarchy.*        Say, we have a project A with subproject L. L has it's own Makefile and we     want to re-use it from our global A/Makefile. Make provides only two ways of@@ -234,22 +240,24 @@  #### Make syntax -  * Make is not able to detect directory content chages. For example, user has-    to rerun the ./Cakegen if they add/remove a source file. I plan to automate-    this kind of checks. so it is on my TODO list.-  * ifdef/endif instructions is not supported generally. User has to move this-    logic in Haskell for now. Also on my TODO list+As a summary - only a samll subset of Make syntax is supported for generation.+For complex algorithms Haskell looks more suitable so implement everything you+need inside the Cakefile.hs. In particular:++  * Cake3 offers no way of detecting directory content chages. For example, user+    has to rerun the ./Cakegen if they add/remove a source file.   * Cake3 doesn't check the contents of Makefile variables. It is user's     responsibility to keep them safe.   * Variables as targets is not supported. Implement this logic in Haskell for     now. + #### General    * Resulting Makefile is actually a GNUMakefile. GNU extensions (shell function     and others) are needed to make various tricks to work. Also, posix     environment with coreututils package is required. So, Linux, Probably Mac,-    Probably Windows+Cygwin.+    Probably Windows+Cygwin are the platforms which run cake3.   * All Cakefiles across the project tree should have unique names in order to     be copied. Duplicates are found, the first one is used 
cake3.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/  name:                cake3-version:             0.3.0.1+version:             0.4.0.0 synopsis:            Third cake the Makefile EDSL description:         Cake3 is a Makefile EDSL written in Haskell. Write your build logic in                      Haskell, obtain clean and safe Makefile, distribute it to the end-users.@@ -36,9 +36,6 @@     >       shell [cmd| gcc -c $(extvar "CFLAGS") -o @(c.="o") $c |]     >   elf <- rule $ do     >     shell [cmd| gcc -o @(file "main.elf") $os |]-    >   rule $ do-    >     phony "clean"-    >     unsafeShell [cmd|rm $elf $os $d|]     >   rule $ do     >     phony "all"     >     depend elf
src/Development/Cake3.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -40,6 +41,8 @@   , (</>)   , toFilePath   , readFileForMake+  , genFile+  , genTmpFile    -- Make parts   , prerequisites@@ -138,23 +141,10 @@     addPlacement 0 (S.findMin (rtgt r))     return (r,a) --- | Adds the phony target for a rule. Typical usage:--- --- > rule $ do--- >  phony "clean"--- >  unsafeShell [cmd|rm $elf $os $d|]--- >-phony ::-  String -- ^ A name of phony target-  -> A ()-phony name = do-  produce (W.fromFilePath name :: File)-  markPhony---- | Build a Recipe using recipe builder provided, than record the Recipe to the--- MakeState. Return the copy of Recipe (which should not be changed in future)--- and the result of recipe builder. The typical recipe builder result is the--- list of it's targets.+-- | Build a Recipe using the builder provided and record it to the MakeState.+-- Return the copy of Recipe (which should not be changed in future) and the+-- result of recipe builder. The typical recipe builder result is the list of+-- it's targets. -- -- /Example/ -- Lets declare a rule which builds "main.o" out of "main.c" and "CFLAGS"@@ -182,5 +172,20 @@ -- | A version of rule, without monad set explicitly rule' :: (MonadMake m) => A a -> m a rule' act = liftMake $ snd <$> withPlacement (rule2 act)+++genFile :: (MonadMake m) => File -> String -> m File+genFile tgt cnt = rule' $do+  shell [cmd|-rm -rf @tgt |]+  forM_ (lines cnt) $ \l -> do+    shell [cmd|echo '$(string (quote_dollar l))' >> @tgt |]+  return tgt+  where+    quote_dollar [] = []+    quote_dollar ('$':cs) = '$':'$':(quote_dollar cs)+    quote_dollar (c:cs) = c : (quote_dollar cs)++genTmpFile :: (MonadMake m) => String -> m File+genTmpFile cnt = tmpFile >>= \f -> genFile f cnt  
src/Development/Cake3/Ext/UrWeb.hs view
@@ -39,7 +39,7 @@ import Development.Cake3.Monad import Development.Cake3 -data UrpAllow = UrpMime | UrpUrl | UrpResponseHeader+data UrpAllow = UrpMime | UrpUrl | UrpResponseHeader | UrpEnvVar | UrpHeader   deriving(Show,Data,Typeable)  data UrpRewrite = UrpStyle | UrpAll@@ -52,7 +52,7 @@                  | UrpLibrary File                  | UrpDebug                  | UrpInclude File-                 | UrpLink File String+                 | UrpLink (Either File String)                  | UrpSrc File String String                  | UrpPkgConfig String                  | UrpFFI File@@ -101,7 +101,7 @@  urpDeps :: Urp -> [File] urpDeps (Urp _ _ hdr mod) = foldl' scan2 (foldl' scan1 mempty hdr) mod where-  scan1 a (UrpLink f _) = f:a+  scan1 a (UrpLink (Left f)) = f:a   scan1 a (UrpSrc f _ _) = (f.="o"):a   scan1 a (UrpInclude f) = f:a   scan1 a _ = a@@ -126,7 +126,7 @@  urpObjs (Urp _ _ hdr _) = foldl' scan [] hdr where   scan a (UrpSrc f _ lfl) = (f.="o"):a-  scan a (UrpLink f lfl) = (f):a+  scan a (UrpLink (Left f)) = (f):a   scan a _ = a  urpLibs (Urp _ _ hdr _) = foldl' scan [] hdr where@@ -153,7 +153,9 @@  instance ToUrpWord UrpAllow where   toUrpWord (UrpMime) = "mime"+  toUrpWord (UrpHeader) = "requestHeader"   toUrpWord (UrpUrl) = "url"+  toUrpWord (UrpEnvVar) = "env"   toUrpWord (UrpResponseHeader) = "responseHeader"  instance ToUrpWord UrpRewrite where@@ -175,8 +177,9 @@     | otherwise = printf "library %s" (up </> toFilePath (dropExtension f))   toUrpLine up (UrpDebug) = printf "debug"   toUrpLine up (UrpInclude f) = printf "include %s" (up </> toFilePath f)-  toUrpLine up (UrpLink f lfl) = printf "link %s %s" lfl (up </> toFilePath f)-  toUrpLine up (UrpSrc f _ lfl) = printf "link %s %s" lfl (up </> toFilePath (f.="o"))+  toUrpLine up (UrpLink (Left f)) = printf "link %s" (up </> toFilePath f)+  toUrpLine up (UrpLink (Right lfl)) = printf "link %s" lfl+  toUrpLine up (UrpSrc f _ _) = printf "link %s" (up </> toFilePath (f.="o"))   toUrpLine up (UrpPkgConfig s) = printf "link %s" (maskPkgCfg s)   toUrpLine up (UrpFFI s) = printf "ffi %s" (up </> toFilePath (dropExtensions s))   toUrpLine up (UrpSafeGet s) = printf "safeGet %s" (dropExtensions s)@@ -192,11 +195,10 @@ newtype UrpGen m a = UrpGen { unUrpGen :: StateT UrpState m a }   deriving(Functor, Applicative, Monad, MonadState UrpState, MonadMake, MonadIO) --- instance (Monad m) => MonadAction (UrpGen (A' m)) m where---   liftAction a = UrpGen (lift a)- toFile f wr = liftIO $ writeFile (toFilePath f) $ execWriter $ wr +toTmpFile wr = genTmpFile $ execWriter $ wr+ line :: (MonadWriter String m) => String -> m () line s = tell (s++"\n") @@ -206,35 +208,32 @@   let u@(Urp _ _ hdr mod) = urpst s   let pkgcfg = (urpPkgCfg u) -  inp <- rule' $ do-    let inp = urpfile .= "urp.in"-    toFile inp $ do+  forM_ (urpSrcs u) $ \(c,fl) -> do+    let flags = concat $ fl : map (\p -> printf "$(shell pkg-config --cflags %s) " p) (urpPkgCfg u)+    let i = makevar "URINCL" "-I$(shell urweb -print-cinclude) " +    let cc = makevar "URCC" "$(shell $(shell urweb -print-ccompiler) -print-prog-name=gcc)"+    let cpp = makevar "URCPP" "$(shell $(shell urweb -print-ccompiler) -print-prog-name=g++)"+    let incfl = extvar "UR_CFLAGS"+    rule2 $ do+      case takeExtension c of+        ".cpp" -> shell [cmd| $cpp -c $incfl $i $(string flags) -o @(c .= "o") $(c) |]+        ".c" -> shell [cmd| $cc -c $i $incfl $(string flags) -o @(c .= "o") $(c) |]+        e -> error ("Unknown C-source extension " ++ e)++  inp_in <- toTmpFile $ do       forM hdr (line . toUrpLine (urpUp urpfile))       line ""       forM mod (line . toUrpLine (urpUp urpfile)) -    forM_ (urpSrcs u) $ \(c,fl) -> do-      let flags = concat $ fl : map (\p -> printf "$(shell pkg-config --cflags %s) " p) (urpPkgCfg u)-      let i = makevar "URINCL" "-I$(shell urweb -print-cinclude) " -      let cc = makevar "URCC" "$(shell $(shell urweb -print-ccompiler) -print-prog-name=gcc)"-      let cpp = makevar "URCPP" "$(shell $(shell urweb -print-ccompiler) -print-prog-name=g++)"-      rule2 $ do-        case takeExtension c of-          ".cpp" -> shell [cmd| $cpp -c $i $(string flags) -o @(c .= "o") $(c) |]-          ".c" -> shell [cmd| $cc -c $i -o $(string flags) @(c .= "o") $(c) |]-          e -> error ("Unknown C-source extension " ++ e)--    depend (urpDeps u)-    depend (urpLibs u)-    shell [cmd|touch @inp|]-   rule' $ do-    let cpy = [cmd|cat $inp|] :: CommandGen' (Make' IO)+    let cpy = [cmd|cat $inp_in|] :: CommandGen' (Make' IO)     let l = foldl' (\a p -> do                             let l = makevar (map toUpper $ printf "lib%s" p) (printf "$(shell pkg-config --libs %s)" p)                             [cmd| $a | sed 's@@$(string $ maskPkgCfg p)@@$l@@'  |]                             ) cpy pkgcfg     shell [cmd| $l > @urpfile |]+    depend (urpDeps u)+    depend (urpLibs u)    return $ UWLib u @@ -269,17 +268,6 @@ urpUp :: File -> FilePath urpUp f = F.joinPath $ map (const "..") $ filter (/= ".") $ F.splitDirectories $ F.takeDirectory $ toFilePath f --- | Dir name , file to embed--- data UrEmbed = Urembed File File---   deriving (Show)---- data UrpLibReference---   = UrpLibStandaloneMake File ---   | UrpLibStandaloneMake2 File ---   | UrpLibInternal UWLib---   | UrpLibEmbed File File---   deriving(Show)- -- | A general method of including a library into the UrWeb project. library' :: (MonadMake m)   => Make [File] -- ^ A monadic action, returning a list of libraries to include@@ -332,13 +320,19 @@ include f = addHdr $ UrpInclude f  link' :: (MonadMake m) => File -> String -> UrpGen m ()-link' f fl = addHdr $ UrpLink f fl+link' f fl = do+  addHdr $ UrpLink (Left f)+  when (fl /= "") $ do+    addHdr $ UrpLink (Right fl)  link :: (MonadMake m) => File -> UrpGen m () link f = link' f []  csrc'  :: (MonadMake m) => File -> String -> String -> UrpGen m ()-csrc' f cfl lfl = addHdr $ UrpSrc f cfl lfl+csrc' f cfl lfl = do+  addHdr $ UrpSrc f cfl lfl+  when (lfl /= "") $ do+    addHdr $ UrpLink (Right lfl)  csrc  :: (MonadMake m) => File -> UrpGen m () csrc f = csrc' f [] []@@ -351,7 +345,10 @@    jsFunc m u j = addHdr $ UrpJSFunc m u j -safeGet s = addHdr $ UrpSafeGet s+safeGet :: (MonadMake m) => File -> String -> UrpGen m ()+safeGet m fn+  | (takeExtension m) /= ".ur" = fail (printf "safeGet: not an Ur/Web module name specified (%s)" (toFilePath m))+  | otherwise = addHdr $ UrpSafeGet (printf "%s/%s" (takeBaseName m) fn)  url = UrpUrl @@ -361,6 +358,10 @@  all = UrpAll +env = UrpEnvVar++hdr = UrpHeader+ responseHeader = UrpResponseHeader  script :: (MonadMake m) => String -> UrpGen m ()@@ -463,6 +464,8 @@ bin' :: (MonadIO m, MonadMake m) => File -> FilePath -> BS.ByteString -> UrpGen m () bin' dir src_name src_contents = do +  liftIO $ createDirectoryIfMissing True (toFilePath dir)+   let mime = guessMime src_name   let mn = (mkname src_name)   let wrapmod ext = (dir </> mn) .= ext@@ -523,7 +526,7 @@     line $ "val text : unit -> transaction string"    include (binmod ".h")-  link (binmod ".o")+  csrc (binmod ".c")   ffi (binmod ".urs")    -- JavaScript FFI Module@@ -562,8 +565,8 @@     addHdr $ UrpJSFunc (modname jsmod) (urname decl) (jsname decl)   ffi (jsmod ".urs") -  safeGet $ printf "%s/blobpage" (modname wrapmod)-  safeGet $ printf "%s/blob" (modname wrapmod)+  safeGet (wrapmod ".ur") "blobpage"+  safeGet (wrapmod ".ur") "blob"   module_ (pair $ wrapmod ".ur")    where
src/Development/Cake3/Ext/UrWeb/UrEmbed.hs view
@@ -86,19 +86,16 @@   when (null ins) $ do     fail "At least one file should be specified, see --help" -  exists <- doesDirectoryExist tgtdir-  when (not exists) $ do-    fail $ "Couldn't create output file, no such directory " ++ show tgtdir+  -- exists <- doesDirectoryExist tgtdir+  -- when (not exists) $ do+  --   fail $ "Couldn't create output file, no such directory " ++ show tgtdir+  liftIO $ createDirectoryIfMissing True tgtdir    when (null tgtdir) $ do     fail "An output directory should be specified, use -o"    when (null ins) $ do     fail "At least one file should be specified, see --help"--  exists <- doesDirectoryExist tgtdir-  when (not exists) $ do-    fail "Output is not a directory"    cntnts <- mapM BS.readFile ins 
src/Development/Cake3/Monad.hs view
@@ -67,10 +67,13 @@   , warnings :: String     -- ^ Warnings found so far   , outputFile :: File+    -- ^ Name of the Makefile being generated+  , tmpIndex :: Int+    -- ^ Index to build temp names   }  -- Oh, such a boilerplate-initialMakeState mf = MS defr defr mempty mempty mempty mempty mempty mempty mempty mf where+initialMakeState mf = MS defr defr mempty mempty mempty mempty mempty mempty mempty mf 0 where   defr = emptyRecipe "<internal>"  getPlacementPos :: Make Int@@ -83,6 +86,12 @@ addMakeDep :: File -> Make () addMakeDep f = modify (\ms -> ms { makeDeps = S.insert f (makeDeps ms) }) +tmpFile :: (MonadMake m) => m File+tmpFile = liftMake $ do+  s <- get+  put s{tmpIndex = (tmpIndex s)+1}+  return (fromFilePath (".cake3" </> ("tmp"++(show (tmpIndex s)))))+ -- | Add prebuild command prebuild, postbuild :: (MonadMake m) => CommandGen -> m () prebuild cmdg = liftMake $ do@@ -203,6 +212,19 @@ -- files. Makefile-specific. markPhony :: (Monad m) => A' m () markPhony = modify $ \r -> r { rflags = S.insert Phony (rflags r) }++-- | Adds the phony target for a rule. Typical usage:+-- +-- > rule $ do+-- >  phony "clean"+-- >  unsafeShell [cmd|rm $elf $os $d|]+-- >+phony :: (Monad m)+  => String -- ^ A name of phony target+  -> A' m ()+phony name = do+  produce (fromFilePath name :: File)+  markPhony  -- | Mark the recipe as 'INTERMEDIATE' i.e. claim that all it's targets may be -- removed after the build process. Makefile-specific.
src/Development/Cake3/Writer.hs view
@@ -9,13 +9,14 @@ import Control.Monad.Identity (runIdentity) import Control.Monad.State (MonadState, StateT(..), runStateT, State(..), execState, evalState, runState, modify, get, put) import Control.Monad.Trans+import Control.Monad.Writer (MonadWriter, WriterT(..), execWriterT, execWriter, tell) import Data.List as L import Data.Char import Data.Maybe (catMaybes) import Data.Monoid import Data.String-import Data.Foldable (Foldable(..), foldl')-import Data.Foldable (forM_)+import Data.Foldable (Foldable(..), forM_)+import qualified Data.Foldable as F import Data.Traversable (forM) import Data.Set (Set) import qualified Data.Set as S@@ -72,18 +73,18 @@ runMakeLL templ m = snd $ execState (unMakeLL m) (names, S.empty) where   names = map fromFilePath $ map (\x -> printf ".%s%d" templ x) $ ([1..] :: [Int]) -produceLL :: Recipe -> MakeLL ()-produceLL r = modify (\(a,b) -> (a,S.insert r b))+copyRecipeLL :: Recipe -> MakeLL ()+copyRecipeLL r = modify (\(a,b) -> (a,S.insert r b))  ruleLL :: A' MakeLL a -> MakeLL Recipe ruleLL act = do   (r,_) <- runA "<internal>" act-  produceLL r+  copyRecipeLL r   return r  applySubprojects :: Map File [Command] -> Set Recipe -> Set Recipe applySubprojects sp rs = runMakeLL "subproject" (transformRecipesM_ f rs) where-  f r | L.null scmds = produceLL r+  f r | L.null scmds = copyRecipeLL r       | otherwise = do           n1 <- fresh           n2 <- fresh@@ -139,7 +140,7 @@             markIntermediate           return ()       | otherwise = do-          produceLL r+          copyRecipeLL r  -- | Operate on a prerequisites which themselfs are targets of a multitarget rule. Make -- the conversion from:@@ -167,6 +168,34 @@           True -> r { rsrc = (rsrc r) `S.union` mulpack }           False -> r) r badlist +hasClean :: (Foldable t) => t Recipe -> Bool+hasClean rs = F.foldl' flt False rs where+  flt True _ = True+  flt False r = (Phony `S.member`(rflags r)) && ((fromFilePath "clean")`S.member`(rtgt r))++intermediateFiles :: (Foldable t) => t Recipe -> Set File+intermediateFiles rs = +  execWriter $ do+    forM_ rs $ \r -> do+      when (not $ Phony `S.member` (rflags r)) $ do+        tell (rtgt r)++cleanRuleLL :: Set File -> MakeLL Recipe+cleanRuleLL fs =+  ruleLL $ do+    phony "clean"+    unsafeShell [cmd|-rm $(fs)|]+    unsafeShell [cmd|-rm -rf .cake3|]++-- | Define a 'clean' phony target. The rule removes all targets except phony+-- targets and the Makefile itself+defineClean :: File -> Set Recipe -> Set Recipe+defineClean mk rs =+  runMakeLL "defineClean" $ do+    let fs = intermediateFiles rs+    cleanRuleLL (fs `S.difference` (S.singleton mk))+    return ()+ -- | Rule referring to the  defaultMakefile :: File defaultMakefile = fromFilePath ("." </> "Makefile")@@ -181,17 +210,14 @@   godeeper = or $ map (\tgt -> or $ map (\r -> isRequiredFor rs r f) (selectBySrc tgt)) (S.toList $ rtgt r)   selectBySrc f = S.toList . fst $ S.partition (\r -> f`S.member`(rsrc r)) rs --- | There are only 2 kind of rules: 1) ones that depend on a Makefile, and 2) ones--- that Makefile depends on. Case-2 is known in advance (for example, when the--- the contents of a file is required to build a Makefile then Makefile depends--- on this file). This function adds the case-1 dependencies.+-- | There are only 2 kind of rules: 1) rules which depend on the Makefile, and+-- 2) rules which Makefile depends on. Case-2 is known in advance (for example,+-- when the contents of a file is required to build a Makefile then Makefile+-- depends on this file). This function adds the case-1 dependencies. addMakeDeps :: File -> Set Recipe -> Set Recipe-addMakeDeps makefile rs-  | S.null (S.filter (\r -> makefile `S.member` (rtgt r)) rs) = rs-  | otherwise = S.map addMakeDeps_ rs-  where-    addMakeDeps_ r | not (isRequiredFor rs r makefile) = addPrerequisite makefile r-                   | otherwise = r+addMakeDeps makefile rs = S.map addMakeDeps_ rs where+  addMakeDeps_ r | not (isRequiredFor rs r makefile) = addPrerequisite makefile r+                 | otherwise = r   @@ -210,19 +236,34 @@     line ""    sr <- region "SERVICE" $ do-    line ""-    line "# Prebuild/postbuild section"-    line ""     r <- runA_ "<internal>" $ do       produce (queryTargets (recipes ms))       unsafeShell [cmd|-mkdir .cake3|]       commands (rcmd $ prebuilds ms)-      unsafeShell [cmd|$(make) -f $(outputFile ms) MAIN=1 $(makecmdgoals)|]+      unsafeShell [cmd|$(make) -f $(outputFile ms) $(makecmdgoals)|]       commands (rcmd $ postbuilds ms)       markPhony-    writeRules $ applyPlacement (placement ms) $ fixMultiTarget [r]+     line ""+    line "# Prebuild/postbuild section"+    line ""+    line "export MAIN=1" +    line "" +    case hasClean (recipes ms) of+      True -> do+        writeRules $ applyPlacement (placement ms)+                   $ fixMultiTarget [r]+      False -> do+        line "ifneq ($(MAKECMDGOALS),clean)"+        line ""+        writeRules $ applyPlacement (placement ms)+                   $ fixMultiTarget [r]+        line ""+        line "endif"+        writeRules $ defineClean (outputFile ms) (recipes ms)+    line ""+   hdr <- runLines $ do     line "# This Makefile was generated by the Cake3"     line "# https://github.com/grwlf/cake3"@@ -264,7 +305,8 @@     | otherwise = do       inner <- writeRegions' rs       runLines $ do-        line (printf "ifdef %s" (map toUpper $ mrname r))+        line (printf "ifeq ($(%s),1)" (map toUpper $ mrname r))+        line (printf "unexport %s" (map toUpper $ mrname r))         text (mrtext r)         line "else"         text inner