From d9bf6788adf6d416d6335fd79c4c1b3fbc7d0ad1 Mon Sep 17 00:00:00 2001
From: "Edward Z. Yang" <ezyang@cs.stanford.edu>
Date: Sun, 31 Jul 2016 21:17:58 -0700
Subject: [PATCH] Per-component new-build support (no Custom support yet).

A bit of a megapatch.  Here's what's in it:

* First, a few miscellaneous utility functions and reexports
  in Cabal.  I could have split these into a separate commit
  but I was too lazy to.

* Distribution.Client.Install got refactored:
  instead of using PackageFixedDeps, it uses IsUnit
  instead.  This is because we weren't using ComponentDeps
  in a nontrivial way; we just need some graph structure
  and IsNode (with UnitId keys) fulfills that. I also removed the
  invariant checking and error reporting because it was
  being annoying (we check the invariants already in
  SolverInstallPlan).

* Look at Distribution.Client.ProjectPlanning.Types.
  This contains the primary type change: ElaboratedConfiguredPackage
  is now EITHER a monolithic ElaboratedPackage, or a per-component
  ElaboratedComponent (it should get renamed but I didn't do that
  in this patch.)  These are what we're going to store in our
  plans: if a package we're building has a Setup script which supports
  per-component builds, we'll explode it into a component.  Otherwise
  we'll keep it as a package.  We'll see codepaths for both
  throughout.

* OK, so the expansion happens in ProjectPlanning, mostly in
  'elaborateAndExpandSolverPackage'.  You should review the
  package hash computation code closely.  When we can separate
  components, we compute a hash for each INDEPENDENTLY.  This
  is good: we get more sharing.

* We need to adjust the target resolution and pruning code
  in ProjectOrchestration and ProjectPlanning.  I did a dumb
  but easy idea: if a user mentions 'packagename' in a
  target name, I spray the PackageTarget on every
  possibly relevant IPID in buildTargets', and then pare
  it down later.

* And of course there's code in ProjectBuilding to actual
  do a configure and then build.

* We change the layout of build directories so that we can
  track each component separately.  While I was doing that,
  I also added compiler and platform information.

Custom doesn't work yet because I need to give them their own
separate component, and teach Cabal how to build them specially.

Signed-off-by: Edward Z. Yang <ezyang@cs.stanford.edu>
---
 Cabal/Distribution/Package.hs                 |   5 +
 Cabal/Distribution/Simple/Configure.hs        |   2 +
 .../Types/ComponentEnabledSpec.hs             |   2 +-
 .../Distribution/Types/PackageDescription.hs  |   9 +
 cabal-install/Distribution/Client/CmdBuild.hs |   1 +
 .../Distribution/Client/CmdFreeze.hs          |  10 +-
 cabal-install/Distribution/Client/CmdRepl.hs  |   1 +
 .../Distribution/Client/DistDirLayout.hs      |  54 +-
 cabal-install/Distribution/Client/Install.hs  |   5 +-
 .../Distribution/Client/InstallPlan.hs        | 275 ++++-------
 .../Distribution/Client/PackageHash.hs        |   5 +
 .../Distribution/Client/ProjectBuilding.hs    | 216 ++++----
 .../Client/ProjectOrchestration.hs            |  75 ++-
 .../Distribution/Client/ProjectPlanOutput.hs  |  35 +-
 .../Distribution/Client/ProjectPlanning.hs    | 461 +++++++++++++-----
 .../Client/ProjectPlanning/Types.hs           | 184 ++++++-
 cabal-install/Distribution/Client/Types.hs    |  44 +-
 .../Solver/Types/ComponentDeps.hs             |  10 +
 cabal-install/cabal-install.cabal             |   5 +
 .../internal-libs/new_build.sh                |   3 +-
 .../new-build/executable/Main.hs              |   1 +
 .../new-build/executable/Setup.hs             |   2 +
 .../new-build/executable/Test.hs              |   1 +
 .../new-build/executable/a.cabal              |  15 +
 .../new-build/executable/cabal.project        |   1 +
 cabal-install/tests/IntegrationTests2.hs      |  17 +-
 .../exception/configure/a.cabal               |   6 +
 .../Distribution/Client/InstallPlan.hs        |  23 +-
 28 files changed, 996 insertions(+), 472 deletions(-)
 create mode 100644 cabal-install/tests/IntegrationTests/new-build/executable/Main.hs
 create mode 100644 cabal-install/tests/IntegrationTests/new-build/executable/Setup.hs
 create mode 100644 cabal-install/tests/IntegrationTests/new-build/executable/Test.hs
 create mode 100644 cabal-install/tests/IntegrationTests/new-build/executable/a.cabal
 create mode 100644 cabal-install/tests/IntegrationTests/new-build/executable/cabal.project

