From 3453175d9f88a4427b042930445ead6c06996493 Mon Sep 17 00:00:00 2001
From: Edsko de Vries <edsko@well-typed.com>
Date: Sat, 28 Feb 2015 18:34:47 +0000
Subject: [PATCH] Make top-down solver more independent

Give the top-down solver it's own copy of `dependencyGraph`. This means that we
now have three independent implementations of `dependencyGraph`:

- `dependencyGraph` in `Cabal` takes a package index indexed by installed
  package IDs and only has access to library dependencies.
- `dependencyGraph` in `Distribution.Client.PlanIndex` in `cabal-install` takes
  a package index indexed by installed package IDs and has access to all
  dependencies.
- `dependencyGraph` in the top-down solver in `cabal-install` takes a package
  index indexed by package _names_, and has access to all dependencies.

Ideally we would switch the top-down solver over to use a package indexed by
installed package IDs, so that this duplication could be avoided, but that's a
bit of work and the top-down solver is legacy code anyway. Can still do that
later, of course.

Moreover, this makes the top-down solver monomorphic where possible, and
introduce its own SourceDeps class so that it is independent of the FixedDeps
class (which we will change over to use InstalledPackageIds instead).
---
 .../Distribution/Client/Dependency/TopDown.hs | 68 +++++++++++++++----
 .../Client/Dependency/TopDown/Constraints.hs  | 14 ++--
 .../Client/Dependency/TopDown/Types.hs        | 48 +++++++++++--
 3 files changed, 104 insertions(+), 26 deletions(-)

diff --git a/cabal-install/Distribution/Client/Dependency/TopDown.hs b/cabal-install/Distribution/Client/Dependency/TopDown.hs
index f76943eb88..a4a083473e 100644
--- a/cabal-install/Distribution/Client/Dependency/TopDown.hs
+++ b/cabal-install/Distribution/Client/Dependency/TopDown.hs
@@ -33,9 +33,8 @@ import Distribution.Client.Dependency.Types
          , Progress(..), foldProgress )
 
 import qualified Distribution.Client.PackageIndex as PackageIndex
-import qualified Distribution.Client.PlanIndex as PlanIndex
 import Distribution.Client.PackageIndex
