{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards, PatternGuards, ScopedTypeVariables, NamedFieldPuns, GADTs #-}
{-# LANGUAGE Rank2Types, ConstraintKinds, TupleSections, ViewPatterns #-}
module Development.Shake.Internal.Core.Build(
getDatabaseValue, getDatabaseValueGeneric,
historyIsEnabled, historySave, historyLoad,
applyKeyValue,
apply, apply1,
) where
import Development.Shake.Classes
import General.Pool
import Development.Shake.Internal.Core.Database
import Development.Shake.Internal.Value
import Development.Shake.Internal.Errors
import Development.Shake.Internal.Core.Types
import Development.Shake.Internal.Core.Action
import Development.Shake.Internal.History.Shared
import Development.Shake.Internal.History.Cloud
import Development.Shake.Internal.Options
import Development.Shake.Internal.Core.Monad
import General.Wait
import qualified Data.ByteString.Char8 as BS
import Control.Monad.IO.Class
import General.Extra
import General.Intern(Id)
import Control.Exception
import Control.Monad.Extra
import Numeric.Extra
import qualified Data.HashMap.Strict as Map
import Development.Shake.Internal.Core.Rules
import Data.Typeable
import Data.Maybe
import Data.List.Extra
import Data.Either.Extra
import System.Time.Extra
setIdKeyStatus :: Global -> Database -> Id -> Key -> Status -> Locked ()
setIdKeyStatus Global{..} db i k v = do
liftIO $ globalDiagnostic $ do
old <- getKeyValueFromId db i
let changeStatus = maybe "Missing" (statusType . snd) old ++ " -> " ++ statusType v ++ ", " ++ maybe "<unknown>" (show . fst) old
let changeValue = case v of
Ready r -> Just $ " = " ++ showBracket (result r) ++ " " ++ (if built r == changed r then "(changed)" else "(unchanged)")
_ -> Nothing
pure $ changeStatus ++ maybe "" ("\n" ++) changeValue
setMem db i k v
getDatabaseValue :: (RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action (Maybe (Result (Either BS.ByteString value)))
getDatabaseValue k =
fmap (fmap $ fmap $ fmap fromValue) $ getDatabaseValueGeneric $ newKey k
getDatabaseValueGeneric :: Key -> Action (Maybe (Result (Either BS.ByteString Value)))
getDatabaseValueGeneric k = do
Global{..} <- Action getRO
Just status <- liftIO $ getValueFromKey globalDatabase k
pure $ getResult status
lookupOne :: Global -> Stack -> Database -> Id -> Wait Locked (Either SomeException (Result (Value, BS_Store)))
lookupOne global stack database i = do
res <- quickly $ liftIO $ getKeyValueFromId database i
case res of
Nothing -> Now $ Left $ errorStructured "Shake Id no longer exists" [("Id", Just $ show i)] ""
Just (k, s) -> case s of
Ready r -> Now $ Right r
Failed e _ -> Now $ Left e
Running{} | Left e <- addStack i k stack -> Now $ Left e
_ -> Later $ \continue -> do
Just (_, s) <- liftIO $ getKeyValueFromId database i
case s of
Ready r -> continue $ Right r
Failed e _ -> continue $ Left e
Running (NoShow w) r -> do
let w2 v = w v >> continue v
setMem database i k $ Running (NoShow w2) r
Loaded r -> buildOne global stack database i k (Just r) `fromLater` continue
Missing -> buildOne global stack database i k Nothing `fromLater` continue
buildOne :: Global -> Stack -> Database -> Id -> Key -> Maybe (Result BS.ByteString) -> Wait Locked (Either SomeException (Result (Value, BS_Store)))
buildOne global@Global{..} stack database i k r = case addStack i k stack of
Left e -> do
quickly $ setIdKeyStatus global database i k $ mkError e
pure $ Left e
Right stack -> Later $ \continue -> do
setIdKeyStatus global database i k (Running (NoShow continue) r)
let go = buildRunMode global stack database r
fromLater go $ \mode -> liftIO $ addPool PoolStart globalPool $
runKey global stack k r mode $ \res -> do
runLocked database $ do
let val = fmap runValue res
res <- liftIO $ getKeyValueFromId database i
w <- case res of
Just (_, Running (NoShow w) _) -> pure w
_ -> throwM $ errorInternal $ "expected Waiting but got " ++ maybe "nothing" (statusType . snd) res ++ ", key " ++ show k
setIdKeyStatus global database i k $ either mkError Ready val
w val
case res of
Right RunResult{..} | runChanged /= ChangedNothing -> setDisk database i k $ Loaded runValue{result=runStore}
_ -> pure ()
where
mkError e = Failed e $ if globalOneShot then Nothing else r
buildRunMode :: Global -> Stack -> Database -> Maybe (Result a) -> Wait Locked RunMode
buildRunMode global stack database me = do
changed <- case me of
Nothing -> pure True
Just me -> buildRunDependenciesChanged global stack database me
pure $ if changed then RunDependenciesChanged else RunDependenciesSame
buildRunDependenciesChanged :: Global -> Stack -> Database -> Result a -> Wait Locked Bool
buildRunDependenciesChanged global stack database me = isJust <$> firstJustM id
[firstJustWaitUnordered (fmap test . lookupOne global stack database) x | Depends x <- depends me]
where
test (Right dep) | changed dep <= built me = Nothing
test _ = Just ()
applyKeyValue :: [String] -> [Key] -> Action [Value]
applyKeyValue callStack ks = do
liftIO $ mapM_ (evaluate . rnf) ks
global@Global{..} <- Action getRO
Local{localStack, localBlockApply} <- Action getRW
let stack = addCallStack callStack localStack
let tk = typeKey $ headDef (newKey ()) ks
whenJust localBlockApply $ throwM . errorNoApply tk (show <$> listToMaybe ks)
let database = globalDatabase
(is, wait) <- liftIO $ runLocked database $ do
is <- mapM (mkId database) ks
wait <- runWait $ do
x <- firstJustWaitUnordered (fmap (either Just (const Nothing)) . lookupOne global stack database) $ nubOrd is
case x of
Just e -> pure $ Left e
Nothing -> quickly $ Right <$> mapM (fmap (\(Just (_, Ready r)) -> fst $ result r) . liftIO . getKeyValueFromId database) is
pure (is, wait)
Action $ modifyRW $ \s -> s{localDepends = Depends is : localDepends s}
case wait of
Now vs -> either throwM pure vs
_ -> do
offset <- liftIO offsetTime
vs <- Action $ captureRAW $ \continue ->
runLocked globalDatabase $ fromLater wait $ \x ->
liftIO $ addPool (if isLeft x then PoolException else PoolResume) globalPool $ continue x
offset <- liftIO offset
Action $ modifyRW $ addDiscount offset
pure vs
runKey
:: Global
-> Stack
-> Key
-> Maybe (Result BS.ByteString)
-> RunMode
-> Capture (Either SomeException (RunResult (Result (Value, BS_Store))))
runKey global@Global{globalOptions=ShakeOptions{..},..} stack k r mode continue = do
let tk = typeKey k
BuiltinRule{..} <- case Map.lookup tk globalRules of
Nothing -> throwM $ errorNoRuleToBuildType tk (Just $ show k) Nothing
Just r -> pure r
let s = (newLocal stack shakeVerbosity){localBuiltinVersion = builtinVersion}
time <- offsetTime
runAction global s (do
res <- builtinRun k (fmap result r) mode
liftIO $ evaluate $ rnf res
when (runChanged res `elem` [ChangedRecomputeSame,ChangedRecomputeDiff]) $ do
globalRuleFinished k
producesCheck
Action $ fmap (res,) getRW) $ \case
Left e ->
continue . Left . toException =<< shakeException global stack e
Right (RunResult{..}, Local{..})
| runChanged == ChangedNothing || runChanged == ChangedStore, Just r <- r ->
continue $ Right $ RunResult runChanged runStore (r{result = mkResult runValue runStore})
| otherwise -> do
dur <- time
let (cr, c) | Just r <- r, runChanged == ChangedRecomputeSame = (ChangedRecomputeSame, changed r)
| otherwise = (ChangedRecomputeDiff, globalStep)
continue $ Right $ RunResult cr runStore Result
{result = mkResult runValue runStore
,changed = c
,built = globalStep
,depends = nubDepends $ reverse localDepends
,execution = doubleToFloat $ dur - localDiscount
,traces = reverse localTraces}
where
mkResult value store = (value, if globalOneShot then BS.empty else store)
apply :: (Partial, RuleResult key ~ value, ShakeValue key, Typeable value) => [key] -> Action [value]
apply [] =
pure []
apply ks =
fmap (map fromValue) $ Action $ stepRAW (callStackFull, map newKey ks)
apply1 :: (Partial, RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action value
apply1 = withFrozenCallStack $ fmap head . apply . pure
historyLoad :: Int -> Action (Maybe BS.ByteString)
historyLoad (Ver -> ver) = do
global@Global{..} <- Action getRO
Local{localStack, localBuiltinVersion} <- Action getRW
if isNothing globalShared && isNothing globalCloud then pure Nothing else do
key <- liftIO $ evaluate $ fromMaybe (error "Can't call historyLoad outside a rule") $ topStack localStack
let database = globalDatabase
res <- liftIO $ runLocked database $ runWait $ do
let ask k = do
i <- quickly $ mkId database k
let identify = runIdentify globalRules k . fst . result
either (const Nothing) identify <$> lookupOne global localStack database i
x <- case globalShared of
Nothing -> pure Nothing
Just shared -> lookupShared shared ask key localBuiltinVersion ver
x <- case x of
Just res -> pure $ Just res
Nothing -> case globalCloud of
Nothing -> pure Nothing
Just cloud -> lookupCloud cloud ask key localBuiltinVersion ver
case x of
Nothing -> pure Nothing
Just (a,b,c) -> quickly $ Just . (a,,c) <$> mapM (mapM $ mkId database) b
res <- case res of
Now x -> pure x
_ -> do
offset <- liftIO offsetTime
res <- Action $ captureRAW $ \continue ->
runLocked globalDatabase $ fromLater res $ \x ->
liftIO $ addPool PoolResume globalPool $ continue $ Right x
offset <- liftIO offset
Action $ modifyRW $ addDiscount offset
pure res
case res of
Nothing -> pure Nothing
Just (res, deps, restore) -> do
liftIO $ globalDiagnostic $ pure $ "History hit for " ++ show key
liftIO restore
Action $ modifyRW $ \s -> s{localDepends = reverse $ map Depends deps}
pure (Just res)
historyIsEnabled :: Action Bool
historyIsEnabled = Action $ do
Global{..} <- getRO
Local{localHistory} <- getRW
pure $ localHistory && (isJust globalShared || isJust globalCloud)
historySave :: Int -> BS.ByteString -> Action ()
historySave (Ver -> ver) store = whenM historyIsEnabled $ Action $ do
Global{..} <- getRO
Local{localProduces, localDepends, localBuiltinVersion, localStack} <- getRW
liftIO $ do
evaluate ver
evaluate store
key <- evaluate $ fromMaybe (error "Can't call historySave outside a rule") $ topStack localStack
let produced = reverse $ map snd localProduces
deps <-
forNothingM (reverse localDepends) $ \(Depends is) -> forNothingM is $ \i -> do
Just (k, Ready r) <- getKeyValueFromId globalDatabase i
pure $ (k,) <$> runIdentify globalRules k (fst $ result r)
let k = topStack localStack
case deps of
Nothing -> liftIO $ globalDiagnostic $ pure $ "Dependency with no identity for " ++ show k
Just deps -> do
whenJust globalShared $ \shared -> addShared shared key localBuiltinVersion ver deps store produced
whenJust globalCloud $ \cloud -> addCloud cloud key localBuiltinVersion ver deps store produced
liftIO $ globalDiagnostic $ pure $ "History saved for " ++ show k
runIdentify :: Map.HashMap TypeRep BuiltinRule -> Key -> Value -> Maybe BS.ByteString
runIdentify mp k v
| Just BuiltinRule{..} <- Map.lookup (typeKey k) mp = builtinIdentity k v
| otherwise = throwImpure $ errorInternal "runIdentify can't find rule"