diff --git a/Cabal/Distribution/Package.hs b/Cabal/Distribution/Package.hs
index 0431b3d280..5e7ff259ae 100644
--- a/Cabal/Distribution/Package.hs
+++ b/Cabal/Distribution/Package.hs
@@ -27,6 +27,7 @@ module Distribution.Package (
         UnitId(..),
         mkUnitId,
         mkLegacyUnitId,
+        unitIdComponentId,
         getHSLibraryName,
         InstalledPackageId, -- backwards compat
 
@@ -176,6 +177,10 @@ mkUnitId = SimpleUnitId . ComponentId
 mkLegacyUnitId :: PackageId -> UnitId
 mkLegacyUnitId = SimpleUnitId . ComponentId . display
 
+-- | Extract 'ComponentId' from 'UnitId'.
+unitIdComponentId :: UnitId -> ComponentId
+unitIdComponentId (SimpleUnitId cid) = cid
+
 -- ------------------------------------------------------------
 -- * Package source dependencies
 -- ------------------------------------------------------------
diff --git a/Cabal/Distribution/Simple/Configure.hs b/Cabal/Distribution/Simple/Configure.hs
index 55ab9e9f52..b9ce2f366b 100644
--- a/Cabal/Distribution/Simple/Configure.hs
+++ b/Cabal/Distribution/Simple/Configure.hs
@@ -35,6 +35,8 @@ module Distribution.Simple.Configure (configure,
                                       tryGetPersistBuildConfig,
                                       maybeGetPersistBuildConfig,
                                       findDistPref, findDistPrefOrDefault,
+                                      mkComponentsGraph,
+                                      getInternalPackages,
                                       computeComponentId,
                                       computeCompatPackageKey,
                                       computeCompatPackageName,
diff --git a/Cabal/Distribution/Types/ComponentEnabledSpec.hs b/Cabal/Distribution/Types/ComponentEnabledSpec.hs
index b78259cddf..2ecfb1f15c 100644
--- a/Cabal/Distribution/Types/ComponentEnabledSpec.hs
+++ b/Cabal/Distribution/Types/ComponentEnabledSpec.hs
@@ -54,7 +54,7 @@ data ComponentEnabledSpec
     = ComponentEnabledSpec { testsEnabled :: Bool,
                              benchmarksEnabled :: Bool }
     | OneComponentEnabledSpec ComponentName
-  deriving (Generic, Read, Show)
+  deriving (Generic, Read, Show, Eq)
 instance Binary ComponentEnabledSpec
 
 -- | The default set of enabled components.  Historically tests and
diff --git a/Cabal/Distribution/Types/PackageDescription.hs b/Cabal/Distribution/Types/PackageDescription.hs
index b026d571fd..734ca3506f 100644
--- a/Cabal/Distribution/Types/PackageDescription.hs
+++ b/Cabal/Distribution/Types/PackageDescription.hs
@@ -43,6 +43,7 @@ module Distribution.Types.PackageDescription (
     updatePackageDescription,
     pkgComponents,
     pkgBuildableComponents,
+    enabledComponents,
     lookupComponent,
     getComponent,
   ) where
@@ -57,6 +58,7 @@ import Distribution.Types.Benchmark
 
 import Distribution.Types.Component
 import Distribution.Types.ComponentName
+import Distribution.Types.ComponentEnabledSpec
 import Distribution.Types.SetupBuildInfo
 import Distribution.Types.BuildInfo
 import Distribution.Types.BuildType
@@ -346,6 +348,13 @@ pkgComponents pkg =
 pkgBuildableComponents :: PackageDescription -> [Component]
 pkgBuildableComponents = filter componentBuildable . pkgComponents
 
+-- | A list of all components in the package that are enabled.
+--
+-- @since 2.0.0.0
+--
+enabledComponents :: PackageDescription -> ComponentEnabledSpec -> [Component]
+enabledComponents pkg enabled = filter (componentEnabled enabled) $ pkgBuildableComponents pkg
+
 lookupComponent :: PackageDescription -> ComponentName -> Maybe Component
 lookupComponent pkg CLibName = fmap CLib (library pkg)
 lookupComponent pkg (CSubLibName name) =
diff --git a/cabal-install/Distribution/Client/CmdBuild.hs b/cabal-install/Distribution/Client/CmdBuild.hs
index 8a5afe80c8..840ecf9a74 100644
--- a/cabal-install/Distribution/Client/CmdBuild.hs
+++ b/cabal-install/Distribution/Client/CmdBuild.hs
@@ -63,6 +63,7 @@ buildAction (configFlags, configExFlags, installFlags, haddockFlags)
     -- repl targets (as opposed to say repl or haddock targets).
     selectBuildTargets =
       selectTargets
+        verbosity
         BuildDefaultComponents
         BuildSpecificComponent
 
diff --git a/cabal-install/Distribution/Client/CmdFreeze.hs b/cabal-install/Distribution/Client/CmdFreeze.hs
index 1aa072ecd9..95b41c5a98 100644
--- a/cabal-install/Distribution/Client/CmdFreeze.hs
+++ b/cabal-install/Distribution/Client/CmdFreeze.hs
@@ -7,13 +7,11 @@ module Distribution.Client.CmdFreeze (
   ) where
 
 import Distribution.Client.ProjectPlanning
-         ( ElaboratedInstallPlan, rebuildInstallPlan )
 import Distribution.Client.ProjectConfig
          ( ProjectConfig(..), ProjectConfigShared(..)
          , commandLineFlagsToProjectConfig, writeProjectLocalFreezeConfig
          , findProjectRoot )
 import Distribution.Client.ProjectPlanning.Types
-         ( ElaboratedConfiguredPackage(..) )
 import Distribution.Client.Targets
          ( UserConstraint(..) )
 import Distribution.Solver.Types.ConstraintSource
@@ -149,8 +147,9 @@ projectFreezeConstraints plan =
     flagAssignments =
       Map.fromList
         [ (pkgname, flags)
-        | InstallPlan.Configured pkg <- InstallPlan.toList plan
-        , let flags   = pkgFlagAssignment pkg
+        | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList plan
+        , let pkg     = getElaboratedPackage pkg_or_comp
+              flags   = pkgFlagAssignment pkg
               pkgname = packageName pkg
         , not (null flags) ]
 
@@ -158,7 +157,8 @@ projectFreezeConstraints plan =
     localPackages =
       Map.fromList
         [ (packageName pkg, ())
-        | InstallPlan.Configured pkg <- InstallPlan.toList plan
+        | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList plan
+        , let pkg = getElaboratedPackage pkg_or_comp
         , pkgLocalToProject pkg
         ]
 
diff --git a/cabal-install/Distribution/Client/CmdRepl.hs b/cabal-install/Distribution/Client/CmdRepl.hs
index e277f50147..3bce9cee58 100644
--- a/cabal-install/Distribution/Client/CmdRepl.hs
+++ b/cabal-install/Distribution/Client/CmdRepl.hs
@@ -67,6 +67,7 @@ replAction (configFlags, configExFlags, installFlags, haddockFlags)
     -- repl targets (as opposed to say build or haddock targets).
     selectReplTargets =
       selectTargets
+        verbosity
         ReplDefaultComponent
         ReplSpecificComponent
 
diff --git a/cabal-install/Distribution/Client/DistDirLayout.hs b/cabal-install/Distribution/Client/DistDirLayout.hs
index 41e8dd25bd..cdf6f37ec1 100644
--- a/cabal-install/Distribution/Client/DistDirLayout.hs
+++ b/cabal-install/Distribution/Client/DistDirLayout.hs
@@ -5,17 +5,43 @@
 -- The layout of the .\/dist\/ directory where cabal keeps all of it's state
 -- and build artifacts.
 --
-module Distribution.Client.DistDirLayout where
+module Distribution.Client.DistDirLayout (
+    -- 'DistDirLayout'
+    DistDirLayout(..),
+    DistDirParams(..),
+    defaultDistDirLayout,
+
+    -- * 'CabalDirLayout'
+    CabalDirLayout(..),
+    defaultCabalDirLayout,
+) where
 
 import System.FilePath
 import Distribution.Package
-         ( PackageId )
+         ( PackageId, UnitId(..) )
 import Distribution.Compiler
 import Distribution.Simple.Compiler (PackageDB(..))
 import Distribution.Text
+import Distribution.Types.ComponentName
+import Distribution.System
 import Distribution.Client.Types
          ( InstalledPackageId )
 
+-- | Information which can be used to construct the path to
+-- the build directory of a build.  This is LESS fine-grained
+-- than what goes into the hashed 'InstalledPackageId',
+-- and for good reason: we don't want this path to change if
+-- the user, say, adds a dependency to their project.
+data DistDirParams = DistDirParams {
+    distParamUnitId         :: UnitId,
+    distParamPackageId      :: PackageId,
+    distParamComponentName  :: Maybe ComponentName,
+    distParamCompilerId     :: CompilerId,
+    distParamPlatform       :: Platform
+    -- TODO (see #3343):
+    --  Flag assignments
+    --  Optimization
+    }
 
 
 -- | The layout of the project state directory. Traditionally this has been
@@ -31,11 +57,11 @@ data DistDirLayout = DistDirLayout {
        -- | The directory under dist where we keep the build artifacts for a
        -- package we're building from a local directory.
        --
-       -- This uses a 'PackageId' not just a 'PackageName' because technically
+       -- This uses a 'UnitId' not just a 'PackageName' because technically
        -- we can have multiple instances of the same package in a solution
        -- (e.g. setup deps).
        --
-       distBuildDirectory           :: PackageId -> FilePath,
+       distBuildDirectory           :: DistDirParams -> FilePath,
        distBuildRootDirectory       :: FilePath,
 
        -- | The directory under dist where we put the unpacked sources of
@@ -55,8 +81,8 @@ data DistDirLayout = DistDirLayout {
        -- | The location for package-specific cache files (e.g. state used in
        -- incremental rebuilds).
        --
-       distPackageCacheFile         :: PackageId -> String -> FilePath,
-       distPackageCacheDirectory    :: PackageId -> FilePath,
+       distPackageCacheFile         :: DistDirParams -> String -> FilePath,
+       distPackageCacheDirectory    :: DistDirParams -> FilePath,
 
        distTempDirectory            :: FilePath,
        distBinDirectory             :: FilePath,
@@ -88,7 +114,17 @@ defaultDistDirLayout projectRootDirectory =
     --TODO: switch to just dist at some point, or some other new name
 
     distBuildRootDirectory   = distDirectory </> "build"
-    distBuildDirectory pkgid = distBuildRootDirectory </> display pkgid
+    distBuildDirectory params =
+        distBuildRootDirectory </>
+        display (distParamPlatform params) </>
+        display (distParamCompilerId params) </>
+        display (distParamPackageId params) </>
+        (case fmap componentNameString (distParamComponentName params) of
+            Nothing         -> ""
+            Just Nothing    -> ""
+            Just (Just str) -> "c" </> str) </>
+        (case distParamUnitId params of -- For Backpack
+            SimpleUnitId _ -> "")
 
     distUnpackedSrcRootDirectory   = distDirectory </> "src"
     distUnpackedSrcDirectory pkgid = distUnpackedSrcRootDirectory
@@ -97,8 +133,8 @@ defaultDistDirLayout projectRootDirectory =
     distProjectCacheDirectory = distDirectory </> "cache"
     distProjectCacheFile name = distProjectCacheDirectory </> name
 
-    distPackageCacheDirectory pkgid = distBuildDirectory pkgid </> "cache"
-    distPackageCacheFile pkgid name = distPackageCacheDirectory pkgid </> name
+    distPackageCacheDirectory params = distBuildDirectory params </> "cache"
+    distPackageCacheFile params name = distPackageCacheDirectory params </> name
 
     distTempDirectory = distDirectory </> "tmp"
 
diff --git a/cabal-install/Distribution/Client/Install.hs b/cabal-install/Distribution/Client/Install.hs
index 4a3b43eacc..44e717a4a3 100644
--- a/cabal-install/Distribution/Client/Install.hs
+++ b/cabal-install/Distribution/Client/Install.hs
@@ -116,7 +116,6 @@ import           Distribution.Solver.Types.ConstraintSource
 import           Distribution.Solver.Types.LabeledPackageConstraint
 import           Distribution.Solver.Types.OptionalStanza
 import qualified Distribution.Solver.Types.PackageIndex as SourcePackageIndex
-import           Distribution.Solver.Types.PackageFixedDeps
 import           Distribution.Solver.Types.PkgConfigDb
                    ( PkgConfigDb, readPkgConfigDb )
 import           Distribution.Solver.Types.SourcePackage as SourcePackage
@@ -614,12 +613,12 @@ packageStatus installedPkgIndex cpkg =
     changes :: Installed.InstalledPackageInfo
             -> ReadyPackage
             -> [MergeResult PackageIdentifier PackageIdentifier]
-    changes pkg pkg' = filter changed $
+    changes pkg (ReadyPackage pkg') = filter changed $
       mergeBy (comparing packageName)
         -- deps of installed pkg
         (resolveInstalledIds $ Installed.depends pkg)
         -- deps of configured pkg
-        (resolveInstalledIds $ CD.nonSetupDeps (depends pkg'))
+        (resolveInstalledIds $ map confInstId (CD.nonSetupDeps (confPkgDeps pkg')))
 
     -- convert to source pkg ids via index
     resolveInstalledIds :: [UnitId] -> [PackageIdentifier]
diff --git a/cabal-install/Distribution/Client/InstallPlan.hs b/cabal-install/Distribution/Client/InstallPlan.hs
index 429b53ccd2..25eed68a6e 100644
--- a/cabal-install/Distribution/Client/InstallPlan.hs
+++ b/cabal-install/Distribution/Client/InstallPlan.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE FlexibleContexts #-}
 -----------------------------------------------------------------------------
 -- |
@@ -21,10 +22,12 @@ module Distribution.Client.InstallPlan (
   GenericInstallPlan,
   PlanPackage,
   GenericPlanPackage(..),
+  IsUnit,
 
   -- * Operations on 'InstallPlan's
   new,
   toList,
+  planIndepGoals,
 
   fromSolverInstallPlan,
   configureInstallPlan,
@@ -42,7 +45,6 @@ module Distribution.Client.InstallPlan (
   -- ** Traversal helpers
   -- $traversal
   Processing,
-  -- NB: these functions are only used by the legacy install-path
   ready,
   completed,
   failed,
@@ -64,17 +66,16 @@ import qualified Distribution.Simple.Setup as Cabal
 import Distribution.InstalledPackageInfo
          ( InstalledPackageInfo )
 import Distribution.Package
-         ( PackageIdentifier(..), Package(..)
+         ( Package(..)
          , HasUnitId(..), UnitId(..) )
 import Distribution.Solver.Types.SolverPackage
 import Distribution.Client.JobControl
 import Distribution.Text
-         ( display )
+import Text.PrettyPrint
 import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
 import Distribution.Client.SolverInstallPlan (SolverInstallPlan)
 
 import qualified Distribution.Solver.Types.ComponentDeps as CD
-import           Distribution.Solver.Types.PackageFixedDeps
 import           Distribution.Solver.Types.Settings
 import           Distribution.Solver.Types.SolverId
 
@@ -82,9 +83,9 @@ import           Distribution.Solver.Types.SolverId
 -- import qualified Distribution.Simple.Configure as Configure
 
 import Data.List
-         ( foldl', intercalate )
+         ( foldl' )
 import Data.Maybe
-         ( fromMaybe, catMaybes, isJust )
+         ( fromMaybe, isJust )
 import qualified Distribution.Compat.Graph as Graph
 import Distribution.Compat.Graph (Graph, IsNode(..))
 import Distribution.Compat.Binary (Binary(..))
@@ -152,18 +153,23 @@ import Prelude hiding (lookup)
 -- dependencies in cabal-install should consider what to do with these
 -- dependencies; if we give a 'PackageInstalled' instance it would be too easy
 -- to get this wrong (and, for instance, call graph traversal functions from
--- Cabal rather than from cabal-install). Instead, see 'PackageFixedDeps'.
+-- Cabal rather than from cabal-install). Instead, see 'PackageInstalled'.
 data GenericPlanPackage ipkg srcpkg
    = PreExisting ipkg
    | Configured  srcpkg
   deriving (Eq, Show, Generic)
 
-instance (HasUnitId ipkg,   PackageFixedDeps ipkg,
-          HasUnitId srcpkg, PackageFixedDeps srcpkg)
+type IsUnit a = (IsNode a, Key a ~ UnitId)
+
+-- NB: Expanded constraint synonym here to avoid undecidable
+-- instance errors in GHC 7.8 and earlier.
+instance (IsNode ipkg, IsNode srcpkg, Key ipkg ~ UnitId, Key srcpkg ~ UnitId)
          => IsNode (GenericPlanPackage ipkg srcpkg) where
-    type Key (GenericPlanPackage ipkg srcpkg) = UnitId -- TODO: change me
-    nodeKey = installedUnitId
-    nodeNeighbors = CD.flatDeps . depends
+    type Key (GenericPlanPackage ipkg srcpkg) = UnitId
+    nodeKey (PreExisting ipkg) = nodeKey ipkg
+    nodeKey (Configured spkg) = nodeKey spkg
+    nodeNeighbors (PreExisting ipkg) = nodeNeighbors ipkg
+    nodeNeighbors (Configured spkg) = nodeNeighbors spkg
 
 instance (Binary ipkg, Binary srcpkg)
       => Binary (GenericPlanPackage ipkg srcpkg)
@@ -176,18 +182,17 @@ instance (Package ipkg, Package srcpkg) =>
   packageId (PreExisting ipkg)     = packageId ipkg
   packageId (Configured  spkg)     = packageId spkg
 
-instance (PackageFixedDeps srcpkg,
-          PackageFixedDeps ipkg) =>
-         PackageFixedDeps (GenericPlanPackage ipkg srcpkg) where
-  depends (PreExisting pkg)     = depends pkg
-  depends (Configured  pkg)     = depends pkg
-
 instance (HasUnitId ipkg, HasUnitId srcpkg) =>
          HasUnitId
          (GenericPlanPackage ipkg srcpkg) where
   installedUnitId (PreExisting ipkg) = installedUnitId ipkg
   installedUnitId (Configured  spkg) = installedUnitId spkg
 
+instance (HasConfiguredId ipkg, HasConfiguredId srcpkg) =>
+          HasConfiguredId (GenericPlanPackage ipkg srcpkg) where
+    configuredId (PreExisting ipkg) = configuredId ipkg
+    configuredId (Configured pkg) = configuredId pkg
+
 data GenericInstallPlan ipkg srcpkg = GenericInstallPlan {
     planIndex      :: !(PlanIndex ipkg srcpkg),
     planIndepGoals :: !IndependentGoals
@@ -200,13 +205,6 @@ type InstallPlan = GenericInstallPlan
 type PlanIndex ipkg srcpkg =
      Graph (GenericPlanPackage ipkg srcpkg)
 
-invariant :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-              HasUnitId srcpkg, PackageFixedDeps srcpkg)
-          => GenericInstallPlan ipkg srcpkg -> Bool
-invariant plan =
-    valid (planIndepGoals plan)
-          (planIndex plan)
-
 -- | Smart constructor that deals with caching the 'Graph' representation.
 --
 mkInstallPlan :: PlanIndex ipkg srcpkg
@@ -221,8 +219,7 @@ mkInstallPlan index indepGoals =
 internalError :: String -> a
 internalError msg = error $ "InstallPlan: internal error: " ++ msg
 
-instance (HasUnitId ipkg,   PackageFixedDeps ipkg,
-          HasUnitId srcpkg, PackageFixedDeps srcpkg,
+instance (IsNode ipkg, Key ipkg ~ UnitId, IsNode srcpkg, Key srcpkg ~ UnitId,
           Binary ipkg, Binary srcpkg)
        => Binary (GenericInstallPlan ipkg srcpkg) where
     put GenericInstallPlan {
@@ -234,16 +231,19 @@ instance (HasUnitId ipkg,   PackageFixedDeps ipkg,
       (index, indepGoals) <- get
       return $! mkInstallPlan index indepGoals
 
-showPlanIndex :: (HasUnitId ipkg, HasUnitId srcpkg)
+showPlanIndex :: (Package ipkg, Package srcpkg,
+                  IsUnit ipkg, IsUnit srcpkg)
               => PlanIndex ipkg srcpkg -> String
-showPlanIndex index =
-    intercalate "\n" (map showPlanPackage (Graph.toList index))
-  where showPlanPackage p =
-            showPlanPackageTag p ++ " "
-                ++ display (packageId p) ++ " ("
-                ++ display (installedUnitId p) ++ ")"
-
-showInstallPlan :: (HasUnitId ipkg, HasUnitId srcpkg)
+showPlanIndex index = renderStyle defaultStyle $
+    vcat (map dispPlanPackage (Graph.toList index))
+  where dispPlanPackage p =
+            hang (hsep [ text (showPlanPackageTag p)
+                       , disp (packageId p)
+                       , parens (disp (nodeKey p))]) 2
+                 (vcat (map disp (nodeNeighbors p)))
+
+showInstallPlan :: (Package ipkg, Package srcpkg,
+                    IsUnit ipkg, IsUnit srcpkg)
                 => GenericInstallPlan ipkg srcpkg -> String
 showInstallPlan = showPlanIndex . planIndex
 
@@ -253,16 +253,10 @@ showPlanPackageTag (Configured  _)   = "Configured"
 
 -- | Build an installation plan from a valid set of resolved packages.
 --
-new :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-        HasUnitId srcpkg, PackageFixedDeps srcpkg)
-    => IndependentGoals
+new :: IndependentGoals
     -> PlanIndex ipkg srcpkg
-    -> Either [PlanProblem ipkg srcpkg]
-              (GenericInstallPlan ipkg srcpkg)
-new indepGoals index =
-  case problems indepGoals index of
-    []    -> Right (mkInstallPlan index indepGoals)
-    probs -> Left probs
+    -> GenericInstallPlan ipkg srcpkg
+new indepGoals index = mkInstallPlan index indepGoals
 
 toList :: GenericInstallPlan ipkg srcpkg
        -> [GenericPlanPackage ipkg srcpkg]
@@ -274,12 +268,10 @@ toList = Graph.toList . planIndex
 -- the dependencies of a package or set of packages without actually
 -- installing the package itself, as when doing development.
 --
-remove :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-           HasUnitId srcpkg, PackageFixedDeps srcpkg)
+remove :: (IsUnit ipkg, IsUnit srcpkg)
        => (GenericPlanPackage ipkg srcpkg -> Bool)
        -> GenericInstallPlan ipkg srcpkg
-       -> Either [PlanProblem ipkg srcpkg]
-                 (GenericInstallPlan ipkg srcpkg)
+       -> GenericInstallPlan ipkg srcpkg
 remove shouldRemove plan =
     new (planIndepGoals plan) newIndex
   where
@@ -290,13 +282,13 @@ remove shouldRemove plan =
 -- must have exactly the same dependencies as the source one was configured
 -- with.
 --
-preexisting :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-                HasUnitId srcpkg, PackageFixedDeps srcpkg)
+preexisting :: (IsUnit ipkg,
+                IsUnit srcpkg)
             => UnitId
             -> ipkg
             -> GenericInstallPlan ipkg srcpkg
             -> GenericInstallPlan ipkg srcpkg
-preexisting pkgid ipkg plan = assert (invariant plan') plan'
+preexisting pkgid ipkg plan = plan'
   where
     plan' = plan {
       planIndex   = Graph.insert (PreExisting ipkg)
@@ -308,8 +300,7 @@ preexisting pkgid ipkg plan = assert (invariant plan') plan'
 
 -- | Lookup a package in the plan.
 --
-lookup :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-           HasUnitId srcpkg, PackageFixedDeps srcpkg)
+lookup :: (IsUnit ipkg, IsUnit srcpkg)
        => GenericInstallPlan ipkg srcpkg
        -> UnitId
        -> Maybe (GenericPlanPackage ipkg srcpkg)
@@ -340,70 +331,7 @@ revDirectDeps plan pkgid =
     Nothing   -> internalError "revDirectDeps: package not in graph"
 
 
--- ------------------------------------------------------------
--- * Checking validity of plans
--- ------------------------------------------------------------
-
--- | A valid installation plan is a set of packages that is 'acyclic',
--- 'closed' and 'consistent'. Also, every 'ConfiguredPackage' in the
--- plan has to have a valid configuration (see 'configuredPackageValid').
---
--- * if the result is @False@ use 'problems' to get a detailed list.
---
-valid :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-          HasUnitId srcpkg, PackageFixedDeps srcpkg)
-      => IndependentGoals
-      -> PlanIndex ipkg srcpkg
-      -> Bool
-valid indepGoals index =
-    null $ problems indepGoals index
-
-data PlanProblem ipkg srcpkg =
-     PackageMissingDeps   (GenericPlanPackage ipkg srcpkg)
-                          [PackageIdentifier]
-   | PackageCycle         [GenericPlanPackage ipkg srcpkg]
-   | PackageStateInvalid  (GenericPlanPackage ipkg srcpkg)
-                          (GenericPlanPackage ipkg srcpkg)
-
--- | For an invalid plan, produce a detailed list of problems as human readable
--- error messages. This is mainly intended for debugging purposes.
--- Use 'showPlanProblem' for a human readable explanation.
---
-problems :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-             HasUnitId srcpkg, PackageFixedDeps srcpkg)
-         => IndependentGoals
-         -> PlanIndex ipkg srcpkg
-         -> [PlanProblem ipkg srcpkg]
-problems _indepGoals index =
-
-     [ PackageMissingDeps pkg
-       (catMaybes
-        (map
-         (fmap packageId . flip Graph.lookup index)
-         missingDeps))
-     | (pkg, missingDeps) <- Graph.broken index ]
-
-  ++ [ PackageCycle cycleGroup
-     | cycleGroup <- Graph.cycles index ]
 
-  ++ [ PackageStateInvalid pkg pkg'
-     | pkg <- Graph.toList index
-     , Just pkg' <- map (flip Graph.lookup index)
-                    (CD.flatDeps (depends pkg))
-     , not (stateDependencyRelation pkg pkg') ]
-
-
--- | The states of packages have that depend on each other must respect
--- this relation. That is for very case where package @a@ depends on
--- package @b@ we require that @dependencyStatesOk a b = True@.
---
-stateDependencyRelation :: GenericPlanPackage ipkg srcpkg
-                        -> GenericPlanPackage ipkg srcpkg
-                        -> Bool
-stateDependencyRelation (PreExisting _) (PreExisting _) = True
-stateDependencyRelation (Configured  _) (PreExisting _) = True
-stateDependencyRelation (Configured  _) (Configured  _) = True
-stateDependencyRelation (PreExisting _) (Configured  _) = False
 
 
 
@@ -431,59 +359,66 @@ reverseDependencyClosure plan = fromMaybe []
                               . Graph.revClosure (planIndex plan)
 
 
+-- Alert alert!   Why does SolverId map to a LIST of plan packages?
+-- The sordid story has to do with 'build-depends' on a package
+-- with libraries and executables.  In an ideal world, we would
+-- ONLY depend on the library in this situation.  But c.f. #3661
+-- some people rely on the build-depends to ALSO implicitly
+-- depend on an executable.
+--
+-- I don't want to commit to a strategy yet, so the only possible
+-- thing you can do in this case is return EVERYTHING and let
+-- the client filter out what they want (executables? libraries?
+-- etc).  This similarly implies we can't return a 'ConfiguredId'
+-- because that's not enough information.
+
 fromSolverInstallPlan ::
-      (HasUnitId ipkg,   PackageFixedDeps ipkg,
-       HasUnitId srcpkg, PackageFixedDeps srcpkg)
-    -- Maybe this should be a UnitId not ConfiguredId?
-    => (   (SolverId -> ConfiguredId)
+      (IsUnit ipkg, IsUnit srcpkg)
+    => (   (SolverId -> [GenericPlanPackage ipkg srcpkg])
         -> SolverInstallPlan.SolverPlanPackage
-        -> GenericPlanPackage ipkg srcpkg)
+        -> [GenericPlanPackage ipkg srcpkg]         )
     -> SolverInstallPlan
     -> GenericInstallPlan ipkg srcpkg
 fromSolverInstallPlan f plan =
-    mkInstallPlan (Graph.fromList pkgs')
+    mkInstallPlan (Graph.fromList pkgs'')
                   (SolverInstallPlan.planIndepGoals plan)
   where
-    (_, pkgs') = foldl' f' (Map.empty, []) (SolverInstallPlan.reverseTopologicalOrder plan)
+    (_, _, pkgs'') = foldl' f' (Map.empty, Map.empty, [])
+                        (SolverInstallPlan.reverseTopologicalOrder plan)
 
-    f' (pidMap, pkgs) pkg = (pidMap', pkg' : pkgs)
+    f' (pidMap, ipiMap, pkgs) pkg = (pidMap', ipiMap', pkgs' ++ pkgs)
       where
-       pkg' = f (mapDep pidMap) pkg
-
-       pidMap'
-         = case sid of
-            PreExistingId _pid uid ->
-                assert (uid == uid') pidMap
-            PlannedId pid ->
-                Map.insert pid uid' pidMap
-         where
-           sid  = nodeKey pkg
-           uid' = nodeKey pkg'
-
-    mapDep _ (PreExistingId pid uid) = ConfiguredId pid uid
-    mapDep pidMap (PlannedId pid)
-        | Just uid <- Map.lookup pid pidMap
-        = ConfiguredId pid uid
-        -- This shouldn't happen, since mapDep should only be called
-        -- on neighbor SolverId, which must have all been done already
-        -- by the reverse top-sort (this also assumes that the graph
-        -- is not broken).
-        | otherwise
-        = error ("fromSolverInstallPlan mapDep: " ++ display pid)
+       pkgs' = f (mapDep pidMap ipiMap) pkg
+
+       (pidMap', ipiMap')
+         = case nodeKey pkg of
+            PreExistingId _ uid -> (pidMap, Map.insert uid pkgs' ipiMap)
+            PlannedId     pid   -> (Map.insert pid pkgs' pidMap, ipiMap)
+
+    mapDep _ ipiMap (PreExistingId _pid uid)
+        | Just pkgs <- Map.lookup uid ipiMap = pkgs
+        | otherwise = error ("fromSolverInstallPlan: PreExistingId " ++ display uid)
+    mapDep pidMap _ (PlannedId pid)
+        | Just pkgs <- Map.lookup pid pidMap = pkgs
+        | otherwise = error ("fromSolverInstallPlan: PlannedId " ++ display pid)
+    -- This shouldn't happen, since mapDep should only be called
+    -- on neighbor SolverId, which must have all been done already
+    -- by the reverse top-sort (we assume the graph is not broken).
 
 -- | Conversion of 'SolverInstallPlan' to 'InstallPlan'.
 -- Similar to 'elaboratedInstallPlan'
 configureInstallPlan :: SolverInstallPlan -> InstallPlan
 configureInstallPlan solverPlan =
     flip fromSolverInstallPlan solverPlan $ \mapDep planpkg ->
-      case planpkg of
+      [case planpkg of
         SolverInstallPlan.PreExisting pkg _ ->
           PreExisting pkg
 
         SolverInstallPlan.Configured  pkg ->
           Configured (configureSolverPackage mapDep pkg)
+      ]
   where
-    configureSolverPackage :: (SolverId -> ConfiguredId)
+    configureSolverPackage :: (SolverId -> [PlanPackage])
                            -> SolverPackage UnresolvedPkgLoc
                            -> ConfiguredPackage UnresolvedPkgLoc
     configureSolverPackage mapDep spkg =
@@ -504,7 +439,7 @@ configureInstallPlan solverPlan =
         confPkgDeps   = deps
       }
       where
-        deps = fmap (map mapDep) (solverPkgDeps spkg)
+        deps = fmap (concatMap (map configuredId . mapDep)) (solverPkgDeps spkg)
 
 
 -- ------------------------------------------------------------
@@ -561,8 +496,7 @@ data Processing = Processing !(Set UnitId) !(Set UnitId) !(Set UnitId)
 -- all the packages that are ready will now be processed and so we can consider
 -- them to be in the processing state.
 --
-ready :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-          HasUnitId srcpkg, PackageFixedDeps srcpkg)
+ready :: (IsUnit ipkg, IsUnit srcpkg)
       => GenericInstallPlan ipkg srcpkg
       -> ([GenericReadyPackage srcpkg], Processing)
 ready plan =
@@ -571,13 +505,13 @@ ready plan =
   where
     !processing =
       Processing
-        (Set.fromList [ installedUnitId pkg | pkg <- readyPackages ])
-        (Set.fromList [ installedUnitId pkg | PreExisting pkg <- toList plan ])
+        (Set.fromList [ nodeKey pkg | pkg <- readyPackages ])
+        (Set.fromList [ nodeKey pkg | PreExisting pkg <- toList plan ])
         Set.empty
     readyPackages =
       [ ReadyPackage pkg
       | Configured pkg <- toList plan
-      , all isPreExisting (directDeps plan (installedUnitId pkg))
+      , all isPreExisting (directDeps plan (nodeKey pkg))
       ]
 
     isPreExisting (PreExisting {}) = True
@@ -588,8 +522,7 @@ ready plan =
 -- and return any packages that are newly in the processing state (ie ready to
 -- process), along with the updated 'Processing' state.
 --
-completed :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-              HasUnitId srcpkg, PackageFixedDeps srcpkg)
+completed :: (IsUnit ipkg, IsUnit srcpkg)
           => GenericInstallPlan ipkg srcpkg
           -> Processing -> UnitId
           -> ([GenericReadyPackage srcpkg], Processing)
@@ -605,20 +538,19 @@ completed plan (Processing processingSet completedSet failedSet) pkgid =
     -- each direct reverse dep where all direct deps are completed
     newlyReady     = [ dep
                      | dep <- revDirectDeps plan pkgid
-                     , all ((`Set.member` completedSet') . installedUnitId)
-                           (directDeps plan (installedUnitId dep))
+                     , all ((`Set.member` completedSet') . nodeKey)
+                           (directDeps plan (nodeKey dep))
                      ]
 
     processingSet' = foldl' (flip Set.insert)
                             (Set.delete pkgid processingSet)
-                            (map installedUnitId newlyReady)
+                            (map nodeKey newlyReady)
     processing'    = Processing processingSet' completedSet' failedSet
 
     asReadyPackage (Configured pkg) = ReadyPackage pkg
     asReadyPackage _ = error "InstallPlan.completed: internal error"
 
-failed :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-           HasUnitId srcpkg, PackageFixedDeps srcpkg)
+failed :: (IsUnit ipkg, IsUnit srcpkg)
        => GenericInstallPlan ipkg srcpkg
        -> Processing -> UnitId
        -> ([srcpkg], Processing)
@@ -634,7 +566,7 @@ failed plan (Processing processingSet completedSet failedSet) pkgid =
   where
     processingSet' = Set.delete pkgid processingSet
     failedSet'     = failedSet `Set.union` Set.fromList newlyFailedIds
-    newlyFailedIds = map installedUnitId newlyFailed
+    newlyFailedIds = map nodeKey newlyFailed
     newlyFailed    = fromMaybe (internalError "package not in graph")
                    $ Graph.revClosure (planIndex plan) [pkgid]
     processing'    = Processing processingSet' completedSet failedSet'
@@ -642,8 +574,7 @@ failed plan (Processing processingSet completedSet failedSet) pkgid =
     asConfiguredPackage (Configured pkg) = pkg
     asConfiguredPackage _ = internalError "not in configured state"
 
-processingInvariant :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-                        HasUnitId srcpkg, PackageFixedDeps srcpkg)
+processingInvariant :: (IsUnit ipkg, IsUnit srcpkg)
                     => GenericInstallPlan ipkg srcpkg
                     -> Processing -> Bool
 processingInvariant plan (Processing processingSet completedSet failedSet) =
@@ -662,7 +593,7 @@ processingInvariant plan (Processing processingSet completedSet failedSet) =
         | pkgid <- Set.toList processingSet ++ Set.toList failedSet ]
   where
     processingClosure = Set.fromList
-                      . map installedUnitId
+                      . map nodeKey
                       . fromMaybe (internalError "processingClosure")
                       . Graph.revClosure (planIndex plan)
                       . Set.toList
@@ -683,8 +614,7 @@ processingInvariant plan (Processing processingSet completedSet failedSet) =
 -- source packages in the dependency graph, albeit not necessarily exactly the
 -- same ordering as that produced by 'reverseTopologicalOrder'.
 --
-executionOrder :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-                   HasUnitId srcpkg, PackageFixedDeps srcpkg)
+executionOrder :: (IsUnit ipkg, IsUnit srcpkg)
         => GenericInstallPlan ipkg srcpkg
         -> [GenericReadyPackage srcpkg]
 executionOrder plan =
@@ -697,7 +627,7 @@ executionOrder plan =
     waitForTasks processing p todo =
         p : tryNewTasks processing' (todo++nextpkgs)
       where
-        (nextpkgs, processing') = completed plan processing (installedUnitId p)
+        (nextpkgs, processing') = completed plan processing (nodeKey p)
 
 
 -- ------------------------------------------------------------
@@ -726,8 +656,7 @@ lookupBuildOutcome = Map.lookup . installedUnitId
 -- can be reversed to keep going and build as many packages as possible.
 --
 execute :: forall m ipkg srcpkg result failure.
-           (HasUnitId ipkg,   PackageFixedDeps ipkg,
-            HasUnitId srcpkg, PackageFixedDeps srcpkg,
+           (IsUnit ipkg, IsUnit srcpkg,
             Monad m)
         => JobControl m (UnitId, Either failure result)
         -> Bool                -- ^ Keep going after failure
@@ -765,7 +694,7 @@ execute jobCtl keepGoing depFailure plan installPkg =
       | otherwise
       = do sequence_ [ spawnJob jobCtl $ do
                          result <- installPkg pkg
-                         return (installedUnitId pkg, result)
+                         return (nodeKey pkg, result)
                      | pkg <- newpkgs ]
            waitForTasks results tasksFailed processing
 
@@ -797,5 +726,5 @@ execute jobCtl keepGoing depFailure plan installPkg =
             (depsfailed, processing') = failed plan processing pkgid
             results'   = Map.insert pkgid result results `Map.union` depResults
             depResults = Map.fromList
-                           [ (installedUnitId deppkg, Left (depFailure deppkg))
+                           [ (nodeKey deppkg, Left (depFailure deppkg))
                            | deppkg <- depsfailed ]
diff --git a/cabal-install/Distribution/Client/PackageHash.hs b/cabal-install/Distribution/Client/PackageHash.hs
index 2f56e5672e..8468cbcbff 100644
--- a/cabal-install/Distribution/Client/PackageHash.hs
+++ b/cabal-install/Distribution/Client/PackageHash.hs
@@ -43,6 +43,7 @@ import Distribution.Text
          ( display )
 import Distribution.Client.Types
          ( InstalledPackageId )
+import qualified Distribution.Solver.Types.ComponentDeps as CD
 
 import qualified Hackage.Security.Client    as Sec
 
@@ -133,6 +134,7 @@ hashedInstalledPackageIdShort pkghashinputs@PackageHashInputs{pkgHashPkgId} =
 --
 data PackageHashInputs = PackageHashInputs {
        pkgHashPkgId         :: PackageId,
+       pkgHashComponent     :: Maybe CD.Component,
        pkgHashSourceHash    :: PackageSourceHash,
        pkgHashDirectDeps    :: Set InstalledPackageId,
        pkgHashOtherConfig   :: PackageHashConfigInputs
@@ -188,6 +190,7 @@ hashPackageHashInputs = hashValue . renderPackageHashInputs
 renderPackageHashInputs :: PackageHashInputs -> LBS.ByteString
 renderPackageHashInputs PackageHashInputs{
                           pkgHashPkgId,
+                          pkgHashComponent,
                           pkgHashSourceHash,
                           pkgHashDirectDeps,
                           pkgHashOtherConfig =
@@ -209,6 +212,7 @@ renderPackageHashInputs PackageHashInputs{
     -- use the config file infrastructure so it can be read back in again.
     LBS.pack $ unlines $ catMaybes
       [ entry "pkgid"       display pkgHashPkgId
+      , mentry "component"  show pkgHashComponent
       , entry "src"         showHashValue pkgHashSourceHash
       , entry "deps"        (intercalate ", " . map display
                                               . Set.toList) pkgHashDirectDeps
@@ -239,6 +243,7 @@ renderPackageHashInputs PackageHashInputs{
       ]
   where
     entry key     format value = Just (key ++ ": " ++ format value)
+    mentry key    format value = fmap (\v -> key ++ ": " ++ format v) value
     opt   key def format value
          | value == def = Nothing
          | otherwise    = entry key format value
diff --git a/cabal-install/Distribution/Client/ProjectBuilding.hs b/cabal-install/Distribution/Client/ProjectBuilding.hs
index a74e8b5616..e1749044b8 100644
--- a/cabal-install/Distribution/Client/ProjectBuilding.hs
+++ b/cabal-install/Distribution/Client/ProjectBuilding.hs
@@ -1,12 +1,16 @@
 {-# LANGUAGE CPP, BangPatterns, RecordWildCards, NamedFieldPuns,
              DeriveGeneric, DeriveDataTypeable, GeneralizedNewtypeDeriving,
              ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 -- |
 --
 module Distribution.Client.ProjectBuilding (
     -- * Dry run phase
     BuildStatus(..),
+    buildStatusToString,
     BuildStatusMap,
     BuildStatusRebuild(..),
     BuildReason(..),
@@ -26,12 +30,13 @@ import           Distribution.Client.PackageHash (renderPackageHashInputs)
 import           Distribution.Client.RebuildMonad
 import           Distribution.Client.ProjectConfig
 import           Distribution.Client.ProjectPlanning
+import           Distribution.Client.ProjectPlanning.Types
 
 import           Distribution.Client.Types
                    hiding (BuildOutcomes, BuildOutcome,
                            BuildResult(..), BuildFailure(..))
 import           Distribution.Client.InstallPlan
-                   ( GenericInstallPlan, GenericPlanPackage )
+                   ( GenericInstallPlan, GenericPlanPackage, IsUnit )
 import qualified Distribution.Client.InstallPlan as InstallPlan
 import           Distribution.Client.DistDirLayout
 import           Distribution.Client.FileMonitor
@@ -44,10 +49,6 @@ import           Distribution.Client.Setup (filterConfigureFlags)
 import           Distribution.Client.SrcDist (allPackageSourceFiles)
 import           Distribution.Client.Utils (removeExistingFile)
 
-import qualified Distribution.Solver.Types.ComponentDeps as CD
-import           Distribution.Solver.Types.ComponentDeps (ComponentDeps)
-import           Distribution.Solver.Types.PackageFixedDeps
-
 import           Distribution.Package hiding (InstalledPackageId, installedPackageId)
 import           Distribution.InstalledPackageInfo (InstalledPackageInfo)
 import qualified Distribution.InstalledPackageInfo as Installed
@@ -64,6 +65,7 @@ import           Distribution.Version
 import           Distribution.Verbosity
 import           Distribution.Text
 import           Distribution.ParseUtils ( showPWarning )
+import           Distribution.Compat.Graph (IsNode(..))
 
 import           Data.Map (Map)
 import qualified Data.Map as Map
@@ -162,6 +164,13 @@ data BuildStatus =
      --   and it does not need to be built.
    | BuildStatusUpToDate BuildResult
 
+buildStatusToString :: BuildStatus -> String
+buildStatusToString BuildStatusPreExisting      = "BuildStatusPreExisting"
+buildStatusToString BuildStatusDownload         = "BuildStatusDownload"
+buildStatusToString (BuildStatusUnpack fp)      = "BuildStatusUnpack " ++ show fp
+buildStatusToString (BuildStatusRebuild fp _)   = "BuildStatusRebuild " ++ show fp
+buildStatusToString (BuildStatusUpToDate _)     = "BuildStatusUpToDate"
+
 -- | For a package that is going to be built or rebuilt, the state it's in now.
 --
 -- So again, this tells us why a package needs to be rebuilt and what build
@@ -229,10 +238,12 @@ buildStatusRequiresBuild _                      = True
 -- the 'ElaboratedInstallPlan' with packages switched to the
 -- 'InstallPlan.Installed' state when we find that they're already up to date.
 --
-rebuildTargetsDryRun :: DistDirLayout
+rebuildTargetsDryRun :: Verbosity
+                     -> DistDirLayout
+                     -> ElaboratedSharedConfig
                      -> ElaboratedInstallPlan
                      -> IO (ElaboratedInstallPlan, BuildStatusMap)
-rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
+rebuildTargetsDryRun verbosity distDirLayout@DistDirLayout{..} shared = \installPlan -> do
 
     -- Do the various checks to work out the 'BuildStatus' of each package
     pkgsBuildStatus <- foldMInstallPlanDepOrder installPlan dryRunPkg
@@ -241,17 +252,18 @@ rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
     -- 'InstallPlan.Installed'.
     let installPlan' = improveInstallPlanWithUpToDatePackages
                          installPlan pkgsBuildStatus
+    debugNoWrap verbosity $ InstallPlan.showInstallPlan installPlan'
 
     return (installPlan', pkgsBuildStatus)
   where
     dryRunPkg :: ElaboratedPlanPackage
-              -> ComponentDeps [BuildStatus]
+              -> [BuildStatus]
               -> IO BuildStatus
     dryRunPkg (InstallPlan.PreExisting _pkg) _depsBuildStatus =
       return BuildStatusPreExisting
 
     dryRunPkg (InstallPlan.Configured pkg) depsBuildStatus = do
-      mloc <- checkFetched (pkgSourceLocation pkg)
+      mloc <- checkFetched (pkgSourceLocation (getElaboratedPackage pkg))
       case mloc of
         Nothing -> return BuildStatusDownload
 
@@ -273,11 +285,11 @@ rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
           dryRunTarballPkg pkg depsBuildStatus tarball
 
     dryRunTarballPkg :: ElaboratedConfiguredPackage
-                     -> ComponentDeps [BuildStatus]
+                     -> [BuildStatus]
                      -> FilePath
                      -> IO BuildStatus
     dryRunTarballPkg pkg depsBuildStatus tarball =
-      case pkgBuildStyle pkg of
+      case pkgBuildStyle (getElaboratedPackage pkg) of
         BuildAndInstall  -> return (BuildStatusUnpack tarball)
         BuildInplaceOnly -> do
           -- TODO: [nice to have] use a proper file monitor rather than this dir exists test
@@ -289,7 +301,7 @@ rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
         srcdir = distUnpackedSrcDirectory (packageId pkg)
 
     dryRunLocalPkg :: ElaboratedConfiguredPackage
-                   -> ComponentDeps [BuildStatus]
+                   -> [BuildStatus]
                    -> FilePath
                    -> IO BuildStatus
     dryRunLocalPkg pkg depsBuildStatus srcdir = do
@@ -307,7 +319,7 @@ rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
             return (BuildStatusUpToDate buildResult)
       where
         packageFileMonitor =
-          newPackageFileMonitor distDirLayout (packageId pkg)
+          newPackageFileMonitor distDirLayout (elabDistDirParams shared pkg)
 
 
 -- | A specialised traversal over the packages in an install plan.
@@ -320,12 +332,10 @@ rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
 --
 foldMInstallPlanDepOrder
   :: forall m ipkg srcpkg b.
-     (Monad m,
-      HasUnitId ipkg,   PackageFixedDeps ipkg,
-      HasUnitId srcpkg, PackageFixedDeps srcpkg)
+     (Monad m, IsUnit ipkg, IsUnit srcpkg)
   => GenericInstallPlan ipkg srcpkg
   -> (GenericPlanPackage ipkg srcpkg ->
-      ComponentDeps [b] -> m b)
+      [b] -> m b)
   -> m (Map InstalledPackageId b)
 foldMInstallPlanDepOrder plan0 visit =
     go Map.empty (InstallPlan.reverseTopologicalOrder plan0)
@@ -337,13 +347,13 @@ foldMInstallPlanDepOrder plan0 visit =
 
     go !results (pkg : pkgs) = do
       -- we go in the right order so the results map has entries for all deps
-      let depresults :: ComponentDeps [b]
+      let depresults :: [b]
           depresults =
-            fmap (map (\ipkgid -> let Just result = Map.lookup ipkgid results
-                                   in result))
-                 (depends pkg)
+            map (\ipkgid -> let Just result = Map.lookup ipkgid results
+                              in result)
+                (nodeNeighbors pkg)
       result <- visit pkg depresults
-      let results' = Map.insert (installedPackageId pkg) result results
+      let results' = Map.insert (nodeKey pkg) result results
       go results' pkgs
 
 improveInstallPlanWithUpToDatePackages :: ElaboratedInstallPlan
@@ -362,12 +372,13 @@ improveInstallPlanWithUpToDatePackages installPlan pkgsBuildStatus =
   where
     replaceWithPrePreExisting =
       foldl' (\plan (ipkgid, mipkg) ->
-                case mipkg of
-                  Just ipkg -> InstallPlan.preexisting ipkgid ipkg plan
-                  -- TODO: Maybe this is a little wrong, because
-                  -- pre-installed executables show up in the
-                  -- InstallPlan as source packages.
-                  Nothing -> plan)
+                -- TODO: A grievous hack.  Better to have a special type
+                -- of entry representing pre-existing executables.
+                let stub_ipkg = Installed.emptyInstalledPackageInfo {
+                                    Installed.installedUnitId = ipkgid
+                                }
+                    ipkg = fromMaybe stub_ipkg mipkg
+                in InstallPlan.preexisting ipkgid ipkg plan)
 
 
 -----------------------------
@@ -398,22 +409,22 @@ data PackageFileMonitor = PackageFileMonitor {
 --
 type BuildResultMisc = (DocsResult, TestsResult)
 
-newPackageFileMonitor :: DistDirLayout -> PackageId -> PackageFileMonitor
-newPackageFileMonitor DistDirLayout{distPackageCacheFile} pkgid =
+newPackageFileMonitor :: DistDirLayout -> DistDirParams -> PackageFileMonitor
+newPackageFileMonitor DistDirLayout{distPackageCacheFile} dparams =
     PackageFileMonitor {
       pkgFileMonitorConfig =
-        newFileMonitor (distPackageCacheFile pkgid "config"),
+        newFileMonitor (distPackageCacheFile dparams "config"),
 
       pkgFileMonitorBuild =
         FileMonitor {
-          fileMonitorCacheFile = distPackageCacheFile pkgid "build",
+          fileMonitorCacheFile = distPackageCacheFile dparams "build",
           fileMonitorKeyValid  = \componentsToBuild componentsAlreadyBuilt ->
             componentsToBuild `Set.isSubsetOf` componentsAlreadyBuilt,
           fileMonitorCheckIfOnlyValueChanged = True
         },
 
       pkgFileMonitorReg =
-        newFileMonitor (distPackageCacheFile pkgid "registration")
+        newFileMonitor (distPackageCacheFile dparams "registration")
     }
 
 -- | Helper function for 'checkPackageFileMonitorChanged',
@@ -424,8 +435,8 @@ newPackageFileMonitor DistDirLayout{distPackageCacheFile} pkgid =
 --
 packageFileMonitorKeyValues :: ElaboratedConfiguredPackage
                             -> (ElaboratedConfiguredPackage, Set ComponentName)
-packageFileMonitorKeyValues pkg =
-    (pkgconfig, buildComponents)
+packageFileMonitorKeyValues pkg_or_comp =
+    (pkg_or_comp_config, buildComponents)
   where
     -- The first part is the value used to guard (re)configuring the package.
     -- That is, if this value changes then we will reconfigure.
@@ -434,17 +445,25 @@ packageFileMonitorKeyValues pkg =
     -- do not affect the configure step need to be nulled out. Those parts are
     -- the specific targets that we're going to build.
     --
-    pkgconfig = pkg {
-      pkgBuildTargets  = [],
-      pkgReplTarget    = Nothing,
-      pkgBuildHaddocks = False
-    }
+    pkg_or_comp_config =
+        case pkg_or_comp of
+            ElabPackage pkg    -> ElabPackage $ pkg {
+                  pkgBuildTargets  = [],
+                  pkgReplTarget    = Nothing,
+                  pkgBuildHaddocks = False
+                }
+            ElabComponent comp ->
+                ElabComponent $ comp {
+                  elabComponentBuildTargets  = [],
+                  elabComponentReplTarget    = Nothing,
+                  elabComponentBuildHaddocks = False
+                }
 
     -- The second part is the value used to guard the build step. So this is
     -- more or less the opposite of the first part, as it's just the info about
     -- what targets we're going to build.
     --
-    buildComponents = pkgBuildTargetWholeComponents pkg
+    buildComponents = pkgBuildTargetWholeComponents pkg_or_comp
 
 -- | Do all the checks on whether a package has changed and thus needs either
 -- rebuilding or reconfiguring and rebuilding.
@@ -452,7 +471,7 @@ packageFileMonitorKeyValues pkg =
 checkPackageFileMonitorChanged :: PackageFileMonitor
                                -> ElaboratedConfiguredPackage
                                -> FilePath
-                               -> ComponentDeps [BuildStatus]
+                               -> [BuildStatus]
                                -> IO (Either BuildStatusRebuild BuildResult)
 checkPackageFileMonitorChanged PackageFileMonitor{..}
                                pkg srcdir depsBuildStatus = do
@@ -469,7 +488,7 @@ checkPackageFileMonitorChanged PackageFileMonitor{..}
           -- The configChanged here includes the identity of the dependencies,
           -- so depsBuildStatus is just needed for the changes in the content
           -- of depencencies.
-        | any buildStatusRequiresBuild (CD.flatDeps depsBuildStatus) -> do
+        | any buildStatusRequiresBuild depsBuildStatus -> do
             regChanged <- checkFileMonitorChanged pkgFileMonitorReg srcdir ()
             let mreg = changedToMaybe regChanged
             return (Left (BuildStatusBuild mreg BuildReasonDepsRebuilt))
@@ -596,6 +615,15 @@ data BuildResult = BuildResult {
        buildResultDocs    :: DocsResult,
        buildResultTests   :: TestsResult,
        buildResultLogFile :: Maybe FilePath,
+       -- | If the build was for a library, this field will be @Just@;
+       -- otherwise, it will be @Nothing@.  What about internal
+       -- libraries?  This never occurs, because a build result is either
+       -- for a per-component build (in which case there won't
+       -- be multiple libraries), or a package with no internal
+       -- libraries (internal libraries with Custom setups are NOT
+       -- supported, and even if they were supported, we could
+       -- assume the Cabal library version was recent enough to
+       -- support per-component build.).
        buildResultLibInfo :: Maybe InstalledPackageInfo
      }
   deriving Show
@@ -656,8 +684,8 @@ rebuildTargets verbosity
     cacheLock     <- newLock -- serialise access to setup exe cache
                              --TODO: [code cleanup] eliminate setup exe cache
 
-    createDirectoryIfMissingVerbose verbosity False distBuildRootDirectory
-    createDirectoryIfMissingVerbose verbosity False distTempDirectory
+    createDirectoryIfMissingVerbose verbosity True distBuildRootDirectory
+    createDirectoryIfMissingVerbose verbosity True distTempDirectory
     mapM_ (createPackageDBIfMissing verbosity compiler progdb) packageDBsToUse
 
     -- Before traversing the install plan, pre-emptively find all packages that
@@ -690,7 +718,8 @@ rebuildTargets verbosity
     packageDBsToUse = -- all the package dbs we may need to create
       (Set.toList . Set.fromList)
         [ pkgdb
-        | InstallPlan.Configured pkg <- InstallPlan.toList installPlan
+        | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList installPlan
+        , let pkg = getElaboratedPackage pkg_or_comp
         , (pkgdb:_) <- map reverse [ pkgBuildPackageDBStack pkg,
                                      pkgRegisterPackageDBStack pkg,
                                      pkgSetupPackageDBStack pkg ]
@@ -726,6 +755,7 @@ rebuildTarget verbosity
       BuildStatusUpToDate    {} -> unexpectedState
   where
     unexpectedState = error "rebuildTarget: unexpected package status"
+    backing_pkg = getElaboratedPackage pkg
 
     downloadPhase = do
         downsrcloc <- annotateFailureNoLog DownloadFailed $
@@ -738,10 +768,10 @@ rebuildTarget verbosity
     unpackTarballPhase tarball =
         withTarballLocalDirectory
           verbosity distDirLayout tarball
-          (packageId pkg) (pkgBuildStyle pkg)
-          (pkgDescriptionOverride pkg) $
+          (packageId pkg) (elabDistDirParams sharedPackageConfig pkg) (pkgBuildStyle backing_pkg)
+          (pkgDescriptionOverride backing_pkg) $
 
-          case pkgBuildStyle pkg of
+          case pkgBuildStyle backing_pkg of
             BuildAndInstall  -> buildAndInstall
             BuildInplaceOnly -> buildInplace buildStatus
               where
@@ -752,11 +782,11 @@ rebuildTarget verbosity
     -- would only start from download or unpack phases.
     --
     rebuildPhase buildStatus srcdir =
-        assert (pkgBuildStyle pkg == BuildInplaceOnly) $
+        assert (pkgBuildStyle backing_pkg == BuildInplaceOnly) $
 
           buildInplace buildStatus srcdir builddir
       where
-        builddir = distBuildDirectory (packageId pkg)
+        builddir = distBuildDirectory (elabDistDirParams sharedPackageConfig pkg)
 
     buildAndInstall srcdir builddir =
         buildAndInstallUnpackedPackage
@@ -804,10 +834,11 @@ asyncDownloadPackages verbosity withRepoCtx installPlan pkgsBuildStatus body
                                                pkgsToDownload body
   where
     pkgsToDownload =
-      [ pkgSourceLocation pkg
-      | InstallPlan.Configured pkg
+      ordNub $
+      [ pkgSourceLocation (getElaboratedPackage pkg_or_comp)
+      | InstallPlan.Configured pkg_or_comp
          <- InstallPlan.reverseTopologicalOrder installPlan
-      , let ipkgid = installedPackageId pkg
+      , let ipkgid = installedPackageId pkg_or_comp
             Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus
       , BuildStatusDownload <- [pkgBuildStatus]
       ]
@@ -820,9 +851,9 @@ waitAsyncPackageDownload :: Verbosity
                          -> AsyncFetchMap
                          -> ElaboratedConfiguredPackage
                          -> IO DownloadedSourceLocation
-waitAsyncPackageDownload verbosity downloadMap pkg = do
+waitAsyncPackageDownload verbosity downloadMap pkg_or_comp = do
     pkgloc <- waitAsyncFetchPackage verbosity downloadMap
-                                    (pkgSourceLocation pkg)
+                                    (pkgSourceLocation (getElaboratedPackage pkg_or_comp))
     case downloadedSourceLocation pkgloc of
       Just loc -> return loc
       Nothing  -> fail "waitAsyncPackageDownload: unexpected source location"
@@ -849,12 +880,15 @@ withTarballLocalDirectory
   -> DistDirLayout
   -> FilePath
   -> PackageId
+  -> DistDirParams
   -> BuildStyle
   -> Maybe CabalFileText
-  -> (FilePath -> FilePath -> IO a)
+  -> (FilePath -> -- Source directory
+      FilePath -> -- Build directory
+      IO a)
   -> IO a
 withTarballLocalDirectory verbosity distDirLayout@DistDirLayout{..}
-                          tarball pkgid buildstyle pkgTextOverride
+                          tarball pkgid dparams buildstyle pkgTextOverride
                           buildPkg  =
       case buildstyle of
         -- In this case we make a temp dir, unpack the tarball to there and
@@ -874,15 +908,15 @@ withTarballLocalDirectory verbosity distDirLayout@DistDirLayout{..}
         BuildInplaceOnly -> do
           let srcrootdir = distUnpackedSrcRootDirectory
               srcdir     = distUnpackedSrcDirectory pkgid
-              builddir   = distBuildDirectory pkgid
+              builddir   = distBuildDirectory dparams
           -- TODO: [nice to have] use a proper file monitor rather than this dir exists test
           exists <- doesDirectoryExist srcdir
           unless exists $ do
-            createDirectoryIfMissingVerbose verbosity False srcrootdir
+            createDirectoryIfMissingVerbose verbosity True srcrootdir
             unpackPackageTarball verbosity tarball srcrootdir
                                  pkgid pkgTextOverride
             moveTarballShippedDistDirectory verbosity distDirLayout
-                                            srcrootdir pkgid
+                                            srcrootdir pkgid dparams
           buildPkg srcdir builddir
 
 
@@ -928,9 +962,9 @@ unpackPackageTarball verbosity tarball parentdir pkgid pkgTextOverride =
 -- system, though we'll still need to keep this hack for older packages.
 --
 moveTarballShippedDistDirectory :: Verbosity -> DistDirLayout
-                                -> FilePath -> PackageId -> IO ()
+                                -> FilePath -> PackageId -> DistDirParams -> IO ()
 moveTarballShippedDistDirectory verbosity DistDirLayout{distBuildDirectory}
-                                parentdir pkgid = do
+                                parentdir pkgid dparams = do
     distDirExists <- doesDirectoryExist tarballDistDir
     when distDirExists $ do
       debug verbosity $ "Moving '" ++ tarballDistDir ++ "' to '"
@@ -939,7 +973,7 @@ moveTarballShippedDistDirectory verbosity DistDirLayout{distBuildDirectory}
       renameDirectory tarballDistDir targetDistDir
   where
     tarballDistDir = parentdir </> display pkgid </> "dist"
-    targetDistDir  = distBuildDirectory pkgid
+    targetDistDir  = distBuildDirectory dparams
 
 
 buildAndInstallUnpackedPackage :: Verbosity
@@ -964,7 +998,7 @@ buildAndInstallUnpackedPackage verbosity
                                rpkg@(ReadyPackage pkg)
                                srcdir builddir = do
 
-    createDirectoryIfMissingVerbose verbosity False builddir
+    createDirectoryIfMissingVerbose verbosity True builddir
     initLogFile
 
     --TODO: [code cleanup] deal consistently with talking to older Setup.hs versions, much like
@@ -977,15 +1011,20 @@ buildAndInstallUnpackedPackage verbosity
     --TODO: [required feature] docs and tests
     --TODO: [required feature] sudo re-exec
 
+    let dispname = case pkg of
+            ElabPackage _ -> display pkgid
+            ElabComponent comp -> display pkgid ++ " "
+                ++ maybe "custom" display (elabComponentName comp)
+
     -- Configure phase
     when isParallelBuild $
-      notice verbosity $ "Configuring " ++ display pkgid ++ "..."
+      notice verbosity $ "Configuring " ++ dispname ++ "..."
     annotateFailure mlogFile ConfigureFailed $
-      setup configureCommand configureFlags
+      setup' configureCommand configureFlags configureArgs
 
     -- Build phase
     when isParallelBuild $
-      notice verbosity $ "Building " ++ display pkgid ++ "..."
+      notice verbosity $ "Building " ++ dispname ++ "..."
     annotateFailure mlogFile BuildFailed $
       setup buildCommand buildFlags
 
@@ -1003,7 +1042,7 @@ buildAndInstallUnpackedPackage verbosity
       setup Cabal.copyCommand copyFlags
 
       LBS.writeFile
-        (InstallDirs.prefix (pkgInstallDirs pkg) </> "cabal-hash.txt") $
+        (InstallDirs.prefix (elabInstallDirs pkg) </> "cabal-hash.txt") $
         (renderPackageHashInputs (packageHashInputs pkgshared pkg))
 
       -- here's where we could keep track of the installed files ourselves if
@@ -1014,7 +1053,7 @@ buildAndInstallUnpackedPackage verbosity
       -- then when it's done, move it to its final location, to reduce problems
       -- with installs failing half-way. Could also register and then move.
 
-      if pkgRequiresRegistration pkg
+      if elabRequiresRegistration pkg
         then do
           -- We register ourselves rather than via Setup.hs. We need to
           -- grab and modify the InstalledPackageInfo. We decide what
@@ -1025,7 +1064,7 @@ buildAndInstallUnpackedPackage verbosity
           criticalSection registerLock $
               Cabal.registerPackage verbosity compiler progdb
                                     HcPkg.MultiInstance
-                                    (pkgRegisterPackageDBStack pkg) ipkg
+                                    (pkgRegisterPackageDBStack (getElaboratedPackage pkg)) ipkg
           return (Just ipkg)
         else return Nothing
 
@@ -1050,6 +1089,7 @@ buildAndInstallUnpackedPackage verbosity
     configureFlags v = flip filterConfigureFlags v $
                        setupHsConfigureFlags rpkg pkgshared
                                              verbosity builddir
+    configureArgs    = setupHsConfigureArgs pkg
 
     buildCommand     = Cabal.buildCommand defaultProgramConfiguration
     buildFlags   _   = setupHsBuildFlags pkg pkgshared verbosity builddir
@@ -1070,13 +1110,16 @@ buildAndInstallUnpackedPackage verbosity
                                          isParallelBuild cacheLock
 
     setup :: CommandUI flags -> (Version -> flags) -> IO ()
-    setup cmd flags =
+    setup cmd flags = setup' cmd flags []
+
+    setup' :: CommandUI flags -> (Version -> flags) -> [String] -> IO ()
+    setup' cmd flags args =
       withLogging $ \mLogFileHandle ->
         setupWrapper
           verbosity
           scriptOptions { useLoggingHandle = mLogFileHandle }
-          (Just (pkgDescription pkg))
-          cmd flags []
+          (Just (pkgDescription (getElaboratedPackage pkg)))
+          cmd flags args
 
     mlogFile :: Maybe FilePath
     mlogFile =
@@ -1123,14 +1166,14 @@ buildInplaceUnpackedPackage verbosity
 
         --TODO: [code cleanup] there is duplication between the distdirlayout and the builddir here
         --      builddir is not enough, we also need the per-package cachedir
-        createDirectoryIfMissingVerbose verbosity False builddir
-        createDirectoryIfMissingVerbose verbosity False (distPackageCacheDirectory pkgid)
+        createDirectoryIfMissingVerbose verbosity True builddir
+        createDirectoryIfMissingVerbose verbosity True (distPackageCacheDirectory dparams)
 
         -- Configure phase
         --
         whenReConfigure $ do
           annotateFailureNoLog ConfigureFailed $
-            setup configureCommand configureFlags []
+            setup configureCommand configureFlags configureArgs
           invalidatePackageRegFileMonitor packageFileMonitor
           updatePackageConfigFileMonitor packageFileMonitor srcdir pkg
 
@@ -1159,7 +1202,7 @@ buildInplaceUnpackedPackage verbosity
         mipkg <- whenReRegister $
                  annotateFailureNoLog InstallFailed $ do
           -- Register locally
-          mipkg <- if pkgRequiresRegistration pkg
+          mipkg <- if elabRequiresRegistration pkg
             then do
                 ipkg0 <- generateInstalledPackageInfo
                 -- We register ourselves rather than via Setup.hs. We need to
@@ -1168,7 +1211,7 @@ buildInplaceUnpackedPackage verbosity
                 let ipkg = ipkg0 { Installed.installedUnitId = ipkgid }
                 criticalSection registerLock $
                     Cabal.registerPackage verbosity compiler progdb HcPkg.NoMultiInstance
-                                          (pkgRegisterPackageDBStack pkg)
+                                          (pkgRegisterPackageDBStack (getElaboratedPackage pkg))
                                           ipkg
                 return (Just ipkg)
 
@@ -1196,27 +1239,27 @@ buildInplaceUnpackedPackage verbosity
         }
 
   where
-    pkgid  = packageId rpkg
-    ipkgid = installedPackageId rpkg
+    ipkgid  = installedUnitId pkg
+    dparams = elabDistDirParams pkgshared pkg
 
     isParallelBuild = buildSettingNumJobs >= 2
 
-    packageFileMonitor = newPackageFileMonitor distDirLayout pkgid
+    packageFileMonitor = newPackageFileMonitor distDirLayout dparams
 
     whenReConfigure action = case buildStatus of
       BuildStatusConfigure _ -> action
       _                      -> return ()
 
     whenRebuild action
-      | null (pkgBuildTargets pkg) = return ()
+      | null (elabBuildTargets pkg) = return ()
       | otherwise                  = action
 
     whenRepl action
-      | isNothing (pkgReplTarget pkg) = return ()
+      | isNothing (elabReplTarget pkg) = return ()
       | otherwise                     = action
 
     whenHaddock action
-      | pkgBuildHaddocks pkg = action
+      | elabBuildHaddocks pkg = action
       | otherwise            = return ()
 
     whenReRegister  action = case buildStatus of
@@ -1228,6 +1271,7 @@ buildInplaceUnpackedPackage verbosity
     configureFlags v = flip filterConfigureFlags v $
                        setupHsConfigureFlags rpkg pkgshared
                                              verbosity builddir
+    configureArgs    = setupHsConfigureArgs pkg
 
     buildCommand     = Cabal.buildCommand defaultProgramConfiguration
     buildFlags   _   = setupHsBuildFlags pkg pkgshared
@@ -1251,7 +1295,7 @@ buildInplaceUnpackedPackage verbosity
     setup cmd flags args =
       setupWrapper verbosity
                    scriptOptions
-                   (Just (pkgDescription pkg))
+                   (Just (pkgDescription (getElaboratedPackage pkg)))
                    cmd flags args
 
     generateInstalledPackageInfo :: IO InstalledPackageInfo
diff --git a/cabal-install/Distribution/Client/ProjectOrchestration.hs b/cabal-install/Distribution/Client/ProjectOrchestration.hs
index ea50730a5a..857c93636a 100644
--- a/cabal-install/Distribution/Client/ProjectOrchestration.hs
+++ b/cabal-install/Distribution/Client/ProjectOrchestration.hs
@@ -58,6 +58,7 @@ module Distribution.Client.ProjectOrchestration (
 
 import           Distribution.Client.ProjectConfig
 import           Distribution.Client.ProjectPlanning
+import           Distribution.Client.ProjectPlanning.Types
 import           Distribution.Client.ProjectBuilding
 
 import           Distribution.Client.Types
@@ -79,7 +80,7 @@ import qualified Distribution.PackageDescription as PD
 import           Distribution.PackageDescription (FlagAssignment)
 import           Distribution.Simple.Setup (HaddockFlags)
 
-import           Distribution.Simple.Utils (die, notice)
+import           Distribution.Simple.Utils (die, notice, debug)
 import           Distribution.Verbosity
 import           Distribution.Text
 
@@ -183,7 +184,7 @@ runProjectPreBuildPhase
     -- This also gives us more accurate reasons for the --dry-run output.
     --
     (elaboratedPlan'', pkgsBuildStatus) <-
-      rebuildTargetsDryRun distDirLayout
+      rebuildTargetsDryRun verbosity distDirLayout elaboratedShared
                            elaboratedPlan'
 
     return ProjectBuildContext {
@@ -243,14 +244,34 @@ runProjectBuildPhase verbosity ProjectBuildContext {..} =
 -- | Adjust an 'ElaboratedInstallPlan' by selecting just those parts of it
 -- required to build the given user targets.
 --
--- How to get the 'PackageTarget's from the 'UserBuildTarget' is customisable.
+-- How to get the 'PackageTarget's from the 'UserBuildTarget' is customisable,
+-- so that we can change the meaning of @pkgname@ to target a build or
+-- repl depending on which command is calling it.
 --
-selectTargets :: PackageTarget
+-- Conceptually, every target identifies one or more roots in the
+-- 'ElaboratedInstallPlan', which we then use to determine the closure
+-- of what packages need to be built, dropping everything from
+-- 'ElaboratedInstallPlan' that is unnecessary.
+--
+-- There is a complication, however: In an ideal world, every
+-- possible target would be a node in the graph.  However, it is
+-- currently not possible (and possibly not even desirable) to invoke a
+-- Setup script to build *just* one file.  Similarly, it is not possible
+-- to invoke a pre Cabal-1.25 custom Setup script and build only one
+-- component.  In these cases, we want to build the entire package, BUT
+-- only actually building some of the files/components.  This is what
+-- 'pkgBuildTargets', 'pkgReplTarget' and 'pkgBuildHaddock' control.
+-- Arguably, these should an out-of-band mechanism rather than stored
+-- in 'ElaboratedInstallPlan', but it's what we have.  We have
+-- to fiddle around with the ElaboratedConfiguredPackage roots to say
+-- what it will build.
+--
+selectTargets :: Verbosity -> PackageTarget
               -> (ComponentTarget -> PackageTarget)
               -> [UserBuildTarget]
               -> ElaboratedInstallPlan
               -> IO ElaboratedInstallPlan
-selectTargets targetDefaultComponents targetSpecificComponent
+selectTargets verbosity targetDefaultComponents targetSpecificComponent
               userBuildTargets installPlan = do
 
     -- Match the user targets against the available targets. If no targets are
@@ -277,6 +298,7 @@ selectTargets targetDefaultComponents targetSpecificComponent
                        targetSpecificComponent
                        installPlan
                        buildTargets
+    debug verbosity ("buildTargets': " ++ show buildTargets')
 
     -- Finally, prune the install plan to cover just those target packages
     -- and their deps.
@@ -285,7 +307,8 @@ selectTargets targetDefaultComponents targetSpecificComponent
   where
     localPackages =
       [ (pkgDescription pkg, pkgSourceLocation pkg)
-      | InstallPlan.Configured pkg <- InstallPlan.toList installPlan ]
+      | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList installPlan
+      , let pkg = getElaboratedPackage pkg_or_comp ]
       --TODO: [code cleanup] is there a better way to identify local packages?
 
 
@@ -301,7 +324,8 @@ resolveAndCheckTargets targetDefaultComponents
                        installPlan targets =
     case partitionEithers (map checkTarget targets) of
       ([], targets') -> Right $ Map.fromListWith (++)
-                                  [ (ipkgid, [t]) | (ipkgid, t) <- targets' ]
+                                  [ (ipkgid, [t]) | (ipkgids, t) <- targets'
+                                                  , ipkgid <- ipkgids ]
       (problems, _)  -> Left problems
   where
     -- TODO [required eventually] currently all build targets refer to packages
@@ -342,16 +366,20 @@ resolveAndCheckTargets targetDefaultComponents
       = Left (BuildTargetNotInProject (buildTargetPackage t))
 
 
-    projAllPkgs, projLocalPkgs :: Map PackageName InstalledPackageId
+    -- NB: It's a list of 'InstalledPackageId', because each component
+    -- in the install plan from a single package needs to be associated with
+    -- the same 'PackageName'.
+    projAllPkgs, projLocalPkgs :: Map PackageName [InstalledPackageId]
     projAllPkgs =
-      Map.fromList
-        [ (packageName pkg, installedPackageId pkg)
+      Map.fromListWith (++)
+        [ (packageName pkg, [installedPackageId pkg])
         | pkg <- InstallPlan.toList installPlan ]
 
     projLocalPkgs =
-      Map.fromList
-        [ (packageName pkg, installedPackageId pkg)
-        | InstallPlan.Configured pkg <- InstallPlan.toList installPlan
+      Map.fromListWith (++)
+        [ (packageName pkg, [installedPackageId pkg_or_comp])
+        | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList installPlan
+        , let pkg = getElaboratedPackage pkg_or_comp
         , case pkgSourceLocation pkg of
             LocalUnpackedPackage _ -> True; _ -> False
           --TODO: [code cleanup] is there a better way to identify local packages?
@@ -418,18 +446,25 @@ printPlan verbosity
     wouldWill | buildSettingDryRun = "would"
               | otherwise          = "will"
 
-    showPkg pkg = display (packageId pkg)
+    showPkg (ReadyPackage (ElabPackage pkg)) = display (packageId pkg)
+    showPkg (ReadyPackage (ElabComponent comp)) =
+        display (packageId (elabComponentPackage comp)) ++
+        " (" ++ maybe "custom" display (elabComponentName comp) ++ ")"
 
     showPkgAndReason :: ElaboratedReadyPackage -> String
-    showPkgAndReason (ReadyPackage pkg) =
-      display (packageId pkg) ++
-      showTargets pkg ++
+    showPkgAndReason (ReadyPackage pkg_or_comp) =
+      display (installedUnitId pkg_or_comp) ++
+      (case pkg_or_comp of
+          ElabPackage _ -> showTargets pkg ++ showStanzas pkg
+          ElabComponent comp ->
+            " (" ++ maybe "custom" display (elabComponentName comp) ++ ")") ++
       showFlagAssignment (nonDefaultFlags pkg) ++
-      showStanzas pkg ++
-      let buildStatus = pkgsBuildStatus Map.! installedPackageId pkg in
+      let buildStatus = pkgsBuildStatus Map.! installedPackageId pkg_or_comp in
       " (" ++ showBuildStatus buildStatus ++ ")"
+     where
+      pkg = getElaboratedPackage pkg_or_comp
 
-    nonDefaultFlags :: ElaboratedConfiguredPackage -> FlagAssignment
+    nonDefaultFlags :: ElaboratedPackage -> FlagAssignment
     nonDefaultFlags pkg = pkgFlagAssignment pkg \\ pkgFlagDefaults pkg
 
     showStanzas pkg = concat
diff --git a/cabal-install/Distribution/Client/ProjectPlanOutput.hs b/cabal-install/Distribution/Client/ProjectPlanOutput.hs
index 2dc10699c2..0d5e6a141c 100644
--- a/cabal-install/Distribution/Client/ProjectPlanOutput.hs
+++ b/cabal-install/Distribution/Client/ProjectPlanOutput.hs
@@ -9,8 +9,6 @@ module Distribution.Client.ProjectPlanOutput (
   ) where
 
 import           Distribution.Client.ProjectPlanning.Types
-                   ( ElaboratedInstallPlan, ElaboratedConfiguredPackage(..)
-                   , ElaboratedSharedConfig(..) )
 import           Distribution.Client.DistDirLayout
 
 import qualified Distribution.Client.InstallPlan as InstallPlan
@@ -66,27 +64,46 @@ encodePlanAsJson elaboratedInstallPlan _elaboratedSharedConfig =
       J.object
         [ "type"       J..= J.String "pre-existing"
         , "id"         J..= jdisplay (installedUnitId ipi)
-        , "components" J..= J.object
-          [ "lib" J..= J.object [ "depends" J..= map jdisplay (installedDepends ipi) ] ]
+        , "depends" J..= map jdisplay (installedDepends ipi)
         ]
 
-    -- ecp :: ElaboratedConfiguredPackage
-    toJ (InstallPlan.Configured ecp) =
+    -- pkg :: ElaboratedPackage
+    toJ (InstallPlan.Configured (ElabPackage pkg)) =
       J.object
         [ "type"       J..= J.String "configured"
-        , "id"         J..= (jdisplay . installedUnitId) ecp
+        , "id"         J..= (jdisplay . installedUnitId) pkg
         , "components" J..= components
+        , "depends"    J..= map (jdisplay . confInstId) flat_deps
         , "flags"      J..= J.object [ fn J..= v
-                                     | (PD.FlagName fn,v) <- pkgFlagAssignment ecp ]
+                                     | (PD.FlagName fn,v) <-
+                                            pkgFlagAssignment pkg ]
         ]
       where
+        flat_deps = ordNub (ComponentDeps.flatDeps (pkgDependencies pkg))
         components = J.object
           [ comp2str c J..= J.object
             [ "depends" J..= map (jdisplay . installedUnitId) v ]
-          | (c,v) <- ComponentDeps.toList (pkgDependencies ecp) ]
+          -- NB: does NOT contain order-only dependencies
+          | (c,v) <- ComponentDeps.toList (pkgDependencies pkg) ]
+
+    -- ecp :: ElaboratedConfiguredPackage
+    toJ (InstallPlan.Configured (ElabComponent comp)) =
+      J.object
+        [ "type"       J..= J.String "configured-component"
+        , "id"         J..= (jdisplay . installedUnitId) comp
+        , "name"       J..= J.String (comp2str (elabComponent comp))
+        , "flags"      J..= J.object [ fn J..= v
+                                     | (PD.FlagName fn,v) <-
+                                            pkgFlagAssignment pkg ]
+        -- NB: does NOT contain order-only dependencies
+        , "depends"    J..= map (jdisplay . installedUnitId) (elabComponentDependencies comp)
+        ]
+      where
+        pkg = elabComponentPackage comp
 
     -- TODO: maybe move this helper to "ComponentDeps" module?
     --       Or maybe define a 'Text' instance?
+    comp2str :: ComponentDeps.Component -> String
     comp2str c = case c of
         ComponentDeps.ComponentLib     -> "lib"
         ComponentDeps.ComponentSubLib s -> "lib:"   <> s
diff --git a/cabal-install/Distribution/Client/ProjectPlanning.hs b/cabal-install/Distribution/Client/ProjectPlanning.hs
index 704c88da99..87db0e9ba6 100644
--- a/cabal-install/Distribution/Client/ProjectPlanning.hs
+++ b/cabal-install/Distribution/Client/ProjectPlanning.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, RankNTypes #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
 
 -- | Planning how to build everything in a project.
 --
@@ -31,6 +34,7 @@ module Distribution.Client.ProjectPlanning (
     -- * Setup.hs CLI flags for building
     setupHsScriptOptions,
     setupHsConfigureFlags,
+    setupHsConfigureArgs,
     setupHsBuildFlags,
     setupHsBuildArgs,
     setupHsReplFlags,
@@ -71,7 +75,6 @@ import           Distribution.Solver.Types.ComponentDeps (ComponentDeps)
 import           Distribution.Solver.Types.ConstraintSource
 import           Distribution.Solver.Types.LabeledPackageConstraint
 import           Distribution.Solver.Types.OptionalStanza
-import           Distribution.Solver.Types.PackageFixedDeps
 import           Distribution.Solver.Types.PkgConfigDb
 import           Distribution.Solver.Types.Settings
 import           Distribution.Solver.Types.SolverId
@@ -108,13 +111,12 @@ import           Distribution.Verbosity
 import           Distribution.Text
 
 import qualified Distribution.Compat.Graph as Graph
+import           Distribution.Compat.Graph(IsNode(..))
 
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import           Data.Set (Set)
 import qualified Data.Set as Set
-import qualified Data.Graph as OldGraph
-import qualified Data.Tree  as Tree
 #if !MIN_VERSION_base(4,8,0)
 import           Control.Applicative
 #endif
@@ -179,15 +181,18 @@ import           System.Directory (doesDirectoryExist)
 -- data BuildStyle =
 
 
--- | Check that an 'ElaboratedConfiguredPackage' actually makes
+-- | Check that an 'ElaboratedPackage' actually makes
 -- sense under some 'ElaboratedSharedConfig'.
-sanityCheckElaboratedConfiguredPackage :: ElaboratedSharedConfig
-                                       -> ElaboratedConfiguredPackage
-                                       -> a
-                                       -> a
-sanityCheckElaboratedConfiguredPackage sharedConfig
-                                       pkg@ElaboratedConfiguredPackage{..}
-                                       ret =
+--
+-- TODO: I guess maybe there's some 'ElaboratedComponent' sanity
+-- check one could also do
+sanityCheckElaboratedPackage :: ElaboratedSharedConfig
+                             -> ElaboratedPackage
+                             -> a
+                             -> a
+sanityCheckElaboratedPackage sharedConfig
+                             pkg@ElaboratedPackage{..}
+                             ret =
 
     -- we should only have enabled stanzas that actually can be built
     -- (according to the solver)
@@ -208,7 +213,7 @@ sanityCheckElaboratedConfiguredPackage sharedConfig
     -- the elaborated configured package
   . assert (pkgBuildStyle == BuildInplaceOnly ||
      installedPackageId pkg == hashedInstalledPackageId
-                                 (packageHashInputs sharedConfig pkg))
+                                 (packageHashInputs sharedConfig (ElabPackage pkg)))
 
     -- either a package is built inplace, or we are not attempting to
     -- build any test suites or benchmarks (we never build these
@@ -282,6 +287,7 @@ rebuildInstallPlan verbosity
            elaboratedShared) <- phaseElaboratePlan projectConfigTransient
                                                    compilerEtc
                                                    solverPlan localPackages
+
           return (elaboratedPlan, elaboratedShared, projectConfig)
 
       -- The improved plan changes each time we install something, whereas
@@ -310,8 +316,8 @@ rebuildInstallPlan verbosity
     phaseReadProjectConfig = do
       liftIO $ do
         info verbosity "Project settings changed, reconfiguring..."
-        createDirectoryIfMissingVerbose verbosity False distDirectory
-        createDirectoryIfMissingVerbose verbosity False distProjectCacheDirectory
+        createDirectoryIfMissingVerbose verbosity True distDirectory
+        createDirectoryIfMissingVerbose verbosity True distProjectCacheDirectory
 
       projectConfig <- readProjectConfig verbosity projectRootDir
 
@@ -517,25 +523,26 @@ rebuildInstallPlan verbosity
             getPackageSourceHashes verbosity withRepoCtx solverPlan
 
         defaultInstallDirs <- liftIO $ userInstallDirTemplates compiler
-        return $
-          elaborateInstallPlan
-            platform compiler progdb
-            distDirLayout
-            cabalDirLayout
-            solverPlan
-            localPackages
-            sourcePackageHashes
-            defaultInstallDirs
-            projectConfigShared
-            projectConfigLocalPackages
-            (getMapMappend projectConfigSpecificPackage)
+        let (elaboratedPlan, elaboratedShared) =
+              elaborateInstallPlan
+                platform compiler progdb
+                distDirLayout
+                cabalDirLayout
+                solverPlan
+                localPackages
+                sourcePackageHashes
+                defaultInstallDirs
+                projectConfigShared
+                projectConfigLocalPackages
+                (getMapMappend projectConfigSpecificPackage)
+        liftIO $ debugNoWrap verbosity (InstallPlan.showInstallPlan elaboratedPlan)
+        return (elaboratedPlan, elaboratedShared)
       where
         withRepoCtx = projectConfigWithSolverRepoContext verbosity
                         cabalPackageCacheDirectory
                         projectConfigShared
                         projectConfigBuildOnly
 
-
     -- Update the files we maintain that reflect our current build environment.
     -- In particular we maintain a JSON representation of the elaborated
     -- install plan.
@@ -577,6 +584,7 @@ rebuildInstallPlan verbosity
         let improvedPlan = improveInstallPlanWithPreExistingPackages
                              storePkgIndex
                              elaboratedPlan
+        liftIO $ debugNoWrap verbosity (InstallPlan.showInstallPlan improvedPlan)
         return improvedPlan
 
       where
@@ -658,7 +666,7 @@ createPackageDBIfMissing verbosity compiler progdb
                          (SpecificPackageDB dbPath) = do
     exists <- liftIO $ Cabal.doesPackageDBExist dbPath
     unless exists $ do
-      createDirectoryIfMissingVerbose verbosity False (takeDirectory dbPath)
+      createDirectoryIfMissingVerbose verbosity True (takeDirectory dbPath)
       Cabal.createPackageDB verbosity compiler progdb False dbPath
 createPackageDBIfMissing _ _ _ _ = return ()
 
@@ -1011,15 +1019,95 @@ elaborateInstallPlan platform compiler compilerprogdb
       flip InstallPlan.fromSolverInstallPlan solverPlan $ \mapDep planpkg ->
         case planpkg of
           SolverInstallPlan.PreExisting pkg _ ->
-            InstallPlan.PreExisting pkg
+            [InstallPlan.PreExisting pkg]
 
           SolverInstallPlan.Configured  pkg ->
-            InstallPlan.Configured
-              (elaborateSolverPackage mapDep pkg)
-
-    elaborateSolverPackage :: (SolverId -> ConfiguredId)
+            map InstallPlan.Configured (elaborateAndExpandSolverPackage mapDep pkg)
+
+    elaborateAndExpandSolverPackage
+        :: (SolverId -> [ElaboratedPlanPackage])
+        -> SolverPackage UnresolvedPkgLoc
+        -> [ElaboratedConfiguredPackage]
+    elaborateAndExpandSolverPackage mapDep spkg
+        | eligible
+        , Right g <- comps_graph
+        = map ElabComponent (snd (mapAccumL buildComponent Map.empty g))
+        | otherwise
+        = [ElabPackage pkg]
+      where
+        pkg   = elaborateSolverPackage mapDep spkg
+        pkgid = pkgSourceId pkg
+        pd    = pkgDescription pkg
+        eligible
+            -- TODO
+            -- At this point in time, only non-Custom setup scripts
+            -- are supported.  Implementing per-component builds with
+            -- Custom would require us to create a new 'ElabSetup'
+            -- type, and teach all of the code paths how to handle it.
+            -- Once you've implemented that, delete this guard.
+            | fromMaybe PD.Custom (PD.buildType pd) == PD.Custom
+            = False
+            -- Only non-Custom or sufficiently recent Custom
+            -- scripts can be expanded.
+            | otherwise
+            = (fromMaybe PD.Custom (PD.buildType pd) /= PD.Custom
+               -- This is when we started distributing dependencies
+               -- per component (instead of glomming them altogether
+               -- and distributing to everything.)  I didn't feel
+               -- like implementing the legacy behavior.
+               && PD.specVersion pd >= Version [1,7,1] []
+              )
+            || PD.specVersion pd >= Version [2,0,0] []
+        internalPkgSet = pkgInternalPackages pkg
+        comps_graph = Cabal.mkComponentsGraph (pkgEnabled pkg) pd internalPkgSet
+
+        buildComponent :: Map ComponentName ConfiguredId
+                     -> (Cabal.Component, [Cabal.ComponentName])
+                     -> (Map ComponentName ConfiguredId, ElaboratedComponent)
+        buildComponent internal_map (comp, cdeps) =
+            (internal_map', ecomp)
+          where
+            cname = Cabal.componentName comp
+            cname' = CD.componentNameToComponent cname
+            ecomp = ElaboratedComponent {
+                    elabComponent = cname',
+                    elabComponentName = Just cname,
+                    elabComponentId = cid,
+                    elabComponentPackage = pkg,
+                    elabComponentDependencies = deps,
+                    -- TODO: track dependencies on executables
+                    elabComponentExeDependencies = [],
+                    -- These are filled in later
+                    elabComponentBuildTargets = [],
+                    elabComponentReplTarget = Nothing,
+                    elabComponentBuildHaddocks = False
+                  }
+            cid = case pkgBuildStyle pkg of
+                    BuildInplaceOnly ->
+                      mkUnitId $
+                        display pkgid ++ "-inplace" ++
+                          (case Cabal.componentNameString cname of
+                              Nothing -> ""
+                              Just s -> "-" ++ s)
+                    BuildAndInstall ->
+                      -- TODO: change these types
+                      hashedInstalledPackageId
+                        (packageHashInputs
+                            elaboratedSharedConfig
+                            (ElabComponent ecomp)) -- knot tied
+            confid = ConfiguredId pkgid cid
+            external_deps = CD.select (== cname') (pkgDependencies pkg)
+            internal_map' = Map.insert cname confid internal_map
+            -- TODO: Custom setup dep.
+            internal_deps = [ confid'
+                            | cdep <- cdeps
+                            , Just confid' <- [Map.lookup cdep internal_map] ]
+            deps = external_deps ++ internal_deps
+
+
+    elaborateSolverPackage :: (SolverId -> [ElaboratedPlanPackage])
                            -> SolverPackage UnresolvedPkgLoc
-                           -> ElaboratedConfiguredPackage
+                           -> ElaboratedPackage
     elaborateSolverPackage
         mapDep
         pkg@(SolverPackage (SourcePackage pkgid gdesc srcloc descOverride)
@@ -1030,11 +1118,15 @@ elaborateInstallPlan platform compiler compilerprogdb
         -- pkgInstalledId, which is calculated by hashing many
         -- of the other fields of the elaboratedPackage.
         --
-        elaboratedPackage = ElaboratedConfiguredPackage {..}
+        elaboratedPackage = ElaboratedPackage {..}
 
-        deps = fmap (map elaborateSolverId) deps0
+        deps = fmap (concatMap elaborateSolverId) deps0
 
-        elaborateSolverId = mapDep
+        elaborateSolverId = map configuredId . filter is_lib . mapDep
+          where is_lib (InstallPlan.PreExisting _) = True
+                is_lib (InstallPlan.Configured (ElabPackage _)) = True
+                is_lib (InstallPlan.Configured (ElabComponent comp))
+                    = elabComponent comp == CD.ComponentLib
 
         pkgInstalledId
           | shouldBuildInplaceOnly pkg
@@ -1045,7 +1137,7 @@ elaborateInstallPlan platform compiler compilerprogdb
             hashedInstalledPackageId
               (packageHashInputs
                 elaboratedSharedConfig
-                elaboratedPackage)  -- recursive use of elaboratedPackage
+                (ElabPackage elaboratedPackage))  -- recursive use of elaboratedPackage
 
           | otherwise
           = error $ "elaborateInstallPlan: non-inplace package "
@@ -1056,15 +1148,18 @@ elaborateInstallPlan platform compiler compilerprogdb
         pkgSourceId         = pkgid
         pkgDescription      = let Right (desc, _) =
                                     PD.finalizePD
-                                    flags enabled (const True)
+                                    flags pkgEnabled (const True)
                                     platform (compilerInfo compiler)
                                     [] gdesc
                                in desc
+        pkgInternalPackages = Cabal.getInternalPackages gdesc
         pkgFlagAssignment   = flags
         pkgFlagDefaults     = [ (Cabal.flagName flag, Cabal.flagDefault flag)
                               | flag <- PD.genPackageFlags gdesc ]
         pkgDependencies     = deps
-        enabled = enableStanzas stanzas
+        -- TODO: add support for dependencies on executables
+        pkgExeDependencies  = CD.empty
+        pkgEnabled          = enableStanzas stanzas
         pkgStanzasAvailable = Set.fromList stanzas
         pkgStanzasRequested =
             -- NB: even if a package stanza is requested, if the package
@@ -1306,7 +1401,6 @@ elaborateInstallPlan platform compiler compilerprogdb
       -- + vanilla libs & exes, exe needs lib, recursive
       -- + ghci or shared lib needed by TH, recursive, ghc version dependent
 
-
 ---------------------------
 -- Build targets
 --
@@ -1319,9 +1413,9 @@ elaborateInstallPlan platform compiler compilerprogdb
 
 
 --TODO: this needs to report some user target/config errors
-elaboratePackageTargets :: ElaboratedConfiguredPackage -> [PackageTarget]
+elaboratePackageTargets :: ElaboratedPackage -> [PackageTarget]
                         -> ([ComponentTarget], Maybe ComponentTarget, Bool)
-elaboratePackageTargets ElaboratedConfiguredPackage{..} targets =
+elaboratePackageTargets ElaboratedPackage{..} targets =
     let buildTargets  = nubComponentTargets
                       . map compatSubComponentTargets
                       . concatMap elaborateBuildTarget
@@ -1389,9 +1483,11 @@ elaboratePackageTargets ElaboratedConfiguredPackage{..} targets =
         (t:_) -> [t]
         []    -> ts
 
-
 pkgHasEphemeralBuildTargets :: ElaboratedConfiguredPackage -> Bool
-pkgHasEphemeralBuildTargets pkg =
+-- TODO: Arguably ElabComponent should have its own notes about
+-- subtargets / repl targets rather than cribbing it off
+-- ElaboratedPackage.
+pkgHasEphemeralBuildTargets (getElaboratedPackage -> pkg) =
     isJust (pkgReplTarget pkg)
  || (not . null) [ () | ComponentTarget _ subtarget <- pkgBuildTargets pkg
                       , subtarget /= WholeComponent ]
@@ -1402,9 +1498,11 @@ pkgHasEphemeralBuildTargets pkg =
 --
 pkgBuildTargetWholeComponents :: ElaboratedConfiguredPackage
                               -> Set ComponentName
-pkgBuildTargetWholeComponents pkg =
+pkgBuildTargetWholeComponents (ElabPackage pkg) =
     Set.fromList
       [ cname | ComponentTarget cname WholeComponent <- pkgBuildTargets pkg ]
+pkgBuildTargetWholeComponents (ElabComponent comp) =
+    Set.fromList $ maybe [] (:[]) (elabComponentName comp)
 
 
 ------------------------------------------------------------------------------
@@ -1419,14 +1517,43 @@ pkgBuildTargetWholeComponents pkg =
 pruneInstallPlanToTargets :: Map InstalledPackageId [PackageTarget]
                           -> ElaboratedInstallPlan -> ElaboratedInstallPlan
 pruneInstallPlanToTargets perPkgTargetsMap =
-    either (\_ -> assert False undefined) id
-  . InstallPlan.new (IndependentGoals False)
+    InstallPlan.new (IndependentGoals False)
   . Graph.fromList
     -- We have to do this in two passes
   . pruneInstallPlanPass2
   . pruneInstallPlanPass1 perPkgTargetsMap
   . InstallPlan.toList
 
+-- | This is a temporary data type, where we temporarily
+-- override the graph dependencies of an 'ElaboratedPackage',
+-- so we can take a closure over them.  We'll throw out the
+-- overriden dependencies when we're done so it's strictly temporary.
+--
+-- This rigamarole is totally unnecessary for 'ElaboratedComponent',
+-- where we don't need to avoid configuring a test suite; it always
+-- is configured separately.
+data PrunedPackage
+    = PrunedPackage ElaboratedPackage [InstalledPackageId]
+    | PrunedComponent ElaboratedComponent
+
+instance Package PrunedPackage where
+    packageId (PrunedPackage pkg _) = packageId pkg
+    packageId (PrunedComponent comp) = packageId comp
+
+instance HasUnitId PrunedPackage where
+    installedUnitId = nodeKey
+
+instance IsNode PrunedPackage where
+    type Key PrunedPackage = InstalledPackageId
+    nodeKey (PrunedPackage pkg _)  = nodeKey pkg
+    nodeKey (PrunedComponent comp) = nodeKey comp
+    nodeNeighbors (PrunedPackage _ deps) = deps
+    nodeNeighbors (PrunedComponent comp) = nodeNeighbors comp
+
+fromPrunedPackage :: PrunedPackage -> ElaboratedConfiguredPackage
+fromPrunedPackage (PrunedPackage pkg _) = ElabPackage pkg
+fromPrunedPackage (PrunedComponent comp) = ElabComponent comp
+
 -- | The first pass does three things:
 --
 -- * Set the build targets based on the user targets (but not rev deps yet).
@@ -1440,22 +1567,62 @@ pruneInstallPlanPass1 :: Map InstalledPackageId [PackageTarget]
                       -> [ElaboratedPlanPackage]
                       -> [ElaboratedPlanPackage]
 pruneInstallPlanPass1 perPkgTargetsMap pkgs =
-    map fst $
-    dependencyClosure
-      (installedPackageId . fst)  -- the pkg id
-      snd                         -- the pruned deps
-      [ (pkg', pruneOptionalDependencies pkg')
-      | pkg <- pkgs
-      , let pkg' = mapConfiguredPackage
-                     (pruneOptionalStanzas . setBuildTargets) pkg
-      ]
-      (Map.keys perPkgTargetsMap)
+    map (mapConfiguredPackage fromPrunedPackage)
+        (fromMaybe [] $ Graph.closure g roots)
   where
+    pkgs' = map (mapConfiguredPackage prune) pkgs
+    g = Graph.fromList pkgs'
+
+    prune (ElabPackage pkg) =
+        let pkg' = (pruneOptionalStanzas . setPkgBuildTargets) pkg
+        in PrunedPackage pkg' (pruneOptionalDependencies pkg')
+    prune (ElabComponent comp) = PrunedComponent (setComponentBuildTargets comp)
+
+    roots = mapMaybe find_root pkgs'
+    find_root (InstallPlan.Configured (PrunedPackage pkg _)) =
+        if not (null (pkgBuildTargets pkg)
+                    && isNothing (pkgReplTarget pkg)
+                    && not (pkgBuildHaddocks pkg))
+            then Just (installedUnitId pkg)
+            else Nothing
+    find_root (InstallPlan.Configured (PrunedComponent comp)) =
+        if not (null (elabComponentBuildTargets comp)
+                    && isNothing (elabComponentReplTarget comp)
+                    && not (elabComponentBuildHaddocks comp))
+            then Just (installedUnitId comp)
+            else Nothing
+    find_root _ = Nothing
+
+    setComponentBuildTargets comp =
+        comp {
+          elabComponentBuildTargets = buildTargets',
+          elabComponentReplTarget   = replTarget',
+          elabComponentBuildHaddocks = buildHaddocks
+        }
+      where
+        -- I didn't feel like reimplementing elaboratePackageTargets,
+        -- so I just called it directly.
+        (buildTargets, replTarget, buildHaddocks)
+                = elaboratePackageTargets (elabComponentPackage comp) targets
+        -- Pare down the results for only things that are relevant
+        -- to us.  This is because were sloppy when assigning targets
+        -- to IPIDs.
+        buildTargets' = mapMaybe f buildTargets
+          where f (ComponentTarget cname sub)
+                    | Just cname == elabComponentName comp = Just sub
+                    | otherwise = Nothing
+        replTarget' = replTarget >>= \(ComponentTarget cname sub) ->
+                        if Just cname == elabComponentName comp
+                            then Just sub
+                            else Nothing
+        targets = fromMaybe []
+                $ Map.lookup (installedPackageId comp) perPkgTargetsMap
+
     -- Elaborate and set the targets we'll build for this package. This is just
     -- based on the targets from the user, not targets implied by reverse
     -- dependencies. Those comes in the second pass once we know the rev deps.
     --
-    setBuildTargets pkg =
+    setPkgBuildTargets pkg =
         pkg {
           pkgBuildTargets   = buildTargets,
           pkgReplTarget     = replTarget,
@@ -1496,18 +1663,17 @@ pruneInstallPlanPass1 perPkgTargetsMap pkgs =
     -- the optional stanzas and we'll make further tweaks to the optional
     -- stanzas in the next pass.
     --
-    pruneOptionalDependencies :: ElaboratedPlanPackage -> [InstalledPackageId]
-    pruneOptionalDependencies (InstallPlan.Configured pkg) =
-        (CD.flatDeps . CD.filterDeps keepNeeded) (depends pkg)
+    pruneOptionalDependencies :: ElaboratedPackage -> [InstalledPackageId]
+    pruneOptionalDependencies pkg =
+        -- TODO: do the right thing when this is a test-suite component itself
+        (CD.flatDeps . CD.filterDeps keepNeeded . fmap (map confInstId)) (pkgDependencies pkg)
       where
         keepNeeded (CD.ComponentTest  _) _ = TestStanzas  `Set.member` stanzas
         keepNeeded (CD.ComponentBench _) _ = BenchStanzas `Set.member` stanzas
         keepNeeded _                     _ = True
         stanzas = pkgStanzasEnabled pkg
-    pruneOptionalDependencies pkg =
-        CD.flatDeps (depends pkg)
 
-    optionalStanzasRequiredByTargets :: ElaboratedConfiguredPackage
+    optionalStanzasRequiredByTargets :: ElaboratedPackage
                                      -> Set OptionalStanza
     optionalStanzasRequiredByTargets pkg =
       Set.fromList
@@ -1517,7 +1683,7 @@ pruneInstallPlanPass1 perPkgTargetsMap pkgs =
         , stanza <- maybeToList (componentOptionalStanza cname)
         ]
 
-    optionalStanzasRequestedByDefault :: ElaboratedConfiguredPackage
+    optionalStanzasRequestedByDefault :: ElaboratedPackage
                                       -> Set OptionalStanza
     optionalStanzasRequestedByDefault =
         Map.keysSet
@@ -1536,7 +1702,7 @@ pruneInstallPlanPass1 perPkgTargetsMap pkgs =
 -- all of the deps needed for the test suite, we go ahead and
 -- enable it always.
 optionalStanzasWithDepsAvailable :: Set InstalledPackageId
-                                 -> ElaboratedConfiguredPackage
+                                 -> ElaboratedPackage
                                  -> Set OptionalStanza
 optionalStanzasWithDepsAvailable availablePkgs pkg =
     Set.fromList
@@ -1586,8 +1752,18 @@ pruneInstallPlanPass2 :: [ElaboratedPlanPackage]
 pruneInstallPlanPass2 pkgs =
     map (mapConfiguredPackage setStanzasDepsAndTargets) pkgs
   where
-    setStanzasDepsAndTargets pkg =
-        pkg {
+    setStanzasDepsAndTargets (ElabComponent comp) =
+        ElabComponent $ comp {
+          elabComponentBuildTargets = elabComponentBuildTargets comp
+                                   ++ targetsRequiredForRevDeps
+        }
+      where
+        targetsRequiredForRevDeps =
+          [ WholeComponent
+          | installedPackageId comp `Set.member` hasReverseLibDeps
+          ]
+    setStanzasDepsAndTargets (ElabPackage pkg) =
+        ElabPackage $ pkg {
           pkgStanzasEnabled = stanzas,
           pkgDependencies   = CD.filterDeps keepNeeded (pkgDependencies pkg),
           pkgBuildTargets   = pkgBuildTargets pkg ++ targetsRequiredForRevDeps
@@ -1614,15 +1790,15 @@ pruneInstallPlanPass2 pkgs =
     hasReverseLibDeps :: Set InstalledPackageId
     hasReverseLibDeps =
       Set.fromList [ depid | pkg <- pkgs
-                           , depid <- CD.flatDeps (depends pkg) ]
-
+                           , depid <- nodeNeighbors pkg ]
 
-mapConfiguredPackage :: (ElaboratedConfiguredPackage -> ElaboratedConfiguredPackage)
-                     -> ElaboratedPlanPackage
-                     -> ElaboratedPlanPackage
+mapConfiguredPackage :: (srcpkg -> srcpkg')
+                     -> InstallPlan.GenericPlanPackage ipkg srcpkg
+                     -> InstallPlan.GenericPlanPackage ipkg srcpkg'
 mapConfiguredPackage f (InstallPlan.Configured pkg) =
   InstallPlan.Configured (f pkg)
-mapConfiguredPackage _ pkg = pkg
+mapConfiguredPackage _ (InstallPlan.PreExisting pkg) =
+  InstallPlan.PreExisting pkg
 
 componentOptionalStanza :: Cabal.ComponentName -> Maybe OptionalStanza
 componentOptionalStanza (Cabal.CTestName  _) = Just TestStanzas
@@ -1630,39 +1806,6 @@ componentOptionalStanza (Cabal.CBenchName _) = Just BenchStanzas
 componentOptionalStanza _                    = Nothing
 
 
-dependencyClosure :: (pkg -> InstalledPackageId)
-                  -> (pkg -> [InstalledPackageId])
-                  -> [pkg]
-                  -> [InstalledPackageId]
-                  -> [pkg]
-dependencyClosure pkgid deps allpkgs =
-    map vertexToPkg
-  . concatMap Tree.flatten
-  . OldGraph.dfs graph
-  . map pkgidToVertex
-  where
-    (graph, vertexToPkg, pkgidToVertex) = dependencyGraph pkgid deps allpkgs
-
--- TODO: Convert this to use Distribution.Compat.Graph, via a newtype
--- which explicitly carries the accessors.
-dependencyGraph :: (pkg -> InstalledPackageId)
-                -> (pkg -> [InstalledPackageId])
-                -> [pkg]
-                -> (OldGraph.Graph,
-                    OldGraph.Vertex -> pkg,
-                    InstalledPackageId -> OldGraph.Vertex)
-dependencyGraph pkgid deps pkgs =
-    (graph, vertexToPkg', pkgidToVertex')
-  where
-    (graph, vertexToPkg, pkgidToVertex) =
-      OldGraph.graphFromEdges [ ( pkg, pkgid pkg, deps pkg )
-                           | pkg <- pkgs ]
-    vertexToPkg'   = (\(pkg,_,_) -> pkg)
-                   . vertexToPkg
-    pkgidToVertex' = fromMaybe (error "dependencyGraph: lookup failure")
-                   . pkgidToVertex
-
-
 ---------------------------
 -- Setup.hs script policy
 --
@@ -1858,7 +2001,9 @@ setupHsScriptOptions :: ElaboratedReadyPackage
                      -> Bool
                      -> Lock
                      -> SetupScriptOptions
-setupHsScriptOptions (ReadyPackage ElaboratedConfiguredPackage{..})
+-- TODO: Fix this so custom is a separate component.  Custom can ALWAYS
+-- be a separate component!!!
+setupHsScriptOptions (ReadyPackage (getElaboratedPackage -> ElaboratedPackage{..}))
                      ElaboratedSharedConfig{..} srcdir builddir
                      isParallelBuild cacheLock =
     SetupScriptOptions {
@@ -1928,20 +2073,25 @@ setupHsConfigureFlags :: ElaboratedReadyPackage
                       -> Verbosity
                       -> FilePath
                       -> Cabal.ConfigFlags
-setupHsConfigureFlags (ReadyPackage
-                         pkg@ElaboratedConfiguredPackage{..})
+setupHsConfigureFlags (ReadyPackage pkg_or_comp)
                       sharedConfig@ElaboratedSharedConfig{..}
                       verbosity builddir =
-    sanityCheckElaboratedConfiguredPackage sharedConfig pkg
+    sanityCheckElaboratedPackage sharedConfig pkg
         (Cabal.ConfigFlags {..})
   where
-    configArgs                = []
+    pkg@ElaboratedPackage{..} = getElaboratedPackage pkg_or_comp
+
+    configArgs                = mempty -- unused, passed via args
     configDistPref            = toFlag builddir
     configCabalFilePath       = mempty
     configVerbosity           = toFlag verbosity
 
-    configIPID                = toFlag (display (installedUnitId pkg))
-    configCID                 = mempty
+    configIPID                = case pkg_or_comp of
+                                  ElabPackage _ -> toFlag (display (installedUnitId pkg))
+                                  ElabComponent _ -> mempty
+    configCID                 = case pkg_or_comp of
+                                  ElabPackage _ -> mempty
+                                  ElabComponent comp -> toFlag (unitIdComponentId (elabComponentId comp))
 
     configProgramPaths        = Map.toList pkgProgramPaths
     configProgramArgs         = Map.toList pkgProgramArgs
@@ -1982,13 +2132,20 @@ setupHsConfigureFlags (ReadyPackage
     configProgPrefix          = maybe mempty toFlag pkgProgPrefix
     configProgSuffix          = maybe mempty toFlag pkgProgSuffix
 
+    -- TODO: do this per-component
     configInstallDirs         = fmap (toFlag . InstallDirs.toPathTemplate)
                                      pkgInstallDirs
 
     -- we only use configDependencies, unless we're talking to an old Cabal
     -- in which case we use configConstraints
+    -- NB: This does NOT use nodeNeighbors, which includes executable
+    -- dependencies which should NOT be fed in here
     configDependencies        = [ (packageName srcid, uid)
-                                | ConfiguredId srcid uid <- CD.nonSetupDeps pkgDependencies ]
+                                | ConfiguredId srcid uid <-
+                                    case pkg_or_comp of
+                                        ElabPackage _ -> CD.nonSetupDeps pkgDependencies
+                                        ElabComponent comp -> elabComponentDependencies comp ]
+    -- TODO: don't need to provide these when pkgComponent is Just
     configConstraints         = [ thisPackageVersion srcid
                                 | ConfiguredId srcid _uid <- CD.nonSetupDeps pkgDependencies ]
 
@@ -1996,8 +2153,12 @@ setupHsConfigureFlags (ReadyPackage
     -- TODO: [required eventually] have to do this differently for older Cabal versions
     configPackageDBs          = Nothing : map Just pkgBuildPackageDBStack
 
-    configTests               = toFlag (TestStanzas  `Set.member` pkgStanzasEnabled)
-    configBenchmarks          = toFlag (BenchStanzas `Set.member` pkgStanzasEnabled)
+    configTests               = case pkg_or_comp of
+                                    ElabPackage _ -> toFlag (TestStanzas  `Set.member` pkgStanzasEnabled)
+                                    ElabComponent _ -> mempty
+    configBenchmarks          = case pkg_or_comp of
+                                    ElabPackage _ -> toFlag (BenchStanzas `Set.member` pkgStanzasEnabled)
+                                    ElabComponent _ -> mempty
 
     configExactConfiguration  = toFlag True
     configFlagError           = mempty --TODO: [research required] appears not to be implemented
@@ -2007,12 +2168,22 @@ setupHsConfigureFlags (ReadyPackage
     configPrograms_           = mempty -- never use, shouldn't exist
 
 
+setupHsConfigureArgs :: ElaboratedConfiguredPackage
+                     -> [String]
+setupHsConfigureArgs (ElabPackage _pkg) = []
+setupHsConfigureArgs (ElabComponent comp) =
+    [showComponentTarget pkg (ComponentTarget cname WholeComponent)]
+  where
+    pkg = elabComponentPackage comp
+    cname = fromMaybe (error "setupHsConfigureArgs: trying to configure setup")
+                      (elabComponentName comp)
+
 setupHsBuildFlags :: ElaboratedConfiguredPackage
                   -> ElaboratedSharedConfig
                   -> Verbosity
                   -> FilePath
                   -> Cabal.BuildFlags
-setupHsBuildFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+setupHsBuildFlags _ _ verbosity builddir =
     Cabal.BuildFlags {
       buildProgramPaths = mempty, --unused, set at configure time
       buildProgramArgs  = mempty, --unused, set at configure time
@@ -2025,11 +2196,11 @@ setupHsBuildFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
 
 
 setupHsBuildArgs :: ElaboratedConfiguredPackage -> [String]
-setupHsBuildArgs pkg =
-    map (showComponentTarget pkg) (pkgBuildTargets pkg)
+setupHsBuildArgs (ElabPackage pkg) = map (showComponentTarget pkg) (pkgBuildTargets pkg)
+setupHsBuildArgs (ElabComponent _comp) = []
 
 
-showComponentTarget :: ElaboratedConfiguredPackage -> ComponentTarget -> String
+showComponentTarget :: ElaboratedPackage -> ComponentTarget -> String
 showComponentTarget pkg =
     showBuildTarget . toBuildTarget
   where
@@ -2052,7 +2223,7 @@ setupHsReplFlags :: ElaboratedConfiguredPackage
                  -> Verbosity
                  -> FilePath
                  -> Cabal.ReplFlags
-setupHsReplFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+setupHsReplFlags _ _ verbosity builddir =
     Cabal.ReplFlags {
       replProgramPaths = mempty, --unused, set at configure time
       replProgramArgs  = mempty, --unused, set at configure time
@@ -2063,9 +2234,11 @@ setupHsReplFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
 
 
 setupHsReplArgs :: ElaboratedConfiguredPackage -> [String]
-setupHsReplArgs pkg =
+setupHsReplArgs (ElabPackage pkg) =
     maybe [] (\t -> [showComponentTarget pkg t]) (pkgReplTarget pkg)
     --TODO: should be able to give multiple modules in one component
+setupHsReplArgs (ElabComponent _comp) =
+    error "setupHsReplArgs: didn't implement me yet"
 
 
 setupHsCopyFlags :: ElaboratedConfiguredPackage
@@ -2091,13 +2264,13 @@ setupHsRegisterFlags :: ElaboratedConfiguredPackage
                      -> FilePath
                      -> FilePath
                      -> Cabal.RegisterFlags
-setupHsRegisterFlags ElaboratedConfiguredPackage {pkgBuildStyle} _
+setupHsRegisterFlags pkg_or_comp _
                      verbosity builddir pkgConfFile =
     Cabal.RegisterFlags {
       regPackageDB   = mempty,  -- misfeature
       regGenScript   = mempty,  -- never use
       regGenPkgConf  = toFlag (Just pkgConfFile),
-      regInPlace     = case pkgBuildStyle of
+      regInPlace     = case pkgBuildStyle (getElaboratedPackage pkg_or_comp) of
                          BuildInplaceOnly -> toFlag True
                          _                -> toFlag False,
       regPrintId     = mempty,  -- never use
@@ -2113,7 +2286,9 @@ setupHsHaddockFlags :: ElaboratedConfiguredPackage
                     -> Verbosity
                     -> FilePath
                     -> Cabal.HaddockFlags
-setupHsHaddockFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+-- TODO: reconsider whether or not Executables/TestSuites/...
+-- needed for component
+setupHsHaddockFlags (getElaboratedPackage -> ElaboratedPackage{..}) _ verbosity builddir =
     Cabal.HaddockFlags {
       haddockProgramPaths  = mempty, --unused, set at configure time
       haddockProgramArgs   = mempty, --unused, set at configure time
@@ -2192,17 +2367,20 @@ packageHashInputs :: ElaboratedSharedConfig
                   -> PackageHashInputs
 packageHashInputs
     pkgshared
-    pkg@ElaboratedConfiguredPackage{
+    (ElabPackage pkg@ElaboratedPackage{
       pkgSourceId,
       pkgSourceHash = Just srchash,
-      pkgDependencies
-    } =
+      pkgDependencies,
+      pkgExeDependencies
+    }) =
     PackageHashInputs {
       pkgHashPkgId       = pkgSourceId,
+      pkgHashComponent   = Nothing,
       pkgHashSourceHash  = srchash,
-      pkgHashDirectDeps  = Set.fromList
-                             [ installedPackageId dep
-                             | dep <- CD.select relevantDeps pkgDependencies ],
+      pkgHashDirectDeps  = Set.fromList $
+                         [ installedPackageId dep
+                         | dep <- CD.select relevantDeps pkgDependencies ] ++
+                         CD.select relevantDeps pkgExeDependencies,
       pkgHashOtherConfig = packageHashConfigInputs pkgshared pkg
     }
   where
@@ -2217,16 +2395,29 @@ packageHashInputs
     relevantDeps (CD.ComponentTest  _) = False
     relevantDeps (CD.ComponentBench _) = False
 
+packageHashInputs
+    pkgshared
+    (ElabComponent comp@ElaboratedComponent {
+        elabComponentPackage = pkg@ElaboratedPackage{ pkgSourceHash = Just srchash }
+    }) =
+    PackageHashInputs {
+      pkgHashPkgId       = packageId comp,
+      pkgHashComponent   = Just (elabComponent comp),
+      pkgHashSourceHash  = srchash,
+      pkgHashDirectDeps  = Set.fromList (nodeNeighbors comp),
+      pkgHashOtherConfig = packageHashConfigInputs pkgshared pkg
+    }
+
 packageHashInputs _ pkg =
     error $ "packageHashInputs: only for packages with source hashes. "
          ++ display (packageId pkg)
 
 packageHashConfigInputs :: ElaboratedSharedConfig
-                        -> ElaboratedConfiguredPackage
+                        -> ElaboratedPackage
                         -> PackageHashConfigInputs
 packageHashConfigInputs
     ElaboratedSharedConfig{..}
-    ElaboratedConfiguredPackage{..} =
+    ElaboratedPackage{..} =
 
     PackageHashConfigInputs {
       pkgHashCompilerId          = compilerId pkgConfigCompiler,
diff --git a/cabal-install/Distribution/Client/ProjectPlanning/Types.hs b/cabal-install/Distribution/Client/ProjectPlanning/Types.hs
index afcefde732..76b60ffe75 100644
--- a/cabal-install/Distribution/Client/ProjectPlanning/Types.hs
+++ b/cabal-install/Distribution/Client/ProjectPlanning/Types.hs
@@ -1,4 +1,6 @@
-{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
 
 -- | Types used while planning how to build everything in a project.
 --
@@ -10,6 +12,17 @@ module Distribution.Client.ProjectPlanning.Types (
     -- * Elaborated install plan types
     ElaboratedInstallPlan,
     ElaboratedConfiguredPackage(..),
+
+    getElaboratedPackage,
+    elabInstallDirs,
+    elabDistDirParams,
+    elabRequiresRegistration,
+    elabBuildTargets,
+    elabReplTarget,
+    elabBuildHaddocks,
+
+    ElaboratedComponent(..),
+    ElaboratedPackage(..),
     ElaboratedPlanPackage,
     ElaboratedSharedConfig(..),
     ElaboratedReadyPackage,
@@ -32,7 +45,9 @@ import           Distribution.Client.InstallPlan
                    ( GenericInstallPlan, GenericPlanPackage )
 import           Distribution.Client.SolverInstallPlan
                    ( SolverInstallPlan )
+import           Distribution.Client.DistDirLayout
 
+import           Distribution.Types.ComponentEnabledSpec
 import           Distribution.Package
                    hiding (InstalledPackageId, installedPackageId)
 import           Distribution.System
@@ -46,9 +61,10 @@ import qualified Distribution.Simple.InstallDirs as InstallDirs
 import           Distribution.Simple.InstallDirs (PathTemplate)
 import           Distribution.Version
 
+import qualified Distribution.Solver.Types.ComponentDeps as CD
 import           Distribution.Solver.Types.ComponentDeps (ComponentDeps)
 import           Distribution.Solver.Types.OptionalStanza
-import           Distribution.Solver.Types.PackageFixedDeps
+import           Distribution.Compat.Graph (IsNode(..))
 
 import           Data.Map (Map)
 import           Data.Set (Set)
@@ -91,16 +107,151 @@ data ElaboratedSharedConfig
 
 instance Binary ElaboratedSharedConfig
 
+-- TODO: This is a misnomer, but I didn't want to rename things
+-- willy-nilly yet
 data ElaboratedConfiguredPackage
-   = ElaboratedConfiguredPackage {
+    = ElabPackage   ElaboratedPackage
+    | ElabComponent ElaboratedComponent
+  deriving (Eq, Show, Generic)
+
+instance IsNode ElaboratedConfiguredPackage where
+    type Key ElaboratedConfiguredPackage = UnitId
+    nodeKey (ElabPackage pkg) = nodeKey pkg
+    nodeKey (ElabComponent comp) = nodeKey comp
+    nodeNeighbors (ElabPackage pkg) = nodeNeighbors pkg
+    nodeNeighbors (ElabComponent comp) = nodeNeighbors comp
+
+elabDistDirParams :: ElaboratedSharedConfig -> ElaboratedConfiguredPackage -> DistDirParams
+elabDistDirParams shared (ElabPackage pkg) = DistDirParams {
+        distParamUnitId = pkgInstalledId pkg,
+        distParamPackageId = pkgSourceId pkg,
+        distParamComponentName = Nothing,
+        distParamCompilerId = compilerId (pkgConfigCompiler shared),
+        distParamPlatform = pkgConfigPlatform shared
+    }
+elabDistDirParams shared (ElabComponent comp) = DistDirParams {
+        distParamUnitId = elabComponentId comp,
+        distParamPackageId = packageId comp, -- NB: NOT the munged ID
+        distParamComponentName = elabComponentName comp, -- TODO: Ick. Change type.
+        distParamCompilerId = compilerId (pkgConfigCompiler shared),
+        distParamPlatform = pkgConfigPlatform shared
+    }
+
+-- TODO: give each component a separate install dir prefix
+elabInstallDirs :: ElaboratedConfiguredPackage -> InstallDirs.InstallDirs FilePath
+elabInstallDirs = pkgInstallDirs . getElaboratedPackage
+
+elabRequiresRegistration :: ElaboratedConfiguredPackage -> Bool
+elabRequiresRegistration (ElabPackage pkg) = pkgRequiresRegistration pkg
+elabRequiresRegistration (ElabComponent comp)
+    = case elabComponent comp of
+        CD.ComponentLib -> True
+        CD.ComponentSubLib _ -> True
+        _ -> False
+
+elabBuildTargets :: ElaboratedConfiguredPackage -> [ComponentTarget]
+elabBuildTargets (ElabPackage pkg) = pkgBuildTargets pkg
+elabBuildTargets (ElabComponent comp)
+    | Just cname <- elabComponentName comp
+    = map (ComponentTarget cname) $ elabComponentBuildTargets comp
+    | otherwise = []
+
+elabReplTarget :: ElaboratedConfiguredPackage -> Maybe ComponentTarget
+elabReplTarget (ElabPackage pkg) = pkgReplTarget pkg
+elabReplTarget (ElabComponent comp)
+    | Just cname <- elabComponentName comp
+    = fmap (ComponentTarget cname) $ elabComponentReplTarget comp
+    | otherwise = Nothing
+
+elabBuildHaddocks :: ElaboratedConfiguredPackage -> Bool
+elabBuildHaddocks (ElabPackage pkg) = pkgBuildHaddocks pkg
+elabBuildHaddocks (ElabComponent comp) = elabComponentBuildHaddocks comp
+
+getElaboratedPackage :: ElaboratedConfiguredPackage -> ElaboratedPackage
+getElaboratedPackage (ElabPackage pkg) = pkg
+getElaboratedPackage (ElabComponent comp) = elabComponentPackage comp
+
+instance Binary ElaboratedConfiguredPackage
+
+instance Package ElaboratedConfiguredPackage where
+  packageId (ElabPackage pkg) = packageId pkg
+  packageId (ElabComponent comp) = packageId comp
+
+instance HasUnitId ElaboratedConfiguredPackage where
+  installedUnitId (ElabPackage pkg) = installedUnitId pkg
+  installedUnitId (ElabComponent comp) = installedUnitId comp
+
+instance HasConfiguredId ElaboratedConfiguredPackage where
+  configuredId (ElabPackage pkg)    = configuredId pkg
+  configuredId (ElabComponent comp) = configuredId comp
+
+-- | Some extra metadata associated with an
+-- 'ElaboratedConfiguredPackage' which indicates that the "package"
+-- in question is actually a single component to be built.  Arguably
+-- it would be clearer if there were an ADT which branched into
+-- package work items and component work items, but I've structured
+-- it this way to minimize change to the existing code (which I
+-- don't feel qualified to rewrite.)
+data ElaboratedComponent
+   = ElaboratedComponent {
+    -- | The name of the component to be built
+    elabComponent :: CD.Component,
+    -- | The name of the component to be built.  Nothing if
+    -- it's a setup dep.
+    elabComponentName :: Maybe ComponentName,
+    -- | The ID of the component to be built
+    elabComponentId :: UnitId,
+    -- | Dependencies of this component
+    elabComponentDependencies :: [ConfiguredId],
+    -- | The order-only dependencies of this component; e.g.,
+    -- if you depend on an executable it goes here.
+    elabComponentExeDependencies :: [UnitId],
+    -- | The 'ElaboratedPackage' this component came from
+    elabComponentPackage :: ElaboratedPackage,
+    -- | What in this component should we build (TRANSIENT, see 'pkgBuildTargets')
+    elabComponentBuildTargets :: [SubComponentTarget],
+    -- | Should we REPL this component (TRANSIENT, see 'pkgReplTarget')
+    elabComponentReplTarget :: Maybe SubComponentTarget,
+    -- | Should we Haddock this component (TRANSIENT, see 'pkgBuildHaddocks')
+    elabComponentBuildHaddocks :: Bool
+    -- NB: Careful, if you add elabComponentInstallDirs, need
+    -- to adjust 'packageHashInputs'!!!
+   }
+  deriving (Eq, Show, Generic)
+
+instance Binary ElaboratedComponent
+
+instance Package ElaboratedComponent where
+    -- NB: DON'T return the munged ID by default.
+    -- The 'Package' type class is about the source package
+    -- name that the component belongs to; 'projAllPkgs'
+    -- in "Distribution.Client.ProjectOrchestration" depends
+    -- on this.
+    packageId = packageId . elabComponentPackage
+
+instance HasConfiguredId ElaboratedComponent where
+    configuredId comp = ConfiguredId (packageId comp) (installedUnitId comp)
+
+instance HasUnitId ElaboratedComponent where
+    installedUnitId = elabComponentId
+
+instance IsNode ElaboratedComponent where
+    type Key ElaboratedComponent = UnitId
+    nodeKey = elabComponentId
+    nodeNeighbors comp =
+           map installedUnitId (elabComponentDependencies comp)
+        ++ elabComponentExeDependencies comp
+
+data ElaboratedPackage
+   = ElaboratedPackage {
 
        pkgInstalledId :: InstalledPackageId,
        pkgSourceId    :: PackageId,
 
-       -- | TODO: [code cleanup] we don't need this, just a few bits from it:
-       --   build type, spec version
        pkgDescription :: Cabal.PackageDescription,
 
+       pkgInternalPackages :: Map PackageName ComponentName,
+
        -- | A total flag assignment for the package
        pkgFlagAssignment   :: Cabal.FlagAssignment,
 
@@ -111,6 +262,13 @@ data ElaboratedConfiguredPackage
        --
        pkgDependencies     :: ComponentDeps [ConfiguredId],
 
+       -- | The executable dependencies, which we don't pass as @--dependency@ flags;
+       -- these just need to be added to the path.
+       pkgExeDependencies :: ComponentDeps [UnitId],
+
+       -- | Another way of phrasing 'pkgStanzasAvailable'.
+       pkgEnabled          :: ComponentEnabledSpec,
+
        -- | Which optional stanzas (ie testsuites, benchmarks) can be built.
        -- This means the solver produced a plan that has them available.
        -- This doesn't necessary mean we build them by default.
@@ -226,16 +384,22 @@ data ElaboratedConfiguredPackage
      }
   deriving (Eq, Show, Generic)
 
-instance Binary ElaboratedConfiguredPackage
+instance Binary ElaboratedPackage
 
-instance Package ElaboratedConfiguredPackage where
+instance Package ElaboratedPackage where
   packageId = pkgSourceId
 
-instance HasUnitId ElaboratedConfiguredPackage where
+instance HasUnitId ElaboratedPackage where
   installedUnitId = pkgInstalledId
 
-instance PackageFixedDeps ElaboratedConfiguredPackage where
-  depends = fmap (map installedPackageId) . pkgDependencies
+instance HasConfiguredId ElaboratedPackage where
+  configuredId pkg = ConfiguredId (pkgSourceId pkg) (pkgInstalledId pkg)
+
+instance IsNode ElaboratedPackage where
+  type Key ElaboratedPackage = UnitId
+  nodeKey = pkgInstalledId
+  nodeNeighbors pkg = map installedUnitId (CD.flatDeps (pkgDependencies pkg))
+                   ++ CD.flatDeps (pkgExeDependencies pkg)
 
 -- | This is used in the install plan to indicate how the package will be
 -- built.
diff --git a/cabal-install/Distribution/Client/Types.hs b/cabal-install/Distribution/Client/Types.hs
index 1b3a08890a..990e6f53f2 100644
--- a/cabal-install/Distribution/Client/Types.hs
+++ b/cabal-install/Distribution/Client/Types.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -----------------------------------------------------------------------------
 -- |
@@ -21,7 +22,7 @@ module Distribution.Client.Types where
 
 import Distribution.Package
          ( PackageName, PackageId, Package(..)
-         , UnitId(..), HasUnitId(..) )
+         , UnitId(..), HasUnitId(..), PackageInstalled(..) )
 import Distribution.InstalledPackageInfo
          ( InstalledPackageInfo )
 import Distribution.PackageDescription
@@ -31,11 +32,13 @@ import Distribution.Version
 
 import Distribution.Solver.Types.PackageIndex
          ( PackageIndex )
+import qualified Distribution.Solver.Types.ComponentDeps as CD
 import Distribution.Solver.Types.ComponentDeps
          ( ComponentDeps )
 import Distribution.Solver.Types.OptionalStanza
 import Distribution.Solver.Types.PackageFixedDeps
 import Distribution.Solver.Types.SourcePackage
+import Distribution.Compat.Graph (IsNode(..))
 
 import Data.Map (Map)
 import Network.URI (URI(..), URIAuth(..), nullURI)
@@ -98,8 +101,23 @@ data ConfiguredPackage loc = ConfiguredPackage {
     }
   deriving (Eq, Show, Generic)
 
+-- | 'HasConfiguredId' indicates data types which have a 'ConfiguredId'.
+-- This type class is mostly used to conveniently finesse between
+-- 'ElaboratedPackage' and 'ElaboratedComponent'.
+--
+instance HasConfiguredId (ConfiguredPackage loc) where
+    configuredId pkg = ConfiguredId (packageId pkg) (confPkgId pkg)
+
+instance IsNode (ConfiguredPackage loc) where
+    type Key (ConfiguredPackage loc) = UnitId
+    nodeKey       = confPkgId
+    -- TODO: if we update ConfiguredPackage to support order-only
+    -- dependencies, need to include those here
+    nodeNeighbors = map confInstId . CD.flatDeps . confPkgDeps
+
 instance (Binary loc) => Binary (ConfiguredPackage loc)
 
+
 -- | A ConfiguredId is a package ID for a configured package.
 --
 -- Once we configure a source package we know it's UnitId. It is still
@@ -115,7 +133,7 @@ data ConfiguredId = ConfiguredId {
     confSrcId  :: PackageId
   , confInstId :: UnitId
   }
-  deriving (Eq, Generic)
+  deriving (Eq, Ord, Generic)
 
 instance Binary ConfiguredId
 
@@ -131,16 +149,28 @@ instance HasUnitId ConfiguredId where
 instance Package (ConfiguredPackage loc) where
   packageId cpkg = packageId (confPkgSource cpkg)
 
-instance PackageFixedDeps (ConfiguredPackage loc) where
-  depends cpkg = fmap (map installedUnitId) (confPkgDeps cpkg)
+instance PackageInstalled (ConfiguredPackage loc) where
+  installedDepends = CD.flatDeps . fmap (map installedUnitId) . confPkgDeps
 
 instance HasUnitId (ConfiguredPackage loc) where
   installedUnitId = confPkgId
 
+class HasConfiguredId a where
+    configuredId :: a -> ConfiguredId
+
+instance HasConfiguredId InstalledPackageInfo where
+    configuredId ipkg = ConfiguredId (packageId ipkg) (installedUnitId ipkg)
+
 -- | Like 'ConfiguredPackage', but with all dependencies guaranteed to be
 -- installed already, hence itself ready to be installed.
 newtype GenericReadyPackage srcpkg = ReadyPackage srcpkg -- see 'ConfiguredPackage'.
-  deriving (Eq, Show, Generic, Package, PackageFixedDeps, HasUnitId, Binary)
+  deriving (Eq, Show, Generic, Package, PackageFixedDeps, HasUnitId, PackageInstalled, Binary)
+
+-- Can't newtype derive this
+instance IsNode srcpkg => IsNode (GenericReadyPackage srcpkg) where
+    type Key (GenericReadyPackage srcpkg) = Key srcpkg
+    nodeKey (ReadyPackage spkg) = nodeKey spkg
+    nodeNeighbors (ReadyPackage spkg) = nodeNeighbors spkg
 
 type ReadyPackage = GenericReadyPackage (ConfiguredPackage UnresolvedPkgLoc)
 
@@ -287,6 +317,10 @@ data BuildFailure = PlanningFailed
 
 instance Exception BuildFailure
 
+-- Note that the @Maybe InstalledPackageInfo@ is a slight hack: we only
+-- the public library's 'InstalledPackageInfo' is stored here, even if
+-- there were 'InstalledPackageInfo' from internal libraries.  This
+-- 'InstalledPackageInfo' is not used anyway, so it makes no difference.
 data BuildResult = BuildResult DocsResult TestsResult
                                (Maybe InstalledPackageInfo)
   deriving (Show, Generic)
diff --git a/cabal-install/Distribution/Solver/Types/ComponentDeps.hs b/cabal-install/Distribution/Solver/Types/ComponentDeps.hs
index 94781b8d0e..6c36cc6083 100644
--- a/cabal-install/Distribution/Solver/Types/ComponentDeps.hs
+++ b/cabal-install/Distribution/Solver/Types/ComponentDeps.hs
@@ -14,6 +14,7 @@
 module Distribution.Solver.Types.ComponentDeps (
     -- * Fine-grained package dependencies
     Component(..)
+  , componentNameToComponent
   , ComponentDep
   , ComponentDeps -- opaque
     -- ** Constructing ComponentDeps
@@ -41,6 +42,8 @@ import Distribution.Compat.Semigroup (Semigroup((<>)))
 import GHC.Generics
 import Data.Foldable (fold)
 
+import qualified Distribution.Types.ComponentName as CN
+
 #if !MIN_VERSION_base(4,8,0)
 import Data.Foldable (Foldable(foldMap))
 import Data.Monoid (Monoid(..))
@@ -90,6 +93,13 @@ instance Traversable ComponentDeps where
 
 instance Binary a => Binary (ComponentDeps a)
 
+componentNameToComponent :: CN.ComponentName -> Component
+componentNameToComponent (CN.CLibName)      = ComponentLib
+componentNameToComponent (CN.CSubLibName s) = ComponentSubLib s
+componentNameToComponent (CN.CExeName s)    = ComponentExe s
+componentNameToComponent (CN.CTestName s)   = ComponentTest s
+componentNameToComponent (CN.CBenchName s)  = ComponentBench s
+
 {-------------------------------------------------------------------------------
   Construction
 -------------------------------------------------------------------------------}
diff --git a/cabal-install/cabal-install.cabal b/cabal-install/cabal-install.cabal
index 626d6c3f79..a157308386 100644
--- a/cabal-install/cabal-install.cabal
+++ b/cabal-install/cabal-install.cabal
@@ -88,6 +88,11 @@ Extra-Source-Files:
   tests/IntegrationTests/new-build/T3460/sub-package-B/B.hs
   tests/IntegrationTests/new-build/T3460/sub-package-B/Setup.hs
   tests/IntegrationTests/new-build/T3460/sub-package-B/sub-package-B.cabal
+  tests/IntegrationTests/new-build/executable/Main.hs
+  tests/IntegrationTests/new-build/executable/Setup.hs
+  tests/IntegrationTests/new-build/executable/Test.hs
+  tests/IntegrationTests/new-build/executable/a.cabal
+  tests/IntegrationTests/new-build/executable/cabal.project
   tests/IntegrationTests/new-build/monitor_cabal_files.sh
   tests/IntegrationTests/new-build/monitor_cabal_files/cabal.project
   tests/IntegrationTests/new-build/monitor_cabal_files/p/P.hs
diff --git a/cabal-install/tests/IntegrationTests/internal-libs/new_build.sh b/cabal-install/tests/IntegrationTests/internal-libs/new_build.sh
index 959c79d079..18f708913a 100644
--- a/cabal-install/tests/IntegrationTests/internal-libs/new_build.sh
+++ b/cabal-install/tests/IntegrationTests/internal-libs/new_build.sh
@@ -1,4 +1,3 @@
 . ./common.sh
 
-cabal new-build p || exit 0
-exit 1 # expect broken
+cabal new-build p
diff --git a/cabal-install/tests/IntegrationTests/new-build/executable/Main.hs b/cabal-install/tests/IntegrationTests/new-build/executable/Main.hs
new file mode 100644
index 0000000000..b3549c2fe3
--- /dev/null
+++ b/cabal-install/tests/IntegrationTests/new-build/executable/Main.hs
@@ -0,0 +1 @@
+main = return ()
diff --git a/cabal-install/tests/IntegrationTests/new-build/executable/Setup.hs b/cabal-install/tests/IntegrationTests/new-build/executable/Setup.hs
new file mode 100644
index 0000000000..9a994af677
--- /dev/null
+++ b/cabal-install/tests/IntegrationTests/new-build/executable/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cabal-install/tests/IntegrationTests/new-build/executable/Test.hs b/cabal-install/tests/IntegrationTests/new-build/executable/Test.hs
new file mode 100644
index 0000000000..b3549c2fe3
--- /dev/null
+++ b/cabal-install/tests/IntegrationTests/new-build/executable/Test.hs
@@ -0,0 +1 @@
+main = return ()
diff --git a/cabal-install/tests/IntegrationTests/new-build/executable/a.cabal b/cabal-install/tests/IntegrationTests/new-build/executable/a.cabal
new file mode 100644
index 0000000000..ed9fc2919a
--- /dev/null
+++ b/cabal-install/tests/IntegrationTests/new-build/executable/a.cabal
@@ -0,0 +1,15 @@
+name: a
+version: 0.1
+cabal-version: >= 1.10
+build-type: Simple
+
+executable aexe
+  main-is: Main.hs
+  build-depends: base
+  default-language: Haskell2010
+
+test-suite atest
+  type: exitcode-stdio-1.0
+  main-is: Test.hs
+  build-depends: base
+  default-language: Haskell2010
diff --git a/cabal-install/tests/IntegrationTests/new-build/executable/cabal.project b/cabal-install/tests/IntegrationTests/new-build/executable/cabal.project
new file mode 100644
index 0000000000..e6fdbadb43
--- /dev/null
+++ b/cabal-install/tests/IntegrationTests/new-build/executable/cabal.project
@@ -0,0 +1 @@
+packages: .
diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs
index 3c7d2ae11e..6e2c3355af 100644
--- a/cabal-install/tests/IntegrationTests2.hs
+++ b/cabal-install/tests/IntegrationTests2.hs
@@ -237,12 +237,13 @@ planProject testdir cliConfig = do
     let targets =
           Map.fromList
             [ (installedUnitId pkg, [BuildDefaultComponents])
-            | InstallPlan.Configured pkg <- InstallPlan.toList elaboratedPlan
+            | InstallPlan.Configured pkg_or_comp <- InstallPlan.toList elaboratedPlan
+            , let pkg = getElaboratedPackage pkg_or_comp
             , pkgBuildStyle pkg == BuildInplaceOnly ]
         elaboratedPlan' = pruneInstallPlanToTargets targets elaboratedPlan
 
     (elaboratedPlan'', pkgsBuildStatus) <-
-      rebuildTargetsDryRun distDirLayout
+      rebuildTargetsDryRun verbosity distDirLayout elaboratedShared
                            elaboratedPlan'
 
     let buildSettings = resolveBuildTimeSettings
@@ -350,30 +351,30 @@ expectPackagePreExisting plan buildOutcomes pkgid = do
       (_, buildResult) -> unexpectedBuildResult "PreExisting" planpkg buildResult
 
 expectPackageConfigured :: ElaboratedInstallPlan -> BuildOutcomes -> PackageId
-                        -> IO ElaboratedConfiguredPackage
+                        -> IO ElaboratedPackage
 expectPackageConfigured plan buildOutcomes pkgid = do
     planpkg <- expectPlanPackage plan pkgid
     case (planpkg, InstallPlan.lookupBuildOutcome planpkg buildOutcomes) of
       (InstallPlan.Configured pkg, Nothing)
-                       -> return pkg
+                       -> return (getElaboratedPackage pkg)
       (_, buildResult) -> unexpectedBuildResult "Configured" planpkg buildResult
 
 expectPackageInstalled :: ElaboratedInstallPlan -> BuildOutcomes -> PackageId
-                       -> IO (ElaboratedConfiguredPackage, BuildResult)
+                       -> IO (ElaboratedPackage, BuildResult)
 expectPackageInstalled plan buildOutcomes pkgid = do
     planpkg <- expectPlanPackage plan pkgid
     case (planpkg, InstallPlan.lookupBuildOutcome planpkg buildOutcomes) of
       (InstallPlan.Configured pkg, Just (Right result))
-                       -> return (pkg, result)
+                       -> return (getElaboratedPackage pkg, result)
       (_, buildResult) -> unexpectedBuildResult "Installed" planpkg buildResult
 
 expectPackageFailed :: ElaboratedInstallPlan -> BuildOutcomes -> PackageId
-                    -> IO (ElaboratedConfiguredPackage, BuildFailure)
+                    -> IO (ElaboratedPackage, BuildFailure)
 expectPackageFailed plan buildOutcomes pkgid = do
     planpkg <- expectPlanPackage plan pkgid
     case (planpkg, InstallPlan.lookupBuildOutcome planpkg buildOutcomes) of
       (InstallPlan.Configured pkg, Just (Left failure))
-                       -> return (pkg, failure)
+                       -> return (getElaboratedPackage pkg, failure)
       (_, buildResult) -> unexpectedBuildResult "Failed" planpkg buildResult
 
 unexpectedBuildResult :: String -> ElaboratedPlanPackage
diff --git a/cabal-install/tests/IntegrationTests2/exception/configure/a.cabal b/cabal-install/tests/IntegrationTests2/exception/configure/a.cabal
index a9dced8d3a..f0bf220bef 100644
--- a/cabal-install/tests/IntegrationTests2/exception/configure/a.cabal
+++ b/cabal-install/tests/IntegrationTests2/exception/configure/a.cabal
@@ -1,3 +1,9 @@
 name: a
 version: 1
 build-type: Simple
+-- This used to be a blank package with no components,
+-- but I refactored new-build so that if a package has
+-- no buildable components, we skip configuring it.
+-- So put in a (failing) component so that we try to
+-- configure.
+executable a
diff --git a/cabal-install/tests/UnitTests/Distribution/Client/InstallPlan.hs b/cabal-install/tests/UnitTests/Distribution/Client/InstallPlan.hs
index f4fbf0fbeb..2d06a8cdb2 100644
--- a/cabal-install/tests/UnitTests/Distribution/Client/InstallPlan.hs
+++ b/cabal-install/tests/UnitTests/Distribution/Client/InstallPlan.hs
@@ -1,10 +1,14 @@
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
+{-# LANGUAGE ConstraintKinds #-}
 module UnitTests.Distribution.Client.InstallPlan (tests) where
 
 import           Distribution.Package
 import           Distribution.Version
 import qualified Distribution.Client.InstallPlan as InstallPlan
-import           Distribution.Client.InstallPlan (GenericInstallPlan)
+import           Distribution.Client.InstallPlan (GenericInstallPlan, IsUnit)
 import qualified Distribution.Compat.Graph as Graph
+import           Distribution.Compat.Graph (IsNode(..))
 import           Distribution.Solver.Types.Settings
 import           Distribution.Solver.Types.PackageFixedDeps
 import           Distribution.Solver.Types.ComponentDeps as CD
@@ -146,6 +150,12 @@ instance Show TestInstallPlan where
 data TestPkg = TestPkg PackageId UnitId [UnitId]
   deriving (Eq, Show)
 
+instance IsNode TestPkg where
+  type Key TestPkg = UnitId
+  nodeKey (TestPkg _ ipkgid _) = ipkgid
+  nodeNeighbors (TestPkg _ _ deps) = deps
+
+
 instance Package TestPkg where
   packageId (TestPkg pkgid _ _) = pkgid
 
@@ -155,6 +165,9 @@ instance HasUnitId TestPkg where
 instance PackageFixedDeps TestPkg where
   depends (TestPkg _ _ deps) = CD.singleton CD.ComponentLib deps
 
+instance PackageInstalled TestPkg where
+  installedDepends (TestPkg _ _ deps) = deps
+
 instance Arbitrary TestInstallPlan where
   arbitrary = arbitraryTestInstallPlan
 
@@ -191,8 +204,8 @@ arbitraryTestInstallPlan = do
 -- It takes generators for installed and source packages and the chance that
 -- each package is installed (for those packages with no prerequisites).
 --
-arbitraryInstallPlan :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
-                         HasUnitId srcpkg, PackageFixedDeps srcpkg)
+arbitraryInstallPlan :: (IsUnit ipkg,
+                         IsUnit srcpkg)
                      => (Vertex -> [Vertex] -> Gen ipkg)
                      -> (Vertex -> [Vertex] -> Gen srcpkg)
                      -> Float
@@ -222,9 +235,7 @@ arbitraryInstallPlan mkIPkg mkSrcPkg ipkgProportion graph = do
                  ]
     let index = Graph.fromList (map InstallPlan.PreExisting ipkgs
                              ++ map InstallPlan.Configured  srcpkgs)
-    case InstallPlan.new (IndependentGoals False) index of
-      Right plan -> return plan
-      Left  _    -> error "arbitraryInstallPlan: generated invalid plan"
+    return $ InstallPlan.new (IndependentGoals False) index
 
 
 -- | Generate a random directed acyclic graph, based on the algorithm presented
-- 
GitLab