Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solver: Support dependencies on sub-libraries (issue #6039). #6047

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cabal/Distribution/Types/CondTree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ traverseCondBranchC f (CondBranch cnd t me) = CondBranch cnd

-- | Extract the condition matched by the given predicate from a cond tree.
--
-- We use this mainly for extracting buildable conditions (see the Note above),
-- but the function is in fact more general.
-- We use this mainly for extracting buildable conditions (see the Note in
-- Distribution.PackageDescription.Configuration), but the function is in fact
-- more general.
extractCondition :: Eq v => (a -> Bool) -> CondTree v c a -> Condition v
extractCondition p = go
where
Expand Down
9 changes: 6 additions & 3 deletions cabal-install/Distribution/Solver/Modular/Dependency.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import qualified Distribution.Solver.Modular.ConflictSet as CS

import Distribution.Solver.Types.ComponentDeps (Component(..))
import Distribution.Solver.Types.PackagePath
import Distribution.Types.LibraryName
import Distribution.Types.PkgconfigVersionRange
import Distribution.Types.UnqualComponentName

Expand Down Expand Up @@ -131,7 +132,9 @@ data PkgComponent qpn = PkgComponent qpn ExposedComponent

-- | A component that can be depended upon by another package, i.e., a library
-- or an executable.
data ExposedComponent = ExposedLib | ExposedExe UnqualComponentName
data ExposedComponent =
ExposedLib LibraryName
| ExposedExe UnqualComponentName
deriving (Eq, Ord, Show)

-- | The reason that a dependency is active. It identifies the package and any
Expand Down Expand Up @@ -185,7 +188,7 @@ qualifyDeps QO{..} (Q pp@(PackagePath ns q) pn) = go
-- Suppose package B has a setup dependency on package A.
-- This will be recorded as something like
--
-- > LDep (DependencyReason "B") (Dep (PkgComponent "A" ExposedLib) (Constrained AnyVersion))
-- > LDep (DependencyReason "B") (Dep (PkgComponent "A" (ExposedLib LMainLibName)) (Constrained AnyVersion))
--
-- Observe that when we qualify this dependency, we need to turn that
-- @"A"@ into @"B-setup.A"@, but we should not apply that same qualifier
Expand All @@ -199,7 +202,7 @@ qualifyDeps QO{..} (Q pp@(PackagePath ns q) pn) = go
goD (Pkg pkn vr) _ = Pkg pkn vr
goD (Dep dep@(PkgComponent qpn (ExposedExe _)) ci) _ =
Dep (Q (PackagePath ns (QualExe pn qpn)) <$> dep) ci
goD (Dep dep@(PkgComponent qpn ExposedLib) ci) comp
goD (Dep dep@(PkgComponent qpn (ExposedLib _)) ci) comp
| qBase qpn = Dep (Q (PackagePath ns (QualBase pn)) <$> dep) ci
| qSetup comp = Dep (Q (PackagePath ns (QualSetup pn)) <$> dep) ci
| otherwise = Dep (Q (PackagePath ns inheritedQ ) <$> dep) ci
Expand Down
18 changes: 17 additions & 1 deletion cabal-install/Distribution/Solver/Modular/Index.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Distribution.Solver.Modular.Index
( Index
, PInfo(..)
, ComponentInfo(..)
, IsVisible(..)
, IsBuildable(..)
, defaultQualifyOptions
, mkIndex
Expand Down Expand Up @@ -30,10 +32,24 @@ type Index = Map PN (Map I PInfo)
-- globally, for reasons external to the solver. We currently use this
-- for shadowing which essentially is a GHC limitation, and for
-- installed packages that are broken.
data PInfo = PInfo (FlaggedDeps PN) (Map ExposedComponent IsBuildable) FlagInfo (Maybe FailReason)
data PInfo = PInfo (FlaggedDeps PN)
(Map ExposedComponent ComponentInfo)
FlagInfo
(Maybe FailReason)

-- | Info associated with each library and executable in a package instance.
data ComponentInfo = ComponentInfo {
compIsVisible :: IsVisible
, compIsBuildable :: IsBuildable
}

-- | Whether a component is visible in the current environment.
newtype IsVisible = IsVisible Bool
deriving Eq

-- | Whether a component is made unbuildable by a "buildable: False" field.
newtype IsBuildable = IsBuildable Bool
deriving Eq

mkIndex :: [(PN, I, PInfo)] -> Index
mkIndex xs = M.map M.fromList (groupMap (L.map (\ (pn, i, pi) -> (pn, (i, pi))) xs))
Expand Down
100 changes: 67 additions & 33 deletions cabal-install/Distribution/Solver/Modular/IndexConversion.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe (mapMaybe, fromMaybe, maybeToList)
import Data.Monoid as Mon
import qualified Distribution.Compat.NonEmptySet as NonEmptySet
import qualified Data.Set as S

import qualified Distribution.InstalledPackageInfo as IPI
Expand All @@ -25,6 +26,7 @@ import Distribution.PackageDescription.Configuration
import qualified Distribution.Simple.PackageIndex as SI
import Distribution.System
import Distribution.Types.ForeignLib
import Distribution.Types.LibraryVisibility

import Distribution.Solver.Types.ComponentDeps
( Component(..), componentNameToComponent )
Expand Down Expand Up @@ -92,11 +94,18 @@ convIP :: SI.InstalledPackageIndex -> IPI.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)
Just fds -> ( pn, i, PInfo fds components M.empty Nothing)
where
-- TODO: Handle sub-libraries and visibility.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this entail?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to look into handling installed packages. It could be tricky because of the hack where the solver treats installed internal libraries as packages with munged names:

-- 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.

I would need to figure out how to distinguish between inter- and intra-package dependencies when enforcing visibility.

components =
M.singleton (ExposedLib LMainLibName)
ComponentInfo {
compIsVisible = IsVisible True
, compIsBuildable = IsBuildable True
}

(pn, i) = convId ipi

-- 'sourceLibName' is unreliable, but for now we only really use this for
-- primary libs anyways
comp = componentNameToComponent $ CLibName $ IPI.sourceLibName ipi
Expand Down Expand Up @@ -140,7 +149,8 @@ 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)
name = ExposedLib LMainLibName -- TODO: Handle sub-libraries.
in Just (D.Simple (LDep dr (Dep (PkgComponent pn name) (Fixed i))) comp)
-- NB: something we pick up from the
-- InstalledPackageIndex is NEVER an executable