-         ( PackageIndex, PackageFixedDeps(depends) )
+         ( PackageIndex )
 import Distribution.Package
          ( PackageName(..), PackageId, Package(..), packageVersion, packageName
          , Dependency(Dependency), thisPackageVersion
@@ -425,7 +424,7 @@ annotateInstalledPackages dfsNumber installed = PackageIndex.fromList
     transitiveDepends :: InstalledPackage -> [PackageId]
     transitiveDepends = map (packageId . toPkg) . tail . Graph.reachable graph
                       . fromJust . toVertex . packageId
-    (graph, toPkg, toVertex) = PlanIndex.dependencyGraph installed
+    (graph, toPkg, toVertex) = dependencyGraph installed
 
 
 -- | Annotate each available packages with its topological sort number and any
@@ -483,7 +482,7 @@ topologicalSortNumbering installedPkgIndex sourcePkgIndex =
          | pkgs@(pkg:_) <- PackageIndex.allPackagesByName installedPkgIndex
          , let deps = [ packageName dep
                       | pkg' <- pkgs
-                      , dep  <- depends pkg' ] ]
+                      , dep  <- sourceDeps pkg' ] ]
       ++ [ ((), packageName pkg, nub deps)
          | pkgs@(pkg:_) <- PackageIndex.allPackagesByName sourcePkgIndex
          , let deps = [ depName
@@ -521,7 +520,7 @@ selectNeededSubset installedPkgIndex sourcePkgIndex = select mempty mempty
                         filter notAlreadyIncluded
                       $ [ packageName dep
                         | pkg <- moreInstalled
-                        , dep <- depends pkg ]
+                        , dep <- sourceDeps pkg ]
                      ++ [ name
                         | SourcePackage _ pkg _ _ <- moreSource
                         , Dependency name _ <-
@@ -581,7 +580,7 @@ finaliseSelectedPackages pref selected constraints =
         -- silly things like deciding to rebuild haskell98 against base 3.
         isCurrent = case mipkg :: Maybe InstalledPackageEx of
           Nothing   -> \_ -> False
-          Just ipkg -> \p -> packageId p `elem` depends ipkg
+          Just ipkg -> \p -> packageId p `elem` sourceDeps ipkg
         -- If there is no upper bound on the version range then we apply a
         -- preferred version according to the hackage or user's suggested
         -- version constraints. TODO: distinguish hacks from prefs
@@ -629,7 +628,7 @@ improvePlan installed constraints0 selected0 =
     improvePkg selected constraints pkgid = do
       Configured pkg  <- PackageIndex.lookupPackageId selected  pkgid
       ipkg            <- PackageIndex.lookupPackageId installed pkgid
-      guard $ all (isInstalled selected) (depends pkg)
+      guard $ all (isInstalled selected) (sourceDeps pkg)
       tryInstalled selected constraints [ipkg]
 
     isInstalled selected pkgid =
@@ -642,12 +641,12 @@ improvePlan installed constraints0 selected0 =
                  -> Maybe (PackageIndex PlanPackage, Constraints)
     tryInstalled selected constraints [] = Just (selected, constraints)
     tryInstalled selected constraints (pkg:pkgs) =
-      case constraintsOk (packageId pkg) (depends pkg) constraints of
+      case constraintsOk (packageId pkg) (sourceDeps pkg) constraints of
         Nothing           -> Nothing
         Just constraints' -> tryInstalled selected' constraints' pkgs'
           where
             selected' = PackageIndex.insert (PreExisting pkg) selected
-            pkgs'      = catMaybes (map notSelected (depends pkg)) ++ pkgs
+            pkgs'      = catMaybes (map notSelected (sourceDeps pkg)) ++ pkgs
             notSelected pkgid =
               case (PackageIndex.lookupPackageId installed pkgid
                    ,PackageIndex.lookupPackageId selected  pkgid) of
@@ -662,13 +661,12 @@ improvePlan installed constraints0 selected0 =
       where
         dep = thisPackageVersion pkgid'
 
-    reverseTopologicalOrder :: PackageFixedDeps pkg
-                            => PackageIndex pkg -> [PackageId]
+    reverseTopologicalOrder :: PackageIndex PlanPackage -> [PackageId]
     reverseTopologicalOrder index = map (packageId . toPkg)
                                   . Graph.topSort
                                   . Graph.transposeG
                                   $ graph
-      where (graph, toPkg, _) = PlanIndex.dependencyGraph index
+      where (graph, toPkg, _) = dependencyGraph index
 
 -- ------------------------------------------------------------
 -- * Adding and recording constraints
@@ -946,3 +944,49 @@ listOf disp [x0] = disp x0
 listOf disp (x0:x1:xs) = disp x0 ++ go x1 xs
   where go x []       = " and " ++ disp x
         go x (x':xs') = ", " ++ disp x ++ go x' xs'
+
+-- ------------------------------------------------------------
+-- * Construct a dependency graph
+-- ------------------------------------------------------------
+
+-- | Builds a graph of the package dependencies.
+--
+-- Dependencies on other packages that are not in the index are discarded.
+-- You can check if there are any such dependencies with 'brokenPackages'.
+--
+-- The top-down solver gets its own implementation, because both
+-- `dependencyGraph` in `Distribution.Client.PlanIndex` (in cabal-install) and
+-- `dependencyGraph` in `Distribution.Simple.PackageIndex` (in Cabal) both work
+-- with `PackageIndex` from `Cabal` (that is, a package index indexed by
+-- installed package IDs rather than package names).
+--
+-- Ideally we would switch the top-down solver over to use that too, so that
+-- this duplication could be avoided, but that's a bit of work and the top-down
+-- solver is legacy code anyway.
+--
+-- (NOTE: This is called at two types: InstalledPackage and PlanPackage.)
+dependencyGraph :: PackageSourceDeps pkg
+                => PackageIndex pkg
+                -> (Graph.Graph,
+                    Graph.Vertex -> pkg,
+                    PackageId -> Maybe Graph.Vertex)
+dependencyGraph index = (graph, vertexToPkg, pkgIdToVertex)
+  where
+    graph = Array.listArray bounds $
+            map (catMaybes . map pkgIdToVertex . sourceDeps) pkgs
+    vertexToPkg vertex = pkgTable Array.! vertex
+    pkgIdToVertex = binarySearch 0 topBound
+
+    pkgTable   = Array.listArray bounds pkgs
+    pkgIdTable = Array.listArray bounds (map packageId pkgs)
+    pkgs = sortBy (comparing packageId) (PackageIndex.allPackages index)
+    topBound = length pkgs - 1
+    bounds = (0, topBound)
+
+    binarySearch a b key
+      | a > b     = Nothing
+      | otherwise = case compare key (pkgIdTable Array.! mid) of
+          LT -> binarySearch a (mid-1) key
+          EQ -> Just mid
+          GT -> binarySearch (mid+1) b key
+      where mid = (a + b) `div` 2
diff --git a/cabal-install/Distribution/Client/Dependency/TopDown/Constraints.hs b/cabal-install/Distribution/Client/Dependency/TopDown/Constraints.hs
index 00004bcdcf..841dd539a3 100644
--- a/cabal-install/Distribution/Client/Dependency/TopDown/Constraints.hs
+++ b/cabal-install/Distribution/Client/Dependency/TopDown/Constraints.hs
@@ -27,7 +27,7 @@ module Distribution.Client.Dependency.TopDown.Constraints (
 import Distribution.Client.Dependency.TopDown.Types
 import qualified Distribution.Client.PackageIndex as PackageIndex
 import Distribution.Client.PackageIndex
-        ( PackageIndex, PackageFixedDeps(depends) )
+        ( PackageIndex )
 import Distribution.Package
          ( PackageName, PackageId, PackageIdentifier(..)
          , Package(packageId), packageName, packageVersion
@@ -225,14 +225,12 @@ transitionsTo constraints @(Constraints _ available  excluded  _ _)
       SourceOnly           b -> SourceOnly    (g b)
       InstalledAndSource a b -> InstalledAndSource (f a) (g b)
 
-
 -- | We construct 'Constraints' with an initial 'PackageIndex' of all the
 -- packages available.
 --
-empty :: (PackageFixedDeps installed, Package source)
-      => PackageIndex installed
-      -> PackageIndex source
-      -> Constraints installed source reason
+empty :: PackageIndex InstalledPackageEx
+      -> PackageIndex UnconfiguredPackage
+      -> Constraints InstalledPackageEx UnconfiguredPackage reason
 empty installed source =
     Constraints targets pkgs excluded pairs pkgs
   where
@@ -254,8 +252,8 @@ empty installed source =
       , let name   = packageName pkg1
             pkgid1 = packageId pkg1
             pkgid2 = packageId pkg2
-      ,    any ((pkgid1==) . packageId) (depends pkg2)
-        || any ((pkgid2==) . packageId) (depends pkg1) ]
+      ,    any ((pkgid1==) . packageId) (sourceDeps pkg2)
+        || any ((pkgid2==) . packageId) (sourceDeps pkg1) ]
 
 
 -- | The package targets.
diff --git a/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs b/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
index dc480e36b9..29632b67af 100644
--- a/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
+++ b/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
@@ -13,9 +13,10 @@
 module Distribution.Client.Dependency.TopDown.Types where
 
 import Distribution.Client.Types
-         ( SourcePackage(..), InstalledPackage, OptionalStanza )
-import Distribution.Client.PackageIndex
-         ( PackageFixedDeps(depends) )
+         ( SourcePackage(..), ReadyPackage(..), InstalledPackage(..)
+         , OptionalStanza )
+import Distribution.Client.InstallPlan
+         ( ConfiguredPackage(..), PlanPackage(..) )
 
 import Distribution.Package
          ( PackageIdentifier, Dependency
@@ -65,9 +66,6 @@ data SemiConfiguredPackage
 instance Package InstalledPackageEx where
   packageId (InstalledPackageEx p _ _) = packageId p
 
-instance PackageFixedDeps InstalledPackageEx where
-  depends (InstalledPackageEx _ _ deps) = deps
-
 instance Package UnconfiguredPackage where
   packageId (UnconfiguredPackage p _ _ _) = packageId p
 
@@ -91,3 +89,41 @@ instance (Package installed, Package source)
 data InstalledConstraint = InstalledConstraint
                          | SourceConstraint
   deriving (Eq, Show)
+
+-- | Package dependencies
+--
+-- The top-down solver uses its down type class for package dependencies,
+-- because it wants to know these dependencies as PackageIds, rather than as
+-- InstalledPackageIds (so it cannot use PackageFixedDeps).
+--
+-- Ideally we would switch the top-down solver over to use InstalledPackageIds
+-- throughout; that means getting rid of this type class, and changing over the
+-- package index type to use Cabal's rather than cabal-install's. That will
+-- avoid the need for the local definitions of dependencyGraph and
+-- reverseTopologicalOrder in the top-down solver.
+--
+-- Note that the top-down solver does not (and probably will never) make a
+-- distinction between the various kinds of dependencies, so we return a flat
+-- list here. If we get rid of this type class then any use of `sourceDeps`
+-- should be replaced by @fold . depends@.
+class Package a => PackageSourceDeps a where
+  sourceDeps :: a -> [PackageIdentifier]
+
+instance PackageSourceDeps InstalledPackageEx where
+  sourceDeps (InstalledPackageEx _ _ deps) = deps
+
+instance PackageSourceDeps ConfiguredPackage where
+  sourceDeps (ConfiguredPackage _ _ _ deps) = deps
+
+instance PackageSourceDeps ReadyPackage where
+  sourceDeps (ReadyPackage _ _ _ deps) = map packageId deps
+
+instance PackageSourceDeps InstalledPackage where
+  sourceDeps (InstalledPackage _ deps) = deps
+
+instance PackageSourceDeps PlanPackage where
+  sourceDeps (PreExisting pkg) = sourceDeps pkg
+  sourceDeps (Configured  pkg) = sourceDeps pkg
+  sourceDeps (Processing pkg)  = sourceDeps pkg
+  sourceDeps (Installed pkg _) = sourceDeps pkg
+  sourceDeps (Failed    pkg _) = sourceDeps pkg
-- 
GitLab