2015-08-10 03:00:58 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2015-08-11 04:35:14 +00:00
|
|
|
module Language.Haskell.GhcMod.Caching (
|
|
|
|
module Language.Haskell.GhcMod.Caching
|
|
|
|
, module Language.Haskell.GhcMod.Caching.Types
|
|
|
|
) where
|
2015-03-28 01:30:51 +00:00
|
|
|
|
2015-08-10 03:00:58 +00:00
|
|
|
import Control.Arrow (first)
|
2015-08-11 04:35:14 +00:00
|
|
|
import Control.Monad
|
2015-03-28 01:30:51 +00:00
|
|
|
import Control.Monad.Trans.Maybe
|
|
|
|
import Data.Maybe
|
2015-11-18 19:51:37 +00:00
|
|
|
import Data.Binary (Binary, encode, decodeOrFail)
|
2015-08-10 03:00:58 +00:00
|
|
|
import Data.Version
|
2015-08-11 04:35:14 +00:00
|
|
|
import Data.Label
|
2015-11-18 20:41:19 +00:00
|
|
|
import qualified Data.ByteString.Lazy as LBS
|
|
|
|
import qualified Data.ByteString as BS
|
|
|
|
import qualified Data.ByteString.Char8 as BS8
|
2015-11-18 19:58:29 +00:00
|
|
|
import Data.Time (UTCTime, getCurrentTime)
|
2015-03-28 01:30:51 +00:00
|
|
|
import System.FilePath
|
2015-04-12 00:39:18 +00:00
|
|
|
import Utils (TimedFile(..), timeMaybe, mightExist)
|
2015-08-10 03:00:58 +00:00
|
|
|
import Paths_ghc_mod (version)
|
2015-03-28 01:30:51 +00:00
|
|
|
|
|
|
|
import Language.Haskell.GhcMod.Monad.Types
|
2015-08-11 04:35:14 +00:00
|
|
|
import Language.Haskell.GhcMod.Caching.Types
|
2015-03-28 01:30:51 +00:00
|
|
|
import Language.Haskell.GhcMod.Logging
|
|
|
|
|
|
|
|
-- | Cache a MonadIO action with proper invalidation.
|
2015-11-18 19:51:37 +00:00
|
|
|
cached :: forall m a d. (Gm m, MonadIO m, Binary a, Eq d, Binary d, Show d)
|
2015-03-28 01:30:51 +00:00
|
|
|
=> FilePath -- ^ Directory to prepend to 'cacheFile'
|
2015-08-11 04:35:14 +00:00
|
|
|
-> Cached m GhcModState d a -- ^ Cache descriptor
|
2015-03-28 01:30:51 +00:00
|
|
|
-> d
|
|
|
|
-> m a
|
|
|
|
cached dir cd d = do
|
|
|
|
mcc <- readCache
|
|
|
|
case mcc of
|
2015-11-18 20:41:19 +00:00
|
|
|
Nothing -> do
|
|
|
|
t <- liftIO $ getCurrentTime
|
|
|
|
writeCache (TimedCacheFiles t []) Nothing "cache missing or unreadable"
|
|
|
|
Just (t, ifs, d', a) | d /= d' -> do
|
|
|
|
tcfs <- timeCacheInput dir ifs
|
|
|
|
writeCache (TimedCacheFiles t tcfs) (Just a) $ "input data changed" -- ++ " was: " ++ show d ++ " is: " ++ show d'
|
|
|
|
Just (t, ifs, _, a) -> do
|
|
|
|
tcfs <- timeCacheInput dir ifs
|
|
|
|
case invalidatingInputFiles $ TimedCacheFiles t tcfs of
|
|
|
|
[] -> return a
|
|
|
|
_ -> writeCache (TimedCacheFiles t tcfs) (Just a) "input files changed"
|
2015-03-28 01:30:51 +00:00
|
|
|
|
|
|
|
where
|
2015-08-10 03:00:58 +00:00
|
|
|
cacheHeader = BS8.pack $ "Written by ghc-mod " ++ showVersion version ++ "\n"
|
|
|
|
|
2015-11-18 20:41:19 +00:00
|
|
|
writeCache tcfs ma cause = do
|
|
|
|
(ifs', a) <- (cachedAction cd) tcfs d ma
|
2015-11-18 19:58:29 +00:00
|
|
|
t <- liftIO $ getCurrentTime
|
2015-03-28 01:30:51 +00:00
|
|
|
gmLog GmDebug "" $ (text "regenerating cache") <+>: text (cacheFile cd)
|
|
|
|
<+> parens (text cause)
|
2015-08-11 04:35:14 +00:00
|
|
|
case cacheLens cd of
|
|
|
|
Nothing -> return ()
|
|
|
|
Just label -> do
|
|
|
|
gmLog GmDebug "" $ (text "writing memory cache") <+>: text (cacheFile cd)
|
2015-11-18 19:58:29 +00:00
|
|
|
setLabel label $ Just (t, ifs', d, a)
|
2015-08-11 04:35:14 +00:00
|
|
|
|
2015-11-18 20:41:19 +00:00
|
|
|
let c = BS.append cacheHeader $ LBS.toStrict $ encode (t, ifs', d, a)
|
|
|
|
|
|
|
|
liftIO $ BS.writeFile (dir </> cacheFile cd) c
|
|
|
|
|
2015-03-28 01:30:51 +00:00
|
|
|
return a
|
|
|
|
|
2015-08-11 04:35:14 +00:00
|
|
|
setLabel l x = do
|
|
|
|
s <- gmsGet
|
|
|
|
gmsPut $ set l x s
|
|
|
|
|
2015-11-18 19:58:29 +00:00
|
|
|
readCache :: m (Maybe (UTCTime, [FilePath], d, a))
|
2015-03-28 01:30:51 +00:00
|
|
|
readCache = runMaybeT $ do
|
2015-08-11 04:35:14 +00:00
|
|
|
case cacheLens cd of
|
|
|
|
Just label -> do
|
|
|
|
c <- MaybeT (get label `liftM` gmsGet) `mplus` readCacheFromFile
|
|
|
|
setLabel label $ Just c
|
|
|
|
return c
|
|
|
|
Nothing ->
|
|
|
|
readCacheFromFile
|
|
|
|
|
2015-11-18 19:58:29 +00:00
|
|
|
readCacheFromFile :: MaybeT m (UTCTime, [FilePath], d, a)
|
2015-08-11 04:35:14 +00:00
|
|
|
readCacheFromFile = do
|
|
|
|
f <- MaybeT $ liftIO $ mightExist $ cacheFile cd
|
|
|
|
readCacheFromFile' f
|
|
|
|
|
2015-11-18 19:58:29 +00:00
|
|
|
readCacheFromFile' :: FilePath -> MaybeT m (UTCTime, [FilePath], d, a)
|
2015-08-11 04:35:14 +00:00
|
|
|
readCacheFromFile' f = MaybeT $ do
|
|
|
|
gmLog GmDebug "" $ (text "reading cache") <+>: text (cacheFile cd)
|
|
|
|
cc <- liftIO $ BS.readFile f
|
|
|
|
case first BS8.words $ BS8.span (/='\n') cc of
|
|
|
|
(["Written", "by", "ghc-mod", ver], rest)
|
|
|
|
| BS8.unpack ver == showVersion version ->
|
2015-11-18 20:41:19 +00:00
|
|
|
return $ either (const Nothing) Just $
|
|
|
|
decodeE $ LBS.fromStrict $ BS.drop 1 rest
|
2015-08-11 04:35:14 +00:00
|
|
|
_ -> return Nothing
|
2015-03-28 01:30:51 +00:00
|
|
|
|
2015-11-18 19:51:37 +00:00
|
|
|
decodeE b = do
|
|
|
|
case decodeOrFail b of
|
|
|
|
Left (_rest, _offset, errmsg) -> Left errmsg
|
|
|
|
Right (_reset, _offset, a) -> Right a
|
2015-11-18 19:58:29 +00:00
|
|
|
|
2015-11-18 20:41:19 +00:00
|
|
|
timeCacheInput :: MonadIO m => FilePath -> [FilePath] -> m [TimedFile]
|
|
|
|
timeCacheInput dir ifs = liftIO $ do
|
2015-03-28 01:30:51 +00:00
|
|
|
ins <- (timeMaybe . (dir </>)) `mapM` ifs
|
2015-11-18 20:41:19 +00:00
|
|
|
return $ catMaybes ins
|
2015-03-28 01:30:51 +00:00
|
|
|
|
2015-11-18 20:41:19 +00:00
|
|
|
invalidatingInputFiles :: TimedCacheFiles -> [FilePath]
|
|
|
|
invalidatingInputFiles (TimedCacheFiles tcreated tcfs) =
|
|
|
|
map tfPath $
|
|
|
|
-- get input files older than tcfile
|
|
|
|
filter ((TimedFile "" tcreated)<) tcfs
|