Skip to content

Commit fe2110d

Browse files
grayjayhvr
authored andcommitted
Solver: Enforce dependencies on libraries (fixes #779).
This commit generalizes the fix for issue #4781 (e86f838) by tracking dependencies on components instead of dependencies on executables. That means that the solver always checks whether a package contains a library before using it to satisfy a build-depends dependency. If a version of a package doesn't contain a library, the solver can try other versions. Associating each dependency with a component also moves towards the design for component-based dependency solving described in issue #4087. (cherry picked from commit 6efb5e2)
1 parent 6c5c401 commit fe2110d

File tree

9 files changed

+195
-157
lines changed

9 files changed

+195
-157
lines changed

cabal-install/Distribution/Solver/Modular/Builder.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs
7272
-- the later addition will have better dependency information.
7373
go g o ((Stanza sn@(SN qpn _) t) : ngs) =
7474
go g (StanzaGoal sn t (flagGR qpn) : o) ngs
75-
go g o ((Simple (LDep dr (Dep _ qpn _)) c) : ngs)
75+
go g o ((Simple (LDep dr (Dep (PkgComponent qpn _) _)) c) : ngs)
7676
| qpn == qpn' =
7777
-- We currently only add a self-dependency to the graph if it is
7878
-- between a package and its setup script. The edge creates a cycle

cabal-install/Distribution/Solver/Modular/Dependency.hs

+23-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module Distribution.Solver.Modular.Dependency (
1616
, FlaggedDep(..)
1717
, LDep(..)
1818
, Dep(..)
19+
, PkgComponent(..)
20+
, ExposedComponent(..)
1921
, DependencyReason(..)
2022
, showDependencyReason
2123
, flattenFlaggedDeps
@@ -112,12 +114,22 @@ data LDep qpn = LDep (DependencyReason qpn) (Dep qpn)
112114
-- | A dependency (constraint) associates a package name with a constrained
113115
-- instance. It can also represent other types of dependencies, such as
114116
-- dependencies on language extensions.
115-
data Dep qpn = Dep (Maybe UnqualComponentName) qpn CI -- ^ dependency on a package (possibly for executable)
116-
| Ext Extension -- ^ dependency on a language extension
117-
| Lang Language -- ^ dependency on a language version
118-
| Pkg PkgconfigName VR -- ^ dependency on a pkg-config package
117+
data Dep qpn = Dep (PkgComponent qpn) CI -- ^ dependency on a package component
118+
| Ext Extension -- ^ dependency on a language extension
119+
| Lang Language -- ^ dependency on a language version
120+
| Pkg PkgconfigName VR -- ^ dependency on a pkg-config package
119121
deriving Functor
120122

123+
-- | An exposed component within a package. This type is used to represent
124+
-- build-depends and build-tool-depends dependencies.
125+
data PkgComponent qpn = PkgComponent qpn ExposedComponent
126+
deriving (Eq, Ord, Functor, Show)
127+
128+
-- | A component that can be depended upon by another package, i.e., a library
129+
-- or an executable.
130+
data ExposedComponent = ExposedLib | ExposedExe UnqualComponentName
131+
deriving (Eq, Ord, Show)
132+
121133
-- | The reason that a dependency is active. It identifies the package and any
122134
-- flag and stanza choices that introduced the dependency. It contains
123135
-- everything needed for creating ConflictSets or describing conflicts in solver
@@ -169,7 +181,7 @@ qualifyDeps QO{..} (Q pp@(PackagePath ns q) pn) = go
169181
-- Suppose package B has a setup dependency on package A.
170182
-- This will be recorded as something like
171183
--
172-
-- > LDep (DependencyReason "B") (Dep Nothing "A" (Constrained AnyVersion))
184+
-- > LDep (DependencyReason "B") (Dep (PkgComponent "A" ExposedLib) (Constrained AnyVersion))
173185
--
174186
-- Observe that when we qualify this dependency, we need to turn that
175187
-- @"A"@ into @"B-setup.A"@, but we should not apply that same qualifier
@@ -181,11 +193,12 @@ qualifyDeps QO{..} (Q pp@(PackagePath ns q) pn) = go
181193
goD (Ext ext) _ = Ext ext
182194
goD (Lang lang) _ = Lang lang
183195
goD (Pkg pkn vr) _ = Pkg pkn vr
184-
goD (Dep mExe dep ci) comp
185-
| isJust mExe = Dep mExe (Q (PackagePath ns (QualExe pn dep)) dep) ci
186-
| qBase dep = Dep mExe (Q (PackagePath ns (QualBase pn )) dep) ci
187-
| qSetup comp = Dep mExe (Q (PackagePath ns (QualSetup pn )) dep) ci
188-
| otherwise = Dep mExe (Q (PackagePath ns inheritedQ ) dep) ci
196+
goD (Dep dep@(PkgComponent qpn (ExposedExe _)) ci) _ =
197+
Dep (Q (PackagePath ns (QualExe pn qpn)) <$> dep) ci
198+
goD (Dep dep@(PkgComponent qpn ExposedLib) ci) comp
199+
| qBase qpn = Dep (Q (PackagePath ns (QualBase pn)) <$> dep) ci
200+
| qSetup comp = Dep (Q (PackagePath ns (QualSetup pn)) <$> dep) ci
201+
| otherwise = Dep (Q (PackagePath ns inheritedQ ) <$> dep) ci
189202

190203
-- If P has a setup dependency on Q, and Q has a regular dependency on R, then
191204
-- we say that the 'Setup' qualifier is inherited: P has an (indirect) setup

cabal-install/Distribution/Solver/Modular/Index.hs

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,19 @@ import Distribution.Solver.Modular.Dependency
1313
import Distribution.Solver.Modular.Flag
1414
import Distribution.Solver.Modular.Package
1515
import Distribution.Solver.Modular.Tree
16-
import Distribution.Types.UnqualComponentName
1716

1817
-- | An index contains information about package instances. This is a nested
1918
-- dictionary. Package names are mapped to instances, which in turn is mapped
2019
-- to info.
2120
type Index = Map PN (Map I PInfo)
2221

2322
-- | Info associated with a package instance.
24-
-- Currently, dependencies, executable names, flags and failure reasons.
23+
-- Currently, dependencies, component names, flags and failure reasons.
2524
-- Packages that have a failure reason recorded for them are disabled
2625
-- globally, for reasons external to the solver. We currently use this
2726
-- for shadowing which essentially is a GHC limitation, and for
2827
-- installed packages that are broken.
29-
data PInfo = PInfo (FlaggedDeps PN) [UnqualComponentName] FlagInfo (Maybe FailReason)
28+
data PInfo = PInfo (FlaggedDeps PN) [ExposedComponent] FlagInfo (Maybe FailReason)
3029

3130
mkIndex :: [(PN, I, PInfo)] -> Index
3231
mkIndex xs = M.map M.fromList (groupMap (L.map (\ (pn, i, pi) -> (pn, (i, pi))) xs))
@@ -40,9 +39,9 @@ defaultQualifyOptions idx = QO {
4039
| -- Find all versions of base ..
4140
Just is <- [M.lookup base idx]
4241
-- .. which are installed ..
43-
, (I _ver (Inst _), PInfo deps _exes _flagNfo _fr) <- M.toList is
42+
, (I _ver (Inst _), PInfo deps _comps _flagNfo _fr) <- M.toList is
4443
-- .. and flatten all their dependencies ..
45-
, (LDep _ (Dep _is_exe dep _ci), _comp) <- flattenFlaggedDeps deps
44+
, (LDep _ (Dep (PkgComponent dep _) _ci), _comp) <- flattenFlaggedDeps deps
4645
]
4746
, qoSetupIndependent = True
4847
}

cabal-install/Distribution/Solver/Modular/IndexConversion.hs

+16-16
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ convIPI' (ShadowPkgs sip) idx =
7272
where
7373

7474
-- shadowing is recorded in the package info
75-
shadow (pn, i, PInfo fdeps exes fds _) | sip = (pn, i, PInfo fdeps exes fds (Just Shadowed))
75+
shadow (pn, i, PInfo fdeps comps fds _)
76+
| sip = (pn, i, PInfo fdeps comps fds (Just Shadowed))
7677
shadow x = x
7778

7879
-- | Extract/recover the the package ID from an installed package info, and convert it to a solver's I.
@@ -87,7 +88,7 @@ convIP :: SI.InstalledPackageIndex -> InstalledPackageInfo -> (PN, I, PInfo)
8788
convIP idx ipi =
8889
case mapM (convIPId (DependencyReason pn M.empty S.empty) comp idx) (IPI.depends ipi) of
8990
Nothing -> (pn, i, PInfo [] [] M.empty (Just Broken))
90-
Just fds -> (pn, i, PInfo fds [] M.empty Nothing)
91+
Just fds -> (pn, i, PInfo fds [ExposedLib] M.empty Nothing)
9192
where
9293
(pn, i) = convId ipi
9394
-- 'sourceLibName' is unreliable, but for now we only really use this for
@@ -133,7 +134,7 @@ convIPId dr comp idx ipid =
133134
case SI.lookupUnitId idx ipid of
134135
Nothing -> Nothing
135136
Just ipi -> let (pn, i) = convId ipi
136-
in Just (D.Simple (LDep dr (Dep Nothing pn (Fixed i))) comp)
137+
in Just (D.Simple (LDep dr (Dep (PkgComponent pn ExposedLib) (Fixed i))) comp)
137138
-- NB: something we pick up from the
138139
-- InstalledPackageIndex is NEVER an executable
139140

@@ -223,7 +224,7 @@ convGPD os arch cinfo strfl solveExes pn
223224
fr | reqSpecVer > maxSpecVer = Just (UnsupportedSpecVer reqSpecVer)
224225
| otherwise = Nothing
225226
in
226-
PInfo flagged_deps (L.map fst exes) fds fr
227+
PInfo flagged_deps (L.map (ExposedExe . fst) exes ++ [ExposedLib | isJust mlib]) fds fr
227228

228229
-- | Create a flagged dependency tree from a list @fds@ of flagged
229230
-- dependencies, using @f@ to form the tree node (@f@ will be
@@ -289,7 +290,7 @@ convCondTree flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes@(Solv
289290
bi = getInfo info
290291

291292
data SimpleFlaggedDepKey qpn =
292-
SimpleFlaggedDepKey (Maybe UnqualComponentName) qpn Component
293+
SimpleFlaggedDepKey (PkgComponent qpn) Component
293294
deriving (Eq, Ord)
294295

295296
data SimpleFlaggedDepValue qpn = SimpleFlaggedDepValue (DependencyReason qpn) VR
@@ -320,9 +321,9 @@ mergeSimpleDeps deps = L.map (uncurry toFlaggedDep) (M.toList merged) ++ unmerge
320321
=> (Map (SimpleFlaggedDepKey qpn) (SimpleFlaggedDepValue qpn), FlaggedDeps qpn)
321322
-> FlaggedDep qpn
322323
-> (Map (SimpleFlaggedDepKey qpn) (SimpleFlaggedDepValue qpn), FlaggedDeps qpn)
323-
f (merged', unmerged') (D.Simple (LDep dr (Dep mExe qpn (Constrained vr))) comp) =
324+
f (merged', unmerged') (D.Simple (LDep dr (Dep dep (Constrained vr))) comp) =
324325
( M.insertWith mergeValues
325-
(SimpleFlaggedDepKey mExe qpn comp)
326+
(SimpleFlaggedDepKey dep comp)
326327
(SimpleFlaggedDepValue dr vr)
327328
merged'
328329
, unmerged')
@@ -337,8 +338,8 @@ mergeSimpleDeps deps = L.map (uncurry toFlaggedDep) (M.toList merged) ++ unmerge
337338
toFlaggedDep :: SimpleFlaggedDepKey qpn
338339
-> SimpleFlaggedDepValue qpn
339340
-> FlaggedDep qpn
340-
toFlaggedDep (SimpleFlaggedDepKey mExe qpn comp) (SimpleFlaggedDepValue dr vr) =
341-
D.Simple (LDep dr (Dep mExe qpn (Constrained vr))) comp
341+
toFlaggedDep (SimpleFlaggedDepKey dep comp) (SimpleFlaggedDepValue dr vr) =
342+
D.Simple (LDep dr (Dep dep (Constrained vr))) comp
342343

343344
-- | Branch interpreter. Mutually recursive with 'convCondTree'.
344345
--
@@ -463,11 +464,10 @@ convBranch flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes (CondBr
463464
-- Union the DependencyReasons, because the extracted dependency can be
464465
-- avoided by removing the dependency from either side of the
465466
-- conditional.
466-
[ D.Simple (LDep (unionDRs vs1 vs2) (Dep mExe1 pn1 (Constrained $ vr1 .||. vr2))) comp
467-
| D.Simple (LDep vs1 (Dep mExe1 pn1 (Constrained vr1))) _ <- ps
468-
, D.Simple (LDep vs2 (Dep mExe2 pn2 (Constrained vr2))) _ <- ps'
469-
, pn1 == pn2
470-
, mExe1 == mExe2
467+
[ D.Simple (LDep (unionDRs vs1 vs2) (Dep dep1 (Constrained $ vr1 .||. vr2))) comp
468+
| D.Simple (LDep vs1 (Dep dep1 (Constrained vr1))) _ <- ps
469+
, D.Simple (LDep vs2 (Dep dep2 (Constrained vr2))) _ <- ps'
470+
, dep1 == dep2
471471
]
472472

473473
-- | Merge DependencyReasons by unioning their variables.
@@ -477,11 +477,11 @@ unionDRs (DependencyReason pn' fs1 ss1) (DependencyReason _ fs2 ss2) =
477477

478478
-- | Convert a Cabal dependency on a library to a solver-specific dependency.
479479
convLibDep :: DependencyReason PN -> Dependency -> LDep PN
480-
convLibDep dr (Dependency pn vr) = LDep dr $ Dep Nothing pn (Constrained vr)
480+
convLibDep dr (Dependency pn vr) = LDep dr $ Dep (PkgComponent pn ExposedLib) (Constrained vr)
481481

482482
-- | Convert a Cabal dependency on an executable (build-tools) to a solver-specific dependency.
483483
convExeDep :: DependencyReason PN -> ExeDependency -> LDep PN
484-
convExeDep dr (ExeDependency pn exe vr) = LDep dr $ Dep (Just exe) pn (Constrained vr)
484+
convExeDep dr (ExeDependency pn exe vr) = LDep dr $ Dep (PkgComponent pn (ExposedExe exe)) (Constrained vr)
485485

486486
-- | Convert setup dependencies
487487
convSetupBuildInfo :: PN -> SetupBuildInfo -> FlaggedDeps PN

cabal-install/Distribution/Solver/Modular/Linking.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ linkDeps target = \deps -> do
245245

246246
go1 :: FlaggedDep QPN -> FlaggedDep QPN -> UpdateState ()
247247
go1 dep rdep = case (dep, rdep) of
248-
(Simple (LDep dr1 (Dep _ qpn _)) _, ~(Simple (LDep dr2 (Dep _ qpn' _)) _)) -> do
248+
(Simple (LDep dr1 (Dep (PkgComponent qpn _) _)) _, ~(Simple (LDep dr2 (Dep (PkgComponent qpn' _) _)) _)) -> do
249249
vs <- get
250250
let lg = M.findWithDefault (lgSingleton qpn Nothing) qpn $ vsLinks vs
251251
lg' = M.findWithDefault (lgSingleton qpn' Nothing) qpn' $ vsLinks vs

cabal-install/Distribution/Solver/Modular/Message.hs

+12-8
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ showFR _ (UnsupportedLanguage lang) = " (conflict: requires " ++ display l
115115
showFR _ (MissingPkgconfigPackage pn vr) = " (conflict: pkg-config package " ++ display pn ++ display vr ++ ", not found in the pkg-config database)"
116116
showFR _ (NewPackageDoesNotMatchExistingConstraint d) = " (conflict: " ++ showConflictingDep d ++ ")"
117117
showFR _ (ConflictingConstraints d1 d2) = " (conflict: " ++ L.intercalate ", " (L.map showConflictingDep [d1, d2]) ++ ")"
118-
showFR _ (NewPackageIsMissingRequiredExe exe dr) = " (does not contain executable " ++ unUnqualComponentName exe ++ ", which is required by " ++ showDependencyReason dr ++ ")"
119-
showFR _ (PackageRequiresMissingExe qpn exe) = " (requires executable " ++ unUnqualComponentName exe ++ " from " ++ showQPN qpn ++ ", but the executable does not exist)"
118+
showFR _ (NewPackageIsMissingRequiredComponent comp dr) = " (does not contain " ++ showExposedComponent comp ++ ", which is required by " ++ showDependencyReason dr ++ ")"
119+
showFR _ (PackageRequiresMissingComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component does not exist)"
120120
showFR _ CannotInstall = " (only already installed instances can be used)"
121121
showFR _ CannotReinstall = " (avoiding to reinstall a package with same version but new dependencies)"
122122
showFR _ Shadowed = " (shadowed by another installed package with same version)"
@@ -138,17 +138,21 @@ showFR _ (MalformedFlagChoice qfn) = " (INTERNAL ERROR: MALFORMED FLAG CH
138138
showFR _ (MalformedStanzaChoice qsn) = " (INTERNAL ERROR: MALFORMED STANZA CHOICE: " ++ showQSN qsn ++ ")"
139139
showFR _ EmptyGoalChoice = " (INTERNAL ERROR: EMPTY GOAL CHOICE)"
140140

141+
showExposedComponent :: ExposedComponent -> String
142+
showExposedComponent ExposedLib = "library"
143+
showExposedComponent (ExposedExe name) = "executable '" ++ unUnqualComponentName name ++ "'"
144+
141145
constraintSource :: ConstraintSource -> String
142146
constraintSource src = "constraint from " ++ showConstraintSource src
143147

144148
showConflictingDep :: ConflictingDep -> String
145-
showConflictingDep (ConflictingDep dr mExe qpn ci) =
149+
showConflictingDep (ConflictingDep dr (PkgComponent qpn comp) ci) =
146150
let DependencyReason qpn' _ _ = dr
147-
exeStr = case mExe of
148-
Just exe -> " (exe " ++ unUnqualComponentName exe ++ ")"
149-
Nothing -> ""
151+
componentStr = case comp of
152+
ExposedExe exe -> " (exe " ++ unUnqualComponentName exe ++ ")"
153+
ExposedLib -> ""
150154
in case ci of
151155
Fixed i -> (if qpn /= qpn' then showDependencyReason dr ++ " => " else "") ++
152-
showQPN qpn ++ exeStr ++ "==" ++ showI i
156+
showQPN qpn ++ componentStr ++ "==" ++ showI i
153157
Constrained vr -> showDependencyReason dr ++ " => " ++ showQPN qpn ++
154-
exeStr ++ showVR vr
158+
componentStr ++ showVR vr

cabal-install/Distribution/Solver/Modular/Tree.hs

+3-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import qualified Distribution.Solver.Modular.WeightedPSQ as W
3131
import Distribution.Solver.Types.ConstraintSource
3232
import Distribution.Solver.Types.Flag
3333
import Distribution.Solver.Types.PackagePath
34-
import Distribution.Types.UnqualComponentName
3534
import Language.Haskell.Extension (Extension, Language)
3635

3736
type Weight = Double
@@ -101,8 +100,8 @@ data FailReason = UnsupportedExtension Extension
101100
| MissingPkgconfigPackage PkgconfigName VR
102101
| NewPackageDoesNotMatchExistingConstraint ConflictingDep
103102
| ConflictingConstraints ConflictingDep ConflictingDep
104-
| NewPackageIsMissingRequiredExe UnqualComponentName (DependencyReason QPN)
105-
| PackageRequiresMissingExe QPN UnqualComponentName
103+
| NewPackageIsMissingRequiredComponent ExposedComponent (DependencyReason QPN)
104+
| PackageRequiresMissingComponent QPN ExposedComponent
106105
| CannotInstall
107106
| CannotReinstall
108107
| Shadowed
@@ -123,7 +122,7 @@ data FailReason = UnsupportedExtension Extension
123122
deriving (Eq, Show)
124123

125124
-- | Information about a dependency involved in a conflict, for error messages.
126-
data ConflictingDep = ConflictingDep (DependencyReason QPN) (Maybe UnqualComponentName) QPN CI
125+
data ConflictingDep = ConflictingDep (DependencyReason QPN) (PkgComponent QPN) CI
127126
deriving (Eq, Show)
128127

129128
-- | Functor for the tree type. 'a' is the type of nodes' children. 'd' and 'c'

0 commit comments

Comments
 (0)