{-# 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 -- 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 -- | Execute Make action, place the recipe above all other recipes (it will be -- 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) -- | Adds the phony target for a rule. Typical usage: -- -- > rule $ do -- > phony "clean" -- > unsafeShell [cmd|rm $elf $os $d|] -- > phony :: String -> A () phony name = do produce (W.fromFilePath name :: File) markPhony -- | Build a Recipe using recipe builder @act. Don't change recipe's priority. rule2 :: (MonadMake m) => A a -> m (Recipe,a) rule2 act = liftMake $ do loc <- getLoc (r,a) <- runA loc act addRecipe r return (r,a) -- | Version of rule2 which places it's recipe above all other recipies. -- -- > let c = file "main.c" -- -- Declare a rule to build "main.o" out of "main.c" and "CFLAGS" variable -- -- > rule $ shell [cmd| gcc -c $(extvar "CFLAGS") -o @(c.="o") $c |] -- rule :: A a -- ^ Rule builder -> Make a rule act = snd <$> withPlacement (rule2 act) -- | Version of rule2, without Make monad set explicitly rule' :: (MonadMake m) => A a -> m a rule' act = liftMake $ snd <$> withPlacement (rule2 act)