1 {-# LANGUAGE BangPatterns #-}
3 {-# LANGUAGE DeriveFunctor #-}
4 {-# LANGUAGE DeriveGeneric #-}
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE NamedFieldPuns #-}
7 {-# LANGUAGE ScopedTypeVariables #-}
8 {-# OPTIONS_GHC -fno-warn-orphans #-}
10 -- | An abstraction to help with re-running actions when files or other
11 -- input values they depend on have changed.
12 module Distribution
.Client
.FileMonitor
13 ( -- * Declaring files to monitor
15 , MonitorKindFile
(..)
20 , monitorNonExistentFile
21 , monitorFileExistence
23 , monitorNonExistentDirectory
24 , monitorDirectoryExistence
25 , monitorFileOrDirectory
27 , monitorFileGlobExistence
28 , monitorFileSearchPath
29 , monitorFileHashedSearchPath
31 -- * Creating and checking sets of monitored files
35 , MonitorChangedReason
(..)
36 , checkFileMonitorChanged
39 , beginUpdateFileMonitor
47 import Distribution
.Client
.Compat
.Prelude
48 import qualified Distribution
.Compat
.Binary
as Binary
51 import Data
.Binary
.Get
(runGetOrFail
)
52 import qualified Data
.ByteString
.Lazy
as BS
53 import qualified Data
.Hashable
as Hashable
54 import qualified Data
.Map
.Strict
as Map
56 import Control
.Exception
58 import Control
.Monad
.Except
64 import Control
.Monad
.State
(StateT
, mapStateT
)
65 import qualified Control
.Monad
.State
as State
66 import Control
.Monad
.Trans
(MonadIO
, liftIO
)
68 import Distribution
.Client
.Glob
69 import Distribution
.Client
.Utils
(MergeResult
(..), mergeBy
)
70 import Distribution
.Compat
.Time
71 import Distribution
.Simple
.Utils
(handleDoesNotExist
, writeFileAtomic
)
72 import Distribution
.Utils
.Structured
(Tag
(..), structuredEncode
)
73 import System
.Directory
74 import System
.FilePath
77 ------------------------------------------------------------------------------
78 -- Types for specifying files to monitor
81 -- | A description of a file (or set of files) to monitor for changes.
83 -- Where file paths are relative they are relative to a common directory
84 -- (e.g. project root), not necessarily the process current directory.
87 { monitorKindFile
:: !MonitorKindFile
88 , monitorKindDir
:: !MonitorKindDir
89 , monitorPath
:: !FilePath
92 { monitorKindFile
:: !MonitorKindFile
93 , monitorKindDir
:: !MonitorKindDir
94 , monitorPathGlob
:: !FilePathGlob
96 deriving (Eq
, Show, Generic
)
103 deriving (Eq
, Show, Generic
)
109 deriving (Eq
, Show, Generic
)
111 instance Binary MonitorFilePath
112 instance Binary MonitorKindFile
113 instance Binary MonitorKindDir
115 instance Structured MonitorFilePath
116 instance Structured MonitorKindFile
117 instance Structured MonitorKindDir
119 -- | Monitor a single file for changes, based on its modification time.
120 -- The monitored file is considered to have changed if it no longer
121 -- exists or if its modification time has changed.
122 monitorFile
:: FilePath -> MonitorFilePath
123 monitorFile
= MonitorFile FileModTime DirNotExists
125 -- | Monitor a single file for changes, based on its modification time
126 -- and content hash. The monitored file is considered to have changed if
127 -- it no longer exists or if its modification time and content hash have
129 monitorFileHashed
:: FilePath -> MonitorFilePath
130 monitorFileHashed
= MonitorFile FileHashed DirNotExists
132 -- | Monitor a single non-existent file for changes. The monitored file
133 -- is considered to have changed if it exists.
134 monitorNonExistentFile
:: FilePath -> MonitorFilePath
135 monitorNonExistentFile
= MonitorFile FileNotExists DirNotExists
137 -- | Monitor a single file for existence only. The monitored file is
138 -- considered to have changed if it no longer exists.
139 monitorFileExistence
:: FilePath -> MonitorFilePath
140 monitorFileExistence
= MonitorFile FileExists DirNotExists
142 -- | Monitor a single directory for changes, based on its modification
143 -- time. The monitored directory is considered to have changed if it no
144 -- longer exists or if its modification time has changed.
145 monitorDirectory
:: FilePath -> MonitorFilePath
146 monitorDirectory
= MonitorFile FileNotExists DirModTime
148 -- | Monitor a single non-existent directory for changes. The monitored
149 -- directory is considered to have changed if it exists.
150 monitorNonExistentDirectory
:: FilePath -> MonitorFilePath
151 -- Just an alias for monitorNonExistentFile, since you can't
152 -- tell the difference between a non-existent directory and
153 -- a non-existent file :)
154 monitorNonExistentDirectory
= monitorNonExistentFile
156 -- | Monitor a single directory for existence. The monitored directory is
157 -- considered to have changed only if it no longer exists.
158 monitorDirectoryExistence
:: FilePath -> MonitorFilePath
159 monitorDirectoryExistence
= MonitorFile FileNotExists DirExists
161 -- | Monitor a single file or directory for changes, based on its modification
162 -- time. The monitored file is considered to have changed if it no longer
163 -- exists or if its modification time has changed.
164 monitorFileOrDirectory
:: FilePath -> MonitorFilePath
165 monitorFileOrDirectory
= MonitorFile FileModTime DirModTime
167 -- | Monitor a set of files (or directories) identified by a file glob.
168 -- The monitored glob is considered to have changed if the set of files
169 -- matching the glob changes (i.e. creations or deletions), or for files if the
170 -- modification time and content hash of any matching file has changed.
171 monitorFileGlob
:: FilePathGlob
-> MonitorFilePath
172 monitorFileGlob
= MonitorFileGlob FileHashed DirExists
174 -- | Monitor a set of files (or directories) identified by a file glob for
175 -- existence only. The monitored glob is considered to have changed if the set
176 -- of files matching the glob changes (i.e. creations or deletions).
177 monitorFileGlobExistence
:: FilePathGlob
-> MonitorFilePath
178 monitorFileGlobExistence
= MonitorFileGlob FileExists DirExists
180 -- | Creates a list of files to monitor when you search for a file which
181 -- unsuccessfully looked in @notFoundAtPaths@ before finding it at
183 monitorFileSearchPath
:: [FilePath] -> FilePath -> [MonitorFilePath
]
184 monitorFileSearchPath notFoundAtPaths foundAtPath
=
185 monitorFile foundAtPath
186 : map monitorNonExistentFile notFoundAtPaths
188 -- | Similar to 'monitorFileSearchPath', but also instructs us to
189 -- monitor the hash of the found file.
190 monitorFileHashedSearchPath
:: [FilePath] -> FilePath -> [MonitorFilePath
]
191 monitorFileHashedSearchPath notFoundAtPaths foundAtPath
=
192 monitorFileHashed foundAtPath
193 : map monitorNonExistentFile notFoundAtPaths
195 ------------------------------------------------------------------------------
196 -- Implementation types, files status
199 -- | The state necessary to determine whether a set of monitored
200 -- files has changed. It consists of two parts: a set of specific
201 -- files to be monitored (index by their path), and a list of
202 -- globs, which monitor may files at once.
203 data MonitorStateFileSet
204 = MonitorStateFileSet
207 -- Morally this is not actually a set but a bag (represented by lists).
208 -- There is no principled reason to use a bag here rather than a set, but
209 -- there is also no particular gain either. That said, we do preserve the
210 -- order of the lists just to reduce confusion (and have predictable I/O
212 deriving (Show, Generic
)
214 instance Binary MonitorStateFileSet
215 instance Structured MonitorStateFileSet
219 -- | The state necessary to determine whether a monitored file has changed.
221 -- This covers all the cases of 'MonitorFilePath' except for globs which is
222 -- covered separately by 'MonitorStateGlob'.
224 -- The @Maybe ModTime@ is to cover the case where we already consider the
225 -- file to have changed, either because it had already changed by the time we
226 -- did the snapshot (i.e. too new, changed since start of update process) or it
227 -- no longer exists at all.
228 data MonitorStateFile
233 !MonitorStateFileStatus
234 deriving (Show, Generic
)
236 data MonitorStateFileStatus
237 = MonitorStateFileExists
238 |
-- | cached file mtime
239 MonitorStateFileModTime
!ModTime
240 |
-- | cached mtime and content hash
241 MonitorStateFileHashed
!ModTime
!Hash
242 | MonitorStateDirExists
243 |
-- | cached dir mtime
244 MonitorStateDirModTime
!ModTime
245 | MonitorStateNonExistent
246 | MonitorStateAlreadyChanged
247 deriving (Show, Generic
)
249 instance Binary MonitorStateFile
250 instance Binary MonitorStateFileStatus
251 instance Structured MonitorStateFile
252 instance Structured MonitorStateFileStatus
254 -- | The state necessary to determine whether the files matched by a globbing
255 -- match have changed.
256 data MonitorStateGlob
262 deriving (Show, Generic
)
264 data MonitorStateGlobRel
265 = MonitorStateGlobDirs
269 ![(FilePath, MonitorStateGlobRel
)] -- invariant: sorted
270 | MonitorStateGlobFiles
273 ![(FilePath, MonitorStateFileStatus
)] -- invariant: sorted
274 | MonitorStateGlobDirTrailing
275 deriving (Show, Generic
)
277 instance Binary MonitorStateGlob
278 instance Binary MonitorStateGlobRel
280 instance Structured MonitorStateGlob
281 instance Structured MonitorStateGlobRel
283 -- | We can build a 'MonitorStateFileSet' from a set of 'MonitorFilePath' by
284 -- inspecting the state of the file system, and we can go in the reverse
285 -- direction by just forgetting the extra info.
286 reconstructMonitorFilePaths
:: MonitorStateFileSet
-> [MonitorFilePath
]
287 reconstructMonitorFilePaths
(MonitorStateFileSet singlePaths globPaths
) =
288 map getSinglePath singlePaths
++ map getGlobPath globPaths
290 getSinglePath
:: MonitorStateFile
-> MonitorFilePath
291 getSinglePath
(MonitorStateFile kindfile kinddir filepath _
) =
292 MonitorFile kindfile kinddir filepath
294 getGlobPath
:: MonitorStateGlob
-> MonitorFilePath
295 getGlobPath
(MonitorStateGlob kindfile kinddir root gstate
) =
296 MonitorFileGlob kindfile kinddir
$
299 MonitorStateGlobDirs glob globs _ _
-> GlobDir glob globs
300 MonitorStateGlobFiles glob _ _
-> GlobFile glob
301 MonitorStateGlobDirTrailing
-> GlobDirTrailing
303 ------------------------------------------------------------------------------
304 -- Checking the status of monitored files
307 -- | A monitor for detecting changes to a set of files. It can be used to
308 -- efficiently test if any of a set of files (specified individually or by
309 -- glob patterns) has changed since some snapshot. In addition, it also checks
310 -- for changes in a value (of type @a@), and when there are no changes in
311 -- either it returns a saved value (of type @b@).
313 -- The main use case looks like this: suppose we have some expensive action
314 -- that depends on certain pure inputs and reads some set of files, and
315 -- produces some pure result. We want to avoid re-running this action when it
316 -- would produce the same result. So we need to monitor the files the action
317 -- looked at, the other pure input values, and we need to cache the result.
318 -- Then at some later point, if the input value didn't change, and none of the
319 -- files changed, then we can re-use the cached result rather than re-running
322 -- This can be achieved using a 'FileMonitor'. Each 'FileMonitor' instance
323 -- saves state in a disk file, so the file for that has to be specified,
324 -- making sure it is unique. The pattern is to use 'checkFileMonitorChanged'
325 -- to see if there's been any change. If there is, re-run the action, keeping
326 -- track of the files, then use 'updateFileMonitor' to record the current
327 -- set of files to monitor, the current input value for the action, and the
328 -- result of the action.
330 -- The typical occurrence of this pattern is captured by 'rerunIfChanged'
331 -- and the 'Rebuild' monad. More complicated cases may need to use
332 -- 'checkFileMonitorChanged' and 'updateFileMonitor' directly.
333 data FileMonitor a b
= FileMonitor
334 { fileMonitorCacheFile
:: FilePath
335 -- ^ The file where this 'FileMonitor' should store its state.
336 , fileMonitorKeyValid
:: a
-> a
-> Bool
337 -- ^ Compares a new cache key with old one to determine if a
338 -- corresponding cached value is still valid.
340 -- Typically this is just an equality test, but in some
341 -- circumstances it can make sense to do things like subset
344 -- The first arg is the new value, the second is the old cached value.
345 , fileMonitorCheckIfOnlyValueChanged
:: Bool
346 -- ^ When this mode is enabled, if 'checkFileMonitorChanged' returns
347 -- 'MonitoredValueChanged' then we have the guarantee that no files
348 -- changed, that the value change was the only change. In the default
349 -- mode no such guarantee is provided which is slightly faster.
352 -- | Define a new file monitor.
354 -- It's best practice to define file monitor values once, and then use the
355 -- same value for 'checkFileMonitorChanged' and 'updateFileMonitor' as this
356 -- ensures you get the same types @a@ and @b@ for reading and writing.
358 -- The path of the file monitor itself must be unique because it keeps state
359 -- on disk and these would clash.
363 -- ^ The file to cache the state of the
364 -- file monitor. Must be unique.
366 newFileMonitor path
= FileMonitor path
(==) False
368 -- | The result of 'checkFileMonitorChanged': either the monitored files or
369 -- value changed (and it tells us which it was) or nothing changed and we get
370 -- the cached result.
371 data MonitorChanged a b
372 = -- | The monitored files and value did not change. The cached result is
375 -- The set of monitored files is also returned. This is useful
376 -- for composing or nesting 'FileMonitor's.
377 MonitorUnchanged b
[MonitorFilePath
]
378 |
-- | The monitor found that something changed. The reason is given.
379 MonitorChanged
(MonitorChangedReason a
)
382 -- | What kind of change 'checkFileMonitorChanged' detected.
383 data MonitorChangedReason a
384 = -- | One of the files changed (existence, file type, mtime or file
385 -- content, depending on the 'MonitorFilePath' in question)
386 MonitoredFileChanged
FilePath
387 |
-- | The pure input value changed.
389 -- The previous cached key value is also returned. This is sometimes
390 -- useful when using a 'fileMonitorKeyValid' function that is not simply
391 -- '(==)', when invalidation can be partial. In such cases it can make
392 -- sense to 'updateFileMonitor' with a key value that's a combination of
393 -- the new and old (e.g. set union).
394 MonitoredValueChanged a
395 |
-- | There was no saved monitor state, cached value etc. Ie the file
396 -- for the 'FileMonitor' does not exist.
398 |
-- | There was existing state, but we could not read it. This typically
399 -- happens when the code has changed compared to an existing 'FileMonitor'
400 -- cache file and type of the input value or cached value has changed such
401 -- that we cannot decode the values. This is completely benign as we can
402 -- treat is just as if there were no cache file and re-run.
404 deriving (Eq
, Show, Functor
)
406 -- | Test if the input value or files monitored by the 'FileMonitor' have
407 -- changed. If not, return the cached value.
409 -- See 'FileMonitor' for a full explanation.
410 checkFileMonitorChanged
412 . (Binary a
, Structured a
, Binary b
, Structured b
)
418 -- ^ guard or key value
419 -> IO (MonitorChanged a b
)
420 -- ^ did the key or any paths change?
421 checkFileMonitorChanged
423 { fileMonitorKeyValid
424 , fileMonitorCheckIfOnlyValueChanged
428 -- Consider it a change if the cache file does not exist,
429 -- or we cannot decode it. Sadly ErrorCall can still happen, despite
430 -- using decodeFileOrFail, e.g. Data.Char.chr errors
432 handleDoesNotExist
(MonitorChanged MonitorFirstRun
) $
433 handleErrorCall
(MonitorChanged MonitorCorruptCache
) $
434 withCacheFile monitor
$
436 (\_
-> return (MonitorChanged MonitorCorruptCache
))
439 checkStatusCache
:: (MonitorStateFileSet
, a
, Either String b
) -> IO (MonitorChanged a b
)
440 checkStatusCache
(cachedFileStatus
, cachedKey
, cachedResult
) = do
441 change
<- checkForChanges
443 Just reason
-> return (MonitorChanged reason
)
444 Nothing
-> case cachedResult
of
445 Left _
-> pure
(MonitorChanged MonitorCorruptCache
)
446 Right cr
-> return (MonitorUnchanged cr monitorFiles
)
448 monitorFiles
= reconstructMonitorFilePaths cachedFileStatus
450 -- In fileMonitorCheckIfOnlyValueChanged mode we want to guarantee that
451 -- if we return MonitoredValueChanged that only the value changed.
452 -- We do that by checking for file changes first. Otherwise it makes
453 -- more sense to do the cheaper test first.
454 checkForChanges
:: IO (Maybe (MonitorChangedReason a
))
456 | fileMonitorCheckIfOnlyValueChanged
=
457 checkFileChange cachedFileStatus cachedKey cachedResult
458 `mplusMaybeT` checkValueChange cachedKey
460 checkValueChange cachedKey
461 `mplusMaybeT` checkFileChange cachedFileStatus cachedKey cachedResult
463 mplusMaybeT
:: Monad m
=> m
(Maybe a1
) -> m
(Maybe a1
) -> m
(Maybe a1
)
464 mplusMaybeT ma mb
= do
468 Just x
-> return (Just x
)
470 -- Check if the guard value has changed
471 checkValueChange
:: a
-> IO (Maybe (MonitorChangedReason a
))
472 checkValueChange cachedKey
473 |
not (fileMonitorKeyValid currentKey cachedKey
) =
474 return (Just
(MonitoredValueChanged cachedKey
))
478 -- Check if any file has changed
479 checkFileChange
:: MonitorStateFileSet
-> a
-> Either String b
-> IO (Maybe (MonitorChangedReason a
))
480 checkFileChange cachedFileStatus cachedKey cachedResult
= do
481 res
<- probeFileSystem root cachedFileStatus
483 -- Some monitored file has changed
485 return (Just
(MonitoredFileChanged
(normalise changedPath
)))
486 -- No monitored file has changed
487 Right
(cachedFileStatus
', cacheStatus
) -> do
488 -- But we might still want to update the cache
489 whenCacheChanged cacheStatus
$
492 Right cr
-> rewriteCacheFile monitor cachedFileStatus
' cachedKey cr
496 -- | Lazily decode a triple, parsing the first two fields strictly and
497 -- returning a lazy value containing either the last one or an error.
498 -- This is helpful for cabal cache files where the first two components
499 -- contain header data that lets one test if the cache is still valid,
500 -- and the last (potentially large) component is the cached value itself.
501 -- This way we can test for cache validity without needing to pay the
502 -- cost of the decode of stale cache data. This lives here rather than
503 -- Distribution.Utils.Structured because it depends on a newer version of
504 -- binary than supported in the Cabal library proper.
505 structuredDecodeTriple
507 . (Structured a
, Structured b
, Structured c
, Binary
.Binary a
, Binary
.Binary b
, Binary
.Binary c
)
509 -> Either String (a
, b
, Either String c
)
510 structuredDecodeTriple lbs
=
512 (`runGetOrFail` lbs
) $ do
513 (_
:: Tag
(a
, b
, c
)) <- Binary
.get
514 (a
:: a
) <- Binary
.get
515 (b
:: b
) <- Binary
.get
517 cleanEither
(Left
(_
, pos
, msg
)) = Left
("Data.Binary.Get.runGet at position " ++ show pos
++ ": " ++ msg
)
518 cleanEither
(Right
(_
, _
, v
)) = Right v
519 in case partialDecode
of
520 Left
(_
, pos
, msg
) -> Left
("Data.Binary.Get.runGet at position " ++ show pos
++ ": " ++ msg
)
521 Right
(lbs
', _
, (x
, y
)) -> Right
(x
, y
, cleanEither
$ runGetOrFail
(Binary
.get
:: Binary
.Get c
) lbs
')
523 -- | Helper for reading the cache file.
525 -- This determines the type and format of the binary cache file.
527 :: (Binary a
, Structured a
, Binary b
, Structured b
)
529 -> (Either String (MonitorStateFileSet
, a
, Either String b
) -> IO r
)
531 withCacheFile
(FileMonitor
{fileMonitorCacheFile
}) k
=
532 withBinaryFile fileMonitorCacheFile ReadMode
$ \hnd
-> do
533 contents
<- structuredDecodeTriple
<$> BS
.hGetContents hnd
536 -- | Helper for writing the cache file.
538 -- This determines the type and format of the binary cache file.
540 :: (Binary a
, Structured a
, Binary b
, Structured b
)
542 -> MonitorStateFileSet
546 rewriteCacheFile FileMonitor
{fileMonitorCacheFile
} fileset key result
=
547 writeFileAtomic fileMonitorCacheFile
$
548 structuredEncode
(fileset
, key
, result
)
550 -- | Probe the file system to see if any of the monitored files have changed.
552 -- It returns Nothing if any file changed, or returns a possibly updated
553 -- file 'MonitorStateFileSet' plus an indicator of whether it actually changed.
555 -- We may need to update the cache since there may be changes in the filesystem
556 -- state which don't change any of our affected files.
558 -- Consider the glob @{proj1,proj2}\/\*.cabal@. Say we first run and find a
559 -- @proj1@ directory containing @proj1.cabal@ yet no @proj2@. If we later run
560 -- and find @proj2@ was created, yet contains no files matching @*.cabal@ then
561 -- we want to update the cache despite no changes in our relevant file set.
562 -- Specifically, we should add an mtime for this directory so we can avoid
563 -- re-traversing the directory in future runs.
566 -> MonitorStateFileSet
567 -> IO (Either FilePath (MonitorStateFileSet
, CacheChanged
))
568 probeFileSystem root
(MonitorStateFileSet singlePaths globPaths
) =
571 [ probeMonitorStateFileStatus root file status
572 | MonitorStateFile _ _ file status
<- singlePaths
574 -- The glob monitors can require state changes
577 [ probeMonitorStateGlob root globPath
578 | globPath
<- globPaths
580 return (MonitorStateFileSet singlePaths globPaths
')
582 -----------------------------------------------
583 -- Monad for checking for file system changes
585 -- We need to be able to bail out if we detect a change (using ExceptT),
586 -- but if there's no change we need to be able to rebuild the monitor
587 -- state. And we want to optimise that rebuilding by keeping track if
588 -- anything actually changed (using StateT), so that in the typical case
589 -- we can avoid rewriting the state file.
591 newtype ChangedM a
= ChangedM
(StateT CacheChanged
(ExceptT
FilePath IO) a
)
592 deriving (Functor
, Applicative
, Monad
, MonadIO
)
594 runChangedM
:: ChangedM a
-> IO (Either FilePath (a
, CacheChanged
))
595 runChangedM
(ChangedM action
) =
596 runExceptT
$ State
.runStateT action CacheUnchanged
598 somethingChanged
:: FilePath -> ChangedM a
599 somethingChanged path
= ChangedM
$ throwError path
601 cacheChanged
:: ChangedM
()
602 cacheChanged
= ChangedM
$ State
.put CacheChanged
604 mapChangedFile
:: (FilePath -> FilePath) -> ChangedM a
-> ChangedM a
605 mapChangedFile adjust
(ChangedM a
) =
606 ChangedM
(mapStateT
(withExceptT adjust
) a
)
608 data CacheChanged
= CacheChanged | CacheUnchanged
610 whenCacheChanged
:: Monad m
=> CacheChanged
-> m
() -> m
()
611 whenCacheChanged CacheChanged action
= action
612 whenCacheChanged CacheUnchanged _
= return ()
614 ----------------------
616 -- | Probe the file system to see if a single monitored file has changed.
617 probeMonitorStateFileStatus
620 -> MonitorStateFileStatus
622 probeMonitorStateFileStatus root file status
=
624 MonitorStateFileExists
->
625 probeFileExistence root file
626 MonitorStateFileModTime mtime
->
627 probeFileModificationTime root file mtime
628 MonitorStateFileHashed mtime hash
->
629 probeFileModificationTimeAndHash root file mtime hash
630 MonitorStateDirExists
->
631 probeDirExistence root file
632 MonitorStateDirModTime mtime
->
633 probeFileModificationTime root file mtime
634 MonitorStateNonExistent
->
635 probeFileNonExistence root file
636 MonitorStateAlreadyChanged
->
637 somethingChanged file
639 -- | Probe the file system to see if a monitored file glob has changed.
640 probeMonitorStateGlob
644 -> ChangedM MonitorStateGlob
645 probeMonitorStateGlob
647 (MonitorStateGlob kindfile kinddir globroot glob
) = do
648 root
<- liftIO
$ getFilePathRootDirectory globroot relroot
651 MonitorStateGlob kindfile kinddir globroot
652 <$> probeMonitorStateGlobRel kindfile kinddir root
"." glob
653 -- for absolute cases, make the changed file we report absolute too
655 mapChangedFile
(root
</>) $
656 MonitorStateGlob kindfile kinddir globroot
657 <$> probeMonitorStateGlobRel kindfile kinddir root
"" glob
659 probeMonitorStateGlobRel
665 -- ^ path of the directory we are
666 -- looking in relative to @root@
667 -> MonitorStateGlobRel
668 -> ChangedM MonitorStateGlobRel
669 probeMonitorStateGlobRel
674 (MonitorStateGlobDirs glob globPath mtime children
) = do
675 change
<- liftIO
$ checkDirectoryModificationTime
(root
</> dirName
) mtime
682 probeMonitorStateGlobRel
688 return (fname
, fstate
')
689 |
(fname
, fstate
) <- children
691 return $! MonitorStateGlobDirs glob globPath mtime children
'
693 -- directory modification time changed:
694 -- a matching subdir may have been added or deleted
698 let subdir
= root
</> dirName
</> entry
699 in liftIO
$ doesDirectoryExist subdir
701 . filter (matchGlob glob
)
702 =<< liftIO
(getDirectoryContents (root
</> dirName
))
705 traverse probeMergeResult
$
707 (\(path1
, _
) path2
-> compare path1 path2
)
710 return $! MonitorStateGlobDirs glob globPath mtime
' children
'
712 -- Note that just because the directory has changed, we don't force
713 -- a cache rewrite with 'cacheChanged' since that has some cost, and
714 -- all we're saving is scanning the directory. But we do rebuild the
715 -- cache with the new mtime', so that if the cache is rewritten for
716 -- some other reason, we'll take advantage of that.
719 :: MergeResult
(FilePath, MonitorStateGlobRel
) FilePath
720 -> ChangedM
(FilePath, MonitorStateGlobRel
)
722 -- Only in cached (directory deleted)
723 probeMergeResult
(OnlyInLeft
(path
, fstate
)) = do
724 case allMatchingFiles
(dirName
</> path
) fstate
of
725 [] -> return (path
, fstate
)
726 -- Strictly speaking we should be returning 'CacheChanged' above
727 -- as we should prune the now-missing 'MonitorStateGlobRel'. However
728 -- we currently just leave these now-redundant entries in the
729 -- cache as they cost no IO and keeping them allows us to avoid
730 -- rewriting the cache.
731 (file
: _
) -> somethingChanged file
733 -- Only in current filesystem state (directory added)
734 probeMergeResult
(OnlyInRight path
) = do
737 buildMonitorStateGlobRel
745 case allMatchingFiles
(dirName
</> path
) fstate
of
746 (file
: _
) -> somethingChanged file
747 -- This is the only case where we use 'cacheChanged' because we can
748 -- have a whole new dir subtree (of unbounded size and cost), so we
749 -- need to save the state of that new subtree in the cache.
750 [] -> cacheChanged
>> return (path
, fstate
)
753 probeMergeResult
(InBoth
(path
, fstate
) _
) = do
755 probeMonitorStateGlobRel
761 return (path
, fstate
')
763 -- \| Does a 'MonitorStateGlob' have any relevant files within it?
764 allMatchingFiles
:: FilePath -> MonitorStateGlobRel
-> [FilePath]
765 allMatchingFiles dir
(MonitorStateGlobFiles _ _ entries
) =
766 [dir
</> fname |
(fname
, _
) <- entries
]
767 allMatchingFiles dir
(MonitorStateGlobDirs _ _ _ entries
) =
769 |
(subdir
, fstate
) <- entries
770 , res
<- allMatchingFiles
(dir
</> subdir
) fstate
772 allMatchingFiles dir MonitorStateGlobDirTrailing
=
774 probeMonitorStateGlobRel
779 (MonitorStateGlobFiles glob mtime children
) = do
780 change
<- liftIO
$ checkDirectoryModificationTime
(root
</> dirName
) mtime
781 mtime
' <- case change
of
782 Nothing
-> return mtime
784 -- directory modification time changed:
785 -- a matching file may have been added or deleted
787 return . filter (matchGlob glob
)
788 =<< liftIO
(getDirectoryContents (root
</> dirName
))
790 traverse_ probeMergeResult
$
792 (\(path1
, _
) path2
-> compare path1 path2
)
797 -- Check that none of the children have changed
798 for_ children
$ \(file
, status
) ->
799 probeMonitorStateFileStatus root
(dirName
</> file
) status
801 return (MonitorStateGlobFiles glob mtime
' children
)
803 -- Again, we don't force a cache rewrite with 'cacheChanged', but we do use
804 -- the new mtime' if any.
807 :: MergeResult
(FilePath, MonitorStateFileStatus
) FilePath
809 probeMergeResult mr
= case mr
of
810 InBoth _ _
-> return ()
811 -- this is just to be able to accurately report which file changed:
812 OnlyInLeft
(path
, _
) -> somethingChanged
(dirName
</> path
)
813 OnlyInRight path
-> somethingChanged
(dirName
</> path
)
814 probeMonitorStateGlobRel _ _ _ _ MonitorStateGlobDirTrailing
=
815 return MonitorStateGlobDirTrailing
817 ------------------------------------------------------------------------------
819 -- | Update the input value and the set of files monitored by the
820 -- 'FileMonitor', plus the cached value that may be returned in future.
822 -- This takes a snapshot of the state of the monitored files right now, so
823 -- 'checkFileMonitorChanged' will look for file system changes relative to
826 -- This is typically done once the action has been completed successfully and
827 -- we have the action's result and we know what files it looked at. See
828 -- 'FileMonitor' for a full explanation.
830 -- If we do take the snapshot after the action has completed then we have a
831 -- problem. The problem is that files might have changed /while/ the action was
832 -- running but /after/ the action read them. If we take the snapshot after the
833 -- action completes then we will miss these changes. The solution is to record
834 -- a timestamp before beginning execution of the action and then we make the
835 -- conservative assumption that any file that has changed since then has
836 -- already changed, ie the file monitor state for these files will be such that
837 -- 'checkFileMonitorChanged' will report that they have changed.
839 -- So if you do use 'updateFileMonitor' after the action (so you can discover
840 -- the files used rather than predicting them in advance) then use
841 -- 'beginUpdateFileMonitor' to get a timestamp and pass that. Alternatively,
842 -- if you take the snapshot in advance of the action, or you're not monitoring
843 -- any files then you can use @Nothing@ for the timestamp parameter.
845 :: (Binary a
, Structured a
, Binary b
, Structured b
)
850 -> Maybe MonitorTimestamp
851 -- ^ timestamp when the update action started
853 -- ^ files of interest relative to root
855 -- ^ the current key value
857 -- ^ the current result value
866 hashcache
<- readCacheFileHashes monitor
867 msfs
<- buildMonitorStateFileSet startTime hashcache root monitorFiles
868 rewriteCacheFile monitor msfs cachedKey cachedResult
870 -- | A timestamp to help with the problem of file changes during actions.
871 -- See 'updateFileMonitor' for details.
872 newtype MonitorTimestamp
= MonitorTimestamp ModTime
874 -- | Record a timestamp at the beginning of an action, and when the action
875 -- completes call 'updateFileMonitor' passing it the timestamp.
876 -- See 'updateFileMonitor' for details.
877 beginUpdateFileMonitor
:: IO MonitorTimestamp
878 beginUpdateFileMonitor
= MonitorTimestamp
<$> getCurTime
880 -- | Take the snapshot of the monitored files. That is, given the
881 -- specification of the set of files we need to monitor, inspect the state
882 -- of the file system now and collect the information we'll need later to
883 -- determine if anything has changed.
884 buildMonitorStateFileSet
885 :: Maybe MonitorTimestamp
886 -- ^ optional: timestamp
887 -- of the start of the action
889 -- ^ existing file hashes
893 -- ^ patterns of interest
895 -> IO MonitorStateFileSet
896 buildMonitorStateFileSet mstartTime hashcache root
=
900 :: [MonitorStateFile
]
901 -> [MonitorStateGlob
]
903 -> IO MonitorStateFileSet
904 go
!singlePaths
!globPaths
[] =
905 return (MonitorStateFileSet
(reverse singlePaths
) (reverse globPaths
))
909 (MonitorFile kindfile kinddir path
: monitors
) = do
911 MonitorStateFile kindfile kinddir path
912 <$> buildMonitorStateFile
919 go
(monitorState
: singlePaths
) globPaths monitors
923 (MonitorFileGlob kindfile kinddir globPath
: monitors
) = do
925 buildMonitorStateGlob
932 go singlePaths
(monitorState
: globPaths
) monitors
934 buildMonitorStateFile
935 :: Maybe MonitorTimestamp
936 -- ^ start time of update
938 -- ^ existing file hashes
942 -- ^ the root directory
944 -> IO MonitorStateFileStatus
945 buildMonitorStateFile mstartTime hashcache kindfile kinddir root path
= do
946 let abspath
= root
</> path
947 isFile
<- doesFileExist abspath
948 isDir
<- doesDirectoryExist abspath
949 case (isFile
, kindfile
, isDir
, kinddir
) of
950 (_
, FileNotExists
, _
, DirNotExists
) ->
951 -- we don't need to care if it exists now, since we check at probe time
952 return MonitorStateNonExistent
953 (False, _
, False, _
) ->
954 return MonitorStateAlreadyChanged
955 (True, FileExists
, _
, _
) ->
956 return MonitorStateFileExists
957 (True, FileModTime
, _
, _
) ->
958 handleIOException MonitorStateAlreadyChanged
$ do
959 mtime
<- getModTime abspath
960 if changedDuringUpdate mstartTime mtime
961 then return MonitorStateAlreadyChanged
962 else return (MonitorStateFileModTime mtime
)
963 (True, FileHashed
, _
, _
) ->
964 handleIOException MonitorStateAlreadyChanged
$ do
965 mtime
<- getModTime abspath
966 if changedDuringUpdate mstartTime mtime
967 then return MonitorStateAlreadyChanged
969 hash
<- getFileHash hashcache abspath abspath mtime
970 return (MonitorStateFileHashed mtime hash
)
971 (_
, _
, True, DirExists
) ->
972 return MonitorStateDirExists
973 (_
, _
, True, DirModTime
) ->
974 handleIOException MonitorStateAlreadyChanged
$ do
975 mtime
<- getModTime abspath
976 if changedDuringUpdate mstartTime mtime
977 then return MonitorStateAlreadyChanged
978 else return (MonitorStateDirModTime mtime
)
979 (False, _
, True, DirNotExists
) -> return MonitorStateAlreadyChanged
980 (True, FileNotExists
, False, _
) -> return MonitorStateAlreadyChanged
982 -- | If we have a timestamp for the beginning of the update, then any file
983 -- mtime later than this means that it changed during the update and we ought
984 -- to consider the file as already changed.
985 changedDuringUpdate
:: Maybe MonitorTimestamp
-> ModTime
-> Bool
986 changedDuringUpdate
(Just
(MonitorTimestamp startTime
)) mtime
=
988 changedDuringUpdate _ _
= False
990 -- | Much like 'buildMonitorStateFileSet' but for the somewhat complicated case
993 -- This gets used both by 'buildMonitorStateFileSet' when we're taking the
994 -- file system snapshot, but also by 'probeGlobStatus' as part of checking
995 -- the monitored (globed) files for changes when we find a whole new subtree.
996 buildMonitorStateGlob
997 :: Maybe MonitorTimestamp
998 -- ^ start time of update
1000 -- ^ existing file hashes
1004 -- ^ the root directory
1006 -- ^ the matching glob
1007 -> IO MonitorStateGlob
1008 buildMonitorStateGlob
1014 (FilePathGlob globroot globPath
) = do
1015 root
<- liftIO
$ getFilePathRootDirectory globroot relroot
1016 MonitorStateGlob kindfile kinddir globroot
1017 <$> buildMonitorStateGlobRel
1026 buildMonitorStateGlobRel
1027 :: Maybe MonitorTimestamp
1028 -- ^ start time of update
1030 -- ^ existing file hashes
1034 -- ^ the root directory
1036 -- ^ directory we are examining
1037 -- relative to the root
1039 -- ^ the matching glob
1040 -> IO MonitorStateGlobRel
1041 buildMonitorStateGlobRel
1049 let absdir
= root
</> dir
1050 dirEntries
<- getDirectoryContents absdir
1051 dirMTime
<- getModTime absdir
1053 GlobDir glob globPath
' -> do
1055 filterM (\subdir
-> doesDirectoryExist (absdir
</> subdir
)) $
1056 filter (matchGlob glob
) dirEntries
1058 for
(sort subdirs
) $ \subdir
-> do
1060 buildMonitorStateGlobRel
1068 return (subdir
, fstate
)
1069 return $! MonitorStateGlobDirs glob globPath
' dirMTime subdirStates
1071 let files
= filter (matchGlob glob
) dirEntries
1073 for
(sort files
) $ \file
-> do
1075 buildMonitorStateFile
1082 return (file
, fstate
)
1083 return $! MonitorStateGlobFiles glob dirMTime filesStates
1085 return MonitorStateGlobDirTrailing
1087 -- | We really want to avoid re-hashing files all the time. We already make
1088 -- the assumption that if a file mtime has not changed then we don't need to
1089 -- bother checking if the content hash has changed. We can apply the same
1090 -- assumption when updating the file monitor state. In the typical case of
1091 -- updating a file monitor the set of files is the same or largely the same so
1092 -- we can grab the previously known content hashes with their corresponding
1094 type FileHashCache
= Map
FilePath (ModTime
, Hash
)
1096 -- | We declare it a cache hit if the mtime of a file is the same as before.
1097 lookupFileHashCache
:: FileHashCache
-> FilePath -> ModTime
-> Maybe Hash
1098 lookupFileHashCache hashcache file mtime
= do
1099 (mtime
', hash
) <- Map
.lookup file hashcache
1100 guard (mtime
' == mtime
)
1103 -- | Either get it from the cache or go read the file
1104 getFileHash
:: FileHashCache
-> FilePath -> FilePath -> ModTime
-> IO Hash
1105 getFileHash hashcache relfile absfile mtime
=
1106 case lookupFileHashCache hashcache relfile mtime
of
1107 Just hash
-> return hash
1108 Nothing
-> readFileHash absfile
1110 -- | Build a 'FileHashCache' from the previous 'MonitorStateFileSet'. While
1111 -- in principle we could preserve the structure of the previous state, given
1112 -- that the set of files to monitor can change then it's simpler just to throw
1113 -- away the structure and use a finite map.
1115 :: (Binary a
, Structured a
, Binary b
, Structured b
)
1118 readCacheFileHashes monitor
=
1119 handleDoesNotExist Map
.empty $
1120 handleErrorCall Map
.empty $
1121 withCacheFile monitor
$ \res
->
1123 Left _
-> return Map
.empty
1124 Right
(msfs
, _
, _
) -> return (mkFileHashCache msfs
)
1126 mkFileHashCache
:: MonitorStateFileSet
-> FileHashCache
1127 mkFileHashCache
(MonitorStateFileSet singlePaths globPaths
) =
1128 collectAllFileHashes singlePaths
1129 `Map
.union` collectAllGlobHashes globPaths
1131 collectAllFileHashes
:: [MonitorStateFile
] -> Map
FilePath (ModTime
, Hash
)
1132 collectAllFileHashes singlePaths
=
1134 [ (fpath
, (mtime
, hash
))
1139 (MonitorStateFileHashed mtime hash
) <-
1143 collectAllGlobHashes
:: [MonitorStateGlob
] -> Map
FilePath (ModTime
, Hash
)
1144 collectAllGlobHashes globPaths
=
1146 [ (fpath
, (mtime
, hash
))
1147 | MonitorStateGlob _ _ _ gstate
<- globPaths
1148 , (fpath
, (mtime
, hash
)) <- collectGlobHashes
"" gstate
1151 collectGlobHashes
:: FilePath -> MonitorStateGlobRel
-> [(FilePath, (ModTime
, Hash
))]
1152 collectGlobHashes dir
(MonitorStateGlobDirs _ _ _ entries
) =
1154 |
(subdir
, fstate
) <- entries
1155 , res
<- collectGlobHashes
(dir
</> subdir
) fstate
1157 collectGlobHashes dir
(MonitorStateGlobFiles _ _ entries
) =
1158 [ (dir
</> fname
, (mtime
, hash
))
1159 |
(fname
, MonitorStateFileHashed mtime hash
) <- entries
1161 collectGlobHashes _dir MonitorStateGlobDirTrailing
=
1164 ------------------------------------------------------------------------------
1168 -- | Within the @root@ directory, check if @file@ has its 'ModTime' is
1169 -- the same as @mtime@, short-circuiting if it is different.
1170 probeFileModificationTime
:: FilePath -> FilePath -> ModTime
-> ChangedM
()
1171 probeFileModificationTime root file mtime
= do
1172 unchanged
<- liftIO
$ checkModificationTimeUnchanged root file mtime
1173 unless unchanged
(somethingChanged file
)
1175 -- | Within the @root@ directory, check if @file@ has its 'ModTime' and
1176 -- 'Hash' is the same as @mtime@ and @hash@, short-circuiting if it is
1178 probeFileModificationTimeAndHash
1184 probeFileModificationTimeAndHash root file mtime hash
= do
1187 checkFileModificationTimeAndHashUnchanged root file mtime hash
1188 unless unchanged
(somethingChanged file
)
1190 -- | Within the @root@ directory, check if @file@ still exists as a file.
1191 -- If it *does not* exist, short-circuit.
1192 probeFileExistence
:: FilePath -> FilePath -> ChangedM
()
1193 probeFileExistence root file
= do
1194 existsFile
<- liftIO
$ doesFileExist (root
</> file
)
1195 unless existsFile
(somethingChanged file
)
1197 -- | Within the @root@ directory, check if @dir@ still exists.
1198 -- If it *does not* exist, short-circuit.
1199 probeDirExistence
:: FilePath -> FilePath -> ChangedM
()
1200 probeDirExistence root dir
= do
1201 existsDir
<- liftIO
$ doesDirectoryExist (root
</> dir
)
1202 unless existsDir
(somethingChanged dir
)
1204 -- | Within the @root@ directory, check if @file@ still does not exist.
1205 -- If it *does* exist, short-circuit.
1206 probeFileNonExistence
:: FilePath -> FilePath -> ChangedM
()
1207 probeFileNonExistence root file
= do
1208 existsFile
<- liftIO
$ doesFileExist (root
</> file
)
1209 existsDir
<- liftIO
$ doesDirectoryExist (root
</> file
)
1210 when (existsFile || existsDir
) (somethingChanged file
)
1212 -- | Returns @True@ if, inside the @root@ directory, @file@ has the same
1213 -- 'ModTime' as @mtime@.
1214 checkModificationTimeUnchanged
1219 checkModificationTimeUnchanged root file mtime
=
1220 handleIOException
False $ do
1221 mtime
' <- getModTime
(root
</> file
)
1222 return (mtime
== mtime
')
1224 -- | Returns @True@ if, inside the @root@ directory, @file@ has the
1225 -- same 'ModTime' and 'Hash' as @mtime and @chash@.
1226 checkFileModificationTimeAndHashUnchanged
1232 checkFileModificationTimeAndHashUnchanged root file mtime chash
=
1233 handleIOException
False $ do
1234 mtime
' <- getModTime
(root
</> file
)
1238 chash
' <- readFileHash
(root
</> file
)
1239 return (chash
== chash
')
1241 -- | Read a non-cryptographic hash of a @file@.
1242 readFileHash
:: FilePath -> IO Hash
1244 withBinaryFile file ReadMode
$ \hnd
->
1245 evaluate
. Hashable
.hash
=<< BS
.hGetContents hnd
1247 -- | Given a directory @dir@, return @Nothing@ if its 'ModTime'
1248 -- is the same as @mtime@, and the new 'ModTime' if it is not.
1249 checkDirectoryModificationTime
:: FilePath -> ModTime
-> IO (Maybe ModTime
)
1250 checkDirectoryModificationTime dir mtime
=
1251 handleIOException Nothing
$ do
1252 mtime
' <- getModTime dir
1255 else return (Just mtime
')
1257 -- | Run an IO computation, returning the first argument @e@ if there is an 'error'
1258 -- call. ('ErrorCall')
1259 handleErrorCall
:: a
-> IO a
-> IO a
1260 handleErrorCall e
= handle handler
where
1261 #if MIN_VERSION_base
(4,9,0)
1262 handler
(ErrorCallWithLocation _ _
) = return e
1264 handler
(ErrorCall _
) = return e
1267 -- | Run an IO computation, returning @e@ if there is any 'IOException'.
1269 -- This policy is OK in the file monitor code because it just causes the
1270 -- monitor to report that something changed, and then code reacting to that
1271 -- will normally encounter the same IO exception when it re-runs the action
1272 -- that uses the file.
1273 handleIOException
:: a
-> IO a
-> IO a
1274 handleIOException e
=
1275 handle
(anyIOException e
)
1277 anyIOException
:: a
-> IOException
-> IO a
1278 anyIOException x _
= return x
1280 ------------------------------------------------------------------------------