module Language.PureScript.Make.BuildPlan
( BuildPlan()
, construct
, getResult
, collectErrors
, collectResults
, markComplete
, needsRebuild
) where
import Prelude
import Control.Concurrent.Lifted as C
import Control.Monad hiding (sequence)
import Control.Monad.Trans.Control (MonadBaseControl(..))
import Data.Aeson (decode)
import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime)
import Data.Version (showVersion)
import Language.PureScript.AST
import Language.PureScript.Crash
import Language.PureScript.Errors
import Language.PureScript.Externs
import Language.PureScript.Make.Actions as Actions
import Language.PureScript.Names (ModuleName)
import qualified Paths_purescript as Paths
data BuildPlan = BuildPlan
{ bpPrebuilt :: M.Map ModuleName Prebuilt
, bpBuildJobs :: M.Map ModuleName BuildJob
}
data Prebuilt = Prebuilt
{ pbModificationTime :: UTCTime
, pbExternsFile :: ExternsFile
}
data BuildJob = BuildJob
{ bjResult :: C.MVar (Maybe (MultipleErrors, ExternsFile))
, bjErrors :: C.MVar (Maybe MultipleErrors)
}
markComplete
:: (MonadBaseControl IO m)
=> BuildPlan
-> ModuleName
-> Maybe (MultipleErrors, ExternsFile)
-> Maybe MultipleErrors
-> m ()
markComplete buildPlan moduleName result errors = do
let BuildJob rVar eVar = fromMaybe (internalError "make: markComplete no barrier") $ M.lookup moduleName (bpBuildJobs buildPlan)
putMVar rVar result
putMVar eVar errors
needsRebuild :: BuildPlan -> ModuleName -> Bool
needsRebuild bp moduleName = M.member moduleName (bpBuildJobs bp)
collectErrors
:: (MonadBaseControl IO m)
=> BuildPlan
-> m [MultipleErrors]
collectErrors buildPlan = do
errors <- traverse readMVar $ map bjErrors $ M.elems (bpBuildJobs buildPlan)
pure (catMaybes errors)
collectResults
:: (MonadBaseControl IO m)
=> BuildPlan
-> m (M.Map ModuleName ExternsFile)
collectResults buildPlan = do
let externs = M.map pbExternsFile (bpPrebuilt buildPlan)
barrierResults <- traverse (takeMVar . bjResult) $ bpBuildJobs buildPlan
let barrierExterns = M.map (snd . fromMaybe (internalError "make: externs were missing but no errors reported.")) barrierResults
pure (M.union externs barrierExterns)
getResult
:: (MonadBaseControl IO m)
=> BuildPlan
-> ModuleName
-> m (Maybe (MultipleErrors, ExternsFile))
getResult buildPlan moduleName =
case M.lookup moduleName (bpPrebuilt buildPlan) of
Just es ->
pure (Just (MultipleErrors [], pbExternsFile es))
Nothing ->
readMVar $ bjResult $ fromMaybe (internalError "make: no barrier") $ M.lookup moduleName (bpBuildJobs buildPlan)
construct
:: forall m. (Monad m, MonadBaseControl IO m)
=> MakeActions m
-> ([Module], [(ModuleName, [ModuleName])])
-> m BuildPlan
construct MakeActions{..} (sorted, graph) = do
prebuilt <- foldM findExistingExtern M.empty sorted
let toBeRebuilt = filter (not . flip M.member prebuilt . getModuleName) sorted
buildJobs <- foldM makeBuildJob M.empty (map getModuleName toBeRebuilt)
pure $ BuildPlan prebuilt buildJobs
where
makeBuildJob prev moduleName = do
buildJob <- BuildJob <$> C.newEmptyMVar <*> C.newEmptyMVar
pure (M.insert moduleName buildJob prev)
findExistingExtern :: M.Map ModuleName Prebuilt -> Module -> m (M.Map ModuleName Prebuilt)
findExistingExtern prev (getModuleName -> moduleName) = do
outputTimestamp <- getOutputTimestamp moduleName
let deps = fromMaybe (internalError "make: module not found in dependency graph.") (lookup moduleName graph)
case traverse (fmap pbModificationTime . flip M.lookup prev) deps of
Nothing ->
pure prev
Just modTimes -> do
let dependencyTimestamp = maximumMaybe modTimes
inputTimestamp <- getInputTimestamp moduleName
let
existingExtern = case (inputTimestamp, dependencyTimestamp, outputTimestamp) of
(Right (Just t1), Just t3, Just t2) ->
if t1 > t2 || t3 > t2 then Nothing else Just t2
(Right (Just t1), Nothing, Just t2) ->
if t1 > t2 then Nothing else Just t2
(Left RebuildNever, _, Just t2) ->
Just t2
_ ->
Nothing
case existingExtern of
Nothing -> pure prev
Just outputTime -> do
mexts <- decodeExterns . snd <$> readExterns moduleName
case mexts of
Just exts ->
pure (M.insert moduleName (Prebuilt outputTime exts) prev)
Nothing -> pure prev
maximumMaybe :: Ord a => [a] -> Maybe a
maximumMaybe [] = Nothing
maximumMaybe xs = Just $ maximum xs
decodeExterns :: Externs -> Maybe ExternsFile
decodeExterns bs = do
externs <- decode bs
guard $ T.unpack (efVersion externs) == showVersion Paths.version
return externs