1 {-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables, BangPatterns #-}
3 -- | An abstraction for re-running actions if values or files have changed.
5 -- This is not a full-blown make-style incremental build system, it's a bit
6 -- more ad-hoc than that, but it's easier to integrate with existing code.
8 -- It's a convenient interface to the "Distribution.Client.FileMonitor"
11 module Distribution
.Client
.RebuildMonad
(
18 -- * Setting up file monitoring
23 monitorNonExistentFile
,
25 monitorNonExistentDirectory
,
26 monitorDirectoryExistence
,
27 monitorFileOrDirectory
,
28 monitorFileSearchPath
,
29 monitorFileHashedSearchPath
,
30 -- ** Monitoring file globs
32 monitorFileGlobExistence
,
38 -- * Using a file monitor
44 delayInitSharedResource
,
45 delayInitSharedResources
,
47 getDirectoryContentsMonitored
,
48 createDirectoryMonitored
,
49 monitorDirectoryStatus
,
50 doesFileExistMonitored
,
53 findFileWithExtensionMonitored
,
54 findFirstFileMonitored
,
59 import Distribution
.Client
.Compat
.Prelude
61 import Distribution
.Client
.FileMonitor
62 import Distribution
.Client
.Glob
hiding (matchFileGlob
)
63 import qualified Distribution
.Client
.Glob
as Glob
(matchFileGlob
)
65 import Distribution
.Simple
.Utils
(debug
)
67 import qualified Data
.Map
.Strict
as Map
68 import Control
.Monad
.State
as State
69 import Control
.Monad
.Reader
as Reader
70 import Control
.Concurrent
.MVar
(MVar
, newMVar
, modifyMVar
)
71 import System
.FilePath
72 import System
.Directory
75 -- | A monad layered on top of 'IO' to help with re-running actions when the
76 -- input files and values they depend on change. The crucial operations are
77 -- 'rerunIfChanged' and 'monitorFiles'.
79 newtype Rebuild a
= Rebuild
(ReaderT
FilePath (StateT
[MonitorFilePath
] IO) a
)
80 deriving (Functor
, Applicative
, Monad
, MonadIO
)
82 -- | Use this within the body action of 'rerunIfChanged' to declare that the
83 -- action depends on the given files. This can be based on what the action
84 -- actually did. It is these files that will be checked for changes next
85 -- time 'rerunIfChanged' is called for that 'FileMonitor'.
87 -- Relative paths are interpreted as relative to an implicit root, ultimately
88 -- passed in to 'runRebuild'.
90 monitorFiles
:: [MonitorFilePath
] -> Rebuild
()
91 monitorFiles filespecs
= Rebuild
(State
.modify
(filespecs
++))
93 -- | Run a 'Rebuild' IO action.
94 unRebuild
:: FilePath -> Rebuild a
-> IO (a
, [MonitorFilePath
])
95 unRebuild rootDir
(Rebuild action
) = runStateT
(runReaderT action rootDir
) []
97 -- | Run a 'Rebuild' IO action.
98 runRebuild
:: FilePath -> Rebuild a
-> IO a
99 runRebuild rootDir
(Rebuild action
) = evalStateT
(runReaderT action rootDir
) []
101 -- | Run a 'Rebuild' IO action.
102 execRebuild
:: FilePath -> Rebuild a
-> IO [MonitorFilePath
]
103 execRebuild rootDir
(Rebuild action
) = execStateT
(runReaderT action rootDir
) []
105 -- | The root that relative paths are interpreted as being relative to.
106 askRoot
:: Rebuild
FilePath
107 askRoot
= Rebuild Reader
.ask
109 -- | This captures the standard use pattern for a 'FileMonitor': given a
110 -- monitor, an action and the input value the action depends on, either
111 -- re-run the action to get its output, or if the value and files the action
112 -- depends on have not changed then return a previously cached action result.
114 -- The result is still in the 'Rebuild' monad, so these can be nested.
116 -- Do not share 'FileMonitor's between different uses of 'rerunIfChanged'.
118 rerunIfChanged
:: (Binary a
, Structured a
, Binary b
, Structured b
)
124 rerunIfChanged verbosity monitor key action
= do
126 changed
<- liftIO
$ checkFileMonitorChanged monitor rootDir key
128 MonitorUnchanged result files
-> do
129 liftIO
$ debug verbosity
$ "File monitor '" ++ monitorName
134 MonitorChanged reason
-> do
135 liftIO
$ debug verbosity
$ "File monitor '" ++ monitorName
136 ++ "' changed: " ++ showReason reason
137 startTime
<- liftIO
$ beginUpdateFileMonitor
138 (result
, files
) <- liftIO
$ unRebuild rootDir action
139 liftIO
$ updateFileMonitor monitor rootDir
140 (Just startTime
) files key result
144 monitorName
= takeFileName
(fileMonitorCacheFile monitor
)
146 showReason
(MonitoredFileChanged file
) = "file " ++ file
147 showReason
(MonitoredValueChanged _
) = "monitor value changed"
148 showReason MonitorFirstRun
= "first run"
149 showReason MonitorCorruptCache
= "invalid cache file"
152 -- | When using 'rerunIfChanged' for each element of a list of actions, it is
153 -- sometimes the case that each action needs to make use of some resource. e.g.
156 -- > [ rerunIfChanged verbosity monitor key $ do
157 -- > resource <- mkResource
158 -- > ... -- use the resource
161 -- For efficiency one would like to share the resource between the actions
162 -- but the straightforward way of doing this means initialising it every time
163 -- even when no actions need re-running.
165 -- > resource <- mkResource
167 -- > [ rerunIfChanged verbosity monitor key $ do
168 -- > ... -- use the resource
171 -- This utility allows one to get the best of both worlds:
173 -- > getResource <- delayInitSharedResource mkResource
175 -- > [ rerunIfChanged verbosity monitor key $ do
176 -- > resource <- getResource
177 -- > ... -- use the resource
180 delayInitSharedResource
:: forall a
. IO a
-> Rebuild
(Rebuild a
)
181 delayInitSharedResource action
= do
182 var
<- liftIO
(newMVar Nothing
)
183 return (liftIO
(getOrInitResource var
))
185 getOrInitResource
:: MVar
(Maybe a
) -> IO a
186 getOrInitResource var
=
187 modifyMVar var
$ \mx
->
189 Just x
-> return (Just x
, x
)
195 -- | Much like 'delayInitSharedResource' but for a keyed set of resources.
197 -- > getResource <- delayInitSharedResource mkResource
199 -- > [ rerunIfChanged verbosity monitor key $ do
200 -- > resource <- getResource key
201 -- > ... -- use the resource
204 delayInitSharedResources
:: forall k v
. Ord k
206 -> Rebuild
(k
-> Rebuild v
)
207 delayInitSharedResources action
= do
208 var
<- liftIO
(newMVar Map
.empty)
209 return (liftIO
. getOrInitResource var
)
211 getOrInitResource
:: MVar
(Map k v
) -> k
-> IO v
212 getOrInitResource var k
=
213 modifyMVar var
$ \m
->
214 case Map
.lookup k m
of
215 Just x
-> return (m
, x
)
218 let !m
' = Map
.insert k x m
222 -- | Utility to match a file glob against the file system, starting from a
223 -- given root directory. The results are all relative to the given root.
225 -- Since this operates in the 'Rebuild' monad, it also monitors the given glob
228 matchFileGlob
:: FilePathGlob
-> Rebuild
[FilePath]
229 matchFileGlob glob
= do
231 monitorFiles
[monitorFileGlobExistence glob
]
232 liftIO
$ Glob
.matchFileGlob root glob
234 getDirectoryContentsMonitored
:: FilePath -> Rebuild
[FilePath]
235 getDirectoryContentsMonitored dir
= do
236 exists
<- monitorDirectoryStatus dir
238 then liftIO
$ getDirectoryContents dir
241 createDirectoryMonitored
:: Bool -> FilePath -> Rebuild
()
242 createDirectoryMonitored createParents dir
= do
243 monitorFiles
[monitorDirectoryExistence dir
]
244 liftIO
$ createDirectoryIfMissing createParents dir
246 -- | Monitor a directory as in 'monitorDirectory' if it currently exists or
247 -- as 'monitorNonExistentDirectory' if it does not.
248 monitorDirectoryStatus
:: FilePath -> Rebuild
Bool
249 monitorDirectoryStatus dir
= do
250 exists
<- liftIO
$ doesDirectoryExist dir
251 monitorFiles
[if exists
252 then monitorDirectory dir
253 else monitorNonExistentDirectory dir
]
256 -- | Like 'doesFileExist', but in the 'Rebuild' monad. This does
257 -- NOT track the contents of 'FilePath'; use 'need' in that case.
258 doesFileExistMonitored
:: FilePath -> Rebuild
Bool
259 doesFileExistMonitored f
= do
261 exists
<- liftIO
$ doesFileExist (root
</> f
)
262 monitorFiles
[if exists
263 then monitorFileExistence f
264 else monitorNonExistentFile f
]
267 -- | Monitor a single file
268 need
:: FilePath -> Rebuild
()
269 need f
= monitorFiles
[monitorFileHashed f
]
271 -- | Monitor a file if it exists; otherwise check for when it
272 -- gets created. This is a bit better for recompilation avoidance
273 -- because sometimes users give bad package metadata, and we don't
274 -- want to repeatedly rebuild in this case (which we would if we
275 -- need'ed a non-existent file).
276 needIfExists
:: FilePath -> Rebuild
()
279 exists
<- liftIO
$ doesFileExist (root
</> f
)
280 monitorFiles
[if exists
281 then monitorFileHashed f
282 else monitorNonExistentFile f
]
284 -- | Like 'findFileWithExtension', but in the 'Rebuild' monad.
285 findFileWithExtensionMonitored
289 -> Rebuild
(Maybe FilePath)
290 findFileWithExtensionMonitored extensions searchPath baseName
=
291 findFirstFileMonitored
id
292 [ path
</> baseName
<.> ext
293 | path
<- nub searchPath
294 , ext
<- nub extensions
]
296 -- | Like 'findFirstFile', but in the 'Rebuild' monad.
297 findFirstFileMonitored
:: forall a
. (a
-> FilePath) -> [a
] -> Rebuild
(Maybe a
)
298 findFirstFileMonitored file
= findFirst
299 where findFirst
:: [a
] -> Rebuild
(Maybe a
)
300 findFirst
[] = return Nothing
301 findFirst
(x
:xs
) = do exists
<- doesFileExistMonitored
(file x
)
306 -- | Like 'findFile', but in the 'Rebuild' monad.
307 findFileMonitored
:: [FilePath] -> FilePath -> Rebuild
(Maybe FilePath)
308 findFileMonitored searchPath fileName
=
309 findFirstFileMonitored
id
311 | path
<- nub searchPath
]