1 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
5 -- Module : Distribution.PackageDescription.Check.Monad
6 -- Copyright : Francesco Ariis 2022
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
20 , CheckPackageContentOps
(..)
21 , CheckPreDistributionOps
(..)
22 , TargetAnnotation
(..)
24 , CheckExplanation
(..)
44 import Distribution
.Compat
.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
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).
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
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
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.
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
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
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.
140 -> GenericPackageDescription
142 pristineCheckCtx ci gpd
=
143 let ens
= map fst (condExecutables gpd
)
147 (specVersion
. packageDescription
$ gpd
)
148 (desugarBuildToolSimple
(packageName gpd
) ens
)
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
160 data TargetAnnotation a
= TargetAnnotation
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.
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
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.
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
216 -- | As 'checkP' but always succeeding.
217 tellP
:: Monad m
=> PackageCheck
-> CheckM m
()
220 -- | Add a package warning withoutu performing any check.
221 tellCM
:: Monad m
=> PackageCheck
-> CheckM m
()
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
)
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
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.
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.
270 ops
<- asksCM
(acc
. ccInterface
)
271 maybe (return ()) l ops
273 l
:: i m
-> CheckM m
()
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.
282 => Bool -- Is there something to warn about?
283 -> PackageCheck
-- Warn message.
285 check
True ck
= tellCM ck
286 check
False _
= return ()
288 -- | Pure check not requiring IO or other interfaces.
291 => Bool -- Is there something to warn about?
292 -> PackageCheck
-- Warn message.
295 pb
<- asksCM
(ciPureChecks
. ccInterface
)
298 -- Check with 'CheckPackageContentOps' operations (i.e. package file checks).
303 => (CheckPackageContentOps m
-> m
Bool)
304 -- Actual check to perform with CPC interface
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.
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).
321 checkIntDep acc mck
= do
322 po
<- asksCM
(acc
. ccInterface
)
323 maybe (return ()) (lc
. mck
) po
325 lc
:: Monad m
=> m
(Maybe PackageCheck
) -> CheckM m
()
328 maybe (return ()) (check
True) b
330 -- | As 'checkIntDep', but 'PackageCheck' does not depend on the monadic
335 => (CheckInterface m
-> Maybe (i m
))
336 -- Where to get the interface (if available).
338 -- Condition to check
340 -- Warning message to add (does not depend on `m`).
348 then return $ Just ck
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`.
364 => CabalSpecVersion
-- Perform this check only if our
365 -- spec version is < than this.
366 -> Bool -- Check condition.
367 -> PackageCheck
-- Check message.
369 checkSpecVer vc cond c
= do
370 vp
<- asksCM ccSpecVersion
371 unless (vp
>= vc
) (checkP cond c
)