{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable, Rank2Types, ScopedTypeVariables, ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-}
module Development.Shake.Forward(
shakeForward, shakeArgsForward,
forwardOptions, forwardRule,
cache, cacheAction, cacheActionWith,
) where
import Control.Monad
import Development.Shake
import Development.Shake.Rule
import Development.Shake.Command
import Development.Shake.Classes
import Development.Shake.FilePath
import Data.IORef.Extra
import Data.Either
import Data.Typeable
import Data.List.Extra
import Control.Exception.Extra
import Numeric
import System.IO.Unsafe
import Data.Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.HashMap.Strict as Map
{-# NOINLINE forwards #-}
forwards :: IORef (Map.HashMap Forward (Action Forward))
forwards = unsafePerformIO $ newIORef Map.empty
newtype Forward = Forward (String, String, BS.ByteString)
deriving (Hashable,Typeable,Eq,NFData,Binary)
mkForward :: (Typeable a, Show a, Binary a) => a -> Forward
mkForward x = Forward (show $ typeOf x, show x, encode' x)
unForward :: forall a . (Typeable a, Binary a) => Forward -> a
unForward (Forward (got,_,x))
| got /= want = error $ "Failed to match forward type, wanted " ++ show want ++ ", got " ++ show got
| otherwise = decode' x
where want = show $ typeRep (Proxy :: Proxy a)
encode' :: Binary a => a -> BS.ByteString
encode' = BS.concat . LBS.toChunks . encode
decode' :: Binary a => BS.ByteString -> a
decode' = decode . LBS.fromChunks . pure
type instance RuleResult Forward = Forward
instance Show Forward where
show (Forward (_,x,_)) = x
shakeForward :: ShakeOptions -> Action () -> IO ()
shakeForward opts act = shake (forwardOptions opts) (forwardRule act)
shakeArgsForward :: ShakeOptions -> Action () -> IO ()
shakeArgsForward opts act = shakeArgs (forwardOptions opts) (forwardRule act)
forwardRule :: Action () -> Rules ()
forwardRule act = do
opts <- getShakeOptionsRules
when (null $ shakeLintInside opts) $
fail "When running in forward mode you must set shakeLintInside to specify where to detect dependencies"
addBuiltinRule noLint noIdentity $ \k old mode ->
case old of
Just old | mode == RunDependenciesSame -> pure $ RunResult ChangedNothing old (decode' old)
_ -> do
res <- liftIO $ atomicModifyIORef forwards $ \mp -> (Map.delete k mp, Map.lookup k mp)
case res of
Nothing -> liftIO $ errorIO $ "Failed to find action name, " ++ show k
Just act -> do
new <- act
pure $ RunResult ChangedRecomputeSame (encode' new) new
action act
forwardOptions :: ShakeOptions -> ShakeOptions
forwardOptions opts = opts{shakeCommandOptions=[AutoDeps]}
cacheAction :: (Typeable a, Binary a, Show a, Typeable b, Binary b, Show b) => a -> Action b -> Action b
cacheAction (mkForward -> key) (action :: Action b) = do
liftIO $ atomicModifyIORef_ forwards $ Map.insert key (mkForward <$> action)
res <- apply1 key
liftIO $ atomicModifyIORef_ forwards $ Map.delete key
pure $ unForward res
newtype With a = With a
deriving (Typeable, Binary, Show)
cacheActionWith :: (Typeable a, Binary a, Show a, Typeable b, Binary b, Show b, Typeable c, Binary c, Show c) => a -> b -> Action c -> Action c
cacheActionWith key argument action = do
cacheAction (With argument) $ do
alwaysRerun
pure argument
cacheAction key $ do
apply1 $ mkForward $ With argument
action
cache :: (forall r . CmdArguments r => r) -> Action ()
cache cmd = do
let CmdArgument args = cmd
let isDull ['-',_] = True; isDull _ = False
let name = headDef "unknown" $ filter (not . isDull) $ drop1 $ rights args
cacheAction (Command $ toStandard name ++ " #" ++ upper (showHex (abs $ hash $ show args) "")) cmd
newtype Command = Command String
deriving (Typeable, Binary)
instance Show Command where
show (Command x) = "command " ++ x