cabal-install-3.0.0.0: Distribution/Solver/Modular/IndexConversion.hs
module Distribution.Solver.Modular.IndexConversion
( convPIs
) where
import Data.List as L
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid as Mon
import Data.Set as S
import Distribution.Compiler
import Distribution.InstalledPackageInfo as IPI
import Distribution.Package -- from Cabal
import Distribution.Simple.BuildToolDepends -- from Cabal
import Distribution.Simple.Utils (cabalVersion) -- from Cabal
import Distribution.Types.ExeDependency -- from Cabal
import Distribution.Types.PkgconfigDependency -- from Cabal
import Distribution.Types.ComponentName -- from Cabal
import Distribution.Types.UnqualComponentName -- from Cabal
import Distribution.Types.CondTree -- from Cabal
import Distribution.Types.MungedPackageId -- from Cabal
import Distribution.Types.MungedPackageName -- from Cabal
import Distribution.PackageDescription as PD -- from Cabal
import Distribution.PackageDescription.Configuration as PDC
import qualified Distribution.Simple.PackageIndex as SI
import Distribution.System
import Distribution.Types.ForeignLib
import Distribution.Solver.Types.ComponentDeps
( Component(..), componentNameToComponent )
import Distribution.Solver.Types.Flag
import Distribution.Solver.Types.LabeledPackageConstraint
import Distribution.Solver.Types.OptionalStanza
import Distribution.Solver.Types.PackageConstraint
import qualified Distribution.Solver.Types.PackageIndex as CI
import Distribution.Solver.Types.Settings
import Distribution.Solver.Types.SourcePackage
import Distribution.Solver.Modular.Dependency as D
import Distribution.Solver.Modular.Flag as F
import Distribution.Solver.Modular.Index
import Distribution.Solver.Modular.Package
import Distribution.Solver.Modular.Tree
import Distribution.Solver.Modular.Version
-- | Convert both the installed package index and the source package
-- index into one uniform solver index.
--
-- We use 'allPackagesBySourcePackageId' for the installed package index
-- because that returns us several instances of the same package and version
-- in order of preference. This allows us in principle to \"shadow\"
-- packages if there are several installed packages of the same version.
-- There are currently some shortcomings in both GHC and Cabal in
-- resolving these situations. However, the right thing to do is to
-- fix the problem there, so for now, shadowing is only activated if
-- explicitly requested.
convPIs :: OS -> Arch -> CompilerInfo -> Map PN [LabeledPackageConstraint]
-> ShadowPkgs -> StrongFlags -> SolveExecutables
-> SI.InstalledPackageIndex -> CI.PackageIndex (SourcePackage loc)
-> Index
convPIs os arch comp constraints sip strfl solveExes iidx sidx =
mkIndex $
convIPI' sip iidx ++ convSPI' os arch comp constraints strfl solveExes sidx
-- | Convert a Cabal installed package index to the simpler,
-- more uniform index format of the solver.
convIPI' :: ShadowPkgs -> SI.InstalledPackageIndex -> [(PN, I, PInfo)]
convIPI' (ShadowPkgs sip) idx =
-- apply shadowing whenever there are multiple installed packages with
-- the same version
[ maybeShadow (convIP idx pkg)
-- IMPORTANT to get internal libraries. See
-- Note [Index conversion with internal libraries]
| (_, pkgs) <- SI.allPackagesBySourcePackageIdAndLibName idx
, (maybeShadow, pkg) <- zip (id : repeat shadow) pkgs ]
where
-- shadowing is recorded in the package info
shadow (pn, i, PInfo fdeps comps fds _)
| sip = (pn, i, PInfo fdeps comps fds (Just Shadowed))
shadow x = x
-- | Extract/recover the the package ID from an installed package info, and convert it to a solver's I.
convId :: InstalledPackageInfo -> (PN, I)
convId ipi = (pn, I ver $ Inst $ IPI.installedUnitId ipi)
where MungedPackageId mpn ver = mungedId ipi
-- HACK. See Note [Index conversion with internal libraries]
pn = encodeCompatPackageName mpn
-- | Convert a single installed package into the solver-specific format.
convIP :: SI.InstalledPackageIndex -> InstalledPackageInfo -> (PN, I, PInfo)
convIP idx ipi =
case mapM (convIPId (DependencyReason pn M.empty S.empty) comp idx) (IPI.depends ipi) of
Nothing -> (pn, i, PInfo [] M.empty M.empty (Just Broken))
Just fds -> ( pn
, i
, PInfo fds (M.singleton ExposedLib (IsBuildable True)) M.empty Nothing)
where
(pn, i) = convId ipi
-- 'sourceLibName' is unreliable, but for now we only really use this for
-- primary libs anyways
comp = componentNameToComponent $ CLibName $ sourceLibName ipi
-- TODO: Installed packages should also store their encapsulations!
-- Note [Index conversion with internal libraries]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Something very interesting happens when we have internal libraries
-- in our index. In this case, we maybe have p-0.1, which itself
-- depends on the internal library p-internal ALSO from p-0.1.
-- Here's the danger:
--
-- - If we treat both of these packages as having PN "p",
-- then the solver will try to pick one or the other,
-- but never both.
--
-- - If we drop the internal packages, now p-0.1 has a
-- dangling dependency on an "installed" package we know
-- nothing about. Oops.
--
-- An expedient hack is to put p-internal into cabal-install's
-- index as a MUNGED package name, so that it doesn't conflict
-- with anyone else (except other instances of itself). But
-- yet, we ought NOT to say that PNs in the solver are munged
-- package names, because they're not; for source packages,
-- we really will never see munged package names.
--
-- The tension here is that the installed package index is actually
-- per library, but the solver is per package. We need to smooth
-- it over, and munging the package names is a pretty good way to
-- do it.
-- | Convert dependencies specified by an installed package id into
-- flagged dependencies of the solver.
--
-- May return Nothing if the package can't be found in the index. That
-- indicates that the original package having this dependency is broken
-- and should be ignored.
convIPId :: DependencyReason PN -> Component -> SI.InstalledPackageIndex -> UnitId -> Maybe (FlaggedDep PN)
convIPId dr comp idx ipid =
case SI.lookupUnitId idx ipid of
Nothing -> Nothing
Just ipi -> let (pn, i) = convId ipi
in Just (D.Simple (LDep dr (Dep (PkgComponent pn ExposedLib) (Fixed i))) comp)
-- NB: something we pick up from the
-- InstalledPackageIndex is NEVER an executable
-- | Convert a cabal-install source package index to the simpler,
-- more uniform index format of the solver.
convSPI' :: OS -> Arch -> CompilerInfo -> Map PN [LabeledPackageConstraint]
-> StrongFlags -> SolveExecutables
-> CI.PackageIndex (SourcePackage loc) -> [(PN, I, PInfo)]
convSPI' os arch cinfo constraints strfl solveExes =
L.map (convSP os arch cinfo constraints strfl solveExes) . CI.allPackages
-- | Convert a single source package into the solver-specific format.
convSP :: OS -> Arch -> CompilerInfo -> Map PN [LabeledPackageConstraint]
-> StrongFlags -> SolveExecutables -> SourcePackage loc -> (PN, I, PInfo)
convSP os arch cinfo constraints strfl solveExes (SourcePackage (PackageIdentifier pn pv) gpd _ _pl) =
let i = I pv InRepo
pkgConstraints = fromMaybe [] $ M.lookup pn constraints
in (pn, i, convGPD os arch cinfo pkgConstraints strfl solveExes pn gpd)
-- We do not use 'flattenPackageDescription' or 'finalizePD'
-- from 'Distribution.PackageDescription.Configuration' here, because we
-- want to keep the condition tree, but simplify much of the test.
-- | Convert a generic package description to a solver-specific 'PInfo'.
convGPD :: OS -> Arch -> CompilerInfo -> [LabeledPackageConstraint]
-> StrongFlags -> SolveExecutables -> PN -> GenericPackageDescription
-> PInfo
convGPD os arch cinfo constraints strfl solveExes pn
(GenericPackageDescription pkg flags mlib sub_libs flibs exes tests benchs) =
let
fds = flagInfo strfl flags
-- | We have to be careful to filter out dependencies on
-- internal libraries, since they don't refer to real packages
-- and thus cannot actually be solved over. We'll do this
-- by creating a set of package names which are "internal"
-- and dropping them as we convert.
ipns = S.fromList $ [ unqualComponentNameToPackageName nm
| (nm, _) <- sub_libs ]
conv :: Mon.Monoid a => Component -> (a -> BuildInfo) -> DependencyReason PN ->
CondTree ConfVar [Dependency] a -> FlaggedDeps PN
conv comp getInfo dr =
convCondTree M.empty dr pkg os arch cinfo pn fds comp getInfo ipns solveExes .
PDC.addBuildableCondition getInfo
initDR = DependencyReason pn M.empty S.empty
flagged_deps
= concatMap (\ds -> conv ComponentLib libBuildInfo initDR ds) (maybeToList mlib)
++ concatMap (\(nm, ds) -> conv (ComponentSubLib nm) libBuildInfo initDR ds) sub_libs
++ concatMap (\(nm, ds) -> conv (ComponentFLib nm) foreignLibBuildInfo initDR ds) flibs
++ concatMap (\(nm, ds) -> conv (ComponentExe nm) buildInfo initDR ds) exes
++ prefix (Stanza (SN pn TestStanzas))
(L.map (\(nm, ds) -> conv (ComponentTest nm) testBuildInfo (addStanza TestStanzas initDR) ds)
tests)
++ prefix (Stanza (SN pn BenchStanzas))
(L.map (\(nm, ds) -> conv (ComponentBench nm) benchmarkBuildInfo (addStanza BenchStanzas initDR) ds)
benchs)
++ maybe [] (convSetupBuildInfo pn) (setupBuildInfo pkg)
addStanza :: Stanza -> DependencyReason pn -> DependencyReason pn
addStanza s (DependencyReason pn' fs ss) = DependencyReason pn' fs (S.insert s ss)
-- | We infer the maximally supported spec-version from @lib:Cabal@'s version
--
-- As we cannot predict the future, we can only properly support
-- spec-versions predating (and including) the @lib:Cabal@ version
-- used by @cabal-install@.
--
-- This relies on 'cabalVersion' having always at least 3 components to avoid
-- comparisons like @2.0.0 > 2.0@ which would result in confusing results.
--
-- NOTE: Before we can switch to a /normalised/ spec-version
-- comparison (e.g. by truncating to 3 components, and removing
-- trailing zeroes) we'd have to make sure all other places where
-- the spec-version is compared against a bound do it
-- consistently.
maxSpecVer = cabalVersion
-- | Required/declared spec-version of the package
--
-- We don't truncate patch-levels, as specifying a patch-level
-- spec-version is discouraged and not supported anymore starting
-- with spec-version 2.2.
reqSpecVer = specVersion pkg
-- | A too-new specVersion is turned into a global 'FailReason'
-- which prevents the solver from selecting this release (and if
-- forced to, emit a meaningful solver error message).
fr | reqSpecVer > maxSpecVer = Just (UnsupportedSpecVer reqSpecVer)
| otherwise = Nothing
components :: Map ExposedComponent IsBuildable
components = M.fromList $ libComps ++ exeComps
where
libComps = [ (ExposedLib, IsBuildable $ isBuildable libBuildInfo lib)
| lib <- maybeToList mlib ]
exeComps = [ (ExposedExe name, IsBuildable $ isBuildable buildInfo exe)
| (name, exe) <- exes ]
isBuildable = isBuildableComponent os arch cinfo constraints
in PInfo flagged_deps components fds fr
-- | Returns true if the component is buildable in the given environment.
-- This function can give false-positives. For example, it only considers flags
-- that are set by unqualified flag constraints, and it doesn't check whether
-- the intra-package dependencies of a component are buildable. It is also
-- possible for the solver to later assign a value to an automatic flag that
-- makes the component unbuildable.
isBuildableComponent :: OS
-> Arch
-> CompilerInfo
-> [LabeledPackageConstraint]
-> (a -> BuildInfo)
-> CondTree ConfVar [Dependency] a
-> Bool
isBuildableComponent os arch cinfo constraints getInfo tree =
case simplifyCondition $ extractCondition (buildable . getInfo) tree of
Lit False -> False
_ -> True
where
flagAssignment :: [(FlagName, Bool)]
flagAssignment =
mconcat [ unFlagAssignment fa
| PackageConstraint (ScopeAnyQualifier _) (PackagePropertyFlags fa)
<- L.map unlabelPackageConstraint constraints]
-- Simplify the condition, using the current environment. Most of this
-- function was copied from convBranch and
-- Distribution.Types.Condition.simplifyCondition.
simplifyCondition :: Condition ConfVar -> Condition ConfVar
simplifyCondition (Var (OS os')) = Lit (os == os')
simplifyCondition (Var (Arch arch')) = Lit (arch == arch')
simplifyCondition (Var (Impl cf cvr))
| matchImpl (compilerInfoId cinfo) ||
-- fixme: Nothing should be treated as unknown, rather than empty
-- list. This code should eventually be changed to either
-- support partial resolution of compiler flags or to
-- complain about incompletely configured compilers.
any matchImpl (fromMaybe [] $ compilerInfoCompat cinfo) = Lit True
| otherwise = Lit False
where
matchImpl (CompilerId cf' cv) = cf == cf' && checkVR cvr cv
simplifyCondition (Var (Flag f))
| Just b <- L.lookup f flagAssignment = Lit b
simplifyCondition (Var v) = Var v
simplifyCondition (Lit b) = Lit b
simplifyCondition (CNot c) =
case simplifyCondition c of
Lit True -> Lit False
Lit False -> Lit True
c' -> CNot c'
simplifyCondition (COr c d) =
case (simplifyCondition c, simplifyCondition d) of
(Lit False, d') -> d'
(Lit True, _) -> Lit True
(c', Lit False) -> c'
(_, Lit True) -> Lit True
(c', d') -> COr c' d'
simplifyCondition (CAnd c d) =
case (simplifyCondition c, simplifyCondition d) of
(Lit False, _) -> Lit False
(Lit True, d') -> d'
(_, Lit False) -> Lit False
(c', Lit True) -> c'
(c', d') -> CAnd c' d'
-- | Create a flagged dependency tree from a list @fds@ of flagged
-- dependencies, using @f@ to form the tree node (@f@ will be
-- something like @Stanza sn@).
prefix :: (FlaggedDeps qpn -> FlaggedDep qpn)
-> [FlaggedDeps qpn] -> FlaggedDeps qpn
prefix _ [] = []
prefix f fds = [f (concat fds)]
-- | Convert flag information. Automatic flags are now considered weak
-- unless strong flags have been selected explicitly.
flagInfo :: StrongFlags -> [PD.Flag] -> FlagInfo
flagInfo (StrongFlags strfl) =
M.fromList . L.map (\ (MkFlag fn _ b m) -> (fn, FInfo b (flagType m) (weak m)))
where
weak m = WeakOrTrivial $ not (strfl || m)
flagType m = if m then Manual else Automatic
-- | Internal package names, which should not be interpreted as true
-- dependencies.
type IPNs = Set PN
-- | Convenience function to delete a 'Dependency' if it's
-- for a 'PN' that isn't actually real.
filterIPNs :: IPNs -> Dependency -> Maybe Dependency
filterIPNs ipns d@(Dependency pn _ _)
| S.notMember pn ipns = Just d
| otherwise = Nothing
-- | Convert condition trees to flagged dependencies. Mutually
-- recursive with 'convBranch'. See 'convBranch' for an explanation
-- of all arguments preceeding the input 'CondTree'.
convCondTree :: Map FlagName Bool -> DependencyReason PN -> PackageDescription -> OS -> Arch -> CompilerInfo -> PN -> FlagInfo ->
Component ->
(a -> BuildInfo) ->
IPNs ->
SolveExecutables ->
CondTree ConfVar [Dependency] a -> FlaggedDeps PN
convCondTree flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes@(SolveExecutables solveExes') (CondNode info ds branches) =
-- Merge all library and build-tool dependencies at every level in
-- the tree of flagged dependencies. Otherwise 'extractCommon'
-- could create duplicate dependencies, and the number of
-- duplicates could grow exponentially from the leaves to the root
-- of the tree.
mergeSimpleDeps $
L.map (\d -> D.Simple (convLibDep dr d) comp)
(mapMaybe (filterIPNs ipns) ds) -- unconditional package dependencies
++ L.map (\e -> D.Simple (LDep dr (Ext e)) comp) (PD.allExtensions bi) -- unconditional extension dependencies
++ L.map (\l -> D.Simple (LDep dr (Lang l)) comp) (PD.allLanguages bi) -- unconditional language dependencies
++ L.map (\(PkgconfigDependency pkn vr) -> D.Simple (LDep dr (Pkg pkn vr)) comp) (PD.pkgconfigDepends bi) -- unconditional pkg-config dependencies
++ concatMap (convBranch flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes) branches
-- build-tools dependencies
-- NB: Only include these dependencies if SolveExecutables
-- is True. It might be false in the legacy solver
-- codepath, in which case there won't be any record of
-- an executable we need.
++ [ D.Simple (convExeDep dr exeDep) comp
| solveExes'
, exeDep <- getAllToolDependencies pkg bi
, not $ isInternal pkg exeDep
]
where
bi = getInfo info
data SimpleFlaggedDepKey qpn =
SimpleFlaggedDepKey (PkgComponent qpn) Component
deriving (Eq, Ord)
data SimpleFlaggedDepValue qpn = SimpleFlaggedDepValue (DependencyReason qpn) VR
-- | Merge 'Simple' dependencies that apply to the same library or build-tool.
-- This function should be able to merge any two dependencies that can be merged
-- by extractCommon, in order to prevent the exponential growth of dependencies.
--
-- Note that this function can merge dependencies that have different
-- DependencyReasons, which can make the DependencyReasons less precise. This
-- loss of precision only affects performance and log messages, not correctness.
-- However, when 'mergeSimpleDeps' is only called on dependencies at a single
-- location in the dependency tree, the only difference between
-- DependencyReasons should be flags that have value FlagBoth. Adding extra
-- flags with value FlagBoth should not affect performance, since they are not
-- added to the conflict set. The only downside is the possibility of the log
-- incorrectly saying that the flag contributed to excluding a specific version
-- of a dependency. For example, if +/-flagA introduces pkg >=2 and +/-flagB
-- introduces pkg <5, the merged dependency would mean that
-- +/-flagA and +/-flagB introduce pkg >=2 && <5, which would incorrectly imply
-- that +/-flagA excludes pkg-6.
mergeSimpleDeps :: Ord qpn => FlaggedDeps qpn -> FlaggedDeps qpn
mergeSimpleDeps deps = L.map (uncurry toFlaggedDep) (M.toList merged) ++ unmerged
where
(merged, unmerged) = L.foldl' f (M.empty, []) deps
where
f :: Ord qpn
=> (Map (SimpleFlaggedDepKey qpn) (SimpleFlaggedDepValue qpn), FlaggedDeps qpn)
-> FlaggedDep qpn
-> (Map (SimpleFlaggedDepKey qpn) (SimpleFlaggedDepValue qpn), FlaggedDeps qpn)
f (merged', unmerged') (D.Simple (LDep dr (Dep dep (Constrained vr))) comp) =
( M.insertWith mergeValues
(SimpleFlaggedDepKey dep comp)
(SimpleFlaggedDepValue dr vr)
merged'
, unmerged')
f (merged', unmerged') unmergeableDep = (merged', unmergeableDep : unmerged')
mergeValues :: SimpleFlaggedDepValue qpn
-> SimpleFlaggedDepValue qpn
-> SimpleFlaggedDepValue qpn
mergeValues (SimpleFlaggedDepValue dr1 vr1) (SimpleFlaggedDepValue dr2 vr2) =
SimpleFlaggedDepValue (unionDRs dr1 dr2) (vr1 .&&. vr2)
toFlaggedDep :: SimpleFlaggedDepKey qpn
-> SimpleFlaggedDepValue qpn
-> FlaggedDep qpn
toFlaggedDep (SimpleFlaggedDepKey dep comp) (SimpleFlaggedDepValue dr vr) =
D.Simple (LDep dr (Dep dep (Constrained vr))) comp
-- | Branch interpreter. Mutually recursive with 'convCondTree'.
--
-- Here, we try to simplify one of Cabal's condition tree branches into the
-- solver's flagged dependency format, which is weaker. Condition trees can
-- contain complex logical expression composed from flag choices and special
-- flags (such as architecture, or compiler flavour). We try to evaluate the
-- special flags and subsequently simplify to a tree that only depends on
-- simple flag choices.
--
-- This function takes a number of arguments:
--
-- 1. A map of flag values that have already been chosen. It allows
-- convBranch to avoid creating nested FlaggedDeps that are
-- controlled by the same flag and avoid creating DependencyReasons with
-- conflicting values for the same flag.
--
-- 2. The DependencyReason calculated at this point in the tree of
-- conditionals. The flag values in the DependencyReason are similar to
-- the values in the map above, except for the use of FlagBoth.
--
-- 3. Some pre dependency-solving known information ('OS', 'Arch',
-- 'CompilerInfo') for @os()@, @arch()@ and @impl()@ variables,
--
-- 4. The package name @'PN'@ which this condition tree
-- came from, so that we can correctly associate @flag()@
-- variables with the correct package name qualifier,
--
-- 5. The flag defaults 'FlagInfo' so that we can populate
-- 'Flagged' dependencies with 'FInfo',
--
-- 6. The name of the component 'Component' so we can record where
-- the fine-grained information about where the component came
-- from (see 'convCondTree'), and
--
-- 7. A selector to extract the 'BuildInfo' from the leaves of
-- the 'CondTree' (which actually contains the needed
-- dependency information.)
--
-- 8. The set of package names which should be considered internal
-- dependencies, and thus not handled as dependencies.
convBranch :: Map FlagName Bool
-> DependencyReason PN
-> PackageDescription
-> OS
-> Arch
-> CompilerInfo
-> PN
-> FlagInfo
-> Component
-> (a -> BuildInfo)
-> IPNs
-> SolveExecutables
-> CondBranch ConfVar [Dependency] a
-> FlaggedDeps PN
convBranch flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes (CondBranch c' t' mf') =
go c'
(\flags' dr' -> convCondTree flags' dr' pkg os arch cinfo pn fds comp getInfo ipns solveExes t')
(\flags' dr' -> maybe [] (convCondTree flags' dr' pkg os arch cinfo pn fds comp getInfo ipns solveExes) mf')
flags dr
where
go :: Condition ConfVar
-> (Map FlagName Bool -> DependencyReason PN -> FlaggedDeps PN)
-> (Map FlagName Bool -> DependencyReason PN -> FlaggedDeps PN)
-> Map FlagName Bool -> DependencyReason PN -> FlaggedDeps PN
go (Lit True) t _ = t
go (Lit False) _ f = f
go (CNot c) t f = go c f t
go (CAnd c d) t f = go c (go d t f) f
go (COr c d) t f = go c t (go d t f)
go (Var (Flag fn)) t f = \flags' ->
case M.lookup fn flags' of
Just True -> t flags'
Just False -> f flags'
Nothing -> \dr' ->
-- Add each flag to the DependencyReason for all dependencies below,
-- including any extracted dependencies. Extracted dependencies are
-- introduced by both flag values (FlagBoth). Note that we don't
-- actually need to add the flag to the extracted dependencies for
-- correct backjumping; the information only improves log messages
-- by giving the user the full reason for each dependency.
let addFlagValue v = addFlagToDependencyReason fn v dr'
addFlag v = M.insert fn v flags'
in extractCommon (t (addFlag True) (addFlagValue FlagBoth))
(f (addFlag False) (addFlagValue FlagBoth))
++ [ Flagged (FN pn fn) (fds M.! fn) (t (addFlag True) (addFlagValue FlagTrue))
(f (addFlag False) (addFlagValue FlagFalse)) ]
go (Var (OS os')) t f
| os == os' = t
| otherwise = f
go (Var (Arch arch')) t f
| arch == arch' = t
| otherwise = f
go (Var (Impl cf cvr)) t f
| matchImpl (compilerInfoId cinfo) ||
-- fixme: Nothing should be treated as unknown, rather than empty
-- list. This code should eventually be changed to either
-- support partial resolution of compiler flags or to
-- complain about incompletely configured compilers.
any matchImpl (fromMaybe [] $ compilerInfoCompat cinfo) = t
| otherwise = f
where
matchImpl (CompilerId cf' cv) = cf == cf' && checkVR cvr cv
addFlagToDependencyReason :: FlagName -> FlagValue -> DependencyReason pn -> DependencyReason pn
addFlagToDependencyReason fn v (DependencyReason pn' fs ss) =
DependencyReason pn' (M.insert fn v fs) ss
-- If both branches contain the same package as a simple dep, we lift it to
-- the next higher-level, but with the union of version ranges. This
-- heuristic together with deferring flag choices will then usually first
-- resolve this package, and try an already installed version before imposing
-- a default flag choice that might not be what we want.
--
-- Note that we make assumptions here on the form of the dependencies that
-- can occur at this point. In particular, no occurrences of Fixed, as all
-- dependencies below this point have been generated using 'convLibDep'.
--
-- WARNING: This is quadratic!
extractCommon :: Eq pn => FlaggedDeps pn -> FlaggedDeps pn -> FlaggedDeps pn
extractCommon ps ps' =
-- Union the DependencyReasons, because the extracted dependency can be
-- avoided by removing the dependency from either side of the
-- conditional.
[ D.Simple (LDep (unionDRs vs1 vs2) (Dep dep1 (Constrained $ vr1 .||. vr2))) comp
| D.Simple (LDep vs1 (Dep dep1 (Constrained vr1))) _ <- ps
, D.Simple (LDep vs2 (Dep dep2 (Constrained vr2))) _ <- ps'
, dep1 == dep2
]
-- | Merge DependencyReasons by unioning their variables.
unionDRs :: DependencyReason pn -> DependencyReason pn -> DependencyReason pn
unionDRs (DependencyReason pn' fs1 ss1) (DependencyReason _ fs2 ss2) =
DependencyReason pn' (M.union fs1 fs2) (S.union ss1 ss2)
-- | Convert a Cabal dependency on a library to a solver-specific dependency.
convLibDep :: DependencyReason PN -> Dependency -> LDep PN
convLibDep dr (Dependency pn vr _) = LDep dr $ Dep (PkgComponent pn ExposedLib) (Constrained vr)
-- | Convert a Cabal dependency on an executable (build-tools) to a solver-specific dependency.
convExeDep :: DependencyReason PN -> ExeDependency -> LDep PN
convExeDep dr (ExeDependency pn exe vr) = LDep dr $ Dep (PkgComponent pn (ExposedExe exe)) (Constrained vr)
-- | Convert setup dependencies
convSetupBuildInfo :: PN -> SetupBuildInfo -> FlaggedDeps PN
convSetupBuildInfo pn nfo =
L.map (\d -> D.Simple (convLibDep (DependencyReason pn M.empty S.empty) d) ComponentSetup)
(PD.setupDepends nfo)