module Coadjute.Task.Required (splitUnnecessary) where
import Control.Monad (foldM, when)
import Control.Monad.State (get, put, StateT, runStateT)
import Data.Graph.Inductive (labNodes, delNodes)
import Data.Maybe (fromJust, isJust, isNothing)
import Data.Set (Set)
import qualified Data.Set as Set
import System.Directory (getModificationTime)
import System.Time (ClockTime)
import Coadjute.CoData
import Coadjute.DB ( MDB, DB (hasHashes), addEntry
, Datum, dbLookup, hasHash, dArgs)
import Coadjute.Hash (Hash, hashFile)
import Coadjute.Task (TaskGraph, Task(..), Target)
import Coadjute.Util.File (allPathsExist)
import Coadjute.Util.Monad (anyM)
splitUnnecessary :: MDB -> TaskGraph -> CoData (TaskGraph, [Task], MDB)
splitUnnecessary mdb gr =
let tNodes = labNodes gr
tasks = map snd tNodes
f = do
usingDB <- asks coUsingDB
if usingDB && isJust mdb
then do
let db = fromJust mdb
checkDep <- selectCheckDepFunc db
(needs,db') <-
foldM (needsBuildingWithDB checkDep) ([],db) tasks
return (reverse needs, Just db')
else do
needs <- io$ mapM needsBuildingWithoutDB tasks
return (needs, mdb)
in do
(needs, mdb') <- f
let (upToDateNodes, upToDateTasks) =
unzip . map snd . filter (not.fst) $ zip needs tNodes
return (delNodes upToDateNodes gr, upToDateTasks, mdb')
needsBuildingWithoutDB :: Task -> IO Bool
needsBuildingWithoutDB t =
anyM ($t) [needsBuildingByExistence, needsBuildingByTimeStamp]
needsBuildingByExistence :: Task -> IO Bool
needsBuildingByExistence = fmap not . allPathsExist . tTargets
needsBuildingByTimeStamp :: Task -> IO Bool
needsBuildingByTimeStamp t = do
oldest <- getOldestModTime (tTargets t)
anyM (fmap (> oldest) . getModificationTime) (tDeps t)
getOldestModTime :: [Target] -> IO ClockTime
getOldestModTime = fmap minimum . mapM getModificationTime
needsBuildingWithDB :: (Task -> Bool -> IO CheckDepFunc)
-> ([Bool],DB) -> Task -> CoData ([Bool],DB)
needsBuildingWithDB getCDF (ns,db) task = do
targetsMissing <- io$ needsBuildingByExistence task
checkDep <- io$ getCDF task targetsMissing
givenArgs <- asks coArgs
let caresAboutArgs = tArgs task
(need, db') <-
foldM (checkDep (givenArgs `Set.intersection` caresAboutArgs)
caresAboutArgs)
targetsMissing
(tDeps task) `runStateT` db
return (need:ns, db')
type CheckDepFunc = Set String -> Set String
-> Bool
-> FilePath
-> StateT DB CoData Bool
selectCheckDepFunc :: DB -> CoData (Task -> Bool -> IO CheckDepFunc)
selectCheckDepFunc db = do
forceNoHash <- asks coForceNoHash
let hashing = not forceNoHash && hasHashes db
if hashing
then return$ \_ _ -> return useHash
else return$ \task targetsMissing ->
if targetsMissing
then return (useTimeStamp Nothing)
else do
mt <- io.getOldestModTime.tTargets $ task
return (useTimeStamp (Just mt))
useHash :: CheckDepFunc
useHash givenCareArgs caresAboutArgs othersNeeds source = do
hash <- io $ hashFile source
db <- get
old <- checkNewness db source givenCareArgs (Just hash)
case old of
Nothing -> return othersNeeds
Just o ->
if o `hasHash` hash &&
argsMatch givenCareArgs caresAboutArgs (dArgs o)
then return othersNeeds
else do put$ addEntry db source givenCareArgs (Just hash)
return True
useTimeStamp :: Maybe ClockTime -> CheckDepFunc
useTimeStamp oldestMod givenCareArgs caresAboutArgs othersNeeds source = do
db <- get
old <- checkNewness db source givenCareArgs Nothing
case old of
Nothing -> checkTime
Just o ->
if argsMatch givenCareArgs caresAboutArgs (dArgs o)
then checkTime
else do
put$ addEntry db source givenCareArgs Nothing
return True
where
checkTime
| othersNeeds = return True
| otherwise =
fmap (> fromJust oldestMod) (io$ getModificationTime source)
checkNewness :: DB -> FilePath -> Set String -> Maybe Hash
-> StateT DB CoData (Maybe Datum)
checkNewness db file givenArgs mhash = do
let entry = dbLookup db file
when (isNothing entry) $
put$ addEntry db file givenArgs mhash
return entry
argsMatch :: Set String -> Set String -> Set String -> Bool
argsMatch givenCareArgs caresAboutArgs builtWithArgs =
givenCareArgs == builtWithArgs `Set.intersection` caresAboutArgs