Merge pull request #10525 from 9999years/field-stanza-names
[cabal.git] / Cabal / src / Distribution / PackageDescription / Check / Monad.hs
blob23d37570800866a8a965bbec6400be38ca42ebcc
1 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
4 -- |
5 -- Module : Distribution.PackageDescription.Check.Monad
6 -- Copyright : Francesco Ariis 2022
7 -- License : BSD3
8 --
9 -- Maintainer : cabal-devel@haskell.org
10 -- Portability : portable
12 -- Primitives for package checking: check types and monadic interface.
13 -- Having these primitives in a different module allows us to appropriately
14 -- limit/manage the interface to suit checking needs.
15 module Distribution.PackageDescription.Check.Monad
16 ( -- * Types and constructors
17 CheckM (..)
18 , execCheckM
19 , CheckInterface (..)
20 , CheckPackageContentOps (..)
21 , CheckPreDistributionOps (..)
22 , TargetAnnotation (..)
23 , PackageCheck (..)
24 , CheckExplanation (..)
25 , CEType (..)
26 , WarnLang (..)
27 , CheckCtx (..)
28 , pristineCheckCtx
29 , initCheckCtx
30 , PNames (..)
32 -- * Operations
33 , ppPackageCheck
34 , isHackageDistError
35 , asksCM
36 , localCM
37 , checkP
38 , checkPkg
39 , liftInt
40 , tellP
41 , checkSpecVer
42 ) where
44 import Distribution.Compat.Prelude
45 import Prelude ()
47 import Distribution.CabalSpecVersion (CabalSpecVersion)
48 import Distribution.Package (packageName)
49 import Distribution.PackageDescription.Check.Warning
50 import Distribution.Simple.BuildToolDepends (desugarBuildToolSimple)
51 import Distribution.Simple.Glob (Glob, GlobResult)
52 import Distribution.Types.ExeDependency (ExeDependency)
53 import Distribution.Types.GenericPackageDescription
54 import Distribution.Types.LegacyExeDependency (LegacyExeDependency)
55 import Distribution.Types.PackageDescription (package, specVersion)
56 import Distribution.Types.PackageId (PackageIdentifier)
57 import Distribution.Types.UnqualComponentName
59 import qualified Control.Monad.Reader as Reader
60 import qualified Control.Monad.Trans.Class as Trans
61 import qualified Control.Monad.Writer as Writer
62 import qualified Data.ByteString.Lazy as BS
63 import qualified Data.Set as Set
65 import Control.Monad
67 -- Monadic interface for for Distribution.PackageDescription.Check.
69 -- Monadic checking allows us to have a fine grained control on checks
70 -- (e.g. omitting warning checks in certain situations).
72 -- * Interfaces
76 -- | Which interface to we have available/should we use? (to perform: pure
77 -- checks, package checks, pre-distribution checks.)
78 data CheckInterface m = CheckInterface
79 { ciPureChecks :: Bool
80 , -- Perform pure checks?
81 ciPackageOps :: Maybe (CheckPackageContentOps m)
82 , -- If you want to perform package contents
83 -- checks, provide an interface.
84 ciPreDistOps :: Maybe (CheckPreDistributionOps m)
85 -- If you want to work-tree checks, provide
86 -- an interface.
89 -- | A record of operations needed to check the contents of packages.
90 -- Abstracted over `m` to provide flexibility (could be IO, a .tar.gz
91 -- file, etc).
92 data CheckPackageContentOps m = CheckPackageContentOps
93 { doesFileExist :: FilePath -> m Bool
94 , doesDirectoryExist :: FilePath -> m Bool
95 , getDirectoryContents :: FilePath -> m [FilePath]
96 , getFileContents :: FilePath -> m BS.ByteString
99 -- | A record of operations needed to check contents *of the work tree*
100 -- (compare it with 'CheckPackageContentOps'). This is still `m` abstracted
101 -- in case in the future we can obtain the same infos other than from IO
102 -- (e.g. a VCS work tree).
103 data CheckPreDistributionOps m = CheckPreDistributionOps
104 { runDirFileGlobM :: FilePath -> Glob -> m [GlobResult FilePath]
105 , getDirectoryContentsM :: FilePath -> m [FilePath]
108 -- | Context to perform checks (will be the Reader part in your monad).
109 data CheckCtx m = CheckCtx
110 { ccInterface :: CheckInterface m
111 , -- Interface for checks.
113 -- Contextual infos for checks.
114 ccFlag :: Bool
115 , -- Are we under a user flag?
117 -- Convenience bits that we prefer to carry
118 -- in our Reader monad instead of passing it
119 -- via ->, as they are often useful and often
120 -- in deeply nested places in the GPD tree.
121 ccSpecVersion :: CabalSpecVersion
122 , -- Cabal version.
123 ccDesugar :: LegacyExeDependency -> Maybe ExeDependency
124 , -- A desugaring function from
125 -- Distribution.Simple.BuildToolDepends
126 -- (desugarBuildToolSimple). Again since it
127 -- eats PackageName and a list of executable
128 -- names, it is more convenient to pass it
129 -- via Reader.
130 ccNames :: PNames
131 -- Various names (id, libs, execs, tests,
132 -- benchs), convenience.
135 -- | Creates a pristing 'CheckCtx'. With pristine we mean everything that
136 -- can be deduced by GPD but *not* user flags information.
137 pristineCheckCtx
138 :: Monad m
139 => CheckInterface m
140 -> GenericPackageDescription
141 -> CheckCtx m
142 pristineCheckCtx ci gpd =
143 let ens = map fst (condExecutables gpd)
144 in CheckCtx
146 False
147 (specVersion . packageDescription $ gpd)
148 (desugarBuildToolSimple (packageName gpd) ens)
149 (initPNames gpd)
151 -- | Adds useful bits to 'CheckCtx' (as now, whether we are operating under
152 -- a user off-by-default flag).
153 initCheckCtx :: Monad m => TargetAnnotation a -> CheckCtx m -> CheckCtx m
154 initCheckCtx t c = c{ccFlag = taPackageFlag t}
156 -- | 'TargetAnnotation' collects contextual information on the target we are
157 -- realising: a buildup of the various slices of the target (a library,
158 -- executable, etc. — is a monoid) whether we are under an off-by-default
159 -- package flag.
160 data TargetAnnotation a = TargetAnnotation
161 { taTarget :: a
162 , -- The target we are building (lib, exe, etc.)
163 taPackageFlag :: Bool
164 -- Whether we are under an off-by-default package flag.
166 deriving (Show, Eq, Ord)
168 -- | A collection os names, shipping tuples around is annoying.
169 data PNames = PNames
170 { pnPackageId :: PackageIdentifier -- Package ID…
171 -- … and a bunch of lib, exe, test, bench names.
172 , pnSubLibs :: [UnqualComponentName]
173 , pnExecs :: [UnqualComponentName]
174 , pnTests :: [UnqualComponentName]
175 , pnBenchs :: [UnqualComponentName]
178 -- | Init names from a GPD.
179 initPNames :: GenericPackageDescription -> PNames
180 initPNames gpd =
181 PNames
182 (package . packageDescription $ gpd)
183 (map fst $ condSubLibraries gpd)
184 (map fst $ condExecutables gpd)
185 (map fst $ condTestSuites gpd)
186 (map fst $ condBenchmarks gpd)
188 -- | Check monad, carrying a context, collecting 'PackageCheck's.
189 -- Using Set for writer (automatic sort) is useful for output stability
190 -- on different platforms.
191 -- It is nothing more than a monad stack with Reader+Writer.
192 -- `m` is the monad that could be used to do package/file checks.
193 newtype CheckM m a
194 = CheckM
195 ( Reader.ReaderT
196 (CheckCtx m)
197 ( Writer.WriterT
198 (Set.Set PackageCheck)
203 deriving (Functor, Applicative, Monad)
205 -- Not autoderiving MonadReader and MonadWriter gives us better
206 -- control on the interface of CheckM.
208 -- | Execute a CheckM monad, leaving `m [PackageCheck]` which can be
209 -- run in the appropriate `m` environment (IO, pure, …).
210 execCheckM :: Monad m => CheckM m () -> CheckCtx m -> m [PackageCheck]
211 execCheckM (CheckM rwm) ctx =
212 let wm = Reader.runReaderT rwm ctx
213 m = Writer.execWriterT wm
214 in Set.toList <$> m
216 -- | As 'checkP' but always succeeding.
217 tellP :: Monad m => PackageCheck -> CheckM m ()
218 tellP = checkP True
220 -- | Add a package warning withoutu performing any check.
221 tellCM :: Monad m => PackageCheck -> CheckM m ()
222 tellCM ck = do
223 cf <- asksCM ccFlag
224 unless
225 (cf && canSkip ck)
226 -- Do not push this message if the warning is not severe *and*
227 -- we are under a non-default package flag.
228 (CheckM . Writer.tell $ Set.singleton ck)
229 where
230 -- Check if we can skip this error if we are under a
231 -- non-default user flag.
232 canSkip :: PackageCheck -> Bool
233 canSkip wck = not (isSevereLocal wck) || isErrAllowable wck
235 isSevereLocal :: PackageCheck -> Bool
236 isSevereLocal (PackageBuildImpossible _) = True
237 isSevereLocal (PackageBuildWarning _) = True
238 isSevereLocal (PackageDistSuspicious _) = False
239 isSevereLocal (PackageDistSuspiciousWarn _) = False
240 isSevereLocal (PackageDistInexcusable _) = True
242 -- There are some errors which, even though severe, will
243 -- be allowed by Hackage *if* under a non-default flag.
244 isErrAllowable :: PackageCheck -> Bool
245 isErrAllowable c = case extractCheckExplantion c of
246 (WErrorUnneeded _) -> True
247 (JUnneeded _) -> True
248 (FDeferTypeErrorsUnneeded _) -> True
249 (DynamicUnneeded _) -> True
250 (ProfilingUnneeded _) -> True
251 _ -> False
253 -- | Lift a monadic computation to CM.
254 liftCM :: Monad m => m a -> CheckM m a
255 liftCM ma = CheckM . Trans.lift . Trans.lift $ ma
257 -- | Lift a monadic action via an interface. Missing interface, no action.
258 liftInt
259 :: forall m i
260 . Monad m
261 => (CheckInterface m -> Maybe (i m))
262 -- Check interface, may or may not exist. If it does not,
263 -- the check simply will not be performed.
264 -> (i m -> m [PackageCheck])
265 -- The actual check to perform with the above-mentioned
266 -- interface. Note the [] around `PackageCheck`, this is
267 -- meant to perform/collect multiple checks.
268 -> CheckM m ()
269 liftInt acc f = do
270 ops <- asksCM (acc . ccInterface)
271 maybe (return ()) l ops
272 where
273 l :: i m -> CheckM m ()
274 l wi = do
275 cks <- liftCM (f wi)
276 mapM_ (check True) cks
278 -- | Most basic check function. You do not want to export this, rather export
279 -- “smart” functions (checkP, checkPkg) to enforce relevant properties.
280 check
281 :: Monad m
282 => Bool -- Is there something to warn about?
283 -> PackageCheck -- Warn message.
284 -> CheckM m ()
285 check True ck = tellCM ck
286 check False _ = return ()
288 -- | Pure check not requiring IO or other interfaces.
289 checkP
290 :: Monad m
291 => Bool -- Is there something to warn about?
292 -> PackageCheck -- Warn message.
293 -> CheckM m ()
294 checkP b ck = do
295 pb <- asksCM (ciPureChecks . ccInterface)
296 when pb (check b ck)
298 -- Check with 'CheckPackageContentOps' operations (i.e. package file checks).
300 checkPkg
301 :: forall m
302 . Monad m
303 => (CheckPackageContentOps m -> m Bool)
304 -- Actual check to perform with CPC interface
305 -> PackageCheck
306 -- Warn message.
307 -> CheckM m ()
308 checkPkg f ck = checkInt ciPackageOps f ck
310 -- | Generalised version for checks that need an interface. We pass a Reader
311 -- accessor to such interface ‘i’, a check function.
312 checkIntDep
313 :: forall m i
314 . Monad m
315 => (CheckInterface m -> Maybe (i m))
316 -- Check interface, may or may not exist. If it does not,
317 -- the check simply will not be performed.
318 -> (i m -> m (Maybe PackageCheck))
319 -- The actual check to perform (single check).
320 -> CheckM m ()
321 checkIntDep acc mck = do
322 po <- asksCM (acc . ccInterface)
323 maybe (return ()) (lc . mck) po
324 where
325 lc :: Monad m => m (Maybe PackageCheck) -> CheckM m ()
326 lc wmck = do
327 b <- liftCM wmck
328 maybe (return ()) (check True) b
330 -- | As 'checkIntDep', but 'PackageCheck' does not depend on the monadic
331 -- computation.
332 checkInt
333 :: forall m i
334 . Monad m
335 => (CheckInterface m -> Maybe (i m))
336 -- Where to get the interface (if available).
337 -> (i m -> m Bool)
338 -- Condition to check
339 -> PackageCheck
340 -- Warning message to add (does not depend on `m`).
341 -> CheckM m ()
342 checkInt acc f ck =
343 checkIntDep
345 ( \ops -> do
346 b <- f ops
347 if b
348 then return $ Just ck
349 else return Nothing
352 -- | `local` (from Control.Monad.Reader) for CheckM.
353 localCM :: Monad m => (CheckCtx m -> CheckCtx m) -> CheckM m () -> CheckM m ()
354 localCM cf (CheckM im) = CheckM $ Reader.local cf im
356 -- | `ask` (from Control.Monad.Reader) for CheckM.
357 asksCM :: Monad m => (CheckCtx m -> a) -> CheckM m a
358 asksCM f = CheckM $ Reader.asks f
360 -- As checkP, but with an additional condition: the check will be performed
361 -- only if our spec version is < `vc`.
362 checkSpecVer
363 :: Monad m
364 => CabalSpecVersion -- Perform this check only if our
365 -- spec version is < than this.
366 -> Bool -- Check condition.
367 -> PackageCheck -- Check message.
368 -> CheckM m ()
369 checkSpecVer vc cond c = do
370 vp <- asksCM ccSpecVersion
371 unless (vp >= vc) (checkP cond c)