import Control.Exception import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Except import Data.List import Data.Maybe import Data.Monoid import System.Console.GetOpt import System.Directory import System.Environment import System.Exit import System.FilePath import System.IO import System.IO.Error import Lib data Flag = Changed | Created | Help deriving (Show,Read,Eq) clOption :: [OptDescr Flag] clOption = [ Option "" ["if"] (ReqArg read "MODE") "Redo mode, valid modes are: IfChanged, IfCreated" , Option "h" ["help"] (NoArg Help) "Print usage info" ] variableInfo :: String variableInfo = unlines [ "Environment variables:" , "\tGDO_DEFAULTS : Path to directory containing default do files" ] usageHeader :: String usageHeader = unlines [ "gdo" ] printUsage :: IO () printUsage = do putStrErr $ usageInfo usageHeader clOption putStrLn "" putStrLn variableInfo getCliOpts :: (MonadIO m) => ExceptT ExitCode m ([Flag],[FilePath]) getCliOpts = do args <- liftIO getArgs case getOpt RequireOrder clOption args of (_,_,_:_) -> do liftIO printUsage throwE (ExitFailure 1) (opts,deps,_) -> return (opts,deps) optsToMode :: [Flag] -> Maybe Flag optsToMode flags = getFirst . mconcat . map (First . ($flags)) $ [ find (==Help) , listToMaybe . reverse ] withTarget :: (MonadIO m) => FilePath -> (FilePath -> m a) -> m a withTarget target fun = do dir <- liftIO getCurrentDirectory let (targetDir,file) = splitFileName target liftIO $ setCurrentDirectory targetDir result <- fun file liftIO $ setCurrentDirectory dir return result main :: IO () main = do defaultDirs <- getDefaultDirs either exitWith (const exitSuccess) =<< runExceptT (do (mMode,deps) <- getCliOpts case optsToMode mMode of Just Changed -> do buildTarget <- getBuildTarget mapM_ (redoIfChanged defaultDirs buildTarget) deps Just Created -> do buildTarget <- getBuildTarget mapM_ (redoIfCreated defaultDirs buildTarget) deps Just Help -> liftIO printUsage Nothing -> mapM_ (`withTarget` redo defaultDirs) deps return () ) redo :: (MonadIO m) => [FilePath] -> FilePath -> ExceptT ExitCode m () redo mDefaultsDir target = do targetDoesExist <- liftIO $ doesFileExist target isUpToDate <- upToDate mDefaultsDir target unless (isUpToDate && targetDoesExist) $ do void . liftIO . tryJust (guard . isDoesNotExistError) $ deleteDepDir maybe (do targetExists <- liftIO $ doesFileExist target unless targetExists $ do (liftIO . putStrLnErr) ("Neither target nor do file found for "++show target) throwE (ExitFailure 1)) (\recipe -> do liftIO $ putStrLnErr $ "redo "++show target redoIfCreated mDefaultsDir target (target++".do") redoIfChanged mDefaultsDir target recipe buildResult <- liftIO $ executeRecipe target recipe when (buildResult /= ExitSuccess) (do liftIO deleteDepDir throwE buildResult) liftIO $ putStrLnErr $ "redone "++show target) =<< getRecipeFile mDefaultsDir target where deleteDepDir = removeDirectoryRecursive =<< getMetaDir target redoIfCreated :: (MonadIO m) => [FilePath] -- ^ directory path of defaults -> FilePath -- ^ build target -> FilePath -- ^ path of the dependency -> ExceptT ExitCode m () redoIfCreated mDefaultsDir buildTarget dep = do depExists <- liftIO $ doesFileExist dep if depExists then redoIfChanged mDefaultsDir buildTarget dep else do metadir <- getMetaDir buildTarget missingDepsFile <- getMissingDepsFile buildTarget liftIO $ createDirectoryIfMissing True metadir liftIO $ withFile missingDepsFile AppendMode (`hPutStrLn` dep) redoIfChanged :: MonadIO m => [FilePath] -- ^ directory of defaults -> FilePath -- ^ build target -> FilePath -- ^ path of the dependency -> ExceptT ExitCode m () redoIfChanged mDefaultsDir buildTarget dep = do (`withTarget` redo mDefaultsDir) dep liftIO $ recordDependency buildTarget dep getBuildTarget :: (MonadIO m) => ExceptT ExitCode m FilePath getBuildTarget = do mTarget <- liftIO $ lookupEnv "REDO_TARGET" case mTarget of Nothing -> do putStrLnErr "Could not detect build target" throwE $ ExitFailure 1 Just target -> return target