{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} module Development.Cake3 ( Variable , Recipe , RefInput(..) , RefOutput(..) , CakeString , string -- Monads , A , Make , buildMake , runMake , writeMake , includeMakefile , MonadMake(..) -- Rules , rule , rule2 , rule' , phony , depend , produce , ignoreDepends , prebuild , postbuild -- Files , FileLike(..) , File , file' , (.=) , () , toFilePath , readFileForMake , genFile , genTmpFile , genTmpFileWithPrefix -- Make parts , prerequisites , shell , unsafeShell , cmd , makevar , extvar , CommandGen'(..) , make , ProjectLocation(..) , currentDirLocation -- Import several essential modules , module Data.String , module Control.Monad , module Control.Applicative ) where import Control.Applicative import Control.Monad import Control.Monad.Trans import Control.Monad.Writer import Control.Monad.State import Control.Monad.Loc import qualified Data.List as L import Data.List (concat,map, (++), reverse,elem,intercalate,delete) import Data.Foldable (Foldable(..), foldr) import qualified Data.Map as M import Data.Map (Map) import qualified Data.Set as S import Data.Set (Set,member,insert) import Data.String import Data.Tuple import System.IO import System.Directory import qualified System.FilePath as F import Text.Printf import Development.Cake3.Types import Development.Cake3.Writer import Development.Cake3.Monad import System.FilePath.Wrapper as W data ProjectLocation = ProjectLocation { root :: FilePath , off :: FilePath } deriving (Show, Eq, Ord) currentDirLocation :: (MonadIO m) => m ProjectLocation currentDirLocation = do cwd <- liftIO $ getCurrentDirectory return $ ProjectLocation cwd cwd file' :: ProjectLocation -> String -> File file' pl f' = fromFilePath (addpoint (F.normalise rel)) where rel = makeRelative (root pl) ((off pl) f) f = F.dropTrailingPathSeparator f' addpoint "." = "." addpoint p = "."p runMake' :: File -- ^ Output file -> Make a -- ^ Make builder -> (String -> IO b) -- ^ Handler to output the file -> IO b runMake' makefile mk output = do ms <- evalMake makefile mk when (not $ L.null (warnings ms)) $ do hPutStr stderr (warnings ms) when (not $ L.null (errors ms)) $ do fail (errors ms) case buildMake ms of Left e -> fail e Right s -> output s -- | Execute the Make monad, build the Makefile, write it to the output file. Also -- note, that errors (if any) go to the stderr. fail will be executed in such -- cases writeMake :: File -- ^ Output file -> Make a -- ^ Makefile builder -> IO () writeMake f mk = runMake' f mk (writeFile (toFilePath f)) -- | A General Make runner. Executes the monad, returns the Makefile as a -- String. Errors go to stdout. fail is possible. runMake :: Make a -> IO String runMake mk = runMake' defaultMakefile mk return -- | Raise the recipe's priority (it will appear higher in the final Makefile) withPlacement :: (MonadMake m) => m (Recipe,a) -> m (Recipe,a) withPlacement mk = do (r,a) <- mk liftMake $ do addPlacement 0 (S.findMin (rtgt r)) return (r,a) -- | 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" -- variable -- -- > let c = file "main.c" -- > rule $ shell [cmd| gcc -c $(extvar "CFLAGS") -o @(c.="o") $c |] -- rule2 :: (MonadMake m) => A a -- ^ Recipe builder -> m (Recipe,a) -- ^ The recipe itself and the recipe builder's result rule2 act = liftMake $ do loc <- getLoc (r,a) <- runA loc act addRecipe r return (r,a) -- | A version of rule2. Rule places it's recipe above all recipies defined so -- far. rule :: A a -- ^ Recipe builder -> Make a rule act = snd <$> withPlacement (rule2 act) -- | 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 genTmpFileWithPrefix :: (MonadMake m) => String -> String -> m File genTmpFileWithPrefix pfx cnt = tmpFile pfx >>= \f -> genFile f cnt