Expand Down Expand Up @@ -213,34 +223,52 @@ convGPD os arch cinfo constraints strfl solveExes pn
Just ver -> Just (UnsupportedSpecVer ver)
Nothing -> Nothing

components :: Map ExposedComponent IsBuildable
components = M.fromList $ libComps ++ exeComps
components :: Map ExposedComponent ComponentInfo
components = M.fromList $ libComps ++ subLibComps ++ exeComps
where
libComps = [ (ExposedLib, IsBuildable $ isBuildable libBuildInfo lib)
libComps = [ (ExposedLib LMainLibName, libToComponentInfo lib)
| lib <- maybeToList mlib ]
exeComps = [ (ExposedExe name, IsBuildable $ isBuildable buildInfo exe)
subLibComps = [ (ExposedLib (LSubLibName name), libToComponentInfo lib)
| (name, lib) <- sub_libs ]
exeComps = [ ( ExposedExe name
, ComponentInfo {
compIsVisible = IsVisible True
, compIsBuildable = IsBuildable $ testCondition (buildable . buildInfo) exe /= Just False
}
)
| (name, exe) <- exes ]
isBuildable = isBuildableComponent os arch cinfo constraints

libToComponentInfo lib =
ComponentInfo {
compIsVisible = IsVisible $ testCondition (isPrivate . libVisibility) lib /= Just True
, compIsBuildable = IsBuildable $ testCondition (buildable . libBuildInfo) lib /= Just False
}

testCondition = testConditionForComponent os arch cinfo constraints

isPrivate LibraryVisibilityPrivate = True
isPrivate LibraryVisibilityPublic = False

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
-- | Applies the given predicate (for example, testing buildability or
-- visibility) to the given component and environment. Values are combined with
-- AND. This function returns 'Nothing' when the result cannot be determined
-- before dependency solving. Additionally, this function only considers flags
-- that are set by unqualified flag constraints, and it doesn't check the
-- intra-package dependencies of a component.
testConditionForComponent :: OS
-> Arch
-> CompilerInfo
-> [LabeledPackageConstraint]
-> (a -> Bool)
-> CondTree ConfVar [Dependency] a
-> Maybe Bool
testConditionForComponent os arch cinfo constraints p tree =
case simplifyCondition $ extractCondition p tree of
Lit True -> Just True
Lit False -> Just False
_ -> Nothing
where
flagAssignment :: [(FlagName, Bool)]
flagAssignment =
Expand Down Expand Up @@ -332,8 +360,10 @@ convCondTree flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes@(Solv
-- 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
[ D.Simple singleDep comp
| dep <- mapMaybe (filterIPNs ipns) ds
, singleDep <- convLibDeps dr dep ] -- unconditional package dependencies

++ L.map (\e -> D.Simple (LDep dr (Ext e)) comp) (allExtensions bi) -- unconditional extension dependencies
++ L.map (\l -> D.Simple (LDep dr (Lang l)) comp) (allLanguages bi) -- unconditional language dependencies
++ L.map (\(PkgconfigDependency pkn vr) -> D.Simple (LDep dr (Pkg pkn vr)) comp) (pkgconfigDepends bi) -- unconditional pkg-config dependencies
Expand Down Expand Up @@ -537,9 +567,12 @@ 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 a set of library components (from a single
-- package) to solver-specific dependencies.
convLibDeps :: DependencyReason PN -> Dependency -> [LDep PN]
convLibDeps dr (Dependency pn vr libs) =
[ LDep dr $ Dep (PkgComponent pn (ExposedLib lib)) (Constrained vr)
| lib <- NonEmptySet.toList libs ]

-- | Convert a Cabal dependency on an executable (build-tools) to a solver-specific dependency.
convExeDep :: DependencyReason PN -> ExeDependency -> LDep PN
Expand All @@ -548,5 +581,6 @@ convExeDep dr (ExeDependency pn exe vr) = LDep dr $ Dep (PkgComponent pn (Expose
-- | 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)
(setupDepends nfo)
[ D.Simple singleDep ComponentSetup
| dep <- setupDepends nfo
, singleDep <- convLibDeps (DependencyReason pn M.empty S.empty) dep ]
13 changes: 9 additions & 4 deletions cabal-install/Distribution/Solver/Modular/Message.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Distribution.Solver.Modular.Version
import Distribution.Solver.Types.ConstraintSource
import Distribution.Solver.Types.PackagePath
import Distribution.Solver.Types.Progress
import Distribution.Types.LibraryName
import Distribution.Types.UnqualComponentName

data Message =
Expand Down Expand Up @@ -220,8 +221,10 @@ showFR _ (MissingPkgconfigPackage pn vr) = " (conflict: pkg-config package " ++
showFR _ (NewPackageDoesNotMatchExistingConstraint d) = " (conflict: " ++ showConflictingDep d ++ ")"
showFR _ (ConflictingConstraints d1 d2) = " (conflict: " ++ L.intercalate ", " (L.map showConflictingDep [d1, d2]) ++ ")"
showFR _ (NewPackageIsMissingRequiredComponent comp dr) = " (does not contain " ++ showExposedComponent comp ++ ", which is required by " ++ showDependencyReason dr ++ ")"
showFR _ (NewPackageHasPrivateRequiredComponent comp dr) = " (" ++ showExposedComponent comp ++ " is private, but it is required by " ++ showDependencyReason dr ++ ")"
showFR _ (NewPackageHasUnbuildableRequiredComponent comp dr) = " (" ++ showExposedComponent comp ++ " is not buildable in the current environment, but it is required by " ++ showDependencyReason dr ++ ")"
showFR _ (PackageRequiresMissingComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component does not exist)"
showFR _ (PackageRequiresPrivateComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component is private)"
showFR _ (PackageRequiresUnbuildableComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component is not buildable in the current environment)"
showFR _ CannotInstall = " (only already installed instances can be used)"
showFR _ CannotReinstall = " (avoiding to reinstall a package with same version but new dependencies)"
Expand All @@ -247,8 +250,9 @@ showFR _ (MalformedStanzaChoice qsn) = " (INTERNAL ERROR: MALFORMED STANZA
showFR _ EmptyGoalChoice = " (INTERNAL ERROR: EMPTY GOAL CHOICE)"

showExposedComponent :: ExposedComponent -> String
showExposedComponent ExposedLib = "library"
showExposedComponent (ExposedExe name) = "executable '" ++ unUnqualComponentName name ++ "'"
showExposedComponent (ExposedLib LMainLibName) = "library"
showExposedComponent (ExposedLib (LSubLibName name)) = "library '" ++ unUnqualComponentName name ++ "'"
showExposedComponent (ExposedExe name) = "executable '" ++ unUnqualComponentName name ++ "'"

constraintSource :: ConstraintSource -> String
constraintSource src = "constraint from " ++ showConstraintSource src
Expand All @@ -257,8 +261,9 @@ showConflictingDep :: ConflictingDep -> String
showConflictingDep (ConflictingDep dr (PkgComponent qpn comp) ci) =
let DependencyReason qpn' _ _ = dr
componentStr = case comp of
ExposedExe exe -> " (exe " ++ unUnqualComponentName exe ++ ")"
ExposedLib -> ""
ExposedExe exe -> " (exe " ++ unUnqualComponentName exe ++ ")"
ExposedLib LMainLibName -> ""
ExposedLib (LSubLibName lib) -> " (lib " ++ unUnqualComponentName lib ++ ")"
in case ci of
Fixed i -> (if qpn /= qpn' then showDependencyReason dr ++ " => " else "") ++
showQPN qpn ++ componentStr ++ "==" ++ showI i
Expand Down
2 changes: 2 additions & 0 deletions cabal-install/Distribution/Solver/Modular/Tree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ data FailReason = UnsupportedExtension Extension
| NewPackageDoesNotMatchExistingConstraint ConflictingDep
| ConflictingConstraints ConflictingDep ConflictingDep
| NewPackageIsMissingRequiredComponent ExposedComponent (DependencyReason QPN)
| NewPackageHasPrivateRequiredComponent ExposedComponent (DependencyReason QPN)
| NewPackageHasUnbuildableRequiredComponent ExposedComponent (DependencyReason QPN)
| PackageRequiresMissingComponent QPN ExposedComponent
| PackageRequiresPrivateComponent QPN ExposedComponent
| PackageRequiresUnbuildableComponent QPN ExposedComponent
| CannotInstall
| CannotReinstall
Expand Down
Loading