Cabal-3.16.1.0: src/Distribution/PackageDescription/Check/Conditional.hs
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Distribution.PackageDescription.Check.Conditional
-- Copyright : Lennart Kolmodin 2008, Francesco Ariis 2023
-- License : BSD3
--
-- Maintainer : cabal-devel@haskell.org
-- Portability : portable
--
-- Checks on conditional targets (libraries, executables, etc. that are
-- still inside a CondTree and related checks that can only be performed
-- here (variables, duplicated modules).
module Distribution.PackageDescription.Check.Conditional
( checkCondTarget
, checkDuplicateModules
) where
import Distribution.Compat.Prelude
import Prelude ()
import Distribution.Compiler
import Distribution.ModuleName (ModuleName)
import Distribution.Package
import Distribution.PackageDescription
import Distribution.PackageDescription.Check.Monad
import Distribution.System
import qualified Data.Map as Map
import Control.Monad
-- As a prerequisite to some checks, we transform a target CondTree into
-- a CondTree of “target + useful context”.
-- This is slightly clearer, is easier to walk without resorting to
-- list comprehensions, allows us in the future to apply some sensible
-- “optimisations” to checks (exclusive branches, etc.).
-- | @nf@ function is needed to appropriately name some targets which need
-- to be spoonfed (otherwise name appears as "").
initTargetAnnotation
:: Monoid a
=> (UnqualComponentName -> a -> a) -- Naming function for targets.
-> UnqualComponentName
-> TargetAnnotation a
initTargetAnnotation nf n = TargetAnnotation (nf n mempty) False
-- | We “build up” target from various slices.
updateTargetAnnotation
:: Monoid a
=> a -- A target (lib, exe, test, …)
-> TargetAnnotation a
-> TargetAnnotation a
updateTargetAnnotation t ta = ta{taTarget = taTarget ta <> t}
-- | Before walking a target 'CondTree', we need to annotate it with
-- information relevant to the checks (read 'TaraAnn' and 'checkCondTarget'
-- doc for more info).
annotateCondTree
:: forall a
. (Eq a, Monoid a)
=> [PackageFlag] -- User flags.
-> TargetAnnotation a
-> CondTree ConfVar [Dependency] a
-> CondTree ConfVar [Dependency] (TargetAnnotation a)
annotateCondTree fs ta (CondNode a c bs) =
let ta' = updateTargetAnnotation a ta
bs' = map (annotateBranch ta') bs
bs'' = crossAnnotateBranches defTrueFlags bs'
in CondNode ta' c bs''
where
annotateBranch
:: TargetAnnotation a
-> CondBranch ConfVar [Dependency] a
-> CondBranch
ConfVar
[Dependency]
(TargetAnnotation a)
annotateBranch wta (CondBranch k t mf) =
let uf = isPkgFlagCond k
wta' = wta{taPackageFlag = taPackageFlag wta || uf}
atf = annotateCondTree fs
in CondBranch
k
(atf wta' t)
(atf wta <$> mf)
-- Note how we are passing the *old* wta
-- in the `else` branch, since we are not
-- under that flag.
-- We only want to pick up variables that are flags and that are
-- \*off* by default.
isPkgFlagCond :: Condition ConfVar -> Bool
isPkgFlagCond (Lit _) = False
isPkgFlagCond (Var (PackageFlag f)) = elem f defOffFlags
isPkgFlagCond (Var _) = False
isPkgFlagCond (CNot cn) = not (isPkgFlagCond cn)
isPkgFlagCond (CAnd ca cb) = isPkgFlagCond ca || isPkgFlagCond cb
isPkgFlagCond (COr ca cb) = isPkgFlagCond ca && isPkgFlagCond cb
-- Package flags that are off by default *and* that are manual.
defOffFlags =
map flagName $
filter
( \f ->
not (flagDefault f)
&& flagManual f
)
fs
defTrueFlags :: [PackageFlag]
defTrueFlags = filter flagDefault fs
-- Propagate contextual information in CondTree branches. This is
-- needed as CondTree is a rosetree and not a binary tree.
crossAnnotateBranches
:: forall a
. (Eq a, Monoid a)
=> [PackageFlag] -- `default: true` flags.
-> [CondBranch ConfVar [Dependency] (TargetAnnotation a)]
-> [CondBranch ConfVar [Dependency] (TargetAnnotation a)]
crossAnnotateBranches fs bs = map crossAnnBranch bs
where
crossAnnBranch
:: CondBranch ConfVar [Dependency] (TargetAnnotation a)
-> CondBranch ConfVar [Dependency] (TargetAnnotation a)
crossAnnBranch wr =
let
rs = filter (/= wr) bs
ts = mapMaybe realiseBranch rs
in
updateTargetAnnBranch (mconcat ts) wr
realiseBranch :: CondBranch ConfVar [Dependency] (TargetAnnotation a) -> Maybe a
realiseBranch b =
let
-- We are only interested in True by default package flags.
realiseBranchFunction :: ConfVar -> Either ConfVar Bool
realiseBranchFunction (PackageFlag n) | elem n (map flagName fs) = Right True
realiseBranchFunction _ = Right False
ms = simplifyCondBranch realiseBranchFunction (fmap taTarget b)
in
fmap snd ms
updateTargetAnnBranch
:: a
-> CondBranch ConfVar [Dependency] (TargetAnnotation a)
-> CondBranch ConfVar [Dependency] (TargetAnnotation a)
updateTargetAnnBranch a (CondBranch k t mt) =
let updateTargetAnnTree (CondNode ka c wbs) =
(CondNode (updateTargetAnnotation a ka) c wbs)
in CondBranch k (updateTargetAnnTree t) (updateTargetAnnTree <$> mt)
-- | A conditional target is a library, exe, benchmark etc., destructured
-- in a CondTree. Traversing method: we render the branches, pass a
-- relevant context, collect checks.
checkCondTarget
:: forall m a
. (Monad m, Eq a, Monoid a)
=> [PackageFlag] -- User flags.
-> (a -> CheckM m ()) -- Check function (a = target).
-> (UnqualComponentName -> a -> a)
-- Naming function (some targets
-- need to have their name
-- spoonfed to them.
-> (UnqualComponentName, CondTree ConfVar [Dependency] a)
-- Target name/condtree.
-> CheckM m ()
checkCondTarget fs cf nf (unqualName, ct) =
wTree $ annotateCondTree fs (initTargetAnnotation nf unqualName) ct
where
-- Walking the tree. Remember that CondTree is not a binary
-- tree but a /rose/tree.
wTree
:: CondTree ConfVar [Dependency] (TargetAnnotation a)
-> CheckM m ()
wTree (CondNode ta _ bs)
-- There are no branches ([] == True) *or* every branch
-- is “simple” (i.e. missing a 'condBranchIfFalse' part).
-- This is convenient but not necessarily correct in all
-- cases; a more precise way would be to check incompatibility
-- among simple branches conditions (or introduce a principled
-- `cond` construct in `.cabal` files.
| all isSimple bs = do
localCM (initCheckCtx ta) (cf $ taTarget ta)
mapM_ wBranch bs
-- If there are T/F conditions, there is no need to check
-- the intermediate 'TargetAnnotation' too.
| otherwise = do
mapM_ wBranch bs
isSimple
:: CondBranch ConfVar [Dependency] (TargetAnnotation a)
-> Bool
isSimple (CondBranch _ _ Nothing) = True
isSimple (CondBranch _ _ (Just _)) = False
wBranch
:: CondBranch ConfVar [Dependency] (TargetAnnotation a)
-> CheckM m ()
wBranch (CondBranch k t mf) = do
checkCondVars k
wTree t
maybe (return ()) wTree mf
-- | Condvar checking (misspelled OS in if conditions, etc).
checkCondVars :: Monad m => Condition ConfVar -> CheckM m ()
checkCondVars cond =
let (_, vs) = simplifyCondition cond (\v -> Left v)
in -- Using simplifyCondition is convenient and correct,
-- if checks become more complex we can always walk
-- 'Condition'.
mapM_ vcheck vs
where
vcheck :: Monad m => ConfVar -> CheckM m ()
vcheck (OS (OtherOS os)) =
tellP (PackageDistInexcusable $ UnknownOS [os])
vcheck (Arch (OtherArch arch)) =
tellP (PackageDistInexcusable $ UnknownArch [arch])
vcheck (Impl (OtherCompiler os) _) =
tellP (PackageDistInexcusable $ UnknownCompiler [os])
vcheck _ = return ()
-- Checking duplicated modules cannot unfortunately be done in the
-- “tree checking”. This is because of the monoidal instance in some targets,
-- where e.g. merged dependencies are `nub`’d, hence losing information for
-- this particular check.
checkDuplicateModules :: GenericPackageDescription -> [PackageCheck]
checkDuplicateModules pkg =
concatMap checkLib (maybe id (:) (condLibrary pkg) . map snd $ condSubLibraries pkg)
++ concatMap checkExe (map snd $ condExecutables pkg)
++ concatMap checkTest (map snd $ condTestSuites pkg)
++ concatMap checkBench (map snd $ condBenchmarks pkg)
where
-- the duplicate modules check is has not been thoroughly vetted for backpack
checkLib = checkDups "library" (\l -> explicitLibModules l ++ map moduleReexportName (reexportedModules l))
checkExe = checkDups "executable" exeModules
checkTest = checkDups "test suite" testModules
checkBench = checkDups "benchmark" benchmarkModules
checkDups :: String -> (a -> [ModuleName]) -> CondTree v c a -> [PackageCheck]
checkDups s getModules t =
let sumPair (x, x') (y, y') = (x + x' :: Int, y + y' :: Int)
mergePair (x, x') (y, y') = (x + x', max y y')
maxPair (x, x') (y, y') = (max x x', max y y')
libMap =
foldCondTree
Map.empty
(\(_, v) -> Map.fromListWith sumPair . map (\x -> (x, (1, 1))) $ getModules v)
(Map.unionWith mergePair) -- if a module may occur in nonexclusive branches count it twice strictly and once loosely.
(Map.unionWith maxPair) -- a module occurs the max of times it might appear in exclusive branches
t
dupLibsStrict = Map.keys $ Map.filter ((> 1) . fst) libMap
dupLibsLax = Map.keys $ Map.filter ((> 1) . snd) libMap
in if not (null dupLibsLax)
then
[ PackageBuildImpossible
(DuplicateModule s dupLibsLax)
]
else
if not (null dupLibsStrict)
then
[ PackageDistSuspicious
(PotentialDupModule s dupLibsStrict)
]
else []