diff --git a/.gitmodules b/.gitmodules
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "cabal"]
 	path = cabal
 	url = git://github.com/gentoo-haskell/cabal.git
+[submodule "hackage-security"]
+	path = hackage-security
+	url = git://github.com/well-typed/hackage-security.git
diff --git a/Cabal2Ebuild.hs b/Cabal2Ebuild.hs
--- a/Cabal2Ebuild.hs
+++ b/Cabal2Ebuild.hs
@@ -51,17 +51,22 @@
                                                else Cabal.description pkg,
     E.homepage        = thisHomepage,
     E.license         = Portage.convertLicense $ Cabal.license pkg,
-    E.slot            = (E.slot E.ebuildTemplate) ++ maybe [] (const "/${PV}") (Cabal.library pkg),
+    E.slot            = (E.slot E.ebuildTemplate) ++ (if hasLibs then "/${PV}" else ""),
     E.my_pn           = if any isUpper cabalPkgName then Just cabalPkgName else Nothing,
     E.features        = E.features E.ebuildTemplate
-                   ++ (if hasExe then ["bin"] else [])
-                   ++ maybe [] (const (["lib","profile","haddock","hoogle"]
-                        ++ if cabalPkgName == "hscolour" then [] else ["hscolour"])
-                        ) (Cabal.library pkg) -- hscolour can't colour its own sources
-                   ++ (if hasTests then ["test-suite"] else [])
+                   ++ (if hasExe then ["bin"]
+                                 else [])
+                   ++ (if hasLibs then (["lib","profile","haddock","hoogle"]
+                                        ++ if cabalPkgName == "hscolour"
+                                                then []
+                                                else ["hscolour"])
+                                  else [])
+                   ++ (if hasTests then ["test-suite"]
+                                   else [])
   } where
         cabal_pn = Cabal.pkgName $ Cabal.package pkg
         cabalPkgName = display cabal_pn
+        hasLibs = Cabal.libraries pkg /= []
         hasExe = (not . null) (Cabal.executables pkg)
         hasTests = (not . null) (Cabal.testSuites pkg)
         thisHomepage = if (null $ Cabal.homepage pkg)
diff --git a/CacheFile.hs b/CacheFile.hs
deleted file mode 100644
--- a/CacheFile.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module CacheFile where
-
-import System.FilePath
-
-indexFile :: String
-indexFile = "00-index.tar.gz"
-
-hackportDir :: String
-hackportDir = ".hackport"
-
-cacheFile :: FilePath -> FilePath
-cacheFile tree = tree </> hackportDir </> indexFile
diff --git a/Diff.hs b/Diff.hs
deleted file mode 100644
--- a/Diff.hs
+++ /dev/null
@@ -1,188 +0,0 @@
-module Diff
-    ( runDiff
-    , DiffMode(..)
-    ) where
-
-import Control.Monad ( mplus )
-import Control.Exception ( assert )
-import Data.Maybe ( fromJust, listToMaybe )
-import Data.List ( sortBy, groupBy )
-import Data.Ord ( comparing )
-
-import qualified Portage.Overlay as Portage
-import qualified Portage.Cabal as Portage
-import qualified Portage.PackageId as Portage
-
-import qualified Data.Version as Cabal
-
--- cabal
-import Distribution.Verbosity
-import Distribution.Text(display)
-import qualified Distribution.Package as Cabal
-import qualified Distribution.Client.PackageIndex as Index
-import Distribution.Simple.Utils (equating)
-
--- cabal-install
-import qualified Distribution.Client.IndexUtils as Index (getSourcePackages)
-import qualified Distribution.Client.Types as Cabal
-import Distribution.Client.Utils (mergeBy, MergeResult(..))
-
-data DiffMode
-	= ShowAll
-	| ShowMissing
-	| ShowAdditions
-	| ShowNewer
-	| ShowCommon
-        | ShowPackages [String]
-	deriving Eq
-
-
-{-
-type DiffState a = MergeResult a a
-tabs :: String -> String
-tabs str = let len = length str in str++(if len < 3*8
-	then replicate (3*8-len) ' '
-	else "")
-
-
--- TODO: is the new showPackageCompareInfo showing the packages in the same
--- way as showDiffState did?
-
-showDiffState :: Cabal.PackageName -> DiffState Portage.Version -> String
-showDiffState pkg st = (tabs (display pkg)) ++ " [" ++ (case st of
-  InBoth x y -> display x ++ (case compare x y of
-    EQ -> "="
-    GT -> ">"
-    LT -> "<") ++ display y
-  OnlyInLeft x -> display x ++ ">none"
-  OnlyInRight y -> "none<" ++ display y) ++ "]"
--}
-
-runDiff :: Verbosity -> FilePath -> DiffMode -> Cabal.Repo -> IO ()
-runDiff verbosity overlayPath dm repo = do
-  -- get package list from hackage
-  pkgDB <- Index.getSourcePackages verbosity [ repo ]
-  let (Cabal.SourcePackageDb hackageIndex _) = pkgDB
-
-  -- get package list from the overlay
-  overlay0 <- (Portage.loadLazy overlayPath)
-  let overlayIndex = Portage.fromOverlay (Portage.reduceOverlay overlay0)
-
-  let (subHackage, subOverlay)
-        = case dm of
-            ShowPackages pkgs ->
-              (concatMap (concatMap snd . Index.searchByNameSubstring hackageIndex) pkgs
-              ,concatMap (concatMap snd . Index.searchByNameSubstring overlayIndex) pkgs)
-            _ ->
-              (Index.allPackages hackageIndex
-              ,Index.allPackages overlayIndex)
-  diff subHackage subOverlay dm
-
-data PackageCompareInfo = PackageCompareInfo {
-    name :: Cabal.PackageName,
---    hackageVersions :: [ Cabal.Version ],
---    overlayVersions :: [ Cabal.Version ]
-    hackageVersion :: Maybe Cabal.Version,
-    overlayVersion :: Maybe Cabal.Version
-  } deriving Show
-
-showPackageCompareInfo :: PackageCompareInfo -> String
-showPackageCompareInfo pkgCmpInfo =
-    display (name pkgCmpInfo) ++ " ["
-    ++ hackageS ++ sign ++ overlayS ++ "]" 
-  where
-  overlay = overlayVersion pkgCmpInfo
-  hackage = hackageVersion pkgCmpInfo
-  hackageS = maybe "none" display hackage
-  overlayS = maybe "none" display overlay
-  sign = case compare hackage overlay of
-          EQ -> "="
-          GT -> ">"
-          LT -> "<"
-
-diff :: [Cabal.SourcePackage]
-     -> [Portage.ExistingEbuild]
-     -> DiffMode
-     -> IO ()
-diff hackage overlay dm = do
-  mapM_ (putStrLn . showPackageCompareInfo) pkgCmpInfos
-  where
-  merged = mergePackages (map (Portage.normalizeCabalPackageId . Cabal.packageId) hackage)
-                         (map Portage.ebuildCabalId overlay)
-  pkgCmpInfos = filter pkgFilter (map (uncurry mergePackageInfo) merged)
-  pkgFilter :: PackageCompareInfo -> Bool
-  pkgFilter pkgCmpInfo =
-    let om = overlayVersion pkgCmpInfo
-        hm = hackageVersion pkgCmpInfo
-        st = case (om,hm) of
-              (Just ov, Just hv) -> InBoth ov hv
-              (Nothing, Just hv) -> OnlyInRight hv
-              (Just ov, Nothing) -> OnlyInLeft ov
-              _ -> error "impossible"
-    in
-    case dm of
-      ShowAll -> True
-      ShowPackages _ -> True -- already filtered
-      ShowNewer -> case st of
-                     InBoth o h -> h>o
-                     _ -> False
-      ShowMissing -> case st of
-                     OnlyInLeft _ -> False
-                     InBoth x y -> x < y
-                     OnlyInRight _ -> True
-      ShowAdditions -> case st of
-                     OnlyInLeft _ -> True
-                     InBoth x y -> x > y
-                     OnlyInRight _ -> False
-      ShowCommon -> case st of
-                     OnlyInLeft _ -> False
-                     InBoth x y -> x == y
-                     OnlyInRight _ -> False
- 
--- | We get the 'PackageCompareInfo' by combining the info for the overlay
--- and hackage versions of a package.
---
--- * We're building info about a various versions of a single named package so
--- the input package info records are all supposed to refer to the same
--- package name.
---
-mergePackageInfo :: [Cabal.PackageIdentifier]
-                 -> [Cabal.PackageIdentifier]
-                 -> PackageCompareInfo
-mergePackageInfo hackage overlay =
-  assert (length overlay + length hackage > 0) $
-  PackageCompareInfo {
-    name              = combine Cabal.pkgName latestHackage
-                                Cabal.pkgName latestOverlay,
---    hackageVersions = map Cabal.pkgVersion hackage,
---    overlayVersions = map Cabal.pkgVersion overlay
-    hackageVersion = fmap Cabal.pkgVersion latestHackage,
-    overlayVersion = fmap Cabal.pkgVersion latestOverlay
-  }
-  where
-    combine f x g y = fromJust (fmap f x `mplus` fmap g y)
-    latestHackage = latestOf hackage
-    latestOverlay = latestOf overlay
-    latestOf :: [Cabal.PackageIdentifier] -> Maybe Cabal.PackageIdentifier
-    latestOf = listToMaybe . reverse . sortBy (comparing Cabal.pkgVersion)
-
--- | Rearrange installed and available packages into groups referring to the
--- same package by name. In the result pairs, the lists are guaranteed to not
--- both be empty.
---
-mergePackages :: [Cabal.PackageIdentifier] -> [Cabal.PackageIdentifier]
-              -> [([Cabal.PackageIdentifier], [Cabal.PackageIdentifier])]
-mergePackages hackage overlay =
-    map collect
-  $ mergeBy (\i a -> fst i `compare` fst a)
-            (groupOn Cabal.pkgName hackage)
-            (groupOn Cabal.pkgName overlay)
-  where
-    collect (OnlyInLeft  (_,is)       ) = (is, [])
-    collect (    InBoth  (_,is) (_,as)) = (is, as)
-    collect (OnlyInRight        (_,as)) = ([], as)
-
-groupOn :: Ord key => (a -> key) -> [a] -> [(key,[a])]
-groupOn key = map (\xs -> (key (head xs), xs))
-            . groupBy (equating key)
-            . sortBy (comparing key)
diff --git a/DistroMap.hs b/DistroMap.hs
deleted file mode 100644
--- a/DistroMap.hs
+++ /dev/null
@@ -1,158 +0,0 @@
-{-# OPTIONS -XPatternGuards #-}
-{-
-Generate a distromap, like these:
-http://hackage.haskell.org/packages/archive/00-distromap/
-Format:
-
-("xmobar","0.8",Just "http://packages.gentoo.org/package/x11-misc/xmobar")
-("xmobar","0.9",Just "http://packages.gentoo.org/package/x11-misc/xmobar")
-("xmobar","0.9.2",Just "http://packages.gentoo.org/package/x11-misc/xmobar")
-("xmonad","0.5",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.6",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.7",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.8",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.8.1",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.9",Just "http://packages.gentoo.org/package/x11-wm/xmonad")
-("xmonad","0.9.1",Just "http://en.gentoo-wiki.com/wiki/Haskell/overlay")
-
-Multiple entries for each package is allowed, given that there are different versions.
-
-
-Setup:
-  Join all packages from portage and the overlay into a big map;
-  From Portage.PackageId: PackageName = category/package
-  PVULine = (packagename, versionstring, url)
-  Create such a map: Map PackageName DistroLine
-  Only one PVULine per version, and prefer portage over the overlay.
-
-Algorithm;
-  1. Take a package from hackage
-  2. Look for it in the map
-    a. For each version:
-        find a match in the list of versions:
-          yield the PVULine
--}
-
-module DistroMap
-  ( distroMap ) where
-
-import Control.Applicative
-import qualified Data.List as List ( nub )
-import qualified Data.Map as Map
-import Data.Map ( Map )
-import System.FilePath ( (</>) )
-import Debug.Trace ( trace )
-import Data.Maybe ( fromJust )
-
-import Distribution.Verbosity
-import Distribution.Text ( display )
-import Distribution.Client.Types ( Repo, SourcePackageDb(..), SourcePackage(..) )
-import Distribution.Simple.Utils ( info )
-
-import qualified Data.Version as Cabal
-import qualified Distribution.Package as Cabal
-import qualified Distribution.Client.PackageIndex as CabalInstall
-import qualified Distribution.Client.IndexUtils as CabalInstall
-
-import Portage.Overlay (  readOverlayByPackage, getDirectoryTree )
-import qualified Portage.PackageId as Portage
-import qualified Portage.Version as Portage
-
-type PVU = (Cabal.PackageName, Cabal.Version, Maybe String)
-type PVU_Map = Map Portage.PackageName [(Cabal.Version, Maybe String)]
-
-distroMap :: Verbosity -> Repo -> FilePath -> FilePath -> [String] -> IO ()
-distroMap verbosity repo portagePath overlayPath args = do
-  info verbosity "distro map called"
-  info verbosity ("verbosity: " ++ show verbosity)
-  info verbosity ("portage: " ++ portagePath)
-  info verbosity ("overlay: " ++ overlayPath)
-  info verbosity ("args: " ++ show args)
-
-  portage <- readOverlayByPackage <$> getDirectoryTree portagePath
-  overlay <- readOverlayByPackage <$> getDirectoryTree overlayPath
-
-  info verbosity ("portage packages: " ++ show (length portage))
-  info verbosity ("overlay packages: " ++ show (length overlay))
-
-  let portageMap = buildPortageMap portage
-      overlayMap = buildOverlayMap overlay
-      completeMap = unionMap portageMap overlayMap
-
-  info verbosity ("portage map: " ++ show (Map.size portageMap))
-  info verbosity ("overlay map: " ++ show (Map.size overlayMap))
-  info verbosity ("complete map: " ++ show (Map.size completeMap))
-
-  SourcePackageDb { packageIndex = packageIndex } <-
-    CabalInstall.getSourcePackages verbosity [repo]
-
-  let pkgs0 = map (map packageInfoId) (CabalInstall.allPackagesByName packageIndex)
-      hackagePkgs = [ (Cabal.pkgName (head p), map Cabal.pkgVersion p) | p <- pkgs0 ]
-
-  info verbosity ("cabal packages: " ++ show (length hackagePkgs))
-
-  let pvus = concat $ map (\(p,vs) -> lookupPVU completeMap p vs) hackagePkgs
-  info verbosity ("found pvus: " ++ show (length pvus))
-
-  mapM_ (putStrLn . showPVU) pvus
-  return ()
-
-
-showPVU :: PVU -> String
-showPVU (p,v,u) = show $ (display p, display v, u)
-
--- building the PVU_Map
-
-reduceVersion :: Portage.Version -> Portage.Version
-reduceVersion (Portage.Version ns _ _ _) = Portage.Version ns Nothing [] 0
-
-reduceVersions :: [Portage.Version] -> [Portage.Version]
-reduceVersions = List.nub . map reduceVersion
-
-buildMap :: [(Portage.PackageName, [Portage.Version])]
-         -> (Portage.PackageName -> Portage.Version -> Maybe String)
-         -> PVU_Map
-buildMap pvs f = Map.mapWithKey (\p vs -> [ (fromJust $ Portage.toCabalVersion v, f p v)
-                                          | v <- reduceVersions vs ])
-                                (Map.fromList pvs)
-
-buildPortageMap :: [(Portage.PackageName, [Portage.Version])] -> PVU_Map
-buildPortageMap lst = buildMap lst $ \ (Portage.PackageName c p) _v ->
-  Just $ "http://packages.gentoo.org/package" </> display c </> display p
-
-buildOverlayMap :: [(Portage.PackageName, [Portage.Version])] -> PVU_Map
-buildOverlayMap lst = buildMap lst $ \_ _ -> Just "http://en.gentoo-wiki.com/wiki/Haskell/overlay"
-
-unionMap :: PVU_Map -> PVU_Map -> PVU_Map
-unionMap = Map.unionWith f
-  where
-  f :: [(Cabal.Version, Maybe String)]
-    -> [(Cabal.Version, Maybe String)]
-    -> [(Cabal.Version, Maybe String)]
-  f vas vbs = Map.toList (Map.union (Map.fromList vas) (Map.fromList vbs))
-
-
--- resolving Cabal.PackageName to Portage.PackageName
-
-lookupPVU :: PVU_Map -> Cabal.PackageName -> [Cabal.Version] -> [PVU]
-lookupPVU pvu_map pn cvs =
-  case findItems (Portage.normalizeCabalPackageName pn) of
-    [] -> []
-    [item] -> ret item
-    items | [item] <- preferableItem items -> ret item
-          | otherwise -> trace (noDefaultText items) []
-  where
-  noDefaultText is = unlines $ ("no default for package: " ++ display pn)
-                           : [ " * " ++ (display cat)
-                             | (Portage.PackageName cat _, _) <- is]
-
-  ret (_, vs) = [ (pn, v, u) | (v, u) <- vs, v `elem` cvs ]
-  preferableItem items =
-    [ item
-    | item@(Portage.PackageName cat _pn, _vs) <- items
-    , cat == Portage.Category "dev-haskell"]
-  findItems cpn = Map.toList $ Map.filterWithKey f pvu_map
-    where
-    f (Portage.PackageName _cat _pn) _vs = cpn == pn
-
-
diff --git a/HackPort/GlobalFlags.hs b/HackPort/GlobalFlags.hs
new file mode 100644
--- /dev/null
+++ b/HackPort/GlobalFlags.hs
@@ -0,0 +1,47 @@
+module HackPort.GlobalFlags
+    ( GlobalFlags(..)
+    , defaultGlobalFlags
+    , withHackPortContext
+    ) where
+
+import qualified Distribution.Verbosity as DV
+import qualified Distribution.Simple.Setup as DSS
+import qualified Distribution.Client.GlobalFlags as DCG
+import qualified Distribution.Client.Types as DCT
+import qualified Distribution.Utils.NubList as DUN
+
+import qualified Network.URI as NU
+
+import System.FilePath ((</>))
+
+import qualified Overlays
+
+data GlobalFlags =
+    GlobalFlags { globalVersion :: DSS.Flag Bool
+                , globalNumericVersion :: DSS.Flag Bool
+                , globalPathToOverlay :: DSS.Flag (Maybe FilePath)
+                , globalPathToPortage :: DSS.Flag (Maybe FilePath)
+                }
+
+defaultGlobalFlags :: GlobalFlags
+defaultGlobalFlags =
+    GlobalFlags { globalVersion = DSS.Flag False
+                , globalNumericVersion = DSS.Flag False
+                , globalPathToOverlay = DSS.Flag Nothing
+                , globalPathToPortage = DSS.Flag Nothing
+                }
+
+defaultRemoteRepo :: DCT.RemoteRepo
+defaultRemoteRepo = (DCT.emptyRemoteRepo name) { DCT.remoteRepoURI = uri }
+   where
+    Just uri = NU.parseURI "https://hackage.haskell.org/"
+    name     = "hackage.haskell.org"
+
+withHackPortContext :: DV.Verbosity -> GlobalFlags -> (DCG.RepoContext -> IO a) -> IO a
+withHackPortContext verbosity global_flags callback = do
+    overlayPath <- Overlays.getOverlayPath verbosity (DSS.fromFlag $ globalPathToOverlay global_flags)
+    let flags = DCG.defaultGlobalFlags {
+                    DCG.globalRemoteRepos = DUN.toNubList [defaultRemoteRepo]
+                  , DCG.globalCacheDir    = DSS.Flag $ overlayPath </> ".hackport"
+                }
+    DCG.withRepoContext verbosity flags callback
diff --git a/Hackage.hs b/Hackage.hs
deleted file mode 100644
--- a/Hackage.hs
+++ /dev/null
@@ -1,35 +0,0 @@
-{-|
-    Author      :  Sergei Trofimovich <slyfox@gentoo.org>
-    Stability   :  experimental
-    Portability :  haskell98
-
-    Utilities to work with hackage-alike repositories
--}
-module Hackage
-    ( defaultRepo
-    , defaultRepoURI
-    ) where
-
-import Distribution.Client.Types (Repo(..), RemoteRepo(..))
-import Network.URI (URI(..), URIAuth(..))
-import System.FilePath
-
-defaultRepo :: FilePath -> Repo
-defaultRepo overlayPath =
-  Repo {
-      repoKind = Left defaultRemoteRepo,
-      repoLocalDir = overlayPath </> ".hackport"
-    }
-
--- A copy from cabal-install/Distribution.Client.Config
-defaultRemoteRepo :: RemoteRepo
-defaultRemoteRepo = RemoteRepo name uri () False
-  where
-    name = "hackage.haskell.org"
-    uri  = URI "http:" (Just (URIAuth "" name "")) "/" "" ""
-
-defaultRepoURI :: FilePath -> URI
-defaultRepoURI overlayPath =
-  case repoKind (defaultRepo overlayPath) of
-    Left (RemoteRepo { remoteRepoURI = uri }) -> uri
-    Right _                                   -> error $ "defaultRepoURI: unable to get URI for " ++ overlayPath
diff --git a/Main-GuessGHC.hs b/Main-GuessGHC.hs
deleted file mode 100644
--- a/Main-GuessGHC.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-module Main where
-
-import System.Environment
-
-import Distribution.PackageDescription
-import Distribution.PackageDescription.Parse
-
-import Distribution.Text
-import Distribution.Verbosity
-
-import Portage.GHCCore
-
-main :: IO ()
-main = do
-  args <- getArgs
-  gpds <- mapM (readPackageDescription silent) args
-  mapM_ guess gpds
-
-guess :: GenericPackageDescription -> IO ()
-guess gpd = do
-  let pkg = package . packageDescription $ gpd
-  let mghc = minimumGHCVersionToBuildPackage gpd
-  putStr (display pkg)
-  putStr "\t\t"
-  putStrLn $ case mghc of
-              Nothing -> "Unknown"
-              Just (compiler, _pkgs) -> display compiler
diff --git a/Main.hs b/Main.hs
--- a/Main.hs
+++ b/Main.hs
@@ -1,4 +1,4 @@
-module Main where
+module Main (main) where
 
 import Control.Applicative
 import Control.Monad
@@ -11,10 +11,9 @@
 import Distribution.Simple.Setup
         ( Flag(..), fromFlag
         , trueArg
-        , flagToList
         , optionVerbosity
         )
-import Distribution.ReadE ( succeedReadE )
+
 import Distribution.Simple.Command -- commandsRun
 import Distribution.Simple.Utils ( die, cabalVersion, warn )
 import qualified Distribution.PackageDescription.Parse as Cabal
@@ -24,29 +23,24 @@
 
 import Distribution.Client.Types
 import Distribution.Client.Update
-import qualified Distribution.Client.HttpUtils as DCH
-
 import qualified Distribution.Client.PackageIndex as Index
 import qualified Distribution.Client.IndexUtils as Index
 
-import Hackage (defaultRepo, defaultRepoURI)
-
 import Portage.Overlay as Overlay ( loadLazy, inOverlay )
 import Portage.Host as Host ( getInfo, portage_dir )
 import Portage.PackageId ( normalizeCabalPackageId )
 
-import Network.URI ( URI(..), parseURI )
 import System.Environment ( getArgs, getProgName )
 import System.Directory ( doesDirectoryExist )
 import System.Exit ( exitFailure )
 import System.FilePath ( (</>) )
 
-import Diff
+import qualified HackPort.GlobalFlags as H
+
 import Error
 import Status
 import Overlays
 import Merge
-import DistroMap ( distroMap )
 
 import qualified Paths_cabal_install
 import qualified Paths_hackport
@@ -57,37 +51,30 @@
 
 data ListFlags = ListFlags {
     listVerbosity :: Flag Verbosity
-    -- , listOverlayPath :: Flag FilePath
-    -- , listServerURI :: Flag String
   }
 
 instance Monoid ListFlags where
   mempty = ListFlags {
     listVerbosity = mempty
-    -- , listOverlayPath = mempty
-    -- , listServerURI = mempty
   }
   mappend a b = ListFlags {
     listVerbosity = combine listVerbosity
-    -- , listOverlayPath = combine listOverlayPath
-    -- , listServerURI = combine listServerURI
   }
     where combine field = field a `mappend` field b
 
 defaultListFlags :: ListFlags
 defaultListFlags = ListFlags {
     listVerbosity = Flag normal
-    -- , listOverlayPath = NoFlag
-    -- , listServerURI = Flag defaultHackageServerURI
   }
 
 listCommand :: CommandUI ListFlags
 listCommand = CommandUI {
     commandName = "list",
-    commandSynopsis = "List packages",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for listCommand\n",
+    commandSynopsis = "List package versions matching pattern",
     commandUsage = usagePackages "list",
+    commandDescription = Nothing,
+    commandNotes = Nothing,
+
     commandDefaultFlags = defaultListFlags,
     commandOptions = \_showOrParseArgs ->
       [ optionVerbosity listVerbosity (\v flags -> flags { listVerbosity = v })
@@ -100,12 +87,12 @@
       ]
   }
 
-listAction :: ListFlags -> [String] -> GlobalFlags -> IO ()
+listAction :: ListFlags -> [String] -> H.GlobalFlags -> IO ()
 listAction flags extraArgs globalFlags = do
-  let verbosity = fromFlag (listVerbosity flags)
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  let repo = defaultRepo overlayPath
-  index <- fmap packageIndex (Index.getSourcePackages verbosity [ repo ])
+ let verbosity = fromFlag (listVerbosity flags)
+ H.withHackPortContext verbosity globalFlags $ \repoContext -> do
+  overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)
+  index <- fmap packageIndex (Index.getSourcePackages verbosity repoContext)
   overlay <- Overlay.loadLazy overlayPath
   let pkgs | null extraArgs = Index.allPackages index
            | otherwise = concatMap (concatMap snd . Index.searchByNameSubstring index) extraArgs
@@ -146,7 +133,7 @@
   , makeEbuildCabalFlags = Flag Nothing
   }
 
-makeEbuildAction :: MakeEbuildFlags -> [String] -> GlobalFlags -> IO ()
+makeEbuildAction :: MakeEbuildFlags -> [String] -> H.GlobalFlags -> IO ()
 makeEbuildAction flags args globalFlags = do
   (catstr,cabals) <- case args of
                       (category:cabal1:cabaln) -> return (category, cabal1:cabaln)
@@ -155,7 +142,7 @@
             Just c -> return c
             Nothing -> throwEx (ArgumentError ("could not parse category: " ++ catstr))
   let verbosity = fromFlag (makeEbuildVerbosity flags)
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
+  overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)
   forM_ cabals $ \cabalFileName -> do
     pkg <- Cabal.readPackageDescription normal cabalFileName
     mergeGenericPackageDescription verbosity overlayPath cat pkg False (fromFlag $ makeEbuildCabalFlags flags)
@@ -164,9 +151,10 @@
 makeEbuildCommand = CommandUI {
     commandName = "make-ebuild",
     commandSynopsis = "Make an ebuild from a .cabal file",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for makeEbuildCommand\n",
     commandUsage = \_ -> [],
+    commandDescription = Nothing,
+    commandNotes = Nothing,
+
     commandDefaultFlags = defaultMakeEbuildFlags,
     commandOptions = \_showOrParseArgs ->
       [ optionVerbosity makeEbuildVerbosity (\v flags -> flags { makeEbuildVerbosity = v })
@@ -182,106 +170,35 @@
   }
 
 -----------------------------------------------------------------------
--- Diff
------------------------------------------------------------------------
-
-data DiffFlags = DiffFlags {
-    -- diffMode :: Flag String, -- DiffMode,
-    diffVerbosity :: Flag Verbosity
-    -- , diffServerURI :: Flag String
-  }
-
-instance Monoid DiffFlags where
-  mempty = DiffFlags {
-    -- diffMode = mempty,
-    diffVerbosity = mempty
-    -- , diffServerURI = mempty
-  }
-  mappend a b = DiffFlags {
-    -- diffMode = combine diffMode,
-    diffVerbosity = combine diffVerbosity
-    -- , diffServerURI = combine diffServerURI
-  }
-    where combine field = field a `mappend` field b
-
-defaultDiffFlags :: DiffFlags
-defaultDiffFlags = DiffFlags {
-    -- diffMode = Flag "all",
-    diffVerbosity = Flag normal
-    -- , diffServerURI = Flag defaultHackageServerURI
-  }
-
-diffCommand :: CommandUI DiffFlags
-diffCommand = CommandUI {
-    commandName = "diff",
-    commandSynopsis = "Run diff",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for diffCommand\n",
-    commandUsage = usagePackages "diff",
-    commandDefaultFlags = defaultDiffFlags,
-    commandOptions = \_showOrParseArgs ->
-      [ optionVerbosity diffVerbosity (\v flags -> flags { diffVerbosity = v })
-      {-
-      , option [] ["mode"]
-         "Diff mode, one of: all, newer, missing, additions, common"
-         diffMode (\v flags -> flags { diffMode = v })
-         (reqArgFlag "MODE") -- I don't know how to map it strictly to DiffMode
-      -}
-      ]
-  }
-
-diffAction :: DiffFlags -> [String] -> GlobalFlags -> IO ()
-diffAction flags args globalFlags = do
-  let verbosity = fromFlag (diffVerbosity flags)
-      -- dm0 = fromFlag (diffMode flags)
-  dm <- case args of
-          [] -> return ShowAll
-          ["all"] -> return ShowAll
-          ["missing"] -> return ShowMissing
-          ["additions"] -> return ShowAdditions
-          ["newer"] -> return ShowNewer
-          ["common"] -> return ShowCommon
-          ("package":  pkgs) -> return (ShowPackages pkgs)
-          -- TODO: ["package",packagePattern] ->
-          --          return ShowPackagePattern packagePattern
-          _ -> die $ "Unknown mode: " ++ unwords args
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  let repo = defaultRepo overlayPath
-  runDiff verbosity overlayPath dm repo
-
------------------------------------------------------------------------
 -- Update
 -----------------------------------------------------------------------
 
 data UpdateFlags = UpdateFlags {
     updateVerbosity :: Flag Verbosity
-    -- , updateServerURI :: Flag String
   }
 
 instance Monoid UpdateFlags where
   mempty = UpdateFlags {
     updateVerbosity = mempty
-    -- , updateServerURI = mempty
   }
   mappend a b = UpdateFlags {
     updateVerbosity = combine updateVerbosity
-    -- , updateServerURI = combine updateServerURI
   }
     where combine field = field a `mappend` field b
 
 defaultUpdateFlags :: UpdateFlags
 defaultUpdateFlags = UpdateFlags {
     updateVerbosity = Flag normal
-    -- , updateServerURI = Flag defaultHackageServerURI
   }
 
 updateCommand :: CommandUI UpdateFlags
 updateCommand = CommandUI {
     commandName = "update",
-    commandSynopsis = "Update the local cache",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for updateCommand\n",
+    commandSynopsis = "Update the local package database",
     commandUsage = usageFlags "update",
+    commandDescription = Nothing,
+    commandNotes = Nothing,
+
     commandDefaultFlags = defaultUpdateFlags,
     commandOptions = \_ ->
       [ optionVerbosity updateVerbosity (\v flags -> flags { updateVerbosity = v })
@@ -295,16 +212,15 @@
       ]
   }
 
-updateAction :: UpdateFlags -> [String] -> GlobalFlags -> IO ()
+updateAction :: UpdateFlags -> [String] -> H.GlobalFlags -> IO ()
 updateAction flags extraArgs globalFlags = do
   unless (null extraArgs) $
     die $ "'update' doesn't take any extra arguments: " ++ unwords extraArgs
   let verbosity = fromFlag (updateVerbosity flags)
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  http_transport <- DCH.configureTransport verbosity Nothing
-  update http_transport verbosity [ defaultRepo overlayPath ]
-  
 
+  H.withHackPortContext verbosity globalFlags $ \repoContext ->
+    update verbosity repoContext
+
 -----------------------------------------------------------------------
 -- Status
 -----------------------------------------------------------------------
@@ -323,10 +239,11 @@
 statusCommand :: CommandUI StatusFlags
 statusCommand = CommandUI {
     commandName = "status",
-    commandSynopsis = "Show status(??)",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for statusCommand\n",
+    commandSynopsis = "Show up-to-date status against other repos (hackage, ::gentoo)",
     commandUsage = usagePackages "status",
+    commandDescription = Nothing,
+    commandNotes = Nothing,
+
     commandDefaultFlags = defaultStatusFlags,
     commandOptions = \_ ->
       [ optionVerbosity statusVerbosity (\v flags -> flags { statusVerbosity = v })
@@ -341,14 +258,16 @@
       ]
   }
 
-statusAction :: StatusFlags -> [String] -> GlobalFlags -> IO ()
+statusAction :: StatusFlags -> [String] -> H.GlobalFlags -> IO ()
 statusAction flags args globalFlags = do
   let verbosity = fromFlag (statusVerbosity flags)
       direction = fromFlag (statusDirection flags)
   portagePath <- getPortageDir verbosity globalFlags
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  runStatus verbosity portagePath overlayPath direction args
+  overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)
 
+  H.withHackPortContext verbosity globalFlags $ \repoContext ->
+      runStatus verbosity portagePath overlayPath direction args repoContext
+
 -----------------------------------------------------------------------
 -- Merge
 -----------------------------------------------------------------------
@@ -379,9 +298,10 @@
 mergeCommand = CommandUI {
     commandName = "merge",
     commandSynopsis = "Make an ebuild out of hackage package",
-    commandDescription = Just $ \_pname ->
-      "TODO: this is the commandDescription for mergeCommand\n",
     commandUsage = usagePackages "merge",
+    commandDescription = Nothing,
+    commandNotes = Nothing,
+
     commandDefaultFlags = defaultMergeFlags,
     commandOptions = \_showOrParseArgs ->
       [ optionVerbosity mergeVerbosity (\v flags -> flags { mergeVerbosity = v })
@@ -396,71 +316,18 @@
       ]
   }
 
-mergeAction :: MergeFlags -> [String] -> GlobalFlags -> IO ()
+mergeAction :: MergeFlags -> [String] -> H.GlobalFlags -> IO ()
 mergeAction flags extraArgs globalFlags = do
   let verbosity = fromFlag (mergeVerbosity flags)
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  let repo = defaultRepo overlayPath
-  merge verbosity repo (defaultRepoURI overlayPath) extraArgs overlayPath (fromFlag $ mergeCabalFlags flags)
-
------------------------------------------------------------------------
--- DistroMap
------------------------------------------------------------------------
-
-data DistroMapFlags = DistroMapFlags {
-    distroMapVerbosity :: Flag Verbosity
-  }
-
-instance Monoid DistroMapFlags where
-  mempty = DistroMapFlags {
-    distroMapVerbosity = mempty
-    -- , mergeServerURI = mempty
-  }
-  mappend a b = DistroMapFlags {
-    distroMapVerbosity = combine distroMapVerbosity
-  }
-    where combine field = field a `mappend` field b
-
-defaultDistroMapFlags :: DistroMapFlags
-defaultDistroMapFlags = DistroMapFlags {
-    distroMapVerbosity = Flag normal
-  }
-
-distroMapCommand :: CommandUI DistroMapFlags
-distroMapCommand = CommandUI {
-    commandName = "distromap",
-    commandSynopsis = "Build a distromap file",
-    commandDescription = Just $ \_pname ->
-      "TODO: this is the commandDescription for distroMapCommand\n",
-    commandUsage = usagePackages "distromap",
-    commandDefaultFlags = defaultDistroMapFlags,
-    commandOptions = \_showOrParseArgs ->
-      [ optionVerbosity distroMapVerbosity (\v flags -> flags { distroMapVerbosity = v })
-      ]
-  }
+  overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)
 
-distroMapAction :: DistroMapFlags-> [String] -> GlobalFlags -> IO ()
-distroMapAction flags extraArgs globalFlags = do
-  let verbosity = fromFlag (distroMapVerbosity flags)
-  overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)
-  let repo = defaultRepo overlayPath
-  portagePath <- getPortageDir verbosity globalFlags
-  distroMap verbosity repo portagePath overlayPath extraArgs
+  H.withHackPortContext verbosity globalFlags $ \repoContext ->
+    merge verbosity repoContext extraArgs overlayPath (fromFlag $ mergeCabalFlags flags)
 
 -----------------------------------------------------------------------
 -- Utils
 -----------------------------------------------------------------------
 
-getServerURI :: String -> IO URI
-getServerURI str =
-  case parseURI str of
-    Just uri -> return uri
-    Nothing -> throwEx (InvalidServer str)
-
-reqArgFlag :: ArgPlaceHolder -> SFlags -> LFlags -> Description ->
-              (b -> Flag String) -> (Flag String -> b -> b) -> OptDescr b
-reqArgFlag ad = reqArg ad (succeedReadE Flag) flagToList
-
 usagePackages :: String -> String -> String
 usagePackages op_name pname =
   "Usage: " ++ pname ++ " " ++ op_name ++ " [FLAGS] [PACKAGE]\n\n"
@@ -471,9 +338,9 @@
       "Usage: " ++ pname ++ " " ++ flag_name ++ " [FLAGS]\n\n"
       ++ "Flags for " ++ flag_name ++ ":"
 
-getPortageDir :: Verbosity -> GlobalFlags -> IO FilePath
+getPortageDir :: Verbosity -> H.GlobalFlags -> IO FilePath
 getPortageDir verbosity globalFlags = do
-  let portagePathM =  fromFlag (globalPathToPortage globalFlags)
+  let portagePathM =  fromFlag (H.globalPathToPortage globalFlags)
   portagePath <- case portagePathM of
                    Nothing -> Host.portage_dir <$> Host.getInfo
                    Just path -> return path
@@ -486,45 +353,31 @@
 -- Main
 -----------------------------------------------------------------------
 
-data GlobalFlags =
-    GlobalFlags { globalVersion :: Flag Bool
-                , globalNumericVersion :: Flag Bool
-                , globalPathToOverlay :: Flag (Maybe FilePath)
-                , globalPathToPortage :: Flag (Maybe FilePath)
-                }
-
-defaultGlobalFlags :: GlobalFlags
-defaultGlobalFlags =
-    GlobalFlags { globalVersion = Flag False
-                , globalNumericVersion = Flag False
-                , globalPathToOverlay = Flag Nothing
-                , globalPathToPortage = Flag Nothing
-                }
-
-globalCommand :: CommandUI GlobalFlags
+globalCommand :: CommandUI H.GlobalFlags
 globalCommand = CommandUI {
     commandName = "",
     commandSynopsis = "",
-    commandDescription = Just $ \_pname ->
-        "TODO: this is the commandDescription for globalCommand\n",
+    commandDescription = Nothing,
+    commandNotes = Nothing,
     commandUsage = \_ -> [],
-    commandDefaultFlags = defaultGlobalFlags,
+
+    commandDefaultFlags = H.defaultGlobalFlags,
     commandOptions = \_showOrParseArgs ->
         [ option ['V'] ["version"]
             "Print version information"
-            globalVersion (\v flags -> flags { globalVersion = v })
+            H.globalVersion (\v flags -> flags { H.globalVersion = v })
             trueArg
         , option [] ["numeric-version"]
             "Print just the version number"
-            globalNumericVersion (\v flags -> flags { globalNumericVersion = v })
+            H.globalNumericVersion (\v flags -> flags { H.globalNumericVersion = v })
             trueArg
         , option ['p'] ["overlay-path"]
             "Override search path list where .hackport/ lives (default list: ['.', paludis-ovls or emerge-ovls])"
-            globalPathToOverlay (\ovrl_path flags -> flags { globalPathToOverlay = ovrl_path })
+            H.globalPathToOverlay (\ovrl_path flags -> flags { H.globalPathToOverlay = ovrl_path })
             (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))
         , option [] ["portage-path"]
             "Override path to your portage tree"
-            globalPathToPortage (\port_path flags -> flags { globalPathToPortage = port_path })
+            H.globalPathToPortage (\port_path flags -> flags { H.globalPathToPortage = port_path })
             (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))
         ]
     }
@@ -537,8 +390,8 @@
     CommandErrors errs -> printErrors errs
     CommandReadyToGo (globalflags, commandParse) -> do
       case commandParse of
-        _ | fromFlag (globalVersion globalflags)        -> printVersion
-          | fromFlag (globalNumericVersion globalflags) -> printNumericVersion
+        _ | fromFlag (H.globalVersion globalflags)        -> printVersion
+          | fromFlag (H.globalNumericVersion globalflags) -> printNumericVersion
         CommandHelp help        -> printHelp help
         CommandList opts        -> printOptionsList opts
         CommandErrors errs      -> printErrors errs
@@ -563,10 +416,8 @@
       [ listCommand `commandAddAction` listAction
       , makeEbuildCommand `commandAddAction` makeEbuildAction
       , statusCommand `commandAddAction` statusAction
-      , diffCommand `commandAddAction` diffAction
       , updateCommand `commandAddAction` updateAction
       , mergeCommand `commandAddAction` mergeAction
-      , distroMapCommand `commandAddAction` distroMapAction
       ]
 
 main :: IO ()
diff --git a/Merge.hs b/Merge.hs
--- a/Merge.hs
+++ b/Merge.hs
@@ -31,10 +31,12 @@
 
 -- cabal-install
 import Distribution.Client.IndexUtils ( getSourcePackages )
+import qualified Distribution.Client.GlobalFlags as CabalInstall
 import qualified Distribution.Client.PackageIndex as Index
 import Distribution.Client.Types
 
 -- others
+import qualified Data.List.Split as DLS
 import System.Directory ( getCurrentDirectory
                         , setCurrentDirectory
                         , createDirectoryIfMissing
@@ -49,8 +51,6 @@
 import qualified Portage.EMeta as EM
 import Error as E
 
-import Network.URI
-
 import qualified Portage.Cabal as Portage
 import qualified Portage.PackageId as Portage
 import qualified Portage.Version as Portage
@@ -64,9 +64,6 @@
 
 import qualified Merge.Dependencies as Merge
 
-import qualified Util as U
-
-
 (<.>) :: String -> String -> String
 a <.> b = a ++ '.':b
 
@@ -98,14 +95,14 @@
 -- | Given a list of available packages, and maybe a preferred version,
 -- return the available package with that version. Latest version is chosen
 -- if no preference.
-resolveVersion :: [SourcePackage] -> Maybe Cabal.Version -> Maybe SourcePackage
+resolveVersion :: [UnresolvedSourcePackage] -> Maybe Cabal.Version -> Maybe UnresolvedSourcePackage
 resolveVersion avails Nothing = Just $ L.maximumBy (comparing (Cabal.pkgVersion . packageInfoId)) avails
 resolveVersion avails (Just ver) = listToMaybe (filter match avails)
   where
     match avail = ver == Cabal.pkgVersion (packageInfoId avail)
 
-merge :: Verbosity -> Repo -> URI -> [String] -> FilePath -> Maybe String -> IO ()
-merge verbosity repo _serverURI args overlayPath users_cabal_flags = do
+merge :: Verbosity -> CabalInstall.RepoContext -> [String] -> FilePath -> Maybe String -> IO ()
+merge verbosity repoContext args overlayPath users_cabal_flags = do
   (m_category, user_pName, m_version) <-
     case readPackageString args of
       Left err -> throwEx err
@@ -125,7 +122,7 @@
   overlay <- Overlay.loadLazy overlayPath
   -- portage_path <- Host.portage_dir `fmap` Host.getInfo
   -- portage <- Overlay.loadLazy portage_path
-  index <- fmap packageIndex $ getSourcePackages verbosity [ repo ]
+  index <- fmap packageIndex $ getSourcePackages verbosity repoContext
 
   -- find all packages that maches the user specified package name
   availablePkgs <-
@@ -186,7 +183,7 @@
                 user_renames = [ (cfn, ein)
                                | ((Cabal.FlagName cfn, ein), Nothing) <- cn_in_mb
                                ]
-                cn_in_mb = map read_fa $ U.split (== ',') user_fas_s
+                cn_in_mb = map read_fa $ DLS.splitOn "," user_fas_s
                 read_fa :: String -> ((Cabal.FlagName, String), Maybe Bool)
                 read_fa [] = error $ "read_fas: empty flag?"
                 read_fa (op:flag) =
@@ -196,7 +193,7 @@
                         _     -> (get_rename (op:flag), Nothing)
                   where get_rename :: String -> (Cabal.FlagName, String)
                         get_rename s =
-                            case U.split (== ':') s of
+                            case DLS.splitOn ":" s of
                                 [cabal_flag_name] -> (Cabal.FlagName cabal_flag_name, cabal_flag_name)
                                 [cabal_flag_name, iuse_name] -> (Cabal.FlagName cabal_flag_name, iuse_name)
                                 _                 -> error $ "get_rename: too many components" ++ show (s)
@@ -356,14 +353,6 @@
       icalate _s [x]    = [x]
       icalate  s (x:xs) = (x ++ s) : icalate s xs
 
-      gamesFlags :: [String]
-      gamesFlags = ["--prefix=\"${GAMES_PREFIX}\""]
-
-      addGamesFlags :: [String] -> [String]
-      addGamesFlags xs
-        | Portage.is_games_cat cat = xs ++ gamesFlags
-        | otherwise                = xs
-
       build_configure_call :: [String] -> [String]
       build_configure_call [] = []
       build_configure_call conf_args = icalate " \\" $
@@ -386,7 +375,8 @@
                . (\e -> e { E.depend_extra  = S.toList $ Merge.dep_e tdeps } )
                . (\e -> e { E.rdepend       =            Merge.rdep tdeps} )
                . (\e -> e { E.rdepend_extra = S.toList $ Merge.rdep_e tdeps } )
-               . (\e -> e { E.src_configure = build_configure_call $ addGamesFlags $ selected_flags (active_flags, user_specified_fas) } )
+               . (\e -> e { E.src_configure = build_configure_call $
+                                                  selected_flags (active_flags, user_specified_fas) } )
                . (\e -> e { E.iuse = E.iuse e ++ map to_iuse active_flag_descs })
                . ( case requested_cabal_flags of
                        Nothing  -> id
diff --git a/Merge/Dependencies.hs b/Merge/Dependencies.hs
--- a/Merge/Dependencies.hs
+++ b/Merge/Dependencies.hs
@@ -114,7 +114,7 @@
   where
     -- hasBuildableExes p = any (buildable . buildInfo) . executables $ p
     treatAsLibrary :: Bool
-    treatAsLibrary = isJust (Cabal.library pkg)
+    treatAsLibrary = Cabal.libraries pkg /= []
     -- without slot business
     raw_haskell_deps :: Portage.Dependency
     raw_haskell_deps = PN.normalize_depend $ Portage.DependAllOf $ haskellDependencies overlay (buildDepends pkg)
@@ -230,14 +230,14 @@
 ---------------------------------------------------------------
 
 findCLibs :: PackageDescription -> [Portage.Dependency]
-findCLibs (PackageDescription { library = lib, executables = exes }) =
+findCLibs (PackageDescription { libraries = libs, executables = exes }) =
   [ trace ("WARNING: This package depends on a C library we don't know the portage name for: " ++ p ++ ". Check the generated ebuild.")
           (any_c_p "unknown-c-lib" p)
   | p <- notFound
   ] ++
   found
   where
-  libE = maybe [] (extraLibs.libBuildInfo) lib
+  libE = concatMap (extraLibs . libBuildInfo) libs
   exeE = concatMap extraLibs (filter buildable (map buildInfo exes))
   allE = libE ++ exeE
 
@@ -357,6 +357,7 @@
       , ("SDL_gfx", any_c_p "media-libs" "sdl-gfx")
       , ("SDL_image", any_c_p "media-libs" "sdl-image")
       , ("SDL_ttf", any_c_p "media-libs" "sdl-ttf")
+      , ("odbc", any_c_p "dev-db" "unixODBC")
       ]
 
 ---------------------------------------------------------------
@@ -364,7 +365,7 @@
 ---------------------------------------------------------------
 
 buildToolsDependencies :: PackageDescription -> [Portage.Dependency]
-buildToolsDependencies (PackageDescription { library = lib, executables = exes }) = nub $
+buildToolsDependencies (PackageDescription { libraries = libs, executables = exes }) = nub $
   [ case pkg of
       Just p -> p
       Nothing -> trace ("WARNING: Unknown build tool '" ++ pn ++ "'. Check the generated ebuild.")
@@ -374,7 +375,7 @@
   ]
   where
   cabalDeps = filter notProvided $ depL ++ depE
-  depL = maybe [] (buildTools.libBuildInfo) lib
+  depL = concatMap (buildTools . libBuildInfo) libs
   depE = concatMap buildTools (filter buildable (map buildInfo exes))
   notProvided (Cabal.Dependency (Cabal.PackageName pn) _range) = pn `notElem` buildToolsProvided
 
@@ -405,10 +406,10 @@
 ---------------------------------------------------------------
 
 pkgConfigDependencies :: Portage.Overlay -> PackageDescription -> [Portage.Dependency]
-pkgConfigDependencies overlay (PackageDescription { library = lib, executables = exes }) = nub $ resolvePkgConfigs overlay cabalDeps
+pkgConfigDependencies overlay (PackageDescription { libraries = libs, executables = exes }) = nub $ resolvePkgConfigs overlay cabalDeps
   where
   cabalDeps = depL ++ depE
-  depL = maybe [] (pkgconfigDepends.libBuildInfo) lib
+  depL = concatMap (pkgconfigDepends . libBuildInfo) libs
   depE = concatMap pkgconfigDepends (filter buildable (map buildInfo exes))
 
 resolvePkgConfigs :: Portage.Overlay -> [Cabal.Dependency] -> [Portage.Dependency]
@@ -517,4 +518,11 @@
   ,("libpq",                       ("dev-db", "postgresql", Portage.AnySlot))
   ,("poppler-glib",                ("app-text", "poppler", Portage.AnySlot))
   ,("gsl",                         ("sci-libs", "gsl", Portage.AnySlot))
+  ,("libvirt",                     ("app-emulation", "libvirt", Portage.AnySlot))
+
+  ,("Qt5Core",                     ("dev-qt", "qtcore", Portage.GivenSlot "5"))
+  ,("Qt5Gui",                      ("dev-qt", "qtgui", Portage.GivenSlot "5"))
+  ,("Qt5Qml",                      ("dev-qt", "qtdeclarative", Portage.GivenSlot "5"))
+  ,("Qt5Quick",                    ("dev-qt", "qtdeclarative", Portage.GivenSlot "5"))
+  ,("Qt5Widgets",                  ("dev-qt", "qtwidgets", Portage.GivenSlot "5"))
   ]
diff --git a/Overlays.hs b/Overlays.hs
--- a/Overlays.hs
+++ b/Overlays.hs
@@ -5,11 +5,10 @@
 import Control.Monad
 import Data.List (nub, inits)
 import Data.Maybe (maybeToList, listToMaybe, isJust, fromJust)
-import System.Directory
+import qualified System.Directory as SD
 import System.FilePath ((</>), splitPath, joinPath)
 
 import Error
-import CacheFile
 import Portage.Host
 
 -- cabal
@@ -32,7 +31,7 @@
     let loop [] = throwEx (MultipleOverlays mul)
         loop (x:xs) = do
           info verbosity $ "Checking '" ++ x ++ "'..."
-          found <- doesFileExist (cacheFile x)
+          found <- SD.doesDirectoryExist (x </> ".hackport")
           if found
             then do
               info verbosity "OK!"
@@ -62,10 +61,9 @@
 
 getLocalOverlay :: IO (Maybe FilePath)
 getLocalOverlay = do
-  curDir <- getCurrentDirectory
+  curDir <- SD.getCurrentDirectory
   let lookIn = map joinPath . reverse . inits . splitPath $ curDir
   fmap listToMaybe (filterM probe lookIn)
 
   where
-    probe dir = doesDirectoryExist (dir </> "dev-haskell")
-
+    probe dir = SD.doesDirectoryExist (dir </> "dev-haskell")
diff --git a/Portage/EBuild.hs b/Portage/EBuild.hs
--- a/Portage/EBuild.hs
+++ b/Portage/EBuild.hs
@@ -7,7 +7,6 @@
         ) where
 
 import Portage.Dependency
-import qualified Portage.PackageId as PI
 import qualified Portage.Dependency.Normalize as PN
 
 import Data.String.Utils
@@ -18,9 +17,7 @@
 import Data.Version(Version(..))
 import qualified Paths_hackport(version)
 
-#if MIN_VERSION_time(1,5,0)
-import qualified System.Locale as SL
-#else
+#if ! MIN_VERSION_time(1,5,0)
 import qualified System.Locale as TC
 #endif
 
@@ -96,13 +93,13 @@
   ss "# Distributed under the terms of the GNU General Public License v2". nl.
   ss "# $Id$". nl.
   nl.
-  ss "EAPI=5". nl.
+  ss "EAPI=6". nl.
   nl.
   ss ("# ebuild generated by hackport " ++ hackportVersion ebuild). nl.
   sconcat (map (\(k, v) -> ss "#hackport: " . ss k . ss ": " . ss v . nl) $ used_options ebuild).
   nl.
   ss "CABAL_FEATURES=". quote' (sepBy " " $ features ebuild). nl.
-  ss "inherit haskell-cabal". if_games (ss " games") . nl.
+  ss "inherit haskell-cabal". nl.
   nl.
   (case my_pn ebuild of
      Nothing -> id
@@ -125,11 +122,6 @@
      Nothing -> id
      Just _ -> nl. ss "S=". quote ("${WORKDIR}/${MY_P}"). nl).
 
-  if_games (nl . ss "pkg_setup() {" . nl.
-            ss (tabify_line " games_pkg_setup") . nl.
-            ss (tabify_line " haskell-cabal_pkg_setup") . nl.
-            ss "}" . nl).
-
   verbatim (nl . ss "src_prepare() {" . nl)
                (src_prepare ebuild)
            (ss "}" . nl).
@@ -138,20 +130,6 @@
                (src_configure ebuild)
            (ss "}" . nl).
 
-  if_games (nl . ss "src_compile() {" . nl.
-            ss (tabify_line " haskell-cabal_src_compile") . nl.
-            ss "}" . nl).
-
-  if_games (nl . ss "src_install() {" . nl.
-            ss (tabify_line " haskell-cabal_src_install") . nl.
-            ss (tabify_line " prepgamesdirs") . nl.
-            ss "}" . nl).
-
-  if_games (nl . ss "pkg_postinst() {" . nl.
-            ss (tabify_line " haskell-cabal_pkg_postinst") . nl.
-            ss (tabify_line " games_pkg_postinst") . nl.
-            ss "}" . nl).
-
   id $ []
   where
         expandVars = replaceMultiVars [ (        name ebuild, "${PN}")
@@ -162,10 +140,6 @@
         toHttps  = replace "http://github.com/" "https://github.com/"
         this_year :: String
         this_year = TC.formatTime TC.defaultTimeLocale "%Y" now
-        if_games :: DString -> DString
-        if_games ds = if PI.is_games_cat (PI.Category (category ebuild))
-                          then ds
-                          else id
 
 -- "+a" -> "a"
 -- "b"  -> "b"
diff --git a/Portage/GHCCore.hs b/Portage/GHCCore.hs
--- a/Portage/GHCCore.hs
+++ b/Portage/GHCCore.hs
@@ -95,8 +95,7 @@
 mkIndex :: [PackageIdentifier] -> InstalledPackageIndex
 mkIndex pids = fromList
   [ emptyInstalledPackageInfo
-      { IPI.installedPackageId = InstalledPackageId $ display name ++ "-" ++  display version
-      , sourcePackageId = pindex
+      { sourcePackageId = pindex
       , exposed = True
       }
   | pindex@(PackageIdentifier name version) <- pids ]
diff --git a/Portage/Host.hs b/Portage/Host.hs
--- a/Portage/Host.hs
+++ b/Portage/Host.hs
@@ -4,6 +4,7 @@
   ) where
 
 import Util (run_cmd)
+import qualified Data.List.Split as DLS
 import Data.Maybe (fromJust, isJust, catMaybes)
 import Control.Applicative ( (<$>) )
 
@@ -73,7 +74,7 @@
 
 parsePaludisInfo :: String -> LocalInfo
 parsePaludisInfo text =
-  let chunks = splitBy (=="") . lines $ text
+  let chunks = DLS.splitOn [""] . lines $ text
       repositories = catMaybes (map parseRepository chunks)
   in  fromJust (mkLocalInfo repositories)
   where
@@ -97,13 +98,6 @@
               , portage_dir = gentooLocation
               , overlay_list = overlays
               })
-
-splitBy :: (a -> Bool) -> [a] -> [[a]]
-splitBy _ [] = []
-splitBy c lst =
-  let (x,xs) = break c lst
-      (_,xs') = span c xs
-  in x : splitBy c xs'
 
 ---------
 -- Emerge
diff --git a/Portage/PackageId.hs b/Portage/PackageId.hs
--- a/Portage/PackageId.hs
+++ b/Portage/PackageId.hs
@@ -12,12 +12,10 @@
     normalizeCabalPackageName,
     normalizeCabalPackageId,
     packageIdToFilePath,
-    cabal_pn_to_PN,
-    is_games_cat
+    cabal_pn_to_PN
   ) where
 
 import Data.Char
-import qualified Data.List as L
 
 import qualified Distribution.Package as Cabal
 import Distribution.Text (Text(..))
@@ -131,6 +129,3 @@
 
 cabal_pn_to_PN :: Cabal.PackageName -> String
 cabal_pn_to_PN = map toLower . display
-
-is_games_cat :: Category -> Bool
-is_games_cat = L.isPrefixOf "games" . unCategory
diff --git a/Progress.hs b/Progress.hs
deleted file mode 100644
--- a/Progress.hs
+++ /dev/null
@@ -1,61 +0,0 @@
------------------------------------------------------------------------------
--- |
--- Module      :  Progress
--- Copyright   :  (c) Duncan Coutts 2008
--- License     :  BSD-like
---
--- Portability :  portable
---
--- Common types for dependency resolution.
------------------------------------------------------------------------------
-module Progress (
-    Progress(..),
-    fold, unfold, fromList,
-  ) where
-
-import Prelude hiding (fail)
-
--- | A type to represent the unfolding of an expensive long running
--- calculation that may fail. We may get intermediate steps before the final
--- retult which may be used to indicate progress and\/or logging messages.
---
-data Progress step fail done = Step step (Progress step fail done)
-                             | Fail fail
-                             | Done done
-
--- | Consume a 'Progress' calculation. Much like 'foldr' for lists but with
--- two base cases, one for a final result and one for failure.
---
--- Eg to convert into a simple 'Either' result use:
---
--- > foldProgress (flip const) Left Right
---
-fold :: (step -> a -> a) -> (fail -> a) -> (done -> a)
-     -> Progress step fail done -> a
-fold step fail done = go
-  where
-    go (Step s p) = step s (go p)
-    go (Fail f)   = fail f
-    go (Done r)   = done r
-
-unfold :: (s -> Either (Either fail done) (step, s))
-       -> s -> Progress step fail done
-unfold f = go
-  where
-    go s = case f s of
-      Left (Left  fail) -> Fail fail
-      Left (Right done) -> Done done
-      Right (step, s')  -> Step step (go s')
-
-fromList :: [a] -> Progress () b [a]
-fromList xs0 = unfold next xs0
-  where
-    next []     = Left (Right xs0)
-    next (_:xs) = Right ((), xs)
-
-instance Functor (Progress step fail) where
-  fmap f = fold Step Fail (Done . f)
-
-instance Monad (Progress step fail) where
-  return a = Done a
-  p >>= f  = fold Step Fail f p
diff --git a/README.rst b/README.rst
--- a/README.rst
+++ b/README.rst
@@ -23,6 +23,7 @@
     $ cd ~/overlays
     $ git clone git://github.com/gentoo-haskell/gentoo-haskell.git
     $ cd gentoo-haskell
+    $ mkdir .hackport
     $ hackport update
     $ ls -1 .hackport/
         00-index.tar
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,5 +1,5 @@
 #!/usr/bin/runhaskell
-module Main where
+module Main (main) where
 
 import Distribution.Simple
 
diff --git a/Status.hs b/Status.hs
--- a/Status.hs
+++ b/Status.hs
@@ -28,16 +28,15 @@
 import Control.Monad
 
 -- cabal
-import Distribution.Client.Types ( Repo, SourcePackageDb(..), SourcePackage(..) )
+import Distribution.Client.Types ( SourcePackageDb(..), SourcePackage(..) )
 import Distribution.Verbosity
 import Distribution.Package (pkgName)
 import Distribution.Simple.Utils (comparing, die, equating)
 import Distribution.Text ( display, simpleParse )
 
-import qualified Distribution.Client.PackageIndex as CabalInstall
+import qualified Distribution.Client.GlobalFlags as CabalInstall
 import qualified Distribution.Client.IndexUtils as CabalInstall
-
-import Hackage (defaultRepo)
+import qualified Distribution.Client.PackageIndex as CabalInstall
 
 data StatusDirection
     = PortagePlusOverlay
@@ -76,9 +75,9 @@
 
 
 
-loadHackage :: Verbosity -> Distribution.Client.Types.Repo -> Overlay -> IO [[PackageId]]
-loadHackage verbosity repo overlay = do
-    SourcePackageDb { packageIndex = pindex } <- CabalInstall.getSourcePackages verbosity [repo]
+loadHackage :: Verbosity -> CabalInstall.RepoContext -> Overlay -> IO [[PackageId]]
+loadHackage verbosity repoContext overlay = do
+    SourcePackageDb { packageIndex = pindex } <- CabalInstall.getSourcePackages verbosity repoContext
     let get_cat cabal_pkg = case resolveCategories overlay (pkgName cabal_pkg) of
                                 []    -> Category "dev-haskell"
                                 [cat] -> cat
@@ -89,11 +88,10 @@
                         (CabalInstall.allPackagesByName pindex)
     return pkg_infos
 
-status :: Verbosity -> FilePath -> FilePath -> IO (Map PackageName [FileStatus ExistingEbuild])
-status verbosity portdir overlaydir = do
-    let repo = defaultRepo overlaydir
+status :: Verbosity -> FilePath -> FilePath -> CabalInstall.RepoContext -> IO (Map PackageName [FileStatus ExistingEbuild])
+status verbosity portdir overlaydir repoContext = do
     overlay <- loadLazy overlaydir
-    hackage <- loadHackage verbosity repo overlay
+    hackage <- loadHackage verbosity repoContext overlay
     portage <- filterByEmail ("haskell@gentoo.org" `elem`) <$> loadLazy portdir
     let (over, both, port) = portageDiff (overlayMap overlay) (overlayMap portage)
 
@@ -132,8 +130,8 @@
   ebuilds <- Map.lookup (packageId pkgid) overlay
   List.find (\e -> ebuildId e == pkgid) ebuilds
 
-runStatus :: Verbosity -> FilePath -> FilePath -> StatusDirection -> [String] -> IO ()
-runStatus verbosity portdir overlaydir direction pkgs = do
+runStatus :: Verbosity -> FilePath -> FilePath -> StatusDirection -> [String] -> CabalInstall.RepoContext -> IO ()
+runStatus verbosity portdir overlaydir direction pkgs repoContext = do
   let pkgFilter = case direction of
                       OverlayToPortage   -> toPortageFilter
                       PortagePlusOverlay -> id
@@ -142,7 +140,7 @@
             case simpleParse p of
               Nothing -> die ("Could not parse package name: " ++ p ++ ". Format cat/pkg")
               Just pn -> return pn
-  tree0 <- status verbosity portdir overlaydir
+  tree0 <- status verbosity portdir overlaydir repoContext
   let tree = pkgFilter tree0
   if (null pkgs')
     then statusPrinter tree
diff --git a/Util.hs b/Util.hs
--- a/Util.hs
+++ b/Util.hs
@@ -8,7 +8,6 @@
 
 module Util
     ( run_cmd -- :: String -> IO (Maybe String)
-    , split -- :: (a -> Bool) -> [a] -> [[a]]
     ) where
 
 import System.IO
@@ -30,10 +29,3 @@
                  return $ if (output == "" || exitCode /= ExitSuccess)
                           then Nothing
                           else Just output
-
-split :: Eq a => (a -> Bool) -> [a] -> [[a]]
-split _ [] = []
-split p xs =
-    case break p xs of
-        (l, [])  -> [l]
-        (l, _:r) -> l: split p r
diff --git a/cabal/.arcconfig b/cabal/.arcconfig
new file mode 100644
--- /dev/null
+++ b/cabal/.arcconfig
@@ -0,0 +1,4 @@
+{
+  "repository.callsign" : "CABAL",
+  "phabricator.uri"     : "https://phabricator.haskell.org"
+}
diff --git a/cabal/.gitignore b/cabal/.gitignore
--- a/cabal/.gitignore
+++ b/cabal/.gitignore
@@ -15,9 +15,11 @@
 /Cabal/dist/
 /Cabal/tests/Setup
 /Cabal/Setup
+/Cabal/source-file-list
 
 /cabal-install/dist/
 /cabal-install/Setup
+/cabal-install/source-file-list
 
 
 # editor temp files
@@ -26,6 +28,7 @@
 .#*
 *~
 .*.swp
+*.bak
 
 # GHC build
 
@@ -41,3 +44,11 @@
 
 # stack artifacts
 /.stack-work/
+
+# Shake artifacts
+.shake*
+progress.txt
+
+# test files
+dist-test
+register.sh
diff --git a/cabal/.travis.yml b/cabal/.travis.yml
--- a/cabal/.travis.yml
+++ b/cabal/.travis.yml
@@ -1,70 +1,37 @@
 # NB: don't set `language: haskell` here
 
-# The following enables several GHC versions to be tested; often it's enough to test only against the last release in a major GHC version. Feel free to omit lines listings versions you don't need/want testing for.
+# The following enables several GHC versions to be tested; often it's enough to
+# test only against the last release in a major GHC version. Feel free to omit
+# lines listings versions you don't need/want testing for.
 env:
  - GHCVER=7.4.2
  - GHCVER=7.6.3
  - GHCVER=7.8.4
- - GHCVER=7.10.1
+ - GHCVER=7.10.3
+ - GHCVER=8.0.1 TEST_OLDER=YES
+ # TODO add PARSEC_BUNDLED=YES when it's so
  - GHCVER=head
 
 # Note: the distinction between `before_install` and `install` is not important.
 before_install:
  - travis_retry sudo add-apt-repository -y ppa:hvr/ghc
  - travis_retry sudo apt-get update
- - travis_retry sudo apt-get install cabal-install-1.22 ghc-$GHCVER-prof ghc-$GHCVER-dyn happy
- - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.22/bin:$PATH
+ - travis_retry sudo apt-get install cabal-install-1.24 ghc-$GHCVER-prof ghc-$GHCVER-dyn happy
+ - if [ "$TEST_OLDER" == "YES" ]; then travis_retry sudo apt-get install ghc-7.0.4-prof ghc-7.0.4-dyn ghc-7.2.2-prof ghc-7.2.2-dyn; fi
+ - export PATH=$HOME/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/1.24/bin:$PATH
+ - git version
 
 install:
  - cabal update
-# We intentionally do not install anything before trying to build Cabal because
-# it should build with each supported GHC version out-of-the-box.
+ # We intentionally do not install anything before trying to build Cabal because
+ # it should build with each supported GHC version out-of-the-box.
 
-# Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail.
-# Using ./dist/setup/setup here instead of cabal-install to avoid breakage
-# when the build config format changed
+# Here starts the actual work to be performed for the package under test; any
+# command which exits with a non-zero exit code causes the build to fail. Using
+# ./dist/setup/setup here instead of cabal-install to avoid breakage when the
+# build config format changed.
 script:
- - cd Cabal
- - mkdir -p ./dist/setup
- - cp Setup.hs ./dist/setup/setup.hs
-# Should be able to build setup without extra dependencies
- - /opt/ghc/$GHCVER/bin/ghc --make -odir ./dist/setup -hidir ./dist/setup -i -i. ./dist/setup/setup.hs -o ./dist/setup/setup -Wall -Werror -threaded  # the command cabal-install would use to build setup
-
-# Need extra dependencies for test suite
- - cabal install --only-dependencies --enable-tests
- - sudo /opt/ghc/$GHCVER/bin/ghc-pkg recache
- - /opt/ghc/$GHCVER/bin/ghc-pkg recache --user
-
- - ./dist/setup/setup configure --user --enable-tests --enable-benchmarks --ghc-option=-Werror -v2  # -v2 provides useful information for debugging
- - ./dist/setup/setup build   # this builds all libraries and executables (including tests/benchmarks)
- - ./dist/setup/setup haddock # see #2198
- - ./dist/setup/setup test --show-details=streaming
- - cabal check
- - cabal sdist   # tests that a source-distribution can be generated
-
-# The following scriptlet checks that the resulting source distribution can be built & installed
- - function install_from_tarball {
-   export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;
-   if [ -f "dist/$SRC_TGZ" ]; then
-      cabal install "dist/$SRC_TGZ" -v2;
-   else
-      echo "expected 'dist/$SRC_TGZ' not found";
-      exit 1;
-   fi
-   }
- - install_from_tarball
-
-# Also build cabal-install.
- - cd ../cabal-install
- - cabal sandbox init
- - cabal sandbox add-source ../Cabal
- - cabal install --dependencies-only --enable-tests
- - cabal configure --enable-tests --ghc-option=-Werror
- - cabal build
- - cabal test
- - cabal check
- - cabal sdist
- - install_from_tarball
+ - ./travis-script.sh
 
 matrix:
   allow_failures:
diff --git a/cabal/Cabal/Cabal.cabal b/cabal/Cabal/Cabal.cabal
--- a/cabal/Cabal/Cabal.cabal
+++ b/cabal/Cabal/Cabal.cabal
@@ -1,5 +1,5 @@
 name: Cabal
-version: 1.23.0.0
+version: 1.25.0.0
 copyright: 2003-2006, Isaac Jones
            2005-2011, Duncan Coutts
 license: BSD3
@@ -19,9 +19,9 @@
   organizing, and cataloging Haskell libraries and tools.
 category: Distribution
 cabal-version: >=1.10
-build-type: Custom
--- Even though we do use the default Setup.lhs it's vital to bootstrapping
--- that we build Setup.lhs using our own local Cabal source code.
+build-type: Simple
+-- If we use a new Cabal feature, this needs to be changed to Custom so
+-- we can bootstrap.
 
 extra-source-files:
   README.md tests/README.md changelog
@@ -29,7 +29,13 @@
   doc/installing-packages.markdown
   doc/misc.markdown
 
-  -- Generated with 'misc/gen-extra-source-files.sh' & 'M-x sort-lines':
+  -- Generated with 'misc/gen-extra-source-files.sh'
+  -- Do NOT edit this section manually; instead, run the script.
+  -- BEGIN gen-extra-source-files
+  tests/PackageTests/AllowNewer/AllowNewer.cabal
+  tests/PackageTests/AllowNewer/benchmarks/Bench.hs
+  tests/PackageTests/AllowNewer/src/Foo.hs
+  tests/PackageTests/AllowNewer/tests/Test.hs
   tests/PackageTests/BenchmarkExeV10/Foo.hs
   tests/PackageTests/BenchmarkExeV10/benchmarks/bench-Foo.hs
   tests/PackageTests/BenchmarkExeV10/my.cabal
@@ -74,18 +80,78 @@
   tests/PackageTests/BuildDeps/TargetSpecificDeps3/MyLibrary.hs
   tests/PackageTests/BuildDeps/TargetSpecificDeps3/lemon.hs
   tests/PackageTests/BuildDeps/TargetSpecificDeps3/my.cabal
+  tests/PackageTests/BuildTestSuiteDetailedV09/Dummy2.hs
+  tests/PackageTests/BuildableField/BuildableField.cabal
+  tests/PackageTests/BuildableField/Main.hs
   tests/PackageTests/CMain/Bar.hs
-  tests/PackageTests/CMain/Setup.hs
   tests/PackageTests/CMain/foo.c
   tests/PackageTests/CMain/my.cabal
+  tests/PackageTests/Configure/A.hs
+  tests/PackageTests/Configure/Setup.hs
+  tests/PackageTests/Configure/X11.cabal
+  tests/PackageTests/CopyComponent/Exe/Main.hs
+  tests/PackageTests/CopyComponent/Exe/Main2.hs
+  tests/PackageTests/CopyComponent/Exe/myprog.cabal
+  tests/PackageTests/CopyComponent/Lib/Main.hs
+  tests/PackageTests/CopyComponent/Lib/p.cabal
+  tests/PackageTests/CopyComponent/Lib/src/P.hs
+  tests/PackageTests/CustomPreProcess/Hello.hs
+  tests/PackageTests/CustomPreProcess/MyCustomPreprocessor.hs
+  tests/PackageTests/CustomPreProcess/Setup.hs
+  tests/PackageTests/CustomPreProcess/internal-preprocessor-test.cabal
   tests/PackageTests/DeterministicAr/Lib.hs
   tests/PackageTests/DeterministicAr/my.cabal
+  tests/PackageTests/DuplicateModuleName/DuplicateModuleName.cabal
+  tests/PackageTests/DuplicateModuleName/src/Foo.hs
+  tests/PackageTests/DuplicateModuleName/tests/Foo.hs
+  tests/PackageTests/DuplicateModuleName/tests2/Foo.hs
   tests/PackageTests/EmptyLib/empty/empty.cabal
+  tests/PackageTests/GhcPkgGuess/SameDirectory/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/SameDirectory/ghc
+  tests/PackageTests/GhcPkgGuess/SameDirectory/ghc-pkg
+  tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/ghc-7.10
+  tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/ghc-pkg-ghc-7.10
+  tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/ghc-7.10
+  tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/ghc-pkg-7.10
+  tests/PackageTests/GhcPkgGuess/Symlink/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/Symlink/bin/ghc
+  tests/PackageTests/GhcPkgGuess/Symlink/bin/ghc-pkg
+  tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/bin/ghc-7.10
+  tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/bin/ghc-pkg-7.10
+  tests/PackageTests/GhcPkgGuess/SymlinkVersion/SameDirectory.cabal
+  tests/PackageTests/GhcPkgGuess/SymlinkVersion/bin/ghc-7.10
+  tests/PackageTests/GhcPkgGuess/SymlinkVersion/bin/ghc-pkg-ghc-7.10
   tests/PackageTests/Haddock/CPP.hs
   tests/PackageTests/Haddock/Literate.lhs
   tests/PackageTests/Haddock/NoCPP.hs
   tests/PackageTests/Haddock/Simple.hs
   tests/PackageTests/Haddock/my.cabal
+  tests/PackageTests/HaddockNewline/A.hs
+  tests/PackageTests/HaddockNewline/HaddockNewline.cabal
+  tests/PackageTests/HaddockNewline/Setup.hs
+  tests/PackageTests/InternalLibraries/Executable/exe/Main.hs
+  tests/PackageTests/InternalLibraries/Executable/foo.cabal
+  tests/PackageTests/InternalLibraries/Executable/src/Foo.hs
+  tests/PackageTests/InternalLibraries/Library/fooexe/Main.hs
+  tests/PackageTests/InternalLibraries/Library/fooexe/fooexe.cabal
+  tests/PackageTests/InternalLibraries/Library/foolib/Foo.hs
+  tests/PackageTests/InternalLibraries/Library/foolib/foolib.cabal
+  tests/PackageTests/InternalLibraries/Library/foolib/private/Internal.hs
+  tests/PackageTests/InternalLibraries/p/Foo.hs
+  tests/PackageTests/InternalLibraries/p/p.cabal
+  tests/PackageTests/InternalLibraries/p/p/P.hs
+  tests/PackageTests/InternalLibraries/p/q/Q.hs
+  tests/PackageTests/InternalLibraries/q/Q.hs
+  tests/PackageTests/InternalLibraries/q/q.cabal
+  tests/PackageTests/Macros/A.hs
+  tests/PackageTests/Macros/B.hs
+  tests/PackageTests/Macros/Main.hs
+  tests/PackageTests/Macros/macros.cabal
+  tests/PackageTests/Macros/src/C.hs
+  tests/PackageTests/Options.hs
   tests/PackageTests/OrderFlags/Foo.hs
   tests/PackageTests/OrderFlags/my.cabal
   tests/PackageTests/PathsModule/Executable/Main.hs
@@ -94,6 +160,9 @@
   tests/PackageTests/PreProcess/Foo.hsc
   tests/PackageTests/PreProcess/Main.hs
   tests/PackageTests/PreProcess/my.cabal
+  tests/PackageTests/PreProcessExtraSources/Foo.hsc
+  tests/PackageTests/PreProcessExtraSources/Main.hs
+  tests/PackageTests/PreProcessExtraSources/my.cabal
   tests/PackageTests/ReexportedModules/ReexportedModules.cabal
   tests/PackageTests/TemplateHaskell/dynamic/Exe.hs
   tests/PackageTests/TemplateHaskell/dynamic/Lib.hs
@@ -107,20 +176,33 @@
   tests/PackageTests/TemplateHaskell/vanilla/Lib.hs
   tests/PackageTests/TemplateHaskell/vanilla/TH.hs
   tests/PackageTests/TemplateHaskell/vanilla/my.cabal
+  tests/PackageTests/TestNameCollision/child/Child.hs
+  tests/PackageTests/TestNameCollision/child/child.cabal
+  tests/PackageTests/TestNameCollision/child/tests/Test.hs
+  tests/PackageTests/TestNameCollision/parent/Parent.hs
+  tests/PackageTests/TestNameCollision/parent/parent.cabal
   tests/PackageTests/TestOptions/TestOptions.cabal
   tests/PackageTests/TestOptions/test-TestOptions.hs
   tests/PackageTests/TestStanza/my.cabal
   tests/PackageTests/TestSuiteTests/ExeV10/Foo.hs
   tests/PackageTests/TestSuiteTests/ExeV10/my.cabal
   tests/PackageTests/TestSuiteTests/ExeV10/tests/test-Foo.hs
-  tests/PackageTests/TestSuiteTests/LibV09/LibV09.cabal
+  tests/PackageTests/TestSuiteTests/ExeV10/tests/test-Short.hs
   tests/PackageTests/TestSuiteTests/LibV09/Lib.hs
+  tests/PackageTests/TestSuiteTests/LibV09/LibV09.cabal
   tests/PackageTests/TestSuiteTests/LibV09/tests/Deadlock.hs
+  tests/PackageTests/Tests.hs
+  tests/PackageTests/UniqueIPID/P1/M.hs
+  tests/PackageTests/UniqueIPID/P1/my.cabal
+  tests/PackageTests/UniqueIPID/P2/M.hs
+  tests/PackageTests/UniqueIPID/P2/my.cabal
+  tests/PackageTests/multInst/my.cabal
   tests/Setup.hs
   tests/hackage/check.sh
   tests/hackage/download.sh
   tests/hackage/unpack.sh
   tests/misc/ghc-supported-languages.hs
+  -- END gen-extra-source-files
 
 source-repository head
   type:     git
@@ -132,21 +214,21 @@
 
 library
   build-depends:
-    base       >= 4.4 && < 5,
-    deepseq    >= 1.3 && < 1.5,
-    filepath   >= 1   && < 1.5,
-    directory  >= 1   && < 1.3,
-    process    >= 1.1.0.1 && < 1.4,
-    time       >= 1.1 && < 1.6,
-    containers >= 0.1 && < 0.6,
     array      >= 0.1 && < 0.6,
-    pretty     >= 1   && < 1.2,
-    bytestring >= 0.9
+    base       >= 4.5 && < 5,
+    bytestring >= 0.9 && < 1,
+    containers >= 0.4 && < 0.6,
+    deepseq    >= 1.3 && < 1.5,
+    directory  >= 1.1 && < 1.3,
+    filepath   >= 1.3 && < 1.5,
+    pretty     >= 1.1 && < 1.2,
+    process    >= 1.1.0.1 && < 1.5,
+    time       >= 1.4 && < 1.7
 
   if flag(bundled-binary-generic)
     build-depends: binary >= 0.5 && < 0.7
   else
-    build-depends: binary >= 0.7 && < 0.8
+    build-depends: binary >= 0.7 && < 0.9
 
   -- Needed for GHC.Generics before GHC 7.6
   if impl(ghc < 7.6)
@@ -154,15 +236,24 @@
 
   if !os(windows)
     build-depends:
-      unix >= 2.0 && < 2.8
+      unix >= 2.5 && < 2.8
 
+  if os(windows)
+    build-depends:
+      Win32 >= 2.2 && < 2.4
+
   ghc-options: -Wall -fno-ignore-asserts -fwarn-tabs
+  if impl(ghc >= 8.0)
+    ghc-options: -Wcompat -Wnoncanonical-monad-instances
+                 -Wnoncanonical-monadfail-instances
 
   exposed-modules:
     Distribution.Compat.CreatePipe
     Distribution.Compat.Environment
     Distribution.Compat.Exception
+    Distribution.Compat.Internal.TempFile
     Distribution.Compat.ReadP
+    Distribution.Compat.Semigroup
     Distribution.Compiler
     Distribution.InstalledPackageInfo
     Distribution.License
@@ -232,16 +323,16 @@
     Distribution.Verbosity
     Distribution.Version
     Language.Haskell.Extension
+    Distribution.Compat.Binary
 
   other-modules:
-    Distribution.Compat.Binary
     Distribution.Compat.CopyFile
-    Distribution.Compat.TempFile
+    Distribution.Compat.MonadFail
     Distribution.GetOpt
     Distribution.Lex
     Distribution.Simple.GHC.Internal
-    Distribution.Simple.GHC.IPI641
     Distribution.Simple.GHC.IPI642
+    Distribution.Simple.GHC.IPIConvert
     Distribution.Simple.GHC.ImplInfo
     Paths_Cabal
 
@@ -251,25 +342,33 @@
       Distribution.Compat.Binary.Generic
 
   default-language: Haskell98
-  default-extensions: CPP
+  -- starting with GHC 7.0, rely on {-# LANGUAGE CPP #-} instead
+  if !impl(ghc >= 7.0)
+    default-extensions: CPP
 
 -- Small, fast running tests.
 test-suite unit-tests
   type: exitcode-stdio-1.0
   hs-source-dirs: tests
   other-modules:
+    Test.Laws
+    Test.QuickCheck.Utils
     UnitTests.Distribution.Compat.CreatePipe
     UnitTests.Distribution.Compat.ReadP
     UnitTests.Distribution.Simple.Program.Internal
+    UnitTests.Distribution.Simple.Utils
+    UnitTests.Distribution.System
     UnitTests.Distribution.Utils.NubList
+    UnitTests.Distribution.Version
   main-is: UnitTests.hs
   build-depends:
     base,
+    directory,
     tasty,
     tasty-hunit,
     tasty-quickcheck,
     pretty,
-    QuickCheck < 2.9,
+    QuickCheck >= 2.7 && < 2.9,
     Cabal
   ghc-options: -Wall
   default-language: Haskell98
@@ -279,57 +378,28 @@
   type: exitcode-stdio-1.0
   main-is: PackageTests.hs
   other-modules:
-    PackageTests.BenchmarkExeV10.Check
-    PackageTests.BenchmarkOptions.Check
     PackageTests.BenchmarkStanza.Check
-    PackageTests.BuildDeps.GlobalBuildDepsNotAdditive1.Check
-    PackageTests.BuildDeps.GlobalBuildDepsNotAdditive2.Check
-    PackageTests.BuildDeps.InternalLibrary0.Check
-    PackageTests.BuildDeps.InternalLibrary1.Check
-    PackageTests.BuildDeps.InternalLibrary2.Check
-    PackageTests.BuildDeps.InternalLibrary3.Check
-    PackageTests.BuildDeps.InternalLibrary4.Check
-    PackageTests.BuildDeps.SameDepsAllRound.Check
-    PackageTests.BuildDeps.TargetSpecificDeps1.Check
-    PackageTests.BuildDeps.TargetSpecificDeps2.Check
-    PackageTests.BuildDeps.TargetSpecificDeps3.Check
-    PackageTests.CMain.Check
-    PackageTests.DeterministicAr.Check
-    PackageTests.EmptyLib.Check
-    PackageTests.Haddock.Check
-    PackageTests.OrderFlags.Check
-    PackageTests.PackageTester
-    PackageTests.PathsModule.Executable.Check
-    PackageTests.PathsModule.Library.Check
-    PackageTests.PreProcess.Check
-    PackageTests.PreProcessExtraSources.Check
-    PackageTests.ReexportedModules.Check
-    PackageTests.TemplateHaskell.Check
-    PackageTests.TestOptions.Check
     PackageTests.TestStanza.Check
+    PackageTests.DeterministicAr.Check
     PackageTests.TestSuiteTests.ExeV10.Check
-    PackageTests.TestSuiteTests.LibV09.Check
-    Test.Distribution.Version
-    Test.Laws
-    Test.QuickCheck.Utils
+    PackageTests.PackageTester
   hs-source-dirs: tests
   build-depends:
     base,
     containers,
+    tagged,
     tasty,
-    tasty-quickcheck,
     tasty-hunit,
-    QuickCheck >= 2.1.0.1 && < 2.9,
+    transformers,
     Cabal,
     process,
     directory,
     filepath,
-    extensible-exceptions,
     bytestring,
     regex-posix,
     old-time
   if !os(windows)
     build-depends: unix
-  ghc-options: -Wall
+  ghc-options: -Wall -rtsopts
   default-extensions: CPP
   default-language: Haskell98
diff --git a/cabal/Cabal/Distribution/Compat/Binary/Class.hs b/cabal/Cabal/Distribution/Compat/Binary/Class.hs
--- a/cabal/Cabal/Distribution/Compat/Binary/Class.hs
+++ b/cabal/Cabal/Distribution/Compat/Binary/Class.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP, FlexibleContexts #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE DefaultSignatures #-}
 -----------------------------------------------------------------------------
@@ -53,13 +53,8 @@
 
 import GHC.Generics
 
---
--- This isn't available in older Hugs or older GHC
---
-#if __GLASGOW_HASKELL__ >= 606
 import qualified Data.Sequence as Seq
 import qualified Data.Foldable as Fold
-#endif
 
 ------------------------------------------------------------------------
 
@@ -467,11 +462,6 @@
 ------------------------------------------------------------------------
 -- Queues and Sequences
 
-#if __GLASGOW_HASKELL__ >= 606
---
--- This is valid Hugs, but you need the most recent Hugs
---
-
 instance (Binary e) => Binary (Seq.Seq e) where
     put s = put (Seq.length s) >> Fold.mapM_ put s
     get = do n <- get :: Get Int
@@ -480,8 +470,6 @@
             rep xs n g = xs `seq` n `seq` do
                            x <- g
                            rep (xs Seq.|> x) (n-1) g
-
-#endif
 
 ------------------------------------------------------------------------
 -- Floating point
diff --git a/cabal/Cabal/Distribution/Compat/CopyFile.hs b/cabal/Cabal/Distribution/Compat/CopyFile.hs
--- a/cabal/Cabal/Distribution/Compat/CopyFile.hs
+++ b/cabal/Cabal/Distribution/Compat/CopyFile.hs
@@ -11,24 +11,22 @@
   setDirOrdinary,
   ) where
 
+import Distribution.Compat.Exception
+import Distribution.Compat.Internal.TempFile
 
 import Control.Monad
          ( when, unless )
 import Control.Exception
-         ( bracket, bracketOnError, throwIO )
+         ( bracketOnError, throwIO )
 import qualified Data.ByteString.Lazy as BSL
-import Distribution.Compat.Exception
-         ( catchIO )
 import System.IO.Error
          ( ioeSetLocation )
 import System.Directory
          ( doesFileExist, renameFile, removeFile )
-import Distribution.Compat.TempFile
-         ( openBinaryTempFile )
 import System.FilePath
          ( takeDirectory )
 import System.IO
-         ( openBinaryFile, IOMode(ReadMode), hClose, hGetBuf, hPutBuf
+         ( IOMode(ReadMode), hClose, hGetBuf, hPutBuf
          , withBinaryFile )
 import Foreign
          ( allocaBytes )
@@ -69,7 +67,7 @@
 copyFile fromFPath toFPath =
   copy
     `catchIO` (\ioe -> throwIO (ioeSetLocation ioe "copyFile"))
-    where copy = bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
+    where copy = withBinaryFile fromFPath ReadMode $ \hFrom ->
                  bracketOnError openTmp cleanTmp $ \(tmpFPath, hTmp) ->
                  do allocaBytes bufferSize $ copyContents hFrom hTmp
                     hClose hTmp
@@ -100,8 +98,7 @@
 filesEqual f1 f2 = do
   ex1 <- doesFileExist f1
   ex2 <- doesFileExist f2
-  if not (ex1 && ex2) then return False else do
-
+  if not (ex1 && ex2) then return False else
     withBinaryFile f1 ReadMode $ \h1 ->
       withBinaryFile f2 ReadMode $ \h2 -> do
         c1 <- BSL.hGetContents h1
diff --git a/cabal/Cabal/Distribution/Compat/Internal/TempFile.hs b/cabal/Cabal/Distribution/Compat/Internal/TempFile.hs
new file mode 100644
--- /dev/null
+++ b/cabal/Cabal/Distribution/Compat/Internal/TempFile.hs
@@ -0,0 +1,124 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_HADDOCK hide #-}
+module Distribution.Compat.Internal.TempFile (
+  openTempFile,
+  openBinaryTempFile,
+  openNewBinaryFile,
+  createTempDirectory,
+  ) where
+
+import Distribution.Compat.Exception
+
+import System.FilePath        ((</>))
+import Foreign.C              (CInt, eEXIST, getErrno, errnoToIOError)
+
+import System.IO              (Handle, openTempFile, openBinaryTempFile)
+import Data.Bits              ((.|.))
+import System.Posix.Internals (c_open, c_close, o_CREAT, o_EXCL, o_RDWR,
+                               o_BINARY, o_NONBLOCK, o_NOCTTY,
+                               withFilePath, c_getpid)
+import System.IO.Error        (isAlreadyExistsError)
+import GHC.IO.Handle.FD       (fdToHandle)
+import Control.Exception      (onException)
+
+#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)
+import System.Directory       ( createDirectory )
+#else
+import qualified System.Posix
+#endif
+
+-- ------------------------------------------------------------
+-- * temporary files
+-- ------------------------------------------------------------
+
+-- This is here for Haskell implementations that do not come with
+-- System.IO.openTempFile. This includes nhc-1.20, hugs-2006.9.
+-- TODO: Not sure about JHC
+-- TODO: This file should probably be removed.
+
+-- This is a copy/paste of the openBinaryTempFile definition, but
+-- if uses 666 rather than 600 for the permissions. The base library
+-- needs to be changed to make this better.
+openNewBinaryFile :: FilePath -> String -> IO (FilePath, Handle)
+openNewBinaryFile dir template = do
+  pid <- c_getpid
+  findTempName pid
+  where
+    -- We split off the last extension, so we can use .foo.ext files
+    -- for temporary files (hidden on Unix OSes). Unfortunately we're
+    -- below file path in the hierarchy here.
+    (prefix,suffix) =
+       case break (== '.') $ reverse template of
+         -- First case: template contains no '.'s. Just re-reverse it.
+         (rev_suffix, "")       -> (reverse rev_suffix, "")
+         -- Second case: template contains at least one '.'. Strip the
+         -- dot from the prefix and prepend it to the suffix (if we don't
+         -- do this, the unique number will get added after the '.' and
+         -- thus be part of the extension, which is wrong.)
+         (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)
+         -- Otherwise, something is wrong, because (break (== '.')) should
+         -- always return a pair with either the empty string or a string
+         -- beginning with '.' as the second component.
+         _                      -> error "bug in System.IO.openTempFile"
+
+    oflags = rw_flags .|. o_EXCL .|. o_BINARY
+
+    findTempName x = do
+      fd <- withFilePath filepath $ \ f ->
+              c_open f oflags 0o666
+      if fd < 0
+       then do
+         errno <- getErrno
+         if errno == eEXIST
+           then findTempName (x+1)
+           else ioError (errnoToIOError "openNewBinaryFile" errno Nothing (Just dir))
+       else do
+         -- TODO: We want to tell fdToHandle what the file path is,
+         -- as any exceptions etc will only be able to report the
+         -- FD currently
+         h <- fdToHandle fd `onException` c_close fd
+         return (filepath, h)
+      where
+        filename        = prefix ++ show x ++ suffix
+        filepath        = dir `combine` filename
+
+        -- FIXME: bits copied from System.FilePath
+        combine a b
+                  | null b = a
+                  | null a = b
+                  | last a == pathSeparator = a ++ b
+                  | otherwise = a ++ [pathSeparator] ++ b
+
+-- FIXME: Should use System.FilePath library
+pathSeparator :: Char
+#ifdef mingw32_HOST_OS
+pathSeparator = '\\'
+#else
+pathSeparator = '/'
+#endif
+
+-- FIXME: Copied from GHC.Handle
+std_flags, output_flags, rw_flags :: CInt
+std_flags    = o_NONBLOCK   .|. o_NOCTTY
+output_flags = std_flags    .|. o_CREAT
+rw_flags     = output_flags .|. o_RDWR
+
+createTempDirectory :: FilePath -> String -> IO FilePath
+createTempDirectory dir template = do
+  pid <- c_getpid
+  findTempName pid
+  where
+    findTempName x = do
+      let dirpath = dir </> template ++ "-" ++ show x
+      r <- tryIO $ mkPrivateDir dirpath
+      case r of
+        Right _ -> return dirpath
+        Left  e | isAlreadyExistsError e -> findTempName (x+1)
+                | otherwise              -> ioError e
+
+mkPrivateDir :: String -> IO ()
+#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)
+mkPrivateDir s = createDirectory s
+#else
+mkPrivateDir s = System.Posix.createDirectory s 0o700
+#endif
diff --git a/cabal/Cabal/Distribution/Compat/MonadFail.hs b/cabal/Cabal/Distribution/Compat/MonadFail.hs
new file mode 100644
--- /dev/null
+++ b/cabal/Cabal/Distribution/Compat/MonadFail.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE CPP #-}
+
+-- | Compatibility layer for "Control.Monad.Fail"
+module Distribution.Compat.MonadFail ( MonadFail(fail) ) where
+#if __GLASGOW_HASKELL__ >= 800
+-- provided by base-4.9.0.0 and later
+import Control.Monad.Fail (MonadFail(fail))
+#else
+-- the following code corresponds to
+-- http://hackage.haskell.org/package/fail-4.9.0.0
+import qualified Prelude as P
+import Prelude hiding (fail)
+
+import Text.ParserCombinators.ReadP
+import Text.ParserCombinators.ReadPrec
+
+class Monad m => MonadFail m where
+    fail :: String -> m a
+
+-- instances provided by base-4.9
+
+instance MonadFail Maybe where
+    fail _ = Nothing
+
+instance MonadFail [] where
+    fail _ = []
+
+instance MonadFail IO where
+    fail = P.fail
+
+instance MonadFail ReadPrec where
+    fail = P.fail -- = P (\_ -> fail s)
+
+instance MonadFail ReadP where
+    fail = P.fail
+#endif
diff --git a/cabal/Cabal/Distribution/Compat/ReadP.hs b/cabal/Cabal/Distribution/Compat/ReadP.hs
--- a/cabal/Cabal/Distribution/Compat/ReadP.hs
+++ b/cabal/Cabal/Distribution/Compat/ReadP.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Compat.ReadP
@@ -70,12 +69,11 @@
   )
  where
 
-import Control.Monad( MonadPlus(..), liftM, liftM2, ap )
+import qualified Distribution.Compat.MonadFail as Fail
+
+import Control.Monad( MonadPlus(..), liftM, liftM2, replicateM, ap, (>=>) )
 import Data.Char (isSpace)
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative (Applicative(..))
-#endif
-import Control.Applicative (Alternative(empty, (<|>)))
+import Control.Applicative as AP (Applicative(..), Alternative(empty, (<|>)))
 
 infixr 5 +++, <++
 
@@ -96,18 +94,21 @@
   fmap = liftM
 
 instance Applicative (P s) where
-  pure = return
+  pure x = Result x Fail
   (<*>) = ap
 
 instance Monad (P s) where
-  return x = Result x Fail
+  return = AP.pure
 
-  (Get f)      >>= k = Get (\c -> f c >>= k)
-  (Look f)     >>= k = Look (\s -> f s >>= k)
+  (Get f)      >>= k = Get (f >=> k)
+  (Look f)     >>= k = Look (f >=> k)
   Fail         >>= _ = Fail
   (Result x p) >>= k = k x `mplus` (p >>= k)
   (Final r)    >>= k = final [ys' | (x,s) <- r, ys' <- run (k x) s]
 
+  fail = Fail.fail
+
+instance Fail.MonadFail (P s) where
   fail _ = Fail
 
 instance Alternative (P s) where
@@ -155,14 +156,17 @@
   fmap h (R f) = R (\k -> f (k . h))
 
 instance Applicative (Parser r s) where
-  pure = return
+  pure x  = R (\k -> k x)
   (<*>) = ap
 
 instance Monad (Parser r s) where
-  return x  = R (\k -> k x)
-  fail _    = R (\_ -> Fail)
+  return = AP.pure
+  fail = Fail.fail
   R m >>= f = R (\k -> m (\a -> let R m' = f a in m' k))
 
+instance Fail.MonadFail (Parser r s) where
+  fail _    = R (const Fail)
+
 --instance MonadPlus (Parser r s) where
 --  mzero = pfail
 --  mplus = (+++)
@@ -197,7 +201,7 @@
 
 pfail :: ReadP r a
 -- ^ Always fails.
-pfail = R (\_ -> Fail)
+pfail = R (const Fail)
 
 (+++) :: ReadP r a -> ReadP r a -> ReadP r a
 -- ^ Symmetric choice.
@@ -230,7 +234,7 @@
  where
   gath l (Get f)      = Get (\c -> gath (l.(c:)) (f c))
   gath _ Fail         = Fail
-  gath l (Look f)     = Look (\s -> gath l (f s))
+  gath l (Look f)     = Look (gath l . f)
   gath l (Result k p) = k (l []) `mplus` gath l p
   gath _ (Final _)    = error "do not use readS_to_P in gather!"
 
@@ -250,9 +254,9 @@
 -- ^ Parses and returns the specified string.
 string this = do s <- look; scan this s
  where
-  scan []     _               = do return this
-  scan (x:xs) (y:ys) | x == y = do get >> scan xs ys
-  scan _      _               = do pfail
+  scan []     _               = return this
+  scan (x:xs) (y:ys) | x == y = get >> scan xs ys
+  scan _      _               = pfail
 
 munch :: (Char -> Bool) -> ReadP r String
 -- ^ Parses the first zero or more characters satisfying the predicate.
@@ -288,7 +292,7 @@
 count :: Int -> ReadP r a -> ReadP r [a]
 -- ^ @ count n p @ parses @n@ occurrences of @p@ in sequence. A list of
 --   results is returned.
-count n p = sequence (replicate n p)
+count n p = replicateM n p
 
 between :: ReadP r open -> ReadP r close -> ReadP r a -> ReadP r a
 -- ^ @ between open close p @ parses @open@, followed by @p@ and finally
diff --git a/cabal/Cabal/Distribution/Compat/Semigroup.hs b/cabal/Cabal/Distribution/Compat/Semigroup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/Cabal/Distribution/Compat/Semigroup.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE CPP                         #-}
+{-# LANGUAGE DeriveGeneric               #-}
+{-# LANGUAGE FlexibleContexts            #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving  #-}
+{-# LANGUAGE TypeOperators               #-}
+
+-- | Compatibility layer for "Data.Semigroup"
+module Distribution.Compat.Semigroup
+    ( Semigroup((<>))
+    , Mon.Monoid(..)
+    , All(..)
+    , Any(..)
+
+    , Last'(..)
+
+    , gmappend
+    , gmempty
+    ) where
+
+import Distribution.Compat.Binary (Binary)
+
+import Control.Applicative as App
+import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 711
+-- Data.Semigroup is available since GHC 8.0/base-4.9
+import Data.Semigroup
+import qualified Data.Monoid as Mon
+#else
+-- provide internal simplified non-exposed class for older GHCs
+import Data.Monoid as Mon (Monoid(..), All(..), Any(..), Dual(..))
+-- containers
+import Data.Set (Set)
+import Data.IntSet (IntSet)
+import Data.Map (Map)
+import Data.IntMap (IntMap)
+
+
+class Semigroup a where
+    (<>) :: a -> a -> a
+
+-- several primitive instances
+instance Semigroup () where
+    _ <> _ = ()
+
+instance Semigroup [a] where
+    (<>) = (++)
+
+instance Semigroup a => Semigroup (Dual a) where
+    Dual a <> Dual b = Dual (b <> a)
+
+instance Semigroup a => Semigroup (Maybe a) where
+    Nothing <> b       = b
+    a       <> Nothing = a
+    Just a  <> Just b  = Just (a <> b)
+
+instance Semigroup (Either a b) where
+    Left _ <> b = b
+    a      <> _ = a
+
+instance Semigroup Ordering where
+    LT <> _ = LT
+    EQ <> y = y
+    GT <> _ = GT
+
+instance Semigroup b => Semigroup (a -> b) where
+    f <> g = \a -> f a <> g a
+
+instance Semigroup All where
+    All a <> All b = All (a && b)
+
+instance Semigroup Any where
+    Any a <> Any b = Any (a || b)
+
+instance (Semigroup a, Semigroup b) => Semigroup (a, b) where
+    (a,b) <> (a',b') = (a<>a',b<>b')
+
+instance (Semigroup a, Semigroup b, Semigroup c)
+         => Semigroup (a, b, c) where
+    (a,b,c) <> (a',b',c') = (a<>a',b<>b',c<>c')
+
+instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d)
+         => Semigroup (a, b, c, d) where
+    (a,b,c,d) <> (a',b',c',d') = (a<>a',b<>b',c<>c',d<>d')
+
+instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d, Semigroup e)
+         => Semigroup (a, b, c, d, e) where
+    (a,b,c,d,e) <> (a',b',c',d',e') = (a<>a',b<>b',c<>c',d<>d',e<>e')
+
+-- containers instances
+instance Semigroup IntSet where
+  (<>) = mappend
+
+instance Ord a => Semigroup (Set a) where
+  (<>) = mappend
+
+instance Semigroup (IntMap v) where
+  (<>) = mappend
+
+instance Ord k => Semigroup (Map k v) where
+  (<>) = mappend
+#endif
+
+-- | Cabal's own 'Data.Monoid.Last' copy to avoid requiring an orphan
+-- 'Binary' instance.
+--
+-- Once the oldest `binary` version we support provides a 'Binary'
+-- instance for 'Data.Monoid.Last' we can remove this one here.
+--
+-- NB: 'Data.Semigroup.Last' is defined differently and not a 'Monoid'
+newtype Last' a = Last' { getLast' :: Maybe a }
+                deriving (Eq, Ord, Read, Show, Binary,
+                          Functor, App.Applicative, Generic)
+
+instance Semigroup (Last' a) where
+    x <> Last' Nothing = x
+    _ <> x             = x
+
+instance Monoid (Last' a) where
+    mempty = Last' Nothing
+    mappend = (<>)
+
+-------------------------------------------------------------------------------
+-------------------------------------------------------------------------------
+-- Stolen from Edward Kmett's BSD3-licensed `semigroups` package
+
+-- | Generically generate a 'Semigroup' ('<>') operation for any type
+-- implementing 'Generic'. This operation will append two values
+-- by point-wise appending their component fields. It is only defined
+-- for product types.
+--
+-- @
+-- 'gmappend' a ('gmappend' b c) = 'gmappend' ('gmappend' a b) c
+-- @
+gmappend :: (Generic a, GSemigroup (Rep a)) => a -> a -> a
+gmappend x y = to (gmappend' (from x) (from y))
+
+class GSemigroup f where
+    gmappend' :: f p -> f p -> f p
+
+instance Semigroup a => GSemigroup (K1 i a) where
+    gmappend' (K1 x) (K1 y) = K1 (x <> y)
+
+instance GSemigroup f => GSemigroup (M1 i c f) where
+    gmappend' (M1 x) (M1 y) = M1 (gmappend' x y)
+
+instance (GSemigroup f, GSemigroup g) => GSemigroup (f :*: g) where
+    gmappend' (x1 :*: x2) (y1 :*: y2) = gmappend' x1 y1 :*: gmappend' x2 y2
+
+-- | Generically generate a 'Monoid' 'mempty' for any product-like type
+-- implementing 'Generic'.
+--
+-- It is only defined for product types.
+--
+-- @
+-- 'gmappend' 'gmempty' a = a = 'gmappend' a 'gmempty'
+-- @
+
+gmempty :: (Generic a, GMonoid (Rep a)) => a
+gmempty = to gmempty'
+
+class GSemigroup f => GMonoid f where
+    gmempty' :: f p
+
+instance (Semigroup a, Monoid a) => GMonoid (K1 i a) where
+    gmempty' = K1 mempty
+
+instance GMonoid f => GMonoid (M1 i c f) where
+    gmempty' = M1 gmempty'
+
+instance (GMonoid f, GMonoid g) => GMonoid (f :*: g) where
+    gmempty' = gmempty' :*: gmempty'
diff --git a/cabal/Cabal/Distribution/Compat/TempFile.hs b/cabal/Cabal/Distribution/Compat/TempFile.hs
deleted file mode 100644
--- a/cabal/Cabal/Distribution/Compat/TempFile.hs
+++ /dev/null
@@ -1,128 +0,0 @@
-{-# LANGUAGE CPP #-}
-{-# OPTIONS_HADDOCK hide #-}
-module Distribution.Compat.TempFile (
-  openTempFile,
-  openBinaryTempFile,
-  openNewBinaryFile,
-  createTempDirectory,
-  ) where
-
-
-import System.FilePath        ((</>))
-import Foreign.C              (eEXIST)
-
-import System.IO              (Handle, openTempFile, openBinaryTempFile)
-import Data.Bits              ((.|.))
-import System.Posix.Internals (c_open, c_close, o_CREAT, o_EXCL, o_RDWR,
-                               o_BINARY, o_NONBLOCK, o_NOCTTY)
-import System.IO.Error        (isAlreadyExistsError)
-import System.Posix.Internals (withFilePath)
-import Foreign.C              (CInt)
-import GHC.IO.Handle.FD       (fdToHandle)
-import Distribution.Compat.Exception (tryIO)
-import Control.Exception      (onException)
-import Foreign.C              (getErrno, errnoToIOError)
-
-import System.Posix.Internals (c_getpid)
-
-#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)
-import System.Directory       ( createDirectory )
-#else
-import qualified System.Posix
-#endif
-
--- ------------------------------------------------------------
--- * temporary files
--- ------------------------------------------------------------
-
--- This is here for Haskell implementations that do not come with
--- System.IO.openTempFile. This includes nhc-1.20, hugs-2006.9.
--- TODO: Not sure about JHC
--- TODO: This file should probably be removed.
-
--- This is a copy/paste of the openBinaryTempFile definition, but
--- if uses 666 rather than 600 for the permissions. The base library
--- needs to be changed to make this better.
-openNewBinaryFile :: FilePath -> String -> IO (FilePath, Handle)
-openNewBinaryFile dir template = do
-  pid <- c_getpid
-  findTempName pid
-  where
-    -- We split off the last extension, so we can use .foo.ext files
-    -- for temporary files (hidden on Unix OSes). Unfortunately we're
-    -- below file path in the hierarchy here.
-    (prefix,suffix) =
-       case break (== '.') $ reverse template of
-         -- First case: template contains no '.'s. Just re-reverse it.
-         (rev_suffix, "")       -> (reverse rev_suffix, "")
-         -- Second case: template contains at least one '.'. Strip the
-         -- dot from the prefix and prepend it to the suffix (if we don't
-         -- do this, the unique number will get added after the '.' and
-         -- thus be part of the extension, which is wrong.)
-         (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)
-         -- Otherwise, something is wrong, because (break (== '.')) should
-         -- always return a pair with either the empty string or a string
-         -- beginning with '.' as the second component.
-         _                      -> error "bug in System.IO.openTempFile"
-
-    oflags = rw_flags .|. o_EXCL .|. o_BINARY
-
-    findTempName x = do
-      fd <- withFilePath filepath $ \ f ->
-              c_open f oflags 0o666
-      if fd < 0
-       then do
-         errno <- getErrno
-         if errno == eEXIST
-           then findTempName (x+1)
-           else ioError (errnoToIOError "openNewBinaryFile" errno Nothing (Just dir))
-       else do
-         -- TODO: We want to tell fdToHandle what the file path is,
-         -- as any exceptions etc will only be able to report the
-         -- FD currently
-         h <- fdToHandle fd `onException` c_close fd
-         return (filepath, h)
-      where
-        filename        = prefix ++ show x ++ suffix
-        filepath        = dir `combine` filename
-
-        -- FIXME: bits copied from System.FilePath
-        combine a b
-                  | null b = a
-                  | null a = b
-                  | last a == pathSeparator = a ++ b
-                  | otherwise = a ++ [pathSeparator] ++ b
-
--- FIXME: Should use System.FilePath library
-pathSeparator :: Char
-#ifdef mingw32_HOST_OS
-pathSeparator = '\\'
-#else
-pathSeparator = '/'
-#endif
-
--- FIXME: Copied from GHC.Handle
-std_flags, output_flags, rw_flags :: CInt
-std_flags    = o_NONBLOCK   .|. o_NOCTTY
-output_flags = std_flags    .|. o_CREAT
-rw_flags     = output_flags .|. o_RDWR
-
-createTempDirectory :: FilePath -> String -> IO FilePath
-createTempDirectory dir template = do
-  pid <- c_getpid
-  findTempName pid
-  where
-    findTempName x = do
-      let dirpath = dir </> template ++ "-" ++ show x
-      r <- tryIO $ mkPrivateDir dirpath
-      case r of
-        Right _ -> return dirpath
-        Left  e | isAlreadyExistsError e -> findTempName (x+1)
-                | otherwise              -> ioError e
-
-mkPrivateDir :: String -> IO ()
-#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)
-mkPrivateDir s = createDirectory s
-#else
-mkPrivateDir s = System.Posix.createDirectory s 0o700
-#endif
diff --git a/cabal/Cabal/Distribution/Compiler.hs b/cabal/Cabal/Distribution/Compiler.hs
--- a/cabal/Cabal/Distribution/Compiler.hs
+++ b/cabal/Cabal/Distribution/Compiler.hs
@@ -42,15 +42,15 @@
   AbiTag(..), abiTagString
   ) where
 
-import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Binary
+import Language.Haskell.Extension
+
 import Data.Data (Data)
 import Data.Typeable (Typeable)
 import Data.Maybe (fromMaybe)
 import Distribution.Version (Version(..))
 import GHC.Generics (Generic)
 
-import Language.Haskell.Extension (Language, Extension)
-
 import qualified System.Info (compilerName, compilerVersion)
 import Distribution.Text (Text(..), display)
 import qualified Distribution.Compat.ReadP as Parse
@@ -180,7 +180,7 @@
 data AbiTag
   = NoAbiTag
   | AbiTag String
-  deriving (Generic, Show, Read)
+  deriving (Eq, Generic, Show, Read)
 
 instance Binary AbiTag
 
diff --git a/cabal/Cabal/Distribution/InstalledPackageInfo.hs b/cabal/Cabal/Distribution/InstalledPackageInfo.hs
--- a/cabal/Cabal/Distribution/InstalledPackageInfo.hs
+++ b/cabal/Cabal/Distribution/InstalledPackageInfo.hs
@@ -28,8 +28,9 @@
 
 module Distribution.InstalledPackageInfo (
         InstalledPackageInfo(..),
-        libraryName,
-        OriginalModule(..), ExposedModule(..),
+        installedComponentId,
+        installedPackageId,
+        ExposedModule(..),
         ParseResult(..), PError(..), PWarning,
         emptyInstalledPackageInfo,
         parseInstalledPackageInfo,
@@ -40,43 +41,30 @@
   ) where
 
 import Distribution.ParseUtils
-         ( FieldDescr(..), ParseResult(..), PError(..), PWarning
-         , simpleField, listField, parseLicenseQ
-         , showFields, showSingleNamedField, showSimpleSingleNamedField
-         , parseFieldsFlat
-         , parseFilePathQ, parseTokenQ, parseModuleNameQ, parsePackageNameQ
-         , showFilePath, showToken, boolField, parseOptVersion
-         , parseFreeText, showFreeText, parseOptCommaList )
-import Distribution.License     ( License(..) )
-import Distribution.Package
-         ( PackageName(..), PackageIdentifier(..)
-         , PackageId, InstalledPackageId(..)
-         , packageName, packageVersion, PackageKey(..)
-         , LibraryName(..) )
+import Distribution.License
+import Distribution.Package hiding (installedUnitId, installedPackageId)
 import qualified Distribution.Package as Package
 import Distribution.ModuleName
-         ( ModuleName )
 import Distribution.Version
-         ( Version(..) )
 import Distribution.Text
-         ( Text(disp, parse) )
-import Text.PrettyPrint as Disp
 import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.Binary
 
-import Distribution.Compat.Binary  (Binary)
+import Text.PrettyPrint as Disp
 import Data.Maybe   (fromMaybe)
 import GHC.Generics (Generic)
 
 -- -----------------------------------------------------------------------------
 -- The InstalledPackageInfo type
 
-
+-- For BC reasons, we continue to name this record an InstalledPackageInfo;
+-- but it would more accurately be called an InstalledUnitInfo with Backpack
 data InstalledPackageInfo
    = InstalledPackageInfo {
         -- these parts are exactly the same as PackageDescription
-        installedPackageId :: InstalledPackageId,
-        sourcePackageId    :: PackageId,
-        packageKey         :: PackageKey,
+        sourcePackageId   :: PackageId,
+        installedUnitId   :: UnitId,
+        compatPackageKey  :: String,
         license           :: License,
         copyright         :: String,
         maintainer        :: String,
@@ -88,9 +76,9 @@
         description       :: String,
         category          :: String,
         -- these parts are required by an installed package only:
+        abiHash           :: AbiHash,
         exposed           :: Bool,
         exposedModules    :: [ExposedModule],
-        instantiatedWith  :: [(ModuleName, OriginalModule)],
         hiddenModules     :: [ModuleName],
         trusted           :: Bool,
         importDirs        :: [FilePath],
@@ -101,7 +89,7 @@
         extraGHCiLibraries:: [String],    -- overrides extraLibraries for GHCi
         includeDirs       :: [FilePath],
         includes          :: [String],
-        depends           :: [InstalledPackageId],
+        depends           :: [UnitId],
         ccOptions         :: [String],
         ldOptions         :: [String],
         frameworkDirs     :: [FilePath],
@@ -110,18 +98,24 @@
         haddockHTMLs      :: [FilePath],
         pkgRoot           :: Maybe FilePath
     }
-    deriving (Generic, Read, Show)
+    deriving (Eq, Generic, Read, Show)
 
-libraryName :: InstalledPackageInfo -> LibraryName
-libraryName ipi = Package.packageKeyLibraryName (sourcePackageId ipi) (packageKey ipi)
+installedComponentId :: InstalledPackageInfo -> ComponentId
+installedComponentId ipi = case installedUnitId ipi of
+                            SimpleUnitId cid -> cid
 
+{-# DEPRECATED installedPackageId "Use installedUnitId instead" #-}
+-- | Backwards compatibility with Cabal pre-1.24.
+installedPackageId :: InstalledPackageInfo -> UnitId
+installedPackageId = installedUnitId
+
 instance Binary InstalledPackageInfo
 
 instance Package.Package InstalledPackageInfo where
    packageId = sourcePackageId
 
-instance Package.HasInstalledPackageId InstalledPackageInfo where
-   installedPackageId = installedPackageId
+instance Package.HasUnitId InstalledPackageInfo where
+   installedUnitId = installedUnitId
 
 instance Package.PackageInstalled InstalledPackageInfo where
    installedDepends = depends
@@ -129,10 +123,9 @@
 emptyInstalledPackageInfo :: InstalledPackageInfo
 emptyInstalledPackageInfo
    = InstalledPackageInfo {
-        installedPackageId = InstalledPackageId "",
-        sourcePackageId    = PackageIdentifier (PackageName "") noVersion,
-        packageKey         = OldPackageKey (PackageIdentifier
-                                               (PackageName "") noVersion),
+        sourcePackageId   = PackageIdentifier (PackageName "") (Version [] []),
+        installedUnitId   = mkUnitId "",
+        compatPackageKey  = "",
         license           = UnspecifiedLicense,
         copyright         = "",
         maintainer        = "",
@@ -143,10 +136,10 @@
         synopsis          = "",
         description       = "",
         category          = "",
+        abiHash           = AbiHash "",
         exposed           = False,
         exposedModules    = [],
         hiddenModules     = [],
-        instantiatedWith  = [],
         trusted           = False,
         importDirs        = [],
         libraryDirs       = [],
@@ -166,45 +159,22 @@
         pkgRoot           = Nothing
     }
 
-noVersion :: Version
-noVersion = Version [] []
-
 -- -----------------------------------------------------------------------------
 -- Exposed modules
 
-data OriginalModule
-   = OriginalModule {
-       originalPackageId :: InstalledPackageId,
-       originalModuleName :: ModuleName
-     }
-  deriving (Generic, Eq, Read, Show)
-
 data ExposedModule
    = ExposedModule {
        exposedName      :: ModuleName,
-       exposedReexport  :: Maybe OriginalModule,
-       exposedSignature :: Maybe OriginalModule -- This field is unused for now.
+       exposedReexport  :: Maybe Module
      }
-  deriving (Generic, Read, Show)
-
-instance Text OriginalModule where
-    disp (OriginalModule ipi m) =
-        disp ipi <> Disp.char ':' <> disp m
-    parse = do
-        ipi <- parse
-        _ <- Parse.char ':'
-        m <- parse
-        return (OriginalModule ipi m)
+  deriving (Eq, Generic, Read, Show)
 
 instance Text ExposedModule where
-    disp (ExposedModule m reexport signature) =
+    disp (ExposedModule m reexport) =
         Disp.sep [ disp m
                  , case reexport of
                     Just m' -> Disp.sep [Disp.text "from", disp m']
                     Nothing -> Disp.empty
-                 , case signature of
-                    Just m' -> Disp.sep [Disp.text "is", disp m']
-                    Nothing -> Disp.empty
                  ]
     parse = do
         m <- parseModuleNameQ
@@ -213,16 +183,9 @@
             _ <- Parse.string "from"
             Parse.skipSpaces
             fmap Just parse
-        Parse.skipSpaces
-        signature <- Parse.option Nothing $ do
-            _ <- Parse.string "is"
-            Parse.skipSpaces
-            fmap Just parse
-        return (ExposedModule m reexport signature)
+        return (ExposedModule m reexport)
 
 
-instance Binary OriginalModule
-
 instance Binary ExposedModule
 
 -- To maintain backwards-compatibility, we accept both comma/non-comma
@@ -234,7 +197,7 @@
 showExposedModules xs
     | all isExposedModule xs = fsep (map disp xs)
     | otherwise = fsep (Disp.punctuate comma (map disp xs))
-    where isExposedModule (ExposedModule _ Nothing Nothing) = True
+    where isExposedModule (ExposedModule _ Nothing) = True
           isExposedModule _ = False
 
 parseExposedModules :: Parse.ReadP r [ExposedModule]
@@ -248,14 +211,6 @@
     parseFieldsFlat (fieldsInstalledPackageInfo ++ deprecatedFieldDescrs)
     emptyInstalledPackageInfo
 
-parseInstantiatedWith :: Parse.ReadP r (ModuleName, OriginalModule)
-parseInstantiatedWith = do k <- parse
-                           _ <- Parse.char '='
-                           n <- parse
-                           _ <- Parse.char '@'
-                           p <- parse
-                           return (k, OriginalModule p n)
-
 -- -----------------------------------------------------------------------------
 -- Pretty-printing
 
@@ -268,9 +223,6 @@
 showSimpleInstalledPackageInfoField :: String -> Maybe (InstalledPackageInfo -> String)
 showSimpleInstalledPackageInfoField = showSimpleSingleNamedField fieldsInstalledPackageInfo
 
-showInstantiatedWith :: (ModuleName, OriginalModule) -> Doc
-showInstantiatedWith (k, OriginalModule p m) = disp k <> text "=" <> disp m <> text "@" <> disp p
-
 -- -----------------------------------------------------------------------------
 -- Description of the fields, for parsing/printing
 
@@ -287,10 +239,11 @@
                            packageVersion         (\ver pkg -> pkg{sourcePackageId=(sourcePackageId pkg){pkgVersion=ver}})
  , simpleField "id"
                            disp                   parse
-                           installedPackageId     (\ipid pkg -> pkg{installedPackageId=ipid})
+                           installedUnitId             (\pk pkg -> pkg{installedUnitId=pk})
+ -- NB: parse these as component IDs
  , simpleField "key"
-                           disp                   parse
-                           packageKey             (\pk pkg -> pkg{packageKey=pk})
+                           (disp . ComponentId)   (fmap (\(ComponentId s) -> s) parse)
+                           compatPackageKey       (\pk pkg -> pkg{compatPackageKey=pk})
  , simpleField "license"
                            disp                   parseLicenseQ
                            license                (\l pkg -> pkg{license=l})
@@ -333,9 +286,9 @@
  , listField   "hidden-modules"
         disp               parseModuleNameQ
         hiddenModules      (\xs    pkg -> pkg{hiddenModules=xs})
- , listField   "instantiated-with"
-        showInstantiatedWith parseInstantiatedWith
-        instantiatedWith   (\xs    pkg -> pkg{instantiatedWith=xs})
+ , simpleField "abi"
+        disp               parse
+        abiHash            (\abi    pkg -> pkg{abiHash=abi})
  , boolField   "trusted"
         trusted            (\val pkg -> pkg{trusted=val})
  , listField   "import-dirs"
diff --git a/cabal/Cabal/Distribution/Lex.hs b/cabal/Cabal/Distribution/Lex.hs
--- a/cabal/Cabal/Distribution/Lex.hs
+++ b/cabal/Cabal/Distribution/Lex.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE PatternGuards #-}
 -----------------------------------------------------------------------------
 -- |
@@ -15,9 +14,7 @@
  ) where
 
 import Data.Char (isSpace)
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-#endif
+import Distribution.Compat.Semigroup as Semi
 
 newtype DList a = DList ([a] -> [a])
 
@@ -29,7 +26,10 @@
 
 instance Monoid (DList a) where
   mempty = DList id
-  DList a `mappend` DList b = DList (a . b)
+  mappend = (Semi.<>)
+
+instance Semigroup (DList a) where
+  DList a <> DList b = DList (a . b)
 
 tokenizeQuotedWords :: String -> [String]
 tokenizeQuotedWords = filter (not . null) . go False mempty
diff --git a/cabal/Cabal/Distribution/License.hs b/cabal/Cabal/Distribution/License.hs
--- a/cabal/Cabal/Distribution/License.hs
+++ b/cabal/Cabal/Distribution/License.hs
@@ -47,13 +47,13 @@
     knownLicenses,
   ) where
 
-import Distribution.Version (Version(Version))
-
-import Distribution.Text (Text(..), display)
+import Distribution.Version
+import Distribution.Text
 import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.Binary
+
 import qualified Text.PrettyPrint as Disp
 import Text.PrettyPrint ((<>))
-import Distribution.Compat.Binary (Binary)
 import qualified Data.Char as Char (isAlphaNum)
 import Data.Data (Data)
 import Data.Typeable (Typeable)
diff --git a/cabal/Cabal/Distribution/Make.hs b/cabal/Cabal/Distribution/Make.hs
--- a/cabal/Cabal/Distribution/Make.hs
+++ b/cabal/Cabal/Distribution/Make.hs
@@ -62,22 +62,19 @@
 
 -- local
 import Distribution.Compat.Exception
-import Distribution.Package --must not specify imports, since we're exporting moule.
-import Distribution.Simple.Program(defaultProgramConfiguration)
+import Distribution.Package
+import Distribution.Simple.Program
 import Distribution.PackageDescription
 import Distribution.Simple.Setup
 import Distribution.Simple.Command
 
-import Distribution.Simple.Utils (rawSystemExit, cabalVersion)
+import Distribution.Simple.Utils
 
-import Distribution.License (License(..))
+import Distribution.License
 import Distribution.Version
-         ( Version(..) )
 import Distribution.Text
-         ( display )
 
 import System.Environment (getArgs, getProgName)
-import Data.List  (intercalate)
 import System.Exit
 
 defaultMain :: IO ()
diff --git a/cabal/Cabal/Distribution/ModuleName.hs b/cabal/Cabal/Distribution/ModuleName.hs
--- a/cabal/Cabal/Distribution/ModuleName.hs
+++ b/cabal/Cabal/Distribution/ModuleName.hs
@@ -22,14 +22,14 @@
   ) where
 
 import Distribution.Text
-         ( Text(..) )
+import Distribution.Compat.Binary
+import qualified Distribution.Compat.ReadP as Parse
 
-import Distribution.Compat.Binary (Binary)
 import qualified Data.Char as Char
          ( isAlphaNum, isUpper )
+import Control.DeepSeq
 import Data.Data (Data)
 import Data.Typeable (Typeable)
-import qualified Distribution.Compat.ReadP as Parse
 import qualified Text.PrettyPrint as Disp
 import Data.List
          ( intercalate, intersperse )
@@ -43,6 +43,9 @@
   deriving (Eq, Generic, Ord, Read, Show, Typeable, Data)
 
 instance Binary ModuleName
+
+instance NFData ModuleName where
+    rnf (ModuleName ms) = rnf ms
 
 instance Text ModuleName where
   disp (ModuleName ms) =
diff --git a/cabal/Cabal/Distribution/Package.hs b/cabal/Cabal/Distribution/Package.hs
--- a/cabal/Cabal/Distribution/Package.hs
+++ b/cabal/Cabal/Distribution/Package.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -21,19 +22,19 @@
         PackageIdentifier(..),
         PackageId,
 
-        -- * Installed package identifiers
-        InstalledPackageId(..),
+        -- * Package keys/installed package IDs (used for linker symbols)
+        ComponentId(..),
+        UnitId(..),
+        mkUnitId,
+        mkLegacyUnitId,
+        getHSLibraryName,
+        InstalledPackageId, -- backwards compat
 
-        -- * Package keys (used for linker symbols)
-        PackageKey(..),
-        mkPackageKey,
-        packageKeyHash,
-        packageKeyLibraryName,
+        -- * Modules
+        Module(..),
 
-        -- * Library name (used for install path, package key)
-        LibraryName(..),
-        emptyLibraryName,
-        getHSLibraryName,
+        -- * ABI hash
+        AbiHash(..),
 
         -- * Package source dependencies
         Dependency(..),
@@ -43,7 +44,8 @@
 
         -- * Package classes
         Package(..), packageName, packageVersion,
-        HasInstalledPackageId(..),
+        HasUnitId(..),
+        installedPackageId,
         PackageInstalled(..),
   ) where
 
@@ -51,22 +53,20 @@
          ( Version(..), VersionRange, anyVersion, thisVersion
          , notThisVersion, simplifyVersionRange )
 
-import Distribution.Text (Text(..), display)
 import qualified Distribution.Compat.ReadP as Parse
-import Distribution.Compat.ReadP ((<++))
 import qualified Text.PrettyPrint as Disp
+import Distribution.Compat.ReadP
+import Distribution.Compat.Binary
+import Distribution.Text
+import Distribution.ModuleName
 
 import Control.DeepSeq (NFData(..))
-import Distribution.Compat.Binary (Binary)
 import qualified Data.Char as Char
-    ( isDigit, isAlphaNum, isUpper, isLower, ord, chr )
+    ( isDigit, isAlphaNum, )
 import Data.Data ( Data )
-import Data.List ( intercalate, foldl', sort )
+import Data.List ( intercalate )
 import Data.Typeable ( Typeable )
-import Data.Word ( Word64 )
-import GHC.Fingerprint ( Fingerprint(..), fingerprintString )
 import GHC.Generics (Generic)
-import Numeric ( showIntAtBase )
 import Text.PrettyPrint ((<>), (<+>), text)
 
 newtype PackageName = PackageName { unPackageName :: String }
@@ -115,190 +115,72 @@
 instance NFData PackageIdentifier where
     rnf (PackageIdentifier name version) = rnf name `seq` rnf version
 
--- ------------------------------------------------------------
--- * Installed Package Ids
--- ------------------------------------------------------------
-
--- | An InstalledPackageId uniquely identifies an instance of an installed
--- package.  There can be at most one package with a given 'InstalledPackageId'
--- in a package database, or overlay of databases.
---
-newtype InstalledPackageId = InstalledPackageId String
- deriving (Generic, Read,Show,Eq,Ord,Typeable,Data)
-
-instance Binary InstalledPackageId
+-- | A module identity uniquely identifies a Haskell module by
+-- qualifying a 'ModuleName' with the 'UnitId' which defined
+-- it.  This type distinguishes between two packages
+-- which provide a module with the same name, or a module
+-- from the same package compiled with different dependencies.
+-- There are a few cases where Cabal needs to know about
+-- module identities, e.g., when writing out reexported modules in
+-- the 'InstalledPackageInfo'.
+data Module =
+    Module { moduleUnitId :: UnitId,
+             moduleName :: ModuleName }
+    deriving (Generic, Read, Show, Eq, Ord, Typeable, Data)
 
-instance Text InstalledPackageId where
-  disp (InstalledPackageId str) = text str
+instance Binary Module
 
-  parse = InstalledPackageId `fmap` Parse.munch1 abi_char
-   where abi_char c = Char.isAlphaNum c || c `elem` "-_."
+instance Text Module where
+    disp (Module uid mod_name) =
+        disp uid <> Disp.text ":" <> disp mod_name
+    parse = do
+        uid <- parse
+        _ <- Parse.char ':'
+        mod_name <- parse
+        return (Module uid mod_name)
 
--- ------------------------------------------------------------
--- * Package Keys
--- ------------------------------------------------------------
+instance NFData Module where
+    rnf (Module uid mod_name) = rnf uid `seq` rnf mod_name
 
--- | A 'PackageKey' is the notion of "package ID" which is visible to the
--- compiler. Why is this not a 'PackageId'? The 'PackageId' is a user-visible
--- concept written explicity in Cabal files; on the other hand, a 'PackageKey'
--- may contain, for example, information about the transitive dependency
--- tree of a package.  Why is this not an 'InstalledPackageId'?  A 'PackageKey'
--- should be stable so that we can incrementally recompile after a source edit;
--- however, an 'InstalledPackageId' may change even with source.
---
--- Package keys may be generated either by Cabal or GHC.  In particular,
--- ordinary, "old-style" packages which don't use Backpack features can
--- have their package keys generated directly by Cabal and coincide with
--- 'LibraryName's.  However, Backpack keys are generated by GHC may exhibit
--- more variation than a 'LibraryName'.
+-- | A 'ComponentId' uniquely identifies the transitive source
+-- code closure of a component.  For non-Backpack components, it also
+-- serves as the basis for install paths, symbols, etc.
 --
-data PackageKey
-    -- | Modern package key which is a hash of the PackageId and the transitive
-    -- dependency key.  It's manually inlined here so we can get the instances
-    -- we need.  There's an optional prefix for compatibility with GHC 7.10.
-    = PackageKey (Maybe String) {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
-    -- | Old-style package key which is just a 'PackageId'.  Required because
-    -- old versions of GHC assume that the 'sourcePackageId' recorded for an
-    -- installed package coincides with the package key it was compiled with.
-    | OldPackageKey !PackageId
+data ComponentId
+    = ComponentId String
     deriving (Generic, Read, Show, Eq, Ord, Typeable, Data)
 
-instance Binary PackageKey
-
--- | Convenience function which converts a fingerprint into a new-style package
--- key.
-fingerprintPackageKey :: Fingerprint -> PackageKey
-fingerprintPackageKey (Fingerprint a b) = PackageKey Nothing a b
-
--- | Generates a 'PackageKey' from a 'PackageId', sorted package keys of the
--- immediate dependencies.
-mkPackageKey :: Bool -- are modern style package keys supported?
-             -> PackageId
-             -> [LibraryName] -- dependencies
-             -> PackageKey
-mkPackageKey True pid deps =
-    fingerprintPackageKey . fingerprintString $
-        display pid ++ "\n" ++
-        concat [ display dep ++ "\n" | dep <- sort deps ]
-mkPackageKey False pid _ = OldPackageKey pid
-
--- The base-62 code is based off of 'locators'
--- ((c) Operational Dynamics Consulting, BSD3 licensed)
-
--- Note: Instead of base-62 encoding a single 128-bit integer
--- (ceil(21.49) characters), we'll base-62 a pair of 64-bit integers
--- (2 * ceil(10.75) characters).  Luckily for us, it's the same number of
--- characters!  In the long term, this should go in GHC.Fingerprint,
--- but not now...
-
--- | Size of a 64-bit word when written as a base-62 string
-word64Base62Len :: Int
-word64Base62Len = 11
-
--- | Converts a 64-bit word into a base-62 string
-toBase62 :: Word64 -> String
-toBase62 w = pad ++ str
-  where
-    pad = replicate len '0'
-    len = word64Base62Len - length str -- 11 == ceil(64 / lg 62)
-    str = showIntAtBase 62 represent w ""
-    represent :: Int -> Char
-    represent x
-        | x < 10 = Char.chr (48 + x)
-        | x < 36 = Char.chr (65 + x - 10)
-        | x < 62 = Char.chr (97 + x - 36)
-        | otherwise = error ("represent (base 62): impossible!")
-
--- | Parses a base-62 string into a 64-bit word
-fromBase62 :: String -> Word64
-fromBase62 ss = foldl' multiply 0 ss
-  where
-    value :: Char -> Int
-    value c
-        | Char.isDigit c = Char.ord c - 48
-        | Char.isUpper c = Char.ord c - 65 + 10
-        | Char.isLower c = Char.ord c - 97 + 36
-        | otherwise = error ("value (base 62): impossible!")
-
-    multiply :: Word64 -> Char -> Word64
-    multiply acc c = acc * 62 + (fromIntegral $ value c)
-
--- | Parses a base-62 string into a fingerprint.
-readBase62Fingerprint :: String -> Fingerprint
-readBase62Fingerprint s = Fingerprint w1 w2
- where (s1,s2) = splitAt word64Base62Len s
-       w1 = fromBase62 s1
-       w2 = fromBase62 (take word64Base62Len s2)
-
--- | Compute the hash (without a prefix) of a package key.  In GHC 7.12
--- this is equivalent to display.
-packageKeyHash :: PackageKey -> String
-packageKeyHash (PackageKey _ w1 w2) = toBase62 w1 ++ toBase62 w2
-packageKeyHash (OldPackageKey pid) = display pid
-
--- | Legacy function for GHC 7.10 to compute a LibraryName based on
--- the package key.
-packageKeyLibraryName :: PackageId -> PackageKey -> LibraryName
-packageKeyLibraryName pid (PackageKey _ w1 w2) =
-  LibraryName (display pid ++ "-" ++ toBase62 w1 ++ toBase62 w2)
-packageKeyLibraryName _ (OldPackageKey pid) = LibraryName (display pid)
-
-instance Text PackageKey where
-  disp (PackageKey mb_prefix w1 w2)
-    = maybe Disp.empty (\r -> Disp.text r <> Disp.char '_') mb_prefix <>
-      Disp.text (toBase62 w1) <> Disp.text (toBase62 w2)
-  disp (OldPackageKey pid) = disp pid
-
-  parse = parseNewWithAnnot <++ parseNew <++ parseOld
-    where parseNew = do
-            fmap (fingerprintPackageKey . readBase62Fingerprint)
-                . Parse.count (word64Base62Len * 2)
-                $ Parse.satisfy Char.isAlphaNum
-          parseNewWithAnnot = do
-            -- this is ignored
-            prefix <- Parse.munch1 (\c -> Char.isAlphaNum c || c `elem` "-")
-            _ <- Parse.char '_' -- if we use '-' it's ambiguous
-            PackageKey _ w1 w2 <- parseNew
-            return (PackageKey (Just prefix) w1 w2)
-          parseOld = do pid <- parse
-                        return (OldPackageKey pid)
-
-instance NFData PackageKey where
-    rnf (PackageKey mb _ _) = rnf mb
-    rnf (OldPackageKey pid) = rnf pid
+{-# DEPRECATED InstalledPackageId "Use UnitId instead" #-}
+type InstalledPackageId = UnitId
 
--- ------------------------------------------------------------
--- * Library names
--- ------------------------------------------------------------
+instance Binary ComponentId
 
--- | A library name consists of not only a source package
--- id ('PackageId') but also the library names of all textual
--- dependencies; thus, a library name uniquely identifies an
--- installed package up to the dependency resolution done by Cabal.
--- Create using 'packageKeyLibraryName'.  Library names are opaque,
--- Cabal-defined strings.
-newtype LibraryName
-    = LibraryName String
-    deriving (Generic, Read, Show, Eq, Ord, Typeable, Data)
+instance Text ComponentId where
+  disp (ComponentId str) = text str
 
-instance Binary LibraryName
+  parse = ComponentId `fmap` Parse.munch1 abi_char
+   where abi_char c = Char.isAlphaNum c || c `elem` "-_."
 
--- | Default library name for when it is not known.
-emptyLibraryName :: LibraryName
-emptyLibraryName = LibraryName ""
+instance NFData ComponentId where
+    rnf (ComponentId pk) = rnf pk
 
 -- | Returns library name prefixed with HS, suitable for filenames
-getHSLibraryName :: LibraryName -> String
-getHSLibraryName (LibraryName s) = "HS" ++ s
+getHSLibraryName :: UnitId -> String
+getHSLibraryName (SimpleUnitId (ComponentId s)) = "HS" ++ s
 
-instance Text LibraryName where
-    disp (LibraryName s) = Disp.text s
-    parse = LibraryName `fmap` Parse.munch1 hash_char
-        where hash_char c = Char.isAlphaNum c || c `elem` "-_."
+-- | For now, there is no distinction between component IDs
+-- and unit IDs in Cabal.
+newtype UnitId = SimpleUnitId ComponentId
+    deriving (Generic, Read, Show, Eq, Ord, Typeable, Data, Binary, Text, NFData)
 
-instance NFData LibraryName where
-    rnf (LibraryName s) = rnf s
+-- | Makes a simple-style UnitId from a string.
+mkUnitId :: String -> UnitId
+mkUnitId = SimpleUnitId . ComponentId
 
+-- | Make an old-style UnitId from a package identifier
+mkLegacyUnitId :: PackageId -> UnitId
+mkLegacyUnitId = SimpleUnitId . ComponentId . display
+
 -- ------------------------------------------------------------
 -- * Package source dependencies
 -- ------------------------------------------------------------
@@ -358,14 +240,30 @@
   packageId = id
 
 -- | Packages that have an installed package ID
-class Package pkg => HasInstalledPackageId pkg where
-  installedPackageId :: pkg -> InstalledPackageId
+class Package pkg => HasUnitId pkg where
+  installedUnitId :: pkg -> UnitId
 
+{-# DEPRECATED installedPackageId "Use installedUnitId instead" #-}
+-- | Compatibility wrapper for Cabal pre-1.24.
+installedPackageId :: HasUnitId pkg => pkg -> UnitId
+installedPackageId = installedUnitId
+
 -- | Class of installed packages.
 --
 -- The primary data type which is an instance of this package is
 -- 'InstalledPackageInfo', but when we are doing install plans in Cabal install
 -- we may have other, installed package-like things which contain more metadata.
 -- Installed packages have exact dependencies 'installedDepends'.
-class HasInstalledPackageId pkg => PackageInstalled pkg where
-  installedDepends :: pkg -> [InstalledPackageId]
+class (HasUnitId pkg) => PackageInstalled pkg where
+  installedDepends :: pkg -> [UnitId]
+
+-- -----------------------------------------------------------------------------
+-- ABI hash
+
+newtype AbiHash = AbiHash String
+    deriving (Eq, Show, Read, Generic)
+instance Binary AbiHash
+
+instance Text AbiHash where
+    disp (AbiHash abi) = Disp.text abi
+    parse = fmap AbiHash (Parse.munch Char.isAlphaNum)
diff --git a/cabal/Cabal/Distribution/PackageDescription.hs b/cabal/Cabal/Distribution/PackageDescription.hs
--- a/cabal/Cabal/Distribution/PackageDescription.hs
+++ b/cabal/Cabal/Distribution/PackageDescription.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -44,6 +44,7 @@
         ModuleReexport(..),
         emptyLibrary,
         withLib,
+        hasPublicLib,
         hasLibs,
         libModules,
 
@@ -90,6 +91,8 @@
         hcSharedOptions,
 
         -- ** Supplementary build information
+        ComponentName(..),
+        defaultLibName,
         HookedBuildInfo,
         emptyHookedBuildInfo,
         updatePackageDescription,
@@ -110,43 +113,34 @@
         SetupBuildInfo(..),
   ) where
 
-import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Binary
+import qualified Distribution.Compat.Semigroup as Semi ((<>))
+import Distribution.Compat.Semigroup as Semi (Monoid(..), Semigroup, gmempty, gmappend)
+import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.ReadP   ((<++))
+import Distribution.Package
+import Distribution.ModuleName
+import Distribution.Version
+import Distribution.License
+import Distribution.Compiler
+import Distribution.System
+import Distribution.Text
+import Language.Haskell.Extension
+
 import Data.Data                  (Data)
-import Data.Foldable              (traverse_)
 import Data.List                  (nub, intercalate)
 import Data.Maybe                 (fromMaybe, maybeToList)
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative        (Applicative((<*>), pure))
-import Data.Monoid                (Monoid(mempty, mappend))
-import Data.Foldable              (Foldable(foldMap))
-import Data.Traversable           (Traversable(traverse))
-#endif
+import Data.Foldable as Fold      (Foldable(foldMap))
+import Data.Traversable as Trav   (Traversable(traverse))
 import Data.Typeable               ( Typeable )
-import Control.Applicative         (Alternative(..))
+import Control.Applicative as AP   (Alternative(..), Applicative(..))
 import Control.Monad               (MonadPlus(mplus,mzero), ap)
 import GHC.Generics                (Generic)
 import Text.PrettyPrint as Disp
-import qualified Distribution.Compat.ReadP as Parse
-import Distribution.Compat.ReadP   ((<++))
 import qualified Data.Char as Char (isAlphaNum, isDigit, toLower)
 import qualified Data.Map as Map
 import Data.Map                    (Map)
 
-import Distribution.Package
-         ( PackageName(PackageName), PackageIdentifier(PackageIdentifier)
-         , Dependency, Package(..), PackageName, packageName )
-import Distribution.ModuleName ( ModuleName )
-import Distribution.Version
-         ( Version(Version), VersionRange, anyVersion, orLaterVersion
-         , asVersionIntervals, LowerBound(..) )
-import Distribution.License  (License(UnspecifiedLicense))
-import Distribution.Compiler (CompilerFlavor)
-import Distribution.System   (OS, Arch)
-import Distribution.Text
-         ( Text(..), display )
-import Language.Haskell.Extension
-         ( Language, Extension )
-
 -- -----------------------------------------------------------------------------
 -- The PackageDescription type
 
@@ -177,6 +171,7 @@
         customFieldsPD :: [(String,String)], -- ^Custom fields starting
                                              -- with x-, stored in a
                                              -- simple assoc-list.
+
         -- | YOU PROBABLY DON'T WANT TO USE THIS FIELD. This field is
         -- special! Depending on how far along processing the
         -- PackageDescription we are, the contents of this field are
@@ -197,7 +192,7 @@
         buildType      :: Maybe BuildType,
         setupBuildInfo :: Maybe SetupBuildInfo,
         -- components
-        library        :: Maybe Library,
+        libraries      :: [Library],
         executables    :: [Executable],
         testSuites     :: [TestSuite],
         benchmarks     :: [Benchmark],
@@ -264,7 +259,7 @@
                       category     = "",
                       customFieldsPD = [],
                       setupBuildInfo = Nothing,
-                      library      = Nothing,
+                      libraries    = [],
                       executables  = [],
                       testSuites   = [],
                       benchmarks   = [],
@@ -322,15 +317,13 @@
 
 instance Binary SetupBuildInfo
 
-instance Monoid SetupBuildInfo where
-  mempty = SetupBuildInfo {
-    setupDepends = mempty
-  }
-  mappend a b = SetupBuildInfo {
-    setupDepends = combine setupDepends
-  }
-    where combine field = field a `mappend` field b
+instance Semi.Monoid SetupBuildInfo where
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup SetupBuildInfo where
+  (<>) = gmappend
+
 -- ---------------------------------------------------------------------------
 -- Module renaming
 
@@ -352,10 +345,13 @@
 instance Binary ModuleRenaming where
 
 instance Monoid ModuleRenaming where
-    ModuleRenaming b rns `mappend` ModuleRenaming b' rns'
-        = ModuleRenaming (b || b') (rns ++ rns') -- ToDo: dedupe?
     mempty = ModuleRenaming False []
+    mappend = (Semi.<>)
 
+instance Semigroup ModuleRenaming where
+    ModuleRenaming b rns <> ModuleRenaming b' rns'
+        = ModuleRenaming (b || b') (rns ++ rns') -- TODO: dedupe?
+
 -- NB: parentheses are mandatory, because later we may extend this syntax
 -- to allow "hiding (A, B)" or other modifier words.
 instance Text ModuleRenaming where
@@ -394,6 +390,7 @@
 -- The Library type
 
 data Library = Library {
+        libName :: String,
         exposedModules    :: [ModuleName],
         reexportedModules :: [ModuleReexport],
         requiredSignatures:: [ModuleName], -- ^ What sigs need implementations?
@@ -407,6 +404,7 @@
 
 instance Monoid Library where
   mempty = Library {
+    libName = mempty,
     exposedModules = mempty,
     reexportedModules = mempty,
     requiredSignatures = mempty,
@@ -414,7 +412,11 @@
     libExposed     = True,
     libBuildInfo   = mempty
   }
-  mappend a b = Library {
+  mappend = (Semi.<>)
+
+instance Semigroup Library where
+  a <> b = Library {
+    libName = combine' libName,
     exposedModules = combine exposedModules,
     reexportedModules = combine reexportedModules,
     requiredSignatures = combine requiredSignatures,
@@ -423,26 +425,31 @@
     libBuildInfo   = combine libBuildInfo
   }
     where combine field = field a `mappend` field b
+          combine' field = case (field a, field b) of
+                      ("","") -> ""
+                      ("", x) -> x
+                      (x, "") -> x
+                      (x, y) -> error $ "Ambiguous values for library field: '"
+                                  ++ x ++ "' and '" ++ y ++ "'"
 
 emptyLibrary :: Library
 emptyLibrary = mempty
 
--- |does this package have any libraries?
-hasLibs :: PackageDescription -> Bool
-hasLibs p = maybe False (buildable . libBuildInfo) (library p)
+-- | Does this package have a PUBLIC library?
+hasPublicLib :: PackageDescription -> Bool
+hasPublicLib p = any f (libraries p)
+    where f lib = buildable (libBuildInfo lib) &&
+                  libName lib == display (packageName (package p))
 
--- |'Maybe' version of 'hasLibs'
-maybeHasLibs :: PackageDescription -> Maybe Library
-maybeHasLibs p =
-   library p >>= \lib -> if buildable (libBuildInfo lib)
-                           then Just lib
-                           else Nothing
+-- | Does this package have any libraries?
+hasLibs :: PackageDescription -> Bool
+hasLibs p = any (buildable . libBuildInfo) (libraries p)
 
 -- |If the package description has a library section, call the given
 --  function with the library build info as argument.
 withLib :: PackageDescription -> (Library -> IO ()) -> IO ()
 withLib pkg_descr f =
-   traverse_ f (maybeHasLibs pkg_descr)
+   sequence_ [f lib | lib <- libraries pkg_descr, buildable (libBuildInfo lib)]
 
 -- | Get all the module names from the library (exposed and internal modules)
 -- which need to be compiled.  (This does not include reexports, which
@@ -499,12 +506,11 @@
 instance Binary Executable
 
 instance Monoid Executable where
-  mempty = Executable {
-    exeName    = mempty,
-    modulePath = mempty,
-    buildInfo  = mempty
-  }
-  mappend a b = Executable{
+  mempty = gmempty
+  mappend = (Semi.<>)
+
+instance Semigroup Executable where
+  a <> b = Executable{
     exeName    = combine' exeName,
     modulePath = combine modulePath,
     buildInfo  = combine buildInfo
@@ -589,8 +595,10 @@
         testBuildInfo = mempty,
         testEnabled   = False
     }
+    mappend = (Semi.<>)
 
-    mappend a b = TestSuite {
+instance Semigroup TestSuite where
+    a <> b = TestSuite {
         testName      = combine' testName,
         testInterface = combine  testInterface,
         testBuildInfo = combine  testBuildInfo,
@@ -605,9 +613,12 @@
 
 instance Monoid TestSuiteInterface where
     mempty  =  TestSuiteUnsupported (TestTypeUnknown mempty (Version [] []))
-    mappend a (TestSuiteUnsupported _) = a
-    mappend _ b                        = b
+    mappend = (Semi.<>)
 
+instance Semigroup TestSuiteInterface where
+    a <> (TestSuiteUnsupported _) = a
+    _ <> b                        = b
+
 emptyTestSuite :: TestSuite
 emptyTestSuite = mempty
 
@@ -722,8 +733,10 @@
         benchmarkBuildInfo = mempty,
         benchmarkEnabled   = False
     }
+    mappend = (Semi.<>)
 
-    mappend a b = Benchmark {
+instance Semigroup Benchmark where
+    a <> b = Benchmark {
         benchmarkName      = combine' benchmarkName,
         benchmarkInterface = combine  benchmarkInterface,
         benchmarkBuildInfo = combine  benchmarkBuildInfo,
@@ -738,9 +751,12 @@
 
 instance Monoid BenchmarkInterface where
     mempty  =  BenchmarkUnsupported (BenchmarkTypeUnknown mempty (Version [] []))
-    mappend a (BenchmarkUnsupported _) = a
-    mappend _ b                        = b
+    mappend = (Semi.<>)
 
+instance Semigroup BenchmarkInterface where
+    a <> (BenchmarkUnsupported _) = a
+    _ <> b                        = b
+
 emptyBenchmark :: Benchmark
 emptyBenchmark = mempty
 
@@ -800,6 +816,7 @@
         ldOptions         :: [String],  -- ^ options for linker
         pkgconfigDepends  :: [Dependency], -- ^ pkg-config packages that are used
         frameworks        :: [String], -- ^support frameworks for Mac OS X
+        extraFrameworkDirs:: [String], -- ^ extra locations to find frameworks.
         cSources          :: [FilePath],
         jsSources         :: [FilePath],
         hsSourceDirs      :: [FilePath], -- ^ where to look for the Haskell module hierarchy
@@ -832,63 +849,68 @@
 
 instance Monoid BuildInfo where
   mempty = BuildInfo {
-    buildable         = True,
-    buildTools        = [],
-    cppOptions        = [],
-    ccOptions         = [],
-    ldOptions         = [],
-    pkgconfigDepends  = [],
-    frameworks        = [],
-    cSources          = [],
-    jsSources         = [],
-    hsSourceDirs      = [],
-    otherModules      = [],
-    defaultLanguage   = Nothing,
-    otherLanguages    = [],
-    defaultExtensions = [],
-    otherExtensions   = [],
-    oldExtensions     = [],
-    extraLibs         = [],
-    extraGHCiLibs     = [],
-    extraLibDirs      = [],
-    includeDirs       = [],
-    includes          = [],
-    installIncludes   = [],
-    options           = [],
-    profOptions       = [],
-    sharedOptions     = [],
-    customFieldsBI    = [],
-    targetBuildDepends = [],
+    buildable           = True,
+    buildTools          = [],
+    cppOptions          = [],
+    ccOptions           = [],
+    ldOptions           = [],
+    pkgconfigDepends    = [],
+    frameworks          = [],
+    extraFrameworkDirs  = [],
+    cSources            = [],
+    jsSources           = [],
+    hsSourceDirs        = [],
+    otherModules        = [],
+    defaultLanguage     = Nothing,
+    otherLanguages      = [],
+    defaultExtensions   = [],
+    otherExtensions     = [],
+    oldExtensions       = [],
+    extraLibs           = [],
+    extraGHCiLibs       = [],
+    extraLibDirs        = [],
+    includeDirs         = [],
+    includes            = [],
+    installIncludes     = [],
+    options             = [],
+    profOptions         = [],
+    sharedOptions       = [],
+    customFieldsBI      = [],
+    targetBuildDepends  = [],
     targetBuildRenaming = Map.empty
   }
-  mappend a b = BuildInfo {
-    buildable         = buildable a && buildable b,
-    buildTools        = combine    buildTools,
-    cppOptions        = combine    cppOptions,
-    ccOptions         = combine    ccOptions,
-    ldOptions         = combine    ldOptions,
-    pkgconfigDepends  = combine    pkgconfigDepends,
-    frameworks        = combineNub frameworks,
-    cSources          = combineNub cSources,
-    jsSources         = combineNub jsSources,
-    hsSourceDirs      = combineNub hsSourceDirs,
-    otherModules      = combineNub otherModules,
-    defaultLanguage   = combineMby defaultLanguage,
-    otherLanguages    = combineNub otherLanguages,
-    defaultExtensions = combineNub defaultExtensions,
-    otherExtensions   = combineNub otherExtensions,
-    oldExtensions     = combineNub oldExtensions,
-    extraLibs         = combine    extraLibs,
-    extraGHCiLibs     = combine    extraGHCiLibs,
-    extraLibDirs      = combineNub extraLibDirs,
-    includeDirs       = combineNub includeDirs,
-    includes          = combineNub includes,
-    installIncludes   = combineNub installIncludes,
-    options           = combine    options,
-    profOptions       = combine    profOptions,
-    sharedOptions     = combine    sharedOptions,
-    customFieldsBI    = combine    customFieldsBI,
-    targetBuildDepends = combineNub targetBuildDepends,
+  mappend = (Semi.<>)
+
+instance Semigroup BuildInfo where
+  a <> b = BuildInfo {
+    buildable           = buildable a && buildable b,
+    buildTools          = combine    buildTools,
+    cppOptions          = combine    cppOptions,
+    ccOptions           = combine    ccOptions,
+    ldOptions           = combine    ldOptions,
+    pkgconfigDepends    = combine    pkgconfigDepends,
+    frameworks          = combineNub frameworks,
+    extraFrameworkDirs  = combineNub extraFrameworkDirs,
+    cSources            = combineNub cSources,
+    jsSources           = combineNub jsSources,
+    hsSourceDirs        = combineNub hsSourceDirs,
+    otherModules        = combineNub otherModules,
+    defaultLanguage     = combineMby defaultLanguage,
+    otherLanguages      = combineNub otherLanguages,
+    defaultExtensions   = combineNub defaultExtensions,
+    otherExtensions     = combineNub otherExtensions,
+    oldExtensions       = combineNub oldExtensions,
+    extraLibs           = combine    extraLibs,
+    extraGHCiLibs       = combine    extraGHCiLibs,
+    extraLibDirs        = combineNub extraLibDirs,
+    includeDirs         = combineNub includeDirs,
+    includes            = combineNub includes,
+    installIncludes     = combineNub installIncludes,
+    options             = combine    options,
+    profOptions         = combine    profOptions,
+    sharedOptions       = combine    sharedOptions,
+    customFieldsBI      = combine    customFieldsBI,
+    targetBuildDepends  = combineNub targetBuildDepends,
     targetBuildRenaming = combineMap targetBuildRenaming
   }
     where
@@ -904,7 +926,7 @@
 -- all buildable executables, test suites and benchmarks.  Useful for gathering
 -- dependencies.
 allBuildInfo :: PackageDescription -> [BuildInfo]
-allBuildInfo pkg_descr = [ bi | Just lib <- [library pkg_descr]
+allBuildInfo pkg_descr = [ bi | lib <- libraries pkg_descr
                               , let bi = libBuildInfo lib
                               , buildable bi ]
                       ++ [ bi | exe <- executables pkg_descr
@@ -939,10 +961,22 @@
 usedExtensions bi = oldExtensions bi
                  ++ defaultExtensions bi
 
-type HookedBuildInfo = (Maybe BuildInfo, [(String, BuildInfo)])
+-- Libraries live in a separate namespace, so must distinguish
+data ComponentName = CLibName   String
+                   | CExeName   String
+                   | CTestName  String
+                   | CBenchName String
+                   deriving (Eq, Generic, Ord, Read, Show)
 
+instance Binary ComponentName
+
+defaultLibName :: PackageIdentifier -> ComponentName
+defaultLibName pid = CLibName (display (pkgName pid))
+
+type HookedBuildInfo = [(ComponentName, BuildInfo)]
+
 emptyHookedBuildInfo :: HookedBuildInfo
-emptyHookedBuildInfo = (Nothing, [])
+emptyHookedBuildInfo = []
 
 -- |Select options for a particular Haskell compiler.
 hcOptions :: CompilerFlavor -> BuildInfo -> [String]
@@ -1098,28 +1132,38 @@
 -- ------------------------------------------------------------
 
 updatePackageDescription :: HookedBuildInfo -> PackageDescription -> PackageDescription
-updatePackageDescription (mb_lib_bi, exe_bi) p
-    = p{ executables = updateExecutables exe_bi    (executables p)
-       , library     = updateLibrary     mb_lib_bi (library     p)
-       }
+updatePackageDescription hooked_bis p
+  = p{ executables = updateMany (CExeName . exeName)         updateExecutable (executables p)
+     , libraries   = updateMany (CLibName . libName)         updateLibrary    (libraries   p)
+     , benchmarks  = updateMany (CBenchName . benchmarkName) updateBenchmark  (benchmarks p)
+     , testSuites  = updateMany (CTestName . testName)       updateTestSuite  (testSuites p)
+     }
     where
-      updateLibrary :: Maybe BuildInfo -> Maybe Library -> Maybe Library
-      updateLibrary (Just bi) (Just lib) = Just (lib{libBuildInfo = bi `mappend` libBuildInfo lib})
-      updateLibrary Nothing   mb_lib     = mb_lib
-      updateLibrary (Just _)  Nothing    = Nothing
+      updateMany :: (a -> ComponentName) -- ^ get 'ComponentName' from @a@
+                 -> (BuildInfo -> a -> a) -- ^ @updateExecutable@, @updateLibrary@, etc
+                 -> [a]          -- ^list of components to update
+                 -> [a]          -- ^list with updated components
+      updateMany name update cs' = foldr (updateOne name update) cs' hooked_bis
 
-      updateExecutables :: [(String, BuildInfo)] -- ^[(exeName, new buildinfo)]
-                        -> [Executable]          -- ^list of executables to update
-                        -> [Executable]          -- ^list with exeNames updated
-      updateExecutables exe_bi' executables' = foldr updateExecutable executables' exe_bi'
+      updateOne :: (a -> ComponentName) -- ^ get 'ComponentName' from @a@
+                -> (BuildInfo -> a -> a) -- ^ @updateExecutable@, @updateLibrary@, etc
+                -> (ComponentName, BuildInfo) -- ^(name, new buildinfo)
+                -> [a]        -- ^list of components to update
+                -> [a]        -- ^list with name component updated
+      updateOne _ _ _                 []         = []
+      updateOne name_sel update hooked_bi'@(name,bi) (c:cs)
+        | name_sel c == name ||
+          -- Special case: an empty name means "please update the BuildInfo for
+          -- the public library, i.e. the one with the same name as the
+          -- package."  See 'parseHookedBuildInfo'.
+          name == CLibName "" && name_sel c == defaultLibName (package p)
+          = update bi c : cs
+        | otherwise          = c : updateOne name_sel update hooked_bi' cs
 
-      updateExecutable :: (String, BuildInfo) -- ^(exeName, new buildinfo)
-                       -> [Executable]        -- ^list of executables to update
-                       -> [Executable]        -- ^list with exeName updated
-      updateExecutable _                 []         = []
-      updateExecutable exe_bi'@(name,bi) (exe:exes)
-        | exeName exe == name = exe{buildInfo = bi `mappend` buildInfo exe} : exes
-        | otherwise           = exe : updateExecutable exe_bi' exes
+      updateExecutable bi exe = exe{buildInfo    = bi `mappend` buildInfo exe}
+      updateLibrary    bi lib = lib{libBuildInfo = bi `mappend` libBuildInfo lib}
+      updateBenchmark  bi ben = ben{benchmarkBuildInfo = bi `mappend` benchmarkBuildInfo ben}
+      updateTestSuite  bi test = test{testBuildInfo = bi `mappend` testBuildInfo test}
 
 -- ---------------------------------------------------------------------------
 -- The GenericPackageDescription type
@@ -1128,17 +1172,17 @@
     GenericPackageDescription {
         packageDescription :: PackageDescription,
         genPackageFlags       :: [Flag],
-        condLibrary        :: Maybe (CondTree ConfVar [Dependency] Library),
+        condLibraries      :: [(String, CondTree ConfVar [Dependency] Library)],
         condExecutables    :: [(String, CondTree ConfVar [Dependency] Executable)],
         condTestSuites     :: [(String, CondTree ConfVar [Dependency] TestSuite)],
         condBenchmarks     :: [(String, CondTree ConfVar [Dependency] Benchmark)]
       }
-    deriving (Show, Eq, Typeable, Data)
+    deriving (Show, Eq, Typeable, Data, Generic)
 
 instance Package GenericPackageDescription where
   packageId = packageId . packageDescription
 
---TODO: make PackageDescription an instance of Text.
+instance Binary GenericPackageDescription
 
 -- | A flag can represent a feature to be included, or a way of linking
 --   a target against its dependencies, or in fact whatever you can think of.
@@ -1148,8 +1192,10 @@
     , flagDefault     :: Bool
     , flagManual      :: Bool
     }
-    deriving (Show, Eq, Typeable, Data)
+    deriving (Show, Eq, Typeable, Data, Generic)
 
+instance Binary Flag
+
 -- | A 'FlagName' is the name of a user-defined configuration flag
 newtype FlagName = FlagName String
     deriving (Eq, Generic, Ord, Show, Read, Typeable, Data)
@@ -1168,15 +1214,17 @@
              | Arch Arch
              | Flag FlagName
              | Impl CompilerFlavor VersionRange
-    deriving (Eq, Show, Typeable, Data)
+    deriving (Eq, Show, Typeable, Data, Generic)
 
+instance Binary ConfVar
+
 -- | A boolean expression parameterized over the variable type used.
 data Condition c = Var c
                  | Lit Bool
                  | CNot (Condition c)
                  | COr (Condition c) (Condition c)
                  | CAnd (Condition c) (Condition c)
-    deriving (Show, Eq, Typeable, Data)
+    deriving (Show, Eq, Typeable, Data, Generic)
 
 cNot :: Condition a -> Condition a
 cNot (Lit b)  = Lit (not b)
@@ -1193,23 +1241,23 @@
 instance Foldable Condition where
   f `foldMap` Var c    = f c
   _ `foldMap` Lit _    = mempty
-  f `foldMap` CNot c   = foldMap f c
+  f `foldMap` CNot c   = Fold.foldMap f c
   f `foldMap` COr c d  = foldMap f c `mappend` foldMap f d
   f `foldMap` CAnd c d = foldMap f c `mappend` foldMap f d
 
 instance Traversable Condition where
   f `traverse` Var c    = Var `fmap` f c
   _ `traverse` Lit c    = pure $ Lit c
-  f `traverse` CNot c   = CNot `fmap` traverse f c
+  f `traverse` CNot c   = CNot `fmap` Trav.traverse f c
   f `traverse` COr c d  = COr  `fmap` traverse f c <*> traverse f d
   f `traverse` CAnd c d = CAnd `fmap` traverse f c <*> traverse f d
 
 instance Applicative Condition where
-  pure  = return
+  pure  = Var
   (<*>) = ap
 
 instance Monad Condition where
-  return = Var
+  return = AP.pure
   -- Terminating cases
   (>>=) (Lit x) _ = Lit x
   (>>=) (Var x) f = f x
@@ -1220,8 +1268,11 @@
 
 instance Monoid (Condition a) where
   mempty = Lit False
-  mappend = COr
+  mappend = (Semi.<>)
 
+instance Semigroup (Condition a) where
+  (<>) = COr
+
 instance Alternative Condition where
   empty = mempty
   (<|>) = mappend
@@ -1230,6 +1281,8 @@
   mzero = mempty
   mplus = mappend
 
+instance Binary c => Binary (Condition c)
+
 data CondTree v c a = CondNode
     { condTreeData        :: a
     , condTreeConstraints :: c
@@ -1237,4 +1290,6 @@
                               , CondTree v c a
                               , Maybe (CondTree v c a))]
     }
-    deriving (Show, Eq, Typeable, Data)
+    deriving (Show, Eq, Typeable, Data, Generic)
+
+instance (Binary v, Binary c, Binary a) => Binary (CondTree v c a)
diff --git a/cabal/Cabal/Distribution/PackageDescription/Check.hs b/cabal/Cabal/Distribution/PackageDescription/Check.hs
--- a/cabal/Cabal/Distribution/PackageDescription/Check.hs
+++ b/cabal/Cabal/Distribution/PackageDescription/Check.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE NoMonoLocalBinds #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.PackageDescription.Check
@@ -33,50 +34,30 @@
         checkPackageFileNames,
   ) where
 
-import Data.Maybe
-         ( isNothing, isJust, catMaybes, maybeToList, fromMaybe )
-import Data.List  (sort, group, isPrefixOf, nub, find)
-import Control.Monad
-         ( filterM, liftM )
-import qualified System.Directory as System
-         ( doesFileExist, doesDirectoryExist )
-import qualified Data.Map as Map
-
 import Distribution.PackageDescription
 import Distribution.PackageDescription.Configuration
-         ( flattenPackageDescription, finalizePackageDescription )
 import Distribution.Compiler
-         ( CompilerFlavor(..), buildCompilerFlavor, CompilerId(..)
-         , unknownCompilerInfo, AbiTag(..) )
 import Distribution.System
-         ( OS(..), Arch(..), buildPlatform )
 import Distribution.License
-         ( License(..), knownLicenses )
 import Distribution.Simple.CCompiler
-         ( filenameCDialect )
-import Distribution.Simple.Utils
-         ( cabalVersion, intercalate, parseFileGlob, FileGlob(..), lowercase, startsWithBOM, fromUTF8 )
-
+import Distribution.Simple.Utils hiding (findPackageDesc, notice)
 import Distribution.Version
-         ( Version(..)
-         , VersionRange(..), foldVersionRange'
-         , anyVersion, noVersion, thisVersion, laterVersion, earlierVersion
-         , orLaterVersion, orEarlierVersion
-         , unionVersionRanges, intersectVersionRanges
-         , asVersionIntervals, UpperBound(..), isNoVersion )
 import Distribution.Package
-         ( PackageName(PackageName), packageName, packageVersion
-         , Dependency(..), pkgName )
-
 import Distribution.Text
-         ( display, disp )
+import Language.Haskell.Extension
+
+import Data.Maybe
+         ( isNothing, isJust, catMaybes, mapMaybe, fromMaybe )
+import Data.List  (sort, group, isPrefixOf, nub, find)
+import Control.Monad
+         ( filterM, liftM )
+import qualified System.Directory as System
+         ( doesFileExist, doesDirectoryExist )
+import qualified Data.Map as Map
+
 import qualified Text.PrettyPrint as Disp
 import Text.PrettyPrint ((<>), (<+>))
 
-import qualified Language.Haskell.Extension as Extension (deprecatedExtensions)
-import Language.Haskell.Extension
-         ( Language(UnknownLanguage), knownLanguages
-         , Extension(..), KnownExtension(..) )
 import qualified System.Directory (getDirectoryContents)
 import System.IO (openBinaryFile, IOMode(ReadMode), hGetContents)
 import System.FilePath
@@ -111,7 +92,7 @@
      | PackageDistSuspicious { explanation :: String }
 
        -- | Like PackageDistSuspicious but will only display warnings
-       -- rather than causing abnormal exit.
+       -- rather than causing abnormal exit when you run 'cabal check'.
      | PackageDistSuspiciousWarn { explanation :: String }
 
        -- | An issue that is OK in the author's environment but is almost
@@ -193,19 +174,19 @@
   , check (all ($ pkg) [ null . executables
                        , null . testSuites
                        , null . benchmarks
-                       , isNothing . library ]) $
+                       , null . libraries ]) $
       PackageBuildImpossible
         "No executables, libraries, tests, or benchmarks found. Nothing to do."
 
   , check (not (null duplicateNames)) $
       PackageBuildImpossible $ "Duplicate sections: " ++ commaSep duplicateNames
-        ++ ". The name of every executable, test suite, and benchmark section in"
+        ++ ". The name of every library, executable, test suite, and benchmark section in"
         ++ " the package must be unique."
   ]
   --TODO: check for name clashes case insensitively: windows file systems cannot
   --cope.
 
-  ++ maybe []  (checkLibrary    pkg) (library pkg)
+  ++ concatMap (checkLibrary    pkg) (libraries pkg)
   ++ concatMap (checkExecutable pkg) (executables pkg)
   ++ concatMap (checkTestSuite  pkg) (testSuites pkg)
   ++ concatMap (checkBenchmark  pkg) (benchmarks pkg)
@@ -219,10 +200,15 @@
         ++ "tool only supports up to version " ++ display cabalVersion ++ "."
   ]
   where
+    -- The public library gets special dispensation, because it
+    -- is common practice to export a library and name the executable
+    -- the same as the package.  We always put the public library
+    -- in the top-level directory in dist, so no conflicts either.
+    libNames = filter (/= unPackageName (packageName pkg)) . map libName $ libraries pkg
     exeNames = map exeName $ executables pkg
     testNames = map testName $ testSuites pkg
     bmNames = map benchmarkName $ benchmarks pkg
-    duplicateNames = dups $ exeNames ++ testNames ++ bmNames
+    duplicateNames = dups $ libNames ++ exeNames ++ testNames ++ bmNames
 
 checkLibrary :: PackageDescription -> Library -> [PackageCheck]
 checkLibrary pkg lib =
@@ -317,14 +303,6 @@
       PackageDistInexcusable $
            "The package uses a C/C++/obj-C source file for the 'main-is' field. "
         ++ "To use this feature you must specify 'cabal-version: >= 1.18'."
-
-    -- Test suites might be built as (internal) libraries named after
-    -- the test suite and thus their names must not clash with the
-    -- name of the package.
-  , check libNameClash $
-      PackageBuildImpossible $
-           "The test suite " ++ testName test
-        ++ " has the same name as the package."
   ]
   where
     moduleDuplicates = dups $ testModules test
@@ -337,13 +315,8 @@
       TestSuiteExeV10 _ f -> takeExtension f `notElem` [".hs", ".lhs"]
       _                   -> False
 
-    libNameClash = testName test `elem` [ libName
-                                        | _lib <- maybeToList (library pkg)
-                                        , let PackageName libName =
-                                                pkgName (package pkg) ]
-
 checkBenchmark :: PackageDescription -> Benchmark -> [PackageCheck]
-checkBenchmark pkg bm =
+checkBenchmark _pkg bm =
   catMaybes [
 
     case benchmarkInterface bm of
@@ -369,12 +342,6 @@
       PackageBuildImpossible $
            "The 'main-is' field must specify a '.hs' or '.lhs' file "
         ++ "(even if it is generated by a preprocessor)."
-
-    -- See comment for similar check on test suites.
-  , check libNameClash $
-      PackageBuildImpossible $
-           "The benchmark " ++ benchmarkName bm
-        ++ " has the same name as the package."
   ]
   where
     moduleDuplicates = dups $ benchmarkModules bm
@@ -383,11 +350,6 @@
       BenchmarkExeV10 _ f -> takeExtension f `notElem` [".hs", ".lhs"]
       _                   -> False
 
-    libNameClash = benchmarkName bm `elem` [ libName
-                                           | _lib <- maybeToList (library pkg)
-                                           , let PackageName libName =
-                                                   pkgName (package pkg) ]
-
 -- ------------------------------------------------------------
 -- * Additional pure checks
 -- ------------------------------------------------------------
@@ -403,6 +365,11 @@
         ++ "need to convert package names to file names so using this name "
         ++ "would cause problems."
 
+  , check ((isPrefixOf "z-") . display . packageName $ pkg) $
+      PackageDistInexcusable $
+           "Package names with the prefix 'z-' are reserved by Cabal and "
+        ++ "cannot be used."
+
   , check (isNothing (buildType pkg)) $
       PackageBuildWarning $
            "No 'build-type' specified. If you do not need a custom Setup.hs or "
@@ -442,14 +409,14 @@
         ++ ". Languages must be specified in either the 'default-language' "
         ++ " or the 'other-languages' field."
 
-  , check (not (null deprecatedExtensions)) $
+  , check (not (null ourDeprecatedExtensions)) $
       PackageDistSuspicious $
            "Deprecated extensions: "
-        ++ commaSep (map (quote . display . fst) deprecatedExtensions)
+        ++ commaSep (map (quote . display . fst) ourDeprecatedExtensions)
         ++ ". " ++ unwords
              [ "Instead of '" ++ display ext
             ++ "' use '" ++ display replacement ++ "'."
-             | (ext, Just replacement) <- deprecatedExtensions ]
+             | (ext, Just replacement) <- ourDeprecatedExtensions ]
 
   , check (null (category pkg)) $
       PackageDistSuspicious "No 'category' field."
@@ -491,8 +458,8 @@
     unknownExtensions = [ name | bi <- allBuildInfo pkg
                                , UnknownExtension name <- allExtensions bi
                                , name `notElem` map display knownLanguages ]
-    deprecatedExtensions = nub $ catMaybes
-      [ find ((==ext) . fst) Extension.deprecatedExtensions
+    ourDeprecatedExtensions = nub $ catMaybes
+      [ find ((==ext) . fst) deprecatedExtensions
       | bi <- allBuildInfo pkg
       , ext <- allExtensions bi ]
     languagesUsedAsExtensions =
@@ -624,7 +591,7 @@
       PackageBuildWarning $
            "'ghc-options: -prof' is not necessary and will lead to problems "
         ++ "when used on a library. Use the configure flag "
-        ++ "--enable-library-profiling and/or --enable-executable-profiling."
+        ++ "--enable-library-profiling and/or --enable-profiling."
 
   , checkFlags ["-o"] $
       PackageBuildWarning $
@@ -713,12 +680,21 @@
 
   , checkAlternatives "ghc-options" "extra-lib-dirs"
       [ (flag, dir) | flag@('-':'L':dir) <- all_ghc_options ]
+
+  , checkAlternatives "ghc-options" "frameworks"
+      [ (flag, fmwk) | (flag@"-framework", fmwk) <-
+           zip all_ghc_options (safeTail all_ghc_options) ]
+
+  , checkAlternatives "ghc-options" "extra-framework-dirs"
+      [ (flag, dir) | (flag@"-framework-path", dir) <-
+           zip all_ghc_options (safeTail all_ghc_options) ]
   ]
 
   where
     all_ghc_options    = concatMap get_ghc_options (allBuildInfo pkg)
-    lib_ghc_options    = maybe [] (get_ghc_options . libBuildInfo) (library pkg)
+    lib_ghc_options    = concatMap (get_ghc_options . libBuildInfo) (libraries pkg)
     get_ghc_options bi = hcOptions GHC bi ++ hcProfOptions GHC bi
+                         ++ hcSharedOptions GHC bi
 
     checkFlags :: [String] -> PackageCheck -> Maybe PackageCheck
     checkFlags flags = check (any (`elem` flags) all_ghc_options)
@@ -939,9 +915,18 @@
         ++ "different modules then list the other ones in the "
         ++ "'other-languages' field."
 
+  , checkVersion [1,23]
+    (case libraries pkg of
+        [lib] -> libName lib /= unPackageName (packageName pkg)
+        [] -> False
+        _ -> True) $
+      PackageDistInexcusable $
+           "To use multiple 'library' sections or a named library section "
+        ++ "the package needs to specify at least 'cabal-version >= 1.23'."
+
     -- check use of reexported-modules sections
   , checkVersion [1,21]
-    (maybe False (not.null.reexportedModules) (library pkg)) $
+    (any (not.null.reexportedModules) (libraries pkg)) $
       PackageDistInexcusable $
            "To use the 'reexported-module' field the package needs to specify "
         ++ "at least 'cabal-version: >= 1.21'."
@@ -955,6 +940,13 @@
         ++ ". To use this new syntax, the package needs to specify at least"
         ++ "'cabal-version: >= 1.21'."
 
+    -- check use of 'extra-framework-dirs' field
+  , checkVersion [1,23] (any (not . null) (buildInfoField extraFrameworkDirs)) $
+      -- Just a warning, because this won't break on old Cabal versions.
+      PackageDistSuspiciousWarn $
+           "To use the 'extra-framework-dirs' field the package needs to specify"
+        ++ " at least 'cabal-version: >= 1.23'."
+
     -- check use of default-extensions field
     -- don't need to do the equivalent check for other-extensions
   , checkVersion [1,10] (any (not . null) (buildInfoField defaultExtensions)) $
@@ -1080,7 +1072,7 @@
   , check (specVersion pkg < Version [1,23] []
            && isNothing (setupBuildInfo pkg)
            && buildType pkg == Just Custom) $
-      PackageDistSuspicious $
+      PackageDistSuspiciousWarn $
            "From version 1.23 cabal supports specifiying explicit dependencies "
         ++ "for Custom setup scripts. Consider using cabal-version >= 1.23 and "
         ++ "adding a 'custom-setup' section with a 'setup-depends' field "
@@ -1152,8 +1144,7 @@
     depsUsingThinningRenamingSyntax =
       [ name
       | bi <- allBuildInfo pkg
-      , (name, rns) <- Map.toList (targetBuildRenaming bi)
-      , rns /= ModuleRenaming True [] ]
+      , (name, _) <- Map.toList (targetBuildRenaming bi) ]
 
     testedWithUsingWildcardSyntax =
       [ Dependency (PackageName (display compiler)) vr
@@ -1341,10 +1332,10 @@
     unknownOSs    = [ os   | OS   (OtherOS os)           <- conditions ]
     unknownArches = [ arch | Arch (OtherArch arch)       <- conditions ]
     unknownImpls  = [ impl | Impl (OtherCompiler impl) _ <- conditions ]
-    conditions = maybe [] freeVars (condLibrary pkg)
-              ++ concatMap (freeVars . snd) (condExecutables pkg)
-    freeVars (CondNode _ _ ifs) = concatMap compfv ifs
-    compfv (c, ct, mct) = condfv c ++ freeVars ct ++ maybe [] freeVars mct
+    conditions = concatMap (fvs . snd) (condLibraries pkg)
+              ++ concatMap (fvs . snd) (condExecutables pkg)
+    fvs (CondNode _ _ ifs) = concatMap compfv ifs -- free variables
+    compfv (c, ct, mct) = condfv c ++ fvs ct ++ maybe [] fvs mct
     condfv c = case c of
       Var v      -> [v]
       Lit _      -> []
@@ -1408,6 +1399,7 @@
     has_Wall         = "-Wall"   `elem` ghc_options
     has_W            = "-W"      `elem` ghc_options
     ghc_options      = hcOptions GHC bi ++ hcProfOptions GHC bi
+                       ++ hcSharedOptions GHC bi
 
     checkFlags :: [String] -> PackageCheck -> Maybe PackageCheck
     checkFlags flags = check (any (`elem` flags) ghc_options)
@@ -1444,8 +1436,8 @@
 
     allConditionalBuildInfo :: [([Condition ConfVar], BuildInfo)]
     allConditionalBuildInfo =
-        concatMap (collectCondTreePaths libBuildInfo)
-                  (maybeToList (condLibrary pkg))
+        concatMap (collectCondTreePaths libBuildInfo . snd)
+                  (condLibraries pkg)
 
      ++ concatMap (collectCondTreePaths buildInfo . snd)
                   (condExecutables pkg)
@@ -1538,7 +1530,8 @@
                                     pdfile ++ " starts with an Unicode byte order mark (BOM). This may cause problems with older cabal versions."
 
 -- |Find a package description file in the given directory.  Looks for
--- @.cabal@ files.
+-- @.cabal@ files.  Like 'Distribution.Simple.Utils.findPackageDesc',
+-- but generalized over monads.
 findPackageDesc :: Monad m => CheckPackageContentOps m
                  -> m (Either PackageCheck FilePath) -- ^<pkgname>.cabal
 findPackageDesc ops
@@ -1610,6 +1603,8 @@
              | bi <- allBuildInfo pkg
              , (dir, kind) <-
                   [ (dir, "extra-lib-dirs") | dir <- extraLibDirs bi ]
+               ++ [ (dir, "extra-framework-dirs")
+                  | dir <- extraFrameworkDirs  bi ]
                ++ [ (dir, "include-dirs")   | dir <- includeDirs  bi ]
                ++ [ (dir, "hs-source-dirs") | dir <- hsSourceDirs bi ]
              , isRelative dir ]
@@ -1658,8 +1653,8 @@
 --
 checkPackageFileNames :: [FilePath] -> [PackageCheck]
 checkPackageFileNames files =
-     (take 1 . catMaybes . map checkWindowsPath $ files)
-  ++ (take 1 . catMaybes . map checkTarPath     $ files)
+     (take 1 . mapMaybe checkWindowsPath $ files)
+  ++ (take 1 . mapMaybe checkTarPath     $ files)
       -- If we get any of these checks triggering then we're likely to get
       -- many, and that's probably not helpful, so return at most one.
 
diff --git a/cabal/Cabal/Distribution/PackageDescription/Configuration.hs b/cabal/Cabal/Distribution/PackageDescription/Configuration.hs
--- a/cabal/Cabal/Distribution/PackageDescription/Configuration.hs
+++ b/cabal/Cabal/Distribution/PackageDescription/Configuration.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -- -fno-warn-deprecations for use of Map.foldWithKey
 {-# OPTIONS_GHC -fno-warn-deprecations #-}
 -----------------------------------------------------------------------------
@@ -23,46 +22,34 @@
     -- Utils
     parseCondition,
     freeVars,
+    extractCondition,
+    addBuildableCondition,
     mapCondTree,
     mapTreeData,
     mapTreeConds,
     mapTreeConstrs,
+    transformAllBuildInfos,
+    transformAllBuildDepends,
   ) where
 
 import Distribution.Package
-         ( PackageName, Dependency(..) )
 import Distribution.PackageDescription
-         ( GenericPackageDescription(..), PackageDescription(..)
-         , Library(..), Executable(..), BuildInfo(..)
-         , Flag(..), FlagName(..), FlagAssignment
-         , Benchmark(..), CondTree(..), ConfVar(..), Condition(..)
-         , TestSuite(..) )
 import Distribution.PackageDescription.Utils
-         ( cabalBug, userBug )
 import Distribution.Version
-         ( VersionRange, anyVersion, intersectVersionRanges, withinRange )
 import Distribution.Compiler
-         ( CompilerId(CompilerId) )
 import Distribution.System
-         ( Platform(..), OS, Arch )
 import Distribution.Simple.Utils
-         ( currentDir, lowercase )
-import Distribution.Simple.Compiler
-         ( CompilerInfo(..) )
-
 import Distribution.Text
-         ( Text(parse) )
 import Distribution.Compat.ReadP as ReadP hiding ( char )
-import Control.Arrow (first)
 import qualified Distribution.Compat.ReadP as ReadP ( char )
+import Distribution.Compat.Semigroup as Semi
 
+import Control.Arrow (first)
 import Data.Char ( isAlphaNum )
-import Data.Maybe ( catMaybes, maybeToList )
+import Data.Maybe ( mapMaybe, maybeToList )
 import Data.Map ( Map, fromListWith, toList )
 import qualified Data.Map as Map
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-#endif
+import Data.Tree ( Tree(Node) )
 
 ------------------------------------------------------------------------------
 
@@ -185,14 +172,14 @@
 --   clarity.
 data DepTestRslt d = DepOk | MissingDeps d
 
-instance Monoid d => Monoid (DepTestRslt d) where
+instance Semigroup d => Monoid (DepTestRslt d) where
     mempty = DepOk
-    mappend DepOk x = x
-    mappend x DepOk = x
-    mappend (MissingDeps d) (MissingDeps d') = MissingDeps (d `mappend` d')
-
+    mappend = (Semi.<>)
 
-data BT a = BTN a | BTB (BT a) (BT a)  -- very simple binary tree
+instance Semigroup d => Semigroup (DepTestRslt d) where
+    DepOk <> x     = x
+    x     <> DepOk = x
+    (MissingDeps d) <> (MissingDeps d') = MissingDeps (d <> d')
 
 
 -- | Try to find a flag assignment that satisfies the constraints of all trees.
@@ -201,8 +188,9 @@
 -- resulting data, the associated dependencies, and the chosen flag
 -- assignments.
 --
--- In case of failure, the _smallest_ number of of missing dependencies is
--- returned. [TODO: Could also be specified with a function argument.]
+-- In case of failure, the union of the dependencies that led to backtracking
+-- on all branches is returned.
+-- [TODO: Could also be specified with a function argument.]
 --
 -- TODO: The current algorithm is rather naive.  A better approach would be to:
 --
@@ -228,65 +216,132 @@
        -- ^ Either the missing dependencies (error case), or a pair of
        -- (set of build targets with dependencies, chosen flag assignments)
 resolveWithFlags dom os arch impl constrs trees checkDeps =
-    case try dom [] of
-      Right r -> Right r
-      Left dbt -> Left $ findShortest dbt
+    either (Left . fromDepMapUnion) Right $ explore (build [] dom)
   where
     extraConstrs = toDepMap constrs
 
     -- simplify trees by (partially) evaluating all conditions and converting
     -- dependencies to dependency maps.
+    simplifiedTrees :: [CondTree FlagName DependencyMap PDTagged]
     simplifiedTrees = map ( mapTreeConstrs toDepMap  -- convert to maps
+                          . addBuildableCondition pdTaggedBuildInfo
                           . mapTreeConds (fst . simplifyWithSysParams os arch impl))
                           trees
 
-    -- @try@ recursively tries all possible flag assignments in the domain and
-    -- either succeeds or returns a binary tree with the missing dependencies
-    -- encountered in each run.  Since the tree is constructed lazily, we
-    -- avoid some computation overhead in the successful case.
-    try [] flags =
+    -- @explore@ searches a tree of assignments, backtracking whenever a flag
+    -- introduces a dependency that cannot be satisfied.  If there is no
+    -- solution, @explore@ returns the union of all dependencies that caused
+    -- it to backtrack.  Since the tree is constructed lazily, we avoid some
+    -- computation overhead in the successful case.
+    explore :: Tree FlagAssignment
+            -> Either DepMapUnion (TargetSet PDTagged, FlagAssignment)
+    explore (Node flags ts) =
         let targetSet = TargetSet $ flip map simplifiedTrees $
                 -- apply additional constraints to all dependencies
                 first (`constrainBy` extraConstrs) .
                 simplifyCondTree (env flags)
             deps = overallDependencies targetSet
         in case checkDeps (fromDepMap deps) of
-             DepOk           -> Right (targetSet, flags)
-             MissingDeps mds -> Left (BTN mds)
+             DepOk | null ts   -> Right (targetSet, flags)
+                   | otherwise -> tryAll $ map explore ts
+             MissingDeps mds   -> Left (toDepMapUnion mds)
 
-    try ((n, vals):rest) flags =
-        tryAll $ map (\v -> try rest ((n, v):flags)) vals
+    -- Builds a tree of all possible flag assignments.  Internal nodes
+    -- have only partial assignments.
+    build :: FlagAssignment -> [(FlagName, [Bool])] -> Tree FlagAssignment
+    build assigned [] = Node assigned []
+    build assigned ((fn, vals) : unassigned) =
+        Node assigned $ map (\v -> build ((fn, v) : assigned) unassigned) vals
 
+    tryAll :: [Either DepMapUnion a] -> Either DepMapUnion a
     tryAll = foldr mp mz
 
     -- special version of `mplus' for our local purposes
-    mp (Left xs)   (Left ys)   = (Left (BTB xs ys))
-    mp (Left _)    m@(Right _) = m
+    mp :: Either DepMapUnion a -> Either DepMapUnion a -> Either DepMapUnion a
     mp m@(Right _) _           = m
+    mp _           m@(Right _) = m
+    mp (Left xs)   (Left ys)   =
+        let union = Map.foldrWithKey (Map.insertWith' combine)
+                    (unDepMapUnion xs) (unDepMapUnion ys)
+            combine x y = simplifyVersionRange $ unionVersionRanges x y
+        in union `seq` Left (DepMapUnion union)
 
     -- `mzero'
-    mz = Left (BTN [])
+    mz :: Either DepMapUnion a
+    mz = Left (DepMapUnion Map.empty)
 
+    env :: FlagAssignment -> FlagName -> Either FlagName Bool
     env flags flag = (maybe (Left flag) Right . lookup flag) flags
 
-    -- for the error case we inspect our lazy tree of missing dependencies and
-    -- pick the shortest list of missing dependencies
-    findShortest (BTN x) = x
-    findShortest (BTB lt rt) =
-        let l = findShortest lt
-            r = findShortest rt
-        in case (l,r) of
-             ([], xs) -> xs  -- [] is too short
-             (xs, []) -> xs
-             ([x], _) -> [x] -- single elem is optimum
-             (_, [x]) -> [x]
-             (xs, ys) -> if lazyLengthCmp xs ys
-                         then xs else ys
-    -- lazy variant of @\xs ys -> length xs <= length ys@
-    lazyLengthCmp [] _ = True
-    lazyLengthCmp _ [] = False
-    lazyLengthCmp (_:xs) (_:ys) = lazyLengthCmp xs ys
+    pdTaggedBuildInfo :: PDTagged -> BuildInfo
+    pdTaggedBuildInfo (Lib _ l) = libBuildInfo l
+    pdTaggedBuildInfo (Exe _ e) = buildInfo e
+    pdTaggedBuildInfo (Test _ t) = testBuildInfo t
+    pdTaggedBuildInfo (Bench _ b) = benchmarkBuildInfo b
+    pdTaggedBuildInfo PDNull = mempty
 
+-- | Transforms a 'CondTree' by putting the input under the "then" branch of a
+-- conditional that is True when Buildable is True. If 'addBuildableCondition'
+-- can determine that Buildable is always True, it returns the input unchanged.
+-- If Buildable is always False, it returns the empty 'CondTree'.
+addBuildableCondition :: (Eq v, Monoid a, Monoid c) => (a -> BuildInfo)
+                      -> CondTree v c a
+                      -> CondTree v c a
+addBuildableCondition getInfo t =
+  case extractCondition (buildable . getInfo) t of
+    Lit True  -> t
+    Lit False -> CondNode mempty mempty []
+    c         -> CondNode mempty mempty [(c, t, Nothing)]
+
+-- | Extract buildable condition from a cond tree.
+--
+-- Background: If the conditions in a cond tree lead to Buildable being set to False,
+-- then none of the dependencies for this cond tree should actually be taken into
+-- account. On the other hand, some of the flags may only be decided in the solver,
+-- so we cannot necessarily make the decision whether a component is Buildable or not
+-- prior to solving.
+--
+-- What we are doing here is to partially evaluate a condition tree in order to extract
+-- the condition under which Buildable is True. The predicate determines whether data
+-- under a 'CondTree' is buildable.
+extractCondition :: Eq v => (a -> Bool) -> CondTree v c a -> Condition v
+extractCondition p = go
+  where
+    go (CondNode x _ cs) | not (p x) = Lit False
+                         | otherwise = goList cs
+
+    goList []               = Lit True
+    goList ((c, t, e) : cs) =
+      let
+        ct = go t
+        ce = maybe (Lit True) go e
+      in
+        ((c `cand` ct) `cor` (CNot c `cand` ce)) `cand` goList cs
+
+    cand (Lit False) _           = Lit False
+    cand _           (Lit False) = Lit False
+    cand (Lit True)  x           = x
+    cand x           (Lit True)  = x
+    cand x           y           = CAnd x y
+
+    cor  (Lit True)  _           = Lit True
+    cor  _           (Lit True)  = Lit True
+    cor  (Lit False) x           = x
+    cor  x           (Lit False) = x
+    cor  c           (CNot d)
+      | c == d                   = Lit True
+    cor  x           y           = COr x y
+
+-- | A map of dependencies that combines version ranges using 'unionVersionRanges'.
+newtype DepMapUnion = DepMapUnion { unDepMapUnion :: Map PackageName VersionRange }
+
+toDepMapUnion :: [Dependency] -> DepMapUnion
+toDepMapUnion ds =
+  DepMapUnion $ fromListWith unionVersionRanges [ (p,vr) | Dependency p vr <- ds ]
+
+fromDepMapUnion :: DepMapUnion -> [Dependency]
+fromDepMapUnion m = [ Dependency p vr | (p,vr) <- toList (unDepMapUnion m) ]
+
 -- | A map of dependencies.  Newtyped since the default monoid instance is not
 --   appropriate.  The monoid instance uses 'intersectVersionRanges'.
 newtype DependencyMap = DependencyMap { unDependencyMap :: Map PackageName VersionRange }
@@ -294,7 +349,10 @@
 
 instance Monoid DependencyMap where
     mempty = DependencyMap Map.empty
-    (DependencyMap a) `mappend` (DependencyMap b) =
+    mappend = (Semi.<>)
+
+instance Semigroup DependencyMap where
+    (DependencyMap a) <> (DependencyMap b) =
         DependencyMap (Map.unionWith intersectVersionRanges a b)
 
 toDepMap :: [Dependency] -> DependencyMap
@@ -304,18 +362,20 @@
 fromDepMap :: DependencyMap -> [Dependency]
 fromDepMap m = [ Dependency p vr | (p,vr) <- toList (unDependencyMap m) ]
 
+-- | Flattens a CondTree using a partial flag assignment.  When a condition
+-- cannot be evaluated, both branches are ignored.
 simplifyCondTree :: (Monoid a, Monoid d) =>
                     (v -> Either v Bool)
                  -> CondTree v d a
                  -> (d, a)
 simplifyCondTree env (CondNode a d ifs) =
-    mconcat $ (d, a) : catMaybes (map simplifyIf ifs)
+    mconcat $ (d, a) : mapMaybe simplifyIf ifs
   where
     simplifyIf (cnd, t, me) =
         case simplifyCondition cnd env of
           (Lit True, _) -> Just $ simplifyCondTree env t
           (Lit False, _) -> fmap (simplifyCondTree env) me
-          _ -> error $ "Environment not defined for all free vars"
+          _ -> Nothing
 
 -- | Flatten a CondTree.  This will resolve the CondTree by taking all
 --  possible paths into account.  Note that since branches represent exclusive
@@ -350,11 +410,11 @@
   where
     (depss, _) = unzip $ filter (removeDisabledSections . snd) targets
     removeDisabledSections :: PDTagged -> Bool
-    removeDisabledSections (Lib _) = True
-    removeDisabledSections (Exe _ _) = True
-    removeDisabledSections (Test _ t) = testEnabled t
-    removeDisabledSections (Bench _ b) = benchmarkEnabled b
-    removeDisabledSections PDNull = True
+    removeDisabledSections (Lib _ l)     = buildable (libBuildInfo l)
+    removeDisabledSections (Exe _ e)   = buildable (buildInfo e)
+    removeDisabledSections (Test _ t)  = testEnabled t && buildable (testBuildInfo t)
+    removeDisabledSections (Bench _ b) = benchmarkEnabled b && buildable (benchmarkBuildInfo b)
+    removeDisabledSections PDNull      = True
 
 -- Apply extra constraints to a dependency map.
 -- Combines dependencies where the result will only contain keys from the left
@@ -375,50 +435,53 @@
 -- | Collect up the targets in a TargetSet of tagged targets, storing the
 -- dependencies as we go.
 flattenTaggedTargets :: TargetSet PDTagged ->
-        (Maybe Library, [(String, Executable)], [(String, TestSuite)]
+        ([(String, Library)], [(String, Executable)], [(String, TestSuite)]
         , [(String, Benchmark)])
-flattenTaggedTargets (TargetSet targets) = foldr untag (Nothing, [], [], []) targets
+flattenTaggedTargets (TargetSet targets) = foldr untag ([], [], [], []) targets
   where
-    untag (_, Lib _) (Just _, _, _, _) = userBug "Only one library expected"
-    untag (deps, Lib l) (Nothing, exes, tests, bms) =
-        (Just l', exes, tests, bms)
+    untag (deps, Lib n l) (libs, exes, tests, bms)
+        | any ((== n) . fst) libs =
+          userBug $ "There exist several libs with the same name: '" ++ n ++ "'"
+        -- NB: libraries live in a different namespace than everything else
+        -- TODO: no, (new-style) TESTS live in same namespace!!
+        | otherwise = ((n, l'):libs, exes, tests, bms)
       where
         l' = l {
                 libBuildInfo = (libBuildInfo l) { targetBuildDepends = fromDepMap deps }
             }
-    untag (deps, Exe n e) (mlib, exes, tests, bms)
+    untag (deps, Exe n e) (libs, exes, tests, bms)
         | any ((== n) . fst) exes =
           userBug $ "There exist several exes with the same name: '" ++ n ++ "'"
         | any ((== n) . fst) tests =
           userBug $ "There exists a test with the same name as an exe: '" ++ n ++ "'"
         | any ((== n) . fst) bms =
           userBug $ "There exists a benchmark with the same name as an exe: '" ++ n ++ "'"
-        | otherwise = (mlib, (n, e'):exes, tests, bms)
+        | otherwise = (libs, (n, e'):exes, tests, bms)
       where
         e' = e {
                 buildInfo = (buildInfo e) { targetBuildDepends = fromDepMap deps }
             }
-    untag (deps, Test n t) (mlib, exes, tests, bms)
+    untag (deps, Test n t) (libs, exes, tests, bms)
         | any ((== n) . fst) tests =
           userBug $ "There exist several tests with the same name: '" ++ n ++ "'"
         | any ((== n) . fst) exes =
           userBug $ "There exists an exe with the same name as the test: '" ++ n ++ "'"
         | any ((== n) . fst) bms =
           userBug $ "There exists a benchmark with the same name as the test: '" ++ n ++ "'"
-        | otherwise = (mlib, exes, (n, t'):tests, bms)
+        | otherwise = (libs, exes, (n, t'):tests, bms)
       where
         t' = t {
             testBuildInfo = (testBuildInfo t)
                 { targetBuildDepends = fromDepMap deps }
             }
-    untag (deps, Bench n b) (mlib, exes, tests, bms)
+    untag (deps, Bench n b) (libs, exes, tests, bms)
         | any ((== n) . fst) bms =
           userBug $ "There exist several benchmarks with the same name: '" ++ n ++ "'"
         | any ((== n) . fst) exes =
           userBug $ "There exists an exe with the same name as the benchmark: '" ++ n ++ "'"
         | any ((== n) . fst) tests =
           userBug $ "There exists a test with the same name as the benchmark: '" ++ n ++ "'"
-        | otherwise = (mlib, exes, tests, (n, b'):bms)
+        | otherwise = (libs, exes, tests, (n, b'):bms)
       where
         b' = b {
             benchmarkBuildInfo = (benchmarkBuildInfo b)
@@ -431,7 +494,7 @@
 -- Convert GenericPackageDescription to PackageDescription
 --
 
-data PDTagged = Lib Library
+data PDTagged = Lib String Library
               | Exe String Executable
               | Test String TestSuite
               | Bench String Benchmark
@@ -440,14 +503,17 @@
 
 instance Monoid PDTagged where
     mempty = PDNull
-    PDNull `mappend` x = x
-    x `mappend` PDNull = x
-    Lib l `mappend` Lib l' = Lib (l `mappend` l')
-    Exe n e `mappend` Exe n' e' | n == n' = Exe n (e `mappend` e')
-    Test n t `mappend` Test n' t' | n == n' = Test n (t `mappend` t')
-    Bench n b `mappend` Bench n' b' | n == n' = Bench n (b `mappend` b')
-    _ `mappend` _ = cabalBug "Cannot combine incompatible tags"
+    mappend = (Semi.<>)
 
+instance Semigroup PDTagged where
+    PDNull    <> x      = x
+    x         <> PDNull = x
+    Lib n l   <> Lib   n' l' | n == n' = Lib n (l <> l')
+    Exe n e   <> Exe   n' e' | n == n' = Exe n (e <> e')
+    Test n t  <> Test  n' t' | n == n' = Test n (t <> t')
+    Bench n b <> Bench n' b' | n == n' = Bench n (b <> b')
+    _         <> _  = cabalBug "Cannot combine incompatible tags"
+
 -- | Create a package description with all configurations resolved.
 --
 -- This function takes a `GenericPackageDescription` and several environment
@@ -465,9 +531,10 @@
 --
 -- This function will fail if it cannot find a flag assignment that leads to
 -- satisfiable dependencies.  (It will not try alternative assignments for
--- explicitly specified flags.)  In case of failure it will return a /minimum/
--- number of dependencies that could not be satisfied.  On success, it will
--- return the package description and the full flag assignment chosen.
+-- explicitly specified flags.)  In case of failure it will return the missing
+-- dependencies that it encountered when trying different flag assignments.
+-- On success, it will return the package description and the full flag
+-- assignment chosen.
 --
 finalizePackageDescription ::
      FlagAssignment  -- ^ Explicitly specified flag assignments
@@ -484,25 +551,21 @@
              -- description along with the flag assignments chosen.
 finalizePackageDescription userflags satisfyDep
         (Platform arch os) impl constraints
-        (GenericPackageDescription pkg flags mlib0 exes0 tests0 bms0) =
+        (GenericPackageDescription pkg flags libs0 exes0 tests0 bms0) =
     case resolveFlags of
-      Right ((mlib, exes', tests', bms'), targetSet, flagVals) ->
-        Right ( pkg { library = mlib
+      Right ((libs', exes', tests', bms'), targetSet, flagVals) ->
+        Right ( pkg { libraries = libs'
                     , executables = exes'
                     , testSuites = tests'
                     , benchmarks = bms'
                     , buildDepends = fromDepMap (overallDependencies targetSet)
-                      --TODO: we need to find a way to avoid pulling in deps
-                      -- for non-buildable components. However cannot simply
-                      -- filter at this stage, since if the package were not
-                      -- available we would have failed already.
                     }
               , flagVals )
 
       Left missing -> Left missing
   where
     -- Combine lib, exes, and tests into one list of @CondTree@s with tagged data
-    condTrees = maybeToList (fmap (mapTreeData Lib) mlib0 )
+    condTrees =    map (\(name,tree) -> mapTreeData (Lib name) tree) libs0
                 ++ map (\(name,tree) -> mapTreeData (Exe name) tree) exes0
                 ++ map (\(name,tree) -> mapTreeData (Test name) tree) tests0
                 ++ map (\(name,tree) -> mapTreeData (Bench name) tree) bms0
@@ -510,8 +573,8 @@
     resolveFlags =
         case resolveWithFlags flagChoices os arch impl constraints condTrees check of
           Right (targetSet, fs) ->
-              let (mlib, exes, tests, bms) = flattenTaggedTargets targetSet in
-              Right ( (fmap libFillInDefaults mlib,
+              let (libs, exes, tests, bms) = flattenTaggedTargets targetSet in
+              Right ( (map (\(n,l) -> (libFillInDefaults l) { libName = n }) libs,
                        map (\(n,e) -> (exeFillInDefaults e) { exeName = n }) exes,
                        map (\(n,t) -> (testFillInDefaults t) { testName = n }) tests,
                        map (\(n,b) -> (benchFillInDefaults b) { benchmarkName = n }) bms),
@@ -554,21 +617,21 @@
 -- default path will be missing from the package description returned by this
 -- function.
 flattenPackageDescription :: GenericPackageDescription -> PackageDescription
-flattenPackageDescription (GenericPackageDescription pkg _ mlib0 exes0 tests0 bms0) =
-    pkg { library = mlib
+flattenPackageDescription (GenericPackageDescription pkg _ libs0 exes0 tests0 bms0) =
+    pkg { libraries = reverse libs
         , executables = reverse exes
         , testSuites = reverse tests
         , benchmarks = reverse bms
-        , buildDepends = ldeps ++ reverse edeps ++ reverse tdeps ++ reverse bdeps
+        , buildDepends = reverse ldeps ++ reverse edeps ++ reverse tdeps ++ reverse bdeps
         }
   where
-    (mlib, ldeps) = case mlib0 of
-        Just lib -> let (l,ds) = ignoreConditions lib in
-                    (Just (libFillInDefaults l), ds)
-        Nothing -> (Nothing, [])
+    (libs, ldeps) = foldr flattenLib ([],[]) libs0
     (exes, edeps) = foldr flattenExe ([],[]) exes0
     (tests, tdeps) = foldr flattenTst ([],[]) tests0
     (bms, bdeps) = foldr flattenBm ([],[]) bms0
+    flattenLib (n, t) (es, ds) =
+        let (e, ds') = ignoreConditions t in
+        ( (libFillInDefaults $ e { libName = n }) : es, ds' ++ ds )
     flattenExe (n, t) (es, ds) =
         let (e, ds') = ignoreConditions t in
         ( (exeFillInDefaults $ e { exeName = n }) : es, ds' ++ ds )
@@ -607,3 +670,82 @@
     if null (hsSourceDirs bi)
     then bi { hsSourceDirs = [currentDir] }
     else bi
+
+-- Walk a 'GenericPackageDescription' and apply @onBuildInfo@/@onSetupBuildInfo@
+-- to all nested 'BuildInfo'/'SetupBuildInfo' values.
+transformAllBuildInfos :: (BuildInfo -> BuildInfo)
+                       -> (SetupBuildInfo -> SetupBuildInfo)
+                       -> GenericPackageDescription
+                       -> GenericPackageDescription
+transformAllBuildInfos onBuildInfo onSetupBuildInfo gpd = gpd'
+  where
+    onLibrary    lib  = lib { libBuildInfo  = onBuildInfo $ libBuildInfo  lib }
+    onExecutable exe  = exe { buildInfo     = onBuildInfo $ buildInfo     exe }
+    onTestSuite  tst  = tst { testBuildInfo = onBuildInfo $ testBuildInfo tst }
+    onBenchmark  bmk  = bmk { benchmarkBuildInfo =
+                                 onBuildInfo $ benchmarkBuildInfo bmk }
+
+    pd = packageDescription gpd
+    pd'  = pd {
+      libraries      = map  onLibrary        (libraries pd),
+      executables    = map  onExecutable     (executables pd),
+      testSuites     = map  onTestSuite      (testSuites pd),
+      benchmarks     = map  onBenchmark      (benchmarks pd),
+      setupBuildInfo = fmap onSetupBuildInfo (setupBuildInfo pd)
+      }
+
+    gpd' = transformAllCondTrees onLibrary onExecutable
+           onTestSuite onBenchmark id
+           $ gpd { packageDescription = pd' }
+
+-- | Walk a 'GenericPackageDescription' and apply @f@ to all nested
+-- @build-depends@ fields.
+transformAllBuildDepends :: (Dependency -> Dependency)
+                         -> GenericPackageDescription
+                         -> GenericPackageDescription
+transformAllBuildDepends f gpd = gpd'
+  where
+    onBI  bi  = bi  { targetBuildDepends = map f $ targetBuildDepends bi }
+    onSBI stp = stp { setupDepends       = map f $ setupDepends stp      }
+    onPD  pd  = pd  { buildDepends       = map f $ buildDepends pd       }
+
+    pd'   = onPD $ packageDescription gpd
+    gpd'  = transformAllCondTrees id id id id (map f)
+            . transformAllBuildInfos onBI onSBI
+            $ gpd { packageDescription = pd' }
+
+-- | Walk all 'CondTree's inside a 'GenericPackageDescription' and apply
+-- appropriate transformations to all nodes. Helper function used by
+-- 'transformAllBuildDepends' and 'transformAllBuildInfos'.
+transformAllCondTrees :: (Library -> Library)
+                      -> (Executable -> Executable)
+                      -> (TestSuite -> TestSuite)
+                      -> (Benchmark -> Benchmark)
+                      -> ([Dependency] -> [Dependency])
+                      -> GenericPackageDescription -> GenericPackageDescription
+transformAllCondTrees onLibrary onExecutable
+  onTestSuite onBenchmark onDepends gpd = gpd'
+  where
+    gpd'    = gpd {
+      condLibraries      = condLibs',
+      condExecutables    = condExes',
+      condTestSuites     = condTests',
+      condBenchmarks     = condBenchs'
+      }
+
+    condLibs   = condLibraries      gpd
+    condExes   = condExecutables    gpd
+    condTests  = condTestSuites     gpd
+    condBenchs = condBenchmarks     gpd
+
+    condLibs'   = map  (mapSnd $ onCondTree onLibrary)    condLibs
+    condExes'   = map  (mapSnd $ onCondTree onExecutable) condExes
+    condTests'  = map  (mapSnd $ onCondTree onTestSuite)  condTests
+    condBenchs' = map  (mapSnd $ onCondTree onBenchmark)  condBenchs
+
+    mapSnd :: (a -> b) -> (c,a) -> (c,b)
+    mapSnd = fmap
+
+    onCondTree :: (a -> b) -> CondTree v [Dependency] a
+               -> CondTree v [Dependency] b
+    onCondTree g = mapCondTree g onDepends id
diff --git a/cabal/Cabal/Distribution/PackageDescription/Parse.hs b/cabal/Cabal/Distribution/PackageDescription/Parse.hs
--- a/cabal/Cabal/Distribution/PackageDescription/Parse.hs
+++ b/cabal/Cabal/Distribution/PackageDescription/Parse.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE PatternGuards #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 -----------------------------------------------------------------------------
 -- |
@@ -41,8 +42,20 @@
         flagFieldDescrs
   ) where
 
+import Distribution.ParseUtils hiding (parseFields)
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Utils
+import Distribution.Package
+import Distribution.ModuleName
+import Distribution.Version
+import Distribution.Verbosity
+import Distribution.Compiler
+import Distribution.PackageDescription.Configuration
+import Distribution.Simple.Utils
+import Distribution.Text
+import Distribution.Compat.ReadP hiding (get)
+
 import Data.Char     (isSpace)
-import Data.Foldable (traverse_)
 import Data.Maybe    (listToMaybe, isJust)
 import Data.List     (nub, unfoldr, partition, (\\))
 import Control.Monad (liftM, foldM, when, unless, ap)
@@ -53,36 +66,10 @@
 import Control.Arrow    (first)
 import System.Directory (doesFileExist)
 import qualified Data.ByteString.Lazy.Char8 as BS.Char8
-import Data.Typeable
-import Data.Data
-import qualified Data.Map as Map
 
-import Distribution.Text
-         ( Text(disp, parse), display, simpleParse )
-import Distribution.Compat.ReadP
-         ((+++), option)
-import qualified Distribution.Compat.ReadP as Parse
 import Text.PrettyPrint
 
-import Distribution.ParseUtils hiding (parseFields)
-import Distribution.PackageDescription
-import Distribution.PackageDescription.Utils
-         ( cabalBug, userBug )
-import Distribution.Package
-         ( PackageIdentifier(..), Dependency(..), packageName, packageVersion )
-import Distribution.ModuleName ( ModuleName )
-import Distribution.Version
-        ( Version(Version), orLaterVersion
-        , LowerBound(..), asVersionIntervals )
-import Distribution.Verbosity (Verbosity)
-import Distribution.Compiler  (CompilerFlavor(..))
-import Distribution.PackageDescription.Configuration (parseCondition, freeVars)
-import Distribution.Simple.Utils
-         ( die, dieWithLocation, warn, intercalate, lowercase, cabalVersion
-         , withFileContents, withUTF8FileContents
-         , writeFileAtomic, writeUTF8File )
 
-
 -- -----------------------------------------------------------------------------
 -- The PackageDescription type
 
@@ -402,8 +389,7 @@
            buildTools         (\xs  binfo -> binfo{buildTools=xs})
  , commaListFieldWithSep vcat "build-depends"
            disp                   parse
-           buildDependsWithRenaming
-           setBuildDependsWithRenaming
+           targetBuildDepends (\xs binfo -> binfo{targetBuildDepends=xs})
  , spaceListField "cpp-options"
            showToken          parseTokenQ'
            cppOptions          (\val binfo -> binfo{cppOptions=val})
@@ -419,6 +405,9 @@
  , listField "frameworks"
            showToken          parseTokenQ
            frameworks         (\val binfo -> binfo{frameworks=val})
+ , listField "extra-framework-dirs"
+           showToken          parseFilePathQ
+           extraFrameworkDirs (\val binfo -> binfo{extraFrameworkDirs=val})
  , listFieldWithSep vcat "c-sources"
            showFilePath       parseFilePathQ
            cSources           (\paths binfo -> binfo{cSources=paths})
@@ -597,7 +586,8 @@
 -- prop_isMapM fs = mapSimpleFields return fs == return fs
 
 
--- names of fields that represents dependencies, thus consrca
+-- names of fields that represents dependencies
+-- TODO: maybe build-tools should go here too?
 constraintFieldNames :: [String]
 constraintFieldNames = ["build-depends"]
 
@@ -605,9 +595,9 @@
 -- they add and define an accessor that specifies what the dependencies
 -- are.  This way we would completely reuse the parsing knowledge from the
 -- field descriptor.
-parseConstraint :: Field -> ParseResult [DependencyWithRenaming]
+parseConstraint :: Field -> ParseResult [Dependency]
 parseConstraint (F l n v)
-    | n == "build-depends" = runP l n (parseCommaList parse) v
+    | n `elem` constraintFieldNames = runP l n (parseCommaList parse) v
 parseConstraint f = userBug $ "Constraint was expected (got: " ++ show f ++ ")"
 
 {-
@@ -640,12 +630,14 @@
 #else
 instance (Monad m, Functor m) => Applicative (StT s m) where
 #endif
-    pure = return
+    pure a = StT (\s -> return (a,s))
     (<*>) = ap
 
 
 instance Monad m => Monad (StT s m) where
+#if __GLASGOW_HASKELL__ < 710
     return a = StT (\s -> return (a,s))
+#endif
     StT f >>= g = StT $ \s -> do
                         (a,s') <- f s
                         runStT (g a) s'
@@ -749,14 +741,14 @@
 
           -- 'getBody' assumes that the remaining fields only consist of
           -- flags, lib and exe sections.
-        (repos, flags, mcsetup, mlib, exes, tests, bms) <- getBody
+        (repos, flags, mcsetup, libs, exes, tests, bms) <- getBody pkg
         warnIfRest  -- warn if getBody did not parse up to the last field.
           -- warn about using old/new syntax with wrong cabal-version:
         maybeWarnCabalVersion (not $ oldSyntax fields0) pkg
-        checkForUndefinedFlags flags mlib exes tests
+        checkForUndefinedFlags flags libs exes tests
         return $ GenericPackageDescription
                    pkg { sourceRepos = repos, setupBuildInfo = mcsetup }
-                   flags mlib exes tests bms
+                   flags libs exes tests bms
 
   where
     oldSyntax = all isSimpleField
@@ -856,17 +848,18 @@
         _ -> return (reverse acc)
 
     --
-    -- body ::= { repo | flag | library | executable | test }+   -- at most one lib
+    -- body ::= { repo | flag | library | executable | test }+
     --
     -- The body consists of an optional sequence of declarations of flags and
-    -- an arbitrary number of executables and at most one library.
-    getBody :: PM ([SourceRepo], [Flag]
+    -- an arbitrary number of libraries/executables/tests.
+    getBody :: PackageDescription
+            -> PM ([SourceRepo], [Flag]
                   ,Maybe SetupBuildInfo
-                  ,Maybe (CondTree ConfVar [Dependency] Library)
+                  ,[(String, CondTree ConfVar [Dependency] Library)]
                   ,[(String, CondTree ConfVar [Dependency] Executable)]
                   ,[(String, CondTree ConfVar [Dependency] TestSuite)]
                   ,[(String, CondTree ConfVar [Dependency] Benchmark)])
-    getBody = peekField >>= \mf -> case mf of
+    getBody pkg = peekField >>= \mf -> case mf of
       Just (Section line_no sec_type sec_label sec_fields)
         | sec_type == "executable" -> do
             when (null sec_label) $ lift $ syntaxError line_no
@@ -874,7 +867,7 @@
             exename <- lift $ runP line_no "executable" parseTokenQ sec_label
             flds <- collectFields parseExeFields sec_fields
             skipField
-            (repos, flags, csetup, lib, exes, tests, bms) <- getBody
+            (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg
             return (repos, flags, csetup, lib, (exename, flds): exes, tests, bms)
 
         | sec_type == "test-suite" -> do
@@ -904,7 +897,6 @@
                         -- Does the current node specify a test type?
                         hasTestType = testInterface ts'
                             /= testInterface emptyTestSuite
-                        components = condTreeComponents ct
                     -- If the current level of the tree specifies a type,
                     -- then we are done. If not, then one of the conditional
                     -- branches below the current node must specify a type.
@@ -912,11 +904,11 @@
                     -- only one need one to specify a type because the
                     -- configure step uses 'mappend' to join together the
                     -- results of flag resolution.
-                    in hasTestType || any checkComponent components
+                    in hasTestType || any checkComponent (condTreeComponents ct)
             if checkTestType emptyTestSuite flds
                 then do
                     skipField
-                    (repos, flags, csetup, lib, exes, tests, bms) <- getBody
+                    (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg
                     return (repos, flags, csetup, lib, exes,
                             (testname, flds) : tests, bms)
                 else lift $ syntaxError line_no $
@@ -953,7 +945,6 @@
                         -- Does the current node specify a benchmark type?
                         hasBenchmarkType = benchmarkInterface ts'
                             /= benchmarkInterface emptyBenchmark
-                        components = condTreeComponents ct
                     -- If the current level of the tree specifies a type,
                     -- then we are done. If not, then one of the conditional
                     -- branches below the current node must specify a type.
@@ -961,11 +952,11 @@
                     -- only one need one to specify a type because the
                     -- configure step uses 'mappend' to join together the
                     -- results of flag resolution.
-                    in hasBenchmarkType || any checkComponent components
+                    in hasBenchmarkType || any checkComponent (condTreeComponents ct)
             if checkBenchmarkType emptyBenchmark flds
                 then do
                     skipField
-                    (repos, flags, csetup, lib, exes, tests, bms) <- getBody
+                    (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg
                     return (repos, flags, csetup, lib, exes,
                             tests, (benchname, flds) : bms)
                 else lift $ syntaxError line_no $
@@ -976,14 +967,15 @@
                       ++ intercalate ", " (map display knownBenchmarkTypes)
 
         | sec_type == "library" -> do
-            unless (null sec_label) $ lift $
-              syntaxError line_no "'library' expects no argument"
+            libname <- if null sec_label
+                        then return (unPackageName (packageName pkg))
+                        -- TODO: relax this parsing so that scoping is handled
+                        -- correctly
+                        else lift $ runP line_no "library" parseTokenQ sec_label
             flds <- collectFields parseLibFields sec_fields
             skipField
-            (repos, flags, csetup, lib, exes, tests, bms) <- getBody
-            when (isJust lib) $ lift $ syntaxError line_no
-              "There can only be one library section in a package description."
-            return (repos, flags, csetup, Just flds, exes, tests, bms)
+            (repos, flags, csetup, libs, exes, tests, bms) <- getBody pkg
+            return (repos, flags, csetup, (libname, flds) : libs, exes, tests, bms)
 
         | sec_type == "flag" -> do
             when (null sec_label) $ lift $
@@ -994,7 +986,7 @@
                     (MkFlag (FlagName (lowercase sec_label)) "" True False)
                     sec_fields
             skipField
-            (repos, flags, csetup, lib, exes, tests, bms) <- getBody
+            (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg
             return (repos, flag:flags, csetup, lib, exes, tests, bms)
 
         | sec_type == "source-repository" -> do
@@ -1019,7 +1011,7 @@
                     }
                     sec_fields
             skipField
-            (repos, flags, csetup, lib, exes, tests, bms) <- getBody
+            (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg
             return (repo:repos, flags, csetup, lib, exes, tests, bms)
 
         | sec_type == "custom-setup" -> do
@@ -1031,7 +1023,7 @@
                              mempty
                              sec_fields
             skipField
-            (repos, flags, csetup0, lib, exes, tests, bms) <- getBody
+            (repos, flags, csetup0, lib, exes, tests, bms) <- getBody pkg
             when (isJust csetup0) $ lift $ syntaxError line_no
               "There can only be one 'custom-setup' section in a package description."
             return (repos, flags, Just flds, lib, exes, tests, bms)
@@ -1039,18 +1031,18 @@
         | otherwise -> do
             lift $ warning $ "Ignoring unknown section type: " ++ sec_type
             skipField
-            getBody
+            getBody pkg
       Just f@(F {}) -> do
             _ <- lift $ syntaxError (lineNo f) $
               "Plain fields are not allowed in between stanzas: " ++ show f
             skipField
-            getBody
+            getBody pkg
       Just f@(IfBlock {}) -> do
             _ <- lift $ syntaxError (lineNo f) $
               "If-blocks are not allowed in between stanzas: " ++ show f
             skipField
-            getBody
-      Nothing -> return ([], [], Nothing, Nothing, [], [], [])
+            getBody pkg
+      Nothing -> return ([], [], Nothing, [], [], [], [])
 
     -- Extracts all fields in a block and returns a 'CondTree'.
     --
@@ -1064,18 +1056,30 @@
             condFlds = [ f | f@IfBlock{} <- allflds ]
             sections = [ s | s@Section{} <- allflds ]
 
-        -- Put these through the normal parsing pass too, so that we
-        -- collect the ModRenamings
-        let depFlds = filter isConstraint simplFlds
-        
         mapM_
             (\(Section l n _ _) -> lift . warning $
                 "Unexpected section '" ++ n ++ "' on line " ++ show l)
             sections
 
         a <- parser simplFlds
-        deps <- liftM concat . mapM (lift . fmap (map dependency) .  parseConstraint) $ depFlds
 
+        -- Dependencies must be treated specially: when we
+        -- parse into a CondTree, not only do we parse them into
+        -- the targetBuildDepends/etc field inside the
+        -- PackageDescription, but we also have to put the
+        -- combined dependencies into CondTree.
+        --
+        -- This information is, in principle, redundant, but
+        -- putting it here makes it easier for the constraint
+        -- solver to pick a flag assignment which supports
+        -- all of the dependencies (because it only has
+        -- to check the CondTree, rather than grovel everywhere
+        -- inside the conditional bits).
+        deps <- liftM concat
+              . mapM (lift . parseConstraint)
+              . filter isConstraint
+              $ simplFlds
+
         ifs <- mapM processIfs condFlds
 
         return (CondNode a deps ifs)
@@ -1115,13 +1119,13 @@
 
     checkForUndefinedFlags ::
         [Flag] ->
-        Maybe (CondTree ConfVar [Dependency] Library) ->
+        [(String, CondTree ConfVar [Dependency] Library)] ->
         [(String, CondTree ConfVar [Dependency] Executable)] ->
         [(String, CondTree ConfVar [Dependency] TestSuite)] ->
         PM ()
-    checkForUndefinedFlags flags mlib exes tests = do
+    checkForUndefinedFlags flags libs exes tests = do
         let definedFlags = map flagName flags
-        traverse_ (checkCondTreeFlags definedFlags) mlib
+        mapM_ (checkCondTreeFlags definedFlags . snd) libs
         mapM_ (checkCondTreeFlags definedFlags . snd) exes
         mapM_ (checkCondTreeFlags definedFlags . snd) tests
 
@@ -1198,24 +1202,39 @@
 parseHookedBuildInfo :: String -> ParseResult HookedBuildInfo
 parseHookedBuildInfo inp = do
   fields <- readFields inp
-  let ss@(mLibFields:exes) = stanzas fields
+  let (mLibFields:rest) = stanzas fields
   mLib <- parseLib mLibFields
-  biExes <- mapM parseExe (maybe ss (const exes) mLib)
-  return (mLib, biExes)
+  foldM parseStanza mLib rest
   where
-    parseLib :: [Field] -> ParseResult (Maybe BuildInfo)
+    -- For backwards compatibility, if you have a bare stanza,
+    -- we assume it's part of the public library.  We don't
+    -- know what the name is, so the people using the HookedBuildInfo
+    -- have to handle this carefully.
+    parseLib :: [Field] -> ParseResult [(ComponentName, BuildInfo)]
     parseLib (bi@(F _ inFieldName _:_))
-        | lowercase inFieldName /= "executable" = liftM Just (parseBI bi)
-    parseLib _ = return Nothing
+        | lowercase inFieldName /= "executable" &&
+          lowercase inFieldName /= "library" &&
+          lowercase inFieldName /= "benchmark" &&
+          lowercase inFieldName /= "test-suite"
+            = liftM (\bis -> [(CLibName "", bis)]) (parseBI bi)
+    parseLib _ = return []
 
-    parseExe :: [Field] -> ParseResult (String, BuildInfo)
-    parseExe (F line inFieldName mName:bi)
-        | lowercase inFieldName == "executable"
-            = do bis <- parseBI bi
-                 return (mName, bis)
-        | otherwise = syntaxError line "expecting 'executable' at top of stanza"
-    parseExe (_:_) = cabalBug "`parseExe' called on a non-field"
-    parseExe [] = syntaxError 0 "error in parsing buildinfo file. Expected executable stanza"
+    parseStanza :: HookedBuildInfo -> [Field] -> ParseResult HookedBuildInfo
+    parseStanza bis (F line inFieldName mName:bi)
+        | Just k <- case lowercase inFieldName of
+                        "executable" -> Just CExeName
+                        "library"    -> Just CLibName
+                        "benchmark"  -> Just CBenchName
+                        "test-suite" -> Just CTestName
+                        _ -> Nothing
+            = do bi' <- parseBI bi
+                 return ((k mName, bi'):bis)
+        | otherwise
+            = syntaxError line $
+                "expecting 'executable', 'library', 'benchmark' or 'test-suite' " ++
+                "at top of stanza, but got '" ++ inFieldName ++ "'"
+    parseStanza _ (_:_) = cabalBug "`parseStanza' called on a non-field"
+    parseStanza _ [] = syntaxError 0 "error in parsing buildinfo file. Expected stanza"
 
     parseBI st = parseFields binfoFieldDescrs storeXFieldsBI emptyBuildInfo st
 
@@ -1231,9 +1250,7 @@
 showPackageDescription pkg = render $
      ppPackage pkg
   $$ ppCustomFields (customFieldsPD pkg)
-  $$ (case library pkg of
-        Nothing  -> empty
-        Just lib -> ppLibrary lib)
+  $$ vcat [ space $$ ppLibrary lib | lib <- libraries pkg ]
   $$ vcat [ space $$ ppExecutable exe | exe <- executables pkg ]
   where
     ppPackage    = ppFields pkgDescrFieldDescrs
@@ -1251,15 +1268,16 @@
                              . showHookedBuildInfo
 
 showHookedBuildInfo :: HookedBuildInfo -> String
-showHookedBuildInfo (mb_lib_bi, ex_bis) = render $
-     (case mb_lib_bi of
-        Nothing -> empty
-        Just bi -> ppBuildInfo bi)
-  $$ vcat [    space
-            $$ text "executable:" <+> text name
+showHookedBuildInfo bis = render $
+     vcat [    space
+            $$ ppName name
             $$ ppBuildInfo bi
-          | (name, bi) <- ex_bis ]
+          | (name, bi) <- bis ]
   where
+    ppName (CLibName name) = text "library:" <+> text name
+    ppName (CExeName name) = text "executable:" <+> text name
+    ppName (CTestName name) = text "test-suite:" <+> text name
+    ppName (CBenchName name) = text "benchmark:" <+> text name
     ppBuildInfo bi = ppFields binfoFieldDescrs bi
                   $$ ppCustomFields (customFieldsBI bi)
 
@@ -1278,32 +1296,3 @@
 
 --test_findIndentTabs = findIndentTabs $ unlines $
 --    [ "foo", "  bar", " \t baz", "\t  biz\t", "\t\t \t mib" ]
-
--- | Dependencies plus module renamings.  This is what users specify; however,
--- renaming information is not used for dependency resolution.
-data DependencyWithRenaming = DependencyWithRenaming Dependency ModuleRenaming
-  deriving (Read, Show, Eq, Typeable, Data)
-
-dependency :: DependencyWithRenaming -> Dependency
-dependency (DependencyWithRenaming dep _) = dep
-
-instance Text DependencyWithRenaming where
-  disp (DependencyWithRenaming d rns) = disp d <+> disp rns
-  parse = do d <- parse
-             Parse.skipSpaces
-             rns <- parse
-             Parse.skipSpaces
-             return (DependencyWithRenaming d rns)
-
-buildDependsWithRenaming :: BuildInfo -> [DependencyWithRenaming]
-buildDependsWithRenaming pkg =
-    map (\dep@(Dependency n _) ->
-            DependencyWithRenaming dep
-                (Map.findWithDefault defaultRenaming n (targetBuildRenaming pkg)))
-        (targetBuildDepends pkg)
-
-setBuildDependsWithRenaming :: [DependencyWithRenaming] -> BuildInfo -> BuildInfo
-setBuildDependsWithRenaming deps pkg = pkg {
-    targetBuildDepends = map dependency deps,
-    targetBuildRenaming = Map.fromList (map (\(DependencyWithRenaming (Dependency n _) rns) -> (n, rns)) deps)
-  }
diff --git a/cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs b/cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs
--- a/cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs
+++ b/cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.PackageDescription.PrettyPrint
@@ -18,27 +17,18 @@
     showGenericPackageDescription,
 ) where
 
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid (Monoid(mempty))
-#endif
 import Distribution.PackageDescription
-       ( Benchmark(..), BenchmarkInterface(..), benchmarkType
-       , TestSuite(..), TestSuiteInterface(..), testType
-       , SourceRepo(..),
-        customFieldsBI, CondTree(..), Condition(..), cNot,
-        FlagName(..), ConfVar(..), Executable(..), Library(..),
-        Flag(..), PackageDescription(..),
-        GenericPackageDescription(..))
+import Distribution.Simple.Utils
+import Distribution.ParseUtils
+import Distribution.PackageDescription.Parse
+import Distribution.Package
+import Distribution.Text
+
+import Data.Monoid as Mon (Monoid(mempty))
+import Data.Maybe (isJust)
 import Text.PrettyPrint
        (hsep, parens, char, nest, empty, isEmpty, ($$), (<+>),
         colon, (<>), text, vcat, ($+$), Doc, render)
-import Distribution.Simple.Utils (writeUTF8File)
-import Distribution.ParseUtils (showFreeText, FieldDescr(..), indentWith, ppField, ppFields)
-import Distribution.PackageDescription.Parse (pkgDescrFieldDescrs,binfoFieldDescrs,libFieldDescrs,
-       sourceRepoFieldDescrs,flagFieldDescrs)
-import Distribution.Package (Dependency(..))
-import Distribution.Text (Text(..))
-import Data.Maybe (isJust)
 
 -- | Recompile with false for regression testing
 simplifiedPrinting :: Bool
@@ -56,7 +46,8 @@
 ppGenericPackageDescription gpd          =
         ppPackageDescription (packageDescription gpd)
         $+$ ppGenPackageFlags (genPackageFlags gpd)
-        $+$ ppLibrary (condLibrary gpd)
+        $+$ ppLibraries (unPackageName (packageName (packageDescription gpd)))
+                        (condLibraries gpd)
         $+$ ppExecutables (condExecutables gpd)
         $+$ ppTestSuites (condTestSuites gpd)
         $+$ ppBenchmarks (condBenchmarks gpd)
@@ -116,10 +107,10 @@
   where
     fields = ppFieldsFiltered flagDefaults flagFieldDescrs flag
 
-ppLibrary :: (Maybe (CondTree ConfVar [Dependency] Library)) -> Doc
-ppLibrary Nothing                        = empty
-ppLibrary (Just condTree)                =
-    emptyLine $ text "library" $+$ nest indentWith (ppCondTree condTree Nothing ppLib)
+ppLibraries :: String -> [(String, CondTree ConfVar [Dependency] Library)] -> Doc
+ppLibraries pn libs                           =
+    vcat [emptyLine $ text (if n == pn then "library" else "library " ++ n)
+              $+$ nest indentWith (ppCondTree condTree Nothing ppLib)| (n,condTree) <- libs]
   where
     ppLib lib Nothing     = ppFieldsFiltered libDefaults libFieldDescrs lib
                             $$  ppCustomFields (customFieldsBI (libBuildInfo lib))
@@ -236,9 +227,9 @@
            -> Condition ConfVar
            -> CondTree ConfVar [Dependency] a
            -> Doc
-ppIf' it ppIt c thenTree = 
+ppIf' it ppIt c thenTree =
   if isEmpty thenDoc
-     then mempty
+     then Mon.mempty
      else ppIfCondition c $$ nest indentWith thenDoc
   where thenDoc = ppCondTree thenTree (if simplifiedPrinting then (Just it) else Nothing) ppIt
 
@@ -249,7 +240,7 @@
               -> Doc
 ppIfElse it ppIt c thenTree elseTree =
   case (isEmpty thenDoc, isEmpty elseDoc) of
-    (True,  True)  -> mempty
+    (True,  True)  -> Mon.mempty
     (False, True)  -> ppIfCondition c $$ nest indentWith thenDoc
     (True,  False) -> ppIfCondition (cNot c) $$ nest indentWith elseDoc
     (False, False) -> (ppIfCondition c $$ nest indentWith thenDoc)
diff --git a/cabal/Cabal/Distribution/ParseUtils.hs b/cabal/Cabal/Distribution/ParseUtils.hs
--- a/cabal/Cabal/Distribution/ParseUtils.hs
+++ b/cabal/Cabal/Distribution/ParseUtils.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.ParseUtils
@@ -40,21 +39,17 @@
         UnrecFieldParser, warnUnrec, ignoreUnrec,
   ) where
 
-import Distribution.Compiler (CompilerFlavor, parseCompilerFlavorCompat)
+import Distribution.Compiler
 import Distribution.License
 import Distribution.Version
-         ( Version(..), VersionRange, anyVersion )
-import Distribution.Package     ( PackageName(..), Dependency(..) )
-import Distribution.ModuleName (ModuleName)
+import Distribution.Package
+import Distribution.ModuleName
+import qualified Distribution.Compat.MonadFail as Fail
 import Distribution.Compat.ReadP as ReadP hiding (get)
 import Distribution.ReadE
 import Distribution.Text
-         ( Text(..) )
 import Distribution.Simple.Utils
-         ( comparing, dropWhileEndLE, intercalate, lowercase
-         , normaliseLineEndings )
 import Language.Haskell.Extension
-         ( Language, Extension )
 
 import Text.PrettyPrint hiding (braces)
 import Data.Char (isSpace, toLower, isAlphaNum, isDigit)
@@ -62,9 +57,7 @@
 import Data.Tree as Tree (Tree(..), flatten)
 import qualified Data.Map as Map
 import Control.Monad (foldM, ap)
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative (Applicative(..))
-#endif
+import Control.Applicative as AP (Applicative(..))
 import System.FilePath (normalise)
 import Data.List (sortBy)
 
@@ -98,16 +91,19 @@
         fmap f (ParseOk ws x) = ParseOk ws $ f x
 
 instance Applicative ParseResult where
-        pure = return
+        pure = ParseOk []
         (<*>) = ap
 
 
 instance Monad ParseResult where
-        return = ParseOk []
+        return = AP.pure
         ParseFailed err >>= _ = ParseFailed err
         ParseOk ws x >>= f = case f x of
                                ParseFailed err -> ParseFailed err
                                ParseOk ws' x' -> ParseOk (ws'++ws) x'
+        fail = Fail.fail
+
+instance Fail.MonadFail ParseResult where
         fail s = ParseFailed (FromString s Nothing)
 
 catchParseError :: ParseResult a -> (PError -> ParseResult a)
@@ -660,8 +656,7 @@
 parseOptVersion :: ReadP r Version
 parseOptVersion = parseQuoted ver <++ ver
   where ver :: ReadP r Version
-        ver = parse <++ return noVersion
-        noVersion = Version [] []
+        ver = parse <++ return (Version [] [])
 
 parseTestedWithQ :: ReadP r (CompilerFlavor,VersionRange)
 parseTestedWithQ = parseQuoted tw <++ tw
diff --git a/cabal/Cabal/Distribution/Simple.hs b/cabal/Cabal/Distribution/Simple.hs
--- a/cabal/Cabal/Distribution/Simple.hs
+++ b/cabal/Cabal/Distribution/Simple.hs
@@ -56,64 +56,47 @@
 -- local
 import Distribution.Simple.Compiler hiding (Flag)
 import Distribution.Simple.UserHooks
-import Distribution.Package --must not specify imports, since we're exporting module.
-import Distribution.PackageDescription
-         ( PackageDescription(..), GenericPackageDescription, Executable(..)
-         , updatePackageDescription, hasLibs
-         , HookedBuildInfo, emptyHookedBuildInfo )
+import Distribution.Package
+import Distribution.PackageDescription hiding (Flag)
 import Distribution.PackageDescription.Parse
-         ( readPackageDescription, readHookedBuildInfo )
 import Distribution.PackageDescription.Configuration
-         ( flattenPackageDescription )
 import Distribution.Simple.Program
-         ( defaultProgramConfiguration, addKnownPrograms, builtinPrograms
-         , restoreProgramConfiguration, reconfigurePrograms )
-import Distribution.Simple.PreProcess (knownSuffixHandlers, PPSuffixHandler)
+import Distribution.Simple.Program.Db
+import Distribution.Simple.PreProcess
 import Distribution.Simple.Setup
 import Distribution.Simple.Command
 
-import Distribution.Simple.Build        ( build, repl )
-import Distribution.Simple.SrcDist      ( sdist )
+import Distribution.Simple.Build
+import Distribution.Simple.SrcDist
 import Distribution.Simple.Register
-         ( register, unregister )
 
 import Distribution.Simple.Configure
-         ( getPersistBuildConfig, maybeGetPersistBuildConfig
-         , writePersistBuildConfig, checkPersistBuildConfigOutdated
-         , configure, checkForeignDeps, findDistPrefOrDefault )
 
-import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..) )
-import Distribution.Simple.Bench (bench)
-import Distribution.Simple.BuildPaths ( srcPref)
-import Distribution.Simple.Test (test)
-import Distribution.Simple.Install (install)
-import Distribution.Simple.Haddock (haddock, hscolour)
+import Distribution.Simple.LocalBuildInfo
+import Distribution.Simple.Bench
+import Distribution.Simple.BuildPaths
+import Distribution.Simple.Test
+import Distribution.Simple.Install
+import Distribution.Simple.Haddock
 import Distribution.Simple.Utils
-         (die, notice, info, warn, setupMessage, chattyTry,
-          defaultPackageDesc, defaultHookedPackageDesc,
-          rawSystemExitWithEnv, cabalVersion, topHandler )
-import Distribution.System
-         ( OS(..), buildOS )
+import Distribution.Utils.NubList
 import Distribution.Verbosity
 import Language.Haskell.Extension
 import Distribution.Version
 import Distribution.License
 import Distribution.Text
-         ( display )
 
 -- Base
 import System.Environment(getArgs, getProgName)
 import System.Directory(removeFile, doesFileExist,
                         doesDirectoryExist, removeDirectoryRecursive)
 import System.Exit       (exitWith,ExitCode(..))
-import System.IO.Error   (isDoesNotExistError)
-import Control.Exception (throwIO)
+import System.FilePath(searchPathSeparator)
 import Distribution.Compat.Environment (getEnvironment)
-import Distribution.Compat.Exception (catchIO)
 
 import Control.Monad   (when)
 import Data.Foldable   (traverse_)
-import Data.List       (intercalate, unionBy, nub, (\\))
+import Data.List       (unionBy)
 
 -- | A simple implementation of @main@ for a Cabal setup script.
 -- It reads the package description file using IO, and performs the
@@ -322,7 +305,7 @@
         flags' = flags { copyDistPref = toFlag distPref }
     hookedAction preCopy copyHook postCopy
                  (getBuildConfig hooks verbosity distPref)
-                 hooks flags' args
+                 hooks flags' { copyArgs = args } args
 
 installAction :: UserHooks -> InstallFlags -> Args -> IO ()
 installAction hooks flags args = do
@@ -424,19 +407,15 @@
    post_hook hooks args flags pkg_descr localbuildinfo
 
 sanityCheckHookedBuildInfo :: PackageDescription -> HookedBuildInfo -> IO ()
-sanityCheckHookedBuildInfo PackageDescription { library = Nothing } (Just _,_)
-    = die $ "The buildinfo contains info for a library, "
-         ++ "but the package does not have a library."
-
-sanityCheckHookedBuildInfo pkg_descr (_, hookExes)
-    | not (null nonExistant)
-    = die $ "The buildinfo contains info for an executable called '"
-         ++ head nonExistant ++ "' but the package does not have a "
-         ++ "executable with that name."
+sanityCheckHookedBuildInfo pkg_descr hooked_bis
+    | not (null nonExistentComponents)
+    = die $ "The buildinfo contains info for these non-existent components:"
+         ++ intercalate ", " (map showComponentName nonExistentComponents)
   where
-    pkgExeNames  = nub (map exeName (executables pkg_descr))
-    hookExeNames = nub (map fst hookExes)
-    nonExistant  = hookExeNames \\ pkgExeNames
+    nonExistentComponents =
+        [ cname
+        | (cname, _) <- hooked_bis
+        , Nothing <- [lookupComponent pkg_descr cname] ]
 
 sanityCheckHookedBuildInfo _ _ = return ()
 
@@ -470,9 +449,9 @@
             -- Since the list of unconfigured programs is not serialized,
             -- restore it to the same value as normally used at the beginning
             -- of a configure run:
-            configPrograms = restoreProgramConfiguration
+            configPrograms_ = restoreProgramConfiguration
                                (builtinPrograms ++ hookedPrograms hooks)
-                               (configPrograms cFlags),
+                               `fmap` configPrograms_ cFlags,
 
             -- Use the current, not saved verbosity level:
             configVerbosity = Flag verbosity
@@ -587,12 +566,9 @@
     = simpleUserHooks
       {
        postConf    = defaultPostConf,
-       preBuild    = \_ flags ->
-                       -- not using 'readHook' here because 'build' takes
-                       -- extra args
-                       getHookedBuildInfo $ fromFlag $ buildVerbosity flags,
+       preBuild    = readHookWithArgs buildVerbosity,
+       preCopy     = readHookWithArgs copyVerbosity,
        preClean    = readHook cleanVerbosity,
-       preCopy     = readHook copyVerbosity,
        preInst     = readHook installVerbosity,
        preHscolour = readHook hscolourVerbosity,
        preHaddock  = readHook haddockVerbosity,
@@ -616,6 +592,12 @@
 
           backwardsCompatHack = False
 
+          readHookWithArgs :: (a -> Flag Verbosity) -> Args -> a -> IO HookedBuildInfo
+          readHookWithArgs get_verbosity _ flags = do
+              getHookedBuildInfo verbosity
+            where
+              verbosity = fromFlag (get_verbosity flags)
+
           readHook :: (a -> Flag Verbosity) -> Args -> a -> IO HookedBuildInfo
           readHook get_verbosity a flags = do
               noExtraFlags a
@@ -626,7 +608,6 @@
 runConfigureScript :: Verbosity -> Bool -> ConfigFlags -> LocalBuildInfo
                    -> IO ()
 runConfigureScript verbosity backwardsCompatHack flags lbi = do
-
   env <- getEnvironment
   let programConfig = withPrograms lbi
   (ccProg, ccFlags) <- configureCCompiler verbosity programConfig
@@ -636,32 +617,25 @@
   -- to ccFlags
   -- We don't try and tell configure which ld to use, as we don't have
   -- a way to pass its flags too
-  let env' = appendToEnvironment ("CFLAGS",  unwords ccFlags)
-             env
-      args' = args ++ ["--with-gcc=" ++ ccProg]
-  handleNoWindowsSH $
-    rawSystemExitWithEnv verbosity "sh" args' env'
+  let extraPath = fromNubList $ configProgramPathExtra flags
+  let cflagsEnv = maybe (unwords ccFlags) (++ (" " ++ unwords ccFlags)) $ lookup "CFLAGS" env
+      spSep = [searchPathSeparator]
+      pathEnv = maybe (intercalate spSep extraPath) ((intercalate spSep extraPath ++ spSep)++) $ lookup "PATH" env
+      overEnv = ("CFLAGS", Just cflagsEnv) : [("PATH", Just pathEnv) | not (null extraPath)]
+      args' = args ++ ["CC=" ++ ccProg]
+      shProg = simpleProgram "sh"
+      progDb = modifyProgramSearchPath (\p -> map ProgramSearchPathDir extraPath ++ p) emptyProgramDb
+  shConfiguredProg <- lookupProgram shProg `fmap` configureProgram  verbosity shProg progDb
+  case shConfiguredProg of
+      Just sh -> runProgramInvocation verbosity (programInvocation (sh {programOverrideEnv = overEnv}) args')
+      Nothing -> die notFoundMsg
 
   where
     args = "./configure" : configureArgs backwardsCompatHack flags
 
-    appendToEnvironment (key, val) [] = [(key, val)]
-    appendToEnvironment (key, val) (kv@(k, v) : rest)
-     | key == k  = (key, v ++ " " ++ val) : rest
-     | otherwise = kv : appendToEnvironment (key, val) rest
-
-    handleNoWindowsSH action
-      | buildOS /= Windows
-      = action
-
-      | otherwise
-      = action
-          `catchIO` \ioe -> if isDoesNotExistError ioe
-                              then die notFoundMsg
-                              else throwIO ioe
-
-    notFoundMsg = "The package has a './configure' script. This requires a "
-               ++ "Unix compatibility toolchain such as MinGW+MSYS or Cygwin."
+    notFoundMsg = "The package has a './configure' script. If you are on Windows, This requires a "
+               ++ "Unix compatibility toolchain such as MinGW+MSYS or Cygwin. "
+               ++ "If you are not on Windows, ensure that an 'sh' command is discoverable in your path."
 
 getHookedBuildInfo :: Verbosity -> IO HookedBuildInfo
 getHookedBuildInfo verbosity = do
@@ -714,6 +688,5 @@
 defaultRegHook pkg_descr localbuildinfo _ flags =
     if hasLibs pkg_descr
     then register pkg_descr localbuildinfo flags
-    else setupMessage verbosity
+    else setupMessage (fromFlag (regVerbosity flags))
            "Package contains no library to register:" (packageId pkg_descr)
-  where verbosity = fromFlag (regVerbosity flags)
diff --git a/cabal/Cabal/Distribution/Simple/Bench.hs b/cabal/Cabal/Distribution/Simple/Bench.hs
--- a/cabal/Cabal/Distribution/Simple/Bench.hs
+++ b/cabal/Cabal/Distribution/Simple/Bench.hs
@@ -16,22 +16,17 @@
     ) where
 
 import qualified Distribution.PackageDescription as PD
-    ( PackageDescription(..), BuildInfo(buildable)
-    , Benchmark(..), BenchmarkInterface(..), benchmarkType, hasBenchmarks )
-import Distribution.Simple.BuildPaths ( exeExtension )
-import Distribution.Simple.Compiler ( compilerInfo )
+import Distribution.Simple.BuildPaths
+import Distribution.Simple.Compiler
 import Distribution.Simple.InstallDirs
-    ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)
-    , substPathTemplate , toPathTemplate, PathTemplate )
 import qualified Distribution.Simple.LocalBuildInfo as LBI
-    ( LocalBuildInfo(..), localLibraryName )
-import Distribution.Simple.Setup ( BenchmarkFlags(..), fromFlag )
-import Distribution.Simple.UserHooks ( Args )
-import Distribution.Simple.Utils ( die, notice, rawSystemExitCode )
+import Distribution.Simple.Setup
+import Distribution.Simple.UserHooks
+import Distribution.Simple.Utils
 import Distribution.Text
 
-import Control.Monad ( when, unless )
-import System.Exit ( ExitCode(..), exitFailure, exitWith )
+import Control.Monad ( when, unless, forM )
+import System.Exit ( ExitCode(..), exitFailure, exitSuccess )
 import System.Directory ( doesFileExist )
 import System.FilePath ( (</>), (<.>) )
 
@@ -78,9 +73,9 @@
                       ++ show (disp $ PD.benchmarkType bm)
                   exitFailure
 
-    when (not $ PD.hasBenchmarks pkg_descr) $ do
+    unless (PD.hasBenchmarks pkg_descr) $ do
         notice verbosity "Package has no benchmarks."
-        exitWith ExitSuccess
+        exitSuccess
 
     when (PD.hasBenchmarks pkg_descr && null enabledBenchmarks) $
         die $ "No benchmarks enabled. Did you remember to configure with "
@@ -88,7 +83,7 @@
 
     bmsToRun <- case benchmarkNames of
             [] -> return enabledBenchmarks
-            names -> flip mapM names $ \bmName ->
+            names -> forM names $ \bmName ->
                 let benchmarkMap = zip enabledNames enabledBenchmarks
                     enabledNames = map PD.benchmarkName enabledBenchmarks
                     allNames = map PD.benchmarkName pkgBenchmarks
@@ -123,6 +118,6 @@
     fromPathTemplate $ substPathTemplate env template
   where
     env = initialPathTemplateEnv
-          (PD.package pkg_descr) (LBI.localLibraryName lbi)
+          (PD.package pkg_descr) (LBI.localUnitId lbi)
           (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++
           [(BenchmarkNameVar, toPathTemplate $ PD.benchmarkName bm)]
diff --git a/cabal/Cabal/Distribution/Simple/Build.hs b/cabal/Cabal/Distribution/Simple/Build.hs
--- a/cabal/Cabal/Distribution/Simple/Build.hs
+++ b/cabal/Cabal/Distribution/Simple/Build.hs
@@ -23,6 +23,7 @@
     writeAutogenFiles,
   ) where
 
+import Distribution.Package
 import qualified Distribution.Simple.GHC   as GHC
 import qualified Distribution.Simple.GHCJS as GHCJS
 import qualified Distribution.Simple.JHC   as JHC
@@ -32,66 +33,39 @@
 
 import qualified Distribution.Simple.Build.Macros      as Build.Macros
 import qualified Distribution.Simple.Build.PathsModule as Build.PathsModule
+import qualified Distribution.Simple.Program.HcPkg as HcPkg
 
-import Distribution.Package
-         ( Package(..), PackageName(..), PackageIdentifier(..)
-         , Dependency(..), thisPackageVersion, PackageKey(..), packageName
-         , LibraryName(..) )
-import Distribution.Simple.Compiler
-         ( Compiler, CompilerFlavor(..), compilerFlavor
-         , PackageDB(..), PackageDBStack )
-import Distribution.PackageDescription
-         ( PackageDescription(..), BuildInfo(..), Library(..), Executable(..)
-         , TestSuite(..), TestSuiteInterface(..), Benchmark(..)
-         , BenchmarkInterface(..), allBuildInfo, defaultRenaming )
+import Distribution.Simple.Compiler hiding (Flag)
+import Distribution.PackageDescription hiding (Flag)
 import qualified Distribution.InstalledPackageInfo as IPI
 import qualified Distribution.ModuleName as ModuleName
-import Distribution.ModuleName (ModuleName)
 
 import Distribution.Simple.Setup
-         ( Flag(..), BuildFlags(..), ReplFlags(..), fromFlag )
 import Distribution.Simple.BuildTarget
-         ( BuildTarget(..), readBuildTargets )
 import Distribution.Simple.PreProcess
-         ( preprocessComponent, preprocessExtras, PPSuffixHandler )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(compiler, buildDir, withPackageDB, withPrograms)
-         , Component(..), componentName, getComponent, componentBuildInfo
-         , ComponentLocalBuildInfo(..), pkgEnabledComponents
-         , withComponentsInBuildOrder, componentsInBuildOrder
-         , ComponentName(..), showComponentName
-         , ComponentDisabledReason(..), componentDisabledReason
-         , inplacePackageId )
 import Distribution.Simple.Program.Types
 import Distribution.Simple.Program.Db
-import qualified Distribution.Simple.Program.HcPkg as HcPkg
 import Distribution.Simple.BuildPaths
-         ( autogenModulesDir, autogenModuleName, cppHeaderName, exeExtension )
+import Distribution.Simple.Configure
 import Distribution.Simple.Register
-         ( registerPackage, inplaceInstalledPackageInfo )
-import Distribution.Simple.Test.LibV09 ( stubFilePath, stubName )
+import Distribution.Simple.Test.LibV09
 import Distribution.Simple.Utils
-         ( createDirectoryIfMissingVerbose, rewriteFile
-         , die, info, debug, warn, setupMessage )
 
-import Distribution.Verbosity
-         ( Verbosity )
+import Distribution.System
 import Distribution.Text
-         ( display )
+import Distribution.Verbosity
 
 import qualified Data.Map as Map
 import qualified Data.Set as Set
-import Data.Either
-         ( partitionEithers )
 import Data.List
-         ( intersect, intercalate )
+         ( intersect )
 import Control.Monad
-         ( when, unless, forM_ )
+         ( when, unless )
 import System.FilePath
          ( (</>), (<.>) )
 import System.Directory
-         ( getCurrentDirectory, removeDirectoryRecursive, removeFile
-         , doesDirectoryExist, doesFileExist )
+         ( getCurrentDirectory )
 
 -- -----------------------------------------------------------------------------
 -- |Build the libraries and executables in this package.
@@ -107,26 +81,27 @@
 
   targets  <- readBuildTargets pkg_descr (buildArgs flags)
   targets' <- checkBuildTargets verbosity pkg_descr targets
-  let componentsToBuild = map fst (componentsInBuildOrder lbi (map fst targets'))
+  let componentsToBuild = componentsInBuildOrder lbi (map fst targets')
   info verbosity $ "Component build order: "
-                ++ intercalate ", " (map showComponentName componentsToBuild)
+                ++ intercalate ", " (map (showComponentName . componentLocalName) componentsToBuild)
 
-  initialBuildSteps distPref pkg_descr lbi verbosity
   when (null targets) $
     -- Only bother with this message if we're building the whole package
     setupMessage verbosity "Building" (packageId pkg_descr)
 
   internalPackageDB <- createInternalPackageDB verbosity lbi distPref
 
-  withComponentsInBuildOrder pkg_descr lbi componentsToBuild $ \comp clbi ->
+  -- TODO: we're computing this twice, do it once!
+  withComponentsInBuildOrder pkg_descr lbi (map fst targets') $ \comp clbi -> do
+    initialBuildSteps distPref pkg_descr lbi clbi verbosity
     let bi     = componentBuildInfo comp
         progs' = addInternalBuildTools pkg_descr lbi bi (withPrograms lbi)
         lbi'   = lbi {
                    withPrograms  = progs',
                    withPackageDB = withPackageDB lbi ++ [internalPackageDB]
                  }
-    in buildComponent verbosity (buildNumJobs flags) pkg_descr
-                      lbi' suffixes comp clbi distPref
+    buildComponent verbosity (buildNumJobs flags) pkg_descr
+                   lbi' suffixes comp clbi distPref
 
 
 repl     :: PackageDescription  -- ^ Mostly information from the .cabal file
@@ -149,9 +124,7 @@
       componentForRepl  = last componentsToBuild
   debug verbosity $ "Component build order: "
                  ++ intercalate ", "
-                      [ showComponentName c | (c,_) <- componentsToBuild ]
-
-  initialBuildSteps distPref pkg_descr lbi verbosity
+                      [ showComponentName (componentLocalName clbi) | clbi <- componentsToBuild ]
 
   internalPackageDB <- createInternalPackageDB verbosity lbi distPref
 
@@ -164,25 +137,30 @@
 
   -- build any dependent components
   sequence_
-    [ let comp = getComponent pkg_descr cname
-          lbi' = lbiForComponent comp lbi
-       in buildComponent verbosity NoFlag
-                         pkg_descr lbi' suffixes comp clbi distPref
-    | (cname, clbi) <- init componentsToBuild ]
+    [ do let cname = componentLocalName clbi
+             comp = getComponent pkg_descr cname
+             lbi' = lbiForComponent comp lbi
+         initialBuildSteps distPref pkg_descr lbi clbi verbosity
+         buildComponent verbosity NoFlag
+                        pkg_descr lbi' suffixes comp clbi distPref
+    | clbi <- init componentsToBuild ]
 
   -- REPL for target components
-  let (cname, clbi) = componentForRepl
+  let clbi = componentForRepl
+      cname = componentLocalName clbi
       comp = getComponent pkg_descr cname
       lbi' = lbiForComponent comp lbi
-   in replComponent verbosity pkg_descr lbi' suffixes comp clbi distPref
+  initialBuildSteps distPref pkg_descr lbi clbi verbosity
+  replComponent verbosity pkg_descr lbi' suffixes comp clbi distPref
 
 
 -- | Start an interpreter without loading any package files.
-startInterpreter :: Verbosity -> ProgramDb -> Compiler -> PackageDBStack -> IO ()
-startInterpreter verbosity programDb comp packageDBs =
+startInterpreter :: Verbosity -> ProgramDb -> Compiler -> Platform
+                 -> PackageDBStack -> IO ()
+startInterpreter verbosity programDb comp platform packageDBs =
   case compilerFlavor comp of
-    GHC   -> GHC.startInterpreter   verbosity programDb comp packageDBs
-    GHCJS -> GHCJS.startInterpreter verbosity programDb comp packageDBs
+    GHC   -> GHC.startInterpreter   verbosity programDb comp platform packageDBs
+    GHCJS -> GHCJS.startInterpreter verbosity programDb comp platform packageDBs
     _     -> die "A REPL is not supported with this compiler."
 
 buildComponent :: Verbosity
@@ -196,9 +174,9 @@
                -> IO ()
 buildComponent verbosity numJobs pkg_descr lbi suffixes
                comp@(CLib lib) clbi distPref = do
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
-    info verbosity "Building library..."
+    info verbosity $ "Building library " ++ libName lib ++ "..."
     let libbi = libBuildInfo lib
         lib' = lib { libBuildInfo = addExtraCSources libbi extras }
     buildLib verbosity numJobs pkg_descr lbi lib' clbi
@@ -207,17 +185,15 @@
     -- on internally defined libraries.
     pwd <- getCurrentDirectory
     let -- The in place registration uses the "-inplace" suffix, not an ABI hash
-        ipkgid           = inplacePackageId (packageId installedPkgInfo)
         installedPkgInfo = inplaceInstalledPackageInfo pwd distPref pkg_descr
-                                                       ipkgid lib' lbi clbi
+                                                       (AbiHash "") lib' lbi clbi
 
-    registerPackage verbosity
-      installedPkgInfo pkg_descr lbi True -- True meaning in place
-      (withPackageDB lbi)
+    registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.MultiInstance
+                    (withPackageDB lbi) installedPkgInfo
 
 buildComponent verbosity numJobs pkg_descr lbi suffixes
                comp@(CExe exe) clbi _ = do
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     info verbosity $ "Building executable " ++ exeName exe ++ "..."
     let ebi = buildInfo exe
@@ -229,7 +205,7 @@
                comp@(CTest test@TestSuite { testInterface = TestSuiteExeV10{} })
                clbi _distPref = do
     let exe = testSuiteExeV10AsExe test
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     info verbosity $ "Building test suite " ++ testName test ++ "..."
     let ebi = buildInfo exe
@@ -249,11 +225,15 @@
     pwd <- getCurrentDirectory
     let (pkg, lib, libClbi, lbi, ipi, exe, exeClbi) =
           testSuiteLibV09AsLibAndExe pkg_descr test clbi lbi0 distPref pwd
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     info verbosity $ "Building test suite " ++ testName test ++ "..."
     buildLib verbosity numJobs pkg lbi lib libClbi
-    registerPackage verbosity ipi pkg lbi True $ withPackageDB lbi
+    -- NB: need to enable multiple instances here, because on 7.10+
+    -- the package name is the same as the library, and we still
+    -- want the registration to go through.
+    registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.MultiInstance
+                    (withPackageDB lbi) ipi
     let ebi = buildInfo exe
         exe' = exe { buildInfo = addExtraCSources ebi extras }
     buildExe verbosity numJobs pkg_descr lbi exe' exeClbi
@@ -269,7 +249,7 @@
                comp@(CBench bm@Benchmark { benchmarkInterface = BenchmarkExeV10 {} })
                clbi _ = do
     let (exe, exeClbi) = benchmarkExeV10asExe bm clbi
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     info verbosity $ "Building benchmark " ++ benchmarkName bm ++ "..."
     let ebi = buildInfo exe
@@ -302,7 +282,7 @@
               -> IO ()
 replComponent verbosity pkg_descr lbi suffixes
                comp@(CLib lib) clbi _ = do
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     let libbi = libBuildInfo lib
         lib' = lib { libBuildInfo = libbi { cSources = cSources libbi ++ extras } }
@@ -310,7 +290,7 @@
 
 replComponent verbosity pkg_descr lbi suffixes
                comp@(CExe exe) clbi _ = do
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     let ebi = buildInfo exe
         exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }
@@ -321,7 +301,7 @@
                comp@(CTest test@TestSuite { testInterface = TestSuiteExeV10{} })
                clbi _distPref = do
     let exe = testSuiteExeV10AsExe test
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     let ebi = buildInfo exe
         exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }
@@ -335,7 +315,7 @@
     pwd <- getCurrentDirectory
     let (pkg, lib, libClbi, lbi, _, _, _) =
           testSuiteLibV09AsLibAndExe pkg_descr test clbi lbi0 distPref pwd
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     let libbi = libBuildInfo lib
         lib' = lib { libBuildInfo = libbi { cSources = cSources libbi ++ extras } }
@@ -352,7 +332,7 @@
                comp@(CBench bm@Benchmark { benchmarkInterface = BenchmarkExeV10 {} })
                clbi _ = do
     let (exe, exeClbi) = benchmarkExeV10asExe bm clbi
-    preprocessComponent pkg_descr comp lbi False verbosity suffixes
+    preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
     extras <- preprocessExtras comp lbi
     let ebi = buildInfo exe
         exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }
@@ -397,6 +377,7 @@
   where
     bi  = testBuildInfo test
     lib = Library {
+            libName = testName test,
             exposedModules = [ m ],
             reexportedModules = [],
             requiredSignatures = [],
@@ -404,24 +385,29 @@
             libExposed     = True,
             libBuildInfo   = bi
           }
+    -- This is, like, the one place where we use a CTestName for a library.
+    -- Should NOT use library name, since that could conflict!
+    PackageIdentifier pkg_name pkg_ver = package pkg_descr
+    compat_name = computeCompatPackageName pkg_name (CTestName (testName test))
+    compat_key = computeCompatPackageKey (compiler lbi) compat_name pkg_ver (componentUnitId clbi)
     libClbi = LibComponentLocalBuildInfo
                 { componentPackageDeps = componentPackageDeps clbi
-                , componentPackageRenaming = componentPackageRenaming clbi
-                , componentLibraryName = LibraryName (testName test)
-                , componentExposedModules = [IPI.ExposedModule m Nothing Nothing]
-                , componentPackageKey = OldPackageKey (PackageIdentifier (PackageName (testName test)) (pkgVersion (package pkg_descr)))
+                , componentLocalName = CLibName (testName test)
+                , componentIsPublic = False
+                , componentIncludes = componentIncludes clbi
+                , componentUnitId = componentUnitId clbi
+                , componentCompatPackageName = compat_name
+                , componentCompatPackageKey = compat_key
+                , componentExposedModules = [IPI.ExposedModule m Nothing]
                 }
     pkg = pkg_descr {
-            package      = (package pkg_descr) {
-                             pkgName = PackageName (testName test)
-                           }
+            package      = (package pkg_descr) { pkgName = compat_name }
           , buildDepends = targetBuildDepends $ testBuildInfo test
           , executables  = []
           , testSuites   = []
-          , library      = Just lib
+          , libraries    = [lib]
           }
-    ipkgid = inplacePackageId (packageId pkg)
-    ipi    = inplaceInstalledPackageInfo pwd distPref pkg ipkgid lib lbi libClbi
+    ipi    = inplaceInstalledPackageInfo pwd distPref pkg (AbiHash "") lib lbi libClbi
     testDir = buildDir lbi </> stubName test
           </> stubName test ++ "-tmp"
     testLibDep = thisPackageVersion $ package pkg
@@ -432,22 +418,22 @@
                            hsSourceDirs       = [ testDir ],
                            targetBuildDepends = testLibDep
                              : (targetBuildDepends $ testBuildInfo test),
-                           targetBuildRenaming =
-                            Map.insert (packageName pkg) defaultRenaming
-                                (targetBuildRenaming $ testBuildInfo test)
+                           targetBuildRenaming = Map.empty
                          }
           }
     -- | The stub executable needs a new 'ComponentLocalBuildInfo'
     -- that exposes the relevant test suite library.
+    deps = (IPI.installedUnitId ipi, packageId ipi)
+         : (filter (\(_, x) -> let PackageName name = pkgName x
+                               in name == "Cabal" || name == "base")
+                   (componentPackageDeps clbi))
     exeClbi = ExeComponentLocalBuildInfo {
-                componentPackageDeps =
-                    (IPI.installedPackageId ipi, packageId ipi)
-                  : (filter (\(_, x) -> let PackageName name = pkgName x
-                                        in name == "Cabal" || name == "base")
-                            (componentPackageDeps clbi)),
-                componentPackageRenaming =
-                    Map.insert (packageName ipi) defaultRenaming
-                               (componentPackageRenaming clbi)
+                -- TODO: this is a hack, but as long as this is unique
+                -- (doesn't clobber something) we won't run into trouble
+                componentUnitId = mkUnitId (stubName test),
+                componentLocalName = CExeName (stubName test),
+                componentPackageDeps = deps,
+                componentIncludes = zip (map fst deps) (repeat defaultRenaming)
               }
 testSuiteLibV09AsLibAndExe _ TestSuite{} _ _ _ _ = error "testSuiteLibV09AsLibAndExe: wrong kind"
 
@@ -465,8 +451,10 @@
             buildInfo  = benchmarkBuildInfo bm
           }
     exeClbi = ExeComponentLocalBuildInfo {
+                componentUnitId = componentUnitId clbi,
+                componentLocalName = CExeName (benchmarkName bm),
                 componentPackageDeps = componentPackageDeps clbi,
-                componentPackageRenaming = componentPackageRenaming clbi
+                componentIncludes = componentIncludes clbi
               }
 benchmarkExeV10asExe Benchmark{} _ = error "benchmarkExeV10asExe: wrong kind"
 
@@ -475,24 +463,14 @@
 createInternalPackageDB :: Verbosity -> LocalBuildInfo -> FilePath
                         -> IO PackageDB
 createInternalPackageDB verbosity lbi distPref = do
-    case compilerFlavor (compiler lbi) of
-      GHC   -> createWith $ GHC.hcPkgInfo   (withPrograms lbi)
-      GHCJS -> createWith $ GHCJS.hcPkgInfo (withPrograms lbi)
-      LHC   -> createWith $ LHC.hcPkgInfo   (withPrograms lbi)
-      _     -> return packageDB
-    where
-      dbPath = distPref </> "package.conf.inplace"
-      packageDB = SpecificPackageDB dbPath
-      createWith hpi = do
-        dir_exists <- doesDirectoryExist dbPath
-        if dir_exists
-            then removeDirectoryRecursive dbPath
-            else do file_exists <- doesFileExist dbPath
-                    when file_exists $ removeFile dbPath
-        if HcPkg.useSingleFileDb hpi
-            then writeFile dbPath "[]"
-            else HcPkg.init hpi verbosity dbPath
-        return packageDB
+    existsAlready <- doesPackageDBExist dbPath
+    when existsAlready $ deletePackageDB dbPath
+    createPackageDB verbosity (compiler lbi) (withPrograms lbi) False dbPath 
+    return (SpecificPackageDB dbPath)
+  where
+      dbPath = case compilerFlavor (compiler lbi) of
+        UHC -> UHC.inplacePackageDbPath lbi
+        _   -> distPref </> "package.conf.inplace"
 
 addInternalBuildTools :: PackageDescription -> LocalBuildInfo -> BuildInfo
                       -> ProgramDb -> ProgramDb
@@ -559,76 +537,33 @@
 initialBuildSteps :: FilePath -- ^"dist" prefix
                   -> PackageDescription  -- ^mostly information from the .cabal file
                   -> LocalBuildInfo -- ^Configuration information
+                  -> ComponentLocalBuildInfo
                   -> Verbosity -- ^The verbosity to use
                   -> IO ()
-initialBuildSteps _distPref pkg_descr lbi verbosity = do
+initialBuildSteps _distPref pkg_descr lbi clbi verbosity = do
   -- check that there's something to build
   unless (not . null $ allBuildInfo pkg_descr) $ do
     let name = display (packageId pkg_descr)
     die $ "No libraries, executables, tests, or benchmarks "
        ++ "are enabled for package " ++ name ++ "."
 
-  createDirectoryIfMissingVerbose verbosity True (buildDir lbi)
+  createDirectoryIfMissingVerbose verbosity True (componentBuildDir lbi clbi)
 
-  writeAutogenFiles verbosity pkg_descr lbi
+  writeAutogenFiles verbosity pkg_descr lbi clbi
 
 -- | Generate and write out the Paths_<pkg>.hs and cabal_macros.h files
 --
 writeAutogenFiles :: Verbosity
                   -> PackageDescription
                   -> LocalBuildInfo
+                  -> ComponentLocalBuildInfo
                   -> IO ()
-writeAutogenFiles verbosity pkg lbi = do
-  createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi)
+writeAutogenFiles verbosity pkg lbi clbi = do
+  createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi clbi)
 
-  let pathsModulePath = autogenModulesDir lbi
+  let pathsModulePath = autogenModulesDir lbi clbi
                     </> ModuleName.toFilePath (autogenModuleName pkg) <.> "hs"
-  rewriteFile pathsModulePath (Build.PathsModule.generate pkg lbi)
-
-  let cppHeaderPath = autogenModulesDir lbi </> cppHeaderName
-  rewriteFile cppHeaderPath (Build.Macros.generate pkg lbi)
-
--- | Check that the given build targets are valid in the current context.
---
--- Also swizzle into a more convenient form.
---
-checkBuildTargets :: Verbosity -> PackageDescription -> [BuildTarget]
-                  -> IO [(ComponentName, Maybe (Either ModuleName FilePath))]
-checkBuildTargets _ pkg []      =
-    return [ (componentName c, Nothing) | c <- pkgEnabledComponents pkg ]
-
-checkBuildTargets verbosity pkg targets = do
-
-    let (enabled, disabled) =
-          partitionEithers
-            [ case componentDisabledReason (getComponent pkg cname) of
-                Nothing     -> Left  target'
-                Just reason -> Right (cname, reason)
-            | target <- targets
-            , let target'@(cname,_) = swizzleTarget target ]
-
-    case disabled of
-      []                 -> return ()
-      ((cname,reason):_) -> die $ formatReason (showComponentName cname) reason
-
-    forM_ [ (c, t) | (c, Just t) <- enabled ] $ \(c, t) ->
-      warn verbosity $ "Ignoring '" ++ either display id t ++ ". The whole "
-                    ++ showComponentName c ++ " will be built. (Support for "
-                    ++ "module and file targets has not been implemented yet.)"
-
-    return enabled
-
-  where
-    swizzleTarget (BuildTargetComponent c)   = (c, Nothing)
-    swizzleTarget (BuildTargetModule    c m) = (c, Just (Left  m))
-    swizzleTarget (BuildTargetFile      c f) = (c, Just (Right f))
+  rewriteFile pathsModulePath (Build.PathsModule.generate pkg lbi clbi)
 
-    formatReason cn DisabledComponent =
-        "Cannot build the " ++ cn ++ " because the component is marked "
-     ++ "as disabled in the .cabal file."
-    formatReason cn DisabledAllTests =
-        "Cannot build the " ++ cn ++ " because test suites are not "
-     ++ "enabled. Run configure with the flag --enable-tests"
-    formatReason cn DisabledAllBenchmarks =
-        "Cannot build the " ++ cn ++ " because benchmarks are not "
-     ++ "enabled. Re-run configure with the flag --enable-benchmarks"
+  let cppHeaderPath = autogenModulesDir lbi clbi </> cppHeaderName
+  rewriteFile cppHeaderPath (Build.Macros.generate pkg lbi clbi)
diff --git a/cabal/Cabal/Distribution/Simple/Build/Macros.hs b/cabal/Cabal/Distribution/Simple/Build/Macros.hs
--- a/cabal/Cabal/Distribution/Simple/Build/Macros.hs
+++ b/cabal/Cabal/Distribution/Simple/Build/Macros.hs
@@ -22,37 +22,30 @@
     generatePackageVersionMacros,
   ) where
 
-import Data.Maybe
-         ( isJust )
 import Distribution.Package
-         ( PackageIdentifier(PackageIdentifier) )
 import Distribution.Version
-         ( Version(versionBranch) )
 import Distribution.PackageDescription
-         ( PackageDescription )
-import Distribution.Simple.Compiler
-         ( packageKeySupported )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(compiler, withPrograms), externalPackageDeps, localPackageKey )
 import Distribution.Simple.Program.Db
-         ( configuredPrograms )
 import Distribution.Simple.Program.Types
-         ( ConfiguredProgram(programId, programVersion) )
 import Distribution.Text
-         ( display )
 
+import Data.Maybe
+         ( isJust )
+
 -- ------------------------------------------------------------
 -- * Generate cabal_macros.h
 -- ------------------------------------------------------------
 
 -- | The contents of the @cabal_macros.h@ for the given configured package.
 --
-generate :: PackageDescription -> LocalBuildInfo -> String
-generate _pkg_descr lbi =
+generate :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> String
+generate pkg_descr lbi clbi =
   "/* DO NOT EDIT: This file is automatically generated by Cabal */\n\n" ++
-  generatePackageVersionMacros (map snd (externalPackageDeps lbi)) ++
+  generatePackageVersionMacros
+    (package pkg_descr : map snd (componentPackageDeps clbi)) ++
   generateToolVersionMacros (configuredPrograms . withPrograms $ lbi) ++
-  generatePackageKeyMacro lbi
+  generateComponentIdMacro lbi clbi
 
 -- | Helper function that generates just the @VERSION_pkg@ and @MIN_VERSION_pkg@
 -- macros for a list of package ids (usually used with the specific deps of
@@ -84,10 +77,10 @@
 -- 'generateToolVersionMacros'.
 --
 generateMacros :: String -> String -> Version -> String
-generateMacros prefix name version =
+generateMacros macro_prefix name version =
   concat
-  ["#define ", prefix, "VERSION_",name," ",show (display version),"\n"
-  ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
+  ["#define ", macro_prefix, "VERSION_",name," ",show (display version),"\n"
+  ,"#define MIN_", macro_prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
   ,"  (major1) <  ",major1," || \\\n"
   ,"  (major1) == ",major1," && (major2) <  ",major2," || \\\n"
   ,"  (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
@@ -96,14 +89,19 @@
   where
     (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)
 
--- | Generate the @CURRENT_PACKAGE_KEY@ definition for the package key
---   of the current package, if supported by the compiler.
---   NB: this only makes sense for definite packages.
-generatePackageKeyMacro :: LocalBuildInfo -> String
-generatePackageKeyMacro lbi
-  | packageKeySupported (compiler lbi) =
-      "#define CURRENT_PACKAGE_KEY \"" ++ display (localPackageKey lbi) ++ "\"\n\n"
-  | otherwise = ""
+-- | Generate the @CURRENT_COMPONENT_ID@ definition for the component ID
+--   of the current package.
+generateComponentIdMacro :: LocalBuildInfo -> ComponentLocalBuildInfo -> String
+generateComponentIdMacro lbi clbi =
+  concat $
+      (case clbi of
+        LibComponentLocalBuildInfo{} ->
+          ["#define CURRENT_PACKAGE_KEY \"" ++ componentCompatPackageKey clbi ++ "\"\n"]
+        _ -> [])
+      ++
+      ["#define CURRENT_COMPONENT_ID \"" ++ display (componentComponentId clbi) ++ "\"\n"
+      ,"#define LOCAL_COMPONENT_ID \"" ++ display (localComponentId lbi) ++ "\"\n"
+      ,"\n"]
 
 fixchar :: Char -> Char
 fixchar '-' = '_'
diff --git a/cabal/Cabal/Distribution/Simple/Build/PathsModule.hs b/cabal/Cabal/Distribution/Simple/Build/PathsModule.hs
--- a/cabal/Cabal/Distribution/Simple/Build/PathsModule.hs
+++ b/cabal/Cabal/Distribution/Simple/Build/PathsModule.hs
@@ -19,25 +19,14 @@
   ) where
 
 import Distribution.System
-         ( OS(Windows), buildOS, Arch(..), buildArch )
 import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), compilerFlavor, compilerVersion )
 import Distribution.Package
-         ( packageId, packageName, packageVersion )
 import Distribution.PackageDescription
-         ( PackageDescription(..), hasLibs )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), InstallDirs(..)
-         , absoluteInstallDirs, prefixRelativeInstallDirs )
-import Distribution.Simple.Setup ( CopyDest(NoCopyDest) )
 import Distribution.Simple.BuildPaths
-         ( autogenModuleName )
 import Distribution.Simple.Utils
-         ( shortRelativePath )
 import Distribution.Text
-         ( display )
 import Distribution.Version
-         ( Version(..), orLaterVersion, withinRange )
 
 import System.FilePath
          ( pathSeparator )
@@ -48,11 +37,11 @@
 -- * Building Paths_<pkg>.hs
 -- ------------------------------------------------------------
 
-generate :: PackageDescription -> LocalBuildInfo -> String
-generate pkg_descr lbi =
+generate :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> String
+generate pkg_descr lbi clbi =
    let pragmas = cpp_pragma ++ ffi_pragmas ++ warning_pragmas
 
-       cpp_pragma | supports_cpp = "{-# LANGUAGE CPP #-}"
+       cpp_pragma | supports_cpp = "{-# LANGUAGE CPP #-}\n"
                   | otherwise    = ""
 
        ffi_pragmas
@@ -64,7 +53,8 @@
           "{-# OPTIONS_JHC -fffi #-}\n"
 
        warning_pragmas =
-        "{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}\n"
+        "{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}\n"++
+        "{-# OPTIONS_GHC -fno-warn-implicit-prelude #-}\n"
 
        foreign_imports
         | absolute = ""
@@ -179,6 +169,8 @@
    in header++body
 
  where
+        cid = componentUnitId clbi
+
         InstallDirs {
           prefix     = flat_prefix,
           bindir     = flat_bindir,
@@ -186,14 +178,14 @@
           datadir    = flat_datadir,
           libexecdir = flat_libexecdir,
           sysconfdir = flat_sysconfdir
-        } = absoluteInstallDirs pkg_descr lbi NoCopyDest
+        } = absoluteComponentInstallDirs pkg_descr lbi cid NoCopyDest
         InstallDirs {
           bindir     = flat_bindirrel,
           libdir     = flat_libdirrel,
           datadir    = flat_datadirrel,
           libexecdir = flat_libexecdirrel,
           sysconfdir = flat_sysconfdirrel
-        } = prefixRelativeInstallDirs (packageId pkg_descr) lbi
+        } = prefixRelativeComponentInstallDirs (packageId pkg_descr) lbi cid
 
         flat_bindirreloc = shortRelativePath flat_prefix flat_bindir
         flat_libdirreloc = shortRelativePath flat_prefix flat_libdir
diff --git a/cabal/Cabal/Distribution/Simple/BuildPaths.hs b/cabal/Cabal/Distribution/Simple/BuildPaths.hs
--- a/cabal/Cabal/Distribution/Simple/BuildPaths.hs
+++ b/cabal/Cabal/Distribution/Simple/BuildPaths.hs
@@ -31,22 +31,17 @@
   ) where
 
 
-import System.FilePath ((</>), (<.>))
-
 import Distribution.Package
-         ( packageName, LibraryName, getHSLibraryName )
-import Distribution.ModuleName (ModuleName)
-import qualified Distribution.ModuleName as ModuleName
+import Distribution.ModuleName as ModuleName
 import Distribution.Compiler
-         ( CompilerId(..) )
-import Distribution.PackageDescription (PackageDescription)
+import Distribution.PackageDescription
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(buildDir) )
-import Distribution.Simple.Setup (defaultDistPref)
+import Distribution.Simple.Setup
 import Distribution.Text
-         ( display )
-import Distribution.System (OS(..), buildOS)
+import Distribution.System
 
+import System.FilePath ((</>), (<.>))
+
 -- ---------------------------------------------------------------------------
 -- Build directories and files
 
@@ -61,8 +56,10 @@
     = distPref </> "doc" </> "html" </> display (packageName pkg_descr)
 
 -- |The directory in which we put auto-generated modules
-autogenModulesDir :: LocalBuildInfo -> String
-autogenModulesDir lbi = buildDir lbi </> "autogen"
+autogenModulesDir :: LocalBuildInfo -> ComponentLocalBuildInfo -> String
+autogenModulesDir lbi clbi = componentBuildDir lbi clbi </> "autogen"
+-- NB: Look at 'checkForeignDeps' for where a simplified version of this
+-- has been copy-pasted.
 
 cppHeaderName :: String
 cppHeaderName = "cabal_macros.h"
@@ -81,16 +78,16 @@
 -- ---------------------------------------------------------------------------
 -- Library file names
 
-mkLibName :: LibraryName -> String
+mkLibName :: UnitId -> String
 mkLibName lib = "lib" ++ getHSLibraryName lib <.> "a"
 
-mkProfLibName :: LibraryName -> String
+mkProfLibName :: UnitId -> String
 mkProfLibName lib =  "lib" ++ getHSLibraryName lib ++ "_p" <.> "a"
 
 -- Implement proper name mangling for dynamical shared objects
 -- libHS<packagename>-<compilerFlavour><compilerVersion>
 -- e.g. libHSbase-2.1-ghc6.6.1.so
-mkSharedLibName :: CompilerId -> LibraryName -> String
+mkSharedLibName :: CompilerId -> UnitId -> String
 mkSharedLibName (CompilerId compilerFlavor compilerVersion) lib
   = "lib" ++ getHSLibraryName lib ++ "-" ++ comp <.> dllExtension
   where comp = display compilerFlavor ++ display compilerVersion
@@ -99,15 +96,13 @@
 -- * Platform file extensions
 -- ------------------------------------------------------------
 
--- ToDo: This should be determined via autoconf (AC_EXEEXT)
--- | Extension for executable files
+-- | Default extension for executable files on the current platform.
 -- (typically @\"\"@ on Unix and @\"exe\"@ on Windows or OS\/2)
 exeExtension :: String
 exeExtension = case buildOS of
                    Windows -> "exe"
                    _       -> ""
 
--- TODO: This should be determined via autoconf (AC_OBJEXT)
 -- | Extension for object files. For GHC the extension is @\"o\"@.
 objExtension :: String
 objExtension = "o"
diff --git a/cabal/Cabal/Distribution/Simple/BuildTarget.hs b/cabal/Cabal/Distribution/Simple/BuildTarget.hs
--- a/cabal/Cabal/Distribution/Simple/BuildTarget.hs
+++ b/cabal/Cabal/Distribution/Simple/BuildTarget.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.BuildTargets
@@ -14,10 +14,14 @@
     -- * Build targets
     BuildTarget(..),
     readBuildTargets,
+    showBuildTarget,
+    QualLevel(..),
+    buildTargetComponentName,
 
     -- * Parsing user build targets
     UserBuildTarget,
     readUserBuildTargets,
+    showUserBuildTarget,
     UserBuildTargetProblem(..),
     reportUserBuildTargetProblems,
 
@@ -25,44 +29,33 @@
     resolveBuildTargets,
     BuildTargetProblem(..),
     reportBuildTargetProblems,
-  ) where
 
-import Distribution.Package
-         ( Package(..), PackageId, packageName )
+    -- * Checking build targets
+    checkBuildTargets
+  ) where
 
 import Distribution.PackageDescription
-         ( PackageDescription
-         , Executable(..)
-         , TestSuite(..), TestSuiteInterface(..), testModules
-         , Benchmark(..), BenchmarkInterface(..), benchmarkModules
-         , BuildInfo(..), libModules, exeModules )
 import Distribution.ModuleName
-         ( ModuleName, toFilePath )
 import Distribution.Simple.LocalBuildInfo
-         ( Component(..), ComponentName(..)
-         , pkgComponents, componentName, componentBuildInfo )
-
 import Distribution.Text
-         ( display )
 import Distribution.Simple.Utils
-         ( die, lowercase, equating )
+import Distribution.Verbosity
 
+import Distribution.Compat.Binary (Binary)
+import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.ReadP
+         ( (+++), (<++) )
+
 import Data.List
-         ( nub, stripPrefix, sortBy, groupBy, partition, intercalate )
-import Data.Ord
+         ( nub, stripPrefix, sortBy, groupBy, partition )
 import Data.Maybe
          ( listToMaybe, catMaybes )
 import Data.Either
          ( partitionEithers )
+import GHC.Generics (Generic)
 import qualified Data.Map as Map
 import Control.Monad
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative (Applicative(..))
-#endif
-import Control.Applicative (Alternative(..))
-import qualified Distribution.Compat.ReadP as Parse
-import Distribution.Compat.ReadP
-         ( (+++), (<++) )
+import Control.Applicative as AP (Alternative(..), Applicative(..))
 import Data.Char
          ( isSpace, isAlphaNum )
 import System.FilePath as FilePath
@@ -127,13 +120,19 @@
      -- | A specific file within a specific component.
      --
    | BuildTargetFile ComponentName FilePath
-  deriving (Show,Eq)
+  deriving (Eq, Show, Generic)
 
+instance Binary BuildTarget
 
--- ------------------------------------------------------------
--- * Do everything
--- ------------------------------------------------------------
+buildTargetComponentName :: BuildTarget -> ComponentName
+buildTargetComponentName (BuildTargetComponent cn)   = cn
+buildTargetComponentName (BuildTargetModule    cn _) = cn
+buildTargetComponentName (BuildTargetFile      cn _) = cn
 
+-- | Read a list of user-supplied build target strings and resolve them to
+-- 'BuildTarget's according to a 'PackageDescription'. If there are problems
+-- with any of the targets e.g. they don't exist or are misformatted, throw an
+-- 'IOException'.
 readBuildTargets :: PackageDescription -> [String] -> IO [BuildTarget]
 readBuildTargets pkg targetStrs = do
     let (uproblems, utargets) = readUserBuildTargets targetStrs
@@ -226,13 +225,17 @@
            ++ " - build foo:Data/Foo.hsc  -- file qualified by component"
 
 showUserBuildTarget :: UserBuildTarget -> String
-showUserBuildTarget = intercalate ":" . components
+showUserBuildTarget = intercalate ":" . getComponents
   where
-    components (UserBuildTargetSingle s1)       = [s1]
-    components (UserBuildTargetDouble s1 s2)    = [s1,s2]
-    components (UserBuildTargetTriple s1 s2 s3) = [s1,s2,s3]
+    getComponents (UserBuildTargetSingle s1)       = [s1]
+    getComponents (UserBuildTargetDouble s1 s2)    = [s1,s2]
+    getComponents (UserBuildTargetTriple s1 s2 s3) = [s1,s2,s3]
 
+showBuildTarget :: QualLevel -> BuildTarget -> String
+showBuildTarget ql bt =
+    showUserBuildTarget (renderBuildTarget ql bt)
 
+
 -- ------------------------------------------------------------
 -- * Resolving user targets to build targets
 -- ------------------------------------------------------------
@@ -267,7 +270,7 @@
       Unambiguous target  -> Right target
       Ambiguous   targets -> Left (BuildTargetAmbiguous userTarget targets')
                                where targets' = disambiguateBuildTargets
-                                                    (packageId pkg) userTarget
+                                                    userTarget
                                                     targets
       None        errs    -> Left (classifyMatchErrors errs)
 
@@ -291,9 +294,9 @@
   deriving Show
 
 
-disambiguateBuildTargets :: PackageId -> UserBuildTarget -> [BuildTarget]
+disambiguateBuildTargets :: UserBuildTarget -> [BuildTarget]
                          -> [(UserBuildTarget, BuildTarget)]
-disambiguateBuildTargets pkgid original =
+disambiguateBuildTargets original =
     disambiguate (userTargetQualLevel original)
   where
     disambiguate ql ts
@@ -312,13 +315,13 @@
             . partition (\g -> length g > 1)
             . groupBy (equating fst)
             . sortBy (comparing fst)
-            . map (\t -> (renderBuildTarget ql t pkgid, t))
+            . map (\t -> (renderBuildTarget ql t, t))
 
 data QualLevel = QL1 | QL2 | QL3
   deriving (Enum, Show)
 
-renderBuildTarget :: QualLevel -> BuildTarget -> PackageId -> UserBuildTarget
-renderBuildTarget ql target pkgid =
+renderBuildTarget :: QualLevel -> BuildTarget -> UserBuildTarget
+renderBuildTarget ql target =
     case ql of
       QL1 -> UserBuildTargetSingle s1        where  s1          = single target
       QL2 -> UserBuildTargetDouble s1 s2     where (s1, s2)     = double target
@@ -337,7 +340,7 @@
     triple (BuildTargetModule    cn m) = (dispKind cn, dispCName cn, display m)
     triple (BuildTargetFile      cn f) = (dispKind cn, dispCName cn, f)
 
-    dispCName = componentStringName pkgid
+    dispCName = componentStringName
     dispKind  = showComponentKindShort . componentKind
 
 reportBuildTargetProblems :: [BuildTargetProblem] -> IO ()
@@ -440,7 +443,7 @@
 pkgComponentInfo pkg =
     [ ComponentInfo {
         cinfoName    = componentName c,
-        cinfoStrName = componentStringName pkg (componentName c),
+        cinfoStrName = componentStringName (componentName c),
         cinfoSrcDirs = hsSourceDirs bi,
         cinfoModules = componentModules c,
         cinfoHsFiles = componentHsFiles c,
@@ -450,11 +453,11 @@
     | c <- pkgComponents pkg
     , let bi = componentBuildInfo c ]
 
-componentStringName :: Package pkg => pkg -> ComponentName -> ComponentStringName
-componentStringName pkg CLibName          = display (packageName pkg)
-componentStringName _   (CExeName  name)  = name
-componentStringName _   (CTestName  name) = name
-componentStringName _   (CBenchName name) = name
+componentStringName :: ComponentName -> ComponentStringName
+componentStringName (CLibName   name) = name
+componentStringName (CExeName   name) = name
+componentStringName (CTestName  name) = name
+componentStringName (CBenchName name) = name
 
 componentModules :: Component -> [ModuleName]
 componentModules (CLib   lib)   = libModules lib
@@ -494,8 +497,8 @@
   deriving (Eq, Ord, Show)
 
 componentKind :: ComponentName -> ComponentKind
-componentKind CLibName       = LibKind
-componentKind (CExeName  _)  = ExeKind
+componentKind (CLibName   _) = LibKind
+componentKind (CExeName   _) = ExeKind
 componentKind (CTestName  _) = TestKind
 componentKind (CBenchName _) = BenchKind
 
@@ -798,11 +801,12 @@
   fmap f (InexactMatch d xs) = InexactMatch d (fmap f xs)
 
 instance Applicative Match where
-  pure = return
+  pure a = ExactMatch 0 [a]
   (<*>) = ap
 
 instance Monad Match where
-  return a                = ExactMatch 0 [a]
+  return = AP.pure
+
   NoMatch      d ms >>= _ = NoMatch d ms
   ExactMatch   d xs >>= f = addDepth d
                           $ foldr matchPlus matchZero (map f xs)
@@ -937,3 +941,49 @@
 
 caseFold :: String -> String
 caseFold = lowercase
+
+
+-- | Check that the given build targets are valid in the current context.
+--
+-- Also swizzle into a more convenient form.
+--
+checkBuildTargets :: Verbosity -> PackageDescription -> [BuildTarget]
+                  -> IO [(ComponentName, Maybe (Either ModuleName FilePath))]
+checkBuildTargets _ pkg []      =
+    return [ (componentName c, Nothing) | c <- pkgEnabledComponents pkg ]
+
+checkBuildTargets verbosity pkg targets = do
+
+    let (enabled, disabled) =
+          partitionEithers
+            [ case componentDisabledReason (getComponent pkg cname) of
+                Nothing     -> Left  target'
+                Just reason -> Right (cname, reason)
+            | target <- targets
+            , let target'@(cname,_) = swizzleTarget target ]
+
+    case disabled of
+      []                 -> return ()
+      ((cname,reason):_) -> die $ formatReason (showComponentName cname) reason
+
+    forM_ [ (c, t) | (c, Just t) <- enabled ] $ \(c, t) ->
+      warn verbosity $ "Ignoring '" ++ either display id t ++ ". The whole "
+                    ++ showComponentName c ++ " will be processed. (Support for "
+                    ++ "module and file targets has not been implemented yet.)"
+
+    return enabled
+
+  where
+    swizzleTarget (BuildTargetComponent c)   = (c, Nothing)
+    swizzleTarget (BuildTargetModule    c m) = (c, Just (Left  m))
+    swizzleTarget (BuildTargetFile      c f) = (c, Just (Right f))
+
+    formatReason cn DisabledComponent =
+        "Cannot process the " ++ cn ++ " because the component is marked "
+     ++ "as disabled in the .cabal file."
+    formatReason cn DisabledAllTests =
+        "Cannot process the " ++ cn ++ " because test suites are not "
+     ++ "enabled. Run configure with the flag --enable-tests"
+    formatReason cn DisabledAllBenchmarks =
+        "Cannot process the " ++ cn ++ " because benchmarks are not "
+     ++ "enabled. Re-run configure with the flag --enable-benchmarks"
diff --git a/cabal/Cabal/Distribution/Simple/CCompiler.hs b/cabal/Cabal/Distribution/Simple/CCompiler.hs
--- a/cabal/Cabal/Distribution/Simple/CCompiler.hs
+++ b/cabal/Cabal/Distribution/Simple/CCompiler.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.CCompiler
@@ -47,10 +46,8 @@
    filenameCDialect
   ) where
 
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-     ( Monoid(..) )
-#endif
+import Distribution.Compat.Semigroup as Semi
+
 import System.FilePath
      ( takeExtension )
 
@@ -66,15 +63,16 @@
 
 instance Monoid CDialect where
   mempty = C
-
-  mappend C                  anything           = anything
-  mappend ObjectiveC         CPlusPlus          = ObjectiveCPlusPlus
-  mappend CPlusPlus          ObjectiveC         = ObjectiveCPlusPlus
-  mappend _                  ObjectiveCPlusPlus = ObjectiveCPlusPlus
-  mappend ObjectiveC         _                  = ObjectiveC
-  mappend CPlusPlus          _                  = CPlusPlus
-  mappend ObjectiveCPlusPlus _                  = ObjectiveCPlusPlus
+  mappend = (Semi.<>)
 
+instance Semigroup CDialect where
+  C                  <> anything           = anything
+  ObjectiveC         <> CPlusPlus          = ObjectiveCPlusPlus
+  CPlusPlus          <> ObjectiveC         = ObjectiveCPlusPlus
+  _                  <> ObjectiveCPlusPlus = ObjectiveCPlusPlus
+  ObjectiveC         <> _                  = ObjectiveC
+  CPlusPlus          <> _                  = CPlusPlus
+  ObjectiveCPlusPlus <> _                  = ObjectiveCPlusPlus
 
 -- | A list of all file extensions which are recognized as possibly containing
 --   some dialect of C code.  Note that this list is only for source files,
diff --git a/cabal/Cabal/Distribution/Simple/Command.hs b/cabal/Cabal/Distribution/Simple/Command.hs
--- a/cabal/Cabal/Distribution/Simple/Command.hs
+++ b/cabal/Cabal/Distribution/Simple/Command.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE ExistentialQuantification #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.Command
@@ -6,7 +6,7 @@
 -- License     :  BSD3
 --
 -- Maintainer  :  cabal-devel@haskell.org
--- Portability :  portable
+-- Portability :  non-portable (ExistentialQuantification)
 --
 -- This is to do with command line handling. The Cabal command line is
 -- organised into a number of named sub-commands (much like darcs). The
@@ -38,6 +38,11 @@
   commandAddAction,
   noExtraFlags,
 
+  -- ** Building lists of commands
+  CommandType(..),
+  CommandSpec(..),
+  commandFromSpec,
+
   -- ** Running commands
   commandsRun,
 
@@ -60,19 +65,17 @@
 
   ) where
 
-import Control.Monad
-import Data.Char (isAlpha, toLower)
-import Data.List (sortBy)
-import Data.Maybe
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-#endif
 import qualified Distribution.GetOpt as GetOpt
 import Distribution.Text
-         ( Text(disp, parse) )
 import Distribution.ParseUtils
 import Distribution.ReadE
-import Distribution.Simple.Utils (die, intercalate)
+import Distribution.Simple.Utils
+
+import Control.Monad
+import Data.Char (isAlpha, toLower)
+import Data.List (sortBy)
+import Data.Maybe
+import Data.Monoid as Mon
 import Text.PrettyPrint ( punctuate, cat, comma, text )
 import Text.PrettyPrint as PP ( empty )
 
@@ -171,7 +174,7 @@
     reqArg ad (succeedReadE mkflag) showflag
 
 -- | (String -> a) variant of "optArg"
-optArg' :: Monoid b => ArgPlaceHolder -> (Maybe String -> b)
+optArg' :: Mon.Monoid b => ArgPlaceHolder -> (Maybe String -> b)
            -> (b -> [Maybe String])
            -> MkOptDescr (a -> b) (b -> a -> a) a
 optArg' ad mkflag showflag =
@@ -605,3 +608,13 @@
     ++ "  " ++ pname ++ " help help\n"
     ++ "    Oh, appararently you already know this.\n"
   }
+
+-- | wraps a @CommandUI@ together with a function that turns it into a @Command@.
+-- By hiding the type of flags for the UI allows construction of a list of all UIs at the
+-- top level of the program. That list can then be used for generation of manual page
+-- as well as for executing the selected command.
+data CommandSpec action
+  = forall flags. CommandSpec (CommandUI flags) (CommandUI flags -> Command action) CommandType
+
+commandFromSpec :: CommandSpec a -> Command a
+commandFromSpec (CommandSpec ui action _) = action ui
diff --git a/cabal/Cabal/Distribution/Simple/Compiler.hs b/cabal/Cabal/Distribution/Simple/Compiler.hs
--- a/cabal/Cabal/Distribution/Simple/Compiler.hs
+++ b/cabal/Cabal/Distribution/Simple/Compiler.hs
@@ -53,22 +53,25 @@
         parmakeSupported,
         reexportedModulesSupported,
         renamingPackageFlagsSupported,
+        unifiedIPIDRequired,
         packageKeySupported,
+        unitIdSupported,
 
         -- * Support for profiling detail levels
         ProfDetailLevel(..),
         knownProfDetailLevels,
         flagToProfDetailLevel,
+        showProfDetailLevel,
   ) where
 
 import Distribution.Compiler
-import Distribution.Version (Version(..))
-import Distribution.Text (display)
-import Language.Haskell.Extension (Language(Haskell98), Extension)
-import Distribution.Simple.Utils (lowercase)
+import Distribution.Version
+import Distribution.Text
+import Language.Haskell.Extension
+import Distribution.Simple.Utils
+import Distribution.Compat.Binary
 
 import Control.Monad (liftM)
-import Distribution.Compat.Binary (Binary)
 import Data.List (nub)
 import qualified Data.Map as M (Map, lookup)
 import Data.Maybe (catMaybes, isNothing, listToMaybe)
@@ -89,7 +92,7 @@
         compilerProperties      :: M.Map String String
         -- ^ A key-value map for properties not covered by the above fields.
     }
-    deriving (Generic, Show, Read)
+    deriving (Eq, Generic, Show, Read)
 
 instance Binary Compiler
 
@@ -276,10 +279,18 @@
 renamingPackageFlagsSupported :: Compiler -> Bool
 renamingPackageFlagsSupported = ghcSupported "Support thinning and renaming package flags"
 
+-- | Does this compiler have unified IPIDs (so no package keys)
+unifiedIPIDRequired :: Compiler -> Bool
+unifiedIPIDRequired = ghcSupported "Requires unified installed package IDs"
+
 -- | Does this compiler support package keys?
 packageKeySupported :: Compiler -> Bool
 packageKeySupported = ghcSupported "Uses package keys"
 
+-- | Does this compiler support unit IDs?
+unitIdSupported :: Compiler -> Bool
+unitIdSupported = ghcSupported "Uses unit IDs"
+
 -- | Utility function for GHC only features
 ghcSupported :: String -> Compiler -> Bool
 ghcSupported key comp =
@@ -331,4 +342,13 @@
   , ("toplevel-functions", ["toplevel", "top"], ProfDetailToplevelFunctions)
   , ("all-functions",      ["all"],             ProfDetailAllFunctions)
   ]
+
+showProfDetailLevel :: ProfDetailLevel -> String
+showProfDetailLevel dl = case dl of
+    ProfDetailNone              -> "none"
+    ProfDetailDefault           -> "default"
+    ProfDetailExportedFunctions -> "exported-functions"
+    ProfDetailToplevelFunctions -> "toplevel-functions"
+    ProfDetailAllFunctions      -> "all-functions"
+    ProfDetailOther other       -> other
 
diff --git a/cabal/Cabal/Distribution/Simple/Configure.hs b/cabal/Cabal/Distribution/Simple/Configure.hs
--- a/cabal/Cabal/Distribution/Simple/Configure.hs
+++ b/cabal/Cabal/Distribution/Simple/Configure.hs
@@ -1,1755 +1,2134 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-#if __GLASGOW_HASKELL__ >= 711
-{-# LANGUAGE PatternSynonyms #-}
-#endif
-
------------------------------------------------------------------------------
--- |
--- Module      :  Distribution.Simple.Configure
--- Copyright   :  Isaac Jones 2003-2005
--- License     :  BSD3
---
--- Maintainer  :  cabal-devel@haskell.org
--- Portability :  portable
---
--- This deals with the /configure/ phase. It provides the 'configure' action
--- which is given the package description and configure flags. It then tries
--- to: configure the compiler; resolves any conditionals in the package
--- description; resolve the package dependencies; check if all the extensions
--- used by this package are supported by the compiler; check that all the build
--- tools are available (including version checks if appropriate); checks for
--- any required @pkg-config@ packages (updating the 'BuildInfo' with the
--- results)
---
--- Then based on all this it saves the info in the 'LocalBuildInfo' and writes
--- it out to the @dist\/setup-config@ file. It also displays various details to
--- the user, the amount of information displayed depending on the verbosity
--- level.
-
-module Distribution.Simple.Configure (configure,
-                                      writePersistBuildConfig,
-                                      getConfigStateFile,
-                                      getPersistBuildConfig,
-                                      checkPersistBuildConfigOutdated,
-                                      tryGetPersistBuildConfig,
-                                      maybeGetPersistBuildConfig,
-                                      findDistPref, findDistPrefOrDefault,
-                                      localBuildInfoFile,
-                                      getInstalledPackages, getPackageDBContents,
-                                      configCompiler, configCompilerAux,
-                                      configCompilerEx, configCompilerAuxEx,
-                                      ccLdOptionsBuildInfo,
-                                      checkForeignDeps,
-                                      interpretPackageDbFlags,
-                                      ConfigStateFileError(..),
-                                      tryGetConfigStateFile,
-                                      platformDefines,
-                                     )
-    where
-
-import Distribution.Compiler
-    ( CompilerId(..) )
-import Distribution.Utils.NubList
-import Distribution.Simple.Compiler
-    ( CompilerFlavor(..), Compiler(..), compilerFlavor, compilerVersion
-    , compilerInfo, ProfDetailLevel(..), knownProfDetailLevels
-    , showCompilerId, unsupportedLanguages, unsupportedExtensions
-    , PackageDB(..), PackageDBStack, reexportedModulesSupported
-    , packageKeySupported, renamingPackageFlagsSupported )
-import Distribution.Simple.PreProcess ( platformDefines )
-import Distribution.Package
-    ( PackageName(PackageName), PackageIdentifier(..), PackageId
-    , packageName, packageVersion, Package(..)
-    , Dependency(Dependency), simplifyDependency
-    , InstalledPackageId(..), thisPackageVersion
-    , mkPackageKey, packageKeyLibraryName )
-import qualified Distribution.InstalledPackageInfo as Installed
-import Distribution.InstalledPackageInfo (InstalledPackageInfo, emptyInstalledPackageInfo)
-import qualified Distribution.Simple.PackageIndex as PackageIndex
-import Distribution.Simple.PackageIndex (InstalledPackageIndex)
-import Distribution.PackageDescription as PD
-    ( PackageDescription(..), specVersion, GenericPackageDescription(..)
-    , Library(..), hasLibs, Executable(..), BuildInfo(..), allExtensions
-    , HookedBuildInfo, updatePackageDescription, allBuildInfo
-    , Flag(flagName), FlagName(..), TestSuite(..), Benchmark(..)
-    , ModuleReexport(..) , defaultRenaming )
-import Distribution.ModuleName
-    ( ModuleName )
-import Distribution.PackageDescription.Configuration
-    ( finalizePackageDescription, mapTreeData )
-import Distribution.PackageDescription.Check
-    ( PackageCheck(..), checkPackage, checkPackageFiles )
-import Distribution.Simple.Program
-    ( Program(..), ProgramLocation(..), ConfiguredProgram(..)
-    , ProgramConfiguration, defaultProgramConfiguration
-    , ProgramSearchPathEntry(..), getProgramSearchPath, setProgramSearchPath
-    , configureAllKnownPrograms, knownPrograms, lookupKnownProgram
-    , userSpecifyArgss, userSpecifyPaths
-    , lookupProgram, requireProgram, requireProgramVersion
-    , pkgConfigProgram, gccProgram, rawSystemProgramStdoutConf )
-import Distribution.Simple.Setup as Setup
-    ( ConfigFlags(..), CopyDest(..), Flag(..), defaultDistPref
-    , fromFlag, fromFlagOrDefault, flagToMaybe, toFlag )
-import Distribution.Simple.InstallDirs
-    ( InstallDirs(..), defaultInstallDirs, combineInstallDirs )
-import Distribution.Simple.LocalBuildInfo
-    ( LocalBuildInfo(..), Component(..), ComponentLocalBuildInfo(..)
-    , absoluteInstallDirs, prefixRelativeInstallDirs, inplacePackageId
-    , ComponentName(..), showComponentName, pkgEnabledComponents
-    , componentBuildInfo, componentName, checkComponentsCyclic )
-import Distribution.Simple.BuildPaths
-    ( autogenModulesDir )
-import Distribution.Simple.Utils
-    ( die, warn, info, setupMessage
-    , createDirectoryIfMissingVerbose, moreRecentFile
-    , intercalate, cabalVersion
-    , writeFileAtomic
-    , withTempFile )
-import Distribution.System
-    ( OS(..), buildOS, Platform (..), buildPlatform )
-import Distribution.Version
-         ( Version(..), anyVersion, orLaterVersion, withinRange, isAnyVersion )
-import Distribution.Verbosity
-    ( Verbosity, lessVerbose )
-
-import qualified Distribution.Simple.GHC   as GHC
-import qualified Distribution.Simple.GHCJS as GHCJS
-import qualified Distribution.Simple.JHC   as JHC
-import qualified Distribution.Simple.LHC   as LHC
-import qualified Distribution.Simple.UHC   as UHC
-import qualified Distribution.Simple.HaskellSuite as HaskellSuite
-
--- Prefer the more generic Data.Traversable.mapM to Prelude.mapM
-import Prelude hiding ( mapM )
-import Control.Exception
-    ( Exception, evaluate, throw, throwIO, try )
-#if __GLASGOW_HASKELL__ >= 711
-import Control.Exception ( pattern ErrorCall )
-#else
-import Control.Exception ( ErrorCall(..) )
-#endif
-import Control.Monad
-    ( liftM, when, unless, foldM, filterM )
-import Distribution.Compat.Binary ( decodeOrFailIO, encode )
-import Data.ByteString.Lazy (ByteString)
-import qualified Data.ByteString            as BS
-import qualified Data.ByteString.Lazy.Char8 as BLC8
-import Data.List
-    ( (\\), nub, partition, isPrefixOf, inits, stripPrefix )
-import Data.Maybe
-    ( isNothing, catMaybes, fromMaybe, isJust )
-import Data.Either
-    ( partitionEithers )
-import qualified Data.Set as Set
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-    ( Monoid(..) )
-#endif
-import qualified Data.Map as Map
-import Data.Map (Map)
-import Data.Traversable
-    ( mapM )
-import Data.Typeable
-import System.Directory
-    ( doesFileExist, createDirectoryIfMissing, getTemporaryDirectory )
-import System.FilePath
-    ( (</>), isAbsolute )
-import qualified System.Info
-    ( compilerName, compilerVersion )
-import System.IO
-    ( hPutStrLn, hClose )
-import Distribution.Text
-    ( Text(disp), display, simpleParse )
-import Text.PrettyPrint
-    ( render, (<>), ($+$), char, text, comma
-    , quotes, punctuate, nest, sep, hsep )
-import Distribution.Compat.Environment ( lookupEnv )
-import Distribution.Compat.Exception ( catchExit, catchIO )
-
--- | The errors that can be thrown when reading the @setup-config@ file.
-data ConfigStateFileError
-    = ConfigStateFileNoHeader -- ^ No header found.
-    | ConfigStateFileBadHeader -- ^ Incorrect header.
-    | ConfigStateFileNoParse -- ^ Cannot parse file contents.
-    | ConfigStateFileMissing -- ^ No file!
-    | ConfigStateFileBadVersion PackageIdentifier PackageIdentifier (Either ConfigStateFileError LocalBuildInfo) -- ^ Mismatched version.
-  deriving (Typeable)
-
-instance Show ConfigStateFileError where
-    show ConfigStateFileNoHeader =
-        "Saved package config file header is missing. "
-        ++ "Try re-running the 'configure' command."
-    show ConfigStateFileBadHeader =
-        "Saved package config file header is corrupt. "
-        ++ "Try re-running the 'configure' command."
-    show ConfigStateFileNoParse =
-        "Saved package config file body is corrupt. "
-        ++ "Try re-running the 'configure' command."
-    show ConfigStateFileMissing = "Run the 'configure' command first."
-    show (ConfigStateFileBadVersion oldCabal oldCompiler _) =
-        "You need to re-run the 'configure' command. "
-        ++ "The version of Cabal being used has changed (was "
-        ++ display oldCabal ++ ", now "
-        ++ display currentCabalId ++ ")."
-        ++ badCompiler
-      where
-        badCompiler
-          | oldCompiler == currentCompilerId = ""
-          | otherwise =
-              " Additionally the compiler is different (was "
-              ++ display oldCompiler ++ ", now "
-              ++ display currentCompilerId
-              ++ ") which is probably the cause of the problem."
-
-instance Exception ConfigStateFileError
-
--- | Read the 'localBuildInfoFile'.  Throw an exception if the file is
--- missing, if the file cannot be read, or if the file was created by an older
--- version of Cabal.
-getConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.
-                   -> IO LocalBuildInfo
-getConfigStateFile filename = do
-    exists <- doesFileExist filename
-    unless exists $ throwIO ConfigStateFileMissing
-    -- Read the config file into a strict ByteString to avoid problems with
-    -- lazy I/O, then convert to lazy because the binary package needs that.
-    contents <- BS.readFile filename
-    let (header, body) = BLC8.span (/='\n') (BLC8.fromChunks [contents])
-
-    headerParseResult <- try $ evaluate $ parseHeader header
-    let (cabalId, compId) =
-            case headerParseResult of
-              Left (ErrorCall _) -> throw ConfigStateFileBadHeader
-              Right x -> x
-
-    let getStoredValue = do
-          result <- decodeOrFailIO (BLC8.tail body)
-          case result of
-            Left _ -> throw ConfigStateFileNoParse
-            Right x -> return x
-        deferErrorIfBadVersion act
-          | cabalId /= currentCabalId = do
-              eResult <- try act
-              throw $ ConfigStateFileBadVersion cabalId compId eResult
-          | otherwise = act
-    deferErrorIfBadVersion getStoredValue
-
--- | Read the 'localBuildInfoFile', returning either an error or the local build info.
-tryGetConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.
-                      -> IO (Either ConfigStateFileError LocalBuildInfo)
-tryGetConfigStateFile = try . getConfigStateFile
-
--- | Try to read the 'localBuildInfoFile'.
-tryGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
-                         -> IO (Either ConfigStateFileError LocalBuildInfo)
-tryGetPersistBuildConfig = try . getPersistBuildConfig
-
--- | Read the 'localBuildInfoFile'. Throw an exception if the file is
--- missing, if the file cannot be read, or if the file was created by an older
--- version of Cabal.
-getPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
-                      -> IO LocalBuildInfo
-getPersistBuildConfig = getConfigStateFile . localBuildInfoFile
-
--- | Try to read the 'localBuildInfoFile'.
-maybeGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
-                           -> IO (Maybe LocalBuildInfo)
-maybeGetPersistBuildConfig =
-    liftM (either (const Nothing) Just) . tryGetPersistBuildConfig
-
--- | After running configure, output the 'LocalBuildInfo' to the
--- 'localBuildInfoFile'.
-writePersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
-                        -> LocalBuildInfo -- ^ The 'LocalBuildInfo' to write.
-                        -> IO ()
-writePersistBuildConfig distPref lbi = do
-    createDirectoryIfMissing False distPref
-    writeFileAtomic (localBuildInfoFile distPref) $
-      BLC8.unlines [showHeader pkgId, encode lbi]
-  where
-    pkgId = packageId $ localPkgDescr lbi
-
--- | Identifier of the current Cabal package.
-currentCabalId :: PackageIdentifier
-currentCabalId = PackageIdentifier (PackageName "Cabal") cabalVersion
-
--- | Identifier of the current compiler package.
-currentCompilerId :: PackageIdentifier
-currentCompilerId = PackageIdentifier (PackageName System.Info.compilerName)
-                                      System.Info.compilerVersion
-
--- | Parse the @setup-config@ file header, returning the package identifiers 
--- for Cabal and the compiler.
-parseHeader :: ByteString -- ^ The file contents.
-            -> (PackageIdentifier, PackageIdentifier)
-parseHeader header = case BLC8.words header of
-  ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId, "using", compId] ->
-      fromMaybe (throw ConfigStateFileBadHeader) $ do
-          _ <- simpleParse (BLC8.unpack pkgId) :: Maybe PackageIdentifier
-          cabalId' <- simpleParse (BLC8.unpack cabalId)
-          compId' <- simpleParse (BLC8.unpack compId)
-          return (cabalId', compId')
-  _ -> throw ConfigStateFileNoHeader
-
--- | Generate the @setup-config@ file header.
-showHeader :: PackageIdentifier -- ^ The processed package.
-            -> ByteString
-showHeader pkgId = BLC8.unwords
-    [ "Saved", "package", "config", "for"
-    , BLC8.pack $ display pkgId
-    , "written", "by"
-    , BLC8.pack $ display currentCabalId
-    , "using"
-    , BLC8.pack $ display currentCompilerId
-    ]
-
--- | Check that localBuildInfoFile is up-to-date with respect to the
--- .cabal file.
-checkPersistBuildConfigOutdated :: FilePath -> FilePath -> IO Bool
-checkPersistBuildConfigOutdated distPref pkg_descr_file = do
-  pkg_descr_file `moreRecentFile` (localBuildInfoFile distPref)
-
--- | Get the path of @dist\/setup-config@.
-localBuildInfoFile :: FilePath -- ^ The @dist@ directory path.
-                    -> FilePath
-localBuildInfoFile distPref = distPref </> "setup-config"
-
--- -----------------------------------------------------------------------------
--- * Configuration
--- -----------------------------------------------------------------------------
-
--- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken from
--- (in order of highest to lowest preference) the override prefix, the \"CABAL_BUILDDIR\"
--- environment variable, or the default prefix.
-findDistPref :: FilePath  -- ^ default \"dist\" prefix
-             -> Setup.Flag FilePath  -- ^ override \"dist\" prefix
-             -> IO FilePath
-findDistPref defDistPref overrideDistPref = do
-    envDistPref <- liftM parseEnvDistPref (lookupEnv "CABAL_BUILDDIR")
-    return $ fromFlagOrDefault defDistPref (mappend envDistPref overrideDistPref)
-  where
-    parseEnvDistPref env =
-      case env of
-        Just distPref | not (null distPref) -> toFlag distPref
-        _ -> NoFlag
-
--- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken from
--- (in order of highest to lowest preference) the override prefix, the \"CABAL_BUILDDIR\"
--- environment variable, or 'defaultDistPref' is used. Call this function to resolve a
--- @*DistPref@ flag whenever it is not known to be set. (The @*DistPref@ flags are always
--- set to a definite value before invoking 'UserHooks'.)
-findDistPrefOrDefault :: Setup.Flag FilePath  -- ^ override \"dist\" prefix
-                      -> IO FilePath
-findDistPrefOrDefault = findDistPref defaultDistPref
-
--- |Perform the \"@.\/setup configure@\" action.
--- Returns the @.setup-config@ file.
-configure :: (GenericPackageDescription, HookedBuildInfo)
-          -> ConfigFlags -> IO LocalBuildInfo
-configure (pkg_descr0, pbi) cfg
-  = do  let distPref = fromFlag (configDistPref cfg)
-            buildDir' = distPref </> "build"
-
-        setupMessage verbosity "Configuring" (packageId pkg_descr0)
-
-        unless (configProfExe cfg == NoFlag) $ do
-          let enable | fromFlag (configProfExe cfg) = "enable"
-                     | otherwise = "disable"
-          warn verbosity
-            ("The flag --" ++ enable ++ "-executable-profiling is deprecated. "
-             ++ "Please use --" ++ enable ++ "-profiling instead.")
-
-        unless (configLibCoverage cfg == NoFlag) $ do
-          let enable | fromFlag (configLibCoverage cfg) = "enable"
-                     | otherwise = "disable"
-          warn verbosity
-            ("The flag --" ++ enable ++ "-library-coverage is deprecated. "
-             ++ "Please use --" ++ enable ++ "-coverage instead.")
-
-        createDirectoryIfMissingVerbose (lessVerbose verbosity) True distPref
-
-        let programsConfig = mkProgramsConfig cfg (configPrograms cfg)
-            userInstall    = fromFlag (configUserInstall cfg)
-            packageDbs     = interpretPackageDbFlags userInstall
-                             (configPackageDBs cfg)
-
-        -- detect compiler
-        (comp, compPlatform, programsConfig') <- configCompilerEx
-          (flagToMaybe $ configHcFlavor cfg)
-          (flagToMaybe $ configHcPath cfg) (flagToMaybe $ configHcPkg cfg)
-          programsConfig (lessVerbose verbosity)
-        let version = compilerVersion comp
-            flavor  = compilerFlavor comp
-
-        -- Create a PackageIndex that makes *any libraries that might be*
-        -- defined internally to this package look like installed packages, in
-        -- case an executable should refer to any of them as dependencies.
-        --
-        -- It must be *any libraries that might be* defined rather than the
-        -- actual definitions, because these depend on conditionals in the .cabal
-        -- file, and we haven't resolved them yet.  finalizePackageDescription
-        -- does the resolution of conditionals, and it takes internalPackageSet
-        -- as part of its input.
-        --
-        -- Currently a package can define no more than one library (which has
-        -- the same name as the package) but we could extend this later.
-        -- If we later allowed private internal libraries, then here we would
-        -- need to pre-scan the conditional data to make a list of all private
-        -- libraries that could possibly be defined by the .cabal file.
-        let pid = packageId pkg_descr0
-            internalPackage = emptyInstalledPackageInfo {
-                --TODO: should use a per-compiler method to map the source
-                --      package ID into an installed package id we can use
-                --      for the internal package set. The open-codes use of
-                --      InstalledPackageId . display here is a hack.
-                Installed.installedPackageId =
-                   InstalledPackageId $ display $ pid,
-                Installed.sourcePackageId = pid
-              }
-            internalPackageSet = PackageIndex.fromList [internalPackage]
-        installedPackageSet <- getInstalledPackages (lessVerbose verbosity) comp
-                                      packageDbs programsConfig'
-
-        (allConstraints, requiredDepsMap) <- either die return $
-          combinedConstraints (configConstraints cfg)
-                              (configDependencies cfg)
-                              installedPackageSet
-
-        let exactConf = fromFlagOrDefault False (configExactConfiguration cfg)
-            -- Constraint test function for the solver
-            dependencySatisfiable d@(Dependency depName verRange)
-              | exactConf =
-                -- When we're given '--exact-configuration', we assume that all
-                -- dependencies and flags are exactly specified on the command
-                -- line. Thus we only consult the 'requiredDepsMap'. Note that
-                -- we're not doing the version range check, so if there's some
-                -- dependency that wasn't specified on the command line,
-                -- 'finalizePackageDescription' will fail.
-                --
-                -- TODO: mention '--exact-configuration' in the error message
-                -- when this fails?
-                (depName `Map.member` requiredDepsMap) || isInternalDep
-
-              | otherwise =
-                -- Normal operation: just look up dependency in the package
-                -- index.
-                not . null . PackageIndex.lookupDependency pkgs' $ d
-              where
-                pkgs' = PackageIndex.insert internalPackage installedPackageSet
-                isInternalDep = pkgName pid == depName
-                                && pkgVersion pid `withinRange` verRange
-            enableTest t = t { testEnabled = fromFlag (configTests cfg) }
-            flaggedTests = map (\(n, t) -> (n, mapTreeData enableTest t))
-                               (condTestSuites pkg_descr0)
-            enableBenchmark bm = bm { benchmarkEnabled =
-                                         fromFlag (configBenchmarks cfg) }
-            flaggedBenchmarks = map (\(n, bm) ->
-                                      (n, mapTreeData enableBenchmark bm))
-                               (condBenchmarks pkg_descr0)
-            pkg_descr0'' = pkg_descr0 { condTestSuites = flaggedTests
-                                      , condBenchmarks = flaggedBenchmarks }
-
-        (pkg_descr0', flags) <-
-                case finalizePackageDescription
-                       (configConfigurationsFlags cfg)
-                       dependencySatisfiable
-                       compPlatform
-                       (compilerInfo comp)
-                       allConstraints
-                       pkg_descr0''
-                of Right r -> return r
-                   Left missing ->
-                       die $ "At least the following dependencies are missing:\n"
-                         ++ (render . nest 4 . sep . punctuate comma
-                                    . map (disp . simplifyDependency)
-                                    $ missing)
-
-        -- Sanity check: if '--exact-configuration' was given, ensure that the
-        -- complete flag assignment was specified on the command line.
-        when exactConf $ do
-          let cmdlineFlags = map fst (configConfigurationsFlags cfg)
-              allFlags     = map flagName . genPackageFlags $ pkg_descr0
-              diffFlags    = allFlags \\ cmdlineFlags
-          when (not . null $ diffFlags) $
-            die $ "'--exact-conf' was given, "
-            ++ "but the following flags were not specified: "
-            ++ intercalate ", " (map show diffFlags)
-
-        -- add extra include/lib dirs as specified in cfg
-        -- we do it here so that those get checked too
-        let pkg_descr = addExtraIncludeLibDirs pkg_descr0'
-
-        unless (renamingPackageFlagsSupported comp ||
-                    and [ rn == defaultRenaming
-                        | bi <- allBuildInfo pkg_descr
-                        , rn <- Map.elems (targetBuildRenaming bi)]) $
-            die $ "Your compiler does not support thinning and renaming on "
-               ++ "package flags.  To use this feature you probably must use "
-               ++ "GHC 7.9 or later."
-
-        when (not (null flags)) $
-          info verbosity $ "Flags chosen: "
-                        ++ intercalate ", " [ name ++ "=" ++ display value
-                                            | (FlagName name, value) <- flags ]
-
-        when (maybe False (not.null.PD.reexportedModules) (PD.library pkg_descr)
-              && not (reexportedModulesSupported comp)) $ do
-            die $ "Your compiler does not support module re-exports. To use "
-               ++ "this feature you probably must use GHC 7.9 or later."
-
-        checkPackageProblems verbosity pkg_descr0
-          (updatePackageDescription pbi pkg_descr)
-
-        -- Handle hole instantiation
-        (holeDeps, hole_insts) <- configureInstantiateWith pkg_descr cfg installedPackageSet
-
-        let selectDependencies :: [Dependency] ->
-                                  ([FailedDependency], [ResolvedDependency])
-            selectDependencies =
-                (\xs -> ([ x | Left x <- xs ], [ x | Right x <- xs ]))
-              . map (selectDependency internalPackageSet installedPackageSet
-                                      requiredDepsMap)
-
-            (failedDeps, allPkgDeps) =
-              selectDependencies (buildDepends pkg_descr)
-
-            internalPkgDeps = [ pkgid
-                              | InternalDependency _ pkgid <- allPkgDeps ]
-            externalPkgDeps = [ pkg
-                              | ExternalDependency _ pkg   <- allPkgDeps ]
-
-        when (not (null internalPkgDeps)
-              && not (newPackageDepsBehaviour pkg_descr)) $
-            die $ "The field 'build-depends: "
-               ++ intercalate ", " (map (display . packageName) internalPkgDeps)
-               ++ "' refers to a library which is defined within the same "
-               ++ "package. To use this feature the package must specify at "
-               ++ "least 'cabal-version: >= 1.8'."
-
-        reportFailedDependencies failedDeps
-        reportSelectedDependencies verbosity allPkgDeps
-
-        let installDeps = Map.elems
-                        . Map.fromList
-                        . map (\v -> (Installed.installedPackageId v, v))
-                        $ externalPkgDeps ++ holeDeps
-
-        packageDependsIndex <-
-          case PackageIndex.dependencyClosure installedPackageSet
-                  (map Installed.installedPackageId installDeps) of
-            Left packageDependsIndex -> return packageDependsIndex
-            Right broken ->
-              die $ "The following installed packages are broken because other"
-                 ++ " packages they depend on are missing. These broken "
-                 ++ "packages must be rebuilt before they can be used.\n"
-                 ++ unlines [ "package "
-                           ++ display (packageId pkg)
-                           ++ " is broken due to missing package "
-                           ++ intercalate ", " (map display deps)
-                            | (pkg, deps) <- broken ]
-
-        let pseudoTopPkg = emptyInstalledPackageInfo {
-                Installed.installedPackageId =
-                   InstalledPackageId (display (packageId pkg_descr)),
-                Installed.sourcePackageId = packageId pkg_descr,
-                Installed.depends =
-                  map Installed.installedPackageId installDeps
-              }
-        case PackageIndex.dependencyInconsistencies
-           . PackageIndex.insert pseudoTopPkg
-           $ packageDependsIndex of
-          [] -> return ()
-          inconsistencies ->
-            warn verbosity $
-                 "This package indirectly depends on multiple versions of the same "
-              ++ "package. This is highly likely to cause a compile failure.\n"
-              ++ unlines [ "package " ++ display pkg ++ " requires "
-                        ++ display (PackageIdentifier name ver)
-                         | (name, uses) <- inconsistencies
-                         , (pkg, ver) <- uses ]
-
-        -- installation directories
-        defaultDirs <- defaultInstallDirs flavor userInstall (hasLibs pkg_descr)
-        let installDirs = combineInstallDirs fromFlagOrDefault
-                            defaultDirs (configInstallDirs cfg)
-
-        -- check languages and extensions
-        let langlist = nub $ catMaybes $ map defaultLanguage
-                       (allBuildInfo pkg_descr)
-        let langs = unsupportedLanguages comp langlist
-        when (not (null langs)) $
-          die $ "The package " ++ display (packageId pkg_descr0)
-             ++ " requires the following languages which are not "
-             ++ "supported by " ++ display (compilerId comp) ++ ": "
-             ++ intercalate ", " (map display langs)
-        let extlist = nub $ concatMap allExtensions (allBuildInfo pkg_descr)
-        let exts = unsupportedExtensions comp extlist
-        when (not (null exts)) $
-          die $ "The package " ++ display (packageId pkg_descr0)
-             ++ " requires the following language extensions which are not "
-             ++ "supported by " ++ display (compilerId comp) ++ ": "
-             ++ intercalate ", " (map display exts)
-
-        -- configured known/required programs & external build tools
-        -- exclude build-tool deps on "internal" exes in the same package
-        let requiredBuildTools =
-              [ buildTool
-              | let exeNames = map exeName (executables pkg_descr)
-              , bi <- allBuildInfo pkg_descr
-              , buildTool@(Dependency (PackageName toolName) reqVer)
-                <- buildTools bi
-              , let isInternal =
-                        toolName `elem` exeNames
-                        -- we assume all internal build-tools are
-                        -- versioned with the package:
-                     && packageVersion pkg_descr `withinRange` reqVer
-              , not isInternal ]
-
-        programsConfig'' <-
-              configureAllKnownPrograms (lessVerbose verbosity) programsConfig'
-          >>= configureRequiredPrograms verbosity requiredBuildTools
-
-        (pkg_descr', programsConfig''') <-
-          configurePkgconfigPackages verbosity pkg_descr programsConfig''
-
-        -- internal component graph
-        buildComponents <-
-          case mkComponentsGraph pkg_descr internalPkgDeps of
-            Left  componentCycle -> reportComponentCycle componentCycle
-            Right components     ->
-              mkComponentsLocalBuildInfo comp packageDependsIndex pkg_descr
-                                         internalPkgDeps externalPkgDeps holeDeps
-                                         (Map.fromList hole_insts)
-                                         components
-
-        split_objs <-
-           if not (fromFlag $ configSplitObjs cfg)
-                then return False
-                else case flavor of
-                            GHC | version >= Version [6,5] [] -> return True
-                            GHCJS                             -> return True
-                            _ -> do warn verbosity
-                                         ("this compiler does not support " ++
-                                          "--enable-split-objs; ignoring")
-                                    return False
-
-        let ghciLibByDefault =
-              case compilerId comp of
-                CompilerId GHC _ ->
-                  -- If ghc is non-dynamic, then ghci needs object files,
-                  -- so we build one by default.
-                  --
-                  -- Technically, archive files should be sufficient for ghci,
-                  -- but because of GHC bug #8942, it has never been safe to
-                  -- rely on them. By the time that bug was fixed, ghci had
-                  -- been changed to read shared libraries instead of archive
-                  -- files (see next code block).
-                  not (GHC.isDynamic comp)
-                CompilerId GHCJS _ ->
-                  not (GHCJS.isDynamic comp)
-                _ -> False
-
-        let sharedLibsByDefault
-              | fromFlag (configDynExe cfg) =
-                  -- build a shared library if dynamically-linked
-                  -- executables are requested
-                  True
-              | otherwise = case compilerId comp of
-                CompilerId GHC _ ->
-                  -- if ghc is dynamic, then ghci needs a shared
-                  -- library, so we build one by default.
-                  GHC.isDynamic comp
-                CompilerId GHCJS _ ->
-                  GHCJS.isDynamic comp
-                _ -> False
-            withSharedLib_ =
-                -- build shared libraries if required by GHC or by the
-                -- executable linking mode, but allow the user to force
-                -- building only static library archives with
-                -- --disable-shared.
-                fromFlagOrDefault sharedLibsByDefault $ configSharedLib cfg
-            withDynExe_ = fromFlag $ configDynExe cfg
-        when (withDynExe_ && not withSharedLib_) $ warn verbosity $
-               "Executables will use dynamic linking, but a shared library "
-            ++ "is not being built. Linking will fail if any executables "
-            ++ "depend on the library."
-
-        -- The --profiling flag sets the default for both libs and exes,
-        -- but can be overidden by --library-profiling, or the old deprecated
-        -- --executable-profiling flag.
-        let profEnabledLibOnly = configProfLib cfg
-            profEnabledBoth    = fromFlagOrDefault False (configProf cfg)
-            profEnabledLib = fromFlagOrDefault profEnabledBoth profEnabledLibOnly
-            profEnabledExe = fromFlagOrDefault profEnabledBoth (configProfExe cfg)
-
-        -- The --profiling-detail and --library-profiling-detail flags behave
-        -- similarly
-        profDetailLibOnly <- checkProfDetail (configProfLibDetail cfg)
-        profDetailBoth    <- liftM (fromFlagOrDefault ProfDetailDefault)
-                                   (checkProfDetail (configProfDetail cfg))
-        let profDetailLib = fromFlagOrDefault profDetailBoth profDetailLibOnly
-            profDetailExe = profDetailBoth
-
-        when (profEnabledExe && not profEnabledLib) $
-          warn verbosity $
-               "Executables will be built with profiling, but library "
-            ++ "profiling is disabled. Linking will fail if any executables "
-            ++ "depend on the library."
-
-        let configCoverage_ =
-              mappend (configCoverage cfg) (configLibCoverage cfg)
-
-            cfg' = cfg { configCoverage = configCoverage_ }
-
-        reloc <-
-           if not (fromFlag $ configRelocatable cfg)
-                then return False
-                else return True
-
-        let lbi = LocalBuildInfo {
-                    configFlags         = cfg',
-                    extraConfigArgs     = [],  -- Currently configure does not
-                                               -- take extra args, but if it
-                                               -- did they would go here.
-                    installDirTemplates = installDirs,
-                    compiler            = comp,
-                    hostPlatform        = compPlatform,
-                    buildDir            = buildDir',
-                    componentsConfigs   = buildComponents,
-                    installedPkgs       = packageDependsIndex,
-                    pkgDescrFile        = Nothing,
-                    localPkgDescr       = pkg_descr',
-                    instantiatedWith    = hole_insts,
-                    withPrograms        = programsConfig''',
-                    withVanillaLib      = fromFlag $ configVanillaLib cfg,
-                    withProfLib         = profEnabledLib,
-                    withSharedLib       = withSharedLib_,
-                    withDynExe          = withDynExe_,
-                    withProfExe         = profEnabledExe,
-                    withProfLibDetail   = profDetailLib,
-                    withProfExeDetail   = profDetailExe,
-                    withOptimization    = fromFlag $ configOptimization cfg,
-                    withDebugInfo       = fromFlag $ configDebugInfo cfg,
-                    withGHCiLib         = fromFlagOrDefault ghciLibByDefault $
-                                          configGHCiLib cfg,
-                    splitObjs           = split_objs,
-                    stripExes           = fromFlag $ configStripExes cfg,
-                    stripLibs           = fromFlag $ configStripLibs cfg,
-                    withPackageDB       = packageDbs,
-                    progPrefix          = fromFlag $ configProgPrefix cfg,
-                    progSuffix          = fromFlag $ configProgSuffix cfg,
-                    relocatable         = reloc
-                  }
-
-        when reloc (checkRelocatable verbosity pkg_descr lbi)
-
-        let dirs = absoluteInstallDirs pkg_descr lbi NoCopyDest
-            relative = prefixRelativeInstallDirs (packageId pkg_descr) lbi
-
-        unless (isAbsolute (prefix dirs)) $ die $
-            "expected an absolute directory name for --prefix: " ++ prefix dirs
-
-        info verbosity $ "Using " ++ display currentCabalId
-                      ++ " compiled by " ++ display currentCompilerId
-        info verbosity $ "Using compiler: " ++ showCompilerId comp
-        info verbosity $ "Using install prefix: " ++ prefix dirs
-
-        let dirinfo name dir isPrefixRelative =
-              info verbosity $ name ++ " installed in: " ++ dir ++ relNote
-              where relNote = case buildOS of
-                      Windows | not (hasLibs pkg_descr)
-                             && isNothing isPrefixRelative
-                             -> "  (fixed location)"
-                      _      -> ""
-
-        dirinfo "Binaries"         (bindir dirs)     (bindir relative)
-        dirinfo "Libraries"        (libdir dirs)     (libdir relative)
-        dirinfo "Private binaries" (libexecdir dirs) (libexecdir relative)
-        dirinfo "Data files"       (datadir dirs)    (datadir relative)
-        dirinfo "Documentation"    (docdir dirs)     (docdir relative)
-        dirinfo "Configuration files" (sysconfdir dirs) (sysconfdir relative)
-
-        sequence_ [ reportProgram verbosity prog configuredProg
-                  | (prog, configuredProg) <- knownPrograms programsConfig''' ]
-
-        return lbi
-
-    where
-      verbosity = fromFlag (configVerbosity cfg)
-
-      addExtraIncludeLibDirs pkg_descr =
-          let extraBi = mempty { extraLibDirs = configExtraLibDirs cfg
-                               , PD.includeDirs = configExtraIncludeDirs cfg}
-              modifyLib l        = l{ libBuildInfo = libBuildInfo l
-                                                     `mappend` extraBi }
-              modifyExecutable e = e{ buildInfo    = buildInfo e
-                                                     `mappend` extraBi}
-          in pkg_descr{ library     = modifyLib        `fmap` library pkg_descr
-                      , executables = modifyExecutable  `map`
-                                      executables pkg_descr}
-
-      checkProfDetail (Flag (ProfDetailOther other)) = do
-        warn verbosity $
-             "Unknown profiling detail level '" ++ other
-          ++ "', using default.\n"
-          ++ "The profiling detail levels are: " ++ intercalate ", "
-             [ name | (name, _, _) <- knownProfDetailLevels ]
-        return (Flag ProfDetailDefault)
-      checkProfDetail other = return other
-
-mkProgramsConfig :: ConfigFlags -> ProgramConfiguration -> ProgramConfiguration
-mkProgramsConfig cfg initialProgramsConfig = programsConfig
-  where
-    programsConfig = userSpecifyArgss (configProgramArgs cfg)
-                   . userSpecifyPaths (configProgramPaths cfg)
-                   . setProgramSearchPath searchpath
-                   $ initialProgramsConfig
-    searchpath     = getProgramSearchPath (initialProgramsConfig)
-                  ++ map ProgramSearchPathDir (fromNubList $ configProgramPathExtra cfg)
-
--- -----------------------------------------------------------------------------
--- Configuring package dependencies
-
-reportProgram :: Verbosity -> Program -> Maybe ConfiguredProgram -> IO ()
-reportProgram verbosity prog Nothing
-    = info verbosity $ "No " ++ programName prog ++ " found"
-reportProgram verbosity prog (Just configuredProg)
-    = info verbosity $ "Using " ++ programName prog ++ version ++ location
-    where location = case programLocation configuredProg of
-            FoundOnSystem p -> " found on system at: " ++ p
-            UserSpecified p -> " given by user at: " ++ p
-          version = case programVersion configuredProg of
-            Nothing -> ""
-            Just v  -> " version " ++ display v
-
-hackageUrl :: String
-hackageUrl = "http://hackage.haskell.org/package/"
-
-data ResolvedDependency = ExternalDependency Dependency InstalledPackageInfo
-                        | InternalDependency Dependency PackageId -- should be a
-                                                                      -- lib name
-
-data FailedDependency = DependencyNotExists PackageName
-                      | DependencyNoVersion Dependency
-
--- | Test for a package dependency and record the version we have installed.
-selectDependency :: InstalledPackageIndex  -- ^ Internally defined packages
-                 -> InstalledPackageIndex  -- ^ Installed packages
-                 -> Map PackageName InstalledPackageInfo
-                    -- ^ Packages for which we have been given specific deps to use
-                 -> Dependency
-                 -> Either FailedDependency ResolvedDependency
-selectDependency internalIndex installedIndex requiredDepsMap
-  dep@(Dependency pkgname vr) =
-  -- If the dependency specification matches anything in the internal package
-  -- index, then we prefer that match to anything in the second.
-  -- For example:
-  --
-  -- Name: MyLibrary
-  -- Version: 0.1
-  -- Library
-  --     ..
-  -- Executable my-exec
-  --     build-depends: MyLibrary
-  --
-  -- We want "build-depends: MyLibrary" always to match the internal library
-  -- even if there is a newer installed library "MyLibrary-0.2".
-  -- However, "build-depends: MyLibrary >= 0.2" should match the installed one.
-  case PackageIndex.lookupPackageName internalIndex pkgname of
-    [(_,[pkg])] | packageVersion pkg `withinRange` vr
-           -> Right $ InternalDependency dep (packageId pkg)
-
-    _      -> case Map.lookup pkgname requiredDepsMap of
-      -- If we know the exact pkg to use, then use it.
-      Just pkginstance -> Right (ExternalDependency dep pkginstance)
-      -- Otherwise we just pick an arbitrary instance of the latest version.
-      Nothing -> case PackageIndex.lookupDependency installedIndex dep of
-        []   -> Left  $ DependencyNotExists pkgname
-        pkgs -> Right $ ExternalDependency dep $
-                case last pkgs of
-                  (_ver, pkginstances) -> head pkginstances
-
-reportSelectedDependencies :: Verbosity
-                           -> [ResolvedDependency] -> IO ()
-reportSelectedDependencies verbosity deps =
-  info verbosity $ unlines
-    [ "Dependency " ++ display (simplifyDependency dep)
-                    ++ ": using " ++ display pkgid
-    | resolved <- deps
-    , let (dep, pkgid) = case resolved of
-            ExternalDependency dep' pkg'   -> (dep', packageId pkg')
-            InternalDependency dep' pkgid' -> (dep', pkgid') ]
-
-reportFailedDependencies :: [FailedDependency] -> IO ()
-reportFailedDependencies []     = return ()
-reportFailedDependencies failed =
-    die (intercalate "\n\n" (map reportFailedDependency failed))
-
-  where
-    reportFailedDependency (DependencyNotExists pkgname) =
-         "there is no version of " ++ display pkgname ++ " installed.\n"
-      ++ "Perhaps you need to download and install it from\n"
-      ++ hackageUrl ++ display pkgname ++ "?"
-
-    reportFailedDependency (DependencyNoVersion dep) =
-        "cannot satisfy dependency " ++ display (simplifyDependency dep) ++ "\n"
-
--- | List all installed packages in the given package databases.
-getInstalledPackages :: Verbosity -> Compiler
-                     -> PackageDBStack -- ^ The stack of package databases.
-                     -> ProgramConfiguration
-                     -> IO InstalledPackageIndex
-getInstalledPackages verbosity comp packageDBs progconf = do
-  when (null packageDBs) $
-    die $ "No package databases have been specified. If you use "
-       ++ "--package-db=clear, you must follow it with --package-db= "
-       ++ "with 'global', 'user' or a specific file."
-
-  info verbosity "Reading installed packages..."
-  case compilerFlavor comp of
-    GHC   -> GHC.getInstalledPackages verbosity comp packageDBs progconf
-    GHCJS -> GHCJS.getInstalledPackages verbosity packageDBs progconf
-    JHC   -> JHC.getInstalledPackages verbosity packageDBs progconf
-    LHC   -> LHC.getInstalledPackages verbosity packageDBs progconf
-    UHC   -> UHC.getInstalledPackages verbosity comp packageDBs progconf
-    HaskellSuite {} ->
-      HaskellSuite.getInstalledPackages verbosity packageDBs progconf
-    flv -> die $ "don't know how to find the installed packages for "
-              ++ display flv
-
--- | Like 'getInstalledPackages', but for a single package DB.
-getPackageDBContents :: Verbosity -> Compiler
-                     -> PackageDB -> ProgramConfiguration
-                     -> IO InstalledPackageIndex
-getPackageDBContents verbosity comp packageDB progconf = do
-  info verbosity "Reading installed packages..."
-  case compilerFlavor comp of
-    GHC -> GHC.getPackageDBContents verbosity packageDB progconf
-    GHCJS -> GHCJS.getPackageDBContents verbosity packageDB progconf
-    -- For other compilers, try to fall back on 'getInstalledPackages'.
-    _   -> getInstalledPackages verbosity comp [packageDB] progconf
-
-
--- | The user interface specifies the package dbs to use with a combination of
--- @--global@, @--user@ and @--package-db=global|user|clear|$file@.
--- This function combines the global/user flag and interprets the package-db
--- flag into a single package db stack.
---
-interpretPackageDbFlags :: Bool -> [Maybe PackageDB] -> PackageDBStack
-interpretPackageDbFlags userInstall specificDBs =
-    extra initialStack specificDBs
-  where
-    initialStack | userInstall = [GlobalPackageDB, UserPackageDB]
-                 | otherwise   = [GlobalPackageDB]
-
-    extra dbs' []            = dbs'
-    extra _    (Nothing:dbs) = extra []             dbs
-    extra dbs' (Just db:dbs) = extra (dbs' ++ [db]) dbs
-
-newPackageDepsBehaviourMinVersion :: Version
-newPackageDepsBehaviourMinVersion = Version [1,7,1] []
-
--- In older cabal versions, there was only one set of package dependencies for
--- the whole package. In this version, we can have separate dependencies per
--- target, but we only enable this behaviour if the minimum cabal version
--- specified is >= a certain minimum. Otherwise, for compatibility we use the
--- old behaviour.
-newPackageDepsBehaviour :: PackageDescription -> Bool
-newPackageDepsBehaviour pkg =
-   specVersion pkg >= newPackageDepsBehaviourMinVersion
-
--- We are given both --constraint="foo < 2.0" style constraints and also
--- specific packages to pick via --dependency="foo=foo-2.0-177d5cdf20962d0581".
---
--- When finalising the package we have to take into account the specific
--- installed deps we've been given, and the finalise function expects
--- constraints, so we have to translate these deps into version constraints.
---
--- But after finalising we then have to make sure we pick the right specific
--- deps in the end. So we still need to remember which installed packages to
--- pick.
-combinedConstraints :: [Dependency] ->
-                       [(PackageName, InstalledPackageId)] ->
-                       InstalledPackageIndex ->
-                       Either String ([Dependency],
-                                      Map PackageName InstalledPackageInfo)
-combinedConstraints constraints dependencies installedPackages = do
-
-    when (not (null badInstalledPackageIds)) $
-      Left $ render $ text "The following package dependencies were requested"
-         $+$ nest 4 (dispDependencies badInstalledPackageIds)
-         $+$ text "however the given installed package instance does not exist."
-
-    when (not (null badNames)) $
-      Left $ render $ text "The following package dependencies were requested"
-         $+$ nest 4 (dispDependencies badNames)
-         $+$ text "however the installed package's name does not match the name given."
-
-    --TODO: we don't check that all dependencies are used!
-
-    return (allConstraints, idConstraintMap)
-
-  where
-    allConstraints :: [Dependency]
-    allConstraints = constraints
-                  ++ [ thisPackageVersion (packageId pkg)
-                     | (_, _, Just pkg) <- dependenciesPkgInfo ]
-
-    idConstraintMap :: Map PackageName InstalledPackageInfo
-    idConstraintMap = Map.fromList
-                        [ (packageName pkg, pkg)
-                        | (_, _, Just pkg) <- dependenciesPkgInfo ]
-
-    -- The dependencies along with the installed package info, if it exists
-    dependenciesPkgInfo :: [(PackageName, InstalledPackageId,
-                             Maybe InstalledPackageInfo)]
-    dependenciesPkgInfo =
-      [ (pkgname, ipkgid, mpkg)
-      | (pkgname, ipkgid) <- dependencies
-      , let mpkg = PackageIndex.lookupInstalledPackageId
-                     installedPackages ipkgid
-      ]
-
-    -- If we looked up a package specified by an installed package id
-    -- (i.e. someone has written a hash) and didn't find it then it's
-    -- an error.
-    badInstalledPackageIds =
-      [ (pkgname, ipkgid)
-      | (pkgname, ipkgid, Nothing) <- dependenciesPkgInfo ]
-
-    -- If someone has written e.g.
-    -- --dependency="foo=MyOtherLib-1.0-07...5bf30" then they have
-    -- probably made a mistake.
-    badNames =
-      [ (requestedPkgName, ipkgid)
-      | (requestedPkgName, ipkgid, Just pkg) <- dependenciesPkgInfo
-      , let foundPkgName = packageName pkg
-      , requestedPkgName /= foundPkgName ]
-
-    dispDependencies deps =
-      hsep [    text "--dependency="
-             <> quotes (disp pkgname <> char '=' <> disp ipkgid)
-           | (pkgname, ipkgid) <- deps ]
-
--- -----------------------------------------------------------------------------
--- Configuring hole instantiation
-
-configureInstantiateWith :: PackageDescription
-                         -> ConfigFlags
-                         -> InstalledPackageIndex -- ^ installed packages
-                         -> IO ([InstalledPackageInfo],
-                                [(ModuleName, (InstalledPackageInfo, ModuleName))])
-configureInstantiateWith pkg_descr cfg installedPackageSet = do
-        -- Holes: First, check and make sure the provided instantiation covers
-        -- all the holes we know about.  Indefinite package installation is
-        -- not handled at all at this point.
-        -- NB: We union together /all/ of the requirements when calculating
-        -- the package key.
-        -- NB: For now, we assume that dependencies don't contribute signatures.
-        -- This will be handled by cabal-install; as far as ./Setup is
-        -- concerned, the most important thing is to be provided correctly
-        -- built dependencies.
-        let signatures =
-              maybe [] (\lib -> requiredSignatures lib ++ exposedSignatures lib)
-                (PD.library pkg_descr)
-            signatureSet = Set.fromList signatures
-            instantiateMap = Map.fromList (configInstantiateWith cfg)
-            missing_impls = filter (not . flip Map.member instantiateMap) signatures
-            hole_insts0 = filter (\(k,_) -> Set.member k signatureSet) (configInstantiateWith cfg)
-
-        when (not (null missing_impls)) $
-          die $ "Missing signature implementations for these modules: "
-            ++ intercalate ", " (map display missing_impls)
-
-        -- Holes: Next, we need to make sure we have packages to actually
-        -- provide the implementations we're talking about.  This is on top
-        -- of the normal dependency resolution process.
-        -- TODO: internal dependencies (e.g. the test package depending on the
-        -- main library) is not currently supported
-        let selectHoleDependency (k,(i,m)) =
-              case PackageIndex.lookupInstalledPackageId installedPackageSet i of
-                Just pkginst -> Right (k,(pkginst, m))
-                Nothing -> Left i
-            (failed_hmap, hole_insts) = partitionEithers (map selectHoleDependency hole_insts0)
-            holeDeps = map (fst.snd) hole_insts -- could have dups
-
-        -- Holes: Finally, any dependencies selected this way have to be
-        -- included in the allPkgs index, as well as the buildComponents.
-        -- But don't report these as potential inconsistencies!
-
-        when (not (null failed_hmap)) $
-          die $ "Could not resolve these package IDs (from signature implementations): "
-            ++ intercalate ", " (map display failed_hmap)
-
-        return (holeDeps, hole_insts)
-
--- -----------------------------------------------------------------------------
--- Configuring program dependencies
-
-configureRequiredPrograms :: Verbosity -> [Dependency] -> ProgramConfiguration
-                             -> IO ProgramConfiguration
-configureRequiredPrograms verbosity deps conf =
-  foldM (configureRequiredProgram verbosity) conf deps
-
-configureRequiredProgram :: Verbosity -> ProgramConfiguration -> Dependency
-                            -> IO ProgramConfiguration
-configureRequiredProgram verbosity conf
-  (Dependency (PackageName progName) verRange) =
-  case lookupKnownProgram progName conf of
-    Nothing -> die ("Unknown build tool " ++ progName)
-    Just prog
-      -- requireProgramVersion always requires the program have a version
-      -- but if the user says "build-depends: foo" ie no version constraint
-      -- then we should not fail if we cannot discover the program version.
-      | verRange == anyVersion -> do
-          (_, conf') <- requireProgram verbosity prog conf
-          return conf'
-      | otherwise -> do
-          (_, _, conf') <- requireProgramVersion verbosity prog verRange conf
-          return conf'
-
--- -----------------------------------------------------------------------------
--- Configuring pkg-config package dependencies
-
-configurePkgconfigPackages :: Verbosity -> PackageDescription
-                           -> ProgramConfiguration
-                           -> IO (PackageDescription, ProgramConfiguration)
-configurePkgconfigPackages verbosity pkg_descr conf
-  | null allpkgs = return (pkg_descr, conf)
-  | otherwise    = do
-    (_, _, conf') <- requireProgramVersion
-                       (lessVerbose verbosity) pkgConfigProgram
-                       (orLaterVersion $ Version [0,9,0] []) conf
-    mapM_ requirePkg allpkgs
-    lib' <- mapM addPkgConfigBILib (library pkg_descr)
-    exes' <- mapM addPkgConfigBIExe (executables pkg_descr)
-    tests' <- mapM addPkgConfigBITest (testSuites pkg_descr)
-    benches' <- mapM addPkgConfigBIBench (benchmarks pkg_descr)
-    let pkg_descr' = pkg_descr { library = lib', executables = exes',
-                                 testSuites = tests', benchmarks = benches' }
-    return (pkg_descr', conf')
-
-  where
-    allpkgs = concatMap pkgconfigDepends (allBuildInfo pkg_descr)
-    pkgconfig = rawSystemProgramStdoutConf (lessVerbose verbosity)
-                  pkgConfigProgram conf
-
-    requirePkg dep@(Dependency (PackageName pkg) range) = do
-      version <- pkgconfig ["--modversion", pkg]
-                 `catchIO`   (\_ -> die notFound)
-                 `catchExit` (\_ -> die notFound)
-      case simpleParse version of
-        Nothing -> die "parsing output of pkg-config --modversion failed"
-        Just v | not (withinRange v range) -> die (badVersion v)
-               | otherwise                 -> info verbosity (depSatisfied v)
-      where
-        notFound     = "The pkg-config package '" ++ pkg ++ "'"
-                    ++ versionRequirement
-                    ++ " is required but it could not be found."
-        badVersion v = "The pkg-config package '" ++ pkg ++ "'"
-                    ++ versionRequirement
-                    ++ " is required but the version installed on the"
-                    ++ " system is version " ++ display v
-        depSatisfied v = "Dependency " ++ display dep
-                      ++ ": using version " ++ display v
-
-        versionRequirement
-          | isAnyVersion range = ""
-          | otherwise          = " version " ++ display range
-
-    -- Adds pkgconfig dependencies to the build info for a component
-    addPkgConfigBI compBI setCompBI comp = do
-      bi <- pkgconfigBuildInfo (pkgconfigDepends (compBI comp))
-      return $ setCompBI comp (compBI comp `mappend` bi)
-
-    -- Adds pkgconfig dependencies to the build info for a library
-    addPkgConfigBILib = addPkgConfigBI libBuildInfo $
-                          \lib bi -> lib { libBuildInfo = bi }
-
-    -- Adds pkgconfig dependencies to the build info for an executable
-    addPkgConfigBIExe = addPkgConfigBI buildInfo $
-                          \exe bi -> exe { buildInfo = bi }
-
-    -- Adds pkgconfig dependencies to the build info for a test suite
-    addPkgConfigBITest = addPkgConfigBI testBuildInfo $
-                          \test bi -> test { testBuildInfo = bi }
-
-    -- Adds pkgconfig dependencies to the build info for a benchmark
-    addPkgConfigBIBench = addPkgConfigBI benchmarkBuildInfo $
-                          \bench bi -> bench { benchmarkBuildInfo = bi }
-
-    pkgconfigBuildInfo :: [Dependency] -> IO BuildInfo
-    pkgconfigBuildInfo []      = return mempty
-    pkgconfigBuildInfo pkgdeps = do
-      let pkgs = nub [ display pkg | Dependency pkg _ <- pkgdeps ]
-      ccflags <- pkgconfig ("--cflags" : pkgs)
-      ldflags <- pkgconfig ("--libs"   : pkgs)
-      return (ccLdOptionsBuildInfo (words ccflags) (words ldflags))
-
--- | Makes a 'BuildInfo' from C compiler and linker flags.
---
--- This can be used with the output from configuration programs like pkg-config
--- and similar package-specific programs like mysql-config, freealut-config etc.
--- For example:
---
--- > ccflags <- rawSystemProgramStdoutConf verbosity prog conf ["--cflags"]
--- > ldflags <- rawSystemProgramStdoutConf verbosity prog conf ["--libs"]
--- > return (ccldOptionsBuildInfo (words ccflags) (words ldflags))
---
-ccLdOptionsBuildInfo :: [String] -> [String] -> BuildInfo
-ccLdOptionsBuildInfo cflags ldflags =
-  let (includeDirs',  cflags')   = partition ("-I" `isPrefixOf`) cflags
-      (extraLibs',    ldflags')  = partition ("-l" `isPrefixOf`) ldflags
-      (extraLibDirs', ldflags'') = partition ("-L" `isPrefixOf`) ldflags'
-  in mempty {
-       PD.includeDirs  = map (drop 2) includeDirs',
-       PD.extraLibs    = map (drop 2) extraLibs',
-       PD.extraLibDirs = map (drop 2) extraLibDirs',
-       PD.ccOptions    = cflags',
-       PD.ldOptions    = ldflags''
-     }
-
--- -----------------------------------------------------------------------------
--- Determining the compiler details
-
-configCompilerAuxEx :: ConfigFlags
-                    -> IO (Compiler, Platform, ProgramConfiguration)
-configCompilerAuxEx cfg = configCompilerEx (flagToMaybe $ configHcFlavor cfg)
-                                           (flagToMaybe $ configHcPath cfg)
-                                           (flagToMaybe $ configHcPkg cfg)
-                                           programsConfig
-                                           (fromFlag (configVerbosity cfg))
-  where
-    programsConfig = mkProgramsConfig cfg defaultProgramConfiguration
-
-configCompilerEx :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath
-                 -> ProgramConfiguration -> Verbosity
-                 -> IO (Compiler, Platform, ProgramConfiguration)
-configCompilerEx Nothing _ _ _ _ = die "Unknown compiler"
-configCompilerEx (Just hcFlavor) hcPath hcPkg conf verbosity = do
-  (comp, maybePlatform, programsConfig) <- case hcFlavor of
-    GHC   -> GHC.configure  verbosity hcPath hcPkg conf
-    GHCJS -> GHCJS.configure verbosity hcPath hcPkg conf
-    JHC   -> JHC.configure  verbosity hcPath hcPkg conf
-    LHC   -> do (_, _, ghcConf) <- GHC.configure  verbosity Nothing hcPkg conf
-                LHC.configure  verbosity hcPath Nothing ghcConf
-    UHC   -> UHC.configure  verbosity hcPath hcPkg conf
-    HaskellSuite {} -> HaskellSuite.configure verbosity hcPath hcPkg conf
-    _    -> die "Unknown compiler"
-  return (comp, fromMaybe buildPlatform maybePlatform, programsConfig)
-
--- Ideally we would like to not have separate configCompiler* and
--- configCompiler*Ex sets of functions, but there are many custom setup scripts
--- in the wild that are using them, so the versions with old types are kept for
--- backwards compatibility. Platform was added to the return triple in 1.18.
-
-{-# DEPRECATED configCompiler
-    "'configCompiler' is deprecated. Use 'configCompilerEx' instead." #-}
-configCompiler :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath
-               -> ProgramConfiguration -> Verbosity
-               -> IO (Compiler, ProgramConfiguration)
-configCompiler mFlavor hcPath hcPkg conf verbosity =
-  fmap (\(a,_,b) -> (a,b)) $ configCompilerEx mFlavor hcPath hcPkg conf verbosity
-
-{-# DEPRECATED configCompilerAux
-    "configCompilerAux is deprecated. Use 'configCompilerAuxEx' instead." #-}
-configCompilerAux :: ConfigFlags
-                  -> IO (Compiler, ProgramConfiguration)
-configCompilerAux = fmap (\(a,_,b) -> (a,b)) . configCompilerAuxEx
-
--- -----------------------------------------------------------------------------
--- Making the internal component graph
-
-
-mkComponentsGraph :: PackageDescription
-                  -> [PackageId]
-                  -> Either [ComponentName]
-                            [(Component, [ComponentName])]
-mkComponentsGraph pkg_descr internalPkgDeps =
-    let graph = [ (c, componentName c, componentDeps c)
-                | c <- pkgEnabledComponents pkg_descr ]
-     in case checkComponentsCyclic graph of
-          Just ccycle -> Left  [ cname | (_,cname,_) <- ccycle ]
-          Nothing     -> Right [ (c, cdeps) | (c, _, cdeps) <- graph ]
-  where
-    -- The dependencies for the given component
-    componentDeps component =
-         [ CExeName toolname | Dependency (PackageName toolname) _
-                               <- buildTools bi
-                             , toolname `elem` map exeName
-                               (executables pkg_descr) ]
-
-      ++ [ CLibName          | Dependency pkgname _ <- targetBuildDepends bi
-                             , pkgname `elem` map packageName internalPkgDeps ]
-      where
-        bi = componentBuildInfo component
-
-reportComponentCycle :: [ComponentName] -> IO a
-reportComponentCycle cnames =
-    die $ "Components in the package depend on each other in a cyclic way:\n  "
-       ++ intercalate " depends on "
-            [ "'" ++ showComponentName cname ++ "'"
-            | cname <- cnames ++ [head cnames] ]
-
-mkComponentsLocalBuildInfo :: Compiler
-                           -> InstalledPackageIndex
-                           -> PackageDescription
-                           -> [PackageId] -- internal package deps
-                           -> [InstalledPackageInfo] -- external package deps
-                           -> [InstalledPackageInfo] -- hole package deps
-                           -> Map ModuleName (InstalledPackageInfo, ModuleName)
-                           -> [(Component, [ComponentName])]
-                           -> IO [(ComponentName, ComponentLocalBuildInfo,
-                                                  [ComponentName])]
-mkComponentsLocalBuildInfo comp installedPackages pkg_descr
-                           internalPkgDeps externalPkgDeps holePkgDeps hole_insts
-                           graph =
-    sequence
-      [ do clbi <- componentLocalBuildInfo c
-           return (componentName c, clbi, cdeps)
-      | (c, cdeps) <- graph ]
-  where
-    -- The allPkgDeps contains all the package deps for the whole package
-    -- but we need to select the subset for this specific component.
-    -- we just take the subset for the package names this component
-    -- needs. Note, this only works because we cannot yet depend on two
-    -- versions of the same package.
-    componentLocalBuildInfo component =
-      case component of
-      CLib lib -> do
-        let exports = map (\n -> Installed.ExposedModule n Nothing Nothing)
-                          (PD.exposedModules lib)
-            esigs = map (\n -> Installed.ExposedModule n Nothing
-                                (fmap (\(pkg,m) -> Installed.OriginalModule
-                                                      (Installed.installedPackageId pkg) m)
-                                      (Map.lookup n hole_insts)))
-                        (PD.exposedSignatures lib)
-        let mb_reexports = resolveModuleReexports installedPackages
-                                                  (packageId pkg_descr)
-                                                  externalPkgDeps lib
-        reexports <- case mb_reexports of
-            Left problems -> reportModuleReexportProblems problems
-            Right r -> return r
-
-        -- Calculate the version hash and package key.
-        let externalPkgs = selectSubset bi externalPkgDeps
-            pkg_key = mkPackageKey (packageKeySupported comp)
-                        (package pkg_descr)
-                        (map Installed.libraryName externalPkgs)
-            version_hash = packageKeyLibraryName (package pkg_descr) pkg_key
-
-        return LibComponentLocalBuildInfo {
-          componentPackageDeps = cpds,
-          componentPackageKey = pkg_key,
-          componentLibraryName = version_hash,
-          componentPackageRenaming = cprns,
-          componentExposedModules = exports ++ reexports ++ esigs
-        }
-      CExe _ ->
-        return ExeComponentLocalBuildInfo {
-          componentPackageDeps = cpds,
-          componentPackageRenaming = cprns
-        }
-      CTest _ ->
-        return TestComponentLocalBuildInfo {
-          componentPackageDeps = cpds,
-          componentPackageRenaming = cprns
-        }
-      CBench _ ->
-        return BenchComponentLocalBuildInfo {
-          componentPackageDeps = cpds,
-          componentPackageRenaming = cprns
-        }
-      where
-        bi = componentBuildInfo component
-        dedup = Map.toList . Map.fromList
-        cpds = if newPackageDepsBehaviour pkg_descr
-               then dedup $
-                    [ (Installed.installedPackageId pkg, packageId pkg)
-                    | pkg <- selectSubset bi externalPkgDeps ]
-                 ++ [ (inplacePackageId pkgid, pkgid)
-                    | pkgid <- selectSubset bi internalPkgDeps ]
-               else [ (Installed.installedPackageId pkg, packageId pkg)
-                    | pkg <- externalPkgDeps ]
-        cprns = if newPackageDepsBehaviour pkg_descr
-                then Map.unionWith mappend
-                        -- We need hole dependencies passed to GHC, so add them here
-                        -- (but note that they're fully thinned out.  If they
-                        -- appeared legitimately the monoid instance will
-                        -- fill them out.
-                        (Map.fromList [(packageName pkg, mempty) | pkg <- holePkgDeps])
-                        (targetBuildRenaming bi)
-                -- Hack: if we have old package-deps behavior, it's impossible
-                -- for non-default renamings to be used, because the Cabal
-                -- version is too early.  This is a good, because while all the
-                -- deps were bundled up in buildDepends, we didn't do this for
-                -- renamings, so it's not even clear how to get the merged
-                -- version.  So just assume that all of them are the default..
-                else Map.fromList (map (\(_,pid) -> (packageName pid, defaultRenaming)) cpds)
-
-    selectSubset :: Package pkg => BuildInfo -> [pkg] -> [pkg]
-    selectSubset bi pkgs =
-        [ pkg | pkg <- pkgs, packageName pkg `elem` names bi ]
-
-    names bi = [ name | Dependency name _ <- targetBuildDepends bi ]
-
--- | Given the author-specified re-export declarations from the .cabal file,
--- resolve them to the form that we need for the package database.
---
--- An invariant of the package database is that we always link the re-export
--- directly to its original defining location (rather than indirectly via a
--- chain of re-exporting packages).
---
-resolveModuleReexports :: InstalledPackageIndex
-                       -> PackageId
-                       -> [InstalledPackageInfo]
-                       -> Library
-                       -> Either [(ModuleReexport, String)] -- errors
-                                 [Installed.ExposedModule] -- ok
-resolveModuleReexports installedPackages srcpkgid externalPkgDeps lib =
-    case partitionEithers (map resolveModuleReexport (PD.reexportedModules lib)) of
-      ([],  ok) -> Right ok
-      (errs, _) -> Left  errs
-  where
-    -- A mapping from visible module names to their original defining
-    -- module name.  We also record the package name of the package which
-    -- *immediately* provided the module (not the original) to handle if the
-    -- user explicitly says which build-depends they want to reexport from.
-    visibleModules :: Map ModuleName [(PackageName, Installed.ExposedModule)]
-    visibleModules =
-      Map.fromListWith (++) $
-        [ (Installed.exposedName exposedModule, [(exportingPackageName,
-                                                  exposedModule)])
-          -- The package index here contains all the indirect deps of the
-          -- package we're configuring, but we want just the direct deps
-        | let directDeps = Set.fromList (map Installed.installedPackageId externalPkgDeps)
-        , pkg <- PackageIndex.allPackages installedPackages
-        , Installed.installedPackageId pkg `Set.member` directDeps
-        , let exportingPackageName = packageName pkg
-        , exposedModule <- visibleModuleDetails pkg
-        ]
-     ++ [ (visibleModuleName, [(exportingPackageName, exposedModule)])
-        | visibleModuleName <- PD.exposedModules lib
-                            ++ otherModules (libBuildInfo lib)
-        , let exportingPackageName = packageName srcpkgid
-              definingModuleName   = visibleModuleName
-              -- we don't know the InstalledPackageId of this package yet
-              -- we will fill it in later, before registration.
-              definingPackageId    = InstalledPackageId ""
-              originalModule = Installed.OriginalModule definingPackageId
-                                                        definingModuleName
-              exposedModule  = Installed.ExposedModule visibleModuleName
-                                                       (Just originalModule)
-                                                             Nothing
-        ]
-
-    -- All the modules exported from this package and their defining name and
-    -- package (either defined here in this package or re-exported from some
-    -- other package).  Return an ExposedModule because we want to hold onto
-    -- signature information.
-    visibleModuleDetails :: InstalledPackageInfo -> [Installed.ExposedModule]
-    visibleModuleDetails pkg = do
-        exposedModule <- Installed.exposedModules pkg
-        case Installed.exposedReexport exposedModule of
-        -- The first case is the modules actually defined in this package.
-        -- In this case the reexport will point to this package.
-            Nothing -> return exposedModule { Installed.exposedReexport =
-                            Just (Installed.OriginalModule (Installed.installedPackageId pkg)
-                                                 (Installed.exposedName exposedModule)) }
-        -- On the other hand, a visible module might actually be itself
-        -- a re-export! In this case, the re-export info for the package
-        -- doing the re-export will point us to the original defining
-        -- module name and package, so we can reuse the entry.
-            Just _ -> return exposedModule
-
-    resolveModuleReexport reexport@ModuleReexport {
-         moduleReexportOriginalPackage = moriginalPackageName,
-         moduleReexportOriginalName    = originalName,
-         moduleReexportName            = newName
-      } =
-
-      let filterForSpecificPackage =
-            case moriginalPackageName of
-              Nothing                  -> id
-              Just originalPackageName ->
-                filter (\(pkgname, _) -> pkgname == originalPackageName)
-
-          matches = filterForSpecificPackage
-                      (Map.findWithDefault [] originalName visibleModules)
-      in
-      case (matches, moriginalPackageName) of
-        ((_, exposedModule):rest, _)
-          -- TODO: Refine this check for signatures
-          | all (\(_, exposedModule') -> Installed.exposedReexport exposedModule
-                                      == Installed.exposedReexport exposedModule') rest
-           -> Right exposedModule { Installed.exposedName = newName }
-
-        ([], Just originalPackageName)
-           -> Left $ (,) reexport
-                   $ "The package " ++ display originalPackageName
-                  ++ " does not export a module " ++ display originalName
-
-        ([], Nothing)
-           -> Left $ (,) reexport
-                   $ "The module " ++ display originalName
-                  ++ " is not exported by any suitable package (this package "
-                  ++ "itself nor any of its 'build-depends' dependencies)."
-
-        (ms, _)
-           -> Left $ (,) reexport
-                   $ "The module " ++ display originalName ++ " is exported "
-                  ++ "by more than one package ("
-                  ++ intercalate ", " [ display pkgname | (pkgname,_) <- ms ]
-                  ++ ") and so the re-export is ambiguous. The ambiguity can "
-                  ++ "be resolved by qualifying by the package name. The "
-                  ++ "syntax is 'packagename:moduleName [as newname]'."
-
-        -- Note: if in future Cabal allows directly depending on multiple
-        -- instances of the same package (e.g. backpack) then an additional
-        -- ambiguity case is possible here: (_, Just originalPackageName)
-        -- with the module being ambiguous despite being qualified by a
-        -- package name. Presumably by that time we'll have a mechanism to
-        -- qualify the instance we're referring to.
-
-reportModuleReexportProblems :: [(ModuleReexport, String)] -> IO a
-reportModuleReexportProblems reexportProblems =
-  die $ unlines
-    [ "Problem with the module re-export '" ++ display reexport ++ "': " ++ msg
-    | (reexport, msg) <- reexportProblems ]
-
--- -----------------------------------------------------------------------------
--- Testing C lib and header dependencies
-
--- Try to build a test C program which includes every header and links every
--- lib. If that fails, try to narrow it down by preprocessing (only) and linking
--- with individual headers and libs.  If none is the obvious culprit then give a
--- generic error message.
--- TODO: produce a log file from the compiler errors, if any.
-checkForeignDeps :: PackageDescription -> LocalBuildInfo -> Verbosity -> IO ()
-checkForeignDeps pkg lbi verbosity = do
-  ifBuildsWith allHeaders (commonCcArgs ++ makeLdArgs allLibs) -- I'm feeling
-                                                               -- lucky
-           (return ())
-           (do missingLibs <- findMissingLibs
-               missingHdr  <- findOffendingHdr
-               explainErrors missingHdr missingLibs)
-      where
-        allHeaders = collectField PD.includes
-        allLibs    = collectField PD.extraLibs
-
-        ifBuildsWith headers args success failure = do
-            ok <- builds (makeProgram headers) args
-            if ok then success else failure
-
-        findOffendingHdr =
-            ifBuildsWith allHeaders ccArgs
-                         (return Nothing)
-                         (go . tail . inits $ allHeaders)
-            where
-              go [] = return Nothing       -- cannot happen
-              go (hdrs:hdrsInits) =
-                    -- Try just preprocessing first
-                    ifBuildsWith hdrs cppArgs
-                      -- If that works, try compiling too
-                      (ifBuildsWith hdrs ccArgs
-                        (go hdrsInits)
-                        (return . Just . Right . last $ hdrs))
-                      (return . Just . Left . last $ hdrs)
-
-              cppArgs = "-E":commonCppArgs -- preprocess only
-              ccArgs  = "-c":commonCcArgs  -- don't try to link
-
-        findMissingLibs = ifBuildsWith [] (makeLdArgs allLibs)
-                                       (return [])
-                                       (filterM (fmap not . libExists) allLibs)
-
-        libExists lib = builds (makeProgram []) (makeLdArgs [lib])
-
-        commonCppArgs = platformDefines lbi
-                     ++ [ "-I" ++ autogenModulesDir lbi ]
-                     ++ [ "-I" ++ dir | dir <- collectField PD.includeDirs ]
-                     ++ ["-I."]
-                     ++ collectField PD.cppOptions
-                     ++ collectField PD.ccOptions
-                     ++ [ "-I" ++ dir
-                        | dep <- deps
-                        , dir <- Installed.includeDirs dep ]
-                     ++ [ opt
-                        | dep <- deps
-                        , opt <- Installed.ccOptions dep ]
-
-        commonCcArgs  = commonCppArgs
-                     ++ collectField PD.ccOptions
-                     ++ [ opt
-                        | dep <- deps
-                        , opt <- Installed.ccOptions dep ]
-
-        commonLdArgs  = [ "-L" ++ dir | dir <- collectField PD.extraLibDirs ]
-                     ++ collectField PD.ldOptions
-                     ++ [ "-L" ++ dir
-                        | dep <- deps
-                        , dir <- Installed.libraryDirs dep ]
-                     --TODO: do we also need dependent packages' ld options?
-        makeLdArgs libs = [ "-l"++lib | lib <- libs ] ++ commonLdArgs
-
-        makeProgram hdrs = unlines $
-                           [ "#include \""  ++ hdr ++ "\"" | hdr <- hdrs ] ++
-                           ["int main(int argc, char** argv) { return 0; }"]
-
-        collectField f = concatMap f allBi
-        allBi = allBuildInfo pkg
-        deps = PackageIndex.topologicalOrder (installedPkgs lbi)
-
-        builds program args = do
-            tempDir <- getTemporaryDirectory
-            withTempFile tempDir ".c" $ \cName cHnd ->
-              withTempFile tempDir "" $ \oNname oHnd -> do
-                hPutStrLn cHnd program
-                hClose cHnd
-                hClose oHnd
-                _ <- rawSystemProgramStdoutConf verbosity
-                  gccProgram (withPrograms lbi) (cName:"-o":oNname:args)
-                return True
-           `catchIO`   (\_ -> return False)
-           `catchExit` (\_ -> return False)
-
-        explainErrors Nothing [] = return () -- should be impossible!
-        explainErrors _ _
-           | isNothing . lookupProgram gccProgram . withPrograms $ lbi
-
-                              = die $ unlines $
-              [ "No working gcc",
-                  "This package depends on foreign library but we cannot "
-               ++ "find a working C compiler. If you have it in a "
-               ++ "non-standard location you can use the --with-gcc "
-               ++ "flag to specify it." ]
-
-        explainErrors hdr libs = die $ unlines $
-             [ if plural
-                 then "Missing dependencies on foreign libraries:"
-                 else "Missing dependency on a foreign library:"
-             | missing ]
-          ++ case hdr of
-               Just (Left h) -> ["* Missing (or bad) header file: " ++ h ]
-               _             -> []
-          ++ case libs of
-               []    -> []
-               [lib] -> ["* Missing C library: " ++ lib]
-               _     -> ["* Missing C libraries: " ++ intercalate ", " libs]
-          ++ [if plural then messagePlural else messageSingular | missing]
-          ++ case hdr of
-               Just (Left  _) -> [ headerCppMessage ]
-               Just (Right h) -> [ (if missing then "* " else "")
-                                   ++ "Bad header file: " ++ h
-                                 , headerCcMessage ]
-               _              -> []
-
-          where
-            plural  = length libs >= 2
-            -- Is there something missing? (as opposed to broken)
-            missing = not (null libs)
-                   || case hdr of Just (Left _) -> True; _ -> False
-
-        messageSingular =
-             "This problem can usually be solved by installing the system "
-          ++ "package that provides this library (you may need the "
-          ++ "\"-dev\" version). If the library is already installed "
-          ++ "but in a non-standard location then you can use the flags "
-          ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "
-          ++ "where it is."
-        messagePlural =
-             "This problem can usually be solved by installing the system "
-          ++ "packages that provide these libraries (you may need the "
-          ++ "\"-dev\" versions). If the libraries are already installed "
-          ++ "but in a non-standard location then you can use the flags "
-          ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "
-          ++ "where they are."
-        headerCppMessage =
-             "If the header file does exist, it may contain errors that "
-          ++ "are caught by the C compiler at the preprocessing stage. "
-          ++ "In this case you can re-run configure with the verbosity "
-          ++ "flag -v3 to see the error messages."
-        headerCcMessage =
-             "The header file contains a compile error. "
-          ++ "You can re-run configure with the verbosity flag "
-          ++ "-v3 to see the error messages from the C compiler."
-
--- | Output package check warnings and errors. Exit if any errors.
-checkPackageProblems :: Verbosity
-                     -> GenericPackageDescription
-                     -> PackageDescription
-                     -> IO ()
-checkPackageProblems verbosity gpkg pkg = do
-  ioChecks      <- checkPackageFiles pkg "."
-  let pureChecks = checkPackage gpkg (Just pkg)
-      errors   = [ e | PackageBuildImpossible e <- pureChecks ++ ioChecks ]
-      warnings = [ w | PackageBuildWarning    w <- pureChecks ++ ioChecks ]
-  if null errors
-    then mapM_ (warn verbosity) warnings
-    else die (intercalate "\n\n" errors)
-
--- | Preform checks if a relocatable build is allowed
-checkRelocatable :: Verbosity
-                 -> PackageDescription
-                 -> LocalBuildInfo
-                 -> IO ()
-checkRelocatable verbosity pkg lbi
-    = sequence_ [ checkOS
-                , checkCompiler
-                , packagePrefixRelative
-                , depsPrefixRelative
-                ]
-  where
-    -- Check if the OS support relocatable builds.
-    --
-    -- If you add new OS' to this list, and your OS supports dynamic libraries
-    -- and RPATH, make sure you add your OS to RPATH-support list of:
-    -- Distribution.Simple.GHC.getRPaths
-    checkOS
-        = unless (os `elem` [ OSX, Linux ])
-        $ die $ "Operating system: " ++ display os ++
-                ", does not support relocatable builds"
-      where
-        (Platform _ os) = hostPlatform lbi
-
-    -- Check if the Compiler support relocatable builds
-    checkCompiler
-        = unless (compilerFlavor comp `elem` [ GHC ])
-        $ die $ "Compiler: " ++ show comp ++
-                ", does not support relocatable builds"
-      where
-        comp = compiler lbi
-
-    -- Check if all the install dirs are relative to same prefix
-    packagePrefixRelative
-        = unless (relativeInstallDirs installDirs)
-        $ die $ "Installation directories are not prefix_relative:\n" ++
-                show installDirs
-      where
-        installDirs = absoluteInstallDirs pkg lbi NoCopyDest
-        p           = prefix installDirs
-        relativeInstallDirs (InstallDirs {..}) =
-          all isJust
-              (fmap (stripPrefix p)
-                    [ bindir, libdir, dynlibdir, libexecdir, includedir, datadir
-                    , docdir, mandir, htmldir, haddockdir, sysconfdir] )
-
-    -- Check if the library dirs of the dependencies that are in the package
-    -- database to which the package is installed are relative to the
-    -- prefix of the package
-    depsPrefixRelative = do
-        pkgr <- GHC.pkgRoot verbosity lbi (last (withPackageDB lbi))
-        mapM_ (doCheck pkgr) ipkgs
-      where
-        doCheck pkgr ipkg
-          | maybe False (== pkgr) (Installed.pkgRoot ipkg)
-          = mapM_ (\l -> when (isNothing $ stripPrefix p l) (die (msg l)))
-                  (Installed.libraryDirs ipkg)
-          | otherwise
-          = return ()
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Simple.Configure
+-- Copyright   :  Isaac Jones 2003-2005
+-- License     :  BSD3
+--
+-- Maintainer  :  cabal-devel@haskell.org
+-- Portability :  portable
+--
+-- This deals with the /configure/ phase. It provides the 'configure' action
+-- which is given the package description and configure flags. It then tries
+-- to: configure the compiler; resolves any conditionals in the package
+-- description; resolve the package dependencies; check if all the extensions
+-- used by this package are supported by the compiler; check that all the build
+-- tools are available (including version checks if appropriate); checks for
+-- any required @pkg-config@ packages (updating the 'BuildInfo' with the
+-- results)
+--
+-- Then based on all this it saves the info in the 'LocalBuildInfo' and writes
+-- it out to the @dist\/setup-config@ file. It also displays various details to
+-- the user, the amount of information displayed depending on the verbosity
+-- level.
+
+module Distribution.Simple.Configure (configure,
+                                      writePersistBuildConfig,
+                                      getConfigStateFile,
+                                      getPersistBuildConfig,
+                                      checkPersistBuildConfigOutdated,
+                                      tryGetPersistBuildConfig,
+                                      maybeGetPersistBuildConfig,
+                                      findDistPref, findDistPrefOrDefault,
+                                      computeComponentId,
+                                      computeCompatPackageKey,
+                                      computeCompatPackageName,
+                                      localBuildInfoFile,
+                                      getInstalledPackages,
+                                      getInstalledPackagesMonitorFiles,
+                                      getPackageDBContents,
+                                      configCompiler, configCompilerAux,
+                                      configCompilerEx, configCompilerAuxEx,
+                                      ccLdOptionsBuildInfo,
+                                      checkForeignDeps,
+                                      interpretPackageDbFlags,
+                                      ConfigStateFileError(..),
+                                      tryGetConfigStateFile,
+                                      platformDefines,
+                                      relaxPackageDeps,
+                                     )
+    where
+
+import Distribution.Compiler
+import Distribution.Utils.NubList
+import Distribution.Simple.Compiler hiding (Flag)
+import Distribution.Simple.PreProcess
+import Distribution.Package
+import qualified Distribution.InstalledPackageInfo as Installed
+import Distribution.InstalledPackageInfo (InstalledPackageInfo
+                                         ,emptyInstalledPackageInfo)
+import qualified Distribution.Simple.PackageIndex as PackageIndex
+import Distribution.Simple.PackageIndex (InstalledPackageIndex)
+import Distribution.PackageDescription as PD hiding (Flag)
+import Distribution.ModuleName
+import Distribution.PackageDescription.Configuration
+import Distribution.PackageDescription.Check hiding (doesFileExist)
+import Distribution.Simple.Program
+import Distribution.Simple.Setup as Setup
+import qualified Distribution.Simple.InstallDirs as InstallDirs
+import Distribution.Simple.LocalBuildInfo
+import Distribution.Simple.Utils
+import Distribution.System
+import Distribution.Version
+import Distribution.Verbosity
+
+import qualified Distribution.Simple.GHC   as GHC
+import qualified Distribution.Simple.GHCJS as GHCJS
+import qualified Distribution.Simple.JHC   as JHC
+import qualified Distribution.Simple.LHC   as LHC
+import qualified Distribution.Simple.UHC   as UHC
+import qualified Distribution.Simple.HaskellSuite as HaskellSuite
+
+-- Prefer the more generic Data.Traversable.mapM to Prelude.mapM
+import Prelude hiding ( mapM )
+import Control.Exception
+    ( Exception, evaluate, throw, throwIO, try )
+import Control.Exception ( ErrorCall )
+import Control.Monad
+    ( liftM, when, unless, foldM, filterM, mplus )
+import Distribution.Compat.Binary ( decodeOrFailIO, encode )
+import GHC.Fingerprint ( Fingerprint(..), fingerprintString )
+import Data.ByteString.Lazy (ByteString)
+import qualified Data.ByteString            as BS
+import qualified Data.ByteString.Lazy.Char8 as BLC8
+import Data.List
+    ( (\\), nub, partition, isPrefixOf, inits, stripPrefix )
+import Data.Maybe
+    ( isNothing, catMaybes, fromMaybe, mapMaybe, isJust )
+import Data.Either
+    ( partitionEithers )
+import qualified Data.Set as Set
+import Data.Monoid as Mon ( Monoid(..) )
+import qualified Data.Map as Map
+import Data.Map (Map)
+import Data.Traversable
+    ( mapM )
+import Data.Typeable
+import Data.Char ( chr, isAlphaNum )
+import Numeric ( showIntAtBase )
+import System.Directory
+    ( doesFileExist, createDirectoryIfMissing, getTemporaryDirectory )
+import System.FilePath
+    ( (</>), isAbsolute )
+import qualified System.Info
+    ( compilerName, compilerVersion )
+import System.IO
+    ( hPutStrLn, hClose )
+import Distribution.Text
+    ( Text(disp), defaultStyle, display, simpleParse )
+import Text.PrettyPrint
+    ( Doc, (<>), (<+>), ($+$), char, comma, empty, hsep, nest
+    , punctuate, quotes, render, renderStyle, sep, text )
+import Distribution.Compat.Environment ( lookupEnv )
+import Distribution.Compat.Exception ( catchExit, catchIO )
+
+import Data.Graph (graphFromEdges, topSort)
+
+-- | The errors that can be thrown when reading the @setup-config@ file.
+data ConfigStateFileError
+    = ConfigStateFileNoHeader -- ^ No header found.
+    | ConfigStateFileBadHeader -- ^ Incorrect header.
+    | ConfigStateFileNoParse -- ^ Cannot parse file contents.
+    | ConfigStateFileMissing -- ^ No file!
+    | ConfigStateFileBadVersion PackageIdentifier PackageIdentifier
+      (Either ConfigStateFileError LocalBuildInfo) -- ^ Mismatched version.
+  deriving (Typeable)
+
+-- | Format a 'ConfigStateFileError' as a user-facing error message.
+dispConfigStateFileError :: ConfigStateFileError -> Doc
+dispConfigStateFileError ConfigStateFileNoHeader =
+    text "Saved package config file header is missing."
+    <+> text "Re-run the 'configure' command."
+dispConfigStateFileError ConfigStateFileBadHeader =
+    text "Saved package config file header is corrupt."
+    <+> text "Re-run the 'configure' command."
+dispConfigStateFileError ConfigStateFileNoParse =
+    text "Saved package config file is corrupt."
+    <+> text "Re-run the 'configure' command."
+dispConfigStateFileError ConfigStateFileMissing =
+    text "Run the 'configure' command first."
+dispConfigStateFileError (ConfigStateFileBadVersion oldCabal oldCompiler _) =
+    text "Saved package config file is outdated:"
+    $+$ badCabal $+$ badCompiler
+    $+$ text "Re-run the 'configure' command."
+    where
+      badCabal =
+          text "• the Cabal version changed from"
+          <+> disp oldCabal <+> "to" <+> disp currentCabalId
+      badCompiler
+        | oldCompiler == currentCompilerId = empty
+        | otherwise =
+            text "• the compiler changed from"
+            <+> disp oldCompiler <+> "to" <+> disp currentCompilerId
+
+instance Show ConfigStateFileError where
+    show = renderStyle defaultStyle . dispConfigStateFileError
+
+instance Exception ConfigStateFileError
+
+-- | Read the 'localBuildInfoFile'.  Throw an exception if the file is
+-- missing, if the file cannot be read, or if the file was created by an older
+-- version of Cabal.
+getConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.
+                   -> IO LocalBuildInfo
+getConfigStateFile filename = do
+    exists <- doesFileExist filename
+    unless exists $ throwIO ConfigStateFileMissing
+    -- Read the config file into a strict ByteString to avoid problems with
+    -- lazy I/O, then convert to lazy because the binary package needs that.
+    contents <- BS.readFile filename
+    let (header, body) = BLC8.span (/='\n') (BLC8.fromChunks [contents])
+
+    headerParseResult <- try $ evaluate $ parseHeader header
+    let (cabalId, compId) =
+            case headerParseResult of
+              Left (_ :: ErrorCall) -> throw ConfigStateFileBadHeader
+              Right x -> x
+
+    let getStoredValue = do
+          result <- decodeOrFailIO (BLC8.tail body)
+          case result of
+            Left _ -> throw ConfigStateFileNoParse
+            Right x -> return x
+        deferErrorIfBadVersion act
+          | cabalId /= currentCabalId = do
+              eResult <- try act
+              throw $ ConfigStateFileBadVersion cabalId compId eResult
+          | otherwise = act
+    deferErrorIfBadVersion getStoredValue
+
+-- | Read the 'localBuildInfoFile', returning either an error or the local build
+-- info.
+tryGetConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.
+                      -> IO (Either ConfigStateFileError LocalBuildInfo)
+tryGetConfigStateFile = try . getConfigStateFile
+
+-- | Try to read the 'localBuildInfoFile'.
+tryGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
+                         -> IO (Either ConfigStateFileError LocalBuildInfo)
+tryGetPersistBuildConfig = try . getPersistBuildConfig
+
+-- | Read the 'localBuildInfoFile'. Throw an exception if the file is
+-- missing, if the file cannot be read, or if the file was created by an older
+-- version of Cabal.
+getPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
+                      -> IO LocalBuildInfo
+getPersistBuildConfig = getConfigStateFile . localBuildInfoFile
+
+-- | Try to read the 'localBuildInfoFile'.
+maybeGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
+                           -> IO (Maybe LocalBuildInfo)
+maybeGetPersistBuildConfig =
+    liftM (either (const Nothing) Just) . tryGetPersistBuildConfig
+
+-- | After running configure, output the 'LocalBuildInfo' to the
+-- 'localBuildInfoFile'.
+writePersistBuildConfig :: FilePath -- ^ The @dist@ directory path.
+                        -> LocalBuildInfo -- ^ The 'LocalBuildInfo' to write.
+                        -> IO ()
+writePersistBuildConfig distPref lbi = do
+    createDirectoryIfMissing False distPref
+    writeFileAtomic (localBuildInfoFile distPref) $
+      BLC8.unlines [showHeader pkgId, encode lbi]
+  where
+    pkgId = packageId $ localPkgDescr lbi
+
+-- | Identifier of the current Cabal package.
+currentCabalId :: PackageIdentifier
+currentCabalId = PackageIdentifier (PackageName "Cabal") cabalVersion
+
+-- | Identifier of the current compiler package.
+currentCompilerId :: PackageIdentifier
+currentCompilerId = PackageIdentifier (PackageName System.Info.compilerName)
+                                      System.Info.compilerVersion
+
+-- | Parse the @setup-config@ file header, returning the package identifiers
+-- for Cabal and the compiler.
+parseHeader :: ByteString -- ^ The file contents.
+            -> (PackageIdentifier, PackageIdentifier)
+parseHeader header = case BLC8.words header of
+  ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId,
+   "using", compId] ->
+      fromMaybe (throw ConfigStateFileBadHeader) $ do
+          _ <- simpleParse (BLC8.unpack pkgId) :: Maybe PackageIdentifier
+          cabalId' <- simpleParse (BLC8.unpack cabalId)
+          compId' <- simpleParse (BLC8.unpack compId)
+          return (cabalId', compId')
+  _ -> throw ConfigStateFileNoHeader
+
+-- | Generate the @setup-config@ file header.
+showHeader :: PackageIdentifier -- ^ The processed package.
+            -> ByteString
+showHeader pkgId = BLC8.unwords
+    [ "Saved", "package", "config", "for"
+    , BLC8.pack $ display pkgId
+    , "written", "by"
+    , BLC8.pack $ display currentCabalId
+    , "using"
+    , BLC8.pack $ display currentCompilerId
+    ]
+
+-- | Check that localBuildInfoFile is up-to-date with respect to the
+-- .cabal file.
+checkPersistBuildConfigOutdated :: FilePath -> FilePath -> IO Bool
+checkPersistBuildConfigOutdated distPref pkg_descr_file = do
+  pkg_descr_file `moreRecentFile` (localBuildInfoFile distPref)
+
+-- | Get the path of @dist\/setup-config@.
+localBuildInfoFile :: FilePath -- ^ The @dist@ directory path.
+                    -> FilePath
+localBuildInfoFile distPref = distPref </> "setup-config"
+
+-- -----------------------------------------------------------------------------
+-- * Configuration
+-- -----------------------------------------------------------------------------
+
+-- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken
+-- from (in order of highest to lowest preference) the override prefix, the
+-- \"CABAL_BUILDDIR\" environment variable, or the default prefix.
+findDistPref :: FilePath  -- ^ default \"dist\" prefix
+             -> Setup.Flag FilePath  -- ^ override \"dist\" prefix
+             -> IO FilePath
+findDistPref defDistPref overrideDistPref = do
+    envDistPref <- liftM parseEnvDistPref (lookupEnv "CABAL_BUILDDIR")
+    return $ fromFlagOrDefault defDistPref (mappend envDistPref overrideDistPref)
+  where
+    parseEnvDistPref env =
+      case env of
+        Just distPref | not (null distPref) -> toFlag distPref
+        _ -> NoFlag
+
+-- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken
+-- from (in order of highest to lowest preference) the override prefix, the
+-- \"CABAL_BUILDDIR\" environment variable, or 'defaultDistPref' is used. Call
+-- this function to resolve a @*DistPref@ flag whenever it is not known to be
+-- set. (The @*DistPref@ flags are always set to a definite value before
+-- invoking 'UserHooks'.)
+findDistPrefOrDefault :: Setup.Flag FilePath  -- ^ override \"dist\" prefix
+                      -> IO FilePath
+findDistPrefOrDefault = findDistPref defaultDistPref
+
+-- |Perform the \"@.\/setup configure@\" action.
+-- Returns the @.setup-config@ file.
+configure :: (GenericPackageDescription, HookedBuildInfo)
+          -> ConfigFlags -> IO LocalBuildInfo
+configure (pkg_descr0', pbi) cfg = do
+    let pkg_descr0 =
+          -- Ignore '--allow-newer' when we're given '--exact-configuration'.
+          if fromFlagOrDefault False (configExactConfiguration cfg)
+          then pkg_descr0'
+          else relaxPackageDeps
+               (fromMaybe AllowNewerNone $ configAllowNewer cfg)
+               pkg_descr0'
+
+    setupMessage verbosity "Configuring" (packageId pkg_descr0)
+
+    checkDeprecatedFlags verbosity cfg
+    checkExactConfiguration pkg_descr0 cfg
+
+    -- Where to build the package
+    let buildDir :: FilePath -- e.g. dist/build
+        -- fromFlag OK due to Distribution.Simple calling
+        -- findDistPrefOrDefault to fill it in
+        buildDir = fromFlag (configDistPref cfg) </> "build"
+    createDirectoryIfMissingVerbose (lessVerbose verbosity) True buildDir
+
+    -- What package database(s) to use
+    let packageDbs :: PackageDBStack
+        packageDbs
+         = interpretPackageDbFlags
+            (fromFlag (configUserInstall cfg))
+            (configPackageDBs cfg)
+
+    -- comp:            the compiler we're building with
+    -- compPlatform:    the platform we're building for
+    -- programsConfig:  location and args of all programs we're
+    --                  building with
+    (comp           :: Compiler,
+     compPlatform   :: Platform,
+     programsConfig :: ProgramConfiguration)
+        <- configCompilerEx
+            (flagToMaybe (configHcFlavor cfg))
+            (flagToMaybe (configHcPath cfg))
+            (flagToMaybe (configHcPkg cfg))
+            (mkProgramsConfig cfg (configPrograms cfg))
+            (lessVerbose verbosity)
+
+    -- The InstalledPackageIndex of all installed packages
+    installedPackageSet :: InstalledPackageIndex
+        <- getInstalledPackages (lessVerbose verbosity) comp
+                                  packageDbs programsConfig
+
+    -- An approximate InstalledPackageIndex of all (possible) internal libraries.
+    -- This database is used to bootstrap the process before we know precisely
+    -- what these libraries are supposed to be.
+    let internalPackageSet :: InstalledPackageIndex
+        internalPackageSet = getInternalPackages pkg_descr0
+
+    -- allConstraints:  The set of all 'Dependency's we have.  Used ONLY
+    --                  to 'configureFinalizedPackage'.
+    -- requiredDepsMap: A map from 'PackageName' to the specifically
+    --                  required 'InstalledPackageInfo', due to --dependency
+    --
+    -- NB: These constraints are to be applied to ALL components of
+    -- a package.  Thus, it's not an error if allConstraints contains
+    -- more constraints than is necessary for a component (another
+    -- component might need it.)
+    --
+    -- NB: The fact that we bundle all the constraints together means
+    -- that is not possible to configure a test-suite to use one
+    -- version of a dependency, and the executable to use another.
+    (allConstraints  :: [Dependency],
+     requiredDepsMap :: Map PackageName InstalledPackageInfo)
+        <- either die return $
+              combinedConstraints (configConstraints cfg)
+                                  (configDependencies cfg)
+                                  installedPackageSet
+
+    -- pkg_descr:   The resolved package description, that does not contain any
+    --              conditionals, because we have have an assignment for
+    --              every flag, either picking them ourselves using a
+    --              simple naive algorithm, or having them be passed to
+    --              us by 'configConfigurationsFlags')
+    -- flags:       The 'FlagAssignment' that the conditionals were
+    --              resolved with.
+    --
+    -- NB: Why doesn't finalizing a package also tell us what the
+    -- dependencies are (e.g. when we run the naive algorithm,
+    -- we are checking if dependencies are satisfiable)?  The
+    -- primary reason is that we may NOT have done any solving:
+    -- if the flags are all chosen for us, this step is a simple
+    -- matter of flattening according to that assignment.  It's
+    -- cleaner to then configure the dependencies afterwards.
+    (pkg_descr :: PackageDescription,
+     flags     :: FlagAssignment)
+        <- configureFinalizedPackage verbosity cfg
+                allConstraints
+                (dependencySatisfiable
+                    (fromFlagOrDefault False (configExactConfiguration cfg))
+                    installedPackageSet
+                    internalPackageSet
+                    requiredDepsMap)
+                comp
+                compPlatform
+                pkg_descr0
+
+    checkCompilerProblems comp pkg_descr
+    checkPackageProblems verbosity pkg_descr0
+        (updatePackageDescription pbi pkg_descr)
+
+    -- The list of 'InstalledPackageInfo' recording the selected
+    -- dependencies...
+    -- internalPkgDeps: ...on internal packages (these are fake!)
+    -- externalPkgDeps: ...on external packages
+    --
+    -- Invariant: For any package name, there is at most one package
+    -- in externalPackageDeps which has that name.
+    --
+    -- NB: The dependency selection is global over ALL components
+    -- in the package (similar to how allConstraints and
+    -- requiredDepsMap are global over all components).  In particular,
+    -- if *any* component (post-flag resolution) has an unsatisfiable
+    -- dependency, we will fail.  This can sometimes be undesirable
+    -- for users, see #1786 (benchmark conflicts with executable),
+    (internalPkgDeps :: [PackageId],
+     externalPkgDeps :: [InstalledPackageInfo])
+        <- configureDependencies
+                verbosity
+                internalPackageSet
+                installedPackageSet
+                requiredDepsMap
+                pkg_descr
+
+    -- The database of transitively reachable installed packages that the
+    -- external components the package (as a whole) depends on.  This will be
+    -- used in several ways:
+    --
+    --      * We'll use it to do a consistency check so we're not depending
+    --        on multiple versions of the same package (TODO: someday relax
+    --        this for private dependencies.)  See right below.
+    --
+    --      * We feed it in when configuring the components to resolve
+    --        module reexports.  (TODO: axe this.)
+    --
+    --      * We'll pass it on in the LocalBuildInfo, where preprocessors
+    --        and other things will incorrectly use it to determine what
+    --        the include paths and everything should be.
+    --
+    packageDependsIndex :: InstalledPackageIndex <-
+      case PackageIndex.dependencyClosure installedPackageSet
+              (map Installed.installedUnitId externalPkgDeps) of
+        Left packageDependsIndex -> return packageDependsIndex
+        Right broken ->
+          die $ "The following installed packages are broken because other"
+             ++ " packages they depend on are missing. These broken "
+             ++ "packages must be rebuilt before they can be used.\n"
+             ++ unlines [ "package "
+                       ++ display (packageId pkg)
+                       ++ " is broken due to missing package "
+                       ++ intercalate ", " (map display deps)
+                        | (pkg, deps) <- broken ]
+
+    -- In this section, we'd like to look at the 'packageDependsIndex'
+    -- and see if we've picked multiple versions of the same
+    -- installed package (this is bad, because it means you might
+    -- get an error could not match foo-0.1:Type with foo-0.2:Type).
+    --
+    -- What is pseudoTopPkg for? I have no idea.  It was used
+    -- in the very original commit which introduced checking for
+    -- inconsistencies 5115bb2be4e13841ea07dc9166b9d9afa5f0d012,
+    -- and then moved out of PackageIndex and put here later.
+    -- TODO: Try this code without it...
+    --
+    -- TODO: Move this into a helper function
+    let pseudoTopPkg :: InstalledPackageInfo
+        pseudoTopPkg = emptyInstalledPackageInfo {
+            Installed.installedUnitId =
+               mkLegacyUnitId (packageId pkg_descr),
+            Installed.sourcePackageId = packageId pkg_descr,
+            Installed.depends =
+              map Installed.installedUnitId externalPkgDeps
+          }
+    case PackageIndex.dependencyInconsistencies
+       . PackageIndex.insert pseudoTopPkg
+       $ packageDependsIndex of
+      [] -> return ()
+      inconsistencies ->
+        warn verbosity $
+             "This package indirectly depends on multiple versions of the same "
+          ++ "package. This is highly likely to cause a compile failure.\n"
+          ++ unlines [ "package " ++ display pkg ++ " requires "
+                    ++ display (PackageIdentifier name ver)
+                     | (name, uses) <- inconsistencies
+                     , (pkg, ver) <- uses ]
+
+    -- Compute installation directory templates, based on user
+    -- configuration.
+    --
+    -- TODO: Move this into a helper function.
+    defaultDirs :: InstallDirTemplates
+        <- defaultInstallDirs (compilerFlavor comp)
+                              (fromFlag (configUserInstall cfg))
+                              (hasLibs pkg_descr)
+    let installDirs :: InstallDirTemplates
+        installDirs = combineInstallDirs fromFlagOrDefault
+                        defaultDirs (configInstallDirs cfg)
+
+    -- Check languages and extensions
+    -- TODO: Move this into a helper function.
+    let langlist = nub $ catMaybes $ map defaultLanguage
+                   (allBuildInfo pkg_descr)
+    let langs = unsupportedLanguages comp langlist
+    when (not (null langs)) $
+      die $ "The package " ++ display (packageId pkg_descr0)
+         ++ " requires the following languages which are not "
+         ++ "supported by " ++ display (compilerId comp) ++ ": "
+         ++ intercalate ", " (map display langs)
+    let extlist = nub $ concatMap allExtensions (allBuildInfo pkg_descr)
+    let exts = unsupportedExtensions comp extlist
+    when (not (null exts)) $
+      die $ "The package " ++ display (packageId pkg_descr0)
+         ++ " requires the following language extensions which are not "
+         ++ "supported by " ++ display (compilerId comp) ++ ": "
+         ++ intercalate ", " (map display exts)
+
+    -- Configure known/required programs & external build tools.
+    -- Exclude build-tool deps on "internal" exes in the same package
+    --
+    -- TODO: Factor this into a helper package.
+    let requiredBuildTools =
+          [ buildTool
+          | let exeNames = map exeName (executables pkg_descr)
+          , bi <- allBuildInfo pkg_descr
+          , buildTool@(Dependency (PackageName toolName) reqVer)
+            <- buildTools bi
+          , let isInternal =
+                    toolName `elem` exeNames
+                    -- we assume all internal build-tools are
+                    -- versioned with the package:
+                 && packageVersion pkg_descr `withinRange` reqVer
+          , not isInternal ]
+
+    programsConfig' <-
+          configureAllKnownPrograms (lessVerbose verbosity) programsConfig
+      >>= configureRequiredPrograms verbosity requiredBuildTools
+
+    (pkg_descr', programsConfig'') <-
+      configurePkgconfigPackages verbosity pkg_descr programsConfig'
+
+    -- Compute internal component graph
+    --
+    -- The general idea is that we take a look at all the source level
+    -- components (which may build-depends on each other) and form a graph.
+    -- From there, we build a ComponentLocalBuildInfo for each of the
+    -- components, which lets us actually build each component.
+    buildComponents <-
+      case mkComponentsGraph pkg_descr internalPkgDeps of
+        Left  componentCycle -> reportComponentCycle componentCycle
+        Right comps          ->
+          mkComponentsLocalBuildInfo cfg comp packageDependsIndex pkg_descr
+                                     internalPkgDeps externalPkgDeps
+                                     comps (configConfigurationsFlags cfg)
+
+    -- Decide if we're going to compile with split objects.
+    split_objs :: Bool <-
+       if not (fromFlag $ configSplitObjs cfg)
+            then return False
+            else case compilerFlavor comp of
+                        GHC | compilerVersion comp >= Version [6,5] []
+                          -> return True
+                        GHCJS
+                          -> return True
+                        _ -> do warn verbosity
+                                     ("this compiler does not support " ++
+                                      "--enable-split-objs; ignoring")
+                                return False
+
+    let ghciLibByDefault =
+          case compilerId comp of
+            CompilerId GHC _ ->
+              -- If ghc is non-dynamic, then ghci needs object files,
+              -- so we build one by default.
+              --
+              -- Technically, archive files should be sufficient for ghci,
+              -- but because of GHC bug #8942, it has never been safe to
+              -- rely on them. By the time that bug was fixed, ghci had
+              -- been changed to read shared libraries instead of archive
+              -- files (see next code block).
+              not (GHC.isDynamic comp)
+            CompilerId GHCJS _ ->
+              not (GHCJS.isDynamic comp)
+            _ -> False
+
+    let sharedLibsByDefault
+          | fromFlag (configDynExe cfg) =
+              -- build a shared library if dynamically-linked
+              -- executables are requested
+              True
+          | otherwise = case compilerId comp of
+            CompilerId GHC _ ->
+              -- if ghc is dynamic, then ghci needs a shared
+              -- library, so we build one by default.
+              GHC.isDynamic comp
+            CompilerId GHCJS _ ->
+              GHCJS.isDynamic comp
+            _ -> False
+        withSharedLib_ =
+            -- build shared libraries if required by GHC or by the
+            -- executable linking mode, but allow the user to force
+            -- building only static library archives with
+            -- --disable-shared.
+            fromFlagOrDefault sharedLibsByDefault $ configSharedLib cfg
+        withDynExe_ = fromFlag $ configDynExe cfg
+    when (withDynExe_ && not withSharedLib_) $ warn verbosity $
+           "Executables will use dynamic linking, but a shared library "
+        ++ "is not being built. Linking will fail if any executables "
+        ++ "depend on the library."
+
+    -- The --profiling flag sets the default for both libs and exes,
+    -- but can be overidden by --library-profiling, or the old deprecated
+    -- --executable-profiling flag.
+    let profEnabledLibOnly = configProfLib cfg
+        profEnabledBoth    = fromFlagOrDefault False (configProf cfg)
+        profEnabledLib = fromFlagOrDefault profEnabledBoth profEnabledLibOnly
+        profEnabledExe = fromFlagOrDefault profEnabledBoth (configProfExe cfg)
+
+    -- The --profiling-detail and --library-profiling-detail flags behave
+    -- similarly
+    profDetailLibOnly <- checkProfDetail (configProfLibDetail cfg)
+    profDetailBoth    <- liftM (fromFlagOrDefault ProfDetailDefault)
+                               (checkProfDetail (configProfDetail cfg))
+    let profDetailLib = fromFlagOrDefault profDetailBoth profDetailLibOnly
+        profDetailExe = profDetailBoth
+
+    when (profEnabledExe && not profEnabledLib) $
+      warn verbosity $
+           "Executables will be built with profiling, but library "
+        ++ "profiling is disabled. Linking will fail if any executables "
+        ++ "depend on the library."
+
+    let configCoverage_ =
+          mappend (configCoverage cfg) (configLibCoverage cfg)
+
+        cfg' = cfg { configCoverage = configCoverage_ }
+
+    reloc <-
+       if not (fromFlag $ configRelocatable cfg)
+            then return False
+            else return True
+
+    let lbi = LocalBuildInfo {
+                configFlags         = cfg',
+                flagAssignment      = flags,
+                extraConfigArgs     = [],  -- Currently configure does not
+                                           -- take extra args, but if it
+                                           -- did they would go here.
+                installDirTemplates = installDirs,
+                compiler            = comp,
+                hostPlatform        = compPlatform,
+                buildDir            = buildDir,
+                componentsConfigs   = buildComponents,
+                installedPkgs       = packageDependsIndex,
+                pkgDescrFile        = Nothing,
+                localPkgDescr       = pkg_descr',
+                withPrograms        = programsConfig'',
+                withVanillaLib      = fromFlag $ configVanillaLib cfg,
+                withProfLib         = profEnabledLib,
+                withSharedLib       = withSharedLib_,
+                withDynExe          = withDynExe_,
+                withProfExe         = profEnabledExe,
+                withProfLibDetail   = profDetailLib,
+                withProfExeDetail   = profDetailExe,
+                withOptimization    = fromFlag $ configOptimization cfg,
+                withDebugInfo       = fromFlag $ configDebugInfo cfg,
+                withGHCiLib         = fromFlagOrDefault ghciLibByDefault $
+                                      configGHCiLib cfg,
+                splitObjs           = split_objs,
+                stripExes           = fromFlag $ configStripExes cfg,
+                stripLibs           = fromFlag $ configStripLibs cfg,
+                withPackageDB       = packageDbs,
+                progPrefix          = fromFlag $ configProgPrefix cfg,
+                progSuffix          = fromFlag $ configProgSuffix cfg,
+                relocatable         = reloc
+              }
+
+    when reloc (checkRelocatable verbosity pkg_descr lbi)
+
+    -- TODO: This is not entirely correct, because the dirs may vary
+    -- across libraries/executables
+    let dirs = absoluteInstallDirs pkg_descr lbi NoCopyDest
+        relative = prefixRelativeInstallDirs (packageId pkg_descr) lbi
+
+    unless (isAbsolute (prefix dirs)) $ die $
+        "expected an absolute directory name for --prefix: " ++ prefix dirs
+
+    info verbosity $ "Using " ++ display currentCabalId
+                  ++ " compiled by " ++ display currentCompilerId
+    info verbosity $ "Using compiler: " ++ showCompilerId comp
+    info verbosity $ "Using install prefix: " ++ prefix dirs
+
+    let dirinfo name dir isPrefixRelative =
+          info verbosity $ name ++ " installed in: " ++ dir ++ relNote
+          where relNote = case buildOS of
+                  Windows | not (hasLibs pkg_descr)
+                         && isNothing isPrefixRelative
+                         -> "  (fixed location)"
+                  _      -> ""
+
+    dirinfo "Binaries"         (bindir dirs)     (bindir relative)
+    dirinfo "Libraries"        (libdir dirs)     (libdir relative)
+    dirinfo "Private binaries" (libexecdir dirs) (libexecdir relative)
+    dirinfo "Data files"       (datadir dirs)    (datadir relative)
+    dirinfo "Documentation"    (docdir dirs)     (docdir relative)
+    dirinfo "Configuration files" (sysconfdir dirs) (sysconfdir relative)
+
+    sequence_ [ reportProgram verbosity prog configuredProg
+              | (prog, configuredProg) <- knownPrograms programsConfig'' ]
+
+    return lbi
+
+    where
+      verbosity = fromFlag (configVerbosity cfg)
+
+      checkProfDetail (Flag (ProfDetailOther other)) = do
+        warn verbosity $
+             "Unknown profiling detail level '" ++ other
+          ++ "', using default.\n"
+          ++ "The profiling detail levels are: " ++ intercalate ", "
+             [ name | (name, _, _) <- knownProfDetailLevels ]
+        return (Flag ProfDetailDefault)
+      checkProfDetail other = return other
+
+mkProgramsConfig :: ConfigFlags -> ProgramConfiguration -> ProgramConfiguration
+mkProgramsConfig cfg initialProgramsConfig = programsConfig
+  where
+    programsConfig = userSpecifyArgss (configProgramArgs cfg)
+                   . userSpecifyPaths (configProgramPaths cfg)
+                   . setProgramSearchPath searchpath
+                   $ initialProgramsConfig
+    searchpath     = getProgramSearchPath (initialProgramsConfig)
+                  ++ map ProgramSearchPathDir
+                     (fromNubList $ configProgramPathExtra cfg)
+
+-- -----------------------------------------------------------------------------
+-- Helper functions for configure
+
+-- | Check if the user used any deprecated flags.
+checkDeprecatedFlags :: Verbosity -> ConfigFlags -> IO ()
+checkDeprecatedFlags verbosity cfg = do
+    unless (configProfExe cfg == NoFlag) $ do
+      let enable | fromFlag (configProfExe cfg) = "enable"
+                 | otherwise = "disable"
+      warn verbosity
+        ("The flag --" ++ enable ++ "-executable-profiling is deprecated. "
+         ++ "Please use --" ++ enable ++ "-profiling instead.")
+
+    unless (configLibCoverage cfg == NoFlag) $ do
+      let enable | fromFlag (configLibCoverage cfg) = "enable"
+                 | otherwise = "disable"
+      warn verbosity
+        ("The flag --" ++ enable ++ "-library-coverage is deprecated. "
+         ++ "Please use --" ++ enable ++ "-coverage instead.")
+
+-- | Sanity check: if '--exact-configuration' was given, ensure that the
+-- complete flag assignment was specified on the command line.
+checkExactConfiguration :: GenericPackageDescription -> ConfigFlags -> IO ()
+checkExactConfiguration pkg_descr0 cfg = do
+    when (fromFlagOrDefault False (configExactConfiguration cfg)) $ do
+      let cmdlineFlags = map fst (configConfigurationsFlags cfg)
+          allFlags     = map flagName . genPackageFlags $ pkg_descr0
+          diffFlags    = allFlags \\ cmdlineFlags
+      when (not . null $ diffFlags) $
+        die $ "'--exact-configuration' was given, "
+        ++ "but the following flags were not specified: "
+        ++ intercalate ", " (map show diffFlags)
+
+-- | Create a PackageIndex that makes *any libraries that might be*
+-- defined internally to this package look like installed packages, in
+-- case an executable should refer to any of them as dependencies.
+--
+-- It must be *any libraries that might be* defined rather than the
+-- actual definitions, because these depend on conditionals in the .cabal
+-- file, and we haven't resolved them yet.  finalizePackageDescription
+-- does the resolution of conditionals, and it takes internalPackageSet
+-- as part of its input.
+getInternalPackages :: GenericPackageDescription
+                    -> InstalledPackageIndex
+getInternalPackages pkg_descr0 =
+    let pkg_descr = flattenPackageDescription pkg_descr0
+        mkInternalPackage lib = emptyInstalledPackageInfo {
+            --TODO: should use a per-compiler method to map the source
+            --      package ID into an installed package id we can use
+            --      for the internal package set.  What we do here
+            --      is skeevy, but we're highly unlikely to accidentally
+            --      shadow something legitimate.
+            Installed.installedUnitId = mkUnitId (libName lib),
+            -- NB: we TEMPORARILY set the package name to be the
+            -- library name.  When we actually register, it won't
+            -- look like this; this is just so that internal
+            -- build-depends get resolved correctly.
+            Installed.sourcePackageId = PackageIdentifier (PackageName (libName lib))
+                                            (pkgVersion (package pkg_descr))
+          }
+    in PackageIndex.fromList (map mkInternalPackage (libraries pkg_descr))
+
+
+-- | Returns true if a dependency is satisfiable.  This is to be passed
+-- to finalizePackageDescription.
+dependencySatisfiable
+    :: Bool
+    -> InstalledPackageIndex -- ^ installed set
+    -> InstalledPackageIndex -- ^ internal set
+    -> Map PackageName InstalledPackageInfo -- ^ required dependencies
+    -> (Dependency -> Bool)
+dependencySatisfiable
+    exact_config installedPackageSet internalPackageSet requiredDepsMap
+    d@(Dependency depName _)
+      | exact_config =
+        -- When we're given '--exact-configuration', we assume that all
+        -- dependencies and flags are exactly specified on the command
+        -- line. Thus we only consult the 'requiredDepsMap'. Note that
+        -- we're not doing the version range check, so if there's some
+        -- dependency that wasn't specified on the command line,
+        -- 'finalizePackageDescription' will fail.
+        --
+        -- TODO: mention '--exact-configuration' in the error message
+        -- when this fails?
+        --
+        -- (However, note that internal deps don't have to be
+        -- specified!)
+        (depName `Map.member` requiredDepsMap) || isInternalDep
+
+      | otherwise =
+        -- Normal operation: just look up dependency in the combined
+        -- package index.
+        not . null . PackageIndex.lookupDependency pkgs $ d
+      where
+        -- NB: Prefer the INTERNAL package set
+        pkgs = PackageIndex.merge installedPackageSet internalPackageSet
+        isInternalDep = not . null
+                      $ PackageIndex.lookupDependency internalPackageSet d
+
+-- | Relax the dependencies of this package if needed.
+relaxPackageDeps :: AllowNewer -> GenericPackageDescription
+                 -> GenericPackageDescription
+relaxPackageDeps AllowNewerNone gpd = gpd
+relaxPackageDeps AllowNewerAll  gpd = transformAllBuildDepends relaxAll gpd
+  where
+    relaxAll = \(Dependency pkgName verRange) ->
+      Dependency pkgName (removeUpperBound verRange)
+relaxPackageDeps (AllowNewerSome allowNewerDeps') gpd =
+  transformAllBuildDepends relaxSome gpd
+  where
+    thisPkgName    = packageName gpd
+    allowNewerDeps = mapMaybe f allowNewerDeps'
+
+    f (Setup.AllowNewerDep p) = Just p
+    f (Setup.AllowNewerDepScoped scope p) | scope == thisPkgName = Just p
+                                          | otherwise            = Nothing
+
+    relaxSome = \d@(Dependency depName verRange) ->
+      if depName `elem` allowNewerDeps
+      then Dependency depName (removeUpperBound verRange)
+      else d
+
+-- | Finalize a generic package description.  The workhorse is
+-- 'finalizePackageDescription' but there's a bit of other nattering
+-- about necessary.
+--
+-- TODO: what exactly is the business with @flaggedTests@ and
+-- @flaggedBenchmarks@?
+configureFinalizedPackage
+    :: Verbosity
+    -> ConfigFlags
+    -> [Dependency]
+    -> (Dependency -> Bool) -- ^ tests if a dependency is satisfiable.
+                            -- Might say it's satisfiable even when not.
+    -> Compiler
+    -> Platform
+    -> GenericPackageDescription
+    -> IO (PackageDescription, FlagAssignment)
+configureFinalizedPackage verbosity cfg
+  allConstraints satisfies comp compPlatform pkg_descr0 = do
+    let enableTest t = t { testEnabled = fromFlag (configTests cfg) }
+        flaggedTests = map (\(n, t) -> (n, mapTreeData enableTest t))
+                           (condTestSuites pkg_descr0)
+        enableBenchmark bm = bm { benchmarkEnabled =
+                                     fromFlag (configBenchmarks cfg) }
+        flaggedBenchmarks = map (\(n, bm) ->
+                                  (n, mapTreeData enableBenchmark bm))
+                           (condBenchmarks pkg_descr0)
+        pkg_descr0'' = pkg_descr0 { condTestSuites = flaggedTests
+                                  , condBenchmarks = flaggedBenchmarks }
+
+    (pkg_descr0', flags) <-
+            case finalizePackageDescription
+                   (configConfigurationsFlags cfg)
+                   satisfies
+                   compPlatform
+                   (compilerInfo comp)
+                   allConstraints
+                   pkg_descr0''
+            of Right r -> return r
+               Left missing ->
+                   die $ "Encountered missing dependencies:\n"
+                     ++ (render . nest 4 . sep . punctuate comma
+                                . map (disp . simplifyDependency)
+                                $ missing)
+
+    -- add extra include/lib dirs as specified in cfg
+    -- we do it here so that those get checked too
+    let pkg_descr = addExtraIncludeLibDirs pkg_descr0'
+
+    when (not (null flags)) $
+      info verbosity $ "Flags chosen: "
+                    ++ intercalate ", " [ name ++ "=" ++ display value
+                                        | (FlagName name, value) <- flags ]
+
+    return (pkg_descr, flags)
+  where
+    addExtraIncludeLibDirs pkg_descr =
+        let extraBi = mempty { extraLibDirs = configExtraLibDirs cfg
+                             , extraFrameworkDirs = configExtraFrameworkDirs cfg
+                             , PD.includeDirs = configExtraIncludeDirs cfg}
+            modifyLib l        = l{ libBuildInfo = libBuildInfo l
+                                                   `mappend` extraBi }
+            modifyExecutable e = e{ buildInfo    = buildInfo e
+                                                   `mappend` extraBi}
+        in pkg_descr{ libraries   = modifyLib         `map` libraries pkg_descr
+                    , executables = modifyExecutable  `map`
+                                      executables pkg_descr}
+
+-- | Check for use of Cabal features which require compiler support
+checkCompilerProblems :: Compiler -> PackageDescription -> IO ()
+checkCompilerProblems comp pkg_descr = do
+    unless (renamingPackageFlagsSupported comp ||
+                and [ True
+                    | bi <- allBuildInfo pkg_descr
+                    , _ <- Map.elems (targetBuildRenaming bi)]) $
+        die $ "Your compiler does not support thinning and renaming on "
+           ++ "package flags.  To use this feature you probably must use "
+           ++ "GHC 7.9 or later."
+
+    when (any (not.null.PD.reexportedModules) (PD.libraries pkg_descr)
+          && not (reexportedModulesSupported comp)) $ do
+        die $ "Your compiler does not support module re-exports. To use "
+           ++ "this feature you probably must use GHC 7.9 or later."
+
+-- | Select dependencies for the package.
+configureDependencies
+    :: Verbosity
+    -> InstalledPackageIndex -- ^ internal packages
+    -> InstalledPackageIndex -- ^ installed packages
+    -> Map PackageName InstalledPackageInfo -- ^ required deps
+    -> PackageDescription
+    -> IO ([PackageId], [InstalledPackageInfo])
+configureDependencies verbosity
+  internalPackageSet installedPackageSet requiredDepsMap pkg_descr = do
+    let selectDependencies :: [Dependency] ->
+                              ([FailedDependency], [ResolvedDependency])
+        selectDependencies =
+            partitionEithers
+          . map (selectDependency internalPackageSet installedPackageSet
+                                  requiredDepsMap)
+
+        (failedDeps, allPkgDeps) =
+          selectDependencies (buildDepends pkg_descr)
+
+        internalPkgDeps = [ pkgid
+                          | InternalDependency _ pkgid <- allPkgDeps ]
+        externalPkgDeps = [ pkg
+                          | ExternalDependency _ pkg   <- allPkgDeps ]
+
+    when (not (null internalPkgDeps)
+          && not (newPackageDepsBehaviour pkg_descr)) $
+        die $ "The field 'build-depends: "
+           ++ intercalate ", " (map (display . packageName) internalPkgDeps)
+           ++ "' refers to a library which is defined within the same "
+           ++ "package. To use this feature the package must specify at "
+           ++ "least 'cabal-version: >= 1.8'."
+
+    reportFailedDependencies failedDeps
+    reportSelectedDependencies verbosity allPkgDeps
+
+    return (internalPkgDeps, externalPkgDeps)
+
+-- -----------------------------------------------------------------------------
+-- Configuring package dependencies
+
+reportProgram :: Verbosity -> Program -> Maybe ConfiguredProgram -> IO ()
+reportProgram verbosity prog Nothing
+    = info verbosity $ "No " ++ programName prog ++ " found"
+reportProgram verbosity prog (Just configuredProg)
+    = info verbosity $ "Using " ++ programName prog ++ version ++ location
+    where location = case programLocation configuredProg of
+            FoundOnSystem p -> " found on system at: " ++ p
+            UserSpecified p -> " given by user at: " ++ p
+          version = case programVersion configuredProg of
+            Nothing -> ""
+            Just v  -> " version " ++ display v
+
+hackageUrl :: String
+hackageUrl = "http://hackage.haskell.org/package/"
+
+data ResolvedDependency = ExternalDependency Dependency InstalledPackageInfo
+                        | InternalDependency Dependency PackageId -- should be a
+                                                                      -- lib name
+
+data FailedDependency = DependencyNotExists PackageName
+                      | DependencyNoVersion Dependency
+
+-- | Test for a package dependency and record the version we have installed.
+selectDependency :: InstalledPackageIndex  -- ^ Internally defined packages
+                 -> InstalledPackageIndex  -- ^ Installed packages
+                 -> Map PackageName InstalledPackageInfo
+                    -- ^ Packages for which we have been given specific deps to
+                    -- use
+                 -> Dependency
+                 -> Either FailedDependency ResolvedDependency
+selectDependency internalIndex installedIndex requiredDepsMap
+  dep@(Dependency pkgname vr) =
+  -- If the dependency specification matches anything in the internal package
+  -- index, then we prefer that match to anything in the second.
+  -- For example:
+  --
+  -- Name: MyLibrary
+  -- Version: 0.1
+  -- Library
+  --     ..
+  -- Executable my-exec
+  --     build-depends: MyLibrary
+  --
+  -- We want "build-depends: MyLibrary" always to match the internal library
+  -- even if there is a newer installed library "MyLibrary-0.2".
+  -- However, "build-depends: MyLibrary >= 0.2" should match the installed one.
+  case PackageIndex.lookupPackageName internalIndex pkgname of
+    [(_,[pkg])] | packageVersion pkg `withinRange` vr
+           -> Right $ InternalDependency dep (packageId pkg)
+
+    _      -> case Map.lookup pkgname requiredDepsMap of
+      -- If we know the exact pkg to use, then use it.
+      Just pkginstance -> Right (ExternalDependency dep pkginstance)
+      -- Otherwise we just pick an arbitrary instance of the latest version.
+      Nothing -> case PackageIndex.lookupDependency installedIndex dep of
+        []   -> Left  $ DependencyNotExists pkgname
+        pkgs -> Right $ ExternalDependency dep $
+                case last pkgs of
+                  (_ver, pkginstances) -> head pkginstances
+
+reportSelectedDependencies :: Verbosity
+                           -> [ResolvedDependency] -> IO ()
+reportSelectedDependencies verbosity deps =
+  info verbosity $ unlines
+    [ "Dependency " ++ display (simplifyDependency dep)
+                    ++ ": using " ++ display pkgid
+    | resolved <- deps
+    , let (dep, pkgid) = case resolved of
+            ExternalDependency dep' pkg'   -> (dep', packageId pkg')
+            InternalDependency dep' pkgid' -> (dep', pkgid') ]
+
+reportFailedDependencies :: [FailedDependency] -> IO ()
+reportFailedDependencies []     = return ()
+reportFailedDependencies failed =
+    die (intercalate "\n\n" (map reportFailedDependency failed))
+
+  where
+    reportFailedDependency (DependencyNotExists pkgname) =
+         "there is no version of " ++ display pkgname ++ " installed.\n"
+      ++ "Perhaps you need to download and install it from\n"
+      ++ hackageUrl ++ display pkgname ++ "?"
+
+    reportFailedDependency (DependencyNoVersion dep) =
+        "cannot satisfy dependency " ++ display (simplifyDependency dep) ++ "\n"
+
+-- | List all installed packages in the given package databases.
+getInstalledPackages :: Verbosity -> Compiler
+                     -> PackageDBStack -- ^ The stack of package databases.
+                     -> ProgramConfiguration
+                     -> IO InstalledPackageIndex
+getInstalledPackages verbosity comp packageDBs progconf = do
+  when (null packageDBs) $
+    die $ "No package databases have been specified. If you use "
+       ++ "--package-db=clear, you must follow it with --package-db= "
+       ++ "with 'global', 'user' or a specific file."
+
+  info verbosity "Reading installed packages..."
+  case compilerFlavor comp of
+    GHC   -> GHC.getInstalledPackages verbosity comp packageDBs progconf
+    GHCJS -> GHCJS.getInstalledPackages verbosity packageDBs progconf
+    JHC   -> JHC.getInstalledPackages verbosity packageDBs progconf
+    LHC   -> LHC.getInstalledPackages verbosity packageDBs progconf
+    UHC   -> UHC.getInstalledPackages verbosity comp packageDBs progconf
+    HaskellSuite {} ->
+      HaskellSuite.getInstalledPackages verbosity packageDBs progconf
+    flv -> die $ "don't know how to find the installed packages for "
+              ++ display flv
+
+-- | Like 'getInstalledPackages', but for a single package DB.
+--
+-- NB: Why isn't this always a fall through to 'getInstalledPackages'?
+-- That is because 'getInstalledPackages' performs some sanity checks
+-- on the package database stack in question.  However, when sandboxes
+-- are involved these sanity checks are not desirable.
+getPackageDBContents :: Verbosity -> Compiler
+                     -> PackageDB -> ProgramConfiguration
+                     -> IO InstalledPackageIndex
+getPackageDBContents verbosity comp packageDB progconf = do
+  info verbosity "Reading installed packages..."
+  case compilerFlavor comp of
+    GHC -> GHC.getPackageDBContents verbosity packageDB progconf
+    GHCJS -> GHCJS.getPackageDBContents verbosity packageDB progconf
+    -- For other compilers, try to fall back on 'getInstalledPackages'.
+    _   -> getInstalledPackages verbosity comp [packageDB] progconf
+
+
+-- | A set of files (or directories) that can be monitored to detect when
+-- there might have been a change in the installed packages.
+--
+getInstalledPackagesMonitorFiles :: Verbosity -> Compiler
+                                 -> PackageDBStack
+                                 -> ProgramConfiguration -> Platform
+                                 -> IO [FilePath]
+getInstalledPackagesMonitorFiles verbosity comp packageDBs progconf platform =
+  case compilerFlavor comp of
+    GHC   -> GHC.getInstalledPackagesMonitorFiles
+               verbosity platform progconf packageDBs
+    other -> do
+      warn verbosity $ "don't know how to find change monitoring files for "
+                    ++ "the installed package databases for " ++ display other
+      return []
+
+-- | The user interface specifies the package dbs to use with a combination of
+-- @--global@, @--user@ and @--package-db=global|user|clear|$file@.
+-- This function combines the global/user flag and interprets the package-db
+-- flag into a single package db stack.
+--
+interpretPackageDbFlags :: Bool -> [Maybe PackageDB] -> PackageDBStack
+interpretPackageDbFlags userInstall specificDBs =
+    extra initialStack specificDBs
+  where
+    initialStack | userInstall = [GlobalPackageDB, UserPackageDB]
+                 | otherwise   = [GlobalPackageDB]
+
+    extra dbs' []            = dbs'
+    extra _    (Nothing:dbs) = extra []             dbs
+    extra dbs' (Just db:dbs) = extra (dbs' ++ [db]) dbs
+
+newPackageDepsBehaviourMinVersion :: Version
+newPackageDepsBehaviourMinVersion = Version [1,7,1] []
+
+-- In older cabal versions, there was only one set of package dependencies for
+-- the whole package. In this version, we can have separate dependencies per
+-- target, but we only enable this behaviour if the minimum cabal version
+-- specified is >= a certain minimum. Otherwise, for compatibility we use the
+-- old behaviour.
+newPackageDepsBehaviour :: PackageDescription -> Bool
+newPackageDepsBehaviour pkg =
+   specVersion pkg >= newPackageDepsBehaviourMinVersion
+
+-- We are given both --constraint="foo < 2.0" style constraints and also
+-- specific packages to pick via --dependency="foo=foo-2.0-177d5cdf20962d0581".
+--
+-- When finalising the package we have to take into account the specific
+-- installed deps we've been given, and the finalise function expects
+-- constraints, so we have to translate these deps into version constraints.
+--
+-- But after finalising we then have to make sure we pick the right specific
+-- deps in the end. So we still need to remember which installed packages to
+-- pick.
+combinedConstraints :: [Dependency] ->
+                       [(PackageName, UnitId)] ->
+                       InstalledPackageIndex ->
+                       Either String ([Dependency],
+                                      Map PackageName InstalledPackageInfo)
+combinedConstraints constraints dependencies installedPackages = do
+
+    when (not (null badUnitIds)) $
+      Left $ render $ text "The following package dependencies were requested"
+         $+$ nest 4 (dispDependencies badUnitIds)
+         $+$ text "however the given installed package instance does not exist."
+
+    when (not (null badNames)) $
+      Left $ render $ text "The following package dependencies were requested"
+         $+$ nest 4 (dispDependencies badNames)
+         $+$ text ("however the installed package's name does not match "
+                   ++ "the name given.")
+
+    --TODO: we don't check that all dependencies are used!
+
+    return (allConstraints, idConstraintMap)
+
+  where
+    allConstraints :: [Dependency]
+    allConstraints = constraints
+                  ++ [ thisPackageVersion (packageId pkg)
+                     | (_, _, Just pkg) <- dependenciesPkgInfo ]
+
+    idConstraintMap :: Map PackageName InstalledPackageInfo
+    idConstraintMap = Map.fromList
+                        [ (packageName pkg, pkg)
+                        | (_, _, Just pkg) <- dependenciesPkgInfo ]
+
+    -- The dependencies along with the installed package info, if it exists
+    dependenciesPkgInfo :: [(PackageName, UnitId,
+                             Maybe InstalledPackageInfo)]
+    dependenciesPkgInfo =
+      [ (pkgname, ipkgid, mpkg)
+      | (pkgname, ipkgid) <- dependencies
+      , let mpkg = PackageIndex.lookupUnitId
+                     installedPackages ipkgid
+      ]
+
+    -- If we looked up a package specified by an installed package id
+    -- (i.e. someone has written a hash) and didn't find it then it's
+    -- an error.
+    badUnitIds =
+      [ (pkgname, ipkgid)
+      | (pkgname, ipkgid, Nothing) <- dependenciesPkgInfo ]
+
+    -- If someone has written e.g.
+    -- --dependency="foo=MyOtherLib-1.0-07...5bf30" then they have
+    -- probably made a mistake.
+    badNames =
+      [ (requestedPkgName, ipkgid)
+      | (requestedPkgName, ipkgid, Just pkg) <- dependenciesPkgInfo
+      , let foundPkgName = packageName pkg
+      , requestedPkgName /= foundPkgName ]
+
+    dispDependencies deps =
+      hsep [    text "--dependency="
+             <> quotes (disp pkgname <> char '=' <> disp ipkgid)
+           | (pkgname, ipkgid) <- deps ]
+
+-- -----------------------------------------------------------------------------
+-- Configuring program dependencies
+
+configureRequiredPrograms :: Verbosity -> [Dependency] -> ProgramConfiguration
+                             -> IO ProgramConfiguration
+configureRequiredPrograms verbosity deps conf =
+  foldM (configureRequiredProgram verbosity) conf deps
+
+configureRequiredProgram :: Verbosity -> ProgramConfiguration -> Dependency
+                            -> IO ProgramConfiguration
+configureRequiredProgram verbosity conf
+  (Dependency (PackageName progName) verRange) =
+  case lookupKnownProgram progName conf of
+    Nothing -> die ("Unknown build tool " ++ progName)
+    Just prog
+      -- requireProgramVersion always requires the program have a version
+      -- but if the user says "build-depends: foo" ie no version constraint
+      -- then we should not fail if we cannot discover the program version.
+      | verRange == anyVersion -> do
+          (_, conf') <- requireProgram verbosity prog conf
+          return conf'
+      | otherwise -> do
+          (_, _, conf') <- requireProgramVersion verbosity prog verRange conf
+          return conf'
+
+-- -----------------------------------------------------------------------------
+-- Configuring pkg-config package dependencies
+
+configurePkgconfigPackages :: Verbosity -> PackageDescription
+                           -> ProgramConfiguration
+                           -> IO (PackageDescription, ProgramConfiguration)
+configurePkgconfigPackages verbosity pkg_descr conf
+  | null allpkgs = return (pkg_descr, conf)
+  | otherwise    = do
+    (_, _, conf') <- requireProgramVersion
+                       (lessVerbose verbosity) pkgConfigProgram
+                       (orLaterVersion $ Version [0,9,0] []) conf
+    mapM_ requirePkg allpkgs
+    libs' <- mapM addPkgConfigBILib (libraries pkg_descr)
+    exes' <- mapM addPkgConfigBIExe (executables pkg_descr)
+    tests' <- mapM addPkgConfigBITest (testSuites pkg_descr)
+    benches' <- mapM addPkgConfigBIBench (benchmarks pkg_descr)
+    let pkg_descr' = pkg_descr { libraries = libs', executables = exes',
+                                 testSuites = tests', benchmarks = benches' }
+    return (pkg_descr', conf')
+
+  where
+    allpkgs = concatMap pkgconfigDepends (allBuildInfo pkg_descr)
+    pkgconfig = rawSystemProgramStdoutConf (lessVerbose verbosity)
+                  pkgConfigProgram conf
+
+    requirePkg dep@(Dependency (PackageName pkg) range) = do
+      version <- pkgconfig ["--modversion", pkg]
+                 `catchIO`   (\_ -> die notFound)
+                 `catchExit` (\_ -> die notFound)
+      case simpleParse version of
+        Nothing -> die "parsing output of pkg-config --modversion failed"
+        Just v | not (withinRange v range) -> die (badVersion v)
+               | otherwise                 -> info verbosity (depSatisfied v)
+      where
+        notFound     = "The pkg-config package '" ++ pkg ++ "'"
+                    ++ versionRequirement
+                    ++ " is required but it could not be found."
+        badVersion v = "The pkg-config package '" ++ pkg ++ "'"
+                    ++ versionRequirement
+                    ++ " is required but the version installed on the"
+                    ++ " system is version " ++ display v
+        depSatisfied v = "Dependency " ++ display dep
+                      ++ ": using version " ++ display v
+
+        versionRequirement
+          | isAnyVersion range = ""
+          | otherwise          = " version " ++ display range
+
+    -- Adds pkgconfig dependencies to the build info for a component
+    addPkgConfigBI compBI setCompBI comp = do
+      bi <- pkgconfigBuildInfo (pkgconfigDepends (compBI comp))
+      return $ setCompBI comp (compBI comp `mappend` bi)
+
+    -- Adds pkgconfig dependencies to the build info for a library
+    addPkgConfigBILib = addPkgConfigBI libBuildInfo $
+                          \lib bi -> lib { libBuildInfo = bi }
+
+    -- Adds pkgconfig dependencies to the build info for an executable
+    addPkgConfigBIExe = addPkgConfigBI buildInfo $
+                          \exe bi -> exe { buildInfo = bi }
+
+    -- Adds pkgconfig dependencies to the build info for a test suite
+    addPkgConfigBITest = addPkgConfigBI testBuildInfo $
+                          \test bi -> test { testBuildInfo = bi }
+
+    -- Adds pkgconfig dependencies to the build info for a benchmark
+    addPkgConfigBIBench = addPkgConfigBI benchmarkBuildInfo $
+                          \bench bi -> bench { benchmarkBuildInfo = bi }
+
+    pkgconfigBuildInfo :: [Dependency] -> IO BuildInfo
+    pkgconfigBuildInfo []      = return Mon.mempty
+    pkgconfigBuildInfo pkgdeps = do
+      let pkgs = nub [ display pkg | Dependency pkg _ <- pkgdeps ]
+      ccflags <- pkgconfig ("--cflags" : pkgs)
+      ldflags <- pkgconfig ("--libs"   : pkgs)
+      return (ccLdOptionsBuildInfo (words ccflags) (words ldflags))
+
+-- | Makes a 'BuildInfo' from C compiler and linker flags.
+--
+-- This can be used with the output from configuration programs like pkg-config
+-- and similar package-specific programs like mysql-config, freealut-config etc.
+-- For example:
+--
+-- > ccflags <- rawSystemProgramStdoutConf verbosity prog conf ["--cflags"]
+-- > ldflags <- rawSystemProgramStdoutConf verbosity prog conf ["--libs"]
+-- > return (ccldOptionsBuildInfo (words ccflags) (words ldflags))
+--
+ccLdOptionsBuildInfo :: [String] -> [String] -> BuildInfo
+ccLdOptionsBuildInfo cflags ldflags =
+  let (includeDirs',  cflags')   = partition ("-I" `isPrefixOf`) cflags
+      (extraLibs',    ldflags')  = partition ("-l" `isPrefixOf`) ldflags
+      (extraLibDirs', ldflags'') = partition ("-L" `isPrefixOf`) ldflags'
+  in mempty {
+       PD.includeDirs  = map (drop 2) includeDirs',
+       PD.extraLibs    = map (drop 2) extraLibs',
+       PD.extraLibDirs = map (drop 2) extraLibDirs',
+       PD.ccOptions    = cflags',
+       PD.ldOptions    = ldflags''
+     }
+
+-- -----------------------------------------------------------------------------
+-- Determining the compiler details
+
+configCompilerAuxEx :: ConfigFlags
+                    -> IO (Compiler, Platform, ProgramConfiguration)
+configCompilerAuxEx cfg = configCompilerEx (flagToMaybe $ configHcFlavor cfg)
+                                           (flagToMaybe $ configHcPath cfg)
+                                           (flagToMaybe $ configHcPkg cfg)
+                                           programsConfig
+                                           (fromFlag (configVerbosity cfg))
+  where
+    programsConfig = mkProgramsConfig cfg defaultProgramConfiguration
+
+configCompilerEx :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath
+                 -> ProgramConfiguration -> Verbosity
+                 -> IO (Compiler, Platform, ProgramConfiguration)
+configCompilerEx Nothing _ _ _ _ = die "Unknown compiler"
+configCompilerEx (Just hcFlavor) hcPath hcPkg conf verbosity = do
+  (comp, maybePlatform, programsConfig) <- case hcFlavor of
+    GHC   -> GHC.configure  verbosity hcPath hcPkg conf
+    GHCJS -> GHCJS.configure verbosity hcPath hcPkg conf
+    JHC   -> JHC.configure  verbosity hcPath hcPkg conf
+    LHC   -> do (_, _, ghcConf) <- GHC.configure  verbosity Nothing hcPkg conf
+                LHC.configure  verbosity hcPath Nothing ghcConf
+    UHC   -> UHC.configure  verbosity hcPath hcPkg conf
+    HaskellSuite {} -> HaskellSuite.configure verbosity hcPath hcPkg conf
+    _    -> die "Unknown compiler"
+  return (comp, fromMaybe buildPlatform maybePlatform, programsConfig)
+
+-- Ideally we would like to not have separate configCompiler* and
+-- configCompiler*Ex sets of functions, but there are many custom setup scripts
+-- in the wild that are using them, so the versions with old types are kept for
+-- backwards compatibility. Platform was added to the return triple in 1.18.
+
+{-# DEPRECATED configCompiler
+    "'configCompiler' is deprecated. Use 'configCompilerEx' instead." #-}
+configCompiler :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath
+               -> ProgramConfiguration -> Verbosity
+               -> IO (Compiler, ProgramConfiguration)
+configCompiler mFlavor hcPath hcPkg conf verbosity =
+  fmap (\(a,_,b) -> (a,b)) $ configCompilerEx mFlavor hcPath hcPkg conf verbosity
+
+{-# DEPRECATED configCompilerAux
+    "configCompilerAux is deprecated. Use 'configCompilerAuxEx' instead." #-}
+configCompilerAux :: ConfigFlags
+                  -> IO (Compiler, ProgramConfiguration)
+configCompilerAux = fmap (\(a,_,b) -> (a,b)) . configCompilerAuxEx
+
+-- -----------------------------------------------------------------------------
+-- Making the internal component graph
+
+-- | Given the package description and the set of package names which
+-- are considered internal (the current package name and any internal
+-- libraries are considered internal), create a graph of dependencies
+-- between the components.  This is NOT necessarily the build order
+-- (although it is in the absence of Backpack.)
+--
+-- TODO: tighten up the type of 'internalPkgDeps'
+mkComponentsGraph :: PackageDescription
+                  -> [PackageId]
+                  -> Either [ComponentName]
+                            [(Component, [ComponentName])]
+mkComponentsGraph pkg_descr internalPkgDeps =
+    let graph = [ (c, componentName c, componentDeps c)
+                | c <- pkgEnabledComponents pkg_descr ]
+     in case checkComponentsCyclic graph of
+          Just ccycle -> Left  [ cname | (_,cname,_) <- ccycle ]
+          Nothing     -> Right [ (c, cdeps) | (c, _, cdeps) <- topSortFromEdges graph ]
+  where
+    -- The dependencies for the given component
+    componentDeps component =
+         [ CExeName toolname | Dependency (PackageName toolname) _
+                               <- buildTools bi
+                             , toolname `elem` map exeName
+                               (executables pkg_descr) ]
+
+      ++ [ CLibName toolname | Dependency pkgname@(PackageName toolname) _
+                               <- targetBuildDepends bi
+                             , pkgname `elem` map packageName internalPkgDeps ]
+      where
+        bi = componentBuildInfo component
+
+reportComponentCycle :: [ComponentName] -> IO a
+reportComponentCycle cnames =
+    die $ "Components in the package depend on each other in a cyclic way:\n  "
+       ++ intercalate " depends on "
+            [ "'" ++ showComponentName cname ++ "'"
+            | cname <- cnames ++ [head cnames] ]
+
+-- | This method computes a default, "good enough" 'ComponentId'
+-- for a package.  The intent is that cabal-install (or the user) will
+-- specify a more detailed IPID via the @--ipid@ flag if necessary.
+computeComponentId
+    :: Flag String
+    -> PackageIdentifier
+    -> ComponentName
+    -- TODO: careful here!
+    -> [ComponentId] -- IPIDs of the component dependencies
+    -> FlagAssignment
+    -> ComponentId
+computeComponentId mb_explicit pid cname dep_ipids flagAssignment = do
+    -- show is found to be faster than intercalate and then replacement of
+    -- special character used in intercalating. We cannot simply hash by
+    -- doubly concating list, as it just flatten out the nested list, so
+    -- different sources can produce same hash
+    let hash = hashToBase62 $
+                -- For safety, include the package + version here
+                -- for GHC 7.10, where just the hash is used as
+                -- the package key
+                         display pid
+                      ++ show dep_ipids
+                      ++ show flagAssignment
+        generated_base = display pid ++ "-" ++ hash
+        explicit_base cid0 = fromPathTemplate (InstallDirs.substPathTemplate env
+                                                    (toPathTemplate cid0))
+            -- Hack to reuse install dirs machinery
+            -- NB: no real IPID available at this point
+          where env = packageTemplateEnv pid (mkUnitId "")
+        actual_base = case mb_explicit of
+                        Flag cid0 -> explicit_base cid0
+                        NoFlag -> generated_base
+    ComponentId $ actual_base
+                    ++ (case componentNameString (pkgName pid) cname of
+                            Nothing -> ""
+                            Just s -> "-" ++ s)
+
+hashToBase62 :: String -> String
+hashToBase62 s = showFingerprint $ fingerprintString s
+  where
+    showIntAtBase62 x = showIntAtBase 62 representBase62 x ""
+    representBase62 x
+        | x < 10 = chr (48 + x)
+        | x < 36 = chr (65 + x - 10)
+        | x < 62 = chr (97 + x - 36)
+        | otherwise = '@'
+    showFingerprint (Fingerprint a b) = showIntAtBase62 a ++ showIntAtBase62 b
+
+-- | Computes the package name for a library.  If this is the public
+-- library, it will just be the original package name; otherwise,
+-- it will be a munged package name recording the original package
+-- name as well as the name of the internal library.
+--
+-- A lot of tooling in the Haskell ecosystem assumes that if something
+-- is installed to the package database with the package name 'foo',
+-- then it actually is an entry for the (only public) library in package
+-- 'foo'.  With internal packages, this is not necessarily true:
+-- a public library as well as arbitrarily many internal libraries may
+-- come from the same package.  To prevent tools from getting confused
+-- in this case, the package name of these internal libraries is munged
+-- so that they do not conflict the public library proper.
+--
+-- We munge into a reserved namespace, "z-", and encode both the
+-- component name and the package name of an internal library using the
+-- following format:
+--
+--      compat-pkg-name ::= "z-" package-name "-z-" library-name
+--
+-- where package-name and library-name have "-" ( "z" + ) "-"
+-- segments encoded by adding an extra "z".
+--
+-- When we have the public library, the compat-pkg-name is just the
+-- package-name, no surprises there!
+--
+computeCompatPackageName :: PackageName -> ComponentName -> PackageName
+computeCompatPackageName pkg_name cname
+    | Just cname_str <- componentNameString pkg_name cname
+    = let zdashcode s = go s (Nothing :: Maybe Int) []
+            where go [] _ r = reverse r
+                  go ('-':z) (Just n) r | n > 0 = go z (Just 0) ('-':'z':r)
+                  go ('-':z) _        r = go z (Just 0) ('-':r)
+                  go ('z':z) (Just n) r = go z (Just (n+1)) ('z':r)
+                  go (c:z)   _        r = go z Nothing (c:r)
+      in PackageName $ "z-" ++ zdashcode (display pkg_name)
+                   ++ "-z-" ++ zdashcode cname_str
+    | otherwise
+    = pkg_name
+
+-- | In GHC 8.0, the string we pass to GHC to use for symbol
+-- names for a package can be an arbitrary, IPID-compatible string.
+-- However, prior to GHC 8.0 there are some restrictions on what
+-- format this string can be (due to how ghc-pkg parsed the key):
+--
+--      1. In GHC 7.10, the string had either be of the form
+--      foo_ABCD, where foo is a non-semantic alphanumeric/hyphenated
+--      prefix and ABCD is two base-64 encoded 64-bit integers,
+--      or a GHC 7.8 style identifier.
+--
+--      2. In GHC 7.8, the string had to be a valid package identifier
+--      like foo-0.1.
+--
+-- So, the problem is that Cabal, in general, has a general IPID,
+-- but needs to figure out a package key / package ID that the
+-- old ghc-pkg will actually accept.  But there's an EVERY WORSE
+-- problem: if ghc-pkg decides to parse an identifier foo-0.1-xxx
+-- as if it were a package identifier, which means it will SILENTLY
+-- DROP the "xxx" (because it's a tag, and Cabal does not allow tags.)
+-- So we must CONNIVE to ensure that we don't pick something that
+-- looks like this.
+--
+-- So this function attempts to define a mapping into the old formats.
+--
+-- The mapping for GHC 7.8 and before:
+--
+--      * We use the *compatibility* package name and version.  For
+--        public libraries this is just the package identifier; for
+--        internal libraries, it's something like "z-pkgname-z-libname-0.1".
+--        See 'computeCompatPackageName' for more details.
+--
+-- The mapping for GHC 7.10:
+--
+--      * For CLibName:
+--          If the IPID is of the form foo-0.1-ABCDEF where foo_ABCDEF would
+--          validly parse as a package key, we pass "ABCDEF".  (NB: not
+--          all hashes parse this way, because GHC 7.10 mandated that
+--          these hashes be two base-62 encoded 64 bit integers),
+--          but hashes that Cabal generated using 'computeComponentId'
+--          are guaranteed to have this form.
+--
+--          If it is not of this form, we rehash the IPID into the
+--          correct form and pass that.
+--
+--      * For sub-components, we rehash the IPID into the correct format
+--        and pass that.
+--
+computeCompatPackageKey
+    :: Compiler
+    -> PackageName
+    -> Version
+    -> UnitId
+    -> String
+computeCompatPackageKey comp pkg_name pkg_version (SimpleUnitId (ComponentId str))
+    | not (packageKeySupported comp) =
+        display pkg_name ++ "-" ++ display pkg_version
+    | not (unifiedIPIDRequired comp) =
+        let mb_verbatim_key
+                = case simpleParse str :: Maybe PackageId of
+                    -- Something like 'foo-0.1', use it verbatim.
+                    -- (NB: hash tags look like tags, so they are parsed,
+                    -- so the extra equality check tests if a tag was dropped.)
+                    Just pid0 | display pid0 == str -> Just str
+                    _ -> Nothing
+            mb_truncated_key
+                = let cand = reverse (takeWhile isAlphaNum (reverse str))
+                  in if length cand == 22 && all isAlphaNum cand
+                        then Just cand
+                        else Nothing
+            rehashed_key = hashToBase62 str
+        in fromMaybe rehashed_key (mb_verbatim_key `mplus` mb_truncated_key)
+    | otherwise = str
+
+
+topSortFromEdges :: Ord key => [(node, key, [key])]
+                            -> [(node, key, [key])]
+topSortFromEdges es =
+    let (graph, vertexToNode, _) = graphFromEdges es
+    in reverse (map vertexToNode (topSort graph))
+
+mkComponentsLocalBuildInfo :: ConfigFlags
+                           -> Compiler
+                           -> InstalledPackageIndex
+                           -> PackageDescription
+                           -> [PackageId] -- internal package deps
+                           -> [InstalledPackageInfo] -- external package deps
+                           -> [(Component, [ComponentName])]
+                           -> FlagAssignment
+                           -> IO [(ComponentLocalBuildInfo,
+                                   [UnitId])]
+mkComponentsLocalBuildInfo cfg comp installedPackages pkg_descr
+                           internalPkgDeps externalPkgDeps
+                           graph flagAssignment =
+    foldM go [] graph
+  where
+    go z (component, dep_cnames) = do
+        clbi <- componentLocalBuildInfo z component
+        -- NB: We want to preserve cdeps because it contains extra
+        -- information like build-tools ordering
+        let dep_uids = [ componentUnitId dep_clbi
+                       | cname <- dep_cnames
+                       -- Being in z relies on topsort!
+                       , (dep_clbi, _) <- z
+                       , componentLocalName dep_clbi == cname ]
+        return ((clbi, dep_uids):z)
+
+    -- The allPkgDeps contains all the package deps for the whole package
+    -- but we need to select the subset for this specific component.
+    -- we just take the subset for the package names this component
+    -- needs. Note, this only works because we cannot yet depend on two
+    -- versions of the same package.
+    componentLocalBuildInfo :: [(ComponentLocalBuildInfo, [UnitId])]
+                            -> Component -> IO ComponentLocalBuildInfo
+    componentLocalBuildInfo internalComps component =
+      case component of
+      CLib lib -> do
+        let exports = map (\n -> Installed.ExposedModule n Nothing)
+                          (PD.exposedModules lib)
+            mb_reexports = resolveModuleReexports installedPackages
+                                                  (packageId pkg_descr)
+                                                  uid
+                                                  externalPkgDeps lib
+        reexports <- case mb_reexports of
+            Left problems -> reportModuleReexportProblems problems
+            Right r -> return r
+
+        return LibComponentLocalBuildInfo {
+          componentPackageDeps = cpds,
+          componentUnitId = uid,
+          componentLocalName = componentName component,
+          componentIsPublic = libName lib == display (packageName (package pkg_descr)),
+          componentCompatPackageKey = compat_key,
+          componentCompatPackageName = compat_name,
+          componentIncludes = includes,
+          componentExposedModules = exports ++ reexports
+        }
+      CExe _ ->
+        return ExeComponentLocalBuildInfo {
+          componentUnitId = uid,
+          componentLocalName = componentName component,
+          componentPackageDeps = cpds,
+          componentIncludes = includes
+        }
+      CTest _ ->
+        return TestComponentLocalBuildInfo {
+          componentUnitId = uid,
+          componentLocalName = componentName component,
+          componentPackageDeps = cpds,
+          componentIncludes = includes
+        }
+      CBench _ ->
+        return BenchComponentLocalBuildInfo {
+          componentUnitId = uid,
+          componentLocalName = componentName component,
+          componentPackageDeps = cpds,
+          componentIncludes = includes
+        }
+      where
+
+        -- TODO configIPID should have name changed
+        cid = computeComponentId (configIPID cfg) (package pkg_descr)
+                (componentName component)
+                (getDeps (componentName component))
+                flagAssignment
+        uid = SimpleUnitId cid
+        PackageIdentifier pkg_name pkg_ver = package pkg_descr
+        compat_name = computeCompatPackageName pkg_name (componentName component)
+        compat_key = computeCompatPackageKey comp compat_name pkg_ver uid
+
+        bi = componentBuildInfo component
+
+        lookupInternalPkg :: PackageId -> UnitId
+        lookupInternalPkg pkgid = do
+            let matcher (clbi, _)
+                    | CLibName str <- componentLocalName clbi
+                    , str == display (pkgName pkgid)
+                    = Just (componentUnitId clbi)
+                matcher _ = Nothing
+            case catMaybes (map matcher internalComps) of
+                [x] -> x
+                _ -> error "lookupInternalPkg"
+
+        cpds = if newPackageDepsBehaviour pkg_descr
+               then dedup $
+                    [ (Installed.installedUnitId pkg, packageId pkg)
+                    | pkg <- selectSubset bi externalPkgDeps ]
+                 ++ [ (lookupInternalPkg pkgid, pkgid)
+                    | pkgid <- selectSubset bi internalPkgDeps ]
+               else [ (Installed.installedUnitId pkg, packageId pkg)
+                    | pkg <- externalPkgDeps ]
+        includes = map (\(i,p) -> (i,lookupRenaming p cprns)) cpds
+        cprns = if newPackageDepsBehaviour pkg_descr
+                then targetBuildRenaming bi
+                else Map.empty
+
+    dedup = Map.toList . Map.fromList
+
+    -- TODO: this should include internal deps too
+    getDeps :: ComponentName -> [ComponentId]
+    getDeps cname =
+      let externalPkgs
+            = maybe [] (\lib -> selectSubset (componentBuildInfo lib)
+                                             externalPkgDeps)
+                       (lookupComponent pkg_descr cname)
+      in map Installed.installedComponentId externalPkgs
+
+    selectSubset :: Package pkg => BuildInfo -> [pkg] -> [pkg]
+    selectSubset bi pkgs =
+        [ pkg | pkg <- pkgs, packageName pkg `elem` names bi ]
+
+    names :: BuildInfo -> [PackageName]
+    names bi = [ name | Dependency name _ <- targetBuildDepends bi ]
+
+-- | Given the author-specified re-export declarations from the .cabal file,
+-- resolve them to the form that we need for the package database.
+--
+-- An invariant of the package database is that we always link the re-export
+-- directly to its original defining location (rather than indirectly via a
+-- chain of re-exporting packages).
+--
+resolveModuleReexports :: InstalledPackageIndex
+                       -> PackageId
+                       -> UnitId
+                       -> [InstalledPackageInfo]
+                       -> Library
+                       -> Either [(ModuleReexport, String)] -- errors
+                                 [Installed.ExposedModule] -- ok
+resolveModuleReexports installedPackages srcpkgid key externalPkgDeps lib =
+    case partitionEithers
+         (map resolveModuleReexport (PD.reexportedModules lib)) of
+      ([],  ok) -> Right ok
+      (errs, _) -> Left  errs
+  where
+    -- A mapping from visible module names to their original defining
+    -- module name.  We also record the package name of the package which
+    -- *immediately* provided the module (not the original) to handle if the
+    -- user explicitly says which build-depends they want to reexport from.
+    visibleModules :: Map ModuleName [(PackageName, Installed.ExposedModule)]
+    visibleModules =
+      Map.fromListWith (++) $
+        [ (Installed.exposedName exposedModule, [(exportingPackageName,
+                                                  exposedModule)])
+          -- The package index here contains all the indirect deps of the
+          -- package we're configuring, but we want just the direct deps
+        | let directDeps = Set.fromList
+                           (map Installed.installedUnitId externalPkgDeps)
+        , pkg <- PackageIndex.allPackages installedPackages
+        , Installed.installedUnitId pkg `Set.member` directDeps
+        , let exportingPackageName = packageName pkg
+        , exposedModule <- visibleModuleDetails pkg
+        ]
+     ++ [ (visibleModuleName, [(exportingPackageName, exposedModule)])
+        | visibleModuleName <- PD.exposedModules lib
+                            ++ otherModules (libBuildInfo lib)
+        , let exportingPackageName = packageName srcpkgid
+              definingModuleName   = visibleModuleName
+              definingPackageId    = key
+              originalModule = Module definingPackageId
+                                      definingModuleName
+              exposedModule  = Installed.ExposedModule visibleModuleName
+                                                       (Just originalModule)
+        ]
+
+    -- All the modules exported from this package and their defining name and
+    -- package (either defined here in this package or re-exported from some
+    -- other package).  Return an ExposedModule because we want to hold onto
+    -- signature information.
+    visibleModuleDetails :: InstalledPackageInfo -> [Installed.ExposedModule]
+    visibleModuleDetails pkg = do
+        exposedModule <- Installed.exposedModules pkg
+        case Installed.exposedReexport exposedModule of
+        -- The first case is the modules actually defined in this package.
+        -- In this case the reexport will point to this package.
+            Nothing -> return exposedModule {
+              Installed.exposedReexport =
+                 Just (Module
+                       (Installed.installedUnitId pkg)
+                       (Installed.exposedName exposedModule)) }
+        -- On the other hand, a visible module might actually be itself
+        -- a re-export! In this case, the re-export info for the package
+        -- doing the re-export will point us to the original defining
+        -- module name and package, so we can reuse the entry.
+            Just _ -> return exposedModule
+
+    resolveModuleReexport reexport@ModuleReexport {
+         moduleReexportOriginalPackage = moriginalPackageName,
+         moduleReexportOriginalName    = originalName,
+         moduleReexportName            = newName
+      } =
+
+      let filterForSpecificPackage =
+            case moriginalPackageName of
+              Nothing                  -> id
+              Just originalPackageName ->
+                filter (\(pkgname, _) -> pkgname == originalPackageName)
+
+          matches = filterForSpecificPackage
+                      (Map.findWithDefault [] originalName visibleModules)
+      in
+      case (matches, moriginalPackageName) of
+        ((_, exposedModule):rest, _)
+          -- TODO: Refine this check for signatures
+          | all (\(_, exposedModule') ->
+                  Installed.exposedReexport exposedModule
+                  == Installed.exposedReexport exposedModule') rest
+           -> Right exposedModule { Installed.exposedName = newName }
+
+        ([], Just originalPackageName)
+           -> Left $ (,) reexport
+                   $ "The package " ++ display originalPackageName
+                  ++ " does not export a module " ++ display originalName
+
+        ([], Nothing)
+           -> Left $ (,) reexport
+                   $ "The module " ++ display originalName
+                  ++ " is not exported by any suitable package (this package "
+                  ++ "itself nor any of its 'build-depends' dependencies)."
+
+        (ms, _)
+           -> Left $ (,) reexport
+                   $ "The module " ++ display originalName ++ " is exported "
+                  ++ "by more than one package ("
+                  ++ intercalate ", " [ display pkgname | (pkgname,_) <- ms ]
+                  ++ ") and so the re-export is ambiguous. The ambiguity can "
+                  ++ "be resolved by qualifying by the package name. The "
+                  ++ "syntax is 'packagename:moduleName [as newname]'."
+
+        -- Note: if in future Cabal allows directly depending on multiple
+        -- instances of the same package (e.g. backpack) then an additional
+        -- ambiguity case is possible here: (_, Just originalPackageName)
+        -- with the module being ambiguous despite being qualified by a
+        -- package name. Presumably by that time we'll have a mechanism to
+        -- qualify the instance we're referring to.
+
+reportModuleReexportProblems :: [(ModuleReexport, String)] -> IO a
+reportModuleReexportProblems reexportProblems =
+  die $ unlines
+    [ "Problem with the module re-export '" ++ display reexport ++ "': " ++ msg
+    | (reexport, msg) <- reexportProblems ]
+
+-- -----------------------------------------------------------------------------
+-- Testing C lib and header dependencies
+
+-- Try to build a test C program which includes every header and links every
+-- lib. If that fails, try to narrow it down by preprocessing (only) and linking
+-- with individual headers and libs.  If none is the obvious culprit then give a
+-- generic error message.
+-- TODO: produce a log file from the compiler errors, if any.
+checkForeignDeps :: PackageDescription -> LocalBuildInfo -> Verbosity -> IO ()
+checkForeignDeps pkg lbi verbosity = do
+  ifBuildsWith allHeaders (commonCcArgs ++ makeLdArgs allLibs) -- I'm feeling
+                                                               -- lucky
+           (return ())
+           (do missingLibs <- findMissingLibs
+               missingHdr  <- findOffendingHdr
+               explainErrors missingHdr missingLibs)
+      where
+        allHeaders = collectField PD.includes
+        allLibs    = collectField PD.extraLibs
+
+        ifBuildsWith headers args success failure = do
+            ok <- builds (makeProgram headers) args
+            if ok then success else failure
+
+        findOffendingHdr =
+            ifBuildsWith allHeaders ccArgs
+                         (return Nothing)
+                         (go . tail . inits $ allHeaders)
+            where
+              go [] = return Nothing       -- cannot happen
+              go (hdrs:hdrsInits) =
+                    -- Try just preprocessing first
+                    ifBuildsWith hdrs cppArgs
+                      -- If that works, try compiling too
+                      (ifBuildsWith hdrs ccArgs
+                        (go hdrsInits)
+                        (return . Just . Right . last $ hdrs))
+                      (return . Just . Left . last $ hdrs)
+
+              cppArgs = "-E":commonCppArgs -- preprocess only
+              ccArgs  = "-c":commonCcArgs  -- don't try to link
+
+        findMissingLibs = ifBuildsWith [] (makeLdArgs allLibs)
+                                       (return [])
+                                       (filterM (fmap not . libExists) allLibs)
+
+        libExists lib = builds (makeProgram []) (makeLdArgs [lib])
+
+        commonCppArgs = platformDefines lbi
+                     -- TODO: This is a massive hack, to work around the
+                     -- fact that the test performed here should be
+                     -- PER-component (c.f. the "I'm Feeling Lucky"; we
+                     -- should NOT be glomming everything together.)
+                     ++ [ "-I" ++ buildDir lbi </> "autogen" ]
+                     ++ [ "-I" ++ dir | dir <- collectField PD.includeDirs ]
+                     ++ ["-I."]
+                     ++ collectField PD.cppOptions
+                     ++ collectField PD.ccOptions
+                     ++ [ "-I" ++ dir
+                        | dep <- deps
+                        , dir <- Installed.includeDirs dep ]
+                     ++ [ opt
+                        | dep <- deps
+                        , opt <- Installed.ccOptions dep ]
+
+        commonCcArgs  = commonCppArgs
+                     ++ collectField PD.ccOptions
+                     ++ [ opt
+                        | dep <- deps
+                        , opt <- Installed.ccOptions dep ]
+
+        commonLdArgs  = [ "-L" ++ dir | dir <- collectField PD.extraLibDirs ]
+                     ++ collectField PD.ldOptions
+                     ++ [ "-L" ++ dir
+                        | dep <- deps
+                        , dir <- Installed.libraryDirs dep ]
+                     --TODO: do we also need dependent packages' ld options?
+        makeLdArgs libs = [ "-l"++lib | lib <- libs ] ++ commonLdArgs
+
+        makeProgram hdrs = unlines $
+                           [ "#include \""  ++ hdr ++ "\"" | hdr <- hdrs ] ++
+                           ["int main(int argc, char** argv) { return 0; }"]
+
+        collectField f = concatMap f allBi
+        allBi = allBuildInfo pkg
+        deps = PackageIndex.topologicalOrder (installedPkgs lbi)
+
+        builds program args = do
+            tempDir <- getTemporaryDirectory
+            withTempFile tempDir ".c" $ \cName cHnd ->
+              withTempFile tempDir "" $ \oNname oHnd -> do
+                hPutStrLn cHnd program
+                hClose cHnd
+                hClose oHnd
+                _ <- rawSystemProgramStdoutConf verbosity
+                  gccProgram (withPrograms lbi) (cName:"-o":oNname:args)
+                return True
+           `catchIO`   (\_ -> return False)
+           `catchExit` (\_ -> return False)
+
+        explainErrors Nothing [] = return () -- should be impossible!
+        explainErrors _ _
+           | isNothing . lookupProgram gccProgram . withPrograms $ lbi
+
+                              = die $ unlines $
+              [ "No working gcc",
+                  "This package depends on foreign library but we cannot "
+               ++ "find a working C compiler. If you have it in a "
+               ++ "non-standard location you can use the --with-gcc "
+               ++ "flag to specify it." ]
+
+        explainErrors hdr libs = die $ unlines $
+             [ if plural
+                 then "Missing dependencies on foreign libraries:"
+                 else "Missing dependency on a foreign library:"
+             | missing ]
+          ++ case hdr of
+               Just (Left h) -> ["* Missing (or bad) header file: " ++ h ]
+               _             -> []
+          ++ case libs of
+               []    -> []
+               [lib] -> ["* Missing C library: " ++ lib]
+               _     -> ["* Missing C libraries: " ++ intercalate ", " libs]
+          ++ [if plural then messagePlural else messageSingular | missing]
+          ++ case hdr of
+               Just (Left  _) -> [ headerCppMessage ]
+               Just (Right h) -> [ (if missing then "* " else "")
+                                   ++ "Bad header file: " ++ h
+                                 , headerCcMessage ]
+               _              -> []
+
+          where
+            plural  = length libs >= 2
+            -- Is there something missing? (as opposed to broken)
+            missing = not (null libs)
+                   || case hdr of Just (Left _) -> True; _ -> False
+
+        messageSingular =
+             "This problem can usually be solved by installing the system "
+          ++ "package that provides this library (you may need the "
+          ++ "\"-dev\" version). If the library is already installed "
+          ++ "but in a non-standard location then you can use the flags "
+          ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "
+          ++ "where it is."
+        messagePlural =
+             "This problem can usually be solved by installing the system "
+          ++ "packages that provide these libraries (you may need the "
+          ++ "\"-dev\" versions). If the libraries are already installed "
+          ++ "but in a non-standard location then you can use the flags "
+          ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "
+          ++ "where they are."
+        headerCppMessage =
+             "If the header file does exist, it may contain errors that "
+          ++ "are caught by the C compiler at the preprocessing stage. "
+          ++ "In this case you can re-run configure with the verbosity "
+          ++ "flag -v3 to see the error messages."
+        headerCcMessage =
+             "The header file contains a compile error. "
+          ++ "You can re-run configure with the verbosity flag "
+          ++ "-v3 to see the error messages from the C compiler."
+
+-- | Output package check warnings and errors. Exit if any errors.
+checkPackageProblems :: Verbosity
+                     -> GenericPackageDescription
+                     -> PackageDescription
+                     -> IO ()
+checkPackageProblems verbosity gpkg pkg = do
+  ioChecks      <- checkPackageFiles pkg "."
+  let pureChecks = checkPackage gpkg (Just pkg)
+      errors   = [ e | PackageBuildImpossible e <- pureChecks ++ ioChecks ]
+      warnings = [ w | PackageBuildWarning    w <- pureChecks ++ ioChecks ]
+  if null errors
+    then mapM_ (warn verbosity) warnings
+    else die (intercalate "\n\n" errors)
+
+-- | Preform checks if a relocatable build is allowed
+checkRelocatable :: Verbosity
+                 -> PackageDescription
+                 -> LocalBuildInfo
+                 -> IO ()
+checkRelocatable verbosity pkg lbi
+    = sequence_ [ checkOS
+                , checkCompiler
+                , packagePrefixRelative
+                , depsPrefixRelative
+                ]
+  where
+    -- Check if the OS support relocatable builds.
+    --
+    -- If you add new OS' to this list, and your OS supports dynamic libraries
+    -- and RPATH, make sure you add your OS to RPATH-support list of:
+    -- Distribution.Simple.GHC.getRPaths
+    checkOS
+        = unless (os `elem` [ OSX, Linux ])
+        $ die $ "Operating system: " ++ display os ++
+                ", does not support relocatable builds"
+      where
+        (Platform _ os) = hostPlatform lbi
+
+    -- Check if the Compiler support relocatable builds
+    checkCompiler
+        = unless (compilerFlavor comp `elem` [ GHC ])
+        $ die $ "Compiler: " ++ show comp ++
+                ", does not support relocatable builds"
+      where
+        comp = compiler lbi
+
+    -- Check if all the install dirs are relative to same prefix
+    packagePrefixRelative
+        = unless (relativeInstallDirs installDirs)
+        $ die $ "Installation directories are not prefix_relative:\n" ++
+                show installDirs
+      where
+        -- NB: should be good enough to check this against the default
+        -- component ID, but if we wanted to be strictly correct we'd
+        -- check for each ComponentId.
+        installDirs = absoluteInstallDirs pkg lbi NoCopyDest
+        p           = prefix installDirs
+        relativeInstallDirs (InstallDirs {..}) =
+          all isJust
+              (fmap (stripPrefix p)
+                    [ bindir, libdir, dynlibdir, libexecdir, includedir, datadir
+                    , docdir, mandir, htmldir, haddockdir, sysconfdir] )
+
+    -- Check if the library dirs of the dependencies that are in the package
+    -- database to which the package is installed are relative to the
+    -- prefix of the package
+    depsPrefixRelative = do
+        pkgr <- GHC.pkgRoot verbosity lbi (last (withPackageDB lbi))
+        mapM_ (doCheck pkgr) ipkgs
+      where
+        doCheck pkgr ipkg
+          | maybe False (== pkgr) (Installed.pkgRoot ipkg)
+          = mapM_ (\l -> when (isNothing $ stripPrefix p l) (die (msg l)))
+                  (Installed.libraryDirs ipkg)
+          | otherwise
+          = return ()
+        -- NB: should be good enough to check this against the default
+        -- component ID, but if we wanted to be strictly correct we'd
+        -- check for each ComponentId.
         installDirs   = absoluteInstallDirs pkg lbi NoCopyDest
         p             = prefix installDirs
         ipkgs         = PackageIndex.allPackages (installedPkgs lbi)
diff --git a/cabal/Cabal/Distribution/Simple/GHC.hs b/cabal/Cabal/Distribution/Simple/GHC.hs
--- a/cabal/Cabal/Distribution/Simple/GHC.hs
+++ b/cabal/Cabal/Distribution/Simple/GHC.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE PatternGuards #-}
+
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.GHC
@@ -33,7 +34,10 @@
 
 module Distribution.Simple.GHC (
         getGhcInfo,
-        configure, getInstalledPackages, getPackageDBContents,
+        configure,
+        getInstalledPackages,
+        getInstalledPackagesMonitorFiles,
+        getPackageDBContents,
         buildLib, buildExe,
         replLib, replExe,
         startInterpreter,
@@ -49,74 +53,52 @@
         pkgRoot
  ) where
 
-import qualified Distribution.Simple.GHC.IPI641 as IPI641
+import Control.Applicative -- 7.10 -Werror workaround
+import Prelude             -- https://ghc.haskell.org/trac/ghc/wiki/Migration/7.10#GHCsaysTheimportof...isredundant
+
 import qualified Distribution.Simple.GHC.IPI642 as IPI642
 import qualified Distribution.Simple.GHC.Internal as Internal
 import Distribution.Simple.GHC.ImplInfo
 import Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(..), Executable(..), Library(..)
-         , allExtensions, libModules, exeModules
-         , hcOptions, hcSharedOptions, hcProfOptions )
-import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo )
+import Distribution.InstalledPackageInfo (InstalledPackageInfo)
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
-                                ( InstalledPackageInfo(..) )
 import Distribution.Simple.PackageIndex (InstalledPackageIndex)
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..)
-         , absoluteInstallDirs, depLibraryPaths )
 import qualified Distribution.Simple.Hpc as Hpc
-import Distribution.Simple.InstallDirs hiding ( absoluteInstallDirs )
 import Distribution.Simple.BuildPaths
 import Distribution.Simple.Utils
 import Distribution.Package
-         ( PackageName(..) )
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.Simple.Program
-         ( Program(..), ConfiguredProgram(..), ProgramConfiguration
-         , ProgramSearchPath
-         , rawSystemProgramStdout, rawSystemProgramStdoutConf
-         , getProgramInvocationOutput, requireProgramVersion, requireProgram
-         , userMaybeSpecifyPath, programPath, lookupProgram, addKnownProgram
-         , ghcProgram, ghcPkgProgram, haddockProgram, hsc2hsProgram, ldProgram )
 import qualified Distribution.Simple.Program.HcPkg as HcPkg
 import qualified Distribution.Simple.Program.Ar    as Ar
 import qualified Distribution.Simple.Program.Ld    as Ld
 import qualified Distribution.Simple.Program.Strip as Strip
 import Distribution.Simple.Program.GHC
 import Distribution.Simple.Setup
-         ( toFlag, fromFlag, fromFlagOrDefault, configCoverage, configDistPref )
 import qualified Distribution.Simple.Setup as Cabal
-        ( Flag(..) )
-import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), CompilerId(..), Compiler(..), compilerVersion
-         , PackageDB(..), PackageDBStack, AbiTag(..) )
+import Distribution.Simple.Compiler hiding (Flag)
 import Distribution.Version
-         ( Version(..), anyVersion, orLaterVersion )
 import Distribution.System
-         ( Platform(..), OS(..) )
 import Distribution.Verbosity
 import Distribution.Text
-         ( display )
 import Distribution.Utils.NubList
-         ( NubListR, overNubListR, toNubListR )
-import Language.Haskell.Extension (Extension(..), KnownExtension(..))
+import Language.Haskell.Extension
 
 import Control.Monad            ( unless, when )
 import Data.Char                ( isDigit, isSpace )
 import Data.List
-import qualified Data.Map as M  ( fromList )
+import qualified Data.Map as M  ( fromList, lookup )
 import Data.Maybe               ( catMaybes )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid              ( Monoid(..) )
-#endif
+import Data.Monoid as Mon       ( Monoid(..) )
 import Data.Version             ( showVersion )
 import System.Directory
-         ( doesFileExist, getAppUserDataDirectory, createDirectoryIfMissing )
-import System.FilePath          ( (</>), (<.>), takeExtension,
-                                  takeDirectory, replaceExtension,
-                                  splitExtension, isRelative )
+         ( doesFileExist, getAppUserDataDirectory, createDirectoryIfMissing
+         , canonicalizePath )
+import System.FilePath          ( (</>), (<.>), takeExtension
+                                , takeDirectory, replaceExtension
+                                , isRelative )
 import qualified System.Info
 
 -- -----------------------------------------------------------------------------
@@ -129,7 +111,7 @@
 
   (ghcProg, ghcVersion, conf1) <-
     requireProgramVersion verbosity ghcProgram
-      (orLaterVersion (Version [6,4] []))
+      (orLaterVersion (Version [6,11] []))
       (userMaybeSpecifyPath "ghc" hcPath conf0)
   let implInfo = ghcVersionImplInfo ghcVersion
 
@@ -154,15 +136,29 @@
       haddockProgram' = haddockProgram {
                            programFindLocation = guessHaddockFromGhcPath ghcProg
                        }
+      hpcProgram' = hpcProgram {
+                        programFindLocation = guessHpcFromGhcPath ghcProg
+                    }
       conf3 = addKnownProgram haddockProgram' $
-              addKnownProgram hsc2hsProgram' conf2
+              addKnownProgram hsc2hsProgram' $
+              addKnownProgram hpcProgram' conf2
 
   languages  <- Internal.getLanguages verbosity implInfo ghcProg
-  extensions <- Internal.getExtensions verbosity implInfo ghcProg
+  extensions0 <- Internal.getExtensions verbosity implInfo ghcProg
 
   ghcInfo <- Internal.getGhcInfo verbosity implInfo ghcProg
   let ghcInfoMap = M.fromList ghcInfo
 
+      -- starting with GHC 8.0, `TemplateHaskell` will be omitted from
+      -- `--supported-extensions` when it's not available.
+      -- for older GHCs we can use the "Have interpreter" property to
+      -- filter out `TemplateHaskell`
+      extensions | ghcVersion < Version [8] []
+                 , Just "NO" <- M.lookup "Have interpreter" ghcInfoMap
+                   = filter ((/= EnableExtension TemplateHaskell) . fst)
+                     extensions0
+                 | otherwise = extensions0
+
   let comp = Compiler {
         compilerId         = CompilerId GHC ghcVersion,
         compilerAbiTag     = NoAbiTag,
@@ -186,30 +182,42 @@
 --
 guessToolFromGhcPath :: Program -> ConfiguredProgram
                      -> Verbosity -> ProgramSearchPath
-                     -> IO (Maybe FilePath)
+                     -> IO (Maybe (FilePath, [FilePath]))
 guessToolFromGhcPath tool ghcProg verbosity searchpath
   = do let toolname          = programName tool
-           path              = programPath ghcProg
-           dir               = takeDirectory path
-           versionSuffix     = takeVersionSuffix (dropExeExtension path)
-           guessNormal       = dir </> toolname <.> exeExtension
-           guessGhcVersioned = dir </> (toolname ++ "-ghc" ++ versionSuffix)
-                               <.> exeExtension
-           guessVersioned    = dir </> (toolname ++ versionSuffix)
-                               <.> exeExtension
-           guesses | null versionSuffix = [guessNormal]
-                   | otherwise          = [guessGhcVersioned,
-                                           guessVersioned,
-                                           guessNormal]
+           given_path        = programPath ghcProg
+           given_dir         = takeDirectory given_path
+       real_path <- canonicalizePath given_path
+       let real_dir           = takeDirectory real_path
+           versionSuffix path = takeVersionSuffix (dropExeExtension path)
+           given_suf = versionSuffix given_path
+           real_suf  = versionSuffix real_path
+           guessNormal       dir = dir </> toolname <.> exeExtension
+           guessGhcVersioned dir suf = dir </> (toolname ++ "-ghc" ++ suf)
+                                           <.> exeExtension
+           guessVersioned    dir suf = dir </> (toolname ++ suf)
+                                           <.> exeExtension
+           mkGuesses dir suf | null suf  = [guessNormal dir]
+                             | otherwise = [guessGhcVersioned dir suf,
+                                            guessVersioned dir suf,
+                                            guessNormal dir]
+           guesses = mkGuesses given_dir given_suf ++
+                            if real_path == given_path
+                                then []
+                                else mkGuesses real_dir real_suf
        info verbosity $ "looking for tool " ++ toolname
-         ++ " near compiler in " ++ dir
+         ++ " near compiler in " ++ given_dir
+       debug verbosity $ "candidate locations: " ++ show guesses
        exists <- mapM doesFileExist guesses
        case [ file | (file, True) <- zip guesses exists ] of
                    -- If we can't find it near ghc, fall back to the usual
                    -- method.
          []     -> programFindLocation tool verbosity searchpath
          (fp:_) -> do info verbosity $ "found " ++ toolname ++ " in " ++ fp
-                      return (Just fp)
+                      let lookedAt = map fst
+                                   . takeWhile (\(_file, exist) -> not exist)
+                                   $ zip guesses exists
+                      return (Just (fp, lookedAt))
 
   where takeVersionSuffix :: FilePath -> String
         takeVersionSuffix = takeWhileEndLE isSuffixChar
@@ -217,12 +225,6 @@
         isSuffixChar :: Char -> Bool
         isSuffixChar c = isDigit c || c == '.' || c == '-'
 
-        dropExeExtension :: FilePath -> FilePath
-        dropExeExtension filepath =
-          case splitExtension filepath of
-            (filepath', extension) | extension == exeExtension -> filepath'
-                                   | otherwise                 -> filepath
-
 -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a
 -- corresponding ghc-pkg, we try looking for both a versioned and unversioned
 -- ghc-pkg in the same dir, that is:
@@ -232,7 +234,8 @@
 -- > /usr/local/bin/ghc-pkg(.exe)
 --
 guessGhcPkgFromGhcPath :: ConfiguredProgram
-                       -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)
+                       -> Verbosity -> ProgramSearchPath
+                       -> IO (Maybe (FilePath, [FilePath]))
 guessGhcPkgFromGhcPath = guessToolFromGhcPath ghcPkgProgram
 
 -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a
@@ -244,7 +247,8 @@
 -- > /usr/local/bin/hsc2hs(.exe)
 --
 guessHsc2hsFromGhcPath :: ConfiguredProgram
-                       -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)
+                       -> Verbosity -> ProgramSearchPath
+                       -> IO (Maybe (FilePath, [FilePath]))
 guessHsc2hsFromGhcPath = guessToolFromGhcPath hsc2hsProgram
 
 -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a
@@ -256,9 +260,16 @@
 -- > /usr/local/bin/haddock(.exe)
 --
 guessHaddockFromGhcPath :: ConfiguredProgram
-                       -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)
+                       -> Verbosity -> ProgramSearchPath
+                       -> IO (Maybe (FilePath, [FilePath]))
 guessHaddockFromGhcPath = guessToolFromGhcPath haddockProgram
 
+guessHpcFromGhcPath :: ConfiguredProgram
+                       -> Verbosity -> ProgramSearchPath
+                       -> IO (Maybe (FilePath, [FilePath]))
+guessHpcFromGhcPath = guessToolFromGhcPath hpcProgram
+
+
 getGhcInfo :: Verbosity -> ConfiguredProgram -> IO [(String, String)]
 getGhcInfo verbosity ghcProg = Internal.getGhcInfo verbosity implInfo ghcProg
   where
@@ -273,7 +284,8 @@
   toPackageIndex verbosity pkgss conf
 
 -- | Given a package DB stack, return all installed packages.
-getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack -> ProgramConfiguration
+getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack
+                     -> ProgramConfiguration
                      -> IO InstalledPackageIndex
 getInstalledPackages verbosity comp packagedbs conf = do
   checkPackageDbEnvVar
@@ -304,7 +316,7 @@
   topDir <- getLibDir' verbosity ghcProg
   let indices = [ PackageIndex.fromList (map (Internal.substTopDir topDir) pkgs)
                 | (_, pkgs) <- pkgss ]
-  return $! (mconcat indices)
+  return $! mconcat indices
 
   where
     Just ghcProg = lookupProgram ghcProgram conf
@@ -327,6 +339,23 @@
     dropWhileEndLE isSpace `fmap`
      rawSystemProgramStdout verbosity ghcProg ["--print-global-package-db"]
 
+-- | Return the 'FilePath' to the per-user GHC package database.
+getUserPackageDB :: Verbosity -> ConfiguredProgram -> Platform -> IO FilePath
+getUserPackageDB _verbosity ghcProg (Platform arch os) = do
+    -- It's rather annoying that we have to reconstruct this, because ghc
+    -- hides this information from us otherwise. But for certain use cases
+    -- like change monitoring it really can't remain hidden.
+    appdir <- getAppUserDataDirectory "ghc"
+    return (appdir </> platformAndVersion </> packageConfFileName)
+  where
+    platformAndVersion = intercalate "-" [ Internal.showArchString arch
+                                         , Internal.showOsString os
+                                         , display ghcVersion ]
+    packageConfFileName
+      | ghcVersion >= Version [6,12] []  = "package.conf.d"
+      | otherwise                        = "package.conf"
+    Just ghcVersion = programVersion ghcProg
+
 checkPackageDbEnvVar :: IO ()
 checkPackageDbEnvVar =
     Internal.checkPackageDbEnvVar "GHC" "GHC_PACKAGE_PATH"
@@ -405,14 +434,41 @@
       = \file content -> case reads content of
           [(pkgs, _)] -> return (map IPI642.toCurrent pkgs)
           _           -> failToRead file
+      -- We dropped support for 6.4.2 and earlier.
       | otherwise
-      = \file content -> case reads content of
-          [(pkgs, _)] -> return (map IPI641.toCurrent pkgs)
-          _           -> failToRead file
+      = \file _ -> failToRead file
     Just ghcProg = lookupProgram ghcProgram conf
     Just ghcVersion = programVersion ghcProg
     failToRead file = die $ "cannot read ghc package database " ++ file
 
+getInstalledPackagesMonitorFiles :: Verbosity -> Platform
+                                 -> ProgramConfiguration
+                                 -> [PackageDB]
+                                 -> IO [FilePath]
+getInstalledPackagesMonitorFiles verbosity platform progdb =
+    mapM getPackageDBPath
+  where
+    getPackageDBPath :: PackageDB -> IO FilePath
+    getPackageDBPath GlobalPackageDB =
+      selectMonitorFile =<< getGlobalPackageDB verbosity ghcProg
+
+    getPackageDBPath UserPackageDB =
+      selectMonitorFile =<< getUserPackageDB verbosity ghcProg platform
+
+    getPackageDBPath (SpecificPackageDB path) = selectMonitorFile path
+
+    -- GHC has old style file dbs, and new style directory dbs.
+    -- Note that for dir style dbs, we only need to monitor the cache file, not
+    -- the whole directory. The ghc program itself only reads the cache file
+    -- so it's safe to only monitor this one file.
+    selectMonitorFile path = do
+      isFileStyle <- doesFileExist path
+      if isFileStyle then return path
+                     else return (path </> "package.cache")
+
+    Just ghcProg = lookupProgram ghcProgram progdb
+
+
 -- -----------------------------------------------------------------------------
 -- Building
 
@@ -428,8 +484,8 @@
                -> PackageDescription -> LocalBuildInfo
                -> Library            -> ComponentLocalBuildInfo -> IO ()
 buildOrReplLib forRepl verbosity numJobs pkg_descr lbi lib clbi = do
-  let libName = componentLibraryName clbi
-      libTargetDir = buildDir lbi
+  let uid = componentUnitId clbi
+      libTargetDir = componentBuildDir lbi clbi
       whenVanillaLib forceVanilla =
         when (forceVanilla || withVanillaLib lbi)
       whenProfLib = when (withProfLib lbi)
@@ -440,10 +496,10 @@
       comp = compiler lbi
       ghcVersion = compilerVersion comp
       implInfo  = getImplInfo comp
-      (Platform _hostArch hostOS) = hostPlatform lbi
+      platform@(Platform _hostArch hostOS) = hostPlatform lbi
 
   (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi)
-  let runGhcProg = runGHC verbosity ghcProg comp
+  let runGhcProg = runGHC verbosity ghcProg comp platform
 
   libBi <- hackThreadedFlag verbosity
              comp (withProfLib lbi) (libBuildInfo lib)
@@ -458,13 +514,15 @@
   -- Determine if program coverage should be enabled and if so, what
   -- '-hpcdir' should be.
   let isCoverageEnabled = fromFlag $ configCoverage $ configFlags lbi
-      -- Component name. Not 'libName' because that has the "HS" prefix
-      -- that GHC gives Haskell libraries.
-      cname = display $ PD.package $ localPkgDescr lbi
+      -- TODO: Historically HPC files have been put into a directory which
+      -- has the package name.  I'm going to avoid changing this for
+      -- now, but it would probably be better for this to be the
+      -- component ID instead...
+      pkg_name = display $ PD.package $ localPkgDescr lbi
       distPref = fromFlag $ configDistPref $ configFlags lbi
       hpcdir way
-        | forRepl = mempty  -- HPC is not supported in ghci
-        | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way cname
+        | forRepl = Mon.mempty  -- HPC is not supported in ghci
+        | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name
         | otherwise = mempty
 
   createDirectoryIfMissingVerbose verbosity True libTargetDir
@@ -498,17 +556,20 @@
                       ghcOptHPCDir      = hpcdir Hpc.Dyn
                     }
       linkerOpts = mempty {
-                      ghcOptLinkOptions    = toNubListR $ PD.ldOptions libBi,
-                      ghcOptLinkLibs       = toNubListR $ extraLibs libBi,
-                      ghcOptLinkLibPath    = toNubListR $ extraLibDirs libBi,
-                      ghcOptLinkFrameworks = toNubListR $ PD.frameworks libBi,
+                      ghcOptLinkOptions       = toNubListR $ PD.ldOptions libBi,
+                      ghcOptLinkLibs          = toNubListR $ extraLibs libBi,
+                      ghcOptLinkLibPath       = toNubListR $ extraLibDirs libBi,
+                      ghcOptLinkFrameworks    = toNubListR $
+                                                PD.frameworks libBi,
+                      ghcOptLinkFrameworkDirs = toNubListR $
+                                                PD.extraFrameworkDirs libBi,
                       ghcOptInputFiles     = toNubListR
                                              [libTargetDir </> x | x <- cObjs]
                    }
       replOpts    = vanillaOpts {
                       ghcOptExtra        = overNubListR
                                            Internal.filterGhciFlags $
-                                           (ghcOptExtra vanillaOpts),
+                                           ghcOptExtra vanillaOpts,
                       ghcOptNumJobs      = mempty
                     }
                     `mappend` linkerOpts
@@ -535,7 +596,7 @@
           then do
               runGhcProg vanillaSharedOpts
               case (hpcdir Hpc.Dyn, hpcdir Hpc.Vanilla) of
-                (Cabal.Flag dynDir, Cabal.Flag vanillaDir) -> do
+                (Cabal.Flag dynDir, Cabal.Flag vanillaDir) ->
                     -- When the vanilla and shared library builds are done
                     -- in one pass, only one set of HPC module interfaces
                     -- are generated. This set should suffice for both
@@ -571,12 +632,13 @@
                                }
                odir          = fromFlag (ghcOptObjDir vanillaCcOpts)
            createDirectoryIfMissingVerbose verbosity True odir
-           needsRecomp <- checkNeedsRecompilation filename vanillaCcOpts
-           when needsRecomp $ do
-               runGhcProg vanillaCcOpts
-               unless forRepl $
-                 whenSharedLib forceSharedLib (runGhcProg sharedCcOpts)
-               unless forRepl $ whenProfLib (runGhcProg profCcOpts)
+           let runGhcProgIfNeeded ccOpts = do
+                 needsRecomp <- checkNeedsRecompilation filename ccOpts
+                 when needsRecomp $ runGhcProg ccOpts
+           runGhcProgIfNeeded vanillaCcOpts
+           unless forRepl $
+             whenSharedLib forceSharedLib (runGhcProgIfNeeded sharedCcOpts)
+           unless forRepl $ whenProfLib (runGhcProgIfNeeded profCcOpts)
       | filename <- cSources libBi]
 
   -- TODO: problem here is we need the .c files built first, so we can load them
@@ -594,25 +656,25 @@
                       (cSources libBi)
         cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension))
                       (cSources libBi)
-        cid = compilerId (compiler lbi)
-        vanillaLibFilePath = libTargetDir </> mkLibName           libName
-        profileLibFilePath = libTargetDir </> mkProfLibName       libName
-        sharedLibFilePath  = libTargetDir </> mkSharedLibName cid libName
-        ghciLibFilePath    = libTargetDir </> Internal.mkGHCiLibName libName
-        libInstallPath = libdir $ absoluteInstallDirs pkg_descr lbi NoCopyDest
-        sharedLibInstallPath = libInstallPath </> mkSharedLibName cid libName
+        compiler_id = compilerId (compiler lbi)
+        vanillaLibFilePath = libTargetDir </> mkLibName uid
+        profileLibFilePath = libTargetDir </> mkProfLibName uid
+        sharedLibFilePath  = libTargetDir </> mkSharedLibName compiler_id uid
+        ghciLibFilePath    = libTargetDir </> Internal.mkGHCiLibName uid
+        libInstallPath = libdir $ absoluteComponentInstallDirs pkg_descr lbi uid NoCopyDest
+        sharedLibInstallPath = libInstallPath </> mkSharedLibName compiler_id uid
 
-    stubObjs <- fmap catMaybes $ sequence
+    stubObjs <- catMaybes <$> sequence
       [ findFileWithExtension [objExtension] [libTargetDir]
           (ModuleName.toFilePath x ++"_stub")
       | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files
       , x <- libModules lib ]
-    stubProfObjs <- fmap catMaybes $ sequence
+    stubProfObjs <- catMaybes <$> sequence
       [ findFileWithExtension ["p_" ++ objExtension] [libTargetDir]
           (ModuleName.toFilePath x ++"_stub")
       | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files
       , x <- libModules lib ]
-    stubSharedObjs <- fmap catMaybes $ sequence
+    stubSharedObjs <- catMaybes <$> sequence
       [ findFileWithExtension ["dyn_" ++ objExtension] [libTargetDir]
           (ModuleName.toFilePath x ++"_stub")
       | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files
@@ -621,12 +683,12 @@
     hObjs     <- Internal.getHaskellObjects implInfo lib lbi
                       libTargetDir objExtension True
     hProfObjs <-
-      if (withProfLib lbi)
+      if withProfLib lbi
               then Internal.getHaskellObjects implInfo lib lbi
                       libTargetDir ("p_" ++ objExtension) True
               else return []
     hSharedObjs <-
-      if (withSharedLib lbi)
+      if withSharedLib lbi
               then Internal.getHaskellObjects implInfo lib lbi
                       libTargetDir ("dyn_" ++ objExtension) False
               else return []
@@ -659,11 +721,13 @@
                 ghcOptDynLinkMode        = toFlag GhcDynamicOnly,
                 ghcOptInputFiles         = toNubListR dynamicObjectFiles,
                 ghcOptOutputFile         = toFlag sharedLibFilePath,
+                ghcOptExtra              = toNubListR $
+                                           hcSharedOptions GHC libBi,
                 -- For dynamic libs, Mac OS/X needs to know the install location
                 -- at build time. This only applies to GHC < 7.8 - see the
                 -- discussion in #1660.
-                ghcOptDylibName          = if (hostOS == OSX
-                                               && ghcVersion < Version [7,8] [])
+                ghcOptDylibName          = if hostOS == OSX
+                                              && ghcVersion < Version [7,8] []
                                             then toFlag sharedLibInstallPath
                                             else mempty,
                 ghcOptNoAutoLinkPackages = toFlag True,
@@ -673,15 +737,17 @@
                 ghcOptLinkLibs           = toNubListR $ extraLibs libBi,
                 ghcOptLinkLibPath        = toNubListR $ extraLibDirs libBi,
                 ghcOptLinkFrameworks     = toNubListR $ PD.frameworks libBi,
+                ghcOptLinkFrameworkDirs  =
+                  toNubListR $ PD.extraFrameworkDirs libBi,
                 ghcOptRPaths             = rpaths
               }
 
       info verbosity (show (ghcOptPackages ghcSharedLinkArgs))
 
-      whenVanillaLib False $ do
+      whenVanillaLib False $
         Ar.createArLibArchive verbosity lbi vanillaLibFilePath staticObjectFiles
 
-      whenProfLib $ do
+      whenProfLib $
         Ar.createArLibArchive verbosity lbi profileLibFilePath profObjectFiles
 
       whenGHCiLib $ do
@@ -693,16 +759,16 @@
         runGhcProg ghcSharedLinkArgs
 
 -- | Start a REPL without loading any source files.
-startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler
+startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler -> Platform
                  -> PackageDBStack -> IO ()
-startInterpreter verbosity conf comp packageDBs = do
+startInterpreter verbosity conf comp platform packageDBs = do
   let replOpts = mempty {
         ghcOptMode       = toFlag GhcModeInteractive,
         ghcOptPackageDBs = packageDBs
         }
   checkPackageDbStack comp packageDBs
   (ghcProg, _) <- requireProgram verbosity ghcProgram conf
-  runGHC verbosity ghcProg comp replOpts
+  runGHC verbosity ghcProg comp platform replOpts
 
 -- | Build an executable with GHC.
 --
@@ -720,8 +786,9 @@
 
   (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi)
   let comp       = compiler lbi
+      platform   = hostPlatform lbi
       implInfo   = getImplInfo comp
-      runGhcProg = runGHC verbosity ghcProg comp
+      runGhcProg = runGHC verbosity ghcProg comp platform
 
   exeBi <- hackThreadedFlag verbosity
              comp (withProfExe lbi) (buildInfo exe)
@@ -732,8 +799,8 @@
                        then exeExtension
                        else "")
 
-  let targetDir = (buildDir lbi) </> exeName'
-  let exeDir    = targetDir </> (exeName' ++ "-tmp")
+  let targetDir = componentBuildDir lbi clbi
+      exeDir    = targetDir </> (exeName' ++ "-tmp")
   createDirectoryIfMissingVerbose verbosity True targetDir
   createDirectoryIfMissingVerbose verbosity True exeDir
   -- TODO: do we need to put hs-boot files into place for mutually recursive
@@ -773,10 +840,11 @@
       profOpts   = baseOpts `mappend` mempty {
                       ghcOptProfilingMode  = toFlag True,
                       ghcOptProfilingAuto  = Internal.profDetailLevelFlag False
-                                               (withProfExeDetail lbi),
+                                             (withProfExeDetail lbi),
                       ghcOptHiSuffix       = toFlag "p_hi",
                       ghcOptObjSuffix      = toFlag "p_o",
-                      ghcOptExtra          = toNubListR (hcProfOptions GHC exeBi),
+                      ghcOptExtra          = toNubListR
+                                             (hcProfOptions GHC exeBi),
                       ghcOptHPCDir         = hpcdir Hpc.Prof
                     }
       dynOpts    = baseOpts `mappend` mempty {
@@ -794,10 +862,13 @@
                       ghcOptHPCDir         = hpcdir Hpc.Dyn
                     }
       linkerOpts = mempty {
-                      ghcOptLinkOptions    = toNubListR $ PD.ldOptions exeBi,
-                      ghcOptLinkLibs       = toNubListR $ extraLibs exeBi,
-                      ghcOptLinkLibPath    = toNubListR $ extraLibDirs exeBi,
-                      ghcOptLinkFrameworks = toNubListR $ PD.frameworks exeBi,
+                      ghcOptLinkOptions       = toNubListR $ PD.ldOptions exeBi,
+                      ghcOptLinkLibs          = toNubListR $ extraLibs exeBi,
+                      ghcOptLinkLibPath       = toNubListR $ extraLibDirs exeBi,
+                      ghcOptLinkFrameworks    = toNubListR $
+                                                PD.frameworks exeBi,
+                      ghcOptLinkFrameworkDirs = toNubListR $
+                                                PD.extraFrameworkDirs exeBi,
                       ghcOptInputFiles     = toNubListR
                                              [exeDir </> x | x <- cObjs]
                     }
@@ -846,10 +917,11 @@
         | isGhcDynamic = doingTH && (withProfExe lbi || withStaticExe)
         | otherwise    = doingTH && (withProfExe lbi || withDynExe lbi)
 
-      linkOpts = commonOpts `mappend`
-                 linkerOpts `mappend`
-                 mempty { ghcOptLinkNoHsMain   = toFlag (not isHaskellMain) } `mappend`
-                 (if withDynExe lbi then dynLinkerOpts else mempty)
+      linkOpts =
+        commonOpts `mappend`
+        linkerOpts `mappend`
+        mempty { ghcOptLinkNoHsMain   = toFlag (not isHaskellMain) } `mappend`
+        (if withDynExe lbi then dynLinkerOpts else mempty)
 
   -- Build static/dynamic object files for TH, if needed.
   when compileForTH $
@@ -874,7 +946,7 @@
               odir  = fromFlag (ghcOptObjDir opts)
           createDirectoryIfMissingVerbose verbosity True odir
           needsRecomp <- checkNeedsRecompilation filename opts
-          when needsRecomp $ 
+          when needsRecomp $
             runGhcProg opts
      | filename <- cSrcs ]
 
@@ -974,8 +1046,9 @@
              (compiler lbi) (withProfLib lbi) (libBuildInfo lib)
   let
       comp        = compiler lbi
+      platform    = hostPlatform lbi
       vanillaArgs =
-        (componentGhcOptions verbosity lbi libBi clbi (buildDir lbi))
+        (componentGhcOptions verbosity lbi libBi clbi (componentBuildDir lbi clbi))
         `mappend` mempty {
           ghcOptMode         = toFlag GhcModeAbiHash,
           ghcOptInputModules = toNubListR $ exposedModules lib
@@ -995,14 +1068,15 @@
                      ghcOptObjSuffix     = toFlag "p_o",
                      ghcOptExtra         = toNubListR $ hcProfOptions GHC libBi
                    }
-      ghcArgs = if withVanillaLib lbi then vanillaArgs
-           else if withSharedLib  lbi then sharedArgs
-           else if withProfLib    lbi then profArgs
-           else error "libAbiHash: Can't find an enabled library way"
-  --
+      ghcArgs
+        | withVanillaLib lbi = vanillaArgs
+        | withSharedLib lbi = sharedArgs
+        | withProfLib lbi = profArgs
+        | otherwise = error "libAbiHash: Can't find an enabled library way"
+
   (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi)
   hash <- getProgramInvocationOutput verbosity
-          (ghcInvocation ghcProg comp ghcArgs)
+          (ghcInvocation ghcProg comp platform ghcArgs)
   return (takeWhile (not . isSpace) hash)
 
 componentGhcOptions :: Verbosity -> LocalBuildInfo
@@ -1057,19 +1131,24 @@
               -> Library
               -> ComponentLocalBuildInfo
               -> IO ()
-installLib verbosity lbi targetDir dynlibTargetDir builtDir _pkg lib clbi = do
+installLib verbosity lbi targetDir dynlibTargetDir _builtDir pkg lib clbi = do
   -- copy .hi files over:
-  whenVanilla $ copyModuleFiles "hi"
-  whenProf    $ copyModuleFiles "p_hi"
-  whenShared  $ copyModuleFiles "dyn_hi"
+  whenRegistered $ do
+    whenVanilla $ copyModuleFiles "hi"
+    whenProf    $ copyModuleFiles "p_hi"
+    whenShared  $ copyModuleFiles "dyn_hi"
 
   -- copy the built library files over:
-  whenVanilla $ installOrdinary builtDir targetDir       vanillaLibName
-  whenProf    $ installOrdinary builtDir targetDir       profileLibName
-  whenGHCi    $ installOrdinary builtDir targetDir       ghciLibName
-  whenShared  $ installShared   builtDir dynlibTargetDir sharedLibName
+  whenRegistered $ do
+    whenVanilla $ installOrdinary builtDir targetDir       vanillaLibName
+    whenProf    $ installOrdinary builtDir targetDir       profileLibName
+    whenGHCi    $ installOrdinary builtDir targetDir       ghciLibName
+  whenRegisteredOrDynExecutable $ do
+    whenShared  $ installShared   builtDir dynlibTargetDir sharedLibName
 
   where
+    builtDir = componentBuildDir lbi clbi
+
     install isShared srcDir dstDir name = do
       let src = srcDir </> name
           dst = dstDir </> name
@@ -1089,12 +1168,12 @@
       findModuleFiles [builtDir] [ext] (libModules lib)
       >>= installOrdinaryFiles verbosity targetDir
 
-    cid = compilerId (compiler lbi)
-    libName = componentLibraryName clbi
-    vanillaLibName = mkLibName              libName
-    profileLibName = mkProfLibName          libName
-    ghciLibName    = Internal.mkGHCiLibName libName
-    sharedLibName  = (mkSharedLibName cid)  libName
+    compiler_id = compilerId (compiler lbi)
+    uid = componentUnitId clbi
+    vanillaLibName = mkLibName              uid
+    profileLibName = mkProfLibName          uid
+    ghciLibName    = Internal.mkGHCiLibName uid
+    sharedLibName  = (mkSharedLibName compiler_id) uid
 
     hasLib    = not $ null (libModules lib)
                    && null (cSources (libBuildInfo lib))
@@ -1103,6 +1182,17 @@
     whenGHCi    = when (hasLib && withGHCiLib    lbi)
     whenShared  = when (hasLib && withSharedLib  lbi)
 
+    -- Some files (e.g. interface files) are completely unnecessary when
+    -- we are not actually going to register the library.  A library is
+    -- not registered if there is no "public library", e.g. in the case
+    -- that we have an internal library and executables, but no public
+    -- library.
+    whenRegistered = when (hasPublicLib pkg)
+
+    -- However, we must always install dynamic libraries when linking
+    -- dynamic executables, because we'll try to load them!
+    whenRegisteredOrDynExecutable = when (hasPublicLib pkg || (hasExes pkg && withDynExe lbi))
+
 -- -----------------------------------------------------------------------------
 -- Registering
 
@@ -1111,7 +1201,10 @@
                                  , HcPkg.noPkgDbStack    = v < [6,9]
                                  , HcPkg.noVerboseFlag   = v < [6,11]
                                  , HcPkg.flagPackageConf = v < [7,5]
-                                 , HcPkg.useSingleFileDb = v < [7,9]
+                                 , HcPkg.supportsDirDbs  = v >= [6,8]
+                                 , HcPkg.requiresDirDbs  = v >= [7,10]
+                                 , HcPkg.nativeMultiInstance  = v >= [7,10]
+                                 , HcPkg.recacheMultiInstance = v >= [6,12]
                                  }
   where
     v               = versionBranch ver
@@ -1120,15 +1213,19 @@
 
 registerPackage
   :: Verbosity
-  -> InstalledPackageInfo
-  -> PackageDescription
-  -> LocalBuildInfo
-  -> Bool
+  -> ProgramConfiguration
+  -> HcPkg.MultiInstance
   -> PackageDBStack
+  -> InstalledPackageInfo
   -> IO ()
-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =
-  HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity
-    packageDbs (Right installedPkgInfo)
+registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo
+  | HcPkg.MultiInstance <- multiInstance
+  = HcPkg.registerMultiInstance (hcPkgInfo progdb) verbosity
+      packageDbs installedPkgInfo
+
+  | otherwise
+  = HcPkg.reregister (hcPkgInfo progdb) verbosity
+      packageDbs (Right installedPkgInfo)
 
 pkgRoot :: Verbosity -> LocalBuildInfo -> PackageDB -> IO FilePath
 pkgRoot verbosity lbi = pkgRoot'
diff --git a/cabal/Cabal/Distribution/Simple/GHC/IPI641.hs b/cabal/Cabal/Distribution/Simple/GHC/IPI641.hs
deleted file mode 100644
--- a/cabal/Cabal/Distribution/Simple/GHC/IPI641.hs
+++ /dev/null
@@ -1,106 +0,0 @@
------------------------------------------------------------------------------
--- |
--- Module      :  Distribution.Simple.GHC.IPI641
--- Copyright   :  (c) The University of Glasgow 2004
--- License     :  BSD3
---
--- Maintainer  :  cabal-devel@haskell.org
--- Portability :  portable
---
-
-module Distribution.Simple.GHC.IPI641 (
-    InstalledPackageInfo(..),
-    toCurrent,
-  ) where
-
-import qualified Distribution.InstalledPackageInfo as Current
-import qualified Distribution.Package as Current hiding (installedPackageId)
-import Distribution.Text (display)
-
-import Distribution.Simple.GHC.IPI642
-         ( PackageIdentifier, convertPackageId
-         , License, convertLicense, convertModuleName )
-
--- | This is the InstalledPackageInfo type used by ghc-6.4 and 6.4.1.
---
--- It's here purely for the 'Read' instance so that we can read the package
--- database used by those ghc versions. It is a little hacky to read the
--- package db directly, but we do need the info and until ghc-6.9 there was
--- no better method.
---
--- In ghc-6.4.2 the format changed a bit. See "Distribution.Simple.GHC.IPI642"
---
-data InstalledPackageInfo = InstalledPackageInfo {
-    package           :: PackageIdentifier,
-    license           :: License,
-    copyright         :: String,
-    maintainer        :: String,
-    author            :: String,
-    stability         :: String,
-    homepage          :: String,
-    pkgUrl            :: String,
-    description       :: String,
-    category          :: String,
-    exposed           :: Bool,
-    exposedModules    :: [String],
-    hiddenModules     :: [String],
-    importDirs        :: [FilePath],
-    libraryDirs       :: [FilePath],
-    hsLibraries       :: [String],
-    extraLibraries    :: [String],
-    includeDirs       :: [FilePath],
-    includes          :: [String],
-    depends           :: [PackageIdentifier],
-    hugsOptions       :: [String],
-    ccOptions         :: [String],
-    ldOptions         :: [String],
-    frameworkDirs     :: [FilePath],
-    frameworks        :: [String],
-    haddockInterfaces :: [FilePath],
-    haddockHTMLs      :: [FilePath]
-  }
-  deriving Read
-
-mkInstalledPackageId :: Current.PackageIdentifier -> Current.InstalledPackageId
-mkInstalledPackageId = Current.InstalledPackageId . display
-
-toCurrent :: InstalledPackageInfo -> Current.InstalledPackageInfo
-toCurrent ipi@InstalledPackageInfo{} =
-  let pid = convertPackageId (package ipi)
-      mkExposedModule m = Current.ExposedModule m Nothing Nothing
-  in Current.InstalledPackageInfo {
-    Current.installedPackageId = mkInstalledPackageId (convertPackageId (package ipi)),
-    Current.sourcePackageId    = pid,
-    Current.packageKey         = Current.OldPackageKey pid,
-    Current.license            = convertLicense (license ipi),
-    Current.copyright          = copyright ipi,
-    Current.maintainer         = maintainer ipi,
-    Current.author             = author ipi,
-    Current.stability          = stability ipi,
-    Current.homepage           = homepage ipi,
-    Current.pkgUrl             = pkgUrl ipi,
-    Current.synopsis           = "",
-    Current.description        = description ipi,
-    Current.category           = category ipi,
-    Current.exposed            = exposed ipi,
-    Current.exposedModules     = map (mkExposedModule . convertModuleName) (exposedModules ipi),
-    Current.instantiatedWith   = [],
-    Current.hiddenModules      = map convertModuleName (hiddenModules ipi),
-    Current.trusted            = Current.trusted Current.emptyInstalledPackageInfo,
-    Current.importDirs         = importDirs ipi,
-    Current.libraryDirs        = libraryDirs ipi,
-    Current.dataDir            = "",
-    Current.hsLibraries        = hsLibraries ipi,
-    Current.extraLibraries     = extraLibraries ipi,
-    Current.extraGHCiLibraries = [],
-    Current.includeDirs        = includeDirs ipi,
-    Current.includes           = includes ipi,
-    Current.depends            = map (mkInstalledPackageId.convertPackageId) (depends ipi),
-    Current.ccOptions          = ccOptions ipi,
-    Current.ldOptions          = ldOptions ipi,
-    Current.frameworkDirs      = frameworkDirs ipi,
-    Current.frameworks         = frameworks ipi,
-    Current.haddockInterfaces  = haddockInterfaces ipi,
-    Current.haddockHTMLs       = haddockHTMLs ipi,
-    Current.pkgRoot            = Nothing
-  }
diff --git a/cabal/Cabal/Distribution/Simple/GHC/IPI642.hs b/cabal/Cabal/Distribution/Simple/GHC/IPI642.hs
--- a/cabal/Cabal/Distribution/Simple/GHC/IPI642.hs
+++ b/cabal/Cabal/Distribution/Simple/GHC/IPI642.hs
@@ -11,22 +11,11 @@
 module Distribution.Simple.GHC.IPI642 (
     InstalledPackageInfo(..),
     toCurrent,
-
-    -- Don't use these, they're only for conversion purposes
-    PackageIdentifier, convertPackageId,
-    License, convertLicense,
-    convertModuleName
   ) where
 
 import qualified Distribution.InstalledPackageInfo as Current
-import qualified Distribution.Package as Current hiding (installedPackageId)
-import qualified Distribution.License as Current
-
-import Distribution.Version (Version)
-import Distribution.ModuleName (ModuleName)
-import Distribution.Text (simpleParse,display)
-
-import Data.Maybe
+import qualified Distribution.Package as Current hiding (installedUnitId)
+import Distribution.Simple.GHC.IPIConvert
 
 -- | This is the InstalledPackageInfo type used by ghc-6.4.2 and later.
 --
@@ -70,43 +59,15 @@
   }
   deriving Read
 
-data PackageIdentifier = PackageIdentifier {
-    pkgName    :: String,
-    pkgVersion :: Version
-  }
-  deriving Read
-
-data License = GPL | LGPL | BSD3 | BSD4
-             | PublicDomain | AllRightsReserved | OtherLicense
-  deriving Read
-
-convertPackageId :: PackageIdentifier -> Current.PackageIdentifier
-convertPackageId PackageIdentifier { pkgName = n, pkgVersion = v } =
-  Current.PackageIdentifier (Current.PackageName n) v
-
-mkInstalledPackageId :: Current.PackageIdentifier -> Current.InstalledPackageId
-mkInstalledPackageId = Current.InstalledPackageId . display
-
-convertModuleName :: String -> ModuleName
-convertModuleName s = fromJust $ simpleParse s
-
-convertLicense :: License -> Current.License
-convertLicense GPL  = Current.GPL  Nothing
-convertLicense LGPL = Current.LGPL Nothing
-convertLicense BSD3 = Current.BSD3
-convertLicense BSD4 = Current.BSD4
-convertLicense PublicDomain = Current.PublicDomain
-convertLicense AllRightsReserved = Current.AllRightsReserved
-convertLicense OtherLicense = Current.OtherLicense
-
 toCurrent :: InstalledPackageInfo -> Current.InstalledPackageInfo
 toCurrent ipi@InstalledPackageInfo{} =
   let pid = convertPackageId (package ipi)
-      mkExposedModule m = Current.ExposedModule m Nothing Nothing
+      mkExposedModule m = Current.ExposedModule m Nothing
   in Current.InstalledPackageInfo {
-    Current.installedPackageId = mkInstalledPackageId (convertPackageId (package ipi)),
     Current.sourcePackageId    = pid,
-    Current.packageKey         = Current.OldPackageKey pid,
+    Current.installedUnitId    = Current.mkLegacyUnitId pid,
+    Current.compatPackageKey   = "",
+    Current.abiHash            = Current.AbiHash "", -- bogus but old GHCs don't care.
     Current.license            = convertLicense (license ipi),
     Current.copyright          = copyright ipi,
     Current.maintainer         = maintainer ipi,
@@ -120,7 +81,6 @@
     Current.exposed            = exposed ipi,
     Current.exposedModules     = map (mkExposedModule . convertModuleName) (exposedModules ipi),
     Current.hiddenModules      = map convertModuleName (hiddenModules ipi),
-    Current.instantiatedWith   = [],
     Current.trusted            = Current.trusted Current.emptyInstalledPackageInfo,
     Current.importDirs         = importDirs ipi,
     Current.libraryDirs        = libraryDirs ipi,
@@ -130,7 +90,7 @@
     Current.extraGHCiLibraries = extraGHCiLibraries ipi,
     Current.includeDirs        = includeDirs ipi,
     Current.includes           = includes ipi,
-    Current.depends            = map (mkInstalledPackageId.convertPackageId) (depends ipi),
+    Current.depends            = map (Current.mkLegacyUnitId . convertPackageId) (depends ipi),
     Current.ccOptions          = ccOptions ipi,
     Current.ldOptions          = ldOptions ipi,
     Current.frameworkDirs      = frameworkDirs ipi,
diff --git a/cabal/Cabal/Distribution/Simple/GHC/IPIConvert.hs b/cabal/Cabal/Distribution/Simple/GHC/IPIConvert.hs
new file mode 100644
--- /dev/null
+++ b/cabal/Cabal/Distribution/Simple/GHC/IPIConvert.hs
@@ -0,0 +1,50 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Simple.GHC.IPI642
+-- Copyright   :  (c) The University of Glasgow 2004
+-- License     :  BSD3
+--
+-- Maintainer  :  cabal-devel@haskell.org
+-- Portability :  portable
+--
+-- Helper functions for 'Distribution.Simple.GHC.IPI642'.
+module Distribution.Simple.GHC.IPIConvert (
+    PackageIdentifier, convertPackageId,
+    License, convertLicense,
+    convertModuleName
+  ) where
+
+import qualified Distribution.Package as Current hiding (installedUnitId)
+import qualified Distribution.License as Current
+
+import Distribution.Version
+import Distribution.ModuleName
+import Distribution.Text
+
+import Data.Maybe
+
+data PackageIdentifier = PackageIdentifier {
+    pkgName    :: String,
+    pkgVersion :: Version
+  }
+  deriving Read
+
+convertPackageId :: PackageIdentifier -> Current.PackageIdentifier
+convertPackageId PackageIdentifier { pkgName = n, pkgVersion = v } =
+  Current.PackageIdentifier (Current.PackageName n) v
+
+data License = GPL | LGPL | BSD3 | BSD4
+             | PublicDomain | AllRightsReserved | OtherLicense
+  deriving Read
+
+convertModuleName :: String -> ModuleName
+convertModuleName s = fromJust $ simpleParse s
+
+convertLicense :: License -> Current.License
+convertLicense GPL  = Current.GPL  Nothing
+convertLicense LGPL = Current.LGPL Nothing
+convertLicense BSD3 = Current.BSD3
+convertLicense BSD4 = Current.BSD4
+convertLicense PublicDomain = Current.PublicDomain
+convertLicense AllRightsReserved = Current.AllRightsReserved
+convertLicense OtherLicense = Current.OtherLicense
diff --git a/cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs b/cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs
--- a/cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs
+++ b/cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs
@@ -15,9 +15,7 @@
         ) where
 
 import Distribution.Simple.Compiler
-  ( Compiler(..), CompilerFlavor(..)
-  , compilerFlavor, compilerVersion, compilerCompatVersion )
-import Distribution.Version ( Version(..) )
+import Distribution.Version
 
 {- |
      Information about features and quirks of a GHC-based implementation.
@@ -33,17 +31,7 @@
 -}
 
 data GhcImplInfo = GhcImplInfo
-  { hasCcOdirBug         :: Bool -- ^ bug in -odir handling for C compilations.
-  , flagInfoLanguages    :: Bool -- ^ --info and --supported-languages flags
-  , fakeRecordPuns       :: Bool -- ^ use -XRecordPuns for NamedFieldPuns
-  , flagStubdir          :: Bool -- ^ -stubdir flag supported
-  , flagOutputDir        :: Bool -- ^ -outputdir flag supported
-  , noExtInSplitSuffix   :: Bool -- ^ split-obj suffix does not contain p_o ext
-  , flagFfiIncludes      :: Bool -- ^ -#include on command line for FFI includes
-  , flagBuildingCabalPkg :: Bool -- ^ -fbuilding-cabal-package flag supported
-  , flagPackageId        :: Bool -- ^ -package-id / -package flags supported
-  , separateGccMingw     :: Bool -- ^ mingw and gcc are in separate directories
-  , supportsHaskell2010  :: Bool -- ^ -XHaskell2010 and -XHaskell98 flags
+  { supportsHaskell2010  :: Bool -- ^ -XHaskell2010 and -XHaskell98 flags
   , reportsNoExt         :: Bool -- ^ --supported-languages gives Ext and NoExt
   , alwaysNondecIndent   :: Bool -- ^ NondecreasingIndentation is always on
   , flagGhciScript       :: Bool -- ^ -ghci-script flag supported
@@ -67,17 +55,7 @@
 
 ghcVersionImplInfo :: Version -> GhcImplInfo
 ghcVersionImplInfo (Version v _) = GhcImplInfo
-  { hasCcOdirBug         = v <  [6,4,1]
-  , flagInfoLanguages    = v >= [6,7]
-  , fakeRecordPuns       = v >= [6,8] && v < [6,10]
-  , flagStubdir          = v >= [6,8]
-  , flagOutputDir        = v >= [6,10]
-  , noExtInSplitSuffix   = v <  [6,11]
-  , flagFfiIncludes      = v <  [6,11]
-  , flagBuildingCabalPkg = v >= [6,11]
-  , flagPackageId        = v >  [6,11]
-  , separateGccMingw     = v <  [6,12]
-  , supportsHaskell2010  = v >= [7]
+  { supportsHaskell2010  = v >= [7]
   , reportsNoExt         = v >= [7]
   , alwaysNondecIndent   = v <  [7,1]
   , flagGhciScript       = v >= [7,2]
@@ -88,17 +66,7 @@
 
 ghcjsVersionImplInfo :: Version -> Version -> GhcImplInfo
 ghcjsVersionImplInfo _ghcjsVer _ghcVer = GhcImplInfo
-  { hasCcOdirBug         = False
-  , flagInfoLanguages    = True
-  , fakeRecordPuns       = False
-  , flagStubdir          = True
-  , flagOutputDir        = True
-  , noExtInSplitSuffix   = False
-  , flagFfiIncludes      = False
-  , flagBuildingCabalPkg = True
-  , flagPackageId        = True
-  , separateGccMingw     = False
-  , supportsHaskell2010  = True
+  { supportsHaskell2010  = True
   , reportsNoExt         = True
   , alwaysNondecIndent   = False
   , flagGhciScript       = True
diff --git a/cabal/Cabal/Distribution/Simple/GHC/Internal.hs b/cabal/Cabal/Distribution/Simple/GHC/Internal.hs
--- a/cabal/Cabal/Distribution/Simple/GHC/Internal.hs
+++ b/cabal/Cabal/Distribution/Simple/GHC/Internal.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE PatternGuards #-}
 -----------------------------------------------------------------------------
 -- |
@@ -27,56 +26,40 @@
         substTopDir,
         checkPackageDbEnvVar,
         profDetailLevelFlag,
+        showArchString,
+        showOsString,
  ) where
 
-import Distribution.Simple.GHC.ImplInfo ( GhcImplInfo (..) )
+import Distribution.Simple.GHC.ImplInfo
 import Distribution.Package
-         ( InstalledPackageId, PackageId, LibraryName
-         , getHSLibraryName )
 import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo )
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
-                                ( InstalledPackageInfo(..) )
-import Distribution.PackageDescription as PD
-         ( BuildInfo(..), Library(..), libModules
-         , hcOptions, usedExtensions, ModuleRenaming, lookupRenaming )
-import Distribution.Compat.Exception ( catchExit, catchIO )
-import Distribution.Lex (tokenizeQuotedWords)
-import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), Compiler(..), DebugInfoLevel(..)
-         , OptimisationLevel(..), ProfDetailLevel(..) )
+import Distribution.PackageDescription as PD hiding (Flag)
+import Distribution.Compat.Exception
+import Distribution.Lex
+import Distribution.Simple.Compiler hiding (Flag)
 import Distribution.Simple.Program.GHC
 import Distribution.Simple.Setup
-         ( Flag, toFlag )
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.Simple.Program
-         ( Program(..), ConfiguredProgram(..), ProgramConfiguration
-         , ProgramLocation(..), ProgramSearchPath, ProgramSearchPathEntry(..)
-         , rawSystemProgram, rawSystemProgramStdout, programPath
-         , addKnownProgram, arProgram, ldProgram, gccProgram, stripProgram
-         , getProgramOutput )
-import Distribution.Simple.Program.Types ( suppressOverrideArgs )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )
 import Distribution.Simple.Utils
 import Distribution.Simple.BuildPaths
-import Distribution.System ( buildOS, OS(..), Platform, platformFromTriple )
+import Distribution.System
 import Distribution.Text ( display, simpleParse )
 import Distribution.Utils.NubList ( toNubListR )
 import Distribution.Verbosity
 import Language.Haskell.Extension
-         ( Language(..), Extension(..), KnownExtension(..) )
 
 import qualified Data.Map as M
 import Data.Char                ( isSpace )
 import Data.Maybe               ( fromMaybe, maybeToList, isJust )
 import Control.Monad            ( unless, when )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid              ( Monoid(..) )
-#endif
+import Data.Monoid as Mon       ( Monoid(..) )
 import System.Directory         ( getDirectoryContents, getTemporaryDirectory )
 import System.Environment       ( getEnv )
-import System.FilePath          ( (</>), (<.>), takeExtension, takeDirectory )
+import System.FilePath          ( (</>), (<.>), takeExtension
+                                , takeDirectory, takeFileName)
 import System.IO                ( hClose, hPutStrLn )
 
 targetPlatform :: [(String, String)] -> Maybe Platform
@@ -89,30 +72,36 @@
                    -> M.Map String String
                    -> ProgramConfiguration
                    -> ProgramConfiguration
-configureToolchain implInfo ghcProg ghcInfo =
+configureToolchain _implInfo ghcProg ghcInfo =
     addKnownProgram gccProgram {
-      programFindLocation = findProg gccProgram extraGccPath,
+      programFindLocation = findProg gccProgramName extraGccPath,
       programPostConf     = configureGcc
     }
   . addKnownProgram ldProgram {
-      programFindLocation = findProg ldProgram extraLdPath,
+      programFindLocation = findProg ldProgramName extraLdPath,
       programPostConf     = configureLd
     }
   . addKnownProgram arProgram {
-      programFindLocation = findProg arProgram extraArPath
+      programFindLocation = findProg arProgramName extraArPath
     }
   . addKnownProgram stripProgram {
-      programFindLocation = findProg stripProgram extraStripPath
+      programFindLocation = findProg stripProgramName extraStripPath
     }
   where
     compilerDir = takeDirectory (programPath ghcProg)
     baseDir     = takeDirectory compilerDir
     mingwBinDir = baseDir </> "mingw" </> "bin"
-    libDir      = baseDir </> "gcc-lib"
-    includeDir  = baseDir </> "include" </> "mingw"
     isWindows   = case buildOS of Windows -> True; _ -> False
     binPrefix   = ""
 
+    maybeName :: Program -> Maybe FilePath -> String
+    maybeName prog   = maybe (programName prog) (dropExeExtension . takeFileName)
+
+    gccProgramName   = maybeName gccProgram   mbGccLocation
+    ldProgramName    = maybeName ldProgram    mbLdLocation
+    arProgramName    = maybeName arProgram    mbArLocation
+    stripProgramName = maybeName stripProgram mbStripLocation
+
     mkExtraPath :: Maybe FilePath -> FilePath -> [FilePath]
     mkExtraPath mbPath mingwPath | isWindows = mbDir ++ [mingwPath]
                                  | otherwise = mbDir
@@ -126,16 +115,15 @@
 
     -- on Windows finding and configuring ghc's gcc & binutils is a bit special
     (windowsExtraGccDir, windowsExtraLdDir,
-     windowsExtraArDir, windowsExtraStripDir)
-      | separateGccMingw implInfo = (baseDir, libDir, libDir, libDir)
-      | otherwise                 = -- GHC >= 6.12
+     windowsExtraArDir, windowsExtraStripDir) =
           let b = mingwBinDir </> binPrefix
           in  (b, b, b, b)
 
-    findProg :: Program -> [FilePath]
-             -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)
-    findProg prog extraPath v searchpath =
-        programFindLocation prog v searchpath'
+    findProg :: String -> [FilePath]
+             -> Verbosity -> ProgramSearchPath
+             -> IO (Maybe (FilePath, [FilePath]))
+    findProg progName extraPath v searchpath =
+        findProgramOnSearchPath v searchpath' progName
       where
         searchpath' = (map ProgramSearchPathDir extraPath) ++ searchpath
 
@@ -165,28 +153,12 @@
             | otherwise -> tokenizeQuotedWords flags
 
     configureGcc :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
-    configureGcc v gccProg = do
-      gccProg' <- configureGcc' v gccProg
-      return gccProg' {
-        programDefaultArgs = programDefaultArgs gccProg'
+    configureGcc _v gccProg = do
+      return gccProg {
+        programDefaultArgs = programDefaultArgs gccProg
                              ++ ccFlags ++ gccLinkerFlags
       }
 
-    configureGcc' :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
-    configureGcc'
-      | isWindows = \_ gccProg -> case programLocation gccProg of
-          -- if it's found on system then it means we're using the result
-          -- of programFindLocation above rather than a user-supplied path
-          -- Pre GHC 6.12, that meant we should add these flags to tell
-          -- ghc's gcc where it lives and thus where gcc can find its
-          -- various files:
-          FoundOnSystem {}
-           | separateGccMingw implInfo ->
-               return gccProg { programDefaultArgs = ["-B" ++ libDir,
-                                                      "-I" ++ includeDir] }
-          _ -> return gccProg
-      | otherwise = \_ gccProg -> return gccProg
-
     configureLd :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
     configureLd v ldProg = do
       ldProg' <- configureLd' v ldProg
@@ -226,8 +198,7 @@
 
 getGhcInfo :: Verbosity -> GhcImplInfo -> ConfiguredProgram
            -> IO [(String, String)]
-getGhcInfo verbosity implInfo ghcProg
-  | flagInfoLanguages implInfo = do
+getGhcInfo verbosity _implInfo ghcProg = do
       xs <- getProgramOutput verbosity (suppressOverrideArgs ghcProg)
                  ["--info"]
       case reads xs of
@@ -236,13 +207,10 @@
               return i
         _ ->
           die "Can't parse --info output of GHC"
-  | otherwise =
-      return []
 
 getExtensions :: Verbosity -> GhcImplInfo -> ConfiguredProgram
               -> IO [(Extension, String)]
-getExtensions verbosity implInfo ghcProg
-  | flagInfoLanguages implInfo = do
+getExtensions verbosity implInfo ghcProg = do
     str <- getProgramOutput verbosity (suppressOverrideArgs ghcProg)
               ["--supported-languages"]
     let extStrs = if reportsNoExt implInfo
@@ -258,94 +226,27 @@
                        ]
     let extensions0 = [ (ext, "-X" ++ display ext)
                       | Just ext <- map simpleParse extStrs ]
-        extensions1 = if fakeRecordPuns implInfo
-                      then -- ghc-6.8 introduced RecordPuns however it
-                           -- should have been NamedFieldPuns. We now
-                           -- encourage packages to use NamedFieldPuns
-                           -- so for compatibility we fake support for
-                           -- it in ghc-6.8 by making it an alias for
-                           -- the old RecordPuns extension.
-                           (EnableExtension  NamedFieldPuns, "-XRecordPuns") :
-                           (DisableExtension NamedFieldPuns, "-XNoRecordPuns") :
-                           extensions0
-                      else extensions0
-        extensions2 = if alwaysNondecIndent implInfo
+        extensions1 = if alwaysNondecIndent implInfo
                       then -- ghc-7.2 split NondecreasingIndentation off
                            -- into a proper extension. Before that it
                            -- was always on.
                            (EnableExtension  NondecreasingIndentation, "") :
                            (DisableExtension NondecreasingIndentation, "") :
-                           extensions1
-                      else extensions1
-    return extensions2
-
-  | otherwise = return oldLanguageExtensions
-
--- | For GHC 6.6.x and earlier, the mapping from supported extensions to flags
-oldLanguageExtensions :: [(Extension, String)]
-oldLanguageExtensions =
-    let doFlag (f, (enable, disable)) = [(EnableExtension  f, enable),
-                                         (DisableExtension f, disable)]
-        fglasgowExts = ("-fglasgow-exts",
-                        "") -- This is wrong, but we don't want to turn
-                            -- all the extensions off when asked to just
-                            -- turn one off
-        fFlag flag = ("-f" ++ flag, "-fno-" ++ flag)
-    in concatMap doFlag
-    [(OverlappingInstances       , fFlag "allow-overlapping-instances")
-    ,(TypeSynonymInstances       , fglasgowExts)
-    ,(TemplateHaskell            , fFlag "th")
-    ,(ForeignFunctionInterface   , fFlag "ffi")
-    ,(MonomorphismRestriction    , fFlag "monomorphism-restriction")
-    ,(MonoPatBinds               , fFlag "mono-pat-binds")
-    ,(UndecidableInstances       , fFlag "allow-undecidable-instances")
-    ,(IncoherentInstances        , fFlag "allow-incoherent-instances")
-    ,(Arrows                     , fFlag "arrows")
-    ,(Generics                   , fFlag "generics")
-    ,(ImplicitPrelude            , fFlag "implicit-prelude")
-    ,(ImplicitParams             , fFlag "implicit-params")
-    ,(CPP                        , ("-cpp", ""{- Wrong -}))
-    ,(BangPatterns               , fFlag "bang-patterns")
-    ,(KindSignatures             , fglasgowExts)
-    ,(RecursiveDo                , fglasgowExts)
-    ,(ParallelListComp           , fglasgowExts)
-    ,(MultiParamTypeClasses      , fglasgowExts)
-    ,(FunctionalDependencies     , fglasgowExts)
-    ,(Rank2Types                 , fglasgowExts)
-    ,(RankNTypes                 , fglasgowExts)
-    ,(PolymorphicComponents      , fglasgowExts)
-    ,(ExistentialQuantification  , fglasgowExts)
-    ,(ScopedTypeVariables        , fFlag "scoped-type-variables")
-    ,(FlexibleContexts           , fglasgowExts)
-    ,(FlexibleInstances          , fglasgowExts)
-    ,(EmptyDataDecls             , fglasgowExts)
-    ,(PatternGuards              , fglasgowExts)
-    ,(GeneralizedNewtypeDeriving , fglasgowExts)
-    ,(MagicHash                  , fglasgowExts)
-    ,(UnicodeSyntax              , fglasgowExts)
-    ,(PatternSignatures          , fglasgowExts)
-    ,(UnliftedFFITypes           , fglasgowExts)
-    ,(LiberalTypeSynonyms        , fglasgowExts)
-    ,(TypeOperators              , fglasgowExts)
-    ,(GADTs                      , fglasgowExts)
-    ,(RelaxedPolyRec             , fglasgowExts)
-    ,(ExtendedDefaultRules       , fFlag "extended-default-rules")
-    ,(UnboxedTuples              , fglasgowExts)
-    ,(DeriveDataTypeable         , fglasgowExts)
-    ,(ConstrainedClassMethods    , fglasgowExts)
-    ]
+                           extensions0
+                      else extensions0
+    return extensions1
 
 componentCcGhcOptions :: Verbosity -> GhcImplInfo -> LocalBuildInfo
                       -> BuildInfo -> ComponentLocalBuildInfo
                       -> FilePath -> FilePath
                       -> GhcOptions
-componentCcGhcOptions verbosity implInfo lbi bi clbi pref filename =
+componentCcGhcOptions verbosity _implInfo lbi bi clbi odir filename =
     mempty {
       ghcOptVerbosity      = toFlag verbosity,
       ghcOptMode           = toFlag GhcModeCompile,
       ghcOptInputFiles     = toNubListR [filename],
 
-      ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi, odir]
+      ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi clbi, odir]
                                           ++ PD.includeDirs bi,
       ghcOptPackageDBs     = withPackageDB lbi,
       ghcOptPackages       = toNubListR $ mkGhcOptPackages clbi,
@@ -361,10 +262,6 @@
                                   PD.ccOptions bi,
       ghcOptObjDir         = toFlag odir
     }
-  where
-    odir | hasCcOdirBug implInfo = pref </> takeDirectory filename
-         | otherwise             = pref
-         -- ghc 6.4.0 had a bug in -odir handling for C compilations.
 
 componentGhcOptions :: Verbosity -> LocalBuildInfo
                     -> BuildInfo -> ComponentLocalBuildInfo -> FilePath
@@ -374,21 +271,21 @@
       ghcOptVerbosity       = toFlag verbosity,
       ghcOptHideAllPackages = toFlag True,
       ghcOptCabal           = toFlag True,
-      ghcOptPackageKey  = case clbi of
-        LibComponentLocalBuildInfo { componentPackageKey = pk } -> toFlag pk
-        _ -> mempty,
-      ghcOptSigOf           = hole_insts,
+      ghcOptThisUnitId      = case clbi of
+        LibComponentLocalBuildInfo { componentCompatPackageKey = pk }
+          -> toFlag pk
+        _ -> Mon.mempty,
       ghcOptPackageDBs      = withPackageDB lbi,
       ghcOptPackages        = toNubListR $ mkGhcOptPackages clbi,
       ghcOptSplitObjs       = toFlag (splitObjs lbi),
       ghcOptSourcePathClear = toFlag True,
       ghcOptSourcePath      = toNubListR $ [odir] ++ (hsSourceDirs bi)
-                                           ++ [autogenModulesDir lbi],
-      ghcOptCppIncludePath  = toNubListR $ [autogenModulesDir lbi, odir]
+                                           ++ [autogenModulesDir lbi clbi],
+      ghcOptCppIncludePath  = toNubListR $ [autogenModulesDir lbi clbi, odir]
                                            ++ PD.includeDirs bi,
       ghcOptCppOptions      = toNubListR $ cppOptions bi,
       ghcOptCppIncludes     = toNubListR $
-                              [autogenModulesDir lbi </> cppHeaderName],
+                              [autogenModulesDir lbi clbi </> cppHeaderName],
       ghcOptFfiIncludes     = toNubListR $ PD.includes bi,
       ghcOptObjDir          = toFlag odir,
       ghcOptHiDir           = toFlag odir,
@@ -413,9 +310,6 @@
     toGhcDebugInfo NormalDebugInfo  = toFlag True
     toGhcDebugInfo MaximalDebugInfo = toFlag True
 
-    hole_insts = map (\(k,(p,n)) -> (k, (InstalledPackageInfo.packageKey p,n)))
-                 (instantiatedWith lbi)
-
 -- | Strip out flags that are not supported in ghci
 filterGhciFlags :: [String] -> [String]
 filterGhciFlags = filter supported
@@ -429,7 +323,7 @@
     supported "-unreg"    = False
     supported _           = True
 
-mkGHCiLibName :: LibraryName -> String
+mkGHCiLibName :: UnitId -> String
 mkGHCiLibName lib = getHSLibraryName lib <.> "o"
 
 ghcLookupProperty :: String -> Compiler -> Bool
@@ -442,11 +336,9 @@
 -- Module_split directory for each module.
 getHaskellObjects :: GhcImplInfo -> Library -> LocalBuildInfo
                   -> FilePath -> String -> Bool -> IO [FilePath]
-getHaskellObjects implInfo lib lbi pref wanted_obj_ext allow_split_objs
+getHaskellObjects _implInfo lib lbi pref wanted_obj_ext allow_split_objs
   | splitObjs lbi && allow_split_objs = do
-        let splitSuffix = if   noExtInSplitSuffix implInfo
-                          then "_split"
-                          else "_" ++ wanted_obj_ext ++ "_split"
+        let splitSuffix = "_" ++ wanted_obj_ext ++ "_split"
             dirs = [ pref </> (ModuleName.toFilePath x ++ splitSuffix)
                    | x <- libModules lib ]
         objss <- mapM getDirectoryContents dirs
@@ -460,10 +352,8 @@
                | x <- libModules lib ]
 
 mkGhcOptPackages :: ComponentLocalBuildInfo
-                 -> [(InstalledPackageId, PackageId, ModuleRenaming)]
-mkGhcOptPackages clbi =
-  map (\(i,p) -> (i,p,lookupRenaming p (componentPackageRenaming clbi)))
-      (componentPackageDeps clbi)
+                 -> [(UnitId, ModuleRenaming)]
+mkGhcOptPackages = componentIncludes
 
 substTopDir :: FilePath -> InstalledPackageInfo -> InstalledPackageInfo
 substTopDir topDir ipo
@@ -502,7 +392,8 @@
         unless (mPP == mcsPP) abort
     where
         lookupEnv :: String -> IO (Maybe String)
-        lookupEnv name = (Just `fmap` getEnv name) `catchIO` const (return Nothing)
+        lookupEnv name = (Just `fmap` getEnv name)
+                         `catchIO` const (return Nothing)
         abort =
             die $ "Use of " ++ compilerName ++ "'s environment variable "
                ++ packagePathEnvVar ++ " is incompatible with Cabal. Use the "
@@ -519,3 +410,20 @@
       ProfDetailToplevelFunctions   -> toFlag GhcProfAutoToplevel
       ProfDetailAllFunctions        -> toFlag GhcProfAutoAll
       ProfDetailOther _             -> mempty
+
+-- | GHC's rendering of it's host or target 'Arch' as used in its platform
+-- strings and certain file locations (such as user package db location).
+--
+showArchString :: Arch -> String
+showArchString PPC   = "powerpc"
+showArchString PPC64 = "powerpc64"
+showArchString other = display other
+
+-- | GHC's rendering of it's host or target 'OS' as used in its platform
+-- strings and certain file locations (such as user package db location).
+--
+showOsString :: OS -> String
+showOsString Windows = "mingw32"
+showOsString OSX     = "darwin"
+showOsString Solaris = "solaris2"
+showOsString other   = display other
diff --git a/cabal/Cabal/Distribution/Simple/GHCJS.hs b/cabal/Cabal/Distribution/Simple/GHCJS.hs
--- a/cabal/Cabal/Distribution/Simple/GHCJS.hs
+++ b/cabal/Cabal/Distribution/Simple/GHCJS.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE PatternGuards #-}
 module Distribution.Simple.GHCJS (
         configure, getInstalledPackages, getPackageDBContents,
         buildLib, buildExe,
@@ -15,70 +15,40 @@
         runCmd
   ) where
 
-import Distribution.Simple.GHC.ImplInfo ( getImplInfo, ghcjsVersionImplInfo )
+import Distribution.Simple.GHC.ImplInfo
 import qualified Distribution.Simple.GHC.Internal as Internal
 import Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(..), Executable(..)
-         , Library(..), libModules, exeModules
-         , hcOptions, hcProfOptions, hcSharedOptions
-         , allExtensions )
 import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo )
-import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
-                                ( InstalledPackageInfo(..) )
-import Distribution.Package ( LibraryName(..), getHSLibraryName )
+import Distribution.Package
 import Distribution.Simple.PackageIndex ( InstalledPackageIndex )
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )
 import qualified Distribution.Simple.Hpc as Hpc
-import Distribution.Simple.InstallDirs hiding ( absoluteInstallDirs )
 import Distribution.Simple.BuildPaths
 import Distribution.Simple.Utils
 import Distribution.Simple.Program
-         ( Program(..), ConfiguredProgram(..), ProgramConfiguration
-         , ProgramSearchPath
-         , rawSystemProgramConf
-         , rawSystemProgramStdout, rawSystemProgramStdoutConf
-         , getProgramInvocationOutput
-         , requireProgramVersion, requireProgram
-         , userMaybeSpecifyPath, programPath
-         , lookupProgram, addKnownPrograms
-         , ghcjsProgram, ghcjsPkgProgram, c2hsProgram, hsc2hsProgram
-         , ldProgram, haddockProgram, stripProgram )
 import qualified Distribution.Simple.Program.HcPkg as HcPkg
 import qualified Distribution.Simple.Program.Ar    as Ar
 import qualified Distribution.Simple.Program.Ld    as Ld
 import qualified Distribution.Simple.Program.Strip as Strip
 import Distribution.Simple.Program.GHC
-import Distribution.Simple.Setup
-         ( toFlag, fromFlag, configCoverage, configDistPref )
+import Distribution.Simple.Setup hiding ( Flag )
 import qualified Distribution.Simple.Setup as Cabal
-        ( Flag(..) )
-import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), CompilerId(..), Compiler(..)
-         , PackageDB(..), PackageDBStack, AbiTag(..) )
+import Distribution.Simple.Compiler hiding ( Flag )
 import Distribution.Version
-         ( Version(..), anyVersion, orLaterVersion )
 import Distribution.System
-         ( Platform(..) )
 import Distribution.Verbosity
 import Distribution.Utils.NubList
-         ( overNubListR, toNubListR )
-import Distribution.Text ( display )
-import Language.Haskell.Extension ( Extension(..)
-                                  , KnownExtension(..))
+import Distribution.Text
+import Language.Haskell.Extension
 
 import Control.Monad            ( unless, when )
 import Data.Char                ( isSpace )
 import qualified Data.Map as M  ( fromList  )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid              ( Monoid(..) )
-#endif
+import Data.Monoid as Mon       ( Monoid(..) )
 import System.Directory         ( doesFileExist )
-import System.FilePath          ( (</>), (<.>), takeExtension,
-                                  takeDirectory, replaceExtension,
-                                  splitExtension )
+import System.FilePath          ( (</>), (<.>), takeExtension
+                                , takeDirectory, replaceExtension )
 
 configure :: Verbosity -> Maybe FilePath -> Maybe FilePath
           -> ProgramConfiguration
@@ -155,24 +125,24 @@
 ghcjsNativeToo = Internal.ghcLookupProperty "Native Too"
 
 guessGhcjsPkgFromGhcjsPath :: ConfiguredProgram -> Verbosity
-                           -> ProgramSearchPath -> IO (Maybe FilePath)
+                           -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath]))
 guessGhcjsPkgFromGhcjsPath = guessToolFromGhcjsPath ghcjsPkgProgram
 
 guessHsc2hsFromGhcjsPath :: ConfiguredProgram -> Verbosity
-                         -> ProgramSearchPath -> IO (Maybe FilePath)
+                         -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath]))
 guessHsc2hsFromGhcjsPath = guessToolFromGhcjsPath hsc2hsProgram
 
 guessC2hsFromGhcjsPath :: ConfiguredProgram -> Verbosity
-                       -> ProgramSearchPath -> IO (Maybe FilePath)
+                       -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath]))
 guessC2hsFromGhcjsPath = guessToolFromGhcjsPath c2hsProgram
 
 guessHaddockFromGhcjsPath :: ConfiguredProgram -> Verbosity
-                          -> ProgramSearchPath -> IO (Maybe FilePath)
+                          -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath]))
 guessHaddockFromGhcjsPath = guessToolFromGhcjsPath haddockProgram
 
 guessToolFromGhcjsPath :: Program -> ConfiguredProgram
                        -> Verbosity -> ProgramSearchPath
-                       -> IO (Maybe FilePath)
+                       -> IO (Maybe (FilePath, [FilePath]))
 guessToolFromGhcjsPath tool ghcjsProg verbosity searchpath
   = do let toolname          = programName tool
            path              = programPath ghcjsProg
@@ -197,19 +167,15 @@
                    -- method.
          []     -> programFindLocation tool verbosity searchpath
          (fp:_) -> do info verbosity $ "found " ++ toolname ++ " in " ++ fp
-                      return (Just fp)
+                      let lookedAt = map fst
+                                   . takeWhile (\(_file, exist) -> not exist)
+                                   $ zip guesses exists
+                      return (Just (fp, lookedAt))
 
   where takeVersionSuffix :: FilePath -> String
         takeVersionSuffix = reverse . takeWhile (`elem ` "0123456789.-") .
                             reverse
 
-        dropExeExtension :: FilePath -> FilePath
-        dropExeExtension filepath =
-          case splitExtension filepath of
-            (filepath', extension) | extension == exeExtension -> filepath'
-                                   | otherwise                 -> filepath
-
-
 -- | Given a single package DB, return all installed packages.
 getPackageDBContents :: Verbosity -> PackageDB -> ProgramConfiguration
                      -> IO InstalledPackageIndex
@@ -301,7 +267,7 @@
                -> PackageDescription -> LocalBuildInfo
                -> Library            -> ComponentLocalBuildInfo -> IO ()
 buildOrReplLib forRepl verbosity numJobs _pkg_descr lbi lib clbi = do
-  let libName@(LibraryName cname) = componentLibraryName clbi
+  let uid = componentUnitId clbi
       libTargetDir = buildDir lbi
       whenVanillaLib forceVanilla =
         when (not forRepl && (forceVanilla || withVanillaLib lbi))
@@ -310,14 +276,13 @@
         when (not forRepl &&  (forceShared || withSharedLib lbi))
       whenGHCiLib = when (not forRepl && withGHCiLib lbi && withVanillaLib lbi)
       ifReplLib = when forRepl
-      comp = compiler lbi
-      implInfo = getImplInfo comp
-      hole_insts = map (\(k,(p,n)) -> (k,(InstalledPackageInfo.packageKey p,n)))
-                       (instantiatedWith lbi)
+      comp      = compiler lbi
+      platform  = hostPlatform lbi
+      implInfo  = getImplInfo comp
       nativeToo = ghcjsNativeToo comp
 
   (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi)
-  let runGhcjsProg        = runGHC verbosity ghcjsProg comp
+  let runGhcjsProg        = runGHC verbosity ghcjsProg comp platform
       libBi               = libBuildInfo lib
       isGhcjsDynamic      = isDynamic comp
       dynamicTooSupported = supportsDynamicToo comp
@@ -329,10 +294,11 @@
   -- Determine if program coverage should be enabled and if so, what
   -- '-hpcdir' should be.
   let isCoverageEnabled = fromFlag $ configCoverage $ configFlags lbi
+      pkg_name = display $ PD.package $ localPkgDescr lbi
       distPref = fromFlag $ configDistPref $ configFlags lbi
       hpcdir way
-        | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way cname
-        | otherwise = mempty
+        | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name
+        | otherwise = Mon.mempty
 
   createDirectoryIfMissingVerbose verbosity True libTargetDir
   -- TODO: do we need to put hs-boot files into place for mutually recursive
@@ -342,14 +308,13 @@
       baseOpts    = componentGhcOptions verbosity lbi libBi clbi libTargetDir
       linkJsLibOpts = mempty {
                         ghcOptExtra = toNubListR $
-                          [ "-link-js-lib"     , getHSLibraryName libName
+                          [ "-link-js-lib"     , getHSLibraryName uid
                           , "-js-lib-outputdir", libTargetDir ] ++
                           concatMap (\x -> ["-js-lib-src",x]) jsSrcs
                       }
       vanillaOptsNoJsLib = baseOpts `mappend` mempty {
                       ghcOptMode         = toFlag GhcModeMake,
                       ghcOptNumJobs      = numJobs,
-                      ghcOptSigOf        = hole_insts,
                       ghcOptInputModules = toNubListR $ libModules lib,
                       ghcOptHPCDir       = hpcdir Hpc.Vanilla
                     }
@@ -457,11 +422,11 @@
                       (cSources libBi)
         cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension))
                       (cSources libBi)
-        cid = compilerId (compiler lbi)
-        vanillaLibFilePath = libTargetDir </> mkLibName            libName
-        profileLibFilePath = libTargetDir </> mkProfLibName        libName
-        sharedLibFilePath  = libTargetDir </> mkSharedLibName cid  libName
-        ghciLibFilePath    = libTargetDir </> Internal.mkGHCiLibName libName
+        compiler_id = compilerId (compiler lbi)
+        vanillaLibFilePath = libTargetDir </> mkLibName            uid
+        profileLibFilePath = libTargetDir </> mkProfLibName        uid
+        sharedLibFilePath  = libTargetDir </> mkSharedLibName compiler_id uid
+        ghciLibFilePath    = libTargetDir </> Internal.mkGHCiLibName uid
 
     hObjs     <- Internal.getHaskellObjects implInfo lib lbi
                       libTargetDir objExtension True
@@ -499,6 +464,8 @@
                 ghcOptDynLinkMode        = toFlag GhcDynamicOnly,
                 ghcOptInputFiles         = toNubListR dynamicObjectFiles,
                 ghcOptOutputFile         = toFlag sharedLibFilePath,
+                ghcOptExtra              = toNubListR $
+                                           ghcjsSharedOptions libBi,
                 ghcOptNoAutoLinkPackages = toFlag True,
                 ghcOptPackageDBs         = withPackageDB lbi,
                 ghcOptPackages           = toNubListR $
@@ -522,16 +489,16 @@
         runGhcjsProg ghcSharedLinkArgs
 
 -- | Start a REPL without loading any source files.
-startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler
+startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler -> Platform
                  -> PackageDBStack -> IO ()
-startInterpreter verbosity conf comp packageDBs = do
+startInterpreter verbosity conf comp platform packageDBs = do
   let replOpts = mempty {
         ghcOptMode       = toFlag GhcModeInteractive,
         ghcOptPackageDBs = packageDBs
         }
   checkPackageDbStack packageDBs
   (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram conf
-  runGHC verbosity ghcjsProg comp replOpts
+  runGHC verbosity ghcjsProg comp platform replOpts
 
 buildExe, replExe :: Verbosity          -> Cabal.Flag (Maybe Int)
                   -> PackageDescription -> LocalBuildInfo
@@ -547,8 +514,9 @@
 
   (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi)
   let comp         = compiler lbi
+      platform     = hostPlatform lbi
       implInfo     = getImplInfo comp
-      runGhcjsProg = runGHC verbosity ghcjsProg comp
+      runGhcjsProg = runGHC verbosity ghcjsProg comp platform
       exeBi        = buildInfo exe
 
   -- exeNameReal, the name that GHC really uses (with .exe on Windows)
@@ -755,12 +723,12 @@
       findModuleFiles [builtDir] [ext] (libModules lib)
       >>= installOrdinaryFiles verbosity targetDir
 
-    cid = compilerId (compiler lbi)
-    libName = componentLibraryName clbi
-    vanillaLibName = mkLibName              libName
-    profileLibName = mkProfLibName          libName
-    ghciLibName    = Internal.mkGHCiLibName libName
-    sharedLibName  = (mkSharedLibName cid)  libName
+    compiler_id = compilerId (compiler lbi)
+    uid = componentUnitId clbi
+    vanillaLibName = mkLibName              uid
+    profileLibName = mkProfLibName          uid
+    ghciLibName    = Internal.mkGHCiLibName uid
+    sharedLibName  = (mkSharedLibName compiler_id)  uid
 
     hasLib    = not $ null (libModules lib)
                    && null (cSources (libBuildInfo lib))
@@ -800,11 +768,12 @@
   let
       libBi       = libBuildInfo lib
       comp        = compiler lbi
+      platform    = hostPlatform lbi
       vanillaArgs =
         (componentGhcOptions verbosity lbi libBi clbi (buildDir lbi))
         `mappend` mempty {
           ghcOptMode         = toFlag GhcModeAbiHash,
-          ghcOptInputModules = toNubListR $ exposedModules lib
+          ghcOptInputModules = toNubListR $ PD.exposedModules lib
         }
       profArgs = adjustExts "js_p_hi" "js_p_o" vanillaArgs `mappend` mempty {
                      ghcOptProfilingMode = toFlag True,
@@ -815,7 +784,8 @@
            else error "libAbiHash: Can't find an enabled library way"
   --
   (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi)
-  getProgramInvocationOutput verbosity (ghcInvocation ghcjsProg comp ghcArgs)
+  getProgramInvocationOutput verbosity
+    (ghcInvocation ghcjsProg comp platform ghcArgs)
 
 adjustExts :: String -> String -> GhcOptions -> GhcOptions
 adjustExts hiSuf objSuf opts =
@@ -825,16 +795,20 @@
   }
 
 registerPackage :: Verbosity
-                -> InstalledPackageInfo
-                -> PackageDescription
-                -> LocalBuildInfo
-                -> Bool
+                -> ProgramConfiguration
+                -> HcPkg.MultiInstance
                 -> PackageDBStack
+                -> InstalledPackageInfo
                 -> IO ()
-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =
-  HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity packageDbs
-    (Right installedPkgInfo)
+registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo
+  | HcPkg.MultiInstance <- multiInstance
+  = HcPkg.registerMultiInstance (hcPkgInfo progdb) verbosity
+      packageDbs installedPkgInfo
 
+  | otherwise
+  = HcPkg.reregister (hcPkgInfo progdb) verbosity
+      packageDbs (Right installedPkgInfo)
+
 componentGhcOptions :: Verbosity -> LocalBuildInfo
                     -> BuildInfo -> ComponentLocalBuildInfo -> FilePath
                     -> GhcOptions
@@ -874,7 +848,10 @@
                                  , HcPkg.noPkgDbStack    = False
                                  , HcPkg.noVerboseFlag   = False
                                  , HcPkg.flagPackageConf = False
-                                 , HcPkg.useSingleFileDb = v < [7,9]
+                                 , HcPkg.supportsDirDbs  = True
+                                 , HcPkg.requiresDirDbs  = v >= [7,10]
+                                 , HcPkg.nativeMultiInstance  = v >= [7,10]
+                                 , HcPkg.recacheMultiInstance = True
                                  }
   where
     v                 = versionBranch ver
diff --git a/cabal/Cabal/Distribution/Simple/Haddock.hs b/cabal/Cabal/Distribution/Simple/Haddock.hs
--- a/cabal/Cabal/Distribution/Simple/Haddock.hs
+++ b/cabal/Cabal/Distribution/Simple/Haddock.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE DeriveGeneric #-}
+
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.Haddock
@@ -24,76 +26,42 @@
 import qualified Distribution.Simple.GHCJS as GHCJS
 
 -- local
+import Distribution.Compat.Semigroup as Semi
 import Distribution.Package
-         ( PackageIdentifier(..)
-         , Package(..)
-         , PackageName(..), packageName, LibraryName(..) )
 import qualified Distribution.ModuleName as ModuleName
-import Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(..), usedExtensions
-         , hcSharedOptions
-         , Library(..), hasLibs, Executable(..)
-         , TestSuite(..), TestSuiteInterface(..)
-         , Benchmark(..), BenchmarkInterface(..) )
-import Distribution.Simple.Compiler
-         ( Compiler, compilerInfo, CompilerFlavor(..)
-         , compilerFlavor, compilerCompatVersion )
+import Distribution.PackageDescription as PD hiding (Flag)
+import Distribution.Simple.Compiler hiding (Flag)
 import Distribution.Simple.Program.GHC
-         ( GhcOptions(..), GhcDynLinkMode(..), renderGhcOptions )
 import Distribution.Simple.Program
-         ( ConfiguredProgram(..), lookupProgramVersion, requireProgramVersion
-         , rawSystemProgram, rawSystemProgramStdout
-         , hscolourProgram, haddockProgram )
 import Distribution.Simple.PreProcess
-         ( PPSuffixHandler, preprocessComponent)
 import Distribution.Simple.Setup
-         ( defaultHscolourFlags
-         , Flag(..), toFlag, flagToMaybe, flagToList, fromFlag
-         , HaddockFlags(..), HscolourFlags(..) )
-import Distribution.Simple.Build (initialBuildSteps)
+import Distribution.Simple.Build
 import Distribution.Simple.InstallDirs
-         ( InstallDirs(..)
-         , PathTemplateEnv, PathTemplate, PathTemplateVariable(..)
-         , toPathTemplate, fromPathTemplate
-         , substPathTemplate, initialPathTemplateEnv )
-import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), Component(..), ComponentLocalBuildInfo(..)
-         , withAllComponentsInBuildOrder )
+import Distribution.Simple.LocalBuildInfo hiding (substPathTemplate)
 import Distribution.Simple.BuildPaths
-         ( haddockName, hscolourPref, autogenModulesDir)
-import Distribution.Simple.PackageIndex (dependencyClosure)
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
-         ( InstalledPackageInfo(..) )
-import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo )
+import Distribution.InstalledPackageInfo ( InstalledPackageInfo )
 import Distribution.Simple.Utils
-         ( die, copyFileTo, warn, notice, intercalate, setupMessage
-         , createDirectoryIfMissingVerbose
-         , TempFileOptions(..), defaultTempFileOptions
-         , withTempFileEx, copyFileVerbose
-         , withTempDirectoryEx, matchFileGlob
-         , findFileWithExtension, findFile )
+import Distribution.System
 import Distribution.Text
-         ( display, simpleParse )
 import Distribution.Utils.NubList
-         ( toNubListR )
-
+import Distribution.Version
 import Distribution.Verbosity
 import Language.Haskell.Extension
 
 
 import Control.Monad    ( when, forM_ )
+import Data.Char        ( isSpace )
 import Data.Either      ( rights )
-import Data.Foldable    ( traverse_ )
-import Data.Monoid
+import Data.Foldable    ( traverse_, foldl' )
 import Data.Maybe       ( fromMaybe, listToMaybe )
+import GHC.Generics     ( Generic )
 
 import System.Directory (doesFileExist)
 import System.FilePath  ( (</>), (<.>)
                         , normalise, splitPath, joinPath, isAbsolute )
-import System.IO        (hClose, hPutStrLn, hSetEncoding, utf8)
-import Distribution.Version
+import System.IO        (hClose, hPutStr, hPutStrLn, hSetEncoding, utf8)
 
 -- ------------------------------------------------------------------------------
 -- Types
@@ -132,7 +100,7 @@
  -- ^ To find the correct GHC, required.
  argTargets :: [FilePath]
  -- ^ Modules to process.
-}
+} deriving Generic
 
 -- | The FilePath of a directory, it's a monoid under '(</>)'.
 newtype Directory = Dir { unDir' :: FilePath } deriving (Read,Show,Eq,Ord)
@@ -162,7 +130,28 @@
         ++ "a library. Perhaps you want to use the --executables, --tests or"
         ++ " --benchmarks flags."
 
-haddock pkg_descr lbi suffixes flags = do
+haddock pkg_descr lbi suffixes flags' = do
+    let verbosity     = flag haddockVerbosity
+        comp          = compiler lbi
+        platform      = hostPlatform lbi
+
+        flags
+          | fromFlag (haddockForHackage flags') = flags'
+            { haddockHoogle       = Flag True
+            , haddockHtml         = Flag True
+            , haddockHtmlLocation = Flag (pkg_url ++ "/docs")
+            , haddockContents     = Flag (toPathTemplate pkg_url)
+            , haddockHscolour     = Flag True
+            }
+          | otherwise = flags'
+        pkg_url       = "/package/$pkg-$version"
+        flag f        = fromFlag $ f flags
+
+        tmpFileOpts   = defaultTempFileOptions
+                       { optKeepTempFiles = flag haddockKeepTempFiles }
+        htmlTemplate  = fmap toPathTemplate . flagToMaybe . haddockHtmlLocation
+                        $ flags
+
     setupMessage verbosity "Running Haddock for" (packageId pkg_descr)
     (confHaddock, version, _) <-
       requireProgramVersion verbosity haddockProgram
@@ -188,8 +177,6 @@
 
     -- the tools match the requests, we can proceed
 
-    initialBuildSteps (flag haddockDistPref) pkg_descr lbi verbosity
-
     when (flag haddockHscolour) $
       hscolour' (warn verbosity) pkg_descr lbi suffixes
       (defaultHscolourFlags `mappend` haddockToHscolour flags)
@@ -198,11 +185,12 @@
     let commonArgs = mconcat
             [ libdirArgs
             , fromFlags (haddockTemplateEnv lbi (packageId pkg_descr)) flags
-            , fromPackageDescription pkg_descr ]
+            , fromPackageDescription forDist pkg_descr ]
+        forDist = fromFlagOrDefault False (haddockForHackage flags)
 
-    let pre c = preprocessComponent pkg_descr c lbi False verbosity suffixes
     withAllComponentsInBuildOrder pkg_descr lbi $ \component clbi -> do
-      pre component
+      initialBuildSteps (flag haddockDistPref) pkg_descr lbi clbi verbosity
+      preprocessComponent pkg_descr component lbi clbi False verbosity suffixes
       let
         doExe com = case (compToExe com) of
           Just exe -> do
@@ -211,7 +199,8 @@
                 exeArgs <- fromExecutable verbosity tmp lbi exe clbi htmlTemplate
                            version
                 let exeArgs' = commonArgs `mappend` exeArgs
-                runHaddock verbosity tmpFileOpts comp confHaddock exeArgs'
+                runHaddock verbosity tmpFileOpts comp platform
+                  confHaddock exeArgs'
           Nothing -> do
            warn (fromFlag $ haddockVerbosity flags)
              "Unsupported component, skipping..."
@@ -223,7 +212,7 @@
               libArgs <- fromLibrary verbosity tmp lbi lib clbi htmlTemplate
                          version
               let libArgs' = commonArgs `mappend` libArgs
-              runHaddock verbosity tmpFileOpts comp confHaddock libArgs'
+              runHaddock verbosity tmpFileOpts comp platform confHaddock libArgs'
         CExe   _ -> when (flag haddockExecutables) $ doExe component
         CTest  _ -> when (flag haddockTestSuites)  $ doExe component
         CBench _ -> when (flag haddockBenchmarks)  $ doExe component
@@ -231,14 +220,6 @@
     forM_ (extraDocFiles pkg_descr) $ \ fpath -> do
       files <- matchFileGlob fpath
       forM_ files $ copyFileTo verbosity (unDir $ argOutputDir commonArgs)
-  where
-    verbosity     = flag haddockVerbosity
-    keepTempFiles = flag haddockKeepTempFiles
-    comp          = compiler lbi
-    tmpFileOpts   = defaultTempFileOptions { optKeepTempFiles = keepTempFiles }
-    flag f        = fromFlag $ f flags
-    htmlTemplate  = fmap toPathTemplate . flagToMaybe . haddockHtmlLocation
-                    $ flags
 
 -- ------------------------------------------------------------------------------
 -- Contributions to HaddockArgs.
@@ -266,12 +247,11 @@
       argOutputDir = maybe mempty Dir . flagToMaybe $ haddockDistPref flags
     }
 
-fromPackageDescription :: PackageDescription -> HaddockArgs
-fromPackageDescription pkg_descr =
+fromPackageDescription :: Bool -> PackageDescription -> HaddockArgs
+fromPackageDescription forDist pkg_descr =
       mempty { argInterfaceFile = Flag $ haddockName pkg_descr,
                argPackageName = Flag $ packageId $ pkg_descr,
-               argOutputDir = Dir $ "doc" </> "html"
-                              </> display (packageName pkg_descr),
+               argOutputDir = Dir $ "doc" </> "html" </> name,
                argPrologue = Flag $ if null desc then synopsis pkg_descr
                                     else desc,
                argTitle = Flag $ showPkg ++ subtitle
@@ -279,6 +259,9 @@
       where
         desc = PD.description pkg_descr
         showPkg = display (packageId pkg_descr)
+        name
+          | forDist = showPkg ++ "-docs"
+          | otherwise = display (packageName pkg_descr)
         subtitle | null (synopsis pkg_descr) = ""
                  | otherwise                 = ": " ++ synopsis pkg_descr
 
@@ -301,7 +284,7 @@
             -> Version
             -> IO HaddockArgs
 fromLibrary verbosity tmp lbi lib clbi htmlTemplate haddockVersion = do
-    inFiles <- map snd `fmap` getLibSourceFiles lbi lib
+    inFiles <- map snd `fmap` getLibSourceFiles lbi lib clbi
     ifaceArgs <- getInterfaces verbosity lbi clbi htmlTemplate
     let vanillaOpts = (componentGhcOptions normal lbi bi clbi (buildDir lbi)) {
                           -- Noooooooooo!!!!!111
@@ -346,7 +329,7 @@
                -> Version
                -> IO HaddockArgs
 fromExecutable verbosity tmp lbi exe clbi htmlTemplate haddockVersion = do
-    inFiles <- map snd `fmap` getExeSourceFiles lbi exe
+    inFiles <- map snd `fmap` getExeSourceFiles lbi exe clbi
     ifaceArgs <- getInterfaces verbosity lbi clbi htmlTemplate
     let vanillaOpts = (componentGhcOptions normal lbi bi clbi (buildDir lbi)) {
                           -- Noooooooooo!!!!!111
@@ -444,13 +427,14 @@
 runHaddock :: Verbosity
               -> TempFileOptions
               -> Compiler
+              -> Platform
               -> ConfiguredProgram
               -> HaddockArgs
               -> IO ()
-runHaddock verbosity tmpFileOpts comp confHaddock args = do
+runHaddock verbosity tmpFileOpts comp platform confHaddock args = do
   let haddockVersion = fromMaybe (error "unable to determine haddock version")
                        (programVersion confHaddock)
-  renderArgs verbosity tmpFileOpts haddockVersion comp args $
+  renderArgs verbosity tmpFileOpts haddockVersion comp platform args $
     \(flags,result)-> do
 
       rawSystemProgram verbosity confHaddock flags
@@ -462,12 +446,13 @@
               -> TempFileOptions
               -> Version
               -> Compiler
+              -> Platform
               -> HaddockArgs
               -> (([String], FilePath) -> IO a)
               -> IO a
-renderArgs verbosity tmpFileOpts version comp args k = do
+renderArgs verbosity tmpFileOpts version comp platform args k = do
   let haddockSupportsUTF8          = version >= Version [2,14,4] []
-      haddockSupportsResponseFiles = version >  Version [2,16,1] []
+      haddockSupportsResponseFiles = version >  Version [2,16,2] []
   createDirectoryIfMissingVerbose verbosity True outputDir
   withTempFileEx tmpFileOpts outputDir "haddock-prologue.txt" $
     \prologueFileName h -> do
@@ -476,13 +461,13 @@
              hPutStrLn h $ fromFlag $ argPrologue args
              hClose h
              let pflag = "--prologue=" ++ prologueFileName
-                 renderedArgs = pflag : renderPureArgs version comp args
-             if haddockSupportsResponseFiles 
+                 renderedArgs = pflag : renderPureArgs version comp platform args
+             if haddockSupportsResponseFiles
                then
                  withTempFileEx tmpFileOpts outputDir "haddock-response.txt" $
                     \responseFileName hf -> do
                          when haddockSupportsUTF8 (hSetEncoding hf utf8)
-                         mapM_ (hPutStrLn hf) renderedArgs
+                         hPutStr hf $ unlines $ map escapeArg renderedArgs
                          hClose hf
                          let respFile = "@" ++ responseFileName
                          k ([respFile], result)
@@ -500,9 +485,22 @@
               pkgstr = display $ packageName pkgid
               pkgid = arg argPackageName
       arg f = fromFlag $ f args
+      -- Support a gcc-like response file syntax.  Each separate
+      -- argument and its possible parameter(s), will be separated in the
+      -- response file by an actual newline; all other whitespace,
+      -- single quotes, double quotes, and the character used for escaping
+      -- (backslash) are escaped.  The called program will need to do a similar
+      -- inverse operation to de-escape and re-constitute the argument list.
+      escape cs c
+        |    isSpace c
+          || '\\' == c
+          || '\'' == c
+          || '"'  == c = c:'\\':cs -- n.b., our caller must reverse the result
+        | otherwise    = c:cs
+      escapeArg = reverse . foldl' escape []
 
-renderPureArgs :: Version -> Compiler -> HaddockArgs -> [String]
-renderPureArgs version comp args = concat
+renderPureArgs :: Version -> Compiler -> Platform -> HaddockArgs -> [String]
+renderPureArgs version comp platform args = concat
     [ (:[]) . (\f -> "--dump-interface="++ unDir (argOutputDir args) </> f)
       . fromFlag . argInterfaceFile $ args
 
@@ -544,7 +542,7 @@
       . fromFlag . argTitle $ args
 
     , [ "--optghc=" ++ opt | (opts, _ghcVer) <- flagToList (argGhcOptions args)
-                           , opt <- renderGhcOptions comp opts ]
+                           , opt <- renderGhcOptions comp platform opts ]
 
     , maybe [] (\l -> ["-B"++l]) $
       flagToMaybe (argGhcLibDir args) -- error if Nothing?
@@ -616,7 +614,7 @@
 haddockPackageFlags lbi clbi htmlTemplate = do
   let allPkgs = installedPkgs lbi
       directDeps = map fst (componentPackageDeps clbi)
-  transitiveDeps <- case dependencyClosure allPkgs directDeps of
+  transitiveDeps <- case PackageIndex.dependencyClosure allPkgs directDeps of
     Left x    -> return x
     Right inf -> die $ "internal error when calculating transitive "
                     ++ "package dependencies.\nDebug info: " ++ show inf
@@ -631,7 +629,9 @@
 haddockTemplateEnv :: LocalBuildInfo -> PackageIdentifier -> PathTemplateEnv
 haddockTemplateEnv lbi pkg_id =
   (PrefixVar, prefix (installDirTemplates lbi))
-  : initialPathTemplateEnv pkg_id (LibraryName (display pkg_id)) (compilerInfo (compiler lbi))
+  -- We want the legacy unit ID here, because it gives us nice paths
+  -- (Haddock people don't care about the dependencies)
+  : initialPathTemplateEnv pkg_id (mkLegacyUnitId pkg_id) (compilerInfo (compiler lbi))
   (hostPlatform lbi)
 
 -- ------------------------------------------------------------------------------
@@ -643,13 +643,7 @@
          -> HscolourFlags
          -> IO ()
 hscolour pkg_descr lbi suffixes flags = do
-  -- we preprocess even if hscolour won't be found on the machine
-  -- will this upset someone?
-  initialBuildSteps distPref pkg_descr lbi verbosity
   hscolour' die pkg_descr lbi suffixes flags
- where
-   verbosity  = fromFlag (hscolourVerbosity flags)
-   distPref = fromFlag $ hscolourDistPref flags
 
 hscolour' :: (String -> IO ()) -- ^ Called when the 'hscolour' exe is not found.
           -> PackageDescription
@@ -668,15 +662,15 @@
       createDirectoryIfMissingVerbose verbosity True $
         hscolourPref distPref pkg_descr
 
-      let pre c = preprocessComponent pkg_descr c lbi False verbosity suffixes
-      withAllComponentsInBuildOrder pkg_descr lbi $ \comp _ -> do
-        pre comp
+      withAllComponentsInBuildOrder pkg_descr lbi $ \comp clbi -> do
+        initialBuildSteps distPref pkg_descr lbi clbi verbosity
+        preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes
         let
           doExe com = case (compToExe com) of
             Just exe -> do
               let outputDir = hscolourPref distPref pkg_descr
                               </> exeName exe </> "src"
-              runHsColour hscolourProg outputDir =<< getExeSourceFiles lbi exe
+              runHsColour hscolourProg outputDir =<< getExeSourceFiles lbi exe clbi
             Nothing -> do
               warn (fromFlag $ hscolourVerbosity flags)
                 "Unsupported component, skipping..."
@@ -684,7 +678,7 @@
         case comp of
           CLib lib -> do
             let outputDir = hscolourPref distPref pkg_descr </> "src"
-            runHsColour hscolourProg outputDir =<< getLibSourceFiles lbi lib
+            runHsColour hscolourProg outputDir =<< getLibSourceFiles lbi lib clbi
           CExe   _ -> when (fromFlag (hscolourExecutables flags)) $ doExe comp
           CTest  _ -> when (fromFlag (hscolourTestSuites  flags)) $ doExe comp
           CBench _ -> when (fromFlag (hscolourBenchmarks  flags)) $ doExe comp
@@ -726,24 +720,26 @@
 
 getLibSourceFiles :: LocalBuildInfo
                      -> Library
+                     -> ComponentLocalBuildInfo
                      -> IO [(ModuleName.ModuleName, FilePath)]
-getLibSourceFiles lbi lib = getSourceFiles searchpaths modules
+getLibSourceFiles lbi lib clbi = getSourceFiles searchpaths modules
   where
     bi               = libBuildInfo lib
     modules          = PD.exposedModules lib ++ otherModules bi
-    searchpaths      = autogenModulesDir lbi : buildDir lbi : hsSourceDirs bi
+    searchpaths      = autogenModulesDir lbi clbi : buildDir lbi : hsSourceDirs bi
 
 getExeSourceFiles :: LocalBuildInfo
                      -> Executable
+                     -> ComponentLocalBuildInfo
                      -> IO [(ModuleName.ModuleName, FilePath)]
-getExeSourceFiles lbi exe = do
+getExeSourceFiles lbi exe clbi = do
     moduleFiles <- getSourceFiles searchpaths modules
     srcMainPath <- findFile (hsSourceDirs bi) (modulePath exe)
     return ((ModuleName.main, srcMainPath) : moduleFiles)
   where
     bi          = buildInfo exe
     modules     = otherModules bi
-    searchpaths = autogenModulesDir lbi : exeBuildDir lbi exe : hsSourceDirs bi
+    searchpaths = autogenModulesDir lbi clbi : exeBuildDir lbi exe : hsSourceDirs bi
 
 getSourceFiles :: [FilePath]
                   -> [ModuleName.ModuleName]
@@ -761,44 +757,15 @@
 -- ------------------------------------------------------------------------------
 -- Boilerplate Monoid instance.
 instance Monoid HaddockArgs where
-    mempty = HaddockArgs {
-                argInterfaceFile = mempty,
-                argPackageName = mempty,
-                argHideModules = mempty,
-                argIgnoreExports = mempty,
-                argLinkSource = mempty,
-                argCssFile = mempty,
-                argContents = mempty,
-                argVerbose = mempty,
-                argOutput = mempty,
-                argInterfaces = mempty,
-                argOutputDir = mempty,
-                argTitle = mempty,
-                argPrologue = mempty,
-                argGhcOptions = mempty,
-                argGhcLibDir = mempty,
-                argTargets = mempty
-             }
-    mappend a b = HaddockArgs {
-                argInterfaceFile = mult argInterfaceFile,
-                argPackageName = mult argPackageName,
-                argHideModules = mult argHideModules,
-                argIgnoreExports = mult argIgnoreExports,
-                argLinkSource = mult argLinkSource,
-                argCssFile = mult argCssFile,
-                argContents = mult argContents,
-                argVerbose = mult argVerbose,
-                argOutput = mult argOutput,
-                argInterfaces = mult argInterfaces,
-                argOutputDir = mult argOutputDir,
-                argTitle = mult argTitle,
-                argPrologue = mult argPrologue,
-                argGhcOptions = mult argGhcOptions,
-                argGhcLibDir = mult argGhcLibDir,
-                argTargets = mult argTargets
-             }
-      where mult f = f a `mappend` f b
+    mempty = gmempty
+    mappend = (Semi.<>)
 
+instance Semigroup HaddockArgs where
+    (<>) = gmappend
+
 instance Monoid Directory where
     mempty = Dir "."
-    mappend (Dir m) (Dir n) = Dir $ m </> n
+    mappend = (Semi.<>)
+
+instance Semigroup Directory where
+    Dir m <> Dir n = Dir $ m </> n
diff --git a/cabal/Cabal/Distribution/Simple/HaskellSuite.hs b/cabal/Cabal/Distribution/Simple/HaskellSuite.hs
--- a/cabal/Cabal/Distribution/Simple/HaskellSuite.hs
+++ b/cabal/Cabal/Distribution/Simple/HaskellSuite.hs
@@ -1,10 +1,6 @@
-{-# LANGUAGE CPP #-}
 module Distribution.Simple.HaskellSuite where
 
 import Control.Monad
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative
-#endif
 import Data.Maybe
 import Data.Version
 import qualified Data.Map as M (empty)
@@ -24,7 +20,6 @@
 import Distribution.Compat.Exception
 import Language.Haskell.Extension
 import Distribution.Simple.Program.Builtin
-  (haskellSuiteProgram, haskellSuitePkgProgram)
 
 configure
   :: Verbosity -> Maybe FilePath -> Maybe FilePath
@@ -60,7 +55,7 @@
       let
         haskellSuiteProgram' =
           haskellSuiteProgram
-            { programFindLocation = \v _p -> findProgramLocation v hcPath }
+            { programFindLocation = \v p -> findProgramOnSearchPath v p hcPath }
 
       -- NB: cannot call requireProgram right away — it'd think that
       -- the program is already configured and won't reconfigure it again.
@@ -106,7 +101,7 @@
 getExtensions :: Verbosity -> ConfiguredProgram -> IO [(Extension, Compiler.Flag)]
 getExtensions verbosity prog = do
   extStrs <-
-    lines <$>
+    lines `fmap`
     rawSystemStdout verbosity (programPath prog) ["--supported-extensions"]
   return
     [ (ext, "-X" ++ display ext) | Just ext <- map simpleParse extStrs ]
@@ -114,7 +109,7 @@
 getLanguages :: Verbosity -> ConfiguredProgram -> IO [(Language, Compiler.Flag)]
 getLanguages verbosity prog = do
   langStrs <-
-    lines <$>
+    lines `fmap`
     rawSystemStdout verbosity (programPath prog) ["--supported-languages"]
   return
     [ (ext, "-G" ++ display ext) | Just ext <- map simpleParse langStrs ]
@@ -170,7 +165,7 @@
   runDbProgram verbosity haskellSuiteProgram conf $
     [ "compile", "--build-dir", odir ] ++
     concat [ ["-i", d] | d <- srcDirs ] ++
-    concat [ ["-I", d] | d <- [autogenModulesDir lbi, odir] ++ includeDirs bi ] ++
+    concat [ ["-I", d] | d <- [autogenModulesDir lbi clbi, odir] ++ includeDirs bi ] ++
     [ packageDbOpt pkgDb | pkgDb <- dbStack ] ++
     [ "--package-name", display pkgid ] ++
     concat [ ["--package-id", display ipkgid ]
@@ -190,8 +185,9 @@
   -> FilePath  -- ^Build location
   -> PackageDescription
   -> Library
+  -> ComponentLocalBuildInfo
   -> IO ()
-installLib verbosity lbi targetDir dynlibTargetDir builtDir pkg lib = do
+installLib verbosity lbi targetDir dynlibTargetDir builtDir pkg lib _clbi = do
   let conf = withPrograms lbi
   runDbProgram verbosity haskellSuitePkgProgram conf $
     [ "install-library"
@@ -203,14 +199,12 @@
 
 registerPackage
   :: Verbosity
-  -> InstalledPackageInfo
-  -> PackageDescription
-  -> LocalBuildInfo
-  -> Bool
+  -> ProgramConfiguration
   -> PackageDBStack
+  -> InstalledPackageInfo
   -> IO ()
-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs = do
-  (hspkg, _) <- requireProgram verbosity haskellSuitePkgProgram (withPrograms lbi)
+registerPackage verbosity progdb packageDbs installedPkgInfo = do
+  (hspkg, _) <- requireProgram verbosity haskellSuitePkgProgram progdb
 
   runProgramInvocation verbosity $
     (programInvocation hspkg
diff --git a/cabal/Cabal/Distribution/Simple/Install.hs b/cabal/Cabal/Distribution/Simple/Install.hs
--- a/cabal/Cabal/Distribution/Simple/Install.hs
+++ b/cabal/Cabal/Distribution/Simple/Install.hs
@@ -16,13 +16,9 @@
         install,
   ) where
 
-import Distribution.PackageDescription (
-        PackageDescription(..), BuildInfo(..), Library(..),
-        hasLibs, withLib, hasExes, withExe )
+import Distribution.PackageDescription
 import Distribution.Package (Package(..))
-import Distribution.Simple.LocalBuildInfo (
-        LocalBuildInfo(..), InstallDirs(..), absoluteInstallDirs,
-        substPathTemplate, withLibLBI)
+import Distribution.Simple.LocalBuildInfo
 import Distribution.Simple.BuildPaths (haddockName, haddockPref)
 import Distribution.Simple.Utils
          ( createDirectoryIfMissingVerbose
@@ -31,6 +27,7 @@
 import Distribution.Simple.Compiler
          ( CompilerFlavor(..), compilerFlavor )
 import Distribution.Simple.Setup (CopyFlags(..), fromFlag)
+import Distribution.Simple.BuildTarget
 
 import qualified Distribution.Simple.GHC   as GHC
 import qualified Distribution.Simple.GHCJS as GHCJS
@@ -51,6 +48,9 @@
 
 -- |Perform the \"@.\/setup install@\" and \"@.\/setup copy@\"
 -- actions.  Move files into place based on the prefix argument.
+--
+-- This does NOT register libraries, you should call 'register'
+-- to do that.
 
 install :: PackageDescription -- ^information from the .cabal file
         -> LocalBuildInfo -- ^information from the configure step
@@ -60,33 +60,41 @@
   let distPref  = fromFlag (copyDistPref flags)
       verbosity = fromFlag (copyVerbosity flags)
       copydest  = fromFlag (copyDest flags)
-      installDirs@(InstallDirs {
-         bindir     = binPref,
-         libdir     = libPref,
---         dynlibdir  = dynlibPref, --see TODO below
+      -- This is a bit of a hack, to handle files which are not
+      -- per-component (data files and Haddock files.)
+      InstallDirs {
          datadir    = dataPref,
+         -- NB: The situation with Haddock is a bit delicate.  On the
+         -- one hand, the easiest to understand Haddock documentation
+         -- path is pkgname-0.1, which means it's per-package (not
+         -- per-component).  But this means that it's impossible to
+         -- install Haddock documentation for internal libraries.  We'll
+         -- keep this constraint for now; this means you can't use
+         -- Cabal to Haddock internal libraries.  This does not seem
+         -- like a big problem.
          docdir     = docPref,
          htmldir    = htmlPref,
-         haddockdir = interfacePref,
-         includedir = incPref})
+         haddockdir = interfacePref}
+             -- Notice use of 'absoluteInstallDirs' (not the
+             -- per-component variant).  This means for non-library
+             -- packages we'll just pick a nondescriptive foo-0.1
              = absoluteInstallDirs pkg_descr lbi copydest
 
-      --TODO: decide if we need the user to be able to control the libdir
-      -- for shared libs independently of the one for static libs. If so
-      -- it should also have a flag in the command line UI
-      -- For the moment use dynlibdir = libdir
-      dynlibPref = libPref
-      progPrefixPref = substPathTemplate (packageId pkg_descr) lbi (progPrefix lbi)
-      progSuffixPref = substPathTemplate (packageId pkg_descr) lbi (progSuffix lbi)
-
   unless (hasLibs pkg_descr || hasExes pkg_descr) $
       die "No executables and no library found. Nothing to do."
+
+  targets <- readBuildTargets pkg_descr (copyArgs flags)
+  targets' <- checkBuildTargets verbosity pkg_descr targets
+
+  -- Install (package-global) data files
+  installDataFiles verbosity pkg_descr dataPref
+
+  -- Install (package-global) Haddock files
+  -- TODO: these should be done per-library
   docExists <- doesDirectoryExist $ haddockPref distPref pkg_descr
   info verbosity ("directory " ++ haddockPref distPref pkg_descr ++
                   " does exist: " ++ show docExists)
 
-  installDataFiles verbosity pkg_descr dataPref
-
   when docExists $ do
       createDirectoryIfMissingVerbose verbosity True htmlPref
       installDirectoryContents verbosity
@@ -114,45 +122,74 @@
       [ installOrdinaryFile verbosity lfile (docPref </> takeFileName lfile)
       | lfile <- lfiles ]
 
-  let buildPref = buildDir lbi
-  when (hasLibs pkg_descr) $
-    notice verbosity ("Installing library in " ++ libPref)
-  when (hasExes pkg_descr) $ do
-    notice verbosity ("Installing executable(s) in " ++ binPref)
+  -- It's not necessary to do these in build-order, but it's harmless
+  withComponentsInBuildOrder pkg_descr lbi (map fst targets') $ \comp clbi ->
+    copyComponent verbosity pkg_descr lbi comp clbi copydest
+
+copyComponent :: Verbosity -> PackageDescription
+              -> LocalBuildInfo -> Component -> ComponentLocalBuildInfo
+              -> CopyDest
+              -> IO ()
+copyComponent verbosity pkg_descr lbi (CLib lib) clbi copydest = do
+    let InstallDirs{
+            libdir = libPref,
+            includedir = incPref
+            } = absoluteComponentInstallDirs pkg_descr lbi (componentUnitId clbi) copydest
+        buildPref = componentBuildDir lbi clbi
+    -- TODO: decide if we need the user to be able to control the libdir
+    -- for shared libs independently of the one for static libs. If so
+    -- it should also have a flag in the command line UI
+    -- For the moment use dynlibdir = libdir
+        dynlibPref = libPref
+
+    if componentUnitId clbi == localUnitId lbi
+        then notice verbosity ("Installing library in " ++ libPref)
+        else notice verbosity ("Installing internal library " ++ libName lib ++ " in " ++ libPref)
+
+    -- install include files for all compilers - they may be needed to compile
+    -- haskell files (using the CPP extension)
+    installIncludeFiles verbosity lib incPref
+
+    case compilerFlavor (compiler lbi) of
+      GHC   -> GHC.installLib   verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      GHCJS -> GHCJS.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      LHC   -> LHC.installLib   verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      JHC   -> JHC.installLib   verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      UHC   -> UHC.installLib   verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      HaskellSuite _ -> HaskellSuite.installLib
+                                verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi
+      _ -> die $ "installing with "
+              ++ display (compilerFlavor (compiler lbi))
+              ++ " is not implemented"
+
+copyComponent verbosity pkg_descr lbi (CExe exe) clbi copydest = do
+    let installDirs@InstallDirs {
+            bindir = binPref
+            } = absoluteComponentInstallDirs pkg_descr lbi (componentUnitId clbi) copydest
+        -- the installers know how to find the actual location of the
+        -- binaries
+        buildPref = buildDir lbi
+        uid = componentUnitId clbi
+        progPrefixPref = substPathTemplate (packageId pkg_descr) lbi uid (progPrefix lbi)
+        progSuffixPref = substPathTemplate (packageId pkg_descr) lbi uid (progSuffix lbi)
+    notice verbosity ("Installing executable " ++ exeName exe ++ " in " ++ binPref)
     inPath <- isInSearchPath binPref
     when (not inPath) $
       warn verbosity ("The directory " ++ binPref
                       ++ " is not in the system search path.")
-
-  -- install include files for all compilers - they may be needed to compile
-  -- haskell files (using the CPP extension)
-  when (hasLibs pkg_descr) $ installIncludeFiles verbosity pkg_descr incPref
+    case compilerFlavor (compiler lbi) of
+      GHC   -> GHC.installExe   verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe
+      GHCJS -> GHCJS.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe
+      LHC   -> LHC.installExe   verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe
+      JHC   -> JHC.installExe   verbosity binPref buildPref (progPrefixPref, progSuffixPref) pkg_descr exe
+      UHC   -> return ()
+      HaskellSuite {} -> return ()
+      _ -> die $ "installing with "
+              ++ display (compilerFlavor (compiler lbi))
+              ++ " is not implemented"
 
-  case compilerFlavor (compiler lbi) of
-     GHC  -> do withLibLBI pkg_descr lbi $
-                  GHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr
-                withExe pkg_descr $
-                  GHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr
-     GHCJS-> do withLibLBI pkg_descr lbi $
-                  GHCJS.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr
-                withExe pkg_descr $
-                  GHCJS.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr
-     LHC  -> do withLibLBI pkg_descr lbi $
-                  LHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr
-                withExe pkg_descr $
-                  LHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr
-     JHC  -> do withLib pkg_descr $
-                  JHC.installLib verbosity libPref buildPref pkg_descr
-                withExe pkg_descr $
-                  JHC.installExe verbosity binPref buildPref (progPrefixPref, progSuffixPref) pkg_descr
-     UHC  -> do withLib pkg_descr $ UHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr
-     HaskellSuite {} ->
-       withLib pkg_descr $
-         HaskellSuite.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr
-     _    -> die $ "installing with "
-                ++ display (compilerFlavor (compiler lbi))
-                ++ " is not implemented"
-  -- register step should be performed by caller.
+-- Nothing to do for benchmark/testsuite
+copyComponent _ _ _ _ _ _ = return ()
 
 -- | Install the files listed in data-files
 --
@@ -167,26 +204,23 @@
                                               (destDataDir </> file')
               | file' <- files ]
 
--- | Install the files listed in install-includes
+-- | Install the files listed in install-includes for a library
 --
-installIncludeFiles :: Verbosity -> PackageDescription -> FilePath -> IO ()
-installIncludeFiles verbosity
-  PackageDescription { library = Just lib } destIncludeDir = do
-
-  incs <- mapM (findInc relincdirs) (installIncludes lbi)
-  sequence_
-    [ do createDirectoryIfMissingVerbose verbosity True destDir
-         installOrdinaryFile verbosity srcFile destFile
-    | (relFile, srcFile) <- incs
-    , let destFile = destIncludeDir </> relFile
-          destDir  = takeDirectory destFile ]
+installIncludeFiles :: Verbosity -> Library -> FilePath -> IO ()
+installIncludeFiles verbosity lib destIncludeDir = do
+    let relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)
+        lbi = libBuildInfo lib
+    incs <- mapM (findInc relincdirs) (installIncludes lbi)
+    sequence_
+      [ do createDirectoryIfMissingVerbose verbosity True destDir
+           installOrdinaryFile verbosity srcFile destFile
+      | (relFile, srcFile) <- incs
+      , let destFile = destIncludeDir </> relFile
+            destDir  = takeDirectory destFile ]
   where
-   relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)
-   lbi = libBuildInfo lib
 
    findInc []         file = die ("can't find include file " ++ file)
    findInc (dir:dirs) file = do
      let path = dir </> file
      exists <- doesFileExist path
      if exists then return (file, path) else findInc dirs file
-installIncludeFiles _ _ _ = die "installIncludeFiles: Can't happen?"
diff --git a/cabal/Cabal/Distribution/Simple/InstallDirs.hs b/cabal/Cabal/Distribution/Simple/InstallDirs.hs
--- a/cabal/Cabal/Distribution/Simple/InstallDirs.hs
+++ b/cabal/Cabal/Distribution/Simple/InstallDirs.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE DeriveFunctor #-}
 {-# LANGUAGE DeriveGeneric #-}
 
 -----------------------------------------------------------------------------
@@ -46,25 +47,19 @@
 
 
 import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Semigroup as Semi
+import Distribution.Package
+import Distribution.System
+import Distribution.Compiler
+import Distribution.Text
+
 import Data.List (isPrefixOf)
 import Data.Maybe (fromMaybe)
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid (Monoid(..))
-#endif
 import GHC.Generics (Generic)
 import System.Directory (getAppUserDataDirectory)
 import System.FilePath ((</>), isPathSeparator, pathSeparator)
 import System.FilePath (dropDrive)
 
-import Distribution.Package
-         ( PackageIdentifier, packageName, packageVersion, LibraryName )
-import Distribution.System
-         ( OS(..), buildOS, Platform(..) )
-import Distribution.Compiler
-         ( AbiTag(..), abiTagString, CompilerInfo(..), CompilerFlavor(..) )
-import Distribution.Text
-         ( display )
-
 #if mingw32_HOST_OS
 import Foreign
 import Foreign.C
@@ -96,46 +91,16 @@
         htmldir      :: dir,
         haddockdir   :: dir,
         sysconfdir   :: dir
-    } deriving (Generic, Read, Show)
+    } deriving (Eq, Read, Show, Functor, Generic)
 
 instance Binary dir => Binary (InstallDirs dir)
 
-instance Functor InstallDirs where
-  fmap f dirs = InstallDirs {
-    prefix       = f (prefix dirs),
-    bindir       = f (bindir dirs),
-    libdir       = f (libdir dirs),
-    libsubdir    = f (libsubdir dirs),
-    dynlibdir    = f (dynlibdir dirs),
-    libexecdir   = f (libexecdir dirs),
-    includedir   = f (includedir dirs),
-    datadir      = f (datadir dirs),
-    datasubdir   = f (datasubdir dirs),
-    docdir       = f (docdir dirs),
-    mandir       = f (mandir dirs),
-    htmldir      = f (htmldir dirs),
-    haddockdir   = f (haddockdir dirs),
-    sysconfdir   = f (sysconfdir dirs)
-  }
+instance (Semigroup dir, Monoid dir) => Monoid (InstallDirs dir) where
+  mempty = gmempty
+  mappend = (Semi.<>)
 
-instance Monoid dir => Monoid (InstallDirs dir) where
-  mempty = InstallDirs {
-      prefix       = mempty,
-      bindir       = mempty,
-      libdir       = mempty,
-      libsubdir    = mempty,
-      dynlibdir    = mempty,
-      libexecdir   = mempty,
-      includedir   = mempty,
-      datadir      = mempty,
-      datasubdir   = mempty,
-      docdir       = mempty,
-      mandir       = mempty,
-      htmldir      = mempty,
-      haddockdir   = mempty,
-      sysconfdir   = mempty
-  }
-  mappend = combineInstallDirs mappend
+instance Semigroup dir => Semigroup (InstallDirs dir) where
+  (<>) = gmappend
 
 combineInstallDirs :: (a -> b -> c)
                    -> InstallDirs a
@@ -287,7 +252,7 @@
 -- substituting for all the variables in the abstract paths, to get real
 -- absolute path.
 absoluteInstallDirs :: PackageIdentifier
-                    -> LibraryName
+                    -> UnitId
                     -> CompilerInfo
                     -> CopyDest
                     -> Platform
@@ -317,7 +282,7 @@
 -- independent\" package).
 --
 prefixRelativeInstallDirs :: PackageIdentifier
-                          -> LibraryName
+                          -> UnitId
                           -> CompilerInfo
                           -> Platform
                           -> InstallDirTemplates
@@ -349,7 +314,8 @@
 -- | An abstract path, possibly containing variables that need to be
 -- substituted for to get a real 'FilePath'.
 --
-newtype PathTemplate = PathTemplate [PathComponent] deriving (Eq, Generic, Ord)
+newtype PathTemplate = PathTemplate [PathComponent]
+  deriving (Eq, Ord, Generic)
 
 instance Binary PathTemplate
 
@@ -372,7 +338,7 @@
      | PkgNameVar    -- ^ The @$pkg@ package name path variable
      | PkgVerVar     -- ^ The @$version@ package version path variable
      | PkgIdVar      -- ^ The @$pkgid@ package Id path variable, eg @foo-1.0@
-     | LibNameVar    -- ^ The @$libname@ expanded package key path variable
+     | LibNameVar    -- ^ The @$libname@ path variable
      | CompilerVar   -- ^ The compiler name and version, eg @ghc-6.6.1@
      | OSVar         -- ^ The operating system name, eg @windows@ or @linux@
      | ArchVar       -- ^ The CPU architecture name, eg @i386@ or @x86_64@
@@ -415,7 +381,7 @@
 
 -- | The initial environment has all the static stuff but no paths
 initialPathTemplateEnv :: PackageIdentifier
-                       -> LibraryName
+                       -> UnitId
                        -> CompilerInfo
                        -> Platform
                        -> PathTemplateEnv
@@ -425,7 +391,7 @@
   ++ platformTemplateEnv platform
   ++ abiTemplateEnv compiler platform
 
-packageTemplateEnv :: PackageIdentifier -> LibraryName -> PathTemplateEnv
+packageTemplateEnv :: PackageIdentifier -> UnitId -> PathTemplateEnv
 packageTemplateEnv pkgId libname =
   [(PkgNameVar,  PathTemplate [Ordinary $ display (packageName pkgId)])
   ,(PkgVerVar,   PathTemplate [Ordinary $ display (packageVersion pkgId)])
@@ -478,7 +444,7 @@
 
 instance Show PathTemplateVariable where
   show PrefixVar     = "prefix"
-  show LibNameVar     = "libname"
+  show LibNameVar    = "libname"
   show BindirVar     = "bindir"
   show LibdirVar     = "libdir"
   show LibsubdirVar  = "libsubdir"
@@ -515,8 +481,8 @@
                  ,("docdir",     DocdirVar)
                  ,("htmldir",    HtmldirVar)
                  ,("pkgid",      PkgIdVar)
-                 ,("pkgkey",     LibNameVar) -- backwards compatibility
                  ,("libname",    LibNameVar)
+                 ,("pkgkey",     LibNameVar) -- backwards compatibility
                  ,("pkg",        PkgNameVar)
                  ,("version",    PkgVerVar)
                  ,("compiler",   CompilerVar)
diff --git a/cabal/Cabal/Distribution/Simple/JHC.hs b/cabal/Cabal/Distribution/Simple/JHC.hs
--- a/cabal/Cabal/Distribution/Simple/JHC.hs
+++ b/cabal/Cabal/Distribution/Simple/JHC.hs
@@ -16,40 +16,23 @@
         installLib, installExe
  ) where
 
-import Distribution.PackageDescription as PD
-       ( PackageDescription(..), BuildInfo(..), Executable(..)
-       , Library(..), libModules, hcOptions, usedExtensions )
+import Distribution.PackageDescription as PD hiding (Flag)
 import Distribution.InstalledPackageInfo
-         ( emptyInstalledPackageInfo, )
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
 import Distribution.Simple.PackageIndex (InstalledPackageIndex)
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )
 import Distribution.Simple.BuildPaths
-                                ( autogenModulesDir, exeExtension )
 import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), CompilerId(..), Compiler(..), AbiTag(..)
-         , PackageDBStack, Flag, languageToFlags, extensionsToFlags )
 import Language.Haskell.Extension
-         ( Language(Haskell98), Extension(..), KnownExtension(..))
 import Distribution.Simple.Program
-         ( ConfiguredProgram(..), jhcProgram, ProgramConfiguration
-         , userMaybeSpecifyPath, requireProgramVersion, lookupProgram
-         , rawSystemProgram, rawSystemProgramStdoutConf )
 import Distribution.Version
-         ( Version(..), orLaterVersion )
 import Distribution.Package
-         ( Package(..), InstalledPackageId(InstalledPackageId),
-           pkgName, pkgVersion, )
 import Distribution.Simple.Utils
-        ( createDirectoryIfMissingVerbose, writeFileAtomic
-        , installOrdinaryFile, installExecutableFile
-        , intercalate )
-import System.FilePath          ( (</>) )
 import Distribution.Verbosity
 import Distribution.Text
-         ( Text(parse), display )
+
+import System.FilePath          ( (</>) )
 import Distribution.Compat.ReadP
     ( readP_to_S, string, skipSpaces )
 import Distribution.System ( Platform )
@@ -57,7 +40,6 @@
 import Data.List                ( nub )
 import Data.Char                ( isSpace )
 import qualified Data.Map as M  ( empty )
-import Data.Maybe               ( fromMaybe )
 
 import qualified Data.ByteString.Lazy.Char8 as BS.Char8
 
@@ -116,8 +98,7 @@
    return $
       PackageIndex.fromList $
       map (\p -> emptyInstalledPackageInfo {
-                    InstalledPackageInfo.installedPackageId =
-                       InstalledPackageId (display p),
+                    InstalledPackageInfo.installedUnitId = mkLegacyUnitId p,
                     InstalledPackageInfo.sourcePackageId = p
                  }) $
       concatMap parseLine $
@@ -162,7 +143,7 @@
      ++ extensionsToFlags (compiler lbi) (usedExtensions bi)
      ++ ["--noauto","-i-"]
      ++ concat [["-i", l] | l <- nub (hsSourceDirs bi)]
-     ++ ["-i", autogenModulesDir lbi]
+     ++ ["-i", autogenModulesDir lbi clbi]
      ++ ["-optc" ++ opt | opt <- PD.ccOptions bi]
      -- It would be better if JHC would accept package names with versions,
      -- but JHC-0.7.2 doesn't accept this.
@@ -173,7 +154,10 @@
 jhcPkgConf :: PackageDescription -> String
 jhcPkgConf pd =
   let sline name sel = name ++ ": "++sel pd
-      lib = fromMaybe (error "no library available") . library
+      lib pd' = case libraries pd' of
+                [lib'] -> lib'
+                [] -> error "no library available"
+                _ -> error "JHC does not support multiple libraries (yet)"
       comma = intercalate "," . map display
   in unlines [sline "name" (display . pkgName . packageId)
              ,sline "version" (display . pkgVersion . packageId)
@@ -181,8 +165,16 @@
              ,sline "hidden-modules" (comma . otherModules . libBuildInfo . lib)
              ]
 
-installLib :: Verbosity -> FilePath -> FilePath -> PackageDescription -> Library -> IO ()
-installLib verb dest build_dir pkg_descr _ = do
+installLib :: Verbosity
+           -> LocalBuildInfo
+           -> FilePath
+           -> FilePath
+           -> FilePath
+           -> PackageDescription
+           -> Library
+           -> ComponentLocalBuildInfo
+           -> IO ()
+installLib verb _lbi dest _dyn_dest build_dir pkg_descr _lib _clbi = do
     let p = display (packageId pkg_descr)++".hl"
     createDirectoryIfMissingVerbose verb True dest
     installOrdinaryFile verb (build_dir </> p) (dest </> p)
diff --git a/cabal/Cabal/Distribution/Simple/LHC.hs b/cabal/Cabal/Distribution/Simple/LHC.hs
--- a/cabal/Cabal/Distribution/Simple/LHC.hs
+++ b/cabal/Cabal/Distribution/Simple/LHC.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.LHC
@@ -41,66 +40,37 @@
         ghcVerbosityOptions
  ) where
 
-import Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(..), Executable(..)
-         , Library(..), libModules, hcOptions, hcProfOptions, hcSharedOptions
-         , usedExtensions, allExtensions )
+import Distribution.PackageDescription as PD hiding (Flag)
 import Distribution.InstalledPackageInfo
-                                ( InstalledPackageInfo
-                                , parseInstalledPackageInfo )
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
-                                ( InstalledPackageInfo(..) )
 import Distribution.Simple.PackageIndex
 import qualified Distribution.Simple.PackageIndex as PackageIndex
-import Distribution.ParseUtils  ( ParseResult(..) )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )
-import Distribution.Simple.InstallDirs
 import Distribution.Simple.BuildPaths
 import Distribution.Simple.Utils
 import Distribution.Package
-         ( Package(..), LibraryName, getHSLibraryName )
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.Simple.Program
-         ( Program(..), ConfiguredProgram(..), ProgramConfiguration
-         , ProgramSearchPath, ProgramLocation(..)
-         , rawSystemProgram, rawSystemProgramConf
-         , rawSystemProgramStdout, rawSystemProgramStdoutConf
-         , requireProgramVersion
-         , userMaybeSpecifyPath, programPath, lookupProgram, addKnownProgram
-         , arProgram, ldProgram
-         , gccProgram, stripProgram
-         , lhcProgram, lhcPkgProgram )
 import qualified Distribution.Simple.Program.HcPkg as HcPkg
 import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), CompilerId(..), Compiler(..), compilerVersion
-         , OptimisationLevel(..), PackageDB(..), PackageDBStack, AbiTag(..)
-         , Flag, languageToFlags, extensionsToFlags )
 import Distribution.Version
-         ( Version(..), orLaterVersion )
-import Distribution.System
-         ( OS(..), buildOS )
 import Distribution.Verbosity
 import Distribution.Text
-         ( display, simpleParse )
+import Distribution.Compat.Exception
+import Distribution.System
 import Language.Haskell.Extension
-         ( Language(Haskell98), Extension(..), KnownExtension(..) )
 
 import Control.Monad            ( unless, when )
+import Data.Monoid as Mon
 import Data.List
 import qualified Data.Map as M  ( empty )
 import Data.Maybe               ( catMaybes )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid              ( Monoid(..) )
-#endif
 import System.Directory         ( removeFile, renameFile,
                                   getDirectoryContents, doesFileExist,
                                   getTemporaryDirectory )
 import System.FilePath          ( (</>), (<.>), takeExtension,
                                   takeDirectory, replaceExtension )
 import System.IO (hClose, hPutStrLn)
-import Distribution.Compat.Exception (catchExit, catchIO)
-import Distribution.System ( Platform )
 
 -- -----------------------------------------------------------------------------
 -- Configuring
@@ -149,22 +119,23 @@
       programPostConf     = configureGcc
     }
   . addKnownProgram ldProgram {
-      programFindLocation = findProg ldProgram (libDir </> "ld.exe"),
+      programFindLocation = findProg ldProgram (gccLibDir </> "ld.exe"),
       programPostConf     = configureLd
     }
   where
     compilerDir = takeDirectory (programPath lhcProg)
     baseDir     = takeDirectory compilerDir
-    libDir      = baseDir </> "gcc-lib"
+    gccLibDir      = baseDir </> "gcc-lib"
     includeDir  = baseDir </> "include" </> "mingw"
     isWindows   = case buildOS of Windows -> True; _ -> False
 
     -- on Windows finding and configuring ghc's gcc and ld is a bit special
     findProg :: Program -> FilePath
-             -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)
+             -> Verbosity -> ProgramSearchPath
+             -> IO (Maybe (FilePath, [FilePath]))
     findProg prog location | isWindows = \verbosity searchpath -> do
         exists <- doesFileExist location
-        if exists then return (Just location)
+        if exists then return (Just (location, []))
                   else do warn verbosity ("Couldn't find " ++ programName prog ++ " where I expected it. Trying the search path.")
                           programFindLocation prog verbosity searchpath
       | otherwise = programFindLocation prog
@@ -177,7 +148,7 @@
           -- that means we should add this extra flag to tell ghc's gcc
           -- where it lives and thus where gcc can find its various files:
           FoundOnSystem {} -> return gccProg {
-                                programDefaultArgs = ["-B" ++ libDir,
+                                programDefaultArgs = ["-B" ++ gccLibDir,
                                                       "-I" ++ includeDir]
                               }
           UserSpecified {} -> return gccProg
@@ -230,7 +201,7 @@
   pkgss <- getInstalledPackages' lhcPkg verbosity packagedbs conf
   let indexes = [ PackageIndex.fromList (map (substTopDir topDir) pkgs)
                 | (_, pkgs) <- pkgss ]
-  return $! (mconcat indexes)
+  return $! (Mon.mconcat indexes)
 
   where
     -- On Windows, various fields have $topdir/foo rather than full
@@ -318,8 +289,8 @@
 buildLib :: Verbosity -> PackageDescription -> LocalBuildInfo
                       -> Library            -> ComponentLocalBuildInfo -> IO ()
 buildLib verbosity pkg_descr lbi lib clbi = do
-  let libName = componentLibraryName clbi
-      pref = buildDir lbi
+  let lib_name = componentUnitId clbi
+      pref = componentBuildDir lbi clbi
       pkgid = packageId pkg_descr
       runGhcProg = rawSystemProgramConf verbosity lhcProgram (withPrograms lbi)
       ifVanillaLib forceVanilla = when (forceVanilla || withVanillaLib lbi)
@@ -373,10 +344,10 @@
   let cObjs = map (`replaceExtension` objExtension) (cSources libBi)
       cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension)) (cSources libBi)
       cid = compilerId (compiler lbi)
-      vanillaLibFilePath = libTargetDir </> mkLibName           libName
-      profileLibFilePath = libTargetDir </> mkProfLibName       libName
-      sharedLibFilePath  = libTargetDir </> mkSharedLibName cid libName
-      ghciLibFilePath    = libTargetDir </> mkGHCiLibName       libName
+      vanillaLibFilePath = libTargetDir </> mkLibName           lib_name
+      profileLibFilePath = libTargetDir </> mkProfLibName       lib_name
+      sharedLibFilePath  = libTargetDir </> mkSharedLibName cid lib_name
+      ghciLibFilePath    = libTargetDir </> mkGHCiLibName       lib_name
 
   stubObjs <- fmap catMaybes $ sequence
     [ findFileWithExtension [objExtension] [libTargetDir]
@@ -455,7 +426,7 @@
             -- This method is called iteratively by xargs. The
             -- output goes to <ldLibName>.tmp, and any existing file
             -- named <ldLibName> is included when linking. The
-            -- output is renamed to <libName>.
+            -- output is renamed to <lib_name>.
           rawSystemProgramConf verbosity ldProgram (withPrograms lbi)
             (args ++ if exists then [ldLibName] else [])
           renameFile (ldLibName <.> "tmp") ldLibName
@@ -525,7 +496,7 @@
           ++ [srcMainFile]
           ++ ["-optl" ++ opt | opt <- PD.ldOptions exeBi]
           ++ ["-l"++lib | lib <- extraLibs exeBi]
-          ++ ["-L"++libDir | libDir <- extraLibDirs exeBi]
+          ++ ["-L"++extraLibDir | extraLibDir <- extraLibDirs exeBi]
           ++ concat [["-framework", f] | f <- PD.frameworks exeBi]
           ++ if profExe
                 then ["-prof",
@@ -607,12 +578,12 @@
      ++ ["-i"]
      ++ ["-i" ++ odir]
      ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)]
-     ++ ["-i" ++ autogenModulesDir lbi]
-     ++ ["-I" ++ autogenModulesDir lbi]
+     ++ ["-i" ++ autogenModulesDir lbi clbi]
+     ++ ["-I" ++ autogenModulesDir lbi clbi]
      ++ ["-I" ++ odir]
      ++ ["-I" ++ dir | dir <- PD.includeDirs bi]
      ++ ["-optP" ++ opt | opt <- cppOptions bi]
-     ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi </> cppHeaderName) ]
+     ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi clbi </> cppHeaderName) ]
      ++ [ "-#include \"" ++ inc ++ "\"" | inc <- PD.includes bi ]
      ++ [ "-odir",  odir, "-hidir", odir ]
      ++ (if compilerVersion c >= Version [6,8] []
@@ -682,7 +653,7 @@
            _              -> ["-optc-O2"])
      ++ ["-odir", odir]
 
-mkGHCiLibName :: LibraryName -> String
+mkGHCiLibName :: UnitId -> String
 mkGHCiLibName lib = getHSLibraryName lib <.> "o"
 
 -- -----------------------------------------------------------------------------
@@ -757,11 +728,11 @@
 
   where
     cid = compilerId (compiler lbi)
-    libName = componentLibraryName clbi
-    vanillaLibName = mkLibName           libName
-    profileLibName = mkProfLibName       libName
-    ghciLibName    = mkGHCiLibName       libName
-    sharedLibName  = mkSharedLibName cid libName
+    lib_name = componentUnitId clbi
+    vanillaLibName = mkLibName           lib_name
+    profileLibName = mkProfLibName       lib_name
+    ghciLibName    = mkGHCiLibName       lib_name
+    sharedLibName  = mkSharedLibName cid lib_name
 
     hasLib    = not $ null (libModules lib)
                    && null (cSources (libBuildInfo lib))
@@ -777,14 +748,12 @@
 
 registerPackage
   :: Verbosity
-  -> InstalledPackageInfo
-  -> PackageDescription
-  -> LocalBuildInfo
-  -> Bool
+  -> ProgramConfiguration
   -> PackageDBStack
+  -> InstalledPackageInfo
   -> IO ()
-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =
-  HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity packageDbs
+registerPackage verbosity progdb packageDbs installedPkgInfo =
+  HcPkg.reregister (hcPkgInfo progdb) verbosity packageDbs
     (Right installedPkgInfo)
 
 hcPkgInfo :: ProgramConfiguration -> HcPkg.HcPkgInfo
@@ -792,7 +761,10 @@
                                  , HcPkg.noPkgDbStack    = False
                                  , HcPkg.noVerboseFlag   = False
                                  , HcPkg.flagPackageConf = False
-                                 , HcPkg.useSingleFileDb = True
+                                 , HcPkg.supportsDirDbs  = True
+                                 , HcPkg.requiresDirDbs  = True
+                                 , HcPkg.nativeMultiInstance  = False -- ?
+                                 , HcPkg.recacheMultiInstance = False -- ?
                                  }
   where
     Just lhcPkgProg = lookupProgram lhcPkgProgram conf
diff --git a/cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs b/cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs
--- a/cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs
+++ b/cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE PatternGuards #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -20,15 +21,20 @@
 module Distribution.Simple.LocalBuildInfo (
         LocalBuildInfo(..),
         externalPackageDeps,
-        inplacePackageId,
-        localPackageKey,
-        localLibraryName,
+        localComponentId,
+        localUnitId,
+        localCompatPackageKey,
 
         -- * Buildable package components
         Component(..),
         ComponentName(..),
+        defaultLibName,
         showComponentName,
+        componentNameString,
         ComponentLocalBuildInfo(..),
+        getLocalComponent,
+        componentComponentId,
+        componentBuildDir,
         foldComponent,
         componentName,
         componentBuildInfo,
@@ -39,6 +45,8 @@
         pkgEnabledComponents,
         lookupComponent,
         getComponent,
+        maybeGetDefaultLibraryLocalBuildInfo,
+        maybeGetComponentLocalBuildInfo,
         getComponentLocalBuildInfo,
         allComponentsInBuildOrder,
         componentsInBuildOrder,
@@ -55,6 +63,7 @@
         -- * Installation directories
         module Distribution.Simple.InstallDirs,
         absoluteInstallDirs, prefixRelativeInstallDirs,
+        absoluteComponentInstallDirs, prefixRelativeComponentInstallDirs,
         substPathTemplate
   ) where
 
@@ -63,30 +72,16 @@
                                                prefixRelativeInstallDirs,
                                                substPathTemplate, )
 import qualified Distribution.Simple.InstallDirs as InstallDirs
-import Distribution.Simple.Program (ProgramConfiguration)
-import Distribution.InstalledPackageInfo (InstalledPackageInfo)
+import Distribution.Simple.Program
 import Distribution.PackageDescription
-         ( PackageDescription(..), withLib, Library(libBuildInfo), withExe
-         , Executable(exeName, buildInfo), withTest, TestSuite(..)
-         , BuildInfo(buildable), Benchmark(..), ModuleRenaming(..) )
 import qualified Distribution.InstalledPackageInfo as Installed
 import Distribution.Package
-         ( PackageId, Package(..), InstalledPackageId(..)
-         , PackageName, LibraryName(..), PackageKey(..) )
 import Distribution.Simple.Compiler
-         ( Compiler, compilerInfo, PackageDBStack, DebugInfoLevel
-         , OptimisationLevel, ProfDetailLevel )
 import Distribution.Simple.PackageIndex
-         ( InstalledPackageIndex, allPackages )
-import Distribution.ModuleName ( ModuleName )
 import Distribution.Simple.Setup
-         ( ConfigFlags )
 import Distribution.Simple.Utils
-         ( shortRelativePath )
 import Distribution.Text
-         ( display )
 import Distribution.System
-         ( Platform (..) )
 
 import Data.Array ((!))
 import Distribution.Compat.Binary (Binary)
@@ -95,7 +90,7 @@
 import Data.Maybe
 import Data.Tree  (flatten)
 import GHC.Generics (Generic)
-import Data.Map (Map)
+import System.FilePath
 
 import System.Directory (doesDirectoryExist, canonicalizePath)
 
@@ -105,6 +100,8 @@
         configFlags   :: ConfigFlags,
         -- ^ Options passed to the configuration step.
         -- Needed to re-run configuration when .cabal is out of date
+        flagAssignment :: FlagAssignment,
+        -- ^ The final set of flags which were picked for this package
         extraConfigArgs     :: [String],
         -- ^ Extra args on the command line for the configuration step.
         -- Needed to re-run configuration when .cabal is out of date
@@ -118,18 +115,21 @@
                 -- ^ The platform we're building for
         buildDir      :: FilePath,
                 -- ^ Where to build the package.
-        componentsConfigs   :: [(ComponentName, ComponentLocalBuildInfo, [ComponentName])],
-                -- ^ All the components to build, ordered by topological sort, and with their dependencies
-                -- over the intrapackage dependency graph
+        componentsConfigs   :: [(ComponentLocalBuildInfo, [UnitId])],
+                -- ^ All the components to build, ordered by topological
+                -- sort, and with their INTERNAL dependencies over the
+                -- intrapackage dependency graph.
+                -- TODO: this is assumed to be short; otherwise we want
+                -- some sort of ordered map.
         installedPkgs :: InstalledPackageIndex,
                 -- ^ All the info about the installed packages that the
                 -- current package depends on (directly or indirectly).
+                -- Does NOT include internal dependencies.
         pkgDescrFile  :: Maybe FilePath,
                 -- ^ the filename containing the .cabal file, if available
         localPkgDescr :: PackageDescription,
                 -- ^ The resolved package description, that does not contain
                 -- any conditionals.
-        instantiatedWith :: [(ModuleName, (InstalledPackageInfo, ModuleName))],
         withPrograms  :: ProgramConfiguration, -- ^Location and args for all programs
         withPackageDB :: PackageDBStack,  -- ^What package database to use, global\/user
         withVanillaLib:: Bool,  -- ^Whether to build normal libs.
@@ -152,48 +152,53 @@
 
 instance Binary LocalBuildInfo
 
--- | Extract the 'PackageKey' from the library component of a
--- 'LocalBuildInfo' if it exists, or make a fake package key based
+-- TODO: Get rid of these functions, as much as possible.  They are
+-- a bit useful in some cases, but you should be very careful!
+
+-- | Extract the 'ComponentId' from the public library component of a
+-- 'LocalBuildInfo' if it exists, or make a fake component ID based
 -- on the package ID.
-localPackageKey :: LocalBuildInfo -> PackageKey
-localPackageKey lbi =
-    foldr go (OldPackageKey (package (localPkgDescr lbi))) (componentsConfigs lbi)
-  where go (_, clbi, _) old_pk = case clbi of
-            LibComponentLocalBuildInfo { componentPackageKey = pk } -> pk
-            _ -> old_pk
+localComponentId :: LocalBuildInfo -> ComponentId
+localComponentId lbi
+    = case localUnitId lbi of
+        SimpleUnitId cid -> cid
 
--- | Extract the 'LibraryName' from the library component of a
--- 'LocalBuildInfo' if it exists, or make a library name based
+-- | Extract the 'UnitId' from the library component of a
+-- 'LocalBuildInfo' if it exists, or make a fake unit ID based on
+-- the package ID.
+localUnitId :: LocalBuildInfo -> UnitId
+localUnitId lbi
+    = case maybeGetDefaultLibraryLocalBuildInfo lbi of
+        Just LibComponentLocalBuildInfo { componentUnitId = uid } -> uid
+        -- Something fake:
+        _ -> mkLegacyUnitId (package (localPkgDescr lbi))
+
+-- | Extract the compatibility package key from the public library component of a
+-- 'LocalBuildInfo' if it exists, or make a fake package key based
 -- on the package ID.
-localLibraryName :: LocalBuildInfo -> LibraryName
-localLibraryName lbi =
-    foldr go (LibraryName (display (package (localPkgDescr lbi)))) (componentsConfigs lbi)
-  where go (_, clbi, _) old_n = case clbi of
-            LibComponentLocalBuildInfo { componentLibraryName = n } -> n
-            _ -> old_n
+localCompatPackageKey :: LocalBuildInfo -> String
+localCompatPackageKey lbi =
+    case maybeGetDefaultLibraryLocalBuildInfo lbi of
+        Just LibComponentLocalBuildInfo { componentCompatPackageKey = pk } -> pk
+        -- Something fake:
+        _ -> display (package (localPkgDescr lbi))
 
 -- | External package dependencies for the package as a whole. This is the
 -- union of the individual 'componentPackageDeps', less any internal deps.
-externalPackageDeps :: LocalBuildInfo -> [(InstalledPackageId, PackageId)]
+externalPackageDeps :: LocalBuildInfo -> [(UnitId, PackageId)]
 externalPackageDeps lbi =
     -- TODO:  what about non-buildable components?
     nub [ (ipkgid, pkgid)
-        | (_,clbi,_)      <- componentsConfigs lbi
+        | (clbi,_)      <- componentsConfigs lbi
         , (ipkgid, pkgid) <- componentPackageDeps clbi
-        , not (internal pkgid) ]
+        , not (internal ipkgid) ]
   where
     -- True if this dependency is an internal one (depends on the library
     -- defined in the same package).
-    internal pkgid = pkgid == packageId (localPkgDescr lbi)
-
--- | The installed package Id we use for local packages registered in the local
--- package db. This is what is used for intra-package deps between components.
---
-inplacePackageId :: PackageId -> InstalledPackageId
-inplacePackageId pkgid = InstalledPackageId (display pkgid ++ "-inplace")
+    internal ipkgid = any ((==ipkgid) . componentUnitId . fst) (componentsConfigs lbi)
 
 -- -----------------------------------------------------------------------------
--- Buildable components
+-- Source-representation of buildable components
 
 data Component = CLib   Library
                | CExe   Executable
@@ -201,48 +206,23 @@
                | CBench Benchmark
                deriving (Show, Eq, Read)
 
-data ComponentName = CLibName   -- currently only a single lib
-                   | CExeName   String
-                   | CTestName  String
-                   | CBenchName String
-                   deriving (Eq, Generic, Ord, Read, Show)
-
-instance Binary ComponentName
+-- | This gets the 'String' component name. In fact, it is
+-- guaranteed to uniquely identify a component, returning
+-- @Nothing@ if the 'ComponentName' was for the public
+-- library (which CAN conflict with an executable name.)
+componentNameString :: PackageName -> ComponentName -> Maybe String
+componentNameString (PackageName pkg_name) (CLibName n) | pkg_name == n = Nothing
+componentNameString _ (CLibName   n) = Just n
+componentNameString _ (CExeName   n) = Just n
+componentNameString _ (CTestName  n) = Just n
+componentNameString _ (CBenchName n) = Just n
 
 showComponentName :: ComponentName -> String
-showComponentName CLibName          = "library"
+showComponentName (CLibName   name) = "library '" ++ name ++ "'"
 showComponentName (CExeName   name) = "executable '" ++ name ++ "'"
 showComponentName (CTestName  name) = "test suite '" ++ name ++ "'"
 showComponentName (CBenchName name) = "benchmark '" ++ name ++ "'"
 
-data ComponentLocalBuildInfo
-  = LibComponentLocalBuildInfo {
-    -- | Resolved internal and external package dependencies for this component.
-    -- The 'BuildInfo' specifies a set of build dependencies that must be
-    -- satisfied in terms of version ranges. This field fixes those dependencies
-    -- to the specific versions available on this machine for this compiler.
-    componentPackageDeps :: [(InstalledPackageId, PackageId)],
-    componentPackageKey :: PackageKey,
-    componentLibraryName :: LibraryName,
-    componentExposedModules :: [Installed.ExposedModule],
-    componentPackageRenaming :: Map PackageName ModuleRenaming
-  }
-  | ExeComponentLocalBuildInfo {
-    componentPackageDeps :: [(InstalledPackageId, PackageId)],
-    componentPackageRenaming :: Map PackageName ModuleRenaming
-  }
-  | TestComponentLocalBuildInfo {
-    componentPackageDeps :: [(InstalledPackageId, PackageId)],
-    componentPackageRenaming :: Map PackageName ModuleRenaming
-  }
-  | BenchComponentLocalBuildInfo {
-    componentPackageDeps :: [(InstalledPackageId, PackageId)],
-    componentPackageRenaming :: Map PackageName ModuleRenaming
-  }
-  deriving (Generic, Read, Show)
-
-instance Binary ComponentLocalBuildInfo
-
 foldComponent :: (Library -> a)
               -> (Executable -> a)
               -> (TestSuite -> a)
@@ -260,7 +240,7 @@
 
 componentName :: Component -> ComponentName
 componentName =
-  foldComponent (const CLibName)
+  foldComponent (CLibName . libName)
                 (CExeName . exeName)
                 (CTestName . testName)
                 (CBenchName . benchmarkName)
@@ -269,7 +249,7 @@
 --
 pkgComponents :: PackageDescription -> [Component]
 pkgComponents pkg =
-    [ CLib  lib | Just lib <- [library pkg] ]
+    [ CLib  lib | lib <- libraries pkg ]
  ++ [ CExe  exe | exe <- executables pkg ]
  ++ [ CTest tst | tst <- testSuites  pkg ]
  ++ [ CBench bm | bm  <- benchmarks  pkg ]
@@ -302,8 +282,9 @@
 componentDisabledReason _                   = Nothing
 
 lookupComponent :: PackageDescription -> ComponentName -> Maybe Component
-lookupComponent pkg CLibName =
-    fmap CLib $ library pkg
+lookupComponent pkg (CLibName "") = lookupComponent pkg (defaultLibName (package pkg))
+lookupComponent pkg (CLibName name) =
+    fmap CLib $ find ((name ==) . libName) (libraries pkg)
 lookupComponent pkg (CExeName name) =
     fmap CExe $ find ((name ==) . exeName) (executables pkg)
 lookupComponent pkg (CTestName name) =
@@ -321,43 +302,149 @@
       error $ "internal error: the package description contains no "
            ++ "component corresponding to " ++ show cname
 
+-- -----------------------------------------------------------------------------
+-- Configuration information of buildable components
 
+data ComponentLocalBuildInfo
+  = LibComponentLocalBuildInfo {
+    -- | It would be very convenient to store the literal Library here,
+    -- but if we do that, it will get serialized (via the Binary)
+    -- instance twice.  So instead we just provide the ComponentName,
+    -- which can be used to find the Component in the
+    -- PackageDescription.  NB: eventually, this will NOT uniquely
+    -- identify the ComponentLocalBuildInfo.
+    componentLocalName :: ComponentName,
+    -- | Resolved internal and external package dependencies for this component.
+    -- The 'BuildInfo' specifies a set of build dependencies that must be
+    -- satisfied in terms of version ranges. This field fixes those dependencies
+    -- to the specific versions available on this machine for this compiler.
+    componentPackageDeps :: [(UnitId, PackageId)],
+    -- | The computed 'UnitId' which uniquely identifies this
+    -- component.
+    componentUnitId :: UnitId,
+    -- | Compatibility "package key" that we pass to older versions of GHC.
+    componentCompatPackageKey :: String,
+    -- | Compatability "package name" that we register this component as.
+    componentCompatPackageName :: PackageName,
+    -- | A list of exposed modules (either defined in this component,
+    -- or reexported from another component.)
+    componentExposedModules :: [Installed.ExposedModule],
+    -- | Convenience field, specifying whether or not this is the
+    -- "public library" that has the same name as the package.
+    componentIsPublic :: Bool,
+    -- | The set of packages that are brought into scope during
+    -- compilation, including a 'ModuleRenaming' which may used
+    -- to hide or rename modules.  This is what gets translated into
+    -- @-package-id@ arguments.  This is a modernized version of
+    -- 'componentPackageDeps', which is kept around for BC purposes.
+    componentIncludes :: [(UnitId, ModuleRenaming)]
+  }
+  | ExeComponentLocalBuildInfo {
+    componentLocalName :: ComponentName,
+    componentUnitId :: UnitId,
+    componentPackageDeps :: [(UnitId, PackageId)],
+    componentIncludes :: [(UnitId, ModuleRenaming)]
+  }
+  | TestComponentLocalBuildInfo {
+    componentLocalName :: ComponentName,
+    componentUnitId :: UnitId,
+    componentPackageDeps :: [(UnitId, PackageId)],
+    componentIncludes :: [(UnitId, ModuleRenaming)]
+  }
+  | BenchComponentLocalBuildInfo {
+    componentLocalName :: ComponentName,
+    componentUnitId :: UnitId,
+    componentPackageDeps :: [(UnitId, PackageId)],
+    componentIncludes :: [(UnitId, ModuleRenaming)]
+  }
+  deriving (Generic, Read, Show)
+
+instance Binary ComponentLocalBuildInfo
+
+getLocalComponent :: PackageDescription -> ComponentLocalBuildInfo -> Component
+getLocalComponent pkg_descr clbi = getComponent pkg_descr (componentLocalName clbi)
+
+componentComponentId :: ComponentLocalBuildInfo -> ComponentId
+componentComponentId clbi = case componentUnitId clbi of
+                                SimpleUnitId cid -> cid
+
+componentBuildDir :: LocalBuildInfo -> ComponentLocalBuildInfo -> FilePath
+componentBuildDir lbi LibComponentLocalBuildInfo{ componentIsPublic = True }
+    = buildDir lbi
+-- For now, we assume that libraries/executables/test-suites/benchmarks
+-- are only ever built once.  With Backpack, we need a special case for
+-- libraries so that we can handle building them multiple times.
+componentBuildDir lbi clbi
+    = buildDir lbi </> case componentLocalName clbi of
+                        CLibName s   -> s
+                        CExeName s   -> s
+                        CTestName s  -> s
+                        CBenchName s -> s
+
 getComponentLocalBuildInfo :: LocalBuildInfo -> ComponentName -> ComponentLocalBuildInfo
 getComponentLocalBuildInfo lbi cname =
+    case maybeGetComponentLocalBuildInfo lbi cname of
+      Just clbi -> clbi
+      Nothing   ->
+          error $ "internal error: there is no configuration data "
+               ++ "for component " ++ show cname
+
+maybeGetComponentLocalBuildInfo
+    :: LocalBuildInfo -> ComponentName -> Maybe ComponentLocalBuildInfo
+maybeGetComponentLocalBuildInfo lbi cname =
     case [ clbi
-         | (cname', clbi, _) <- componentsConfigs lbi
-         , cname == cname' ] of
-      [clbi] -> clbi
-      _      -> missingComponent
-  where
-    missingComponent =
-      error $ "internal error: there is no configuration data "
-           ++ "for component " ++ show cname
+         | (clbi, _) <- componentsConfigs lbi
+         , cid <- componentNameToUnitIds lbi cname
+         , cid == componentUnitId clbi ] of
+      [clbi] -> Just clbi
+      _      -> Nothing
 
+maybeGetDefaultLibraryLocalBuildInfo
+    :: LocalBuildInfo
+    -> Maybe ComponentLocalBuildInfo
+maybeGetDefaultLibraryLocalBuildInfo lbi =
+    case [ clbi
+         | (clbi@LibComponentLocalBuildInfo{}, _) <- componentsConfigs lbi
+         , componentIsPublic clbi
+         ] of
+      [clbi] -> Just clbi
+      _ -> Nothing
 
--- |If the package description has a library section, call the given
---  function with the library build info as argument.  Extended version of
--- 'withLib' that also gives corresponding build info.
+componentNameToUnitIds :: LocalBuildInfo -> ComponentName -> [UnitId]
+componentNameToUnitIds lbi cname =
+    [ componentUnitId clbi
+    | (clbi, _) <- componentsConfigs lbi
+    , componentName (getLocalComponent (localPkgDescr lbi) clbi) == cname ]
+
+-- | Perform the action on each buildable 'library' in the package
+-- description.  Extended version of 'withLib' that also gives
+-- corresponding build info.
 withLibLBI :: PackageDescription -> LocalBuildInfo
            -> (Library -> ComponentLocalBuildInfo -> IO ()) -> IO ()
-withLibLBI pkg_descr lbi f =
-    withLib pkg_descr $ \lib ->
-      f lib (getComponentLocalBuildInfo lbi CLibName)
+withLibLBI pkg lbi f =
+    sequence_
+        [ f lib clbi
+        | (clbi@LibComponentLocalBuildInfo{}, _) <- componentsConfigs lbi
+        , CLib lib <- [getComponent pkg (componentLocalName clbi)] ]
 
 -- | Perform the action on each buildable 'Executable' in the package
 -- description.  Extended version of 'withExe' that also gives corresponding
 -- build info.
 withExeLBI :: PackageDescription -> LocalBuildInfo
            -> (Executable -> ComponentLocalBuildInfo -> IO ()) -> IO ()
-withExeLBI pkg_descr lbi f =
-    withExe pkg_descr $ \exe ->
-      f exe (getComponentLocalBuildInfo lbi (CExeName (exeName exe)))
+withExeLBI pkg lbi f =
+    sequence_
+        [ f exe clbi
+        | (clbi@ExeComponentLocalBuildInfo{}, _) <- componentsConfigs lbi
+        , CExe exe <- [getComponent pkg (componentLocalName clbi)] ]
 
 withTestLBI :: PackageDescription -> LocalBuildInfo
             -> (TestSuite -> ComponentLocalBuildInfo -> IO ()) -> IO ()
-withTestLBI pkg_descr lbi f =
-    withTest pkg_descr $ \test ->
-      f test (getComponentLocalBuildInfo lbi (CTestName (testName test)))
+withTestLBI pkg lbi f =
+    sequence_
+        [ f test clbi
+        | (clbi@TestComponentLocalBuildInfo{}, _) <- componentsConfigs lbi
+        , CTest test <- [getComponent pkg (componentLocalName clbi)] ]
 
 {-# DEPRECATED withComponentsLBI "Use withAllComponentsInBuildOrder" #-}
 withComponentsLBI :: PackageDescription -> LocalBuildInfo
@@ -373,37 +460,44 @@
                               -> IO ()
 withAllComponentsInBuildOrder pkg lbi f =
     sequence_
-      [ f (getComponent pkg cname) clbi
-      | (cname, clbi) <- allComponentsInBuildOrder lbi ]
+      [ f (getLocalComponent pkg clbi) clbi
+      | clbi <- allComponentsInBuildOrder lbi ]
 
 withComponentsInBuildOrder :: PackageDescription -> LocalBuildInfo
-                                  -> [ComponentName]
-                                  -> (Component -> ComponentLocalBuildInfo -> IO ())
-                                  -> IO ()
+                           -> [ComponentName]
+                           -> (Component -> ComponentLocalBuildInfo -> IO ())
+                           -> IO ()
 withComponentsInBuildOrder pkg lbi cnames f =
     sequence_
-      [ f (getComponent pkg cname') clbi
-      | (cname', clbi) <- componentsInBuildOrder lbi cnames ]
+      [ f (getLocalComponent pkg clbi) clbi
+      | clbi <- componentsInBuildOrder lbi cnames ]
 
 allComponentsInBuildOrder :: LocalBuildInfo
-                          -> [(ComponentName, ComponentLocalBuildInfo)]
+                          -> [ComponentLocalBuildInfo]
 allComponentsInBuildOrder lbi =
-    componentsInBuildOrder lbi
-      [ cname | (cname, _, _) <- componentsConfigs lbi ]
+    componentsInBuildOrder' lbi
+      [ componentUnitId clbi | (clbi, _) <- componentsConfigs lbi ]
 
 componentsInBuildOrder :: LocalBuildInfo -> [ComponentName]
-                       -> [(ComponentName, ComponentLocalBuildInfo)]
+                       -> [ComponentLocalBuildInfo]
 componentsInBuildOrder lbi cnames =
-      map ((\(clbi,cname,_) -> (cname,clbi)) . vertexToNode)
+    componentsInBuildOrder' lbi
+        (concatMap (componentNameToUnitIds lbi) cnames)
+
+componentsInBuildOrder' :: LocalBuildInfo -> [UnitId]
+                       -> [ComponentLocalBuildInfo]
+componentsInBuildOrder' lbi cids =
+      map ((\(clbi,_,_) -> clbi) . vertexToNode)
     . postOrder graph
-    . map (\cname -> fromMaybe (noSuchComp cname) (keyToVertex cname))
-    $ cnames
+    . map (\cid -> fromMaybe (noSuchComp cid) (keyToVertex cid))
+    $ cids
   where
     (graph, vertexToNode, keyToVertex) =
-      graphFromEdges (map (\(a,b,c) -> (b,a,c)) (componentsConfigs lbi))
+      graphFromEdges (map (\(clbi,internal_deps) ->
+                            (clbi,componentUnitId clbi,internal_deps)) (componentsConfigs lbi))
 
-    noSuchComp cname = error $ "internal error: componentsInBuildOrder: "
-                            ++ "no such component: " ++ show cname
+    noSuchComp cid = error $ "internal error: componentsInBuildOrder: "
+                            ++ "no such component: " ++ show cid
 
     postOrder :: Graph -> [Vertex] -> [Vertex]
     postOrder g vs = postorderF (dfs g vs) []
@@ -437,27 +531,30 @@
                 -> IO [FilePath]
 depLibraryPaths inplace relative lbi clbi = do
     let pkgDescr    = localPkgDescr lbi
-        installDirs = absoluteInstallDirs pkgDescr lbi NoCopyDest
+        installDirs = absoluteComponentInstallDirs pkgDescr lbi (componentUnitId clbi) NoCopyDest
         executable  = case clbi of
                         ExeComponentLocalBuildInfo {} -> True
                         _                             -> False
         relDir | executable = bindir installDirs
                | otherwise  = libdir installDirs
 
-    let hasInternalDeps = not $ null
-                        $ [ pkgid
-                          | (_,pkgid) <- componentPackageDeps clbi
-                          , internal pkgid
-                          ]
+    let -- TODO: this is kind of inefficient
+        internalDeps = [ cid
+                       | (cid, _) <- componentPackageDeps clbi
+                       -- Test that it's internal
+                       , (sub_clbi, _) <- componentsConfigs lbi
+                       , componentUnitId sub_clbi == cid ]
+        internalLibs = [ getLibDir sub_clbi
+                       | sub_clbi <- componentsInBuildOrder'
+                                        lbi internalDeps ]
+        getLibDir sub_clbi
+          | inplace    = componentBuildDir lbi sub_clbi
+          | otherwise  = libdir (absoluteComponentInstallDirs pkgDescr lbi (componentUnitId sub_clbi) NoCopyDest)
 
     let ipkgs          = allPackages (installedPkgs lbi)
         allDepLibDirs  = concatMap Installed.libraryDirs ipkgs
-        internalLib
-          | inplace    = buildDir lbi
-          | otherwise  = libdir installDirs
-        allDepLibDirs' = if hasInternalDeps
-                            then internalLib : allDepLibDirs
-                            else allDepLibDirs
+
+        allDepLibDirs' = internalLibs ++ allDepLibDirs
     allDepLibDirsC <- mapM canonicalizePathNoFail allDepLibDirs'
 
     let p                = prefix installDirs
@@ -473,7 +570,6 @@
 
     return libPaths
   where
-    internal pkgid = pkgid == packageId (localPkgDescr lbi)
     -- 'canonicalizePath' fails on UNIX when the directory does not exists.
     -- So just don't canonicalize when it doesn't exist.
     canonicalizePathNoFail p = do
@@ -486,35 +582,58 @@
 -- -----------------------------------------------------------------------------
 -- Wrappers for a couple functions from InstallDirs
 
--- |See 'InstallDirs.absoluteInstallDirs'
-absoluteInstallDirs :: PackageDescription -> LocalBuildInfo -> CopyDest
+-- | Backwards compatibility function which computes the InstallDirs
+-- assuming that @$libname@ points to the public library (or some fake
+-- package identifier if there is no public library.)  IF AT ALL
+-- POSSIBLE, please use 'absoluteComponentInstallDirs' instead.
+absoluteInstallDirs :: PackageDescription -> LocalBuildInfo
+                    -> CopyDest
                     -> InstallDirs FilePath
 absoluteInstallDirs pkg lbi copydest =
+    absoluteComponentInstallDirs pkg lbi (localUnitId lbi) copydest
+
+-- | See 'InstallDirs.absoluteInstallDirs'.
+absoluteComponentInstallDirs :: PackageDescription -> LocalBuildInfo
+                             -> UnitId
+                             -> CopyDest
+                             -> InstallDirs FilePath
+absoluteComponentInstallDirs pkg lbi uid copydest =
   InstallDirs.absoluteInstallDirs
     (packageId pkg)
-    (localLibraryName lbi)
+    uid
     (compilerInfo (compiler lbi))
     copydest
     (hostPlatform lbi)
     (installDirTemplates lbi)
 
--- |See 'InstallDirs.prefixRelativeInstallDirs'
+-- | Backwards compatibility function which computes the InstallDirs
+-- assuming that @$libname@ points to the public library (or some fake
+-- package identifier if there is no public library.)  IF AT ALL
+-- POSSIBLE, please use 'prefixRelativeComponentInstallDirs' instead.
 prefixRelativeInstallDirs :: PackageId -> LocalBuildInfo
                           -> InstallDirs (Maybe FilePath)
 prefixRelativeInstallDirs pkg_descr lbi =
+    prefixRelativeComponentInstallDirs pkg_descr lbi (localUnitId lbi)
+
+-- |See 'InstallDirs.prefixRelativeInstallDirs'
+prefixRelativeComponentInstallDirs :: PackageId -> LocalBuildInfo
+                                   -> UnitId
+                                   -> InstallDirs (Maybe FilePath)
+prefixRelativeComponentInstallDirs pkg_descr lbi uid =
   InstallDirs.prefixRelativeInstallDirs
     (packageId pkg_descr)
-    (localLibraryName lbi)
+    uid
     (compilerInfo (compiler lbi))
     (hostPlatform lbi)
     (installDirTemplates lbi)
 
 substPathTemplate :: PackageId -> LocalBuildInfo
+                  -> UnitId
                   -> PathTemplate -> FilePath
-substPathTemplate pkgid lbi = fromPathTemplate
-                                . ( InstallDirs.substPathTemplate env )
+substPathTemplate pkgid lbi uid = fromPathTemplate
+                                    . ( InstallDirs.substPathTemplate env )
     where env = initialPathTemplateEnv
                    pkgid
-                   (localLibraryName lbi)
+                   uid
                    (compilerInfo (compiler lbi))
                    (hostPlatform lbi)
diff --git a/cabal/Cabal/Distribution/Simple/PackageIndex.hs b/cabal/Cabal/Distribution/Simple/PackageIndex.hs
--- a/cabal/Cabal/Distribution/Simple/PackageIndex.hs
+++ b/cabal/Cabal/Distribution/Simple/PackageIndex.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveGeneric #-}
 
 -----------------------------------------------------------------------------
@@ -11,8 +10,51 @@
 -- Maintainer  :  cabal-devel@haskell.org
 -- Portability :  portable
 --
--- An index of packages.
+-- An index of packages whose primary key is 'UnitId'.  Public libraries
+-- are additionally indexed by 'PackageName' and 'Version'.
+-- Technically, these are an index of *units* (so we should eventually
+-- rename it to 'UnitIndex'); but in the absence of internal libraries
+-- or Backpack each unit is equivalent to a package.
 --
+-- 'PackageIndex' is parametric over what it actually records, and it
+-- is used in two ways:
+--
+--      * The 'InstalledPackageIndex' (defined here) contains a graph of
+--        'InstalledPackageInfo's representing the packages in a
+--        package database stack.  It is used in a variety of ways:
+--
+--          * The primary use to let Cabal access the same installed
+--            package database which is used by GHC during compilation.
+--            For example, this data structure is used by 'ghc-pkg'
+--            and 'Cabal' to do consistency checks on the database
+--            (are the references closed).
+--
+--          * Given a set of dependencies, we can compute the transitive
+--            closure of dependencies.  This is to check if the versions
+--            of packages are consistent, and also needed by multiple
+--            tools (Haddock must be explicitly told about the every
+--            transitive package to do cross-package linking;
+--            preprocessors must know about the include paths of all
+--            transitive dependencies.)
+--
+--      * The 'PlanIndex' (defined in 'Distribution.Client.InstallPlan'),
+--        contains a graph of 'GenericPlanPackage'.  Ignoring its type
+--        parameters for a moment, a 'PlanIndex' is an extension of the
+--        'InstalledPackageIndex' to also record nodes for packages
+--        which are *planned* to be installed, but not actually
+--        installed yet.  A 'PlanIndex' containing only 'PreExisting'
+--        packages is essentially a 'PackageIndex'.
+--
+--        'PlanIndex'es actually require some auxiliary information, so
+--        most users interact with a 'GenericInstallPlan'.  This type is
+--        specialized as an 'ElaboratedInstallPlan' (for @cabal
+--        new-build@) or an 'InstallPlan' (for @cabal install@).
+--
+-- This 'PackageIndex' is NOT to be confused with
+-- 'Distribution.Client.PackageIndex', which indexes packages only by
+-- 'PackageName' (this makes it suitable for indexing source packages,
+-- for which we don't know 'UnitId's.)
+--
 module Distribution.Simple.PackageIndex (
   -- * Package index data type
   InstalledPackageIndex,
@@ -26,7 +68,7 @@
 
   insert,
 
-  deleteInstalledPackageId,
+  deleteUnitId,
   deleteSourcePackageId,
   deletePackageName,
 --  deleteDependency,
@@ -34,7 +76,7 @@
   -- * Queries
 
   -- ** Precise lookups
-  lookupInstalledPackageId,
+  lookupUnitId,
   lookupSourcePackageId,
   lookupPackageId,
   lookupPackageName,
@@ -60,19 +102,27 @@
   dependencyCycles,
   dependencyGraph,
   moduleNameIndex,
+
+  -- * Backwards compatibility
+  deleteInstalledPackageId,
+  lookupInstalledPackageId,
   ) where
 
+import Distribution.Compat.Binary
+import Distribution.Compat.Semigroup as Semi
+import Distribution.Package
+import Distribution.ModuleName
+import qualified Distribution.InstalledPackageInfo as IPI
+import Distribution.Version
+import Distribution.Simple.Utils
+
 import Control.Exception (assert)
 import Data.Array ((!))
 import qualified Data.Array as Array
-import Distribution.Compat.Binary (Binary)
 import qualified Data.Graph as Graph
 import Data.List as List
          ( null, foldl', sort
-         , groupBy, sortBy, find, isInfixOf, nubBy, deleteBy, deleteFirstsBy )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid (Monoid(..))
-#endif
+         , groupBy, sortBy, find, nubBy, deleteBy, deleteFirstsBy )
 import Data.Map (Map)
 import qualified Data.Map as Map
 import Data.Maybe (isNothing, fromMaybe)
@@ -80,32 +130,17 @@
 import GHC.Generics (Generic)
 import Prelude hiding (lookup)
 
-import Distribution.Package
-         ( PackageName(..), PackageId
-         , Package(..), packageName, packageVersion
-         , Dependency(Dependency)--, --PackageFixedDeps(..)
-         , InstalledPackageId(..)
-         , HasInstalledPackageId(..), PackageInstalled(..) )
-import Distribution.ModuleName
-         ( ModuleName )
-import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo )
-import qualified Distribution.InstalledPackageInfo as IPI
-import Distribution.Version
-         ( Version, withinRange )
-import Distribution.Simple.Utils (lowercase, comparing, equating)
-
 -- | The collection of information about packages from one or more 'PackageDB's.
 -- These packages generally should have an instance of 'PackageInstalled'
 --
--- Packages are uniquely identified in by their 'InstalledPackageId', they can
+-- Packages are uniquely identified in by their 'UnitId', they can
 -- also be efficiently looked up by package name or by name and version.
 --
 data PackageIndex a = PackageIndex
   -- The primary index. Each InstalledPackageInfo record is uniquely identified
-  -- by its InstalledPackageId.
+  -- by its UnitId.
   --
-  !(Map InstalledPackageId a)
+  !(Map UnitId a)
 
   -- This auxiliary index maps package names (case-sensitively) to all the
   -- versions and instances of that package. This allows us to find all
@@ -113,39 +148,42 @@
   --
   -- It is a three-level index. The first level is the package name,
   -- the second is the package version and the final level is instances
-  -- of the same package version. These are unique by InstalledPackageId
+  -- of the same package version. These are unique by UnitId
   -- and are kept in preference order.
   --
   -- FIXME: Clarify what "preference order" means. Check that this invariant is
   -- preserved. See #1463 for discussion.
   !(Map PackageName (Map Version [a]))
 
-  deriving (Generic, Show, Read)
+  deriving (Eq, Generic, Show, Read)
 
 instance Binary a => Binary (PackageIndex a)
 
 -- | The default package index which contains 'InstalledPackageInfo'.  Normally
 -- use this.
-type InstalledPackageIndex = PackageIndex InstalledPackageInfo
+type InstalledPackageIndex = PackageIndex IPI.InstalledPackageInfo
 
-instance HasInstalledPackageId a => Monoid (PackageIndex a) where
+instance HasUnitId a => Monoid (PackageIndex a) where
   mempty  = PackageIndex Map.empty Map.empty
-  mappend = merge
+  mappend = (Semi.<>)
   --save one mappend with empty in the common case:
   mconcat [] = mempty
   mconcat xs = foldr1 mappend xs
 
-invariant :: HasInstalledPackageId a => PackageIndex a -> Bool
+instance HasUnitId a => Semigroup (PackageIndex a) where
+  (<>) = merge
+
+invariant :: HasUnitId a => PackageIndex a -> Bool
 invariant (PackageIndex pids pnames) =
-     map installedPackageId (Map.elems pids)
+     map installedUnitId (Map.elems pids)
   == sort
-     [ assert pinstOk (installedPackageId pinst)
+     [ assert pinstOk (installedUnitId pinst)
      | (pname, pvers)  <- Map.toList pnames
      , let pversOk = not (Map.null pvers)
      , (pver,  pinsts) <- assert pversOk $ Map.toList pvers
-     , let pinsts'  = sortBy (comparing installedPackageId) pinsts
+     , let pinsts'  = sortBy (comparing installedUnitId) pinsts
            pinstsOk = all (\g -> length g == 1)
-                          (groupBy (equating installedPackageId) pinsts')
+                          (groupBy (equating installedUnitId) pinsts')
      , pinst           <- assert pinstsOk $ pinsts'
      , let pinstOk = packageName    pinst == pname
                   && packageVersion pinst == pver
@@ -156,8 +194,8 @@
 -- * Internal helpers
 --
 
-mkPackageIndex :: HasInstalledPackageId a
-               => Map InstalledPackageId a
+mkPackageIndex :: HasUnitId a
+               => Map UnitId a
                -> Map PackageName (Map Version [a])
                -> PackageIndex a
 mkPackageIndex pids pnames = assert (invariant index) index
@@ -170,13 +208,13 @@
 
 -- | Build an index out of a bunch of packages.
 --
--- If there are duplicates by 'InstalledPackageId' then later ones mask earlier
+-- If there are duplicates by 'UnitId' then later ones mask earlier
 -- ones.
 --
-fromList :: HasInstalledPackageId a => [a] -> PackageIndex a
+fromList :: HasUnitId a => [a] -> PackageIndex a
 fromList pkgs = mkPackageIndex pids pnames
   where
-    pids      = Map.fromList [ (installedPackageId pkg, pkg) | pkg <- pkgs ]
+    pids      = Map.fromList [ (installedUnitId pkg, pkg) | pkg <- pkgs ]
     pnames    =
       Map.fromList
         [ (packageName (head pkgsN), pvers)
@@ -186,7 +224,7 @@
         , let pvers =
                 Map.fromList
                 [ (packageVersion (head pkgsNV),
-                   nubBy (equating installedPackageId) (reverse pkgsNV))
+                   nubBy (equating installedUnitId) (reverse pkgsNV))
                 | pkgsNV <- groupBy (equating packageVersion) pkgsN
                 ]
         ]
@@ -198,14 +236,14 @@
 -- | Merge two indexes.
 --
 -- Packages from the second mask packages from the first if they have the exact
--- same 'InstalledPackageId'.
+-- same 'UnitId'.
 --
 -- For packages with the same source 'PackageId', packages from the second are
 -- \"preferred\" over those from the first. Being preferred means they are top
 -- result when we do a lookup by source 'PackageId'. This is the mechanism we
 -- use to prefer user packages over global packages.
 --
-merge :: HasInstalledPackageId a => PackageIndex a -> PackageIndex a
+merge :: HasUnitId a => PackageIndex a -> PackageIndex a
       -> PackageIndex a
 merge (PackageIndex pids1 pnames1) (PackageIndex pids2 pnames2) =
   mkPackageIndex (Map.unionWith (\_ y -> y) pids1 pids2)
@@ -214,7 +252,7 @@
     -- Packages in the second list mask those in the first, however preferred
     -- packages go first in the list.
     mergeBuckets xs ys = ys ++ (xs \\ ys)
-    (\\) = deleteFirstsBy (equating installedPackageId)
+    (\\) = deleteFirstsBy (equating installedUnitId)
 
 
 -- | Inserts a single package into the index.
@@ -222,12 +260,12 @@
 -- This is equivalent to (but slightly quicker than) using 'mappend' or
 -- 'merge' with a singleton index.
 --
-insert :: HasInstalledPackageId a => a -> PackageIndex a -> PackageIndex a
+insert :: HasUnitId a => a -> PackageIndex a -> PackageIndex a
 insert pkg (PackageIndex pids pnames) =
     mkPackageIndex pids' pnames'
 
   where
-    pids'   = Map.insert (installedPackageId pkg) pkg pids
+    pids'   = Map.insert (installedUnitId pkg) pkg pids
     pnames' = insertPackageName pnames
     insertPackageName =
       Map.insertWith' (\_ -> insertPackageVersion)
@@ -239,15 +277,15 @@
                      (packageVersion pkg) [pkg]
 
     insertPackageInstance pkgs =
-      pkg : deleteBy (equating installedPackageId) pkg pkgs
+      pkg : deleteBy (equating installedUnitId) pkg pkgs
 
 
 -- | Removes a single installed package from the index.
 --
-deleteInstalledPackageId :: HasInstalledPackageId a
-                         => InstalledPackageId -> PackageIndex a
-                         -> PackageIndex a
-deleteInstalledPackageId ipkgid original@(PackageIndex pids pnames) =
+deleteUnitId :: HasUnitId a
+             => UnitId -> PackageIndex a
+             -> PackageIndex a
+deleteUnitId ipkgid original@(PackageIndex pids pnames) =
   case Map.updateLookupWithKey (\_ _ -> Nothing) ipkgid pids of
     (Nothing,     _)     -> original
     (Just spkgid, pids') -> mkPackageIndex pids'
@@ -263,12 +301,18 @@
 
     deletePkgInstance =
         (\xs -> if List.null xs then Nothing else Just xs)
-      . List.deleteBy (\_ pkg -> installedPackageId pkg == ipkgid) undefined
+      . List.deleteBy (\_ pkg -> installedUnitId pkg == ipkgid) undefined
 
+-- | Backwards compatibility wrapper for Cabal pre-1.24.
+{-# DEPRECATED deleteInstalledPackageId "Use deleteUnitId instead" #-}
+deleteInstalledPackageId :: HasUnitId a
+                         => UnitId -> PackageIndex a
+                         -> PackageIndex a
+deleteInstalledPackageId = deleteUnitId
 
 -- | Removes all packages with this source 'PackageId' from the index.
 --
-deleteSourcePackageId :: HasInstalledPackageId a => PackageId -> PackageIndex a
+deleteSourcePackageId :: HasUnitId a => PackageId -> PackageIndex a
                       -> PackageIndex a
 deleteSourcePackageId pkgid original@(PackageIndex pids pnames) =
   case Map.lookup (packageName pkgid) pnames of
@@ -276,7 +320,7 @@
     Just pvers  -> case Map.lookup (packageVersion pkgid) pvers of
       Nothing   -> original
       Just pkgs -> mkPackageIndex
-                     (foldl' (flip (Map.delete . installedPackageId)) pids pkgs)
+                     (foldl' (flip (Map.delete . installedUnitId)) pids pkgs)
                      (deletePkgName pnames)
   where
     deletePkgName =
@@ -289,13 +333,13 @@
 
 -- | Removes all packages with this (case-sensitive) name from the index.
 --
-deletePackageName :: HasInstalledPackageId a => PackageName -> PackageIndex a
+deletePackageName :: HasUnitId a => PackageName -> PackageIndex a
                   -> PackageIndex a
 deletePackageName name original@(PackageIndex pids pnames) =
   case Map.lookup name pnames of
     Nothing     -> original
     Just pvers  -> mkPackageIndex
-                     (foldl' (flip (Map.delete . installedPackageId)) pids
+                     (foldl' (flip (Map.delete . installedUnitId)) pids
                              (concat (Map.elems pvers)))
                      (Map.delete name pnames)
 
@@ -329,7 +373,7 @@
 --
 -- They are grouped by source package id (package name and version).
 --
-allPackagesBySourcePackageId :: HasInstalledPackageId a => PackageIndex a
+allPackagesBySourcePackageId :: HasUnitId a => PackageIndex a
                              -> [(PackageId, [a])]
 allPackagesBySourcePackageId (PackageIndex _ pnames) =
   [ (packageId ipkg, ipkgs)
@@ -342,18 +386,24 @@
 
 -- | Does a lookup by source package id (name & version).
 --
--- Since multiple package DBs mask each other by 'InstalledPackageId',
+-- Since multiple package DBs mask each other by 'UnitId',
 -- then we get back at most one package.
 --
-lookupInstalledPackageId :: PackageIndex a -> InstalledPackageId
+lookupUnitId :: PackageIndex a -> UnitId
+             -> Maybe a
+lookupUnitId (PackageIndex pids _) pid = Map.lookup pid pids
+
+-- | Backwards compatibility for Cabal pre-1.24.
+{-# DEPRECATED lookupInstalledPackageId "Use lookupUnitId instead" #-}
+lookupInstalledPackageId :: PackageIndex a -> UnitId
                          -> Maybe a
-lookupInstalledPackageId (PackageIndex pids _) pid = Map.lookup pid pids
+lookupInstalledPackageId = lookupUnitId
 
 
 -- | Does a lookup by source package id (name & version).
 --
 -- There can be multiple installed packages with the same source 'PackageId'
--- but different 'InstalledPackageId'. They are returned in order of
+-- but different 'UnitId'. They are returned in order of
 -- preference, with the most preferred first.
 --
 lookupSourcePackageId :: PackageIndex a -> PackageId -> [a]
@@ -457,7 +507,7 @@
 dependencyCycles index =
   [ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ]
   where
-    adjacencyList = [ (pkg, installedPackageId pkg, installedDepends pkg)
+    adjacencyList = [ (pkg, installedUnitId pkg, installedDepends pkg)
                     | pkg <- allPackages index ]
 
 
@@ -466,12 +516,12 @@
 -- Returns such packages along with the dependencies that they're missing.
 --
 brokenPackages :: PackageInstalled a => PackageIndex a
-               -> [(a, [InstalledPackageId])]
+               -> [(a, [UnitId])]
 brokenPackages index =
   [ (pkg, missing)
   | pkg  <- allPackages index
   , let missing = [ pkg' | pkg' <- installedDepends pkg
-                         , isNothing (lookupInstalledPackageId index pkg') ]
+                         , isNothing (lookupUnitId index pkg') ]
   , not (null missing) ]
 
 -- | Tries to take the transitive closure of the package dependencies.
@@ -483,17 +533,17 @@
 -- the original given 'PackageId's do not occur in the index.
 --
 dependencyClosure :: PackageInstalled a => PackageIndex a
-                  -> [InstalledPackageId]
+                  -> [UnitId]
                   -> Either (PackageIndex a)
-                            [(a, [InstalledPackageId])]
+                            [(a, [UnitId])]
 dependencyClosure index pkgids0 = case closure mempty [] pkgids0 of
   (completed, []) -> Left completed
   (completed, _)  -> Right (brokenPackages completed)
  where
     closure completed failed []             = (completed, failed)
-    closure completed failed (pkgid:pkgids) = case lookupInstalledPackageId index pkgid of
+    closure completed failed (pkgid:pkgids) = case lookupUnitId index pkgid of
       Nothing   -> closure completed (pkgid:failed) pkgids
-      Just pkg  -> case lookupInstalledPackageId completed (installedPackageId pkg) of
+      Just pkg  -> case lookupUnitId completed (installedUnitId pkg) of
         Just _  -> closure completed  failed pkgids
         Nothing -> closure completed' failed pkgids'
           where completed' = insert pkg completed
@@ -504,7 +554,7 @@
 -- * The given 'PackageId's must be in the index.
 --
 reverseDependencyClosure :: PackageInstalled a => PackageIndex a
-                         -> [InstalledPackageId]
+                         -> [UnitId]
                          -> [a]
 reverseDependencyClosure index =
     map vertexToPkg
@@ -538,7 +588,7 @@
 dependencyGraph :: PackageInstalled a => PackageIndex a
                 -> (Graph.Graph,
                     Graph.Vertex -> a,
-                    InstalledPackageId -> Maybe Graph.Vertex)
+                    UnitId -> Maybe Graph.Vertex)
 dependencyGraph index = (graph, vertex_to_pkg, id_to_vertex)
   where
     graph = Array.listArray bounds
@@ -546,7 +596,7 @@
               | pkg <- pkgs ]
 
     pkgs             = sortBy (comparing packageId) (allPackages index)
-    vertices         = zip (map installedPackageId pkgs) [0..]
+    vertices         = zip (map installedUnitId pkgs) [0..]
     vertex_map       = Map.fromList vertices
     id_to_vertex pid = Map.lookup pid vertex_map
 
@@ -583,15 +633,20 @@
              Map.fromList [(ipid,(dep,[packageId pkg]))])
           | pkg <- allPackages index
           , ipid <- installedDepends pkg
-          , Just dep <- [lookupInstalledPackageId index ipid]
+          , Just dep <- [lookupUnitId index ipid]
           ]
 
+        -- Added in 991e52a474e2b8280432257c1771dc474a320a30,
+        -- this is a special case to handle the base 3 compatibility
+        -- package which shipped with GHC 6.10 and GHC 6.12
+        -- (it was removed in GHC 7.0).  Remove this when GHC 6.12
+        -- goes out of our support window.
         reallyIsInconsistent :: PackageInstalled a => [a] -> Bool
         reallyIsInconsistent []       = False
         reallyIsInconsistent [_p]     = False
         reallyIsInconsistent [p1, p2] =
-          let pid1 = installedPackageId p1
-              pid2 = installedPackageId p2
+          let pid1 = installedUnitId p1
+              pid2 = installedUnitId p2
           in pid1 `notElem` installedDepends p2
           && pid2 `notElem` installedDepends p1
         reallyIsInconsistent _ = True
@@ -600,15 +655,15 @@
 -- 'InstalledPackageIndex' and turns it into a map from module names to their
 -- source packages.  It's used to initialize the @build-deps@ field in @cabal
 -- init@.
-moduleNameIndex :: InstalledPackageIndex -> Map ModuleName [InstalledPackageInfo]
+moduleNameIndex :: InstalledPackageIndex -> Map ModuleName [IPI.InstalledPackageInfo]
 moduleNameIndex index =
   Map.fromListWith (++) $ do
     pkg <- allPackages index
-    IPI.ExposedModule m reexport _ <- IPI.exposedModules pkg
+    IPI.ExposedModule m reexport <- IPI.exposedModules pkg
     case reexport of
         Nothing -> return (m, [pkg])
-        Just (IPI.OriginalModule _ m') | m == m'   -> []
-                                       | otherwise -> return (m', [pkg])
+        Just (Module _ m') | m == m'   -> []
+                           | otherwise -> return (m', [pkg])
         -- The heuristic is this: we want to prefer the original package
         -- which originally exported a module.  However, if a reexport
         -- also *renamed* the module (m /= m'), then we have to use the
diff --git a/cabal/Cabal/Distribution/Simple/PreProcess.hs b/cabal/Cabal/Distribution/Simple/PreProcess.hs
--- a/cabal/Cabal/Distribution/Simple/PreProcess.hs
+++ b/cabal/Cabal/Distribution/Simple/PreProcess.hs
@@ -27,49 +27,25 @@
     where
 
 
-import Control.Monad
-import Distribution.Simple.PreProcess.Unlit (unlit)
+import Distribution.Simple.PreProcess.Unlit
 import Distribution.Package
-         ( Package(..), PackageName(..) )
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(..)
-         , Executable(..)
-         , Library(..), libModules
-         , TestSuite(..), testModules
-         , TestSuiteInterface(..)
-         , Benchmark(..), benchmarkModules, BenchmarkInterface(..) )
 import qualified Distribution.InstalledPackageInfo as Installed
-         ( InstalledPackageInfo(..) )
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.CCompiler
-         ( cSourceExtensions )
 import Distribution.Simple.Compiler
-         ( CompilerFlavor(..)
-         , compilerFlavor, compilerCompatVersion, compilerVersion )
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), Component(..) )
-import Distribution.Simple.BuildPaths (autogenModulesDir,cppHeaderName)
+import Distribution.Simple.BuildPaths
 import Distribution.Simple.Utils
-         ( createDirectoryIfMissingVerbose, withUTF8FileContents, writeUTF8File
-         , die, setupMessage, intercalate, copyFileVerbose, moreRecentFile
-         , findFileWithExtension, findFileWithExtension'
-         , getDirectoryContentsRecursive )
 import Distribution.Simple.Program
-         ( Program(..), ConfiguredProgram(..), programPath
-         , requireProgram, requireProgramVersion
-         , rawSystemProgramConf, rawSystemProgram
-         , greencardProgram, cpphsProgram, hsc2hsProgram, c2hsProgram
-         , happyProgram, alexProgram, ghcProgram, ghcjsProgram, gccProgram )
 import Distribution.Simple.Test.LibV09
-         ( writeSimpleTestStub, stubFilePath, stubName )
 import Distribution.System
-         ( OS(..), buildOS, Arch(..), Platform(..) )
 import Distribution.Text
 import Distribution.Version
-         ( Version(..), anyVersion, orLaterVersion )
 import Distribution.Verbosity
 
+import Control.Monad
 import Data.Maybe (fromMaybe)
 import Data.List (nub, isSuffixOf)
 import System.Directory (doesFileExist)
@@ -156,26 +132,27 @@
 -- |A preprocessor for turning non-Haskell files with the given extension
 -- into plain Haskell source files.
 type PPSuffixHandler
-    = (String, BuildInfo -> LocalBuildInfo -> PreProcessor)
+    = (String, BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor)
 
 -- | Apply preprocessors to the sources from 'hsSourceDirs' for a given
 -- component (lib, exe, or test suite).
 preprocessComponent :: PackageDescription
                     -> Component
                     -> LocalBuildInfo
+                    -> ComponentLocalBuildInfo
                     -> Bool
                     -> Verbosity
                     -> [PPSuffixHandler]
                     -> IO ()
-preprocessComponent pd comp lbi isSrcDist verbosity handlers = case comp of
+preprocessComponent pd comp lbi clbi isSrcDist verbosity handlers = case comp of
   (CLib lib@Library{ libBuildInfo = bi }) -> do
-    let dirs = hsSourceDirs bi ++ [autogenModulesDir lbi]
+    let dirs = hsSourceDirs bi ++ [autogenModulesDir lbi clbi]
     setupMessage verbosity "Preprocessing library" (packageId pd)
     forM_ (map ModuleName.toFilePath $ libModules lib) $
       pre dirs (buildDir lbi) (localHandlers bi)
   (CExe exe@Executable { buildInfo = bi, exeName = nm }) -> do
     let exeDir = buildDir lbi </> nm </> nm ++ "-tmp"
-        dirs   = hsSourceDirs bi ++ [autogenModulesDir lbi]
+        dirs   = hsSourceDirs bi ++ [autogenModulesDir lbi clbi]
     setupMessage verbosity ("Preprocessing executable '" ++ nm ++ "' for") (packageId pd)
     forM_ (map ModuleName.toFilePath $ otherModules bi) $
       pre dirs exeDir (localHandlers bi)
@@ -206,7 +183,7 @@
     builtinHaskellSuffixes = ["hs", "lhs", "hsig", "lhsig"]
     builtinCSuffixes       = cSourceExtensions
     builtinSuffixes        = builtinHaskellSuffixes ++ builtinCSuffixes
-    localHandlers bi = [(ext, h bi lbi) | (ext, h) <- handlers]
+    localHandlers bi = [(ext, h bi lbi clbi) | (ext, h) <- handlers]
     pre dirs dir lhndlrs fp =
       preprocessFile dirs dir isSrcDist fp verbosity builtinSuffixes lhndlrs
     preProcessTest test = preProcessComponent (testBuildInfo test)
@@ -215,7 +192,7 @@
                          (benchmarkModules bm)
     preProcessComponent bi modules exePath dir = do
         let biHandlers = localHandlers bi
-            sourceDirs = hsSourceDirs bi ++ [ autogenModulesDir lbi ]
+            sourceDirs = hsSourceDirs bi ++ [ autogenModulesDir lbi clbi ]
         sequence_ [ preprocessFile sourceDirs dir isSrcDist
                 (ModuleName.toFilePath modu) verbosity builtinSuffixes
                 biHandlers
@@ -318,8 +295,8 @@
 -- * known preprocessors
 -- ------------------------------------------------------------
 
-ppGreenCard :: BuildInfo -> LocalBuildInfo -> PreProcessor
-ppGreenCard _ lbi
+ppGreenCard :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppGreenCard _ lbi _
     = PreProcessor {
         platformIndependent = False,
         runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity ->
@@ -338,21 +315,21 @@
         either (writeUTF8File outFile) die (unlit inFile contents)
   }
 
-ppCpp :: BuildInfo -> LocalBuildInfo -> PreProcessor
+ppCpp :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
 ppCpp = ppCpp' []
 
-ppCpp' :: [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor
-ppCpp' extraArgs bi lbi =
+ppCpp' :: [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppCpp' extraArgs bi lbi clbi =
   case compilerFlavor (compiler lbi) of
-    GHC   -> ppGhcCpp ghcProgram   (>= Version [6,6] []) args bi lbi
-    GHCJS -> ppGhcCpp ghcjsProgram (const True)          args bi lbi
-    _     -> ppCpphs  args bi lbi
+    GHC   -> ppGhcCpp ghcProgram   (>= Version [6,6] []) args bi lbi clbi
+    GHCJS -> ppGhcCpp ghcjsProgram (const True)          args bi lbi clbi
+    _     -> ppCpphs  args bi lbi clbi
   where cppArgs = getCppOptions bi lbi
         args    = cppArgs ++ extraArgs
 
 ppGhcCpp :: Program -> (Version -> Bool)
-         -> [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor
-ppGhcCpp program xHs extraArgs _bi lbi =
+         -> [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppGhcCpp program xHs extraArgs _bi lbi clbi =
   PreProcessor {
     platformIndependent = False,
     runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do
@@ -366,13 +343,13 @@
           -- double-unlitted. In the future we might switch to
           -- using cpphs --unlit instead.
        ++ (if xHs version then ["-x", "hs"] else [])
-       ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi </> cppHeaderName) ]
+       ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi clbi </> cppHeaderName) ]
        ++ ["-o", outFile, inFile]
        ++ extraArgs
   }
 
-ppCpphs :: [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor
-ppCpphs extraArgs _bi lbi =
+ppCpphs :: [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppCpphs extraArgs _bi lbi clbi =
   PreProcessor {
     platformIndependent = False,
     runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do
@@ -382,13 +359,13 @@
           ("-O" ++ outFile) : inFile
         : "--noline" : "--strip"
         : (if cpphsVersion >= Version [1,6] []
-             then ["--include="++ (autogenModulesDir lbi </> cppHeaderName)]
+             then ["--include="++ (autogenModulesDir lbi clbi </> cppHeaderName)]
              else [])
         ++ extraArgs
   }
 
-ppHsc2hs :: BuildInfo -> LocalBuildInfo -> PreProcessor
-ppHsc2hs bi lbi =
+ppHsc2hs :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppHsc2hs bi lbi clbi =
   PreProcessor {
     platformIndependent = False,
     runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do
@@ -425,8 +402,8 @@
        ++ [ "--cflag="   ++ opt | opt <- PD.ccOptions    bi
                                       ++ PD.cppOptions   bi ]
        ++ [ "--cflag="   ++ opt | opt <-
-               [ "-I" ++ autogenModulesDir lbi,
-                 "-include", autogenModulesDir lbi </> cppHeaderName ] ]
+               [ "-I" ++ autogenModulesDir lbi clbi,
+                 "-include", autogenModulesDir lbi clbi </> cppHeaderName ] ]
        ++ [ "--lflag=-L" ++ opt | opt <- PD.extraLibDirs bi ]
        ++ [ "--lflag=-Wl,-R," ++ opt | isELF
                                 , opt <- PD.extraLibDirs bi ]
@@ -448,9 +425,15 @@
        ++ ["-o", outFile, inFile]
   }
   where
+    -- TODO: installedPkgs contains ALL dependencies associated with
+    -- the package, but we really only want to look at packages for the
+    -- *current* dependency.  We should use PackageIndex.dependencyClosure
+    -- on the direct depends of the component.  The signature of this
+    -- function was recently refactored, so this should be fixable
+    -- now.  Tracked with #2971 (which has a test case.)
     pkgs = PackageIndex.topologicalOrder (packageHacks (installedPkgs lbi))
     isOSX = case buildOS of OSX -> True; _ -> False
-    isELF = case buildOS of OSX -> False; Windows -> False; _ -> True;
+    isELF = case buildOS of OSX -> False; Windows -> False; AIX -> False; _ -> True;
     packageHacks = case compilerFlavor (compiler lbi) of
       GHC   -> hackRtsPackage
       GHCJS -> hackRtsPackage
@@ -469,8 +452,8 @@
 ppHsc2hsExtras buildBaseDir = filter ("_hsc.c" `isSuffixOf`) `fmap`
                               getDirectoryContentsRecursive buildBaseDir
 
-ppC2hs :: BuildInfo -> LocalBuildInfo -> PreProcessor
-ppC2hs bi lbi =
+ppC2hs :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppC2hs bi lbi clbi =
   PreProcessor {
     platformIndependent = False,
     runPreProcessor = \(inBaseDir, inRelativeFile)
@@ -484,7 +467,7 @@
           -- Options from the current package:
            [ "--cpp=" ++ programPath gccProg, "--cppopts=-E" ]
         ++ [ "--cppopts=" ++ opt | opt <- getCppOptions bi lbi ]
-        ++ [ "--cppopts=-include" ++ (autogenModulesDir lbi </> cppHeaderName) ]
+        ++ [ "--cppopts=-include" ++ (autogenModulesDir lbi clbi </> cppHeaderName) ]
         ++ [ "--include=" ++ outBaseDir ]
 
           -- Options from dependent packages
@@ -598,16 +581,16 @@
       JavaScript  -> ["javascript"]
       OtherArch _ -> []
 
-ppHappy :: BuildInfo -> LocalBuildInfo -> PreProcessor
-ppHappy _ lbi = pp { platformIndependent = True }
+ppHappy :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppHappy _ lbi _ = pp { platformIndependent = True }
   where pp = standardPP lbi happyProgram (hcFlags hc)
         hc = compilerFlavor (compiler lbi)
         hcFlags GHC = ["-agc"]
         hcFlags GHCJS = ["-agc"]
         hcFlags _ = []
 
-ppAlex :: BuildInfo -> LocalBuildInfo -> PreProcessor
-ppAlex _ lbi = pp { platformIndependent = True }
+ppAlex :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
+ppAlex _ lbi _ = pp { platformIndependent = True }
   where pp = standardPP lbi alexProgram (hcFlags hc)
         hc = compilerFlavor (compiler lbi)
         hcFlags GHC = ["-g"]
diff --git a/cabal/Cabal/Distribution/Simple/Program.hs b/cabal/Cabal/Distribution/Simple/Program.hs
--- a/cabal/Cabal/Distribution/Simple/Program.hs
+++ b/cabal/Cabal/Distribution/Simple/Program.hs
@@ -38,7 +38,8 @@
     , ProgramSearchPath
     , ProgramSearchPathEntry(..)
     , simpleProgram
-    , findProgramLocation
+    , findProgramOnSearchPath
+    , defaultProgramSearchPath
     , findProgramVersion
 
     -- * Configured program and related functions
@@ -122,6 +123,7 @@
     , rawSystemProgramConf
     , rawSystemProgramStdoutConf
     , findProgramOnPath
+    , findProgramLocation
 
     ) where
 
@@ -129,11 +131,9 @@
 import Distribution.Simple.Program.Run
 import Distribution.Simple.Program.Db
 import Distribution.Simple.Program.Builtin
-
+import Distribution.Simple.Program.Find
 import Distribution.Simple.Utils
-         ( die, findProgramLocation, findProgramVersion )
 import Distribution.Verbosity
-         ( Verbosity )
 
 
 -- | Runs the given configured program.
@@ -217,6 +217,8 @@
                                          -> ProgramConfiguration
 restoreProgramConfiguration = restoreProgramDb
 
-{-# DEPRECATED findProgramOnPath "use findProgramLocation instead" #-}
+{-# DEPRECATED findProgramOnPath "use findProgramOnSearchPath instead" #-}
 findProgramOnPath :: String -> Verbosity -> IO (Maybe FilePath)
-findProgramOnPath = flip findProgramLocation
+findProgramOnPath name verbosity =
+    fmap (fmap fst) $
+    findProgramOnSearchPath verbosity defaultProgramSearchPath name
diff --git a/cabal/Cabal/Distribution/Simple/Program/Ar.hs b/cabal/Cabal/Distribution/Simple/Program/Ar.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Ar.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Ar.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -84,7 +85,8 @@
         | inv <- multiStageProgramInvocation
                    simple (initial, middle, final) files ]
 
-  unless (hostArch == Arm) $ -- See #1537
+  unless (hostArch == Arm -- See #1537
+          || hostOS == AIX) $ -- AIX uses its own "ar" format variant
     wipeMetadata tmpPath
   equal <- filesEqual tmpPath targetPath
   unless equal $ renameFile tmpPath targetPath
diff --git a/cabal/Cabal/Distribution/Simple/Program/Builtin.hs b/cabal/Cabal/Distribution/Simple/Program/Builtin.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Builtin.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Builtin.hs
@@ -46,27 +46,16 @@
   ) where
 
 import Distribution.Simple.Program.Find
-         ( findProgramOnSearchPath )
 import Distribution.Simple.Program.Internal
-         ( stripExtractVersion )
 import Distribution.Simple.Program.Run
-         ( getProgramInvocationOutput, programInvocation )
 import Distribution.Simple.Program.Types
-         ( Program(..), ConfiguredProgram(..), simpleProgram )
 import Distribution.Simple.Utils
-         ( findProgramVersion )
 import Distribution.Compat.Exception
-         ( catchIO )
 import Distribution.Verbosity
-         ( lessVerbose )
 import Distribution.Version
-         ( Version(..), withinRange, earlierVersion, laterVersion
-         , intersectVersionRanges )
+
 import Data.Char
          ( isDigit )
-
-import Data.List
-         ( isInfixOf )
 import qualified Data.Map as Map
 
 -- ------------------------------------------------------------
@@ -227,16 +216,16 @@
 haskellSuiteProgram = (simpleProgram "haskell-suite") {
     -- pretend that the program exists, otherwise it won't be in the
     -- "configured" state
-    programFindLocation =
-      \_verbosity _searchPath -> return $ Just "haskell-suite-dummy-location"
+    programFindLocation = \_verbosity _searchPath ->
+      return $ Just ("haskell-suite-dummy-location", [])
   }
 
 -- This represent a haskell-suite package manager. See the comments for
 -- haskellSuiteProgram.
 haskellSuitePkgProgram :: Program
 haskellSuitePkgProgram = (simpleProgram "haskell-suite-pkg") {
-    programFindLocation =
-      \_verbosity _searchPath -> return $ Just "haskell-suite-pkg-dummy-location"
+    programFindLocation = \_verbosity _searchPath ->
+      return $ Just ("haskell-suite-pkg-dummy-location", [])
   }
 
 
diff --git a/cabal/Cabal/Distribution/Simple/Program/Db.hs b/cabal/Cabal/Distribution/Simple/Program/Db.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Db.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Db.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveGeneric #-}
-
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Simple.Program.Db
@@ -59,29 +56,19 @@
   ) where
 
 import Distribution.Simple.Program.Types
-         ( Program(..), ProgArg, ConfiguredProgram(..), ProgramLocation(..) )
 import Distribution.Simple.Program.Find
-         ( ProgramSearchPath, defaultProgramSearchPath
-         , findProgramOnSearchPath, programSearchPathAsPATHVar )
 import Distribution.Simple.Program.Builtin
-         ( builtinPrograms )
 import Distribution.Simple.Utils
-         ( die, doesExecutableExist )
 import Distribution.Version
-         ( Version, VersionRange, isAnyVersion, withinRange )
 import Distribution.Text
-         ( display )
 import Distribution.Verbosity
-         ( Verbosity )
+import Distribution.Compat.Binary
 
-import Distribution.Compat.Binary (Binary(..))
-#if __GLASGOW_HASKELL__ < 710
-import Data.Functor ((<$>))
-#endif
 import Data.List
          ( foldl' )
 import Data.Maybe
          ( catMaybes )
+import Data.Tuple (swap)
 import qualified Data.Map as Map
 import Control.Monad
          ( join, foldM )
@@ -129,28 +116,42 @@
 
 
 -- Read & Show instances are based on listToFM
--- Note that we only serialise the configured part of the database, this is
--- because we don't need the unconfigured part after the configure stage, and
--- additionally because we cannot read/show 'Program' as it contains functions.
+
+-- | Note that this instance does not preserve the known 'Program's.
+-- See 'restoreProgramDb' for details.
+--
 instance Show ProgramDb where
   show = show . Map.toAscList . configuredProgs
 
+-- | Note that this instance does not preserve the known 'Program's.
+-- See 'restoreProgramDb' for details.
+--
 instance Read ProgramDb where
   readsPrec p s =
     [ (emptyProgramDb { configuredProgs = Map.fromList s' }, r)
     | (s', r) <- readsPrec p s ]
 
+-- | Note that this instance does not preserve the known 'Program's.
+-- See 'restoreProgramDb' for details.
+--
 instance Binary ProgramDb where
-  put = put . configuredProgs
+  put db = do
+    put (progSearchPath db)
+    put (configuredProgs db)
+
   get = do
-      progs <- get
-      return $! emptyProgramDb { configuredProgs = progs }
+    searchpath <- get
+    progs      <- get
+    return $! emptyProgramDb {
+      progSearchPath  = searchpath,
+      configuredProgs = progs
+    }
 
 
--- | The Read\/Show instance does not preserve all the unconfigured 'Programs'
--- because 'Program' is not in Read\/Show because it contains functions. So to
--- fully restore a deserialised 'ProgramDb' use this function to add
--- back all the known 'Program's.
+-- | The 'Read'\/'Show' and 'Binary' instances do not preserve all the
+-- unconfigured 'Programs' because 'Program' is not in 'Read'\/'Show' because
+-- it contains functions. So to fully restore a deserialised 'ProgramDb' use
+-- this function to add back all the known 'Program's.
 --
 -- * It does not add the default programs, but you probably want them, use
 --   'builtinPrograms' in addition to any extra you might need.
@@ -319,21 +320,23 @@
 configureProgram verbosity prog conf = do
   let name = programName prog
   maybeLocation <- case userSpecifiedPath prog conf of
-    Nothing   -> programFindLocation prog verbosity (progSearchPath conf)
-             >>= return . fmap FoundOnSystem
+    Nothing   ->
+      programFindLocation prog verbosity (progSearchPath conf)
+      >>= return . fmap (swap . fmap FoundOnSystem . swap)
     Just path -> do
       absolute <- doesExecutableExist path
       if absolute
-        then return (Just (UserSpecified path))
+        then return (Just (UserSpecified path, []))
         else findProgramOnSearchPath verbosity (progSearchPath conf) path
-         >>= maybe (die notFound) (return . Just . UserSpecified)
+             >>= maybe (die notFound)
+                       (return . Just . swap . fmap UserSpecified . swap)
       where notFound = "Cannot find the program '" ++ name
                      ++ "'. User-specified path '"
                      ++ path ++ "' does not refer to an executable and "
                      ++ "the program is not on the system path."
   case maybeLocation of
     Nothing -> return conf
-    Just location -> do
+    Just (location, triedLocations) -> do
       version <- programFindVersion prog verbosity (locationPath location)
       newPath <- programSearchPathAsPATHVar (progSearchPath conf)
       let configuredProg        = ConfiguredProgram {
@@ -343,7 +346,8 @@
             programOverrideArgs = userSpecifiedArgs prog conf,
             programOverrideEnv  = [("PATH", Just newPath)],
             programProperties   = Map.empty,
-            programLocation     = location
+            programLocation     = location,
+            programMonitorFiles = triedLocations
           }
       configuredProg' <- programPostConf prog verbosity configuredProg
       return (updateConfiguredProgs (Map.insert name configuredProg') conf)
@@ -443,7 +447,7 @@
           | otherwise                 ->
             return $! Left (badVersion version location)
         Nothing                       ->
-          return $! Left (noVersion location)
+          return $! Left (unknownVersion location)
 
   where notFound       = "The program '"
                       ++ programName prog ++ "'" ++ versionRequirement
@@ -452,7 +456,7 @@
                       ++ programName prog ++ "'" ++ versionRequirement
                       ++ " is required but the version found at "
                       ++ locationPath l ++ " is version " ++ display v
-        noVersion l    = "The program '"
+        unknownVersion l = "The program '"
                       ++ programName prog ++ "'" ++ versionRequirement
                       ++ " is required but the version of "
                       ++ locationPath l ++ " could not be determined."
@@ -467,5 +471,5 @@
                       -> ProgramDb
                       -> IO (ConfiguredProgram, Version, ProgramDb)
 requireProgramVersion verbosity prog range programDb =
-  join $ either die return <$>
+  join $ either die return `fmap`
   lookupProgramVersion verbosity prog range programDb
diff --git a/cabal/Cabal/Distribution/Simple/Program/Find.hs b/cabal/Cabal/Distribution/Simple/Program/Find.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Find.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Find.hs
@@ -1,6 +1,8 @@
+{-# LANGUAGE CPP, DeriveGeneric #-}
+
 -----------------------------------------------------------------------------
 -- |
--- Module      :  Distribution.Simple.Program.Types
+-- Module      :  Distribution.Simple.Program.Find
 -- Copyright   :  Duncan Coutts 2013
 --
 -- Maintainer  :  cabal-devel@haskell.org
@@ -26,23 +28,26 @@
     defaultProgramSearchPath,
     findProgramOnSearchPath,
     programSearchPathAsPATHVar,
+    getSystemSearchPath,
   ) where
 
 import Distribution.Verbosity
-         ( Verbosity )
 import Distribution.Simple.Utils
-         ( debug, doesExecutableExist )
 import Distribution.System
-         ( OS(..), buildOS )
-import System.Directory
-         ( findExecutable )
 import Distribution.Compat.Environment
-         ( getEnvironment )
-import System.FilePath
-         ( (</>), (<.>), splitSearchPath, searchPathSeparator )
-import Data.List
-         ( intercalate )
+import Distribution.Compat.Binary
 
+import qualified System.Directory as Directory
+         ( findExecutable )
+import System.FilePath as FilePath
+         ( (</>), (<.>), splitSearchPath, searchPathSeparator, getSearchPath
+         , takeDirectory )
+import Data.List
+         ( nub )
+import GHC.Generics
+#if defined(mingw32_HOST_OS)
+import qualified System.Win32 as Win32
+#endif
 
 -- | A search path to use when locating executables. This is analogous
 -- to the unix @$PATH@ or win32 @%PATH%@ but with the ability to use
@@ -60,58 +65,69 @@
 data ProgramSearchPathEntry =
          ProgramSearchPathDir FilePath  -- ^ A specific dir
        | ProgramSearchPathDefault       -- ^ The system default
+  deriving (Eq, Generic)
 
+instance Binary ProgramSearchPathEntry
+
 defaultProgramSearchPath :: ProgramSearchPath
 defaultProgramSearchPath = [ProgramSearchPathDefault]
 
 findProgramOnSearchPath :: Verbosity -> ProgramSearchPath
-                        -> FilePath -> IO (Maybe FilePath)
+                        -> FilePath -> IO (Maybe (FilePath, [FilePath]))
 findProgramOnSearchPath verbosity searchpath prog = do
     debug verbosity $ "Searching for " ++ prog ++ " in path."
-    res <- tryPathElems searchpath
+    res <- tryPathElems [] searchpath
     case res of
       Nothing   -> debug verbosity ("Cannot find " ++ prog ++ " on the path")
-      Just path -> debug verbosity ("Found " ++ prog ++ " at "++ path)
+      Just (path, _) -> debug verbosity ("Found " ++ prog ++ " at "++ path)
     return res
   where
-    tryPathElems []       = return Nothing
-    tryPathElems (pe:pes) = do
+    tryPathElems :: [[FilePath]] -> [ProgramSearchPathEntry]
+                 -> IO (Maybe (FilePath, [FilePath]))
+    tryPathElems _     []       = return Nothing
+    tryPathElems tried (pe:pes) = do
       res <- tryPathElem pe
       case res of
-        Nothing -> tryPathElems pes
-        Just _  -> return res
+        (Nothing,      notfoundat) -> tryPathElems (notfoundat : tried) pes
+        (Just foundat, notfoundat) -> return (Just (foundat, alltried))
+          where
+            alltried = concat (reverse (notfoundat : tried))
 
+    tryPathElem :: ProgramSearchPathEntry -> IO (Maybe FilePath, [FilePath])
     tryPathElem (ProgramSearchPathDir dir) =
-        findFirstExe [ dir </> prog <.> ext | ext <- extensions ]
-      where
-        -- Possible improvement: on Windows, read the list of extensions from
-        -- the PATHEXT environment variable. By default PATHEXT is ".com; .exe;
-        -- .bat; .cmd".
-        extensions = case buildOS of
-                       Windows -> ["", "exe"]
-                       Ghcjs   -> ["", "exe"]
-                       _       -> [""]
+        findFirstExe [ dir </> prog <.> ext | ext <- exeExtensions ]
 
-    tryPathElem ProgramSearchPathDefault = do
-      -- 'findExecutable' doesn't check that the path really refers to an
-      -- executable on Windows (at least with GHC < 7.8). See
-      -- https://ghc.haskell.org/trac/ghc/ticket/2184
-      mExe <- findExecutable prog
+    -- On windows, getSystemSearchPath is not guaranteed 100% correct so we
+    -- use findExecutable and then approximate the not-found-at locations.
+    tryPathElem ProgramSearchPathDefault | buildOS == Windows = do
+      mExe    <- findExecutable prog
+      syspath <- getSystemSearchPath
       case mExe of
-        Just exe -> do
-          exeExists <- doesExecutableExist exe
-          if exeExists
-            then return mExe
-            else return Nothing
-        _        -> return mExe
+        Nothing ->
+          let notfoundat = [ dir </> prog | dir <- syspath ] in
+          return (Nothing, notfoundat)
 
-    findFirstExe []     = return Nothing
-    findFirstExe (f:fs) = do
-      isExe <- doesExecutableExist f
-      if isExe
-        then return (Just f)
-        else findFirstExe fs
+        Just foundat -> do
+          let founddir   = takeDirectory foundat
+              notfoundat = [ dir </> prog
+                           | dir <- takeWhile (/= founddir) syspath ]
+          return (Just foundat, notfoundat)
 
+    -- On other OSs we can just do the simple thing
+    tryPathElem ProgramSearchPathDefault = do
+      dirs <- getSystemSearchPath
+      findFirstExe [ dir </> prog <.> ext | dir <- dirs, ext <- exeExtensions ]
+
+    findFirstExe :: [FilePath] -> IO (Maybe FilePath, [FilePath])
+    findFirstExe = go []
+      where
+        go fs' []     = return (Nothing, reverse fs')
+        go fs' (f:fs) = do
+          isExe <- doesExecutableExist f
+          if isExe
+            then return (Just f, reverse fs')
+            else go (f:fs') fs
+
 -- | Interpret a 'ProgramSearchPath' to construct a new @$PATH@ env var.
 -- Note that this is close but not perfect because on Windows the search
 -- algorithm looks at more than just the @%PATH%@.
@@ -124,3 +140,46 @@
     getEntries ProgramSearchPathDefault   = do
       env <- getEnvironment
       return (maybe [] splitSearchPath (lookup "PATH" env))
+
+-- | Get the system search path. On Unix systems this is just the @$PATH@ env
+-- var, but on windows it's a bit more complicated.
+--
+getSystemSearchPath :: IO [FilePath]
+getSystemSearchPath = fmap nub $ do
+#if defined(mingw32_HOST_OS)
+    processdir <- takeDirectory `fmap` Win32.getModuleFileName Win32.nullHANDLE
+    currentdir <- Win32.getCurrentDirectory
+    systemdir  <- Win32.getSystemDirectory
+    windowsdir <- Win32.getWindowsDirectory
+    pathdirs   <- FilePath.getSearchPath
+    let path = processdir : currentdir
+             : systemdir  : windowsdir
+             : pathdirs
+    return path
+#else
+    FilePath.getSearchPath
+#endif
+
+#ifdef MIN_VERSION_directory
+#if MIN_VERSION_directory(1,2,1)
+#define HAVE_directory_121
+#endif
+#endif
+
+findExecutable :: FilePath -> IO (Maybe FilePath)
+#ifdef HAVE_directory_121
+findExecutable = Directory.findExecutable
+#else
+findExecutable prog = do
+      -- With directory < 1.2.1 'findExecutable' doesn't check that the path
+      -- really refers to an executable.
+      mExe <- Directory.findExecutable prog
+      case mExe of
+        Just exe -> do
+          exeExists <- doesExecutableExist exe
+          if exeExists
+            then return mExe
+            else return Nothing
+        _     -> return mExe
+#endif
+
diff --git a/cabal/Cabal/Distribution/Simple/Program/GHC.hs b/cabal/Cabal/Distribution/Simple/Program/GHC.hs
--- a/cabal/Cabal/Distribution/Simple/Program/GHC.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/GHC.hs
@@ -1,4 +1,6 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
+
 module Distribution.Simple.Program.GHC (
     GhcOptions(..),
     GhcMode(..),
@@ -13,25 +15,23 @@
 
   ) where
 
-import Distribution.Simple.GHC.ImplInfo ( getImplInfo, GhcImplInfo(..) )
+import Distribution.Compat.Semigroup as Semi
+import Distribution.Simple.GHC.ImplInfo
 import Distribution.Package
 import Distribution.PackageDescription hiding (Flag)
 import Distribution.ModuleName
 import Distribution.Simple.Compiler hiding (Flag)
-import Distribution.Simple.Setup    ( Flag(..), flagToMaybe, fromFlagOrDefault,
-                                      flagToList )
+import Distribution.Simple.Setup
 import Distribution.Simple.Program.Types
 import Distribution.Simple.Program.Run
+import Distribution.System
 import Distribution.Text
 import Distribution.Verbosity
-import Distribution.Utils.NubList   ( NubListR, fromNubListR )
-import Language.Haskell.Extension   ( Language(..), Extension(..) )
+import Distribution.Utils.NubList
+import Language.Haskell.Extension
 
+import GHC.Generics (Generic)
 import qualified Data.Map as M
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-#endif
-import Data.List ( intercalate )
 
 -- | A structured set of GHC options/flags
 --
@@ -74,18 +74,20 @@
   -------------
   -- Packages
 
-  -- | The package key the modules will belong to; the @ghc -this-package-key@
-  -- flag.
-  ghcOptPackageKey   :: Flag PackageKey,
+  -- | The unit ID the modules will belong to; the @ghc -this-unit-id@
+  -- flag (or @-this-package-key@ or @-package-name@ on older
+  -- versions of GHC).  This is a 'String' because we assume you've
+  -- already figured out what the correct format for this string is
+  -- (we need to handle backwards compatibility.)
+  ghcOptThisUnitId   :: Flag String,
 
   -- | GHC package databases to use, the @ghc -package-conf@ flag.
   ghcOptPackageDBs    :: PackageDBStack,
 
-  -- | The GHC packages to use. For compatability with old and new ghc, this
-  -- requires both the short and long form of the package id;
-  -- the @ghc -package@ or @ghc -package-id@ flags.
+  -- | The GHC packages to bring into scope when compiling,
+  -- the @ghc -package-id@ flags.
   ghcOptPackages      ::
-    NubListR (InstalledPackageId, PackageId, ModuleRenaming),
+    NubListR (UnitId, ModuleRenaming),
 
   -- | Start with a clean package set; the @ghc -hide-all-packages@ flag
   ghcOptHideAllPackages :: Flag Bool,
@@ -94,9 +96,6 @@
   -- -no-auto-link-packages@ flag.
   ghcOptNoAutoLinkPackages :: Flag Bool,
 
-  -- | What packages are implementing the signatures
-  ghcOptSigOf :: [(ModuleName, (PackageKey, ModuleName))],
-
   -----------------
   -- Linker stuff
 
@@ -112,6 +111,10 @@
   -- | OSX only: frameworks to link in; the @ghc -framework@ flag.
   ghcOptLinkFrameworks :: NubListR String,
 
+  -- | OSX only: Search path for frameworks to link in; the
+  -- @ghc -framework-path@ flag.
+  ghcOptLinkFrameworkDirs :: NubListR String,
+
   -- | Don't do the link step, useful in make mode; the @ghc -no-link@ flag.
   ghcOptNoLink :: Flag Bool,
 
@@ -211,7 +214,7 @@
   -- Modifies some of the GHC error messages.
   ghcOptCabal         :: Flag Bool
 
-} deriving Show
+} deriving (Show, Generic)
 
 
 data GhcMode = GhcModeCompile     -- ^ @ghc -c@
@@ -239,17 +242,19 @@
                  | GhcProfAutoExported  -- ^ @-fprof-auto-exported@
  deriving (Show, Eq)
 
-runGHC :: Verbosity -> ConfiguredProgram -> Compiler -> GhcOptions -> IO ()
-runGHC verbosity ghcProg comp opts = do
-  runProgramInvocation verbosity (ghcInvocation ghcProg comp opts)
+runGHC :: Verbosity -> ConfiguredProgram -> Compiler -> Platform  -> GhcOptions
+       -> IO ()
+runGHC verbosity ghcProg comp platform opts = do
+  runProgramInvocation verbosity (ghcInvocation ghcProg comp platform opts)
 
 
-ghcInvocation :: ConfiguredProgram -> Compiler -> GhcOptions -> ProgramInvocation
-ghcInvocation prog comp opts =
-    programInvocation prog (renderGhcOptions comp opts)
+ghcInvocation :: ConfiguredProgram -> Compiler -> Platform -> GhcOptions
+              -> ProgramInvocation
+ghcInvocation prog comp platform opts =
+    programInvocation prog (renderGhcOptions comp platform opts)
 
-renderGhcOptions :: Compiler -> GhcOptions -> [String]
-renderGhcOptions comp opts
+renderGhcOptions :: Compiler -> Platform -> GhcOptions -> [String]
+renderGhcOptions comp _platform@(Platform _arch os) opts
   | compilerFlavor comp `notElem` [GHC, GHCJS] =
     error $ "Distribution.Simple.Program.GHC.renderGhcOptions: "
     ++ "compiler flavor must be 'GHC' or 'GHCJS'!"
@@ -274,8 +279,7 @@
 
   , maybe [] verbosityOpts (flagToMaybe (ghcOptVerbosity opts))
 
-  , [ "-fbuilding-cabal-package" | flagBool ghcOptCabal
-                                 , flagBuildingCabalPkg implInfo ]
+  , [ "-fbuilding-cabal-package" | flagBool ghcOptCabal ]
 
   ----------------
   -- Compilation
@@ -337,12 +341,10 @@
   , concat [ ["-hisuf",   suf] | suf <- flag ghcOptHiSuffix  ]
   , concat [ ["-dynosuf", suf] | suf <- flag ghcOptDynObjSuffix ]
   , concat [ ["-dynhisuf",suf] | suf <- flag ghcOptDynHiSuffix  ]
-  , concat [ ["-outputdir", dir] | dir <- flag ghcOptOutputDir
-                                 , flagOutputDir implInfo ]
+  , concat [ ["-outputdir", dir] | dir <- flag ghcOptOutputDir ]
   , concat [ ["-odir",    dir] | dir <- flag ghcOptObjDir ]
   , concat [ ["-hidir",   dir] | dir <- flag ghcOptHiDir  ]
-  , concat [ ["-stubdir", dir] | dir <- flag ghcOptStubDir
-                               , flagStubdir implInfo ]
+  , concat [ ["-stubdir", dir] | dir <- flag ghcOptStubDir ]
 
   -----------------------
   -- Source search path
@@ -357,8 +359,6 @@
   , [ "-optP" ++ opt | opt <- flags ghcOptCppOptions ]
   , concat [ [ "-optP-include", "-optP" ++ inc]
            | inc <- flags ghcOptCppIncludes ]
-  , [ "-#include \"" ++ inc ++ "\""
-    | inc <- flags ghcOptFfiIncludes, flagFfiIncludes implInfo ]
   , [ "-optc" ++ opt | opt <- flags ghcOptCcOptions ]
 
   -----------------
@@ -367,7 +367,14 @@
   , [ "-optl" ++ opt | opt <- flags ghcOptLinkOptions ]
   , ["-l" ++ lib     | lib <- flags ghcOptLinkLibs ]
   , ["-L" ++ dir     | dir <- flags ghcOptLinkLibPath ]
-  , concat [ ["-framework", fmwk] | fmwk <- flags ghcOptLinkFrameworks ]
+  , if isOSX
+    then concat [ ["-framework", fmwk]
+                | fmwk <- flags ghcOptLinkFrameworks ]
+    else []
+  , if isOSX
+    then concat [ ["-framework-path", path]
+                | path <- flags ghcOptLinkFrameworkDirs ]
+    else []
   , [ "-no-hs-main"  | flagBool ghcOptLinkNoHsMain ]
   , [ "-dynload deploy" | not (null (flags ghcOptRPaths)) ]
   , concat [ [ "-optl-Wl,-rpath," ++ dir]
@@ -376,32 +383,22 @@
   -------------
   -- Packages
 
-  , concat [ [if packageKeySupported comp
-                then "-this-package-key"
-                else "-package-name", display pkgid]
-             | pkgid <- flag ghcOptPackageKey ]
+  , concat [ [ case () of
+                _ | unitIdSupported comp     -> "-this-unit-id"
+                  | packageKeySupported comp -> "-this-package-key"
+                  | otherwise                -> "-package-name"
+             , this_arg ]
+             | this_arg <- flag ghcOptThisUnitId ]
 
   , [ "-hide-all-packages"     | flagBool ghcOptHideAllPackages ]
   , [ "-no-auto-link-packages" | flagBool ghcOptNoAutoLinkPackages ]
 
   , packageDbArgs implInfo (ghcOptPackageDBs opts)
 
-  , if null (ghcOptSigOf opts)
-        then []
-        else "-sig-of"
-             : intercalate "," (map (\(n,(p,m)) -> display n ++ " is "
-                                                ++ display p ++ ":"
-                                                ++ display m)
-                                    (ghcOptSigOf opts))
-             : []
-
-  , concat $ if flagPackageId implInfo
-      then let space "" = ""
-               space xs = ' ' : xs
-           in [ ["-package-id", display ipkgid ++ space (display rns)]
-              | (ipkgid,_,rns) <- flags ghcOptPackages ]
-      else [ ["-package",    display  pkgid]
-           | (_,pkgid,_)  <- flags ghcOptPackages ]
+  , concat $ let space "" = ""
+                 space xs = ' ' : xs
+             in [ ["-package-id", display ipkgid ++ space (display rns)]
+                | (ipkgid,rns) <- flags ghcOptPackages ]
 
   ----------------------------
   -- Language and extensions
@@ -441,6 +438,7 @@
 
   where
     implInfo     = getImplInfo comp
+    isOSX        = os == OSX
     flag     flg = flagToList (flg opts)
     flags    flg = fromNubListR . flg $ opts
     flagBool flg = fromFlagOrDefault False (flg opts)
@@ -493,113 +491,8 @@
 -- Boilerplate Monoid instance for GhcOptions
 
 instance Monoid GhcOptions where
-  mempty = GhcOptions {
-    ghcOptMode               = mempty,
-    ghcOptExtra              = mempty,
-    ghcOptExtraDefault       = mempty,
-    ghcOptInputFiles         = mempty,
-    ghcOptInputModules       = mempty,
-    ghcOptOutputFile         = mempty,
-    ghcOptOutputDynFile      = mempty,
-    ghcOptSourcePathClear    = mempty,
-    ghcOptSourcePath         = mempty,
-    ghcOptPackageKey         = mempty,
-    ghcOptPackageDBs         = mempty,
-    ghcOptPackages           = mempty,
-    ghcOptHideAllPackages    = mempty,
-    ghcOptNoAutoLinkPackages = mempty,
-    ghcOptSigOf              = mempty,
-    ghcOptLinkLibs           = mempty,
-    ghcOptLinkLibPath        = mempty,
-    ghcOptLinkOptions        = mempty,
-    ghcOptLinkFrameworks     = mempty,
-    ghcOptNoLink             = mempty,
-    ghcOptLinkNoHsMain       = mempty,
-    ghcOptCcOptions          = mempty,
-    ghcOptCppOptions         = mempty,
-    ghcOptCppIncludePath     = mempty,
-    ghcOptCppIncludes        = mempty,
-    ghcOptFfiIncludes        = mempty,
-    ghcOptLanguage           = mempty,
-    ghcOptExtensions         = mempty,
-    ghcOptExtensionMap       = mempty,
-    ghcOptOptimisation       = mempty,
-    ghcOptDebugInfo          = mempty,
-    ghcOptProfilingMode      = mempty,
-    ghcOptProfilingAuto      = mempty,
-    ghcOptSplitObjs          = mempty,
-    ghcOptNumJobs            = mempty,
-    ghcOptHPCDir             = mempty,
-    ghcOptGHCiScripts        = mempty,
-    ghcOptHiSuffix           = mempty,
-    ghcOptObjSuffix          = mempty,
-    ghcOptDynHiSuffix        = mempty,
-    ghcOptDynObjSuffix       = mempty,
-    ghcOptHiDir              = mempty,
-    ghcOptObjDir             = mempty,
-    ghcOptOutputDir          = mempty,
-    ghcOptStubDir            = mempty,
-    ghcOptDynLinkMode        = mempty,
-    ghcOptShared             = mempty,
-    ghcOptFPic               = mempty,
-    ghcOptDylibName          = mempty,
-    ghcOptRPaths             = mempty,
-    ghcOptVerbosity          = mempty,
-    ghcOptCabal              = mempty
-  }
-  mappend a b = GhcOptions {
-    ghcOptMode               = combine ghcOptMode,
-    ghcOptExtra              = combine ghcOptExtra,
-    ghcOptExtraDefault       = combine ghcOptExtraDefault,
-    ghcOptInputFiles         = combine ghcOptInputFiles,
-    ghcOptInputModules       = combine ghcOptInputModules,
-    ghcOptOutputFile         = combine ghcOptOutputFile,
-    ghcOptOutputDynFile      = combine ghcOptOutputDynFile,
-    ghcOptSourcePathClear    = combine ghcOptSourcePathClear,
-    ghcOptSourcePath         = combine ghcOptSourcePath,
-    ghcOptPackageKey         = combine ghcOptPackageKey,
-    ghcOptPackageDBs         = combine ghcOptPackageDBs,
-    ghcOptPackages           = combine ghcOptPackages,
-    ghcOptHideAllPackages    = combine ghcOptHideAllPackages,
-    ghcOptNoAutoLinkPackages = combine ghcOptNoAutoLinkPackages,
-    ghcOptSigOf              = combine ghcOptSigOf,
-    ghcOptLinkLibs           = combine ghcOptLinkLibs,
-    ghcOptLinkLibPath        = combine ghcOptLinkLibPath,
-    ghcOptLinkOptions        = combine ghcOptLinkOptions,
-    ghcOptLinkFrameworks     = combine ghcOptLinkFrameworks,
-    ghcOptNoLink             = combine ghcOptNoLink,
-    ghcOptLinkNoHsMain       = combine ghcOptLinkNoHsMain,
-    ghcOptCcOptions          = combine ghcOptCcOptions,
-    ghcOptCppOptions         = combine ghcOptCppOptions,
-    ghcOptCppIncludePath     = combine ghcOptCppIncludePath,
-    ghcOptCppIncludes        = combine ghcOptCppIncludes,
-    ghcOptFfiIncludes        = combine ghcOptFfiIncludes,
-    ghcOptLanguage           = combine ghcOptLanguage,
-    ghcOptExtensions         = combine ghcOptExtensions,
-    ghcOptExtensionMap       = combine ghcOptExtensionMap,
-    ghcOptOptimisation       = combine ghcOptOptimisation,
-    ghcOptDebugInfo          = combine ghcOptDebugInfo,
-    ghcOptProfilingMode      = combine ghcOptProfilingMode,
-    ghcOptProfilingAuto      = combine ghcOptProfilingAuto,
-    ghcOptSplitObjs          = combine ghcOptSplitObjs,
-    ghcOptNumJobs            = combine ghcOptNumJobs,
-    ghcOptHPCDir             = combine ghcOptHPCDir,
-    ghcOptGHCiScripts        = combine ghcOptGHCiScripts,
-    ghcOptHiSuffix           = combine ghcOptHiSuffix,
-    ghcOptObjSuffix          = combine ghcOptObjSuffix,
-    ghcOptDynHiSuffix        = combine ghcOptDynHiSuffix,
-    ghcOptDynObjSuffix       = combine ghcOptDynObjSuffix,
-    ghcOptHiDir              = combine ghcOptHiDir,
-    ghcOptObjDir             = combine ghcOptObjDir,
-    ghcOptOutputDir          = combine ghcOptOutputDir,
-    ghcOptStubDir            = combine ghcOptStubDir,
-    ghcOptDynLinkMode        = combine ghcOptDynLinkMode,
-    ghcOptShared             = combine ghcOptShared,
-    ghcOptFPic               = combine ghcOptFPic,
-    ghcOptDylibName          = combine ghcOptDylibName,
-    ghcOptRPaths             = combine ghcOptRPaths,
-    ghcOptVerbosity          = combine ghcOptVerbosity,
-    ghcOptCabal              = combine ghcOptCabal
-  }
-    where
-      combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
+
+instance Semigroup GhcOptions where
+  (<>) = gmappend
diff --git a/cabal/Cabal/Distribution/Simple/Program/HcPkg.hs b/cabal/Cabal/Distribution/Simple/Program/HcPkg.hs
--- a/cabal/Cabal/Distribution/Simple/Program/HcPkg.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/HcPkg.hs
@@ -11,58 +11,54 @@
 
 module Distribution.Simple.Program.HcPkg (
     HcPkgInfo(..),
+    MultiInstance(..),
 
     init,
     invoke,
     register,
     reregister,
+    registerMultiInstance,
     unregister,
+    recache,
     expose,
     hide,
     dump,
+    describe,
     list,
 
     -- * Program invocations
     initInvocation,
     registerInvocation,
     reregisterInvocation,
+    registerMultiInstanceInvocation,
     unregisterInvocation,
+    recacheInvocation,
     exposeInvocation,
     hideInvocation,
     dumpInvocation,
+    describeInvocation,
     listInvocation,
   ) where
 
-import Prelude hiding (init)
-import Distribution.Package
-         ( PackageId, InstalledPackageId(..) )
+import Distribution.Package hiding (installedUnitId)
 import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo, InstalledPackageInfo(..)
-         , showInstalledPackageInfo
-         , emptyInstalledPackageInfo, fieldsInstalledPackageInfo )
 import Distribution.ParseUtils
 import Distribution.Simple.Compiler
-         ( PackageDB(..), PackageDBStack )
 import Distribution.Simple.Program.Types
-         ( ConfiguredProgram(programId) )
 import Distribution.Simple.Program.Run
-         ( ProgramInvocation(..), IOEncoding(..), programInvocation
-         , runProgramInvocation, getProgramInvocationOutput )
 import Distribution.Text
-         ( display, simpleParse )
 import Distribution.Simple.Utils
-         ( die )
 import Distribution.Verbosity
-         ( Verbosity, deafening, silent )
 import Distribution.Compat.Exception
-         ( catchExit )
 
+import Prelude hiding (init)
 import Data.Char
          ( isSpace )
 import Data.List
          ( stripPrefix )
 import System.FilePath as FilePath
-         ( (</>), splitPath, splitDirectories, joinPath, isPathSeparator )
+         ( (</>), (<.>)
+         , splitPath, splitDirectories, joinPath, isPathSeparator )
 import qualified System.FilePath.Posix as FilePath.Posix
 
 -- | Information about the features and capabilities of an @hc-pkg@
@@ -73,17 +69,29 @@
   , noPkgDbStack    :: Bool -- ^ no package DB stack supported
   , noVerboseFlag   :: Bool -- ^ hc-pkg does not support verbosity flags
   , flagPackageConf :: Bool -- ^ use package-conf option instead of package-db
-  , useSingleFileDb :: Bool -- ^ requires single file package database
+  , supportsDirDbs  :: Bool -- ^ supports directory style package databases
+  , requiresDirDbs  :: Bool -- ^ requires directory style package databases
+  , nativeMultiInstance  :: Bool -- ^ supports --enable-multi-instance flag
+  , recacheMultiInstance :: Bool -- ^ supports multi-instance via recache
   }
 
+-- | Whether or not use multi-instance functionality.
+data MultiInstance = MultiInstance | NoMultiInstance
+  deriving (Show, Read, Eq, Ord)
+
 -- | Call @hc-pkg@ to initialise a package database at the location {path}.
 --
 -- > hc-pkg init {path}
 --
-init :: HcPkgInfo -> Verbosity -> FilePath -> IO ()
-init hpi verbosity path =
-  runProgramInvocation verbosity (initInvocation hpi verbosity path)
+init :: HcPkgInfo -> Verbosity -> Bool -> FilePath -> IO ()
+init hpi verbosity preferCompat path
+  |  not (supportsDirDbs hpi)
+ || (not (requiresDirDbs hpi) && preferCompat)
+  = writeFile path "[]"
 
+  | otherwise
+  = runProgramInvocation verbosity (initInvocation hpi verbosity path)
+
 -- | Run @hc-pkg@ using a given package DB stack, directly forwarding the
 -- provided command-line arguments to it.
 invoke :: HcPkgInfo -> Verbosity -> PackageDBStack -> [String] -> IO ()
@@ -118,7 +126,51 @@
   runProgramInvocation verbosity
     (reregisterInvocation hpi verbosity packagedb pkgFile)
 
+registerMultiInstance :: HcPkgInfo -> Verbosity
+                      -> PackageDBStack
+                      -> InstalledPackageInfo
+                      -> IO ()
+registerMultiInstance hpi verbosity packagedbs pkgInfo
+  | nativeMultiInstance hpi
+  = runProgramInvocation verbosity
+      (registerMultiInstanceInvocation hpi verbosity packagedbs (Right pkgInfo))
 
+    -- This is a trick. Older versions of GHC do not support the
+    -- --enable-multi-instance flag for ghc-pkg register but it turns out that
+    -- the same ability is available by using ghc-pkg recache. The recache
+    -- command is there to support distro package managers that like to work
+    -- by just installing files and running update commands, rather than
+    -- special add/remove commands. So the way to register by this method is
+    -- to write the package registration file directly into the package db and
+    -- then call hc-pkg recache.
+    --
+  | recacheMultiInstance hpi
+  = do let pkgdb = last packagedbs
+       writeRegistrationFileDirectly hpi pkgdb pkgInfo
+       recache hpi verbosity pkgdb
+
+  | otherwise
+  = die $ "HcPkg.registerMultiInstance: the compiler does not support "
+       ++ "registering multiple instances of packages."
+
+writeRegistrationFileDirectly :: HcPkgInfo
+                              -> PackageDB
+                              -> InstalledPackageInfo
+                              -> IO ()
+writeRegistrationFileDirectly hpi (SpecificPackageDB dir) pkgInfo
+  | supportsDirDbs hpi
+  = do let pkgfile = dir </> display (installedUnitId pkgInfo) <.> "conf"
+       writeUTF8File pkgfile (showInstalledPackageInfo pkgInfo)
+
+  | otherwise
+  = die $ "HcPkg.writeRegistrationFileDirectly: compiler does not support dir style package dbs"
+
+writeRegistrationFileDirectly _ _ _ =
+    -- We don't know here what the dir for the global or user dbs are,
+    -- if that's needed it'll require a bit more plumbing to support.
+    die $ "HcPkg.writeRegistrationFileDirectly: only supports SpecificPackageDB for now"
+
+
 -- | Call @hc-pkg@ to unregister a package
 --
 -- > hc-pkg unregister [pkgid] [--user | --global | --package-db]
@@ -129,6 +181,16 @@
     (unregisterInvocation hpi verbosity packagedb pkgid)
 
 
+-- | Call @hc-pkg@ to recache the registered packages.
+--
+-- > hc-pkg recache [--user | --global | --package-db]
+--
+recache :: HcPkgInfo -> Verbosity -> PackageDB -> IO ()
+recache hpi verbosity packagedb =
+  runProgramInvocation verbosity
+    (recacheInvocation hpi verbosity packagedb)
+
+
 -- | Call @hc-pkg@ to expose a package.
 --
 -- > hc-pkg expose [pkgid] [--user | --global | --package-db]
@@ -138,7 +200,22 @@
   runProgramInvocation verbosity
     (exposeInvocation hpi verbosity packagedb pkgid)
 
+-- | Call @hc-pkg@ to retrieve a specific package
+--
+-- > hc-pkg describe [pkgid] [--user | --global | --package-db]
+--
+describe :: HcPkgInfo -> Verbosity -> PackageDBStack -> PackageId -> IO [InstalledPackageInfo]
+describe hpi verbosity packagedb pid = do
 
+  output <- getProgramInvocationOutput verbosity
+              (describeInvocation hpi verbosity packagedb pid)
+    `catchIO` \_ -> return ""
+
+  case parsePackages output of
+    Left ok -> return ok
+    _       -> die $ "failed to parse output of '"
+                  ++ programId (hcPkgProgram hpi) ++ " describe " ++ display pid ++ "'"
+
 -- | Call @hc-pkg@ to hide a package.
 --
 -- > hc-pkg hide [pkgid] [--user | --global | --package-db]
@@ -157,40 +234,40 @@
 
   output <- getProgramInvocationOutput verbosity
               (dumpInvocation hpi verbosity packagedb)
-    `catchExit` \_ -> die $ programId (hcPkgProgram hpi) ++ " dump failed"
+    `catchIO` \_ -> die $ programId (hcPkgProgram hpi) ++ " dump failed"
 
   case parsePackages output of
     Left ok -> return ok
     _       -> die $ "failed to parse output of '"
                   ++ programId (hcPkgProgram hpi) ++ " dump'"
 
+parsePackages :: String -> Either [InstalledPackageInfo] [PError]
+parsePackages str =
+  let parsed = map parseInstalledPackageInfo' (splitPkgs str)
+   in case [ msg | ParseFailed msg <- parsed ] of
+        []   -> Left [   setUnitId
+                       . maybe id mungePackagePaths (pkgRoot pkg)
+                       $ pkg
+                     | ParseOk _ pkg <- parsed ]
+        msgs -> Right msgs
   where
-    parsePackages str =
-      let parsed = map parseInstalledPackageInfo' (splitPkgs str)
-       in case [ msg | ParseFailed msg <- parsed ] of
-            []   -> Left [   setInstalledPackageId
-                           . maybe id mungePackagePaths (pkgRoot pkg)
-                           $ pkg
-                         | ParseOk _ pkg <- parsed ]
-            msgs -> Right msgs
-
     parseInstalledPackageInfo' =
       parseFieldsFlat fieldsInstalledPackageInfo emptyInstalledPackageInfo
 
-    --TODO: this could be a lot faster. We're doing normaliseLineEndings twice
-    -- and converting back and forth with lines/unlines.
-    splitPkgs :: String -> [String]
-    splitPkgs = checkEmpty . map unlines . splitWith ("---" ==) . lines
-      where
-        -- Handle the case of there being no packages at all.
-        checkEmpty [s] | all isSpace s = []
-        checkEmpty ss                  = ss
+--TODO: this could be a lot faster. We're doing normaliseLineEndings twice
+-- and converting back and forth with lines/unlines.
+splitPkgs :: String -> [String]
+splitPkgs = checkEmpty . map unlines . splitWith ("---" ==) . lines
+  where
+    -- Handle the case of there being no packages at all.
+    checkEmpty [s] | all isSpace s = []
+    checkEmpty ss                  = ss
 
-        splitWith :: (a -> Bool) -> [a] -> [[a]]
-        splitWith p xs = ys : case zs of
-                           []   -> []
-                           _:ws -> splitWith p ws
-          where (ys,zs) = break p xs
+    splitWith :: (a -> Bool) -> [a] -> [[a]]
+    splitWith p xs = ys : case zs of
+                       []   -> []
+                       _:ws -> splitWith p ws
+      where (ys,zs) = break p xs
 
 mungePackagePaths :: FilePath -> InstalledPackageInfo -> InstalledPackageInfo
 -- Perform path/URL variable substitution as per the Cabal ${pkgroot} spec
@@ -230,26 +307,24 @@
         _                                  -> Nothing
 
 
--- Older installed package info files did not have the installedPackageId
+-- Older installed package info files did not have the installedUnitId
 -- field, so if it is missing then we fill it as the source package ID.
-setInstalledPackageId :: InstalledPackageInfo -> InstalledPackageInfo
-setInstalledPackageId pkginfo@InstalledPackageInfo {
-                        installedPackageId = InstalledPackageId "",
-                        sourcePackageId    = pkgid
+setUnitId :: InstalledPackageInfo -> InstalledPackageInfo
+setUnitId pkginfo@InstalledPackageInfo {
+                        installedUnitId = SimpleUnitId (ComponentId ""),
+                        sourcePackageId = pkgid
                       }
                     = pkginfo {
-                        --TODO use a proper named function for the conversion
-                        -- from source package id to installed package id
-                        installedPackageId = InstalledPackageId (display pkgid)
+                        installedUnitId = mkLegacyUnitId pkgid
                       }
-setInstalledPackageId pkginfo = pkginfo
+setUnitId pkginfo = pkginfo
 
 
 -- | Call @hc-pkg@ to get the source package Id of all the packages in the
 -- given package database.
 --
 -- This is much less information than with 'dump', but also rather quicker.
--- Note in particular that it does not include the 'InstalledPackageId', just
+-- Note in particular that it does not include the 'UnitId', just
 -- the source 'PackageId' which is not necessarily unique in any package db.
 --
 list :: HcPkgInfo -> Verbosity -> PackageDB
@@ -258,7 +333,7 @@
 
   output <- getProgramInvocationOutput verbosity
               (listInvocation hpi verbosity packagedb)
-    `catchExit` \_ -> die $ programId (hcPkgProgram hpi) ++ " list failed"
+    `catchIO` \_ -> die $ programId (hcPkgProgram hpi) ++ " list failed"
 
   case parsePackageIds output of
     Just ok -> return ok
@@ -279,38 +354,36 @@
     args = ["init", path]
         ++ verbosityOpts hpi verbosity
 
-registerInvocation, reregisterInvocation
+registerInvocation, reregisterInvocation, registerMultiInstanceInvocation
   :: HcPkgInfo -> Verbosity -> PackageDBStack
   -> Either FilePath InstalledPackageInfo
   -> ProgramInvocation
-registerInvocation   = registerInvocation' "register"
-reregisterInvocation = registerInvocation' "update"
-
+registerInvocation   = registerInvocation' "register" NoMultiInstance
+reregisterInvocation = registerInvocation' "update"   NoMultiInstance
+registerMultiInstanceInvocation = registerInvocation' "update" MultiInstance
 
-registerInvocation' :: String -> HcPkgInfo -> Verbosity -> PackageDBStack
+registerInvocation' :: String -> MultiInstance
+                    -> HcPkgInfo -> Verbosity -> PackageDBStack
                     -> Either FilePath InstalledPackageInfo
                     -> ProgramInvocation
-registerInvocation' cmdname hpi verbosity packagedbs (Left pkgFile) =
-    programInvocation (hcPkgProgram hpi) args
-  where
-    args = [cmdname, pkgFile]
-        ++ (if noPkgDbStack hpi
-              then [packageDbOpts hpi (last packagedbs)]
-              else packageDbStackOpts hpi packagedbs)
-        ++ verbosityOpts hpi verbosity
+registerInvocation' cmdname multiInstance hpi
+                    verbosity packagedbs pkgFileOrInfo =
+    case pkgFileOrInfo of
+      Left pkgFile ->
+        programInvocation (hcPkgProgram hpi) (args pkgFile)
 
-registerInvocation' cmdname hpi verbosity packagedbs (Right pkgInfo) =
-    (programInvocation (hcPkgProgram hpi) args) {
-      progInvokeInput         = Just (showInstalledPackageInfo pkgInfo),
-      progInvokeInputEncoding = IOEncodingUTF8
-    }
+      Right pkgInfo ->
+        (programInvocation (hcPkgProgram hpi) (args "-")) {
+          progInvokeInput         = Just (showInstalledPackageInfo pkgInfo),
+          progInvokeInputEncoding = IOEncodingUTF8
+        }
   where
-    args = [cmdname, "-"]
-        ++ (if noPkgDbStack hpi
-              then [packageDbOpts hpi (last packagedbs)]
-              else packageDbStackOpts hpi packagedbs)
-        ++ verbosityOpts hpi verbosity
-
+    args file = [cmdname, file]
+             ++ (if noPkgDbStack hpi
+                   then [packageDbOpts hpi (last packagedbs)]
+                   else packageDbStackOpts hpi packagedbs)
+             ++ [ "--enable-multi-instance" | multiInstance == MultiInstance ]
+             ++ verbosityOpts hpi verbosity
 
 unregisterInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId
                      -> ProgramInvocation
@@ -320,6 +393,14 @@
     ++ verbosityOpts hpi verbosity
 
 
+recacheInvocation :: HcPkgInfo -> Verbosity -> PackageDB
+                  -> ProgramInvocation
+recacheInvocation hpi verbosity packagedb =
+  programInvocation (hcPkgProgram hpi) $
+       ["recache", packageDbOpts hpi packagedb]
+    ++ verbosityOpts hpi verbosity
+
+
 exposeInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId
                  -> ProgramInvocation
 exposeInvocation hpi verbosity packagedb pkgid =
@@ -327,6 +408,15 @@
        ["expose", packageDbOpts hpi packagedb, display pkgid]
     ++ verbosityOpts hpi verbosity
 
+describeInvocation :: HcPkgInfo -> Verbosity -> PackageDBStack -> PackageId
+                   -> ProgramInvocation
+describeInvocation hpi verbosity packagedbs pkgid =
+  programInvocation (hcPkgProgram hpi) $
+       ["describe", display pkgid]
+    ++ (if noPkgDbStack hpi
+          then [packageDbOpts hpi (last packagedbs)]
+          else packageDbStackOpts hpi packagedbs)
+    ++ verbosityOpts hpi verbosity
 
 hideInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId
                -> ProgramInvocation
diff --git a/cabal/Cabal/Distribution/Simple/Program/Hpc.hs b/cabal/Cabal/Distribution/Simple/Program/Hpc.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Hpc.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Hpc.hs
@@ -13,14 +13,13 @@
     , union
     ) where
 
-import Distribution.ModuleName ( ModuleName )
+import Distribution.ModuleName
 import Distribution.Simple.Program.Run
-         ( ProgramInvocation, programInvocation, runProgramInvocation )
-import Distribution.Simple.Program.Types ( ConfiguredProgram(..) )
-import Distribution.Text ( display )
-import Distribution.Simple.Utils ( warn )
-import Distribution.Verbosity ( Verbosity )
-import Distribution.Version ( Version(..), orLaterVersion, withinRange )
+import Distribution.Simple.Program.Types
+import Distribution.Text
+import Distribution.Simple.Utils
+import Distribution.Verbosity
+import Distribution.Version
 
 -- | Invoke hpc with the given parameters.
 --
diff --git a/cabal/Cabal/Distribution/Simple/Program/Run.hs b/cabal/Cabal/Distribution/Simple/Program/Run.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Run.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Run.hs
@@ -24,12 +24,9 @@
   ) where
 
 import Distribution.Simple.Program.Types
-         ( ConfiguredProgram(..), programPath )
 import Distribution.Simple.Utils
-         ( die, rawSystemExit, rawSystemIOWithEnv, rawSystemStdInOut
-         , toUTF8, fromUTF8, normaliseLineEndings )
 import Distribution.Verbosity
-         ( Verbosity )
+import Distribution.Compat.Environment
 
 import Data.List
          ( foldl', unfoldr )
@@ -38,8 +35,6 @@
          ( when )
 import System.Exit
          ( ExitCode(..), exitWith )
-import Distribution.Compat.Environment
-         ( getEnvironment )
 
 -- | Represents a specific invocation of a specific program.
 --
diff --git a/cabal/Cabal/Distribution/Simple/Program/Script.hs b/cabal/Cabal/Distribution/Simple/Program/Script.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Script.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Script.hs
@@ -17,9 +17,7 @@
   ) where
 
 import Distribution.Simple.Program.Run
-         ( ProgramInvocation(..) )
 import Distribution.System
-         ( OS(..) )
 
 import Data.Maybe
          ( maybeToList )
diff --git a/cabal/Cabal/Distribution/Simple/Program/Strip.hs b/cabal/Cabal/Distribution/Simple/Program/Strip.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Strip.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Strip.hs
@@ -10,16 +10,13 @@
 module Distribution.Simple.Program.Strip (stripLib, stripExe)
        where
 
-import Distribution.Simple.Program (ProgramConfiguration, lookupProgram
-                                   , programVersion, rawSystemProgram
-                                   , stripProgram)
-import Distribution.Simple.Utils   (warn)
-import Distribution.System         (Arch(..), Platform(..), OS (..), buildOS)
-import Distribution.Verbosity      (Verbosity)
-import Distribution.Version        (orLaterVersion, withinRange)
+import Distribution.Simple.Program
+import Distribution.Simple.Utils
+import Distribution.System
+import Distribution.Verbosity
+import Distribution.Version
 
 import Control.Monad               (unless)
-import Data.Version                (Version(..))
 import System.FilePath             (takeBaseName)
 
 runStrip :: Verbosity -> ProgramConfiguration -> FilePath -> [String] -> IO ()
@@ -47,10 +44,11 @@
 stripLib :: Verbosity -> Platform -> ProgramConfiguration -> FilePath -> IO ()
 stripLib verbosity (Platform arch os) conf path = do
   case os of
-    OSX -> -- '--strip-unneeded' is not supported on OS X, iOS or
+    OSX -> -- '--strip-unneeded' is not supported on OS X, iOS, AIX, or
            -- Solaris. See #1630.
            return ()
     IOS -> return ()
+    AIX -> return ()
     Solaris -> return ()
     Windows -> -- Stripping triggers a bug in 'strip.exe' for
                -- libraries with lots identically named modules. See
diff --git a/cabal/Cabal/Distribution/Simple/Program/Types.hs b/cabal/Cabal/Distribution/Simple/Program/Types.hs
--- a/cabal/Cabal/Distribution/Simple/Program/Types.hs
+++ b/cabal/Cabal/Distribution/Simple/Program/Types.hs
@@ -33,14 +33,10 @@
   ) where
 
 import Distribution.Simple.Program.Find
-         ( ProgramSearchPath, ProgramSearchPathEntry(..)
-         , findProgramOnSearchPath )
 import Distribution.Version
-         ( Version )
 import Distribution.Verbosity
-         ( Verbosity )
+import Distribution.Compat.Binary
 
-import Distribution.Compat.Binary (Binary)
 import qualified Data.Map as Map
 import GHC.Generics (Generic)
 
@@ -59,8 +55,13 @@
        --
        -- It is supplied with the prevailing search path which will typically
        -- just be used as-is, but can be extended or ignored as needed.
+       --
+       -- For the purpose of change monitoring, in addition to the location
+       -- where the program was found, it returns all the other places that
+       -- were tried.
+       --
        programFindLocation :: Verbosity -> ProgramSearchPath
-                              -> IO (Maybe FilePath),
+                              -> IO (Maybe (FilePath, [FilePath])),
 
        -- | Try to find the version of the program. For many programs this is
        -- not possible or is not necessary so it's OK to return Nothing.
@@ -108,9 +109,18 @@
        programProperties :: Map.Map String String,
 
        -- | Location of the program. eg. @\/usr\/bin\/ghc-6.4@
-       programLocation :: ProgramLocation
-     } deriving (Eq, Generic, Read, Show)
+       programLocation :: ProgramLocation,
 
+       -- | In addition to the 'programLocation' where the program was found,
+       -- these are additional locations that were looked at. The combination
+       -- of ths found location and these not-found locations can be used to
+       -- monitor to detect when the re-configuring the program might give a
+       -- different result (e.g. found in a different location).
+       --
+       programMonitorFiles :: [FilePath]
+     }
+  deriving (Eq, Generic, Read, Show)
+
 instance Binary ConfiguredProgram
 
 -- | Where a program was found. Also tells us whether it's specified by user or
@@ -160,5 +170,6 @@
      programOverrideArgs = [],
      programOverrideEnv  = [],
      programProperties   = Map.empty,
-     programLocation     = loc
+     programLocation     = loc,
+     programMonitorFiles = [] -- did not look in any other locations
   }
diff --git a/cabal/Cabal/Distribution/Simple/Register.hs b/cabal/Cabal/Distribution/Simple/Register.hs
--- a/cabal/Cabal/Distribution/Simple/Register.hs
+++ b/cabal/Cabal/Distribution/Simple/Register.hs
@@ -28,6 +28,10 @@
     unregister,
 
     initPackageDB,
+    doesPackageDBExist,
+    createPackageDB,
+    deletePackageDB,
+
     invokeHcPkg,
     registerPackage,
     generateRegistrationInfo,
@@ -37,10 +41,7 @@
   ) where
 
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), ComponentLocalBuildInfo(..)
-         , ComponentName(..), getComponentLocalBuildInfo
-         , InstallDirs(..), absoluteInstallDirs )
-import Distribution.Simple.BuildPaths (haddockName)
+import Distribution.Simple.BuildPaths
 
 import qualified Distribution.Simple.GHC   as GHC
 import qualified Distribution.Simple.GHCJS as GHCJS
@@ -49,42 +50,25 @@
 import qualified Distribution.Simple.HaskellSuite as HaskellSuite
 
 import Distribution.Simple.Compiler
-         ( compilerVersion, Compiler, CompilerFlavor(..), compilerFlavor
-         , PackageDB, PackageDBStack, absolutePackageDBPaths
-         , registrationPackageDB )
 import Distribution.Simple.Program
-         ( ProgramConfiguration, runProgramInvocation )
 import Distribution.Simple.Program.Script
-         ( invocationAsSystemScript )
-import           Distribution.Simple.Program.HcPkg (HcPkgInfo)
 import qualified Distribution.Simple.Program.HcPkg as HcPkg
 import Distribution.Simple.Setup
-         ( RegisterFlags(..), CopyDest(..)
-         , fromFlag, fromFlagOrDefault, flagToMaybe )
 import Distribution.PackageDescription
-         ( PackageDescription(..), Library(..), BuildInfo(..), libModules )
 import Distribution.Package
-         ( Package(..), packageName, InstalledPackageId(..)
-         , getHSLibraryName )
-import Distribution.InstalledPackageInfo
-         ( InstalledPackageInfo, InstalledPackageInfo(InstalledPackageInfo)
-         , showInstalledPackageInfo )
 import qualified Distribution.InstalledPackageInfo as IPI
+import Distribution.InstalledPackageInfo (InstalledPackageInfo)
 import Distribution.Simple.Utils
-         ( writeUTF8File, writeFileAtomic, setFileExecutable
-         , die, notice, setupMessage, shortRelativePath )
 import Distribution.System
-         ( OS(..), buildOS )
 import Distribution.Text
-         ( display )
-import Distribution.Version ( Version(..) )
 import Distribution.Verbosity as Verbosity
-         ( Verbosity, normal )
 
 import System.FilePath ((</>), (<.>), isAbsolute)
 import System.Directory
-         ( getCurrentDirectory )
+         ( getCurrentDirectory, removeDirectoryRecursive, removeFile
+         , doesDirectoryExist, doesFileExist )
 
+import Data.Version
 import Control.Monad (when)
 import Data.Maybe
          ( isJust, fromMaybe, maybeToList )
@@ -98,24 +82,43 @@
 register :: PackageDescription -> LocalBuildInfo
          -> RegisterFlags -- ^Install in the user's database?; verbose
          -> IO ()
-register pkg@PackageDescription { library       = Just lib  } lbi regFlags
+register pkg lbi regFlags =
+    -- We do NOT register libraries outside of the inplace database
+    -- if there is no public library, since no one else can use it
+    -- usefully (they're not public.)  If we start supporting scoped
+    -- packages, we'll have to relax this.
+    when (hasPublicLib pkg) $
+        let maybeRegister (CLib lib) _clbi =
+                registerOne pkg lbi regFlags lib
+            maybeRegister _comp _clbi = return ()
+        in withAllComponentsInBuildOrder pkg lbi maybeRegister
+
+registerOne :: PackageDescription -> LocalBuildInfo -> RegisterFlags
+            -> Library
+            -> IO ()
+registerOne pkg lbi regFlags lib
   = do
-    let clbi = getComponentLocalBuildInfo lbi CLibName
+    let clbi = getComponentLocalBuildInfo lbi (CLibName (libName lib))
 
     absPackageDBs    <- absolutePackageDBPaths packageDbs
+    -- TODO: registration info named base on LIBNAME!!!
     installedPkgInfo <- generateRegistrationInfo
                            verbosity pkg lib lbi clbi inplace reloc distPref
                            (registrationPackageDB absPackageDBs)
 
+    info verbosity (IPI.showInstalledPackageInfo installedPkgInfo)
+
     when (fromFlag (regPrintId regFlags)) $ do
-      putStrLn (display (IPI.installedPackageId installedPkgInfo))
+      putStrLn (display (IPI.installedUnitId installedPkgInfo))
 
      -- Three different modes:
     case () of
      _ | modeGenerateRegFile   -> writeRegistrationFile installedPkgInfo
        | modeGenerateRegScript -> writeRegisterScript   installedPkgInfo
-       | otherwise             -> registerPackage verbosity
-                                    installedPkgInfo pkg lbi inplace packageDbs
+       | otherwise             -> do
+           setupMessage verbosity "Registering" (packageId pkg)
+           registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.NoMultiInstance
+                           packageDbs installedPkgInfo
 
   where
     modeGenerateRegFile = isJust (flagToMaybe (regGenPkgConf regFlags))
@@ -136,7 +139,7 @@
 
     writeRegistrationFile installedPkgInfo = do
       notice verbosity ("Creating package registration file: " ++ regFile)
-      writeUTF8File regFile (showInstalledPackageInfo installedPkgInfo)
+      writeUTF8File regFile (IPI.showInstalledPackageInfo installedPkgInfo)
 
     writeRegisterScript installedPkgInfo =
       case compilerFlavor (compiler lbi) of
@@ -147,11 +150,7 @@
                (compiler lbi) (withPrograms lbi)
                (writeHcPkgRegisterScript verbosity installedPkgInfo packageDbs)
 
-register _ _ regFlags = notice verbosity "No package to register"
-  where
-    verbosity = fromFlag (regVerbosity regFlags)
 
-
 generateRegistrationInfo :: Verbosity
                          -> PackageDescription
                          -> Library
@@ -166,59 +165,80 @@
   --TODO: eliminate pwd!
   pwd <- getCurrentDirectory
 
-  --TODO: the method of setting the InstalledPackageId is compiler specific
+  --TODO: the method of setting the UnitId is compiler specific
   --      this aspect should be delegated to a per-compiler helper.
   let comp = compiler lbi
-  ipid <-
+  abi_hash <-
     case compilerFlavor comp of
      GHC | compilerVersion comp >= Version [6,11] [] -> do
-            s <- GHC.libAbiHash verbosity pkg lbi lib clbi
-            return (InstalledPackageId (display (packageId pkg) ++ '-':s))
+            fmap AbiHash $ GHC.libAbiHash verbosity pkg lbi lib clbi
      GHCJS -> do
-            s <- GHCJS.libAbiHash verbosity pkg lbi lib clbi
-            return (InstalledPackageId (display (packageId pkg) ++ '-':s))
-     _other -> do
-            return (InstalledPackageId (display (packageId pkg)))
+            fmap AbiHash $ GHCJS.libAbiHash verbosity pkg lbi lib clbi
+     _ -> return (AbiHash "")
 
   installedPkgInfo <-
     if inplace
       then return (inplaceInstalledPackageInfo pwd distPref
-                     pkg ipid lib lbi clbi)
+                     pkg abi_hash lib lbi clbi)
     else if reloc
       then relocRegistrationInfo verbosity
-                     pkg lib lbi clbi ipid packageDb
+                     pkg lib lbi clbi abi_hash packageDb
       else return (absoluteInstalledPackageInfo
-                     pkg ipid lib lbi clbi)
+                     pkg abi_hash lib lbi clbi)
 
 
-  return installedPkgInfo{ IPI.installedPackageId = ipid }
+  return installedPkgInfo{ IPI.abiHash = abi_hash }
 
 relocRegistrationInfo :: Verbosity
                       -> PackageDescription
                       -> Library
                       -> LocalBuildInfo
                       -> ComponentLocalBuildInfo
-                      -> InstalledPackageId
+                      -> AbiHash
                       -> PackageDB
                       -> IO InstalledPackageInfo
-relocRegistrationInfo verbosity pkg lib lbi clbi ipid packageDb =
+relocRegistrationInfo verbosity pkg lib lbi clbi abi_hash packageDb =
   case (compilerFlavor (compiler lbi)) of
     GHC -> do fs <- GHC.pkgRoot verbosity lbi packageDb
               return (relocatableInstalledPackageInfo
-                        pkg ipid lib lbi clbi fs)
+                        pkg abi_hash lib lbi clbi fs)
     _   -> die "Distribution.Simple.Register.relocRegistrationInfo: \
                \not implemented for this compiler"
 
+initPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> FilePath -> IO ()
+initPackageDB verbosity comp progdb dbPath =
+    createPackageDB verbosity comp progdb False dbPath
+
 -- | Create an empty package DB at the specified location.
-initPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> FilePath
-                 -> IO ()
-initPackageDB verbosity comp conf dbPath =
-  case compilerFlavor comp of
-    HaskellSuite {} -> HaskellSuite.initPackageDB verbosity conf dbPath
-    _               -> withHcPkg "Distribution.Simple.Register.initPackageDB: \
-                                 \not implemented for this compiler" comp conf
-                                 (\hpi -> HcPkg.init hpi verbosity dbPath)
+createPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> Bool
+                -> FilePath -> IO ()
+createPackageDB verbosity comp progdb preferCompat dbPath =
+    case compilerFlavor comp of
+      GHC   -> HcPkg.init (GHC.hcPkgInfo   progdb) verbosity preferCompat dbPath
+      GHCJS -> HcPkg.init (GHCJS.hcPkgInfo progdb) verbosity False dbPath
+      LHC   -> HcPkg.init (LHC.hcPkgInfo   progdb) verbosity False dbPath
+      UHC   -> return ()
+      HaskellSuite _ -> HaskellSuite.initPackageDB verbosity progdb dbPath
+      _              -> die $ "Distribution.Simple.Register.createPackageDB: "
+                           ++ "not implemented for this compiler"
 
+doesPackageDBExist :: FilePath -> IO Bool
+doesPackageDBExist dbPath = do
+    -- currently one impl for all compiler flavours, but could change if needed
+    dir_exists <- doesDirectoryExist dbPath
+    if dir_exists
+        then return True
+        else doesFileExist dbPath
+
+deletePackageDB :: FilePath -> IO ()
+deletePackageDB dbPath = do
+    -- currently one impl for all compiler flavours, but could change if needed
+    dir_exists <- doesDirectoryExist dbPath
+    if dir_exists
+        then removeDirectoryRecursive dbPath
+        else do file_exists <- doesFileExist dbPath
+                when file_exists $ removeFile dbPath
+
 -- | Run @hc-pkg@ using a given package DB stack, directly forwarding the
 -- provided command-line arguments to it.
 invokeHcPkg :: Verbosity -> Compiler -> ProgramConfiguration -> PackageDBStack
@@ -228,7 +248,7 @@
     (\hpi -> HcPkg.invoke hpi verbosity dbStack extraArgs)
 
 withHcPkg :: String -> Compiler -> ProgramConfiguration
-          -> (HcPkgInfo -> IO a) -> IO a
+          -> (HcPkg.HcPkgInfo -> IO a) -> IO a
 withHcPkg name comp conf f =
   case compilerFlavor comp of
     GHC   -> f (GHC.hcPkgInfo conf)
@@ -238,31 +258,29 @@
                   \not implemented for this compiler")
 
 registerPackage :: Verbosity
-                -> InstalledPackageInfo
-                -> PackageDescription
-                -> LocalBuildInfo
-                -> Bool
+                -> Compiler
+                -> ProgramConfiguration
+                -> HcPkg.MultiInstance
                 -> PackageDBStack
+                -> InstalledPackageInfo
                 -> IO ()
-registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs = do
-  let msg = if inplace
-            then "In-place registering"
-            else "Registering"
-  setupMessage verbosity msg (packageId pkg)
-  case compilerFlavor (compiler lbi) of
-    GHC   -> GHC.registerPackage   verbosity installedPkgInfo pkg lbi inplace packageDbs
-    GHCJS -> GHCJS.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs
-    LHC   -> LHC.registerPackage   verbosity installedPkgInfo pkg lbi inplace packageDbs
-    UHC   -> UHC.registerPackage   verbosity installedPkgInfo pkg lbi inplace packageDbs
+registerPackage verbosity comp progdb multiInstance packageDbs installedPkgInfo =
+  case compilerFlavor comp of
+    GHC   -> GHC.registerPackage   verbosity progdb multiInstance packageDbs installedPkgInfo
+    GHCJS -> GHCJS.registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo
+    _ | HcPkg.MultiInstance == multiInstance
+          -> die "Registering multiple package instances is not yet supported for this compiler"
+    LHC   -> LHC.registerPackage   verbosity      progdb packageDbs installedPkgInfo
+    UHC   -> UHC.registerPackage   verbosity comp progdb packageDbs installedPkgInfo
     JHC   -> notice verbosity "Registering for jhc (nothing to do)"
     HaskellSuite {} ->
-      HaskellSuite.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs
+      HaskellSuite.registerPackage verbosity      progdb packageDbs installedPkgInfo
     _    -> die "Registering is not implemented for this compiler"
 
 writeHcPkgRegisterScript :: Verbosity
                          -> InstalledPackageInfo
                          -> PackageDBStack
-                         -> HcPkgInfo
+                         -> HcPkg.HcPkgInfo
                          -> IO ()
 writeHcPkgRegisterScript verbosity installedPkgInfo packageDbs hpi = do
   let invocation  = HcPkg.reregisterInvocation hpi Verbosity.normal
@@ -289,17 +307,19 @@
   :: ([FilePath] -> [FilePath]) -- ^ Translate relative include dir paths to
                                 -- absolute paths.
   -> PackageDescription
-  -> InstalledPackageId
+  -> AbiHash
   -> Library
   -> LocalBuildInfo
   -> ComponentLocalBuildInfo
   -> InstallDirs FilePath
   -> InstalledPackageInfo
-generalInstalledPackageInfo adjustRelIncDirs pkg ipid lib lbi clbi installDirs =
-  InstalledPackageInfo {
-    IPI.installedPackageId = ipid,
-    IPI.sourcePackageId    = packageId   pkg,
-    IPI.packageKey         = componentPackageKey clbi,
+generalInstalledPackageInfo adjustRelIncDirs pkg abi_hash lib lbi clbi installDirs =
+  IPI.InstalledPackageInfo {
+    IPI.sourcePackageId    = (packageId   pkg) {
+                                pkgName = componentCompatPackageName clbi
+                             },
+    IPI.installedUnitId    = componentUnitId clbi,
+    IPI.compatPackageKey   = componentCompatPackageKey clbi,
     IPI.license            = license     pkg,
     IPI.copyright          = copyright   pkg,
     IPI.maintainer         = maintainer  pkg,
@@ -310,12 +330,10 @@
     IPI.synopsis           = synopsis    pkg,
     IPI.description        = description pkg,
     IPI.category           = category    pkg,
+    IPI.abiHash            = abi_hash,
     IPI.exposed            = libExposed  lib,
-    IPI.exposedModules     = map fixupSelf (componentExposedModules clbi),
+    IPI.exposedModules     = componentExposedModules clbi,
     IPI.hiddenModules      = otherModules bi,
-    IPI.instantiatedWith   = map (\(k,(p,n)) ->
-                                   (k,IPI.OriginalModule (IPI.installedPackageId p) n))
-                                 (instantiatedWith lbi),
     IPI.trusted            = IPI.trusted IPI.emptyInstalledPackageInfo,
     IPI.importDirs         = [ libdir installDirs | hasModules ],
     -- Note. the libsubdir and datasubdir templates have already been expanded
@@ -325,7 +343,7 @@
                                else                      extraLibDirs bi,
     IPI.dataDir            = datadir installDirs,
     IPI.hsLibraries        = if hasLibrary
-                               then [getHSLibraryName (componentLibraryName clbi)]
+                               then [getHSLibraryName (componentUnitId clbi)]
                                else [],
     IPI.extraLibraries     = extraLibs bi,
     IPI.extraGHCiLibraries = extraGHCiLibs bi,
@@ -336,8 +354,8 @@
                                  -- We don't want cc-options to be propagated
                                  -- to C compilations in other packages.
     IPI.ldOptions          = ldOptions bi,
-    IPI.frameworkDirs      = [],
     IPI.frameworks         = frameworks bi,
+    IPI.frameworkDirs      = extraFrameworkDirs bi,
     IPI.haddockInterfaces  = [haddockdir installDirs </> haddockName pkg],
     IPI.haddockHTMLs       = [htmldir installDirs],
     IPI.pkgRoot            = Nothing
@@ -350,18 +368,6 @@
                             || (not (null (jsSources bi)) &&
                                 compilerFlavor (compiler lbi) == GHCJS)
 
-    -- Since we currently don't decide the InstalledPackageId of our package
-    -- until just before we register, we didn't have one for the re-exports
-    -- of modules defined within this package, so we used an empty one that
-    -- we fill in here now that we know what it is. It's a bit of a hack,
-    -- we ought really to decide the InstalledPackageId ahead of time.
-    fixupSelf (IPI.ExposedModule n o o') =
-        IPI.ExposedModule n (fmap fixupOriginalModule o)
-                            (fmap fixupOriginalModule o')
-    fixupOriginalModule (IPI.OriginalModule i m) = IPI.OriginalModule (fixupIpid i) m
-    fixupIpid (InstalledPackageId []) = ipid
-    fixupIpid x = x
-
 -- | Construct 'InstalledPackageInfo' for a library that is in place in the
 -- build tree.
 --
@@ -370,19 +376,20 @@
 inplaceInstalledPackageInfo :: FilePath -- ^ top of the build tree
                             -> FilePath -- ^ location of the dist tree
                             -> PackageDescription
-                            -> InstalledPackageId
+                            -> AbiHash
                             -> Library
                             -> LocalBuildInfo
                             -> ComponentLocalBuildInfo
                             -> InstalledPackageInfo
-inplaceInstalledPackageInfo inplaceDir distPref pkg ipid lib lbi clbi =
+inplaceInstalledPackageInfo inplaceDir distPref pkg abi_hash lib lbi clbi =
     generalInstalledPackageInfo adjustRelativeIncludeDirs
-                                pkg ipid lib lbi clbi installDirs
+                                pkg abi_hash lib lbi clbi installDirs
   where
     adjustRelativeIncludeDirs = map (inplaceDir </>)
+    libTargetDir = componentBuildDir lbi clbi
     installDirs =
-      (absoluteInstallDirs pkg lbi NoCopyDest) {
-        libdir     = inplaceDir </> buildDir lbi,
+      (absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest) {
+        libdir     = inplaceDir </> libTargetDir,
         datadir    = inplaceDir </> dataDir pkg,
         docdir     = inplaceDocdir,
         htmldir    = inplaceHtmldir,
@@ -398,14 +405,14 @@
 -- This function knows about the layout of installed packages.
 --
 absoluteInstalledPackageInfo :: PackageDescription
-                             -> InstalledPackageId
+                             -> AbiHash
                              -> Library
                              -> LocalBuildInfo
                              -> ComponentLocalBuildInfo
                              -> InstalledPackageInfo
-absoluteInstalledPackageInfo pkg ipid lib lbi clbi =
+absoluteInstalledPackageInfo pkg abi_hash lib lbi clbi =
     generalInstalledPackageInfo adjustReativeIncludeDirs
-                                pkg ipid lib lbi clbi installDirs
+                                pkg abi_hash lib lbi clbi installDirs
   where
     -- For installed packages we install all include files into one dir,
     -- whereas in the build tree they may live in multiple local dirs.
@@ -413,19 +420,19 @@
       | null (installIncludes bi) = []
       | otherwise                 = [includedir installDirs]
     bi = libBuildInfo lib
-    installDirs = absoluteInstallDirs pkg lbi NoCopyDest
+    installDirs = absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest
 
 
 relocatableInstalledPackageInfo :: PackageDescription
-                                -> InstalledPackageId
+                                -> AbiHash
                                 -> Library
                                 -> LocalBuildInfo
                                 -> ComponentLocalBuildInfo
                                 -> FilePath
                                 -> InstalledPackageInfo
-relocatableInstalledPackageInfo pkg ipid lib lbi clbi pkgroot =
+relocatableInstalledPackageInfo pkg abi_hash lib lbi clbi pkgroot =
     generalInstalledPackageInfo adjustReativeIncludeDirs
-                                pkg ipid lib lbi clbi installDirs
+                                pkg abi_hash lib lbi clbi installDirs
   where
     -- For installed packages we install all include files into one dir,
     -- whereas in the build tree they may live in multiple local dirs.
@@ -435,7 +442,7 @@
     bi = libBuildInfo lib
 
     installDirs = fmap (("${pkgroot}" </>) . shortRelativePath pkgroot)
-                $ absoluteInstallDirs pkg lbi NoCopyDest
+                $ absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest
 
 -- -----------------------------------------------------------------------------
 -- Unregistration
diff --git a/cabal/Cabal/Distribution/Simple/Setup.hs b/cabal/Cabal/Distribution/Simple/Setup.hs
--- a/cabal/Cabal/Distribution/Simple/Setup.hs
+++ b/cabal/Cabal/Distribution/Simple/Setup.hs
@@ -30,12 +30,12 @@
 -- read and written from files. This would allow us to save configure flags in
 -- config files.
 
-{-# LANGUAGE CPP #-}
-
 module Distribution.Simple.Setup (
 
   GlobalFlags(..),   emptyGlobalFlags,   defaultGlobalFlags,   globalCommand,
   ConfigFlags(..),   emptyConfigFlags,   defaultConfigFlags,   configureCommand,
+  configPrograms,
+  AllowNewer(..),    AllowNewerDep(..),  isAllowNewer,
   configAbsolutePaths, readPackageDbList, showPackageDbList,
   CopyFlags(..),     emptyCopyFlags,     defaultCopyFlags,     copyCommand,
   InstallFlags(..),  emptyInstallFlags,  defaultInstallFlags,  installCommand,
@@ -56,6 +56,7 @@
   configureArgs, configureOptions, configureCCompiler, configureLinker,
   buildOptions, haddockOptions, installDirsOptions,
   programConfigurationOptions, programConfigurationPaths',
+  splitArgs,
 
   defaultDistPref, optionDistPref,
 
@@ -65,51 +66,34 @@
   fromFlagOrDefault,
   flagToMaybe,
   flagToList,
-  boolOpt, boolOpt', trueArg, falseArg, optionVerbosity, optionNumJobs ) where
+  boolOpt, boolOpt', trueArg, falseArg,
+  optionVerbosity, optionNumJobs, readPToMaybe ) where
 
-import Distribution.Compiler ()
+import Distribution.Compiler
 import Distribution.ReadE
 import Distribution.Text
-         ( Text(..), display )
 import qualified Distribution.Compat.ReadP as Parse
 import qualified Text.PrettyPrint as Disp
-import Distribution.ModuleName
-import Distribution.Package ( Dependency(..)
-                            , PackageName
-                            , InstalledPackageId )
-import Distribution.PackageDescription
-         ( FlagName(..), FlagAssignment )
+import Distribution.Package
+import Distribution.PackageDescription hiding (Flag)
 import Distribution.Simple.Command hiding (boolOpt, boolOpt')
 import qualified Distribution.Simple.Command as Command
-import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), defaultCompilerFlavor, PackageDB(..)
-         , DebugInfoLevel(..), flagToDebugInfoLevel
-         , OptimisationLevel(..), flagToOptimisationLevel
-         , ProfDetailLevel(..), flagToProfDetailLevel
-         , absolutePackageDBPath )
+import Distribution.Simple.Compiler hiding (Flag)
 import Distribution.Simple.Utils
-         ( wrapText, wrapLine, lowercase, intercalate )
-import Distribution.Simple.Program (Program(..), ProgramConfiguration,
-                             requireProgram,
-                             programInvocation, progInvokePath, progInvokeArgs,
-                             knownPrograms,
-                             addKnownProgram, emptyProgramConfiguration,
-                             haddockProgram, ghcProgram, gccProgram, ldProgram)
+import Distribution.Simple.Program
 import Distribution.Simple.InstallDirs
-         ( InstallDirs(..), CopyDest(..),
-           PathTemplate, toPathTemplate, fromPathTemplate )
 import Distribution.Verbosity
 import Distribution.Utils.NubList
-
-import Control.Monad (liftM)
 import Distribution.Compat.Binary (Binary)
-import Data.List   ( sort )
-import Data.Char   ( isSpace, isAlpha )
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid ( Monoid(..) )
-#endif
-import GHC.Generics (Generic)
+import Distribution.Compat.Semigroup as Semi
 
+import Control.Applicative as A ( Applicative(..), (<*) )
+import Control.Monad            ( liftM )
+import Data.List                ( sort )
+import Data.Maybe               ( listToMaybe )
+import Data.Char                ( isSpace, isAlpha )
+import GHC.Generics             ( Generic )
+
 -- FIXME Not sure where this should live
 defaultDistPref :: FilePath
 defaultDistPref = "dist"
@@ -145,9 +129,12 @@
 
 instance Monoid (Flag a) where
   mempty = NoFlag
-  _ `mappend` f@(Flag _) = f
-  f `mappend` NoFlag     = f
+  mappend = (Semi.<>)
 
+instance Semigroup (Flag a) where
+  _ <> f@(Flag _) = f
+  f <> NoFlag     = f
+
 instance Bounded a => Bounded (Flag a) where
   minBound = toFlag minBound
   maxBound = toFlag maxBound
@@ -202,7 +189,7 @@
 data GlobalFlags = GlobalFlags {
     globalVersion        :: Flag Bool,
     globalNumericVersion :: Flag Bool
-  }
+  } deriving (Generic)
 
 defaultGlobalFlags :: GlobalFlags
 defaultGlobalFlags  = GlobalFlags {
@@ -227,8 +214,8 @@
         align str = str ++ replicate (maxlen - length str) ' '
       in
          "Commands:\n"
-      ++ unlines [ "  " ++ align name ++ "    " ++ description
-                 | (name, description) <- cmdDescs ]
+      ++ unlines [ "  " ++ align name ++ "    " ++ descr
+                 | (name, descr) <- cmdDescs ]
       ++ "\n"
       ++ "For more information about a command use\n"
       ++ "  " ++ pname ++ " COMMAND --help\n\n"
@@ -253,20 +240,78 @@
 emptyGlobalFlags = mempty
 
 instance Monoid GlobalFlags where
-  mempty = GlobalFlags {
-    globalVersion        = mempty,
-    globalNumericVersion = mempty
-  }
-  mappend a b = GlobalFlags {
-    globalVersion        = combine globalVersion,
-    globalNumericVersion = combine globalNumericVersion
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup GlobalFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Config flags
 -- ------------------------------------------------------------
 
+-- | Policy for relaxing upper bounds in dependencies. For example, given
+-- 'build-depends: array >= 0.3 && < 0.5', are we allowed to relax the upper
+-- bound and choose a version of 'array' that is greater or equal to 0.5? By
+-- default the upper bounds are always strictly honored.
+data AllowNewer =
+
+  -- | Default: honor the upper bounds in all dependencies, never choose
+  -- versions newer than allowed.
+  AllowNewerNone
+
+  -- | Ignore upper bounds in dependencies on the given packages.
+  | AllowNewerSome [AllowNewerDep]
+
+  -- | Ignore upper bounds in dependencies on all packages.
+  | AllowNewerAll
+  deriving (Eq, Read, Show, Generic)
+
+-- | Dependencies can be relaxed either for all packages in the install plan, or
+-- only for some packages.
+data AllowNewerDep = AllowNewerDep PackageName
+                   | AllowNewerDepScoped PackageName PackageName
+                   deriving (Eq, Read, Show, Generic)
+
+instance Text AllowNewerDep where
+  disp (AllowNewerDep p0)          = disp p0
+  disp (AllowNewerDepScoped p0 p1) = disp p0 Disp.<> Disp.colon Disp.<> disp p1
+
+  parse = scopedP Parse.<++ normalP
+    where
+      scopedP = AllowNewerDepScoped `fmap` parse A.<* Parse.char ':' A.<*> parse
+      normalP = AllowNewerDep       `fmap` parse
+
+instance Binary AllowNewer
+instance Binary AllowNewerDep
+
+instance Semigroup AllowNewer where
+  AllowNewerNone       <> r                    = r
+  l@AllowNewerAll      <> _                    = l
+  l@(AllowNewerSome _) <> AllowNewerNone       = l
+  (AllowNewerSome   _) <> r@AllowNewerAll      = r
+  (AllowNewerSome   a) <> (AllowNewerSome b)   = AllowNewerSome (a ++ b)
+
+instance Monoid AllowNewer where
+  mempty  = AllowNewerNone
+  mappend = (Semi.<>)
+
+-- | Convert 'AllowNewer' to a boolean.
+isAllowNewer :: AllowNewer -> Bool
+isAllowNewer AllowNewerNone     = False
+isAllowNewer (AllowNewerSome _) = True
+isAllowNewer AllowNewerAll      = True
+
+allowNewerParser :: Parse.ReadP r (Maybe AllowNewer)
+allowNewerParser =
+  (Just . AllowNewerSome) `fmap` Parse.sepBy1 parse (Parse.char ',')
+
+allowNewerPrinter :: (Maybe AllowNewer) -> [Maybe String]
+allowNewerPrinter Nothing                      = []
+allowNewerPrinter (Just AllowNewerNone)        = []
+allowNewerPrinter (Just AllowNewerAll)         = [Nothing]
+allowNewerPrinter (Just (AllowNewerSome pkgs)) = map (Just . display) $ pkgs
+
 -- | Flags to @configure@ command.
 --
 -- IMPORTANT: every time a new flag is added, 'D.C.Setup.filterConfigureFlags'
@@ -276,8 +321,8 @@
     -- because the type of configure is constrained by the UserHooks.
     -- when we change UserHooks next we should pass the initial
     -- ProgramConfiguration directly and not via ConfigFlags
-    configPrograms      :: ProgramConfiguration, -- ^All programs that cabal may
-                                                 -- run
+    configPrograms_     :: Last' ProgramConfiguration, -- ^All programs that
+                                                       -- @cabal@ may run
 
     configProgramPaths  :: [(String, FilePath)], -- ^user specified programs paths
     configProgramArgs   :: [(String, [String])], -- ^user specified programs args
@@ -308,7 +353,10 @@
                                                             -- paths
     configScratchDir    :: Flag FilePath,
     configExtraLibDirs  :: [FilePath],   -- ^ path to search for extra libraries
+    configExtraFrameworkDirs :: [FilePath],   -- ^ path to search for extra
+                                              -- frameworks (OS X only)
     configExtraIncludeDirs :: [FilePath],   -- ^ path to search for header files
+    configIPID          :: Flag String, -- ^ explicit IPID to be used
 
     configDistPref :: Flag FilePath, -- ^"dist" prefix
     configVerbosity :: Flag Verbosity, -- ^verbosity level
@@ -320,8 +368,7 @@
     configStripLibs :: Flag Bool,      -- ^Enable library stripping
     configConstraints :: [Dependency], -- ^Additional constraints for
                                        -- dependencies.
-    configDependencies :: [(PackageName, InstalledPackageId)],
-    configInstantiateWith :: [(ModuleName, (InstalledPackageId, ModuleName))],
+    configDependencies :: [(PackageName, UnitId)],
       -- ^The packages depended on.
     configConfigurationsFlags :: FlagAssignment,
     configTests               :: Flag Bool, -- ^Enable test suite compilation
@@ -334,12 +381,20 @@
     configFlagError :: Flag String,
       -- ^Halt and show an error message indicating an error in flag assignment
     configRelocatable :: Flag Bool, -- ^ Enable relocatable package built
-    configDebugInfo :: Flag DebugInfoLevel  -- ^ Emit debug info.
+    configDebugInfo :: Flag DebugInfoLevel,  -- ^ Emit debug info.
+    configAllowNewer :: Maybe AllowNewer
+    -- ^ Ignore upper bounds on all or some dependencies. Wrapped in 'Maybe' to
+    -- distinguish between "default" and "explicitly disabled".
   }
   deriving (Generic, Read, Show)
 
 instance Binary ConfigFlags
 
+-- | More convenient version of 'configPrograms'. Results in an
+-- 'error' if internal invariant is violated.
+configPrograms :: ConfigFlags -> ProgramConfiguration
+configPrograms = maybe (error "FIXME: remove configPrograms") id . getLast' . configPrograms_
+
 configAbsolutePaths :: ConfigFlags -> IO ConfigFlags
 configAbsolutePaths f =
   (\v -> f { configPackageDBs = v })
@@ -348,7 +403,7 @@
 
 defaultConfigFlags :: ProgramConfiguration -> ConfigFlags
 defaultConfigFlags progConf = emptyConfigFlags {
-    configPrograms     = progConf,
+    configPrograms_    = pure progConf,
     configHcFlavor     = maybe NoFlag Flag defaultCompilerFlavor,
     configVanillaLib   = Flag True,
     configProfLib      = NoFlag,
@@ -380,7 +435,8 @@
     configExactConfiguration = Flag False,
     configFlagError    = NoFlag,
     configRelocatable  = Flag False,
-    configDebugInfo    = Flag NoDebugInfo
+    configDebugInfo    = Flag NoDebugInfo,
+    configAllowNewer   = Nothing
   }
 
 configureCommand :: ProgramConfiguration -> CommandUI ConfigFlags
@@ -573,11 +629,22 @@
          configExtraIncludeDirs (\v flags -> flags {configExtraIncludeDirs = v})
          (reqArg' "PATH" (\x -> [x]) id)
 
+      ,option "" ["ipid"]
+         "Installed package ID to compile this package as"
+         configIPID (\v flags -> flags {configIPID = v})
+         (reqArgFlag "IPID")
+
       ,option "" ["extra-lib-dirs"]
          "A list of directories to search for external libraries"
          configExtraLibDirs (\v flags -> flags {configExtraLibDirs = v})
          (reqArg' "PATH" (\x -> [x]) id)
 
+      ,option "" ["extra-framework-dirs"]
+         "A list of directories to search for external frameworks (OS X only)"
+         configExtraFrameworkDirs
+         (\v flags -> flags {configExtraFrameworkDirs = v})
+         (reqArg' "PATH" (\x -> [x]) id)
+
       ,option "" ["extra-prog-path"]
          "A list of directories to search for required programs (in addition to the normal search locations)"
          configProgramPathExtra (\v flags -> flags {configProgramPathExtra = v})
@@ -597,13 +664,6 @@
                  (readP_to_E (const "dependency expected") ((\x -> [x]) `fmap` parseDependency))
                  (map (\x -> display (fst x) ++ "=" ++ display (snd x))))
 
-      ,option "" ["instantiate-with"]
-         "A mapping of signature names to concrete module instantiations. E.g., --instantiate-with=\"Map=Data.Map.Strict@containers-0.5.5.1-inplace\""
-         configInstantiateWith (\v flags -> flags { configInstantiateWith = v })
-         (reqArg "NAME=PKG:MOD"
-                 (readP_to_E (const "signature mapping expected") ((\x -> [x]) `fmap` parseHoleMapEntry))
-                 (map (\(n,(p,m)) -> display n ++ "=" ++ display m ++ "@" ++ display p)))
-
       ,option "" ["tests"]
          "dependency checking and compilation for test suites listed in the package description file."
          configTests (\v flags -> flags { configTests = v })
@@ -619,6 +679,13 @@
          configLibCoverage (\v flags -> flags { configLibCoverage = v })
          (boolOpt [] [])
 
+      ,option [] ["allow-newer"]
+       ("Ignore upper bounds in all dependencies or DEPS")
+       configAllowNewer (\v flags -> flags { configAllowNewer = v})
+       (optArg "DEPS"
+        (readP_to_E ("Cannot parse the list of packages: " ++) allowNewerParser)
+        (Just AllowNewerAll) allowNewerPrinter)
+
       ,option "" ["exact-configuration"]
          "All direct dependencies and flags are provided on the command line."
          configExactConfiguration
@@ -667,33 +734,16 @@
     showPackageDb (Just (SpecificPackageDB db)) = db
 
 showProfDetailLevelFlag :: Flag ProfDetailLevel -> [String]
-showProfDetailLevelFlag dl =
-  case dl of
-    NoFlag                           -> []
-    Flag ProfDetailNone              -> ["none"]
-    Flag ProfDetailDefault           -> ["default"]
-    Flag ProfDetailExportedFunctions -> ["exported-functions"]
-    Flag ProfDetailToplevelFunctions -> ["toplevel-functions"]
-    Flag ProfDetailAllFunctions      -> ["all-functions"]
-    Flag (ProfDetailOther other)     -> [other]
-
+showProfDetailLevelFlag NoFlag    = []
+showProfDetailLevelFlag (Flag dl) = [showProfDetailLevel dl]
 
-parseDependency :: Parse.ReadP r (PackageName, InstalledPackageId)
+parseDependency :: Parse.ReadP r (PackageName, UnitId)
 parseDependency = do
   x <- parse
   _ <- Parse.char '='
   y <- parse
   return (x, y)
 
-parseHoleMapEntry :: Parse.ReadP r (ModuleName, (InstalledPackageId, ModuleName))
-parseHoleMapEntry = do
-  x <- parse
-  _ <- Parse.char '='
-  y <- parse
-  _ <- Parse.char '@'
-  z <- parse
-  return (x, (z, y))
-
 installDirsOptions :: [OptionField (InstallDirs (Flag PathTemplate))]
 installDirsOptions =
   [ option "" ["prefix"]
@@ -760,98 +810,12 @@
 emptyConfigFlags = mempty
 
 instance Monoid ConfigFlags where
-  mempty = ConfigFlags {
-    configPrograms      = error "FIXME: remove configPrograms",
-    configProgramPaths  = mempty,
-    configProgramArgs   = mempty,
-    configProgramPathExtra = mempty,
-    configHcFlavor      = mempty,
-    configHcPath        = mempty,
-    configHcPkg         = mempty,
-    configVanillaLib    = mempty,
-    configProfLib       = mempty,
-    configSharedLib     = mempty,
-    configDynExe        = mempty,
-    configProfExe       = mempty,
-    configProf          = mempty,
-    configProfDetail    = mempty,
-    configProfLibDetail = mempty,
-    configConfigureArgs = mempty,
-    configOptimization  = mempty,
-    configProgPrefix    = mempty,
-    configProgSuffix    = mempty,
-    configInstallDirs   = mempty,
-    configScratchDir    = mempty,
-    configDistPref      = mempty,
-    configVerbosity     = mempty,
-    configUserInstall   = mempty,
-    configPackageDBs    = mempty,
-    configGHCiLib       = mempty,
-    configSplitObjs     = mempty,
-    configStripExes     = mempty,
-    configStripLibs     = mempty,
-    configExtraLibDirs  = mempty,
-    configConstraints   = mempty,
-    configDependencies  = mempty,
-    configInstantiateWith     = mempty,
-    configExtraIncludeDirs    = mempty,
-    configConfigurationsFlags = mempty,
-    configTests               = mempty,
-    configCoverage         = mempty,
-    configLibCoverage   = mempty,
-    configExactConfiguration  = mempty,
-    configBenchmarks          = mempty,
-    configFlagError     = mempty,
-    configRelocatable   = mempty,
-    configDebugInfo     = mempty
-  }
-  mappend a b =  ConfigFlags {
-    configPrograms      = configPrograms b,
-    configProgramPaths  = combine configProgramPaths,
-    configProgramArgs   = combine configProgramArgs,
-    configProgramPathExtra = combine configProgramPathExtra,
-    configHcFlavor      = combine configHcFlavor,
-    configHcPath        = combine configHcPath,
-    configHcPkg         = combine configHcPkg,
-    configVanillaLib    = combine configVanillaLib,
-    configProfLib       = combine configProfLib,
-    configSharedLib     = combine configSharedLib,
-    configDynExe        = combine configDynExe,
-    configProfExe       = combine configProfExe,
-    configProf          = combine configProf,
-    configProfDetail    = combine configProfDetail,
-    configProfLibDetail = combine configProfLibDetail,
-    configConfigureArgs = combine configConfigureArgs,
-    configOptimization  = combine configOptimization,
-    configProgPrefix    = combine configProgPrefix,
-    configProgSuffix    = combine configProgSuffix,
-    configInstallDirs   = combine configInstallDirs,
-    configScratchDir    = combine configScratchDir,
-    configDistPref      = combine configDistPref,
-    configVerbosity     = combine configVerbosity,
-    configUserInstall   = combine configUserInstall,
-    configPackageDBs    = combine configPackageDBs,
-    configGHCiLib       = combine configGHCiLib,
-    configSplitObjs     = combine configSplitObjs,
-    configStripExes     = combine configStripExes,
-    configStripLibs     = combine configStripLibs,
-    configExtraLibDirs  = combine configExtraLibDirs,
-    configConstraints   = combine configConstraints,
-    configDependencies  = combine configDependencies,
-    configInstantiateWith     = combine configInstantiateWith,
-    configExtraIncludeDirs    = combine configExtraIncludeDirs,
-    configConfigurationsFlags = combine configConfigurationsFlags,
-    configTests               = combine configTests,
-    configCoverage         = combine configCoverage,
-    configLibCoverage         = combine configLibCoverage,
-    configExactConfiguration  = combine configExactConfiguration,
-    configBenchmarks          = combine configBenchmarks,
-    configFlagError     = combine configFlagError,
-    configRelocatable   = combine configRelocatable,
-    configDebugInfo     = combine configDebugInfo
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup ConfigFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Copy flags
 -- ------------------------------------------------------------
@@ -860,27 +824,39 @@
 data CopyFlags = CopyFlags {
     copyDest      :: Flag CopyDest,
     copyDistPref  :: Flag FilePath,
-    copyVerbosity :: Flag Verbosity
+    copyVerbosity :: Flag Verbosity,
+    -- This is the same hack as in 'buildArgs'.  But I (ezyang) don't
+    -- think it's a hack, it's the right way to make hooks more robust
+    copyArgs :: [String]
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultCopyFlags :: CopyFlags
 defaultCopyFlags  = CopyFlags {
     copyDest      = Flag NoCopyDest,
     copyDistPref  = NoFlag,
-    copyVerbosity = Flag normal
+    copyVerbosity = Flag normal,
+    copyArgs      = []
   }
 
 copyCommand :: CommandUI CopyFlags
 copyCommand = CommandUI
   { commandName         = "copy"
-  , commandSynopsis     = "Copy the files into the install locations."
+  , commandSynopsis     = "Copy the files of all/specific components to install locations."
   , commandDescription  = Just $ \_ -> wrapText $
-          "Does not call register, and allows a prefix at install time. "
+          "Components encompass executables and libraries."
+       ++ "Does not call register, and allows a prefix at install time. "
        ++ "Without the --destdir flag, configure determines location.\n"
-  , commandNotes        = Nothing
-  , commandUsage        = \pname ->
-      "Usage: " ++ pname ++ " copy [FLAGS]\n"
+  , commandNotes        = Just $ \pname ->
+       "Examples:\n"
+        ++ "  " ++ pname ++ " build           "
+        ++ "    All the components in the package\n"
+        ++ "  " ++ pname ++ " build foo       "
+        ++ "    A component (i.e. lib, exe, test suite)"
+  , commandUsage        = usageAlternatives "copy" $
+      [ "[FLAGS]"
+      , "COMPONENTS [FLAGS]"
+      ]
   , commandDefaultFlags = defaultCopyFlags
   , commandOptions      = \showOrParseArgs ->
       [optionVerbosity copyVerbosity (\v flags -> flags { copyVerbosity = v })
@@ -901,18 +877,12 @@
 emptyCopyFlags = mempty
 
 instance Monoid CopyFlags where
-  mempty = CopyFlags {
-    copyDest      = mempty,
-    copyDistPref  = mempty,
-    copyVerbosity = mempty
-  }
-  mappend a b = CopyFlags {
-    copyDest      = combine copyDest,
-    copyDistPref  = combine copyDistPref,
-    copyVerbosity = combine copyVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup CopyFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Install flags
 -- ------------------------------------------------------------
@@ -925,7 +895,7 @@
     installInPlace    :: Flag Bool,
     installVerbosity :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultInstallFlags :: InstallFlags
 defaultInstallFlags  = InstallFlags {
@@ -978,22 +948,12 @@
 emptyInstallFlags = mempty
 
 instance Monoid InstallFlags where
-  mempty = InstallFlags{
-    installPackageDB = mempty,
-    installDistPref  = mempty,
-    installUseWrapper = mempty,
-    installInPlace    = mempty,
-    installVerbosity = mempty
-  }
-  mappend a b = InstallFlags{
-    installPackageDB = combine installPackageDB,
-    installDistPref  = combine installDistPref,
-    installUseWrapper = combine installUseWrapper,
-    installInPlace    = combine installInPlace,
-    installVerbosity = combine installVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup InstallFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * SDist flags
 -- ------------------------------------------------------------
@@ -1006,7 +966,7 @@
     sDistListSources :: Flag FilePath,
     sDistVerbosity   :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultSDistFlags :: SDistFlags
 defaultSDistFlags = SDistFlags {
@@ -1055,22 +1015,12 @@
 emptySDistFlags = mempty
 
 instance Monoid SDistFlags where
-  mempty = SDistFlags {
-    sDistSnapshot    = mempty,
-    sDistDirectory   = mempty,
-    sDistDistPref    = mempty,
-    sDistListSources = mempty,
-    sDistVerbosity   = mempty
-  }
-  mappend a b = SDistFlags {
-    sDistSnapshot    = combine sDistSnapshot,
-    sDistDirectory   = combine sDistDirectory,
-    sDistDistPref    = combine sDistDistPref,
-    sDistListSources = combine sDistListSources,
-    sDistVerbosity   = combine sDistVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup SDistFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Register flags
 -- ------------------------------------------------------------
@@ -1086,7 +1036,7 @@
     regPrintId     :: Flag Bool,
     regVerbosity   :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultRegisterFlags :: RegisterFlags
 defaultRegisterFlags = RegisterFlags {
@@ -1178,26 +1128,12 @@
 emptyRegisterFlags = mempty
 
 instance Monoid RegisterFlags where
-  mempty = RegisterFlags {
-    regPackageDB   = mempty,
-    regGenScript   = mempty,
-    regGenPkgConf  = mempty,
-    regInPlace     = mempty,
-    regPrintId     = mempty,
-    regDistPref    = mempty,
-    regVerbosity   = mempty
-  }
-  mappend a b = RegisterFlags {
-    regPackageDB   = combine regPackageDB,
-    regGenScript   = combine regGenScript,
-    regGenPkgConf  = combine regGenPkgConf,
-    regInPlace     = combine regInPlace,
-    regPrintId     = combine regPrintId,
-    regDistPref    = combine regDistPref,
-    regVerbosity   = combine regVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup RegisterFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * HsColour flags
 -- ------------------------------------------------------------
@@ -1210,7 +1146,7 @@
     hscolourDistPref    :: Flag FilePath,
     hscolourVerbosity   :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 emptyHscolourFlags :: HscolourFlags
 emptyHscolourFlags = mempty
@@ -1226,24 +1162,12 @@
   }
 
 instance Monoid HscolourFlags where
-  mempty = HscolourFlags {
-    hscolourCSS         = mempty,
-    hscolourExecutables = mempty,
-    hscolourTestSuites  = mempty,
-    hscolourBenchmarks  = mempty,
-    hscolourDistPref    = mempty,
-    hscolourVerbosity   = mempty
-  }
-  mappend a b = HscolourFlags {
-    hscolourCSS         = combine hscolourCSS,
-    hscolourExecutables = combine hscolourExecutables,
-    hscolourTestSuites  = combine hscolourTestSuites,
-    hscolourBenchmarks  = combine hscolourBenchmarks,
-    hscolourDistPref    = combine hscolourDistPref,
-    hscolourVerbosity   = combine hscolourVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup HscolourFlags where
+  (<>) = gmappend
+
 hscolourCommand :: CommandUI HscolourFlags
 hscolourCommand = CommandUI
   { commandName         = "hscolour"
@@ -1303,6 +1227,7 @@
     haddockHoogle       :: Flag Bool,
     haddockHtml         :: Flag Bool,
     haddockHtmlLocation :: Flag String,
+    haddockForHackage   :: Flag Bool,
     haddockExecutables  :: Flag Bool,
     haddockTestSuites   :: Flag Bool,
     haddockBenchmarks   :: Flag Bool,
@@ -1315,7 +1240,7 @@
     haddockKeepTempFiles:: Flag Bool,
     haddockVerbosity    :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultHaddockFlags :: HaddockFlags
 defaultHaddockFlags  = HaddockFlags {
@@ -1324,6 +1249,7 @@
     haddockHoogle       = Flag False,
     haddockHtml         = Flag False,
     haddockHtmlLocation = NoFlag,
+    haddockForHackage   = Flag False,
     haddockExecutables  = Flag False,
     haddockTestSuites   = Flag False,
     haddockBenchmarks   = Flag False,
@@ -1389,6 +1315,11 @@
    haddockHtmlLocation (\v flags -> flags { haddockHtmlLocation = v })
    (reqArgFlag "URL")
 
+  ,option "" ["for-hackage"]
+   "Collection of flags to generate documentation suitable for upload to hackage"
+   haddockForHackage (\v flags -> flags { haddockForHackage = v })
+   trueArg
+
   ,option "" ["executables"]
    "Run haddock for Executables targets"
    haddockExecutables (\v flags -> flags { haddockExecutables = v })
@@ -1446,44 +1377,12 @@
 emptyHaddockFlags = mempty
 
 instance Monoid HaddockFlags where
-  mempty = HaddockFlags {
-    haddockProgramPaths = mempty,
-    haddockProgramArgs  = mempty,
-    haddockHoogle       = mempty,
-    haddockHtml         = mempty,
-    haddockHtmlLocation = mempty,
-    haddockExecutables  = mempty,
-    haddockTestSuites   = mempty,
-    haddockBenchmarks   = mempty,
-    haddockInternal     = mempty,
-    haddockCss          = mempty,
-    haddockHscolour     = mempty,
-    haddockHscolourCss  = mempty,
-    haddockContents     = mempty,
-    haddockDistPref     = mempty,
-    haddockKeepTempFiles= mempty,
-    haddockVerbosity    = mempty
-  }
-  mappend a b = HaddockFlags {
-    haddockProgramPaths = combine haddockProgramPaths,
-    haddockProgramArgs  = combine haddockProgramArgs,
-    haddockHoogle       = combine haddockHoogle,
-    haddockHtml         = combine haddockHtml,
-    haddockHtmlLocation = combine haddockHtmlLocation,
-    haddockExecutables  = combine haddockExecutables,
-    haddockTestSuites   = combine haddockTestSuites,
-    haddockBenchmarks   = combine haddockBenchmarks,
-    haddockInternal     = combine haddockInternal,
-    haddockCss          = combine haddockCss,
-    haddockHscolour     = combine haddockHscolour,
-    haddockHscolourCss  = combine haddockHscolourCss,
-    haddockContents     = combine haddockContents,
-    haddockDistPref     = combine haddockDistPref,
-    haddockKeepTempFiles= combine haddockKeepTempFiles,
-    haddockVerbosity    = combine haddockVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup HaddockFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Clean flags
 -- ------------------------------------------------------------
@@ -1493,7 +1392,7 @@
     cleanDistPref  :: Flag FilePath,
     cleanVerbosity :: Flag Verbosity
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultCleanFlags :: CleanFlags
 defaultCleanFlags  = CleanFlags {
@@ -1529,18 +1428,12 @@
 emptyCleanFlags = mempty
 
 instance Monoid CleanFlags where
-  mempty = CleanFlags {
-    cleanSaveConf  = mempty,
-    cleanDistPref  = mempty,
-    cleanVerbosity = mempty
-  }
-  mappend a b = CleanFlags {
-    cleanSaveConf  = combine cleanSaveConf,
-    cleanDistPref  = combine cleanDistPref,
-    cleanVerbosity = combine cleanVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup CleanFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Build flags
 -- ------------------------------------------------------------
@@ -1555,7 +1448,7 @@
     -- UserHooks stop us from passing extra info in other ways
     buildArgs :: [String]
   }
-  deriving Show
+  deriving (Show, Generic)
 
 {-# DEPRECATED buildVerbose "Use buildVerbosity instead" #-}
 buildVerbose :: BuildFlags -> Verbosity
@@ -1630,24 +1523,12 @@
 emptyBuildFlags = mempty
 
 instance Monoid BuildFlags where
-  mempty = BuildFlags {
-    buildProgramPaths = mempty,
-    buildProgramArgs = mempty,
-    buildVerbosity   = mempty,
-    buildDistPref    = mempty,
-    buildNumJobs     = mempty,
-    buildArgs        = mempty
-  }
-  mappend a b = BuildFlags {
-    buildProgramPaths = combine buildProgramPaths,
-    buildProgramArgs = combine buildProgramArgs,
-    buildVerbosity   = combine buildVerbosity,
-    buildDistPref    = combine buildDistPref,
-    buildNumJobs     = combine buildNumJobs,
-    buildArgs        = combine buildArgs
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup BuildFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * REPL Flags
 -- ------------------------------------------------------------
@@ -1659,7 +1540,7 @@
     replVerbosity   :: Flag Verbosity,
     replReload      :: Flag Bool
   }
-  deriving Show
+  deriving (Show, Generic)
 
 defaultReplFlags :: ReplFlags
 defaultReplFlags  = ReplFlags {
@@ -1671,22 +1552,12 @@
   }
 
 instance Monoid ReplFlags where
-  mempty = ReplFlags {
-    replProgramPaths = mempty,
-    replProgramArgs = mempty,
-    replVerbosity   = mempty,
-    replDistPref    = mempty,
-    replReload      = mempty
-  }
-  mappend a b = ReplFlags {
-    replProgramPaths = combine replProgramPaths,
-    replProgramArgs = combine replProgramArgs,
-    replVerbosity   = combine replVerbosity,
-    replDistPref    = combine replDistPref,
-    replReload      = combine replReload
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup ReplFlags where
+  (<>) = gmappend
+
 replCommand :: ProgramConfiguration -> CommandUI ReplFlags
 replCommand progConf = CommandUI
   { commandName         = "repl"
@@ -1759,7 +1630,7 @@
 -- * Test flags
 -- ------------------------------------------------------------
 
-data TestShowDetails = Never | Failures | Always | Streaming
+data TestShowDetails = Never | Failures | Always | Streaming | Direct
     deriving (Eq, Ord, Enum, Bounded, Show)
 
 knownTestShowDetails :: [TestShowDetails]
@@ -1779,8 +1650,11 @@
 --TODO: do we need this instance?
 instance Monoid TestShowDetails where
     mempty = Never
-    mappend a b = if a < b then b else a
+    mappend = (Semi.<>)
 
+instance Semigroup TestShowDetails where
+    a <> b = if a < b then b else a
+
 data TestFlags = TestFlags {
     testDistPref    :: Flag FilePath,
     testVerbosity   :: Flag Verbosity,
@@ -1790,7 +1664,7 @@
     testKeepTix     :: Flag Bool,
     -- TODO: think about if/how options are passed to test exes
     testOptions     :: [PathTemplate]
-  }
+  } deriving (Generic)
 
 defaultTestFlags :: TestFlags
 defaultTestFlags  = TestFlags {
@@ -1847,7 +1721,8 @@
             ("'always': always show results of individual test cases. "
              ++ "'never': never show results of individual test cases. "
              ++ "'failures': show results of failing test cases. "
-             ++ "'streaming': show results of test cases in real time.")
+             ++ "'streaming': show results of test cases in real time."
+             ++ "'direct': send results of test cases in real time; no log file.")
             testShowDetails (\v flags -> flags { testShowDetails = v })
             (reqArg "FILTER"
                 (readP_to_E (\_ -> "--show-details flag expects one of "
@@ -1881,26 +1756,12 @@
 emptyTestFlags  = mempty
 
 instance Monoid TestFlags where
-  mempty = TestFlags {
-    testDistPref    = mempty,
-    testVerbosity   = mempty,
-    testHumanLog    = mempty,
-    testMachineLog  = mempty,
-    testShowDetails = mempty,
-    testKeepTix     = mempty,
-    testOptions     = mempty
-  }
-  mappend a b = TestFlags {
-    testDistPref    = combine testDistPref,
-    testVerbosity   = combine testVerbosity,
-    testHumanLog    = combine testHumanLog,
-    testMachineLog  = combine testMachineLog,
-    testShowDetails = combine testShowDetails,
-    testKeepTix     = combine testKeepTix,
-    testOptions     = combine testOptions
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup TestFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Benchmark flags
 -- ------------------------------------------------------------
@@ -1909,7 +1770,7 @@
     benchmarkDistPref  :: Flag FilePath,
     benchmarkVerbosity :: Flag Verbosity,
     benchmarkOptions   :: [PathTemplate]
-  }
+  } deriving (Generic)
 
 defaultBenchmarkFlags :: BenchmarkFlags
 defaultBenchmarkFlags  = BenchmarkFlags {
@@ -1968,18 +1829,12 @@
 emptyBenchmarkFlags = mempty
 
 instance Monoid BenchmarkFlags where
-  mempty = BenchmarkFlags {
-    benchmarkDistPref  = mempty,
-    benchmarkVerbosity = mempty,
-    benchmarkOptions   = mempty
-  }
-  mappend a b = BenchmarkFlags {
-    benchmarkDistPref  = combine benchmarkDistPref,
-    benchmarkVerbosity = combine benchmarkVerbosity,
-    benchmarkOptions   = combine benchmarkOptions
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (Semi.<>)
 
+instance Semigroup BenchmarkFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Shared options utils
 -- ------------------------------------------------------------
@@ -2133,13 +1988,16 @@
         _        -> case reads s of
           [(n, "")]
             | n < 1     -> Left "The number of jobs should be 1 or more."
-            | n > 64    -> Left "You probably don't want that many jobs."
             | otherwise -> Right (Just n)
           _             -> Left "The jobs value should be a number or '$ncpus'"
 
 -- ------------------------------------------------------------
 -- * Other Utils
 -- ------------------------------------------------------------
+
+readPToMaybe :: Parse.ReadP a a -> String -> Maybe a
+readPToMaybe p str = listToMaybe [ r | (r,s) <- Parse.readP_to_S p str
+                                     , all isSpace s ]
 
 -- | Arguments to pass to a @configure@ script, e.g. generated by
 -- @autoconf@.
diff --git a/cabal/Cabal/Distribution/Simple/SrcDist.hs b/cabal/Cabal/Distribution/Simple/SrcDist.hs
--- a/cabal/Cabal/Distribution/Simple/SrcDist.hs
+++ b/cabal/Cabal/Distribution/Simple/SrcDist.hs
@@ -40,35 +40,20 @@
 
   )  where
 
-import Distribution.PackageDescription
-         ( PackageDescription(..), BuildInfo(..), Executable(..), Library(..)
-         , TestSuite(..), TestSuiteInterface(..), Benchmark(..)
-         , BenchmarkInterface(..) )
-import Distribution.PackageDescription.Check
-         ( PackageCheck(..), checkConfiguredPackage, checkPackageFiles )
+import Distribution.PackageDescription hiding (Flag)
+import Distribution.PackageDescription.Check hiding (doesFileExist)
 import Distribution.Package
-         ( PackageIdentifier(pkgVersion), Package(..), packageVersion )
-import Distribution.ModuleName (ModuleName)
+import Distribution.ModuleName
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.Version
-         ( Version(versionBranch) )
 import Distribution.Simple.Utils
-         ( createDirectoryIfMissingVerbose, withUTF8FileContents, writeUTF8File
-         , installOrdinaryFiles, installMaybeExecutableFiles
-         , findFile, findFileWithExtension, findAllFilesWithExtension, matchFileGlob
-         , withTempDirectory, defaultPackageDesc
-         , die, warn, notice, info, setupMessage )
-import Distribution.Simple.Setup ( Flag(..), SDistFlags(..)
-                                 , fromFlag, flagToMaybe)
-import Distribution.Simple.PreProcess ( PPSuffixHandler, ppSuffixes
-                                      , preprocessComponent )
+import Distribution.Simple.Setup
+import Distribution.Simple.PreProcess
 import Distribution.Simple.LocalBuildInfo
-         ( LocalBuildInfo(..), withAllComponentsInBuildOrder )
-import Distribution.Simple.BuildPaths ( autogenModuleName )
-import Distribution.Simple.Program ( defaultProgramConfiguration, requireProgram,
-                                     runProgram, programProperties, tarProgram )
+import Distribution.Simple.BuildPaths
+import Distribution.Simple.Program
 import Distribution.Text
-         ( display )
+import Distribution.Verbosity
 
 import Control.Monad(when, unless, forM)
 import Data.Char (toLower)
@@ -78,7 +63,6 @@
 import Data.Time (UTCTime, getCurrentTime, toGregorian, utctDay)
 import System.Directory ( doesFileExist )
 import System.IO (IOMode(WriteMode), hPutStrLn, withFile)
-import Distribution.Verbosity (Verbosity)
 import System.FilePath
          ( (</>), (<.>), dropExtension, isAbsolute )
 
@@ -169,19 +153,20 @@
   fmap concat . sequence $
   [
     -- Library sources.
-    withLib $ \Library { exposedModules = modules, libBuildInfo = libBi } ->
+    fmap concat
+    . withAllLib $ \Library { exposedModules = modules, libBuildInfo = libBi } ->
      allSourcesBuildInfo libBi pps modules
 
     -- Executables sources.
   , fmap concat
-    . withExe $ \Executable { modulePath = mainPath, buildInfo = exeBi } -> do
+    . withAllExe $ \Executable { modulePath = mainPath, buildInfo = exeBi } -> do
        biSrcs  <- allSourcesBuildInfo exeBi pps []
        mainSrc <- findMainExeFile exeBi pps mainPath
        return (mainSrc:biSrcs)
 
     -- Test suites sources.
   , fmap concat
-    . withTest $ \t -> do
+    . withAllTest $ \t -> do
        let bi  = testBuildInfo t
        case testInterface t of
          TestSuiteExeV10 _ mainPath -> do
@@ -200,7 +185,7 @@
 
     -- Benchmarks sources.
   , fmap concat
-    . withBenchmark $ \bm -> do
+    . withAllBenchmark $ \bm -> do
        let  bi = benchmarkBuildInfo bm
        case benchmarkInterface bm of
          BenchmarkExeV10 _ mainPath -> do
@@ -229,7 +214,8 @@
   , return (licenseFiles pkg_descr)
 
     -- Install-include files.
-  , withLib $ \ l -> do
+  , fmap concat
+    . withAllLib $ \ l -> do
        let lbi = libBuildInfo l
            relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)
        mapM (fmap snd . findIncludeFile relincdirs) (installIncludes lbi)
@@ -244,10 +230,10 @@
   where
     -- We have to deal with all libs and executables, so we have local
     -- versions of these functions that ignore the 'buildable' attribute:
-    withLib       action = maybe (return []) action (library pkg_descr)
-    withExe       action = mapM action (executables pkg_descr)
-    withTest      action = mapM action (testSuites pkg_descr)
-    withBenchmark action = mapM action (benchmarks pkg_descr)
+    withAllLib       action = mapM action (libraries pkg_descr)
+    withAllExe       action = mapM action (executables pkg_descr)
+    withAllTest      action = mapM action (testSuites pkg_descr)
+    withAllBenchmark action = mapM action (benchmarks pkg_descr)
 
 
 -- |Prepare a directory tree of source files.
@@ -263,8 +249,8 @@
   case mb_lbi of
     Just lbi | not (null pps) -> do
       let lbi' = lbi{ buildDir = targetDir </> buildDir lbi }
-      withAllComponentsInBuildOrder pkg_descr lbi' $ \c _ ->
-        preprocessComponent pkg_descr c lbi' True verbosity pps
+      withAllComponentsInBuildOrder pkg_descr lbi' $ \c clbi ->
+        preprocessComponent pkg_descr c lbi' clbi True verbosity pps
     _ -> return ()
 
   (ordinary, mExecutable)  <- listPackageSources verbosity pkg_descr0 pps
@@ -325,7 +311,7 @@
 filterAutogenModule pkg_descr0 = mapLib filterAutogenModuleLib $
                                  mapAllBuildInfo filterAutogenModuleBI pkg_descr0
   where
-    mapLib f pkg = pkg { library = fmap f (library pkg) }
+    mapLib f pkg = pkg { libraries = map f (libraries pkg) }
     filterAutogenModuleLib lib = lib {
       exposedModules = filter (/=autogenModule) (exposedModules lib)
     }
@@ -481,7 +467,7 @@
 mapAllBuildInfo :: (BuildInfo -> BuildInfo)
                 -> (PackageDescription -> PackageDescription)
 mapAllBuildInfo f pkg = pkg {
-    library     = fmap mapLibBi (library pkg),
+    libraries   = fmap mapLibBi (libraries pkg),
     executables = fmap mapExeBi (executables pkg),
     testSuites  = fmap mapTestBi (testSuites pkg),
     benchmarks  = fmap mapBenchBi (benchmarks pkg)
diff --git a/cabal/Cabal/Distribution/Simple/Test.hs b/cabal/Cabal/Distribution/Simple/Test.hs
--- a/cabal/Cabal/Distribution/Simple/Test.hs
+++ b/cabal/Cabal/Distribution/Simple/Test.hs
@@ -16,23 +16,17 @@
     ) where
 
 import qualified Distribution.PackageDescription as PD
-         ( PackageDescription(..), BuildInfo(buildable)
-         , TestSuite(..)
-         , TestSuiteInterface(..), testType, hasTests )
-import Distribution.Simple.Compiler ( compilerInfo )
-import Distribution.Simple.Hpc ( markupPackage )
+import Distribution.Simple.Compiler
+import Distribution.Simple.Hpc
 import Distribution.Simple.InstallDirs
-    ( fromPathTemplate, initialPathTemplateEnv, substPathTemplate
-    , PathTemplate )
 import qualified Distribution.Simple.LocalBuildInfo as LBI
-    ( LocalBuildInfo(..), localLibraryName )
-import Distribution.Simple.Setup ( TestFlags(..), fromFlag, configCoverage )
-import Distribution.Simple.UserHooks ( Args )
+import Distribution.Simple.Setup
+import Distribution.Simple.UserHooks
 import qualified Distribution.Simple.Test.ExeV10 as ExeV10
 import qualified Distribution.Simple.Test.LibV09 as LibV09
 import Distribution.Simple.Test.Log
-import Distribution.Simple.Utils ( die, notice )
-import Distribution.TestSuite ( Result(..) )
+import Distribution.Simple.Utils
+import Distribution.TestSuite
 import Distribution.Text
 
 import Control.Monad ( when, unless, filterM )
@@ -132,5 +126,5 @@
     fromPathTemplate $ substPathTemplate env template
     where
         env = initialPathTemplateEnv
-                (PD.package pkg_descr) (LBI.localLibraryName lbi)
+                (PD.package pkg_descr) (LBI.localUnitId lbi)
                 (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi)
diff --git a/cabal/Cabal/Distribution/Simple/Test/ExeV10.hs b/cabal/Cabal/Distribution/Simple/Test/ExeV10.hs
--- a/cabal/Cabal/Distribution/Simple/Test/ExeV10.hs
+++ b/cabal/Cabal/Distribution/Simple/Test/ExeV10.hs
@@ -2,26 +2,22 @@
        ( runTest
        ) where
 
-import Distribution.Compat.CreatePipe ( createPipe )
-import Distribution.Compat.Environment ( getEnvironment )
+import Distribution.Compat.CreatePipe
+import Distribution.Compat.Environment
 import qualified Distribution.PackageDescription as PD
-import Distribution.Simple.Build.PathsModule ( pkgPathEnvVar )
-import Distribution.Simple.BuildPaths ( exeExtension )
-import Distribution.Simple.Compiler ( compilerInfo )
-import Distribution.Simple.Hpc ( guessWay, markupTest, tixDir, tixFilePath )
+import Distribution.Simple.Build.PathsModule
+import Distribution.Simple.BuildPaths
+import Distribution.Simple.Compiler
+import Distribution.Simple.Hpc
 import Distribution.Simple.InstallDirs
-    ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)
-    , substPathTemplate , toPathTemplate, PathTemplate )
 import qualified Distribution.Simple.LocalBuildInfo as LBI
 import Distribution.Simple.Setup
-    ( TestFlags(..), TestShowDetails(..), fromFlag, configCoverage )
 import Distribution.Simple.Test.Log
 import Distribution.Simple.Utils
-    ( die, notice, rawSystemIOWithEnv, addLibraryPath )
-import Distribution.System ( Platform (..) )
+import Distribution.System
 import Distribution.TestSuite
 import Distribution.Text
-import Distribution.Verbosity ( normal )
+import Distribution.Verbosity
 
 import Control.Concurrent (forkIO)
 import Control.Monad ( unless, void, when )
@@ -30,7 +26,7 @@
     , getCurrentDirectory, removeDirectoryRecursive )
 import System.Exit ( ExitCode(..) )
 import System.FilePath ( (</>), (<.>) )
-import System.IO ( hGetContents, hPutStr, stdout )
+import System.IO ( hGetContents, hPutStr, stdout, stderr )
 
 runTest :: PD.PackageDescription
         -> LBI.LocalBuildInfo
@@ -63,16 +59,21 @@
     -- Write summary notices indicating start of test suite
     notice verbosity $ summarizeSuiteStart $ PD.testName suite
 
-    (rOut, wOut) <- createPipe
+    (wOut, wErr, logText) <- case details of
+        Direct -> return (stdout, stderr, "")
+        _ -> do
+            (rOut, wOut) <- createPipe
 
-    -- Read test executable's output lazily (returns immediately)
-    logText <- hGetContents rOut
-    -- Force the IO manager to drain the test output pipe
-    void $ forkIO $ length logText `seq` return ()
+            -- Read test executable's output lazily (returns immediately)
+            logText <- hGetContents rOut
+            -- Force the IO manager to drain the test output pipe
+            void $ forkIO $ length logText `seq` return ()
 
-    -- '--show-details=streaming': print the log output in another thread
-    when (details == Streaming) $ void $ forkIO $ hPutStr stdout logText
+            -- '--show-details=streaming': print the log output in another thread
+            when (details == Streaming) $ void $ forkIO $ hPutStr stdout logText
 
+            return (wOut, wOut, logText)
+
     -- Run the test executable
     let opts = map (testOption pkg_descr lbi suite)
                    (testOptions flags)
@@ -93,7 +94,7 @@
 
     exit <- rawSystemIOWithEnv verbosity cmd opts Nothing (Just shellEnv')
                                -- these handles are automatically closed
-                               Nothing (Just wOut) (Just wOut)
+                               Nothing (Just wOut) (Just wErr)
 
     -- Generate TestSuiteLog from executable exit code and a machine-
     -- readable test log.
@@ -112,12 +113,10 @@
     -- Show the contents of the human-readable log file on the terminal
     -- if there is a failure and/or detailed output is requested
     let whenPrinting = when $
-            (details > Never)
-            && (not (suitePassed $ testLogs suiteLog) || details == Always)
+            ( details == Always ||
+              details == Failures && not (suitePassed $ testLogs suiteLog))
             -- verbosity overrides show-details
             && verbosity >= normal
-            -- if streaming, we already printed the log
-            && details /= Streaming
     whenPrinting $ putStr $ unlines $ lines logText
 
     -- Write summary notice to terminal indicating end of test suite
@@ -163,6 +162,6 @@
     fromPathTemplate $ substPathTemplate env template
   where
     env = initialPathTemplateEnv
-          (PD.package pkg_descr) (LBI.localLibraryName lbi)
+          (PD.package pkg_descr) (LBI.localUnitId lbi)
           (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++
           [(TestSuiteNameVar, toPathTemplate $ PD.testName suite)]
diff --git a/cabal/Cabal/Distribution/Simple/Test/LibV09.hs b/cabal/Cabal/Distribution/Simple/Test/LibV09.hs
--- a/cabal/Cabal/Distribution/Simple/Test/LibV09.hs
+++ b/cabal/Cabal/Distribution/Simple/Test/LibV09.hs
@@ -6,32 +6,27 @@
        , writeSimpleTestStub
        ) where
 
-import Distribution.Compat.CreatePipe ( createPipe )
-import Distribution.Compat.Environment ( getEnvironment )
-import Distribution.Compat.TempFile ( openTempFile )
-import Distribution.ModuleName ( ModuleName )
+import Distribution.Compat.CreatePipe
+import Distribution.Compat.Environment
+import Distribution.Compat.Internal.TempFile
+import Distribution.ModuleName
 import qualified Distribution.PackageDescription as PD
-import Distribution.Simple.Build.PathsModule ( pkgPathEnvVar )
-import Distribution.Simple.BuildPaths ( exeExtension )
-import Distribution.Simple.Compiler ( compilerInfo )
-import Distribution.Simple.Hpc ( guessWay, markupTest, tixDir, tixFilePath )
+import Distribution.Simple.Build.PathsModule
+import Distribution.Simple.BuildPaths
+import Distribution.Simple.Compiler
+import Distribution.Simple.Hpc
 import Distribution.Simple.InstallDirs
-    ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)
-    , substPathTemplate , toPathTemplate, PathTemplate )
 import qualified Distribution.Simple.LocalBuildInfo as LBI
 import Distribution.Simple.Setup
-    ( TestFlags(..), TestShowDetails(..), fromFlag, configCoverage )
 import Distribution.Simple.Test.Log
 import Distribution.Simple.Utils
-    ( die, notice, rawSystemIOWithEnv, addLibraryPath )
-import Distribution.System ( Platform (..) )
+import Distribution.System
 import Distribution.TestSuite
 import Distribution.Text
-import Distribution.Verbosity ( normal )
+import Distribution.Verbosity
 
-import Control.Concurrent (forkIO)
 import Control.Exception ( bracket )
-import Control.Monad ( when, unless, void )
+import Control.Monad ( when, unless )
 import Data.Maybe ( mapMaybe )
 import System.Directory
     ( createDirectoryIfMissing, doesDirectoryExist, doesFileExist
@@ -40,6 +35,7 @@
 import System.Exit ( ExitCode(..), exitWith )
 import System.FilePath ( (</>), (<.>) )
 import System.IO ( hClose, hGetContents, hPutStr )
+import System.Process (StdStream(..), waitForProcess)
 
 runTest :: PD.PackageDescription
         -> LBI.LocalBuildInfo
@@ -74,22 +70,11 @@
 
     suiteLog <- bracket openCabalTemp deleteIfExists $ \tempLog -> do
 
-        (rIn, wIn) <- createPipe
         (rOut, wOut) <- createPipe
 
-        -- Prepare standard input for test executable
-        --appendFile tempInput $ show (tempInput, PD.testName suite)
-        hPutStr wIn $ show (tempLog, PD.testName suite)
-        hClose wIn
-
-        -- Append contents of temporary log file to the final human-
-        -- readable log file
-        logText <- hGetContents rOut
-        -- Force the IO manager to drain the test output pipe
-        void $ forkIO $ length logText `seq` return ()
-
         -- Run test executable
-        _ <- do let opts = map (testOption pkg_descr lbi suite) $ testOptions flags
+        (Just wIn, _, _, process) <- do
+                let opts = map (testOption pkg_descr lbi suite) $ testOptions flags
                     dataDirPath = pwd </> PD.dataDir pkg_descr
                     tixFile = pwd </> tixFilePath distPref way (PD.testName suite)
                     pkgPathEnv = (pkgPathEnvVar pkg_descr "datadir", dataDirPath)
@@ -108,10 +93,23 @@
                                              True False lbi clbi
                                   return (addLibraryPath os paths shellEnv)
                                 else return shellEnv
-                rawSystemIOWithEnv verbosity cmd opts Nothing (Just shellEnv')
-                                   -- these handles are closed automatically
-                                   (Just rIn) (Just wOut) (Just wOut)
+                createProcessWithEnv verbosity cmd opts Nothing (Just shellEnv')
+                                     -- these handles are closed automatically
+                                     CreatePipe (UseHandle wOut) (UseHandle wOut)
 
+        hPutStr wIn $ show (tempLog, PD.testName suite)
+        hClose wIn
+
+        -- Append contents of temporary log file to the final human-
+        -- readable log file
+        logText <- hGetContents rOut
+        -- Force the IO manager to drain the test output pipe
+        length logText `seq` return ()
+
+        exitcode <- waitForProcess process
+        unless (exitcode == ExitSuccess) $ do
+            debug verbosity $ cmd ++ " returned " ++ show exitcode
+
         -- Generate final log file name
         let finalLogName l = testLogDir
                              </> testSuiteLogPath
@@ -171,7 +169,7 @@
     fromPathTemplate $ substPathTemplate env template
   where
     env = initialPathTemplateEnv
-          (PD.package pkg_descr) (LBI.localLibraryName lbi)
+          (PD.package pkg_descr) (LBI.localUnitId lbi)
           (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++
           [(TestSuiteNameVar, toPathTemplate $ PD.testName suite)]
 
diff --git a/cabal/Cabal/Distribution/Simple/Test/Log.hs b/cabal/Cabal/Distribution/Simple/Test/Log.hs
--- a/cabal/Cabal/Distribution/Simple/Test/Log.hs
+++ b/cabal/Cabal/Distribution/Simple/Test/Log.hs
@@ -11,18 +11,16 @@
        , testSuiteLogPath
        ) where
 
-import Distribution.Package ( PackageId )
+import Distribution.Package
 import qualified Distribution.PackageDescription as PD
-import Distribution.Simple.Compiler ( Compiler(..), compilerInfo, CompilerId )
+import Distribution.Simple.Compiler
 import Distribution.Simple.InstallDirs
-    ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)
-    , substPathTemplate , toPathTemplate, PathTemplate )
 import qualified Distribution.Simple.LocalBuildInfo as LBI
-import Distribution.Simple.Setup ( TestShowDetails(..) )
-import Distribution.Simple.Utils ( notice )
-import Distribution.System ( Platform )
-import Distribution.TestSuite ( Options, Result(..) )
-import Distribution.Verbosity ( Verbosity )
+import Distribution.Simple.Setup
+import Distribution.Simple.Utils
+import Distribution.System
+import Distribution.TestSuite
+import Distribution.Verbosity
 
 import Control.Monad ( when )
 import Data.Char ( toUpper )
@@ -109,13 +107,13 @@
                  -> String -- ^ test suite name
                  -> TestLogs -- ^ test suite results
                  -> FilePath
-testSuiteLogPath template pkg_descr lbi name result =
+testSuiteLogPath template pkg_descr lbi test_name result =
     fromPathTemplate $ substPathTemplate env template
     where
         env = initialPathTemplateEnv
-                (PD.package pkg_descr) (LBI.localLibraryName lbi)
+                (PD.package pkg_descr) (LBI.localUnitId lbi)
                 (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi)
-                ++  [ (TestSuiteNameVar, toPathTemplate name)
+                ++  [ (TestSuiteNameVar, toPathTemplate test_name)
                     , (TestSuiteResultVar, toPathTemplate $ resultString result)
                     ]
 
diff --git a/cabal/Cabal/Distribution/Simple/UHC.hs b/cabal/Cabal/Distribution/Simple/UHC.hs
--- a/cabal/Cabal/Distribution/Simple/UHC.hs
+++ b/cabal/Cabal/Distribution/Simple/UHC.hs
@@ -16,15 +16,12 @@
 
 module Distribution.Simple.UHC (
     configure, getInstalledPackages,
-    buildLib, buildExe, installLib, registerPackage
+    buildLib, buildExe, installLib, registerPackage, inplacePackageDbPath
   ) where
 
-import Control.Monad
-import Data.List
-import qualified Data.Map as M ( empty )
 import Distribution.Compat.ReadP
 import Distribution.InstalledPackageInfo
-import Distribution.Package hiding (installedPackageId)
+import Distribution.Package hiding (installedUnitId)
 import Distribution.PackageDescription
 import Distribution.Simple.BuildPaths
 import Distribution.Simple.Compiler as C
@@ -35,10 +32,14 @@
 import Distribution.Text
 import Distribution.Verbosity
 import Distribution.Version
+import Distribution.System
 import Language.Haskell.Extension
+
+import Control.Monad
+import Data.List
+import qualified Data.Map as M ( empty )
 import System.Directory
 import System.FilePath
-import Distribution.System ( Platform )
 
 -- -----------------------------------------------------------------------------
 -- Configuring
@@ -91,14 +92,13 @@
                      -> IO InstalledPackageIndex
 getInstalledPackages verbosity comp packagedbs conf = do
   let compilerid = compilerId comp
-  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram conf ["--meta-pkgdir-system"]
+  systemPkgDir <- getGlobalPackageDir verbosity conf
   userPkgDir   <- getUserPackageDir
   let pkgDirs    = nub (concatMap (packageDbPaths userPkgDir systemPkgDir) packagedbs)
   -- putStrLn $ "pkgdirs: " ++ show pkgDirs
-  -- call to "lines" necessary, because pkgdir contains an extra newline at the end
-  pkgs <- liftM (map addBuiltinVersions . concat) .
-          mapM (\ d -> getDirectoryContents d >>= filterM (isPkgDir (display compilerid) d)) .
-          concatMap lines $ pkgDirs
+  pkgs <- liftM (map addBuiltinVersions . concat) $
+          mapM (\ d -> getDirectoryContents d >>= filterM (isPkgDir (display compilerid) d))
+          pkgDirs
   -- putStrLn $ "pkgs: " ++ show pkgs
   let iPkgs =
         map mkInstalledPackageInfo $
@@ -107,9 +107,16 @@
   -- putStrLn $ "installed pkgs: " ++ show iPkgs
   return (fromList iPkgs)
 
+getGlobalPackageDir :: Verbosity -> ProgramConfiguration -> IO FilePath
+getGlobalPackageDir verbosity conf = do
+    output <- rawSystemProgramStdoutConf verbosity
+                uhcProgram conf ["--meta-pkgdir-system"]
+    -- call to "lines" necessary, because pkgdir contains an extra newline at the end
+    let [pkgdir] = lines output
+    return pkgdir
+
 getUserPackageDir :: IO FilePath
-getUserPackageDir =
-  do
+getUserPackageDir = do
     homeDir <- getHomeDirectory
     return $ homeDir </> ".cabal" </> "lib"  -- TODO: determine in some other way
 
@@ -150,8 +157,8 @@
 -- | Create a trivial package info from a directory name.
 mkInstalledPackageInfo :: PackageId -> InstalledPackageInfo
 mkInstalledPackageInfo p = emptyInstalledPackageInfo
-  { installedPackageId = InstalledPackageId (display p),
-    sourcePackageId    = p }
+  { installedUnitId = mkLegacyUnitId p,
+    sourcePackageId = p }
 
 
 -- -----------------------------------------------------------------------------
@@ -161,7 +168,7 @@
                       -> Library            -> ComponentLocalBuildInfo -> IO ()
 buildLib verbosity pkg_descr lbi lib clbi = do
 
-  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]
+  systemPkgDir <- getGlobalPackageDir verbosity (withPrograms lbi)
   userPkgDir   <- getUserPackageDir
   let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi)
   let uhcArgs =    -- set package name
@@ -183,7 +190,7 @@
 buildExe :: Verbosity -> PackageDescription -> LocalBuildInfo
                       -> Executable         -> ComponentLocalBuildInfo -> IO ()
 buildExe verbosity _pkg_descr lbi exe clbi = do
-  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]
+  systemPkgDir <- getGlobalPackageDir verbosity (withPrograms lbi)
   userPkgDir   <- getUserPackageDir
   let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi)
   let uhcArgs =    -- common flags lib/exe
@@ -216,7 +223,7 @@
      -- search paths
   ++ ["-i" ++ odir]
   ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)]
-  ++ ["-i" ++ autogenModulesDir lbi]
+  ++ ["-i" ++ autogenModulesDir lbi clbi]
      -- cpp options
   ++ ["--optP=" ++ opt | opt <- cppOptions bi]
      -- output path
@@ -236,8 +243,8 @@
 
 installLib :: Verbosity -> LocalBuildInfo
            -> FilePath -> FilePath -> FilePath
-           -> PackageDescription -> Library -> IO ()
-installLib verbosity _lbi targetDir _dynlibTargetDir builtDir pkg _library = do
+           -> PackageDescription -> Library -> ComponentLocalBuildInfo -> IO ()
+installLib verbosity _lbi targetDir _dynlibTargetDir builtDir pkg _library _clbi = do
     -- putStrLn $ "dest:  " ++ targetDir
     -- putStrLn $ "built: " ++ builtDir
     installDirectoryContents verbosity (builtDir </> display (packageId pkg)) targetDir
@@ -258,19 +265,23 @@
 
 registerPackage
   :: Verbosity
-  -> InstalledPackageInfo
-  -> PackageDescription
-  -> LocalBuildInfo
-  -> Bool
+  -> Compiler
+  -> ProgramConfiguration
   -> PackageDBStack
+  -> InstalledPackageInfo
   -> IO ()
-registerPackage verbosity installedPkgInfo pkg lbi inplace _packageDbs = do
-    let installDirs = absoluteInstallDirs pkg lbi NoCopyDest
-        pkgdir  | inplace   = buildDir lbi       </> uhcPackageDir    (display pkgid) (display compilerid)
-                | otherwise = libdir installDirs </> uhcPackageSubDir                 (display compilerid)
+registerPackage verbosity comp progdb packageDbs installedPkgInfo = do
+    dbdir <- case last packageDbs of
+      GlobalPackageDB       -> getGlobalPackageDir verbosity progdb
+      UserPackageDB         -> getUserPackageDir
+      SpecificPackageDB dir -> return dir
+    let pkgdir = dbdir </> uhcPackageDir (display pkgid) (display compilerid)
     createDirectoryIfMissingVerbose verbosity True pkgdir
     writeUTF8File (pkgdir </> installedPkgConfig)
                   (showInstalledPackageInfo installedPkgInfo)
   where
-    pkgid      = packageId pkg
-    compilerid = compilerId (compiler lbi)
+    pkgid      = sourcePackageId installedPkgInfo
+    compilerid = compilerId comp
+
+inplacePackageDbPath :: LocalBuildInfo -> FilePath
+inplacePackageDbPath lbi = buildDir lbi
diff --git a/cabal/Cabal/Distribution/Simple/UserHooks.hs b/cabal/Cabal/Distribution/Simple/UserHooks.hs
--- a/cabal/Cabal/Distribution/Simple/UserHooks.hs
+++ b/cabal/Cabal/Distribution/Simple/UserHooks.hs
@@ -29,16 +29,11 @@
   ) where
 
 import Distribution.PackageDescription
-         (PackageDescription, GenericPackageDescription,
-          HookedBuildInfo, emptyHookedBuildInfo)
-import Distribution.Simple.Program    (Program)
-import Distribution.Simple.Command    (noExtraFlags)
-import Distribution.Simple.PreProcess (PPSuffixHandler)
+import Distribution.Simple.Program
+import Distribution.Simple.Command
+import Distribution.Simple.PreProcess
 import Distribution.Simple.Setup
-         (ConfigFlags, BuildFlags, ReplFlags, CleanFlags, CopyFlags,
-          InstallFlags, SDistFlags, RegisterFlags, HscolourFlags,
-          HaddockFlags, TestFlags, BenchmarkFlags)
-import Distribution.Simple.LocalBuildInfo (LocalBuildInfo)
+import Distribution.Simple.LocalBuildInfo
 
 type Args = [String]
 
@@ -178,7 +173,7 @@
       preClean  = rn,
       cleanHook = ru,
       postClean = ru,
-      preCopy   = rn,
+      preCopy   = rn',
       copyHook  = ru,
       postCopy  = ru,
       preInst   = rn,
diff --git a/cabal/Cabal/Distribution/Simple/Utils.hs b/cabal/Cabal/Distribution/Simple/Utils.hs
--- a/cabal/Cabal/Distribution/Simple/Utils.hs
+++ b/cabal/Cabal/Distribution/Simple/Utils.hs
@@ -26,6 +26,9 @@
         debugNoWrap, chattyTry,
         printRawCommandAndArgs, printRawCommandAndArgsAndEnv,
 
+        -- * exceptions
+        handleDoesNotExist,
+
         -- * running programs
         rawSystemExit,
         rawSystemExitCode,
@@ -33,6 +36,7 @@
         rawSystemStdout,
         rawSystemStdInOut,
         rawSystemIOWithEnv,
+        createProcessWithEnv,
         maybeExit,
         xargs,
         findProgramLocation,
@@ -64,6 +68,8 @@
         -- * file names
         currentDir,
         shortRelativePath,
+        dropExeExtension,
+        exeExtensions,
 
         -- * finding files
         findFile,
@@ -131,12 +137,37 @@
         listUnionRight,
         ordNub,
         ordNubRight,
+        safeTail,
         wrapText,
         wrapLine,
   ) where
 
+import Distribution.Text
+import Distribution.Package
+import Distribution.ModuleName as ModuleName
+import Distribution.System
+import Distribution.Version
+import Distribution.Compat.CopyFile
+import Distribution.Compat.Internal.TempFile
+import Distribution.Compat.Exception
+import Distribution.Verbosity
+
+#if __GLASGOW_HASKELL__ < 711
+#ifdef VERSION_base
+#define BOOTSTRAPPED_CABAL 1
+#endif
+#else
+#ifdef CURRENT_PACKAGE_KEY
+#define BOOTSTRAPPED_CABAL 1
+#endif
+#endif
+
+#ifdef BOOTSTRAPPED_CABAL
+import qualified Paths_Cabal (version)
+#endif
+
 import Control.Monad
-    ( join, when, unless, filterM )
+    ( when, unless, filterM )
 import Control.Concurrent.MVar
     ( newEmptyMVar, putMVar, takeMVar )
 import Data.Bits
@@ -146,9 +177,11 @@
 import Data.Foldable
     ( traverse_ )
 import Data.List
-    ( nub, unfoldr, isPrefixOf, tails, intercalate )
+    ( nub, unfoldr, intercalate, isInfixOf )
 import Data.Typeable
     ( cast )
+import Data.Ord
+    ( comparing )
 import qualified Data.ByteString.Lazy as BS
 import qualified Data.ByteString.Lazy.Char8 as BS.Char8
 import qualified Data.Set as Set
@@ -181,40 +214,17 @@
     ( unsafeInterleaveIO )
 import qualified Control.Exception as Exception
 
-import Distribution.Text
-    ( display, simpleParse )
-import Distribution.Package
-    ( PackageIdentifier )
-import Distribution.ModuleName (ModuleName)
-import qualified Distribution.ModuleName as ModuleName
-import Distribution.System
-    ( OS (..) )
-import Distribution.Version
-    (Version(..))
-
 import Control.Exception (IOException, evaluate, throwIO)
 import Control.Concurrent (forkIO)
 import qualified System.Process as Process
          ( CreateProcess(..), StdStream(..), proc)
 import System.Process
-         ( createProcess, rawSystem, runInteractiveProcess
+         ( ProcessHandle, createProcess, rawSystem, runInteractiveProcess
          , showCommandForUser, waitForProcess)
-import Distribution.Compat.CopyFile
-         ( copyFile, copyOrdinaryFile, copyExecutableFile
-         , setFileOrdinary, setFileExecutable, setDirOrdinary )
-import Distribution.Compat.TempFile
-         ( openTempFile, createTempDirectory )
-import Distribution.Compat.Exception
-         ( tryIO, catchIO, catchExit )
-import Distribution.Verbosity
 
-#ifdef VERSION_base
-import qualified Paths_Cabal (version)
-#endif
-
 -- We only get our own version number when we're building with ourselves
 cabalVersion :: Version
-#if defined(VERSION_base)
+#if defined(BOOTSTRAPPED_CABAL)
 cabalVersion = Paths_Cabal.version
 #elif defined(CABAL_VERSION)
 cabalVersion = Version [CABAL_VERSION] []
@@ -258,7 +268,7 @@
     handle se = do
       hFlush stdout
       pname <- getProgName
-      hPutStr stderr (message pname se)
+      hPutStr stderr (wrapText (message pname se))
       cont se
 
     message :: String -> Exception.SomeException -> String
@@ -272,7 +282,7 @@
                                l@(n:_) | Char.isDigit n -> ':' : l
                                _                        -> ""
               detail       = ioeGetErrorString ioe
-          in wrapText (pname ++ ": " ++ file ++ detail)
+          in pname ++ ": " ++ file ++ detail
         Nothing ->
 #if __GLASGOW_HASKELL__ < 710
           show se
@@ -345,6 +355,14 @@
   catchIO action $ \exception ->
     putStrLn $ "Error while " ++ desc ++ ": " ++ show exception
 
+-- | Run an IO computation, returning @e@ if it raises a "file
+-- does not exist" error.
+handleDoesNotExist :: a -> IO a -> IO a
+handleDoesNotExist e =
+    Exception.handleJust
+      (\ioe -> if isDoesNotExistError ioe then Just ioe else Nothing)
+      (\_ -> return e)
+
 -- -----------------------------------------------------------------------------
 -- Helper functions
 
@@ -450,29 +468,47 @@
                    -> Maybe Handle  -- ^ stderr
                    -> IO ExitCode
 rawSystemIOWithEnv verbosity path args mcwd menv inp out err = do
+    (_,_,_,ph) <- createProcessWithEnv verbosity path args mcwd menv
+                                       (mbToStd inp) (mbToStd out) (mbToStd err)
+    exitcode <- waitForProcess ph
+    unless (exitcode == ExitSuccess) $ do
+      debug verbosity $ path ++ " returned " ++ show exitcode
+    return exitcode
+  where
+    mbToStd :: Maybe Handle -> Process.StdStream
+    mbToStd = maybe Process.Inherit Process.UseHandle
+
+createProcessWithEnv ::
+  Verbosity
+  -> FilePath
+  -> [String]
+  -> Maybe FilePath           -- ^ New working dir or inherit
+  -> Maybe [(String, String)] -- ^ New environment or inherit
+  -> Process.StdStream  -- ^ stdin
+  -> Process.StdStream  -- ^ stdout
+  -> Process.StdStream  -- ^ stderr
+  -> IO (Maybe Handle, Maybe Handle, Maybe Handle,ProcessHandle)
+  -- ^ Any handles created for stdin, stdout, or stderr
+  -- with 'CreateProcess', and a handle to the process.
+createProcessWithEnv verbosity path args mcwd menv inp out err = do
     printRawCommandAndArgsAndEnv verbosity path args menv
     hFlush stdout
-    (_,_,_,ph) <- createProcess $
-                  (Process.proc path args) { Process.cwd           = mcwd
-                                           , Process.env           = menv
-                                           , Process.std_in        = mbToStd inp
-                                           , Process.std_out       = mbToStd out
-                                           , Process.std_err       = mbToStd err
+    (inp', out', err', ph) <- createProcess $
+                                (Process.proc path args) {
+                                    Process.cwd           = mcwd
+                                  , Process.env           = menv
+                                  , Process.std_in        = inp
+                                  , Process.std_out       = out
+                                  , Process.std_err       = err
 #ifdef MIN_VERSION_process
 #if MIN_VERSION_process(1,2,0)
 -- delegate_ctlc has been added in process 1.2, and we still want to be able to
 -- bootstrap GHC on systems not having that version
-                                           , Process.delegate_ctlc = True
+                                  , Process.delegate_ctlc = True
 #endif
 #endif
-                                           }
-    exitcode <- waitForProcess ph
-    unless (exitcode == ExitSuccess) $ do
-      debug verbosity $ path ++ " returned " ++ show exitcode
-    return exitcode
-  where
-    mbToStd :: Maybe Handle -> Process.StdStream
-    mbToStd = maybe Process.Inherit Process.UseHandle
+                                  }
+    return (inp', out', err', ph)
 
 -- | Run a command and return its output.
 --
@@ -554,6 +590,8 @@
       return (out, err, exitcode)
 
 
+{-# DEPRECATED findProgramLocation
+    "No longer used within Cabal, try findProgramOnSearchPath" #-}
 -- | Look for a program on the path.
 findProgramLocation :: Verbosity -> FilePath -> IO (Maybe FilePath)
 findProgramLocation verbosity prog = do
@@ -699,12 +737,12 @@
                -> [String]    -- ^ search suffixes
                -> ModuleName  -- ^ module
                -> IO (FilePath, FilePath)
-findModuleFile searchPath extensions moduleName =
+findModuleFile searchPath extensions mod_name =
       maybe notFound return
   =<< findFileWithExtension' extensions searchPath
-                             (ModuleName.toFilePath moduleName)
+                             (ModuleName.toFilePath mod_name)
   where
-    notFound = die $ "Error: Could not find module: " ++ display moduleName
+    notFound = die $ "Error: Could not find module: " ++ display mod_name
                   ++ " with any suffix: " ++ show extensions
                   ++ " in the search path: " ++ show searchPath
 
@@ -998,7 +1036,8 @@
 copyDirectoryRecursive verbosity srcDir destDir = do
   info verbosity ("copy directory '" ++ srcDir ++ "' to '" ++ destDir ++ "'.")
   srcFiles <- getDirectoryContentsRecursive srcDir
-  copyFilesWith (const copyFile) verbosity destDir [ (srcDir, f) | f <- srcFiles ]
+  copyFilesWith (const copyFile) verbosity destDir [ (srcDir, f)
+                                                   | f <- srcFiles ]
 
 -------------------
 -- File permissions
@@ -1060,7 +1099,8 @@
   Exception.bracket
     (openTempFile tmpDir template)
     (\(name, handle) -> do hClose handle
-                           unless (optKeepTempFiles opts) $ removeFile name)
+                           unless (optKeepTempFiles opts) $
+                             handleDoesNotExist () . removeFile $ name)
     (uncurry action)
 
 -- | Create and use a temporary directory.
@@ -1086,7 +1126,8 @@
 withTempDirectoryEx _verbosity opts targetDir template =
   Exception.bracket
     (createTempDirectory targetDir template)
-    (unless (optKeepTempFiles opts) . removeDirectoryRecursive)
+    (unless (optKeepTempFiles opts)
+     . handleDoesNotExist () . removeDirectoryRecursive)
 
 -----------------------------------
 -- Safely reading and writing files
@@ -1153,6 +1194,24 @@
         | x == y    = dropCommonPrefix xs ys
     dropCommonPrefix xs ys = (xs,ys)
 
+-- | Drop the extension if it's one of 'exeExtensions', or return the path
+-- unchanged.
+dropExeExtension :: FilePath -> FilePath
+dropExeExtension filepath =
+  case splitExtension filepath of
+    (filepath', extension) | extension `elem` exeExtensions -> filepath'
+                           | otherwise                      -> filepath
+
+-- | List of possible executable file extensions on the current platform.
+exeExtensions :: [String]
+exeExtensions = case buildOS of
+  -- Possible improvement: on Windows, read the list of extensions from the
+  -- PATHEXT environment variable. By default PATHEXT is ".com; .exe; .bat;
+  -- .cmd".
+  Windows -> ["", "exe"]
+  Ghcjs   -> ["", "exe"]
+  _       -> [""]
+
 -- ------------------------------------------------------------
 -- * Finding the description file
 -- ------------------------------------------------------------
@@ -1191,7 +1250,7 @@
 
 -- |Like 'findPackageDesc', but calls 'die' in case of error.
 tryFindPackageDesc :: FilePath -> IO FilePath
-tryFindPackageDesc dir = join . fmap (either die return) $ findPackageDesc dir
+tryFindPackageDesc dir = either die return =<< findPackageDesc dir
 
 -- |Optional auxiliary package information file (/pkgname/@.buildinfo@)
 defaultHookedPackageDesc :: IO (Maybe FilePath)
@@ -1415,14 +1474,13 @@
   where
     bSet = Set.fromList b
 
+-- | A total variant of 'tail'.
+safeTail :: [a] -> [a]
+safeTail []     = []
+safeTail (_:xs) = xs
+
 equating :: Eq a => (b -> a) -> b -> b -> Bool
 equating p x y = p x == p y
-
-comparing :: Ord a => (b -> a) -> b -> b -> Ordering
-comparing p x y = p x `compare` p y
-
-isInfixOf :: String -> String -> Bool
-isInfixOf needle haystack = any (isPrefixOf needle) (tails haystack)
 
 lowercase :: String -> String
 lowercase = map Char.toLower
diff --git a/cabal/Cabal/Distribution/System.hs b/cabal/Cabal/Distribution/System.hs
--- a/cabal/Cabal/Distribution/System.hs
+++ b/cabal/Cabal/Distribution/System.hs
@@ -13,7 +13,7 @@
 -- probably know about the 'System.Info.os' however using that is very
 -- inconvenient because it is a string and different Haskell implementations
 -- do not agree on using the same strings for the same platforms! (In
--- particular see the controversy over \"windows\" vs \"ming32\"). So to make it
+-- particular see the controversy over \"windows\" vs \"mingw32\"). So to make it
 -- more consistent and easy to use we have an 'OS' enumeration.
 --
 module Distribution.System (
@@ -28,18 +28,24 @@
   -- * Platform is a pair of arch and OS
   Platform(..),
   buildPlatform,
-  platformFromTriple
+  platformFromTriple,
+
+  -- * Internal
+  knownOSs,
+  knownArches
   ) where
 
 import qualified System.Info (os, arch)
-import qualified Data.Char as Char (toLower, isAlphaNum)
+import qualified Data.Char as Char (toLower, isAlphaNum, isAlpha)
 
-import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Binary
+import Distribution.Text
+import qualified Distribution.Compat.ReadP as Parse
+
+import Control.Monad (liftM2)
 import Data.Data (Data)
 import Data.Typeable (Typeable)
 import Data.Maybe (fromMaybe, listToMaybe)
-import Distribution.Text (Text(..), display)
-import qualified Distribution.Compat.ReadP as Parse
 import GHC.Generics (Generic)
 import qualified Text.PrettyPrint as Disp
 import Text.PrettyPrint ((<>))
@@ -178,11 +184,21 @@
 
 instance Text Platform where
   disp (Platform arch os) = disp arch <> Disp.char '-' <> disp os
+  -- TODO: there are ambigious platforms like: `arch-word-os`
+  -- which could be parsed as
+  --   * Platform "arch-word" "os"
+  --   * Platform "arch" "word-os"
+  -- We could support that preferring variants 'OtherOS' or 'OtherArch'
+  --
+  -- For now we split into arch and os parts on the first dash.
   parse = do
-    arch <- parse
+    arch <- parseDashlessArch
     _ <- Parse.char '-'
     os   <- parse
     return (Platform arch os)
+      where
+        parseDashlessArch :: Parse.ReadP r Arch
+        parseDashlessArch = fmap (classifyArch Strict) dashlessIdent
 
 -- | The platform Cabal was compiled on. In most cases,
 -- @LocalBuildInfo.hostPlatform@ should be used instead (the platform we're
@@ -193,9 +209,15 @@
 -- Utils:
 
 ident :: Parse.ReadP r String
-ident = Parse.munch1 (\c -> Char.isAlphaNum c || c == '_' || c == '-')
-  --TODO: probably should disallow starting with a number
+ident = liftM2 (:) first rest
+  where first = Parse.satisfy Char.isAlpha
+        rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_' || c == '-')
 
+dashlessIdent :: Parse.ReadP r String
+dashlessIdent = liftM2 (:) first rest
+  where first = Parse.satisfy Char.isAlpha
+        rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_')
+
 lowercase :: String -> String
 lowercase = map Char.toLower
 
@@ -204,10 +226,10 @@
   fmap fst (listToMaybe $ Parse.readP_to_S parseTriple triple)
   where parseWord = Parse.munch1 (\c -> Char.isAlphaNum c || c == '_')
         parseTriple = do
-          arch <- fmap (classifyArch Strict) parseWord
+          arch <- fmap (classifyArch Permissive) parseWord
           _ <- Parse.char '-'
           _ <- parseWord -- Skip vendor
           _ <- Parse.char '-'
-          os <- fmap (classifyOS Compat) ident -- OS may have hyphens, like
+          os <- fmap (classifyOS Permissive) ident -- OS may have hyphens, like
                                                -- 'nto-qnx'
           return $ Platform arch os
diff --git a/cabal/Cabal/Distribution/Text.hs b/cabal/Cabal/Distribution/Text.hs
--- a/cabal/Cabal/Distribution/Text.hs
+++ b/cabal/Cabal/Distribution/Text.hs
@@ -13,6 +13,7 @@
 --
 module Distribution.Text (
   Text(..),
+  defaultStyle,
   display,
   simpleParse,
   ) where
@@ -27,13 +28,15 @@
   disp  :: a -> Disp.Doc
   parse :: Parse.ReadP r a
 
+-- | The default rendering style used in Cabal for console output.
+defaultStyle :: Disp.Style
+defaultStyle = Disp.Style { Disp.mode           = Disp.PageMode
+                          , Disp.lineLength     = 79
+                          , Disp.ribbonsPerLine = 1.0
+                          }
+
 display :: Text a => a -> String
-display = Disp.renderStyle style . disp
-  where style = Disp.Style {
-          Disp.mode            = Disp.PageMode,
-          Disp.lineLength      = 79,
-          Disp.ribbonsPerLine  = 1.0
-        }
+display = Disp.renderStyle defaultStyle . disp
 
 simpleParse :: Text a => String -> Maybe a
 simpleParse str = case [ p | (p, s) <- Parse.readP_to_S parse str
@@ -51,19 +54,20 @@
                        , (Parse.string "False" Parse.+++
                           Parse.string "false") >> return False ]
 
+instance Text Int where
+  disp  = Disp.text . show
+  parse = (fmap negate $ Parse.char '-' >> parseNat) Parse.+++ parseNat
+
+-- | Parser for non-negative integers.
+parseNat :: Parse.ReadP r Int
+parseNat = read `fmap` Parse.munch1 Char.isDigit
+
 instance Text Version where
   disp (Version branch _tags)     -- Death to version tags!!
     = Disp.hcat (Disp.punctuate (Disp.char '.') (map Disp.int branch))
 
   parse = do
-      branch <- Parse.sepBy1 digits (Parse.char '.')
+      branch <- Parse.sepBy1 parseNat (Parse.char '.')
                 -- allow but ignore tags:
       _tags  <- Parse.many (Parse.char '-' >> Parse.munch1 Char.isAlphaNum)
       return (Version branch [])
-    where
-      digits = do
-        first <- Parse.satisfy Char.isDigit
-        if first == '0'
-          then return 0
-          else do rest <- Parse.munch Char.isDigit
-                  return (read (first : rest))
diff --git a/cabal/Cabal/Distribution/Utils/NubList.hs b/cabal/Cabal/Distribution/Utils/NubList.hs
--- a/cabal/Cabal/Distribution/Utils/NubList.hs
+++ b/cabal/Cabal/Distribution/Utils/NubList.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 module Distribution.Utils.NubList
     ( NubList    -- opaque
     , toNubList  -- smart construtor
@@ -11,12 +10,9 @@
     , overNubListR
     ) where
 
+import Distribution.Compat.Semigroup as Semi
 import Distribution.Compat.Binary
-#if __GLASGOW_HASKELL__ < 710
-import Data.Monoid
-#endif
-
-import Distribution.Simple.Utils (ordNub, listUnion, ordNubRight, listUnionRight)
+import Distribution.Simple.Utils
 
 import qualified Text.Read as R
 
@@ -54,8 +50,11 @@
 
 instance Ord a => Monoid (NubList a) where
     mempty = NubList []
-    mappend (NubList xs) (NubList ys) = NubList $ xs `listUnion` ys
+    mappend = (Semi.<>)
 
+instance Ord a => Semigroup (NubList a) where
+    (NubList xs) <> (NubList ys) = NubList $ xs `listUnion` ys
+
 instance Show a => Show (NubList a) where
     show (NubList list) = show list
 
@@ -91,7 +90,10 @@
 
 instance Ord a => Monoid (NubListR a) where
   mempty = NubListR []
-  mappend (NubListR xs) (NubListR ys) = NubListR $ xs `listUnionRight` ys
+  mappend = (Semi.<>)
+
+instance Ord a => Semigroup (NubListR a) where
+  (NubListR xs) <> (NubListR ys) = NubListR $ xs `listUnionRight` ys
 
 instance Show a => Show (NubListR a) where
   show (NubListR list) = show list
diff --git a/cabal/Cabal/Distribution/Verbosity.hs b/cabal/Cabal/Distribution/Verbosity.hs
--- a/cabal/Cabal/Distribution/Verbosity.hs
+++ b/cabal/Cabal/Distribution/Verbosity.hs
@@ -24,9 +24,10 @@
   showForCabal, showForGHC
  ) where
 
-import Distribution.Compat.Binary (Binary)
-import Data.List (elemIndex)
+import Distribution.Compat.Binary
 import Distribution.ReadE
+
+import Data.List (elemIndex)
 import GHC.Generics
 
 data Verbosity = Silent | Normal | Verbose | Deafening
diff --git a/cabal/Cabal/Distribution/Version.hs b/cabal/Cabal/Distribution/Version.hs
--- a/cabal/Cabal/Distribution/Version.hs
+++ b/cabal/Cabal/Distribution/Version.hs
@@ -3,7 +3,28 @@
 #if __GLASGOW_HASKELL__ < 707
 {-# LANGUAGE StandaloneDeriving #-}
 #endif
+
+-- Hack approach to support bootstrapping.
+-- When MIN_VERSION_binary macro is available, use it. But it's not available
+-- during bootstrapping (or anyone else building Setup.hs directly). If the
+-- builder specifies -DMIN_VERSION_binary_0_8_0=1 or =0 then we respect that.
+-- Otherwise we pick a default based on GHC version: assume binary <0.8 when
+-- GHC < 8.0, and binary >=0.8 when GHC >= 8.0.
+#ifdef MIN_VERSION_binary
+#define MIN_VERSION_binary_0_8_0 MIN_VERSION_binary(0,8,0)
+#else
+#ifndef MIN_VERSION_binary_0_8_0
+#if __GLASGOW_HASKELL__ >= 800
+#define MIN_VERSION_binary_0_8_0 1
+#else
+#define MIN_VERSION_binary_0_8_0 0
+#endif
+#endif
+#endif
+
+#if !MIN_VERSION_binary_0_8_0
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+#endif
 
 -----------------------------------------------------------------------------
 -- |
@@ -81,9 +102,10 @@
 import Data.Version     ( Version(..) )
 import GHC.Generics     ( Generic )
 
-import Distribution.Text ( Text(..) )
+import Distribution.Text
 import qualified Distribution.Compat.ReadP as Parse
-import Distribution.Compat.ReadP ((+++))
+import Distribution.Compat.ReadP hiding (get)
+
 import qualified Text.PrettyPrint as Disp
 import Text.PrettyPrint ((<>), (<+>))
 import qualified Data.Char as Char (isDigit)
@@ -113,6 +135,7 @@
 deriving instance Data Version
 #endif
 
+#if !(MIN_VERSION_binary_0_8_0)
 -- Deriving this instance from Generic gives trouble on GHC 7.2 because the
 -- Generic instance has to be standalone-derived. So, we hand-roll our own.
 -- We can't use a generic Binary instance on later versions because we must
@@ -123,14 +146,22 @@
         tags <- get
         return $ Version br tags
     put (Version br tags) = put br >> put tags
+#endif
 
-{-# DEPRECATED AnyVersion "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED ThisVersion "use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED LaterVersion "use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED EarlierVersion "use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED WildcardVersion "use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED UnionVersionRanges "use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}
-{-# DEPRECATED IntersectVersionRanges "use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DeprecateD AnyVersion
+    "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED ThisVersion
+    "Use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED LaterVersion
+    "Use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED EarlierVersion
+    "Use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED WildcardVersion
+    "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED UnionVersionRanges
+    "Use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}
+{-# DEPRECATED IntersectVersionRanges
+    "Use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'"#-}
 
 -- | The version range @-any@. That is, a version range containing all
 -- versions.
@@ -216,7 +247,8 @@
 --
 invertVersionRange :: VersionRange -> VersionRange
 invertVersionRange =
-    fromVersionIntervals . invertVersionIntervals . VersionIntervals . asVersionIntervals
+    fromVersionIntervals . invertVersionIntervals
+    . VersionIntervals . asVersionIntervals
 
 -- | The version range @== v.*@.
 --
@@ -286,9 +318,10 @@
                    (orLaterVersion v)
                    (earlierVersion (wildcardUpperBound v))
 
--- | An extended variant of 'foldVersionRange' that also provides a view of
--- in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"== v.*\"@ is presented
--- explicitly rather than in terms of the other basic syntax.
+-- | An extended variant of 'foldVersionRange' that also provides a view of the
+-- expression in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"==
+-- v.*\"@ is presented explicitly rather than in terms of the other basic
+-- syntax.
 --
 foldVersionRange' :: a                         -- ^ @\"-any\"@ version
                   -> (Version -> a)            -- ^ @\"== v\"@
@@ -697,10 +730,12 @@
       -- Empty interval set
       [] -> VersionIntervals [(noLowerBound, NoUpperBound)]
       -- Interval with no lower bound
-      ((lb, ub) : more) | lb == noLowerBound -> VersionIntervals $ invertVersionIntervals' ub more
+      ((lb, ub) : more) | lb == noLowerBound ->
+        VersionIntervals $ invertVersionIntervals' ub more
       -- Interval with a lower bound
       ((lb, ub) : more) ->
-          VersionIntervals $ (noLowerBound, invertLowerBound lb) : invertVersionIntervals' ub more
+          VersionIntervals $ (noLowerBound, invertLowerBound lb)
+          : invertVersionIntervals' ub more
     where
       -- Invert subsequent version intervals given the upper bound of
       -- the intervals already inverted.
@@ -743,8 +778,10 @@
            (\v   -> (Disp.text ">=" <> disp v                   , 0))
            (\v   -> (Disp.text "<=" <> disp v                   , 0))
            (\v _ -> (Disp.text "==" <> dispWild v               , 0))
-           (\(r1, p1) (r2, p2) -> (punct 2 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2))
-           (\(r1, p1) (r2, p2) -> (punct 1 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1))
+           (\(r1, p1) (r2, p2) ->
+             (punct 2 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2))
+           (\(r1, p1) (r2, p2) ->
+             (punct 1 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1))
            (\(r, _)   -> (Disp.parens r, 0))
 
     where dispWild (Version b _) =
@@ -812,14 +849,12 @@
 --
 -- @since 1.24.0.0
 hasUpperBound :: VersionRange -> Bool
-hasUpperBound AnyVersion = False
-hasUpperBound (ThisVersion _) = True
-hasUpperBound (LaterVersion _) = False
-hasUpperBound (EarlierVersion _) = True
-hasUpperBound (WildcardVersion _) = True
-hasUpperBound (UnionVersionRanges x y) = hasUpperBound x && hasUpperBound y
-hasUpperBound (IntersectVersionRanges x y) = hasUpperBound x || hasUpperBound y
-hasUpperBound (VersionRangeParens x) = hasUpperBound x
+hasUpperBound = foldVersionRange
+                False
+                (const True)
+                (const False)
+                (const True)
+                (&&) (||)
 
 -- | Does the version range have an explicit lower bound?
 --
@@ -828,11 +863,9 @@
 --
 -- @since 1.24.0.0
 hasLowerBound :: VersionRange -> Bool
-hasLowerBound AnyVersion = False
-hasLowerBound (ThisVersion _) = True
-hasLowerBound (LaterVersion _) = True
-hasLowerBound (EarlierVersion _) = False
-hasLowerBound (WildcardVersion _) = True
-hasLowerBound (UnionVersionRanges x y) = hasLowerBound x && hasLowerBound y
-hasLowerBound (IntersectVersionRanges x y) = hasLowerBound x || hasLowerBound y
-hasLowerBound (VersionRangeParens x) = hasLowerBound x
+hasLowerBound = foldVersionRange
+                False
+                (const True)
+                (const True)
+                (const False)
+                (&&) (||)
diff --git a/cabal/Cabal/Language/Haskell/Extension.hs b/cabal/Cabal/Language/Haskell/Extension.hs
--- a/cabal/Cabal/Language/Haskell/Extension.hs
+++ b/cabal/Cabal/Language/Haskell/Extension.hs
@@ -22,12 +22,13 @@
         deprecatedExtensions
   ) where
 
-import Distribution.Text (Text(..))
+import Distribution.Text
 import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.Binary
+
 import qualified Text.PrettyPrint as Disp
 import qualified Data.Char as Char (isAlphaNum)
 import Data.Array (Array, accumArray, bounds, Ix(inRange), (!))
-import Distribution.Compat.Binary (Binary)
 import Data.Data (Data)
 import Data.Typeable (Typeable)
 import GHC.Generics (Generic)
@@ -713,6 +714,57 @@
   --
   -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/other-type-extensions.html#derive-any-class>
   | DeriveAnyClass
+
+  -- | Enable @deriving@ for the 'Language.Haskell.TH.Syntax.Lift' class.
+  --
+  -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/deriving.html#deriving-lift>
+  | DeriveLift
+
+  -- | Enable support for 'static pointers' (and the @static@
+  -- keyword) to refer to globally stable names, even across
+  -- different programs.
+  --
+  -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/static-pointers.html>
+  | StaticPointers
+
+  -- | Switches data type declarations to be strict by default (as if
+  -- they had a bang using @BangPatterns@), and allow opt-in field
+  -- laziness using @~@.
+  | StrictData
+
+  -- | Switches all pattern bindings to be strict by default (as if
+  -- they had a bang using @BangPatterns@), ordinary patterns are
+  -- recovered using @~@. Implies @StrictData@.
+  | Strict
+
+  -- | Allows @do@-notation for types that are @'Applicative'@ as well
+  -- as @'Monad'@. When enabled, desugaring @do@ notation tries to use
+  -- @(<*>)@ and @'fmap'@ and @'join'@ as far as possible.
+  | ApplicativeDo
+
+  -- | Allow records to use duplicated field labels for accessors.
+  | DuplicateRecordFields
+
+  -- | Enable explicit type applications with the syntax @id \@Int@.
+  | TypeApplications
+
+  -- | Dissolve the distinction between types and kinds, allowing the compiler
+  -- to reason about kind equality and therefore enabling GADTs to be promoted
+  -- to the type-level.
+  | TypeInType
+
+  -- | Allow recursive (and therefore undecideable) super-class relationships.
+  | UndecidableSuperClasses
+
+  -- | A temporary extension to help library authors check if their
+  -- code will compile with the new planned desugaring of fail.
+  | MonadFailDesugaring
+
+  -- | A subset of @TemplateHaskell@ including only quasi-quoting.
+  | TemplateHaskellQuotes
+
+  -- | Allows use of the @#label@ syntax.
+  | OverloadedLabels
 
   deriving (Generic, Show, Read, Eq, Ord, Enum, Bounded, Typeable, Data)
 
diff --git a/cabal/Cabal/Makefile b/cabal/Cabal/Makefile
--- a/cabal/Cabal/Makefile
+++ b/cabal/Cabal/Makefile
@@ -1,5 +1,5 @@
 
-VERSION=1.23.0.0
+VERSION=1.25.0.0
 
 #KIND=devel
 KIND=rc
@@ -14,7 +14,10 @@
 
 # build the library itself
 
-SOURCES=Distribution/*.hs Distribution/Simple/*.hs Distribution/PackageDescription/*.hs Distribution/Simple/GHC/*.hs Distribution/Simple/Build/*.hs Distribution/Compat/*.hs Distribution/Simple/Program/*.hs
+SOURCES=Distribution/*.hs Distribution/Simple/*.hs \
+	Distribution/PackageDescription/*.hs Distribution/Simple/GHC/*.hs \
+	Distribution/Simple/Build/*.hs Distribution/Compat/*.hs \
+	Distribution/Simple/Program/*.hs
 CONFIG_STAMP=dist/setup-config
 BUILD_STAMP=dist/build/libHSCabal-$(VERSION).a
 HADDOCK_STAMP=dist/doc/html/Cabal/index.html
@@ -27,7 +30,8 @@
 
 setup: $(SOURCES) Setup.hs
 	-mkdir -p dist/setup
-	$(HC) $(GHCFLAGS) --make -i. -odir dist/setup -hidir dist/setup Setup.hs -o setup
+	$(HC) $(GHCFLAGS) --make -i. -odir dist/setup -hidir dist/setup Setup.hs \
+		-o setup
 
 $(CONFIG_STAMP): setup Cabal.cabal
 	./setup configure --with-compiler=$(HC) --prefix=$(PREFIX)
@@ -58,7 +62,9 @@
 	mkdir -p $(PANDOC_HTML_OUTDIR)
 	for file in $^; do \
 		[ $${file} != doc/index.markdown ] && TOC=--table-of-contents || TOC=; \
-		$(PANDOC) $(PANDOC_OPTIONS) $${TOC} --from=markdown --to=html --output $(PANDOC_HTML_OUTDIR)/$$(basename $${file} .markdown).html $${file}; \
+		$(PANDOC) $(PANDOC_OPTIONS) $${TOC} --from=markdown --to=html \
+			--output $(PANDOC_HTML_OUTDIR)/$$(basename $${file} .markdown).html \
+			$${file}; \
 	done
 	cp doc/$(PANDOC_HTML_CSS) $(PANDOC_HTML_OUTDIR)
 
diff --git a/cabal/Cabal/Setup.hs b/cabal/Cabal/Setup.hs
--- a/cabal/Cabal/Setup.hs
+++ b/cabal/Cabal/Setup.hs
@@ -8,3 +8,6 @@
 -- on any previous installation. This also means we can use any new features
 -- immediately because we never have to worry about building Cabal with an
 -- older version of itself.
+--
+-- NOTE 25/01/2015: Bootstrapping is disabled for now, see
+-- https://github.com/haskell/cabal/issues/3003.
diff --git a/cabal/Cabal/changelog b/cabal/Cabal/changelog
--- a/cabal/Cabal/changelog
+++ b/cabal/Cabal/changelog
@@ -1,16 +1,49 @@
 -*-change-log-*-
 
-1.23.x.x (current development version)
+1.25.x.x (current development version)
+	* Dropped support for versions of GHC earlier than 6.12 (#3111).
+	* Convenience/internal libraries are now supported (#269).
+	  An internal library is declared using the stanza "library
+	  'libname'".
+	* Backwards incompatible change to preprocessor interface:
+	  the function in 'PPSuffixHandler' now takes an additional
+	  'ComponentLocalBuildInfo' specifying the build information
+	  of the component being preprocessed.
+	* Backwards incompatible change to 'cabal_macros.h' (#1893): we now
+	  generate a macro file for each component which contains only
+	  information about the direct dependencies of that component.
+	  Consequently, 'dist/build/autogen/cabal_macros.h' contains
+	  only the macros for the library, and is not generated if a
+	  package has no library; to find the macros for an executable
+	  named 'foobar', look in 'dist/build/foobar/autogen/cabal_macros.h'.
+
+1.24.0.0 Ryan Thomas <ryan@ryant.org> March 2016
+	* Support GHC 8.
 	* Deal with extra C sources from preprocessors (#238).
 	* Include cabal_macros.h when running c2hs (#2600).
 	* Don't recompile C sources unless needed (#2601).
 	* Read 'builddir' option from 'CABAL_BUILDDIR' environment variable
-	* Add '--profiling-defail=$level' flag with a default for libraries
+	* Add '--profiling-detail=$level' flag with a default for libraries
 	  and executables of 'exported-functions' and 'toplevel-functions'
 	  respetively (GHC's '-fprof-auto-{exported,top}' flags) (#193).
 	* New 'custom-setup' stanza to specify setup deps. Setup is also built
 	  with the cabal_macros.h style macros, for conditional compilation.
 	* Support Haddock response files (#2746).
+	* Fixed a bug in the Text instance for Platform (#2862).
+	* New 'setup haddock' option: '--for-hackage' (#2852).
+	* New --show-detail=direct; like streaming, but allows the test
+	  program to detect that is connected to a terminal, and works
+	  reliable with a non-threaded runtime (#2911, and serves as a
+	  work-around for #2398)
+	* Library support for multi-instance package DBs (#2948).
+	* Improved the './Setup configure' solver (#3082, #3076).
+	* The '--allow-newer' option can be now used with './Setup
+	configure' (#3163).
+	* Added a way to specify extra locations to find OS X frameworks
+	in ('extra-framework-dirs'). Can be used both in .cabal files and
+	as an argument to './Setup configure' (#3158).
+	* Macros 'VERSION_$pkgname' and 'MIN_VERSION_$pkgname' are now
+	also generated for the current package. (#3235).
 
 1.22.0.0 Johan Tibell <johan.tibell@gmail.com> January 2015
 	* Support GHC 7.10.
@@ -43,7 +76,7 @@
 	* Make 'cabal test'/'cabal bench' build only what's needed for
 	running tests/benchmarks (#1821).
 	* Build shared libraries by default when linking executables dynamically.
-	* Build profiled libraries by default when profiling executable.s
+	* Build profiled libraries by default when profiling executables.
 
 1.20.0.1 Johan Tibell <johan.tibell@gmail.com> May 2014
 	* Fix streaming test output.
diff --git a/cabal/Cabal/doc/developing-packages.markdown b/cabal/Cabal/doc/developing-packages.markdown
--- a/cabal/Cabal/doc/developing-packages.markdown
+++ b/cabal/Cabal/doc/developing-packages.markdown
@@ -676,10 +676,11 @@
 The syntax of the value depends on the field.  Field types include:
 
 _token_, _filename_, _directory_
-:   Either a sequence of one or more non-space non-comma characters, or
-    a quoted string in Haskell 98 lexical syntax. Unless otherwise
-    stated, relative filenames and directories are interpreted from the
-    package root directory.
+:   Either a sequence of one or more non-space non-comma characters, or a quoted
+    string in Haskell 98 lexical syntax. The latter can be used for escaping
+    whitespace, for example: `ghc-options: -Wall "-with-rtsopts=-T -I1"`.
+    Unless otherwise stated, relative filenames and directories are interpreted
+    from the package root directory.
 
 _freeform_, _URL_, _address_
 :   An arbitrary, uninterpreted string.
@@ -965,6 +966,43 @@
 The library section may also contain build information fields (see the
 section on [build information](#build-information)).
 
+Cabal 1.23 and later support "internal libraries", which are extra named
+libraries (as opposed to the usual unnamed library section).  For
+example, suppose that your test suite needs access to some internal
+modules in your library, which you do not otherwise want to export.  You
+could put these modules in an internal library, which the main library
+and the test suite `build-depends` upon.  Then your Cabal file might
+look something like this:
+
+~~~~~~~~~~~~~~~~
+name:           foo
+version:        1.0
+license:        BSD3
+cabal-version:  >= 1.23
+build-type:     Simple
+
+library foo-internal
+    exposed-modules: Foo.Internal
+    build-depends: base
+
+library
+    exposed-modules: Foo.Public
+    build-depends: foo-internal, base
+
+test-suite test-foo
+    type:       exitcode-stdio-1.0
+    main-is:    test-foo.hs
+    build-depends: foo-internal, base
+~~~~~~~~~~~~~~~~
+
+Internal libraries are also useful for packages that define multiple
+executables, but do not define a publically accessible library.
+Internal libraries are only visible internally in the package (so they
+can only be added to the `build-depends` of same-package libraries,
+executables, test suites, etc.)  Internal libraries locally shadow any
+packages which have the same name (so don't name an internal library
+with the same name as an external dependency.)
+
 #### Opening an interpreter session ####
 
 While developing a package, it is often useful to make its code available inside
@@ -1002,6 +1040,32 @@
 `cabal.config` file.  All environments which share this file will use the
 dependency versions specified in it.
 
+#### Generating dependency version bounds ####
+
+Cabal also has the ability to suggest dependency version bounds that conform to
+[Package Versioning Policy][PVP], which is a recommended versioning system for
+publicly released Cabal packages. This is done by running the `gen-bounds`
+command:
+
+~~~~~~~~~~~~~~~~
+cabal gen-bounds
+~~~~~~~~~~~~~~~~
+
+For example, given the following dependencies specified in `build-depends`:
+
+~~~~~~~~~~~~~~~~
+foo == 0.5.2
+bar == 1.1
+~~~~~~~~~~~~~~~~
+
+`gen-bounds` will suggest changing them to the following:
+
+~~~~~~~~~~~~~~~~
+foo >= 0.5.2 && < 0.6
+bar >= 1.1 && < 1.2
+~~~~~~~~~~~~~~~~
+
+
 ### Executables ###
 
 Executable sections (if present) describe executable programs contained
@@ -1255,12 +1319,12 @@
 
 ### Build information ###
 
-The following fields may be optionally present in a library or
-executable section, and give information for the building of the
+The following fields may be optionally present in a library, executable, test
+suite or benchmark section, and give information for the building of the
 corresponding library or executable.  See also the sections on
 [system-dependent parameters](#system-dependent-parameters) and
-[configurations](#configurations) for a way to supply system-dependent
-values for these fields.
+[configurations](#configurations) for a way to supply system-dependent values
+for these fields.
 
 `build-depends:` _package list_
 :   A list of packages needed to build this one. Each package can be
@@ -1290,29 +1354,16 @@
 
     It is only syntactic sugar. It is exactly equivalent to `foo >= 1.2 && < 1.3`.
 
-    With Cabal 1.20 and GHC 7.10, `build-depends` also supports module
-    thinning and renaming, which allows you to selectively decide what
-    modules become visible from a package dependency.  For example:
-
-    ~~~~~~~~~~~~~~~~
-    build-depends: containers (Data.Set, Data.IntMap as Map)
-    ~~~~~~~~~~~~~~~~
-
-    This results in only the modules `Data.Set` and `Map` being visible to
-    the user from containers, hiding all other modules.  To add additional
-    names for modules without hiding the others, you can use the `with`
-    keyword:
-
-    ~~~~~~~~~~~~~~~~
-    build-depends: containers with (Data.IntMap as Map)
-    ~~~~~~~~~~~~~~~~
-
     Note: Prior to Cabal 1.8, build-depends specified in each section
     were global to all sections. This was unintentional, but some packages
     were written to depend on it, so if you need your build-depends to
     be local to each section, you must specify at least
     `Cabal-Version: >= 1.8` in your `.cabal` file.
 
+    Note: Cabal 1.20 experimentally supported module thinning and
+    renaming in `build-depends`; however, this support has since been
+    removed and should not be used.
+
 `other-modules:` _identifier list_
 :   A list of modules used by the component but not exposed to users.
     For a library component, these would be hidden modules of the
@@ -1362,8 +1413,10 @@
 
 `build-tools:` _program list_
 :   A list of programs, possibly annotated with versions, needed to
-    build this package, e.g. `c2hs >= 0.15, cpphs`.If no version
+    build this package, e.g. `c2hs >= 0.15, cpphs`.  If no version
     constraint is specified, any version is assumed to be acceptable.
+    `build-tools` can refer to locally defined executables, in which
+    case Cabal will make sure that executable is built first.
 
 `buildable:` _boolean_ (default: `True`)
 :   Is the component buildable? Like some of the other fields below,
@@ -1378,12 +1431,15 @@
     Options required only by one module may be specified by placing an
     `OPTIONS_GHC` pragma in the source file affected.
 
+    As with many other fields, whitespace can be escaped by using Haskell string
+    syntax. Example: `ghc-options: -Wcompat "-with-rtsopts=-T -I1" -Wall`.
+
 `ghc-prof-options:` _token list_
 :   Additional options for GHC when the package is built with profiling
     enabled.
 
     Note that as of Cabal-1.24, the default profiling detail level defaults to
-    `exported-functions` for libraries and `toplevel-funcitons` for
+    `exported-functions` for libraries and `toplevel-functions` for
     executables. For GHC these correspond to the flags `-fprof-auto-exported`
     and `-fprof-auto-top`. Prior to Cabal-1.24 the level defaulted to `none`.
     These levels can be adjusted by the person building the package with the
@@ -1399,6 +1455,9 @@
 
 `ghc-shared-options:` _token list_
 :   Additional options for GHC when the package is built as shared library.
+    The options specified via this field are combined with the ones specified
+    via `ghc-options`, and are passed to GHC during both the compile and
+    link phases.
 
 `includes:` _filename list_
 :   A list of header files to be included in any compilations via C.
@@ -1460,7 +1519,7 @@
     parameters](#system-dependent-parameters)>.
 
 `pkgconfig-depends:` _package list_
-:   A list of [pkg-config][] packages, needed to build this package.
+:   A list of [pkg-config] packages, needed to build this package.
     They can be annotated with versions, e.g. `gtk+-2.0 >= 2.10, cairo
     >= 1.0`. If no version constraint is specified, any version is
     assumed to be acceptable. Cabal uses `pkg-config` to find if the
@@ -1477,6 +1536,10 @@
     developer documentation for more details on frameworks.  This entry
     is ignored on all other platforms.
 
+`extra-frameworks-dirs:` _directory list_
+:   On Darwin/MacOS X, a list of directories to search for frameworks.
+    This entry is ignored on all other platforms.
+
 ### Configurations ###
 
 Library and executable sections may include conditional
@@ -2189,3 +2252,4 @@
 [Hackage]:    http://hackage.haskell.org/
 [pkg-config]: http://www.freedesktop.org/wiki/Software/pkg-config/
 [REPL]:       http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop
+[PVP]:        https://wiki.haskell.org/Package_versioning_policy
diff --git a/cabal/Cabal/doc/index.markdown b/cabal/Cabal/doc/index.markdown
--- a/cabal/Cabal/doc/index.markdown
+++ b/cabal/Cabal/doc/index.markdown
@@ -1,4 +1,5 @@
 % Cabal User Guide
+**Version: 1.25.0.0**
 
 Cabal is the standard package system for [Haskell] software. It helps
 people to configure, build and install Haskell software and to
diff --git a/cabal/Cabal/doc/installing-packages.markdown b/cabal/Cabal/doc/installing-packages.markdown
--- a/cabal/Cabal/doc/installing-packages.markdown
+++ b/cabal/Cabal/doc/installing-packages.markdown
@@ -1,5 +1,140 @@
 % Cabal User Guide
 
+# Configuration #
+
+## Overview ##
+
+The global configuration file for `cabal-install` is `~/.cabal/config`. If you
+do not have this file, `cabal` will create it for you on the first call to
+`cabal update`. Alternatively, you can explicitly ask `cabal` to create it for
+you using
+
+> `cabal user-config update`
+
+Most of the options in this configuration file are also available as command
+line arguments, and the corresponding documentation can be used to lookup their
+meaning. The created configuration file only specifies values for a handful of
+options. Most options are left at their default value, which it documents;
+for instance,
+
+~~~~~~~~~~~~~~~~
+-- executable-stripping: True
+~~~~~~~~~~~~~~~~
+
+means that the configuration file currently does not specify a value for the
+`executable-stripping` option (the line is commented out), and that the default
+is `True`; if you wanted to disable stripping of executables by default, you
+would change this line to
+
+~~~~~~~~~~~~~~~~
+executable-stripping: False
+~~~~~~~~~~~~~~~~
+
+You can also use `cabal user-config update` to migrate configuration files
+created by older versions of `cabal`.
+
+## Repository specification ##
+
+An important part of the configuration if the specification of the repository.
+When `cabal` creates a default config file, it configures the repository to
+be the central Hackage server:
+
+~~~~~~~~~~~~~~~~
+repository hackage.haskell.org
+  url: http://hackage.haskell.org/
+~~~~~~~~~~~~~~~~
+
+The name of the repository is given on the first line, and can be anything;
+packages downloaded from this repository will be cached under
+`~/.cabal/packages/hackage.haskell.org` (or whatever name you specify; you can
+change the prefix by changing the value of `remote-repo-cache`). If you want,
+you can configure multiple repositories, and `cabal` will combine them and be
+able to download packages from any of them.
+
+### Using secure repositories ###
+
+For repositories that support the TUF security infrastructure (this includes
+Hackage), you can enable secure access to the repository by specifying:
+
+~~~~~~~~~~~~~~~~
+repository hackage.haskell.org
+  url: http://hackage.haskell.org/
+  secure: True
+  root-keys: <root-key-IDs>
+  key-threshold: <key-threshold>
+~~~~~~~~~~~~~~~~
+
+The `<root-key-IDs>` and `<key-threshold>` values are used for bootstrapping. As
+part of the TUF infrastructure the repository will contain a file `root.json`
+(for instance,
+[http://hackage.haskell.org/root.json](http://hackage.haskell.org/root.json))
+which the client needs to do verification. However, how  can `cabal` verify the
+`root.json` file _itself_? This is known as bootstrapping: if you specify a list
+of root key IDs and a corresponding  threshold, `cabal` will verify that the
+downloaded `root.json` file has been  signed with at least `<key-threshold>`
+keys from your set of `<root-key-IDs>`.
+
+You can, but are not recommended to, omit these two fields. In that case `cabal`
+will download the `root.json` field and use it without verification. Although
+this bootstrapping step is then unsafe, all subsequent access is secure
+(provided that the downloaded `root.json` was not tempered with). Of course,
+adding `root-keys` and `key-threshold` to your repository specification only
+shifts the problem, because now you somehow need to make sure that the key IDs
+you received were the right ones. How that is done is however outside the scope
+of `cabal` proper.
+
+More information about the security infrastructure can be found at
+[https://github.com/well-typed/hackage-security](https://github.com/well-typed/hackage-security).
+
+### Legacy repositories ###
+
+Currently `cabal` supports two kinds of &ldquo;legacy&rdquo; repositories. The
+first is specified using
+
+~~~~~~~~~~~~~~~~
+remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive
+~~~~~~~~~~~~~~~~
+
+This is just syntactic sugar for
+
+~~~~~~~~~~~~~~~~
+repository hackage.haskell.org
+  url: hackage.haskell.org:http://hackage.haskell.org/packages/archive
+~~~~~~~~~~~~~~~~
+
+although, in (and only in) the specific case of Hackage, the URL
+`http://hackage.haskell.org/packages/archive` will be silently translated to
+`http://hackage.haskell.org/`.
+
+The second kind of legacy repositories are so-called &ldquo;local&rdquo;
+repositories:
+
+~~~~~~~~~~~~~~~~
+local-repo: my-local-repo:/path/to/local/repo
+~~~~~~~~~~~~~~~~
+
+This can be used to access repositories on the local file system. However, the
+layout of these local repositories is different from the layout of remote
+repositories, and usage of these local repositories is deprecated.
+
+### Secure local repositories ###
+
+If you want to use repositories on your local file system, it is recommended
+instead to use a _secure_ local repository:
+
+~~~~~~~~~~~~~~~~
+repository my-local-repo
+  url: file:/path/to/local/repo
+  secure: True
+  root-keys: <root-key-IDs>
+  key-threshold: <key-threshold>
+~~~~~~~~~~~~~~~~
+
+The layout of these secure local repos matches the layout of remote repositories
+exactly; the
+[hackage-repo-tool](http://hackage.haskell.org/package/hackage-repo-tool) can be
+used to create and manage such repositories.
+
 # Building and installing packages #
 
 After you've unpacked a Cabal package, you can build it by moving into
@@ -610,6 +745,14 @@
     be a file or directory. Not all implementations support arbitrary
     package databases.
 
+`--default-user-config=` _file_
+:   Allows a "default" `cabal.config` freeze file to be passed in
+    manually. This file will only be used if one does not exist in the
+    project directory already. Typically, this can be set from the global
+    cabal `config` file so as to provide a default set of partial
+    constraints to be used by projects, providing a way for users to peg
+    themselves to stable package collections.
+
 `--enable-optimization`[=_n_] or `-O`[_n_]
 :   (default) Build with optimization flags (if available). This is
     appropriate for production use, taking more time to build faster
@@ -789,6 +932,10 @@
 :   An extra directory to search for system libraries files. You can use
     this flag multiple times to get a list of directories.
 
+`--extra-framework-dirs`[=_dir_]
+:   An extra directory to search for frameworks (OS X only). You can use this
+    flag multiple times to get a list of directories.
+
     You might need to use this flag if you have standard system
     libraries in a non-standard location that is not mentioned in the
     package's `.cabal` file. Using this option has the same affect as
@@ -835,8 +982,26 @@
     $ cabal install --allow-newer=bar --constraint="bar==2.1" foo
     ~~~~~~~~~~~~~~~~
 
-    It's also possible to enable `--allow-newer` permanently by setting
-    `allow-newer: True` in the `~/.cabal/config` file.
+    It's also possible to limit the scope of `--allow-newer` to single
+    packages with the `--allow-newer=scope:dep` syntax. This means that the
+    dependency on `dep` will be relaxed only for the package `scope`.
+
+    Example:
+
+    ~~~~~~~~~~~~~~~~
+    # Relax upper bound in foo's dependency on base; also relax upper bound in
+    # every package's dependency on lens.
+    $ cabal install --allow-newer=foo:base,lens
+
+    # Relax upper bounds in foo's dependency on base and bar's dependency
+    # on time; also relax the upper bound in the dependency on lens specified by
+    # any package.
+    $ cabal install --allow-newer=foo:base,lens --allow-newer=bar:time
+    ~~~~~~~~~~~~~~~~
+
+    Finally, one can enable `--allow-newer` permanently by setting `allow-newer:
+    True` in the `~/.cabal/config` file. Enabling 'allow-newer' selectively is
+    also supported in the config file (`allow-newer: foo, bar, baz:base`).
 
 `--constraint=`_constraint_
 :   Restrict solutions involving a package to a given version range.
diff --git a/cabal/Cabal/misc/gen-extra-source-files.sh b/cabal/Cabal/misc/gen-extra-source-files.sh
--- a/cabal/Cabal/misc/gen-extra-source-files.sh
+++ b/cabal/Cabal/misc/gen-extra-source-files.sh
@@ -1,5 +1,22 @@
-#! /bin/sh
+#!/bin/sh
 
+if [ "$#" -ne 1 ]; then
+    echo "Error: too few arguments!"
+    echo "Usage: $0 FILE"
+    exit 1
+fi
+
+set -ex
+
 find tests -type f \( -name '*.hs' -or -name '*.lhs' -or -name '*.c' -or -name '*.sh' \
-    -or -name '*.cabal' -or -name '*.hsc' \) -and -not -regex ".*/dist/.*" \
-    | awk '/Check.hs$|UnitTests|PackageTester|autogen|PackageTests.hs|CreatePipe/ { next } { print }'
+    -or -name '*.cabal' -or -name '*.hsc' -or -name '*.err' -or -name '*.out' -or -name "ghc*" \) -and -not -regex ".*/dist.*/.*" \
+    | awk '/Check.hs$|UnitTests|PackageTester|autogen|PackageTests.hs|IntegrationTests.hs|CreatePipe|^tests\/Test/ { next } { print }' \
+    | LC_ALL=C sort \
+    | sed -e 's/^/  /' \
+    > source-file-list
+
+lead='^  -- BEGIN gen-extra-source-files'
+tail='^  -- END gen-extra-source-files'
+# cribbed off of http://superuser.com/questions/440013/how-to-replace-part-of-a-text-file-between-markers-with-another-text-file
+sed -i.bak -e "/$lead/,/$tail/{ /$lead/{p; r source-file-list
+              }; /$tail/p; d }" $1
diff --git a/cabal/Cabal/misc/travis-diff-files.sh b/cabal/Cabal/misc/travis-diff-files.sh
new file mode 100644
--- /dev/null
+++ b/cabal/Cabal/misc/travis-diff-files.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+git status > /dev/null # See https://github.com/haskell/cabal/pull/3088#commitcomment-15818452
+git diff-files -p --exit-code
diff --git a/cabal/HACKING.md b/cabal/HACKING.md
--- a/cabal/HACKING.md
+++ b/cabal/HACKING.md
@@ -34,7 +34,7 @@
 _The steps below will make use of sandboxes for building. The process might be
 somewhat different when you do not want to use sandboxes._
 
-Building Cabal from from source requires the following:
+Building Cabal from source requires the following:
 
 * Glorious/Glasgow Haskell Compiler (ghc).
 * An existing (relatively recent) `cabal` binary (e.g. obtained as part of the
@@ -70,7 +70,7 @@
     we cannot use `cabal` for the next steps;
     we need to use Setup instead.
     So, compile Setup.hs:
-    
+
     ~~~~
     ghc --make -threaded Setup.hs
     ~~~~
@@ -89,7 +89,7 @@
     ~~~~
     ~/MyHaskellCode/cabal/Cabal/.cabal-sandbox/$SOMESTUFF-packages.conf.d
     ~~~~
-    
+
     (or, as a relative path with my setup:)
 
     ~~~~
@@ -128,6 +128,18 @@
 (In addition, the absolute sandbox path will be slightly different
 because we have to use the `cabal-install` sandbox, not the Cabal one. If you
 use the relative path, you are set.)
+
+Coding Conventions
+------------------
+
+Use spaces, not tabs. Use lines no longer than 80 characters. If you modify a
+file, please follow the style conventions used in that file. When you add a new
+top-level definition, please also add a Haddock comment. Use explicit import
+lists for third-party and standard library imports. Use of GHC extensions is
+allowed (except Template Haskell), provided that the dependencies policy is
+respected. In general, try to adhere to [this style guide][guide].
+
+[guide]: https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md
 
 Dependencies policy
 -------------------
diff --git a/cabal/Paths_Cabal.hs b/cabal/Paths_Cabal.hs
--- a/cabal/Paths_Cabal.hs
+++ b/cabal/Paths_Cabal.hs
@@ -5,4 +5,4 @@
 import Data.Version (Version(..))
 
 version :: Version
-version = Version {versionBranch = [1,23,0,0], versionTags = []}
+version = Version [1,25,0,0] []
diff --git a/cabal/Paths_cabal_install.hs b/cabal/Paths_cabal_install.hs
--- a/cabal/Paths_cabal_install.hs
+++ b/cabal/Paths_cabal_install.hs
@@ -5,4 +5,4 @@
 import Data.Version (Version(..))
 
 version :: Version
-version = Version {versionBranch = [1,23,0,0], versionTags = []}
+version = Version [1,25,0,0] []
diff --git a/cabal/README.md b/cabal/README.md
--- a/cabal/README.md
+++ b/cabal/README.md
@@ -1,4 +1,4 @@
-# Cabal [![Build Status](https://secure.travis-ci.org/haskell/cabal.svg?branch=master)](http://travis-ci.org/haskell/cabal)
+# Cabal [![Hackage version](https://img.shields.io/hackage/v/Cabal.svg?label=Hackage)](https://hackage.haskell.org/package/Cabal) [![Build Status](https://secure.travis-ci.org/haskell/cabal.svg?branch=master)](http://travis-ci.org/haskell/cabal) [![Windows build status](https://ci.appveyor.com/api/projects/status/yotutrf4i4wn5d9y/branch/master?svg=true)](https://ci.appveyor.com/project/23Skidoo/cabal)
 
 This Cabal Git repository contains the following packages:
 
diff --git a/cabal/appveyor.yml b/cabal/appveyor.yml
new file mode 100644
--- /dev/null
+++ b/cabal/appveyor.yml
@@ -0,0 +1,28 @@
+install:
+  - choco install ghc -version 7.10.3 > NUL
+  - SET PATH=%PATH%;C:\tools\ghc\ghc-7.10.3\bin
+  - curl -o cabal.zip --silent https://www.haskell.org/cabal/release/cabal-install-1.24.0.0-rc1/cabal-install-1.24.0.0-rc1-x86_64-unknown-mingw32.zip
+  - 7z x -bd cabal.zip
+  - cabal --version
+  - cabal update
+
+build_script:
+  - cd Cabal
+  - ghc --make -threaded -i -i. Setup.hs -Wall -Werror
+
+  # 'echo "" |' works around an AppVeyor issue:
+  # https://github.com/commercialhaskell/stack/issues/1097#issuecomment-145747849
+  - echo "" | ..\cabal install --only-dependencies --enable-tests
+
+  - Setup configure --user --ghc-option=-Werror --enable-tests
+  - Setup build
+  - Setup test --show-details=streaming --test-option=--hide-successes
+  - Setup install
+  - cd ..\cabal-install
+  - ghc --make -threaded -i -i. Setup.hs -Wall -Werror
+  - echo "" | ..\cabal install happy
+  - echo "" | ..\cabal install --only-dependencies --enable-tests
+  - ..\cabal configure --user --ghc-option=-Werror --enable-tests
+  - ..\cabal build
+  - ..\cabal test unit-tests --show-details=streaming --test-option=--pattern=!FileMonitor --test-option=--hide-successes
+  - ..\cabal test integration-tests --show-details=streaming --test-option=--pattern=!exec --test-option=--hide-successes
diff --git a/cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs b/cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs
--- a/cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs
+++ b/cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs
@@ -191,7 +191,6 @@
   ParseFailed perror -> Left  msg where (_, msg) = locatedErrorMsg perror
   ParseOk   _ report -> Right report
 
---FIXME: this does not allow for optional or repeated fields
 parseFields :: String -> ParseResult BuildReport
 parseFields input = do
   fields <- mapM extractField =<< readFields input
diff --git a/cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs b/cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs
--- a/cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs
+++ b/cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs
@@ -79,7 +79,9 @@
                -> [(BuildReport, Repo, RemoteRepo)]
     onlyRemote rs =
       [ (report, repo, remoteRepo)
-      | (report, Just repo@Repo { repoKind = Left remoteRepo }) <- rs ]
+      | (report, Just repo) <- rs
+      , Just remoteRepo     <- [maybeRepoRemote repo]
+      ]
 
 storeLocal :: CompilerInfo -> [PathTemplate] -> [(BuildReport, Maybe Repo)]
            -> Platform -> IO ()
@@ -101,7 +103,7 @@
         fromPathTemplate (substPathTemplate env template)
       where env = initialPathTemplateEnv
                     (BuildReport.package  report)
-                    -- ToDo: In principle, we can support $pkgkey, but only
+                    -- TODO: In principle, we can support $pkgkey, but only
                     -- if the configure step succeeds.  So add a Maybe field
                     -- to the build report, and either use that or make up
                     -- a fake identifier if it's not available.
@@ -130,7 +132,7 @@
                 -> InstallPlan.PlanPackage
                 -> Maybe (BuildReport, Maybe Repo)
 fromPlanPackage (Platform arch os) comp planPackage = case planPackage of
-  InstallPlan.Installed (ReadyPackage (ConfiguredPackage srcPkg flags _ _) deps)
+  InstallPlan.Installed (ReadyPackage (ConfiguredPackage srcPkg flags _ deps))
                          _ result
     -> Just $ ( BuildReport.new os arch comp
                                 (packageId srcPkg) flags
diff --git a/cabal/cabal-install/Distribution/Client/BuildReports/Types.hs b/cabal/cabal-install/Distribution/Client/BuildReports/Types.hs
--- a/cabal/cabal-install/Distribution/Client/BuildReports/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/BuildReports/Types.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.BuildReports.Types
@@ -24,9 +25,14 @@
 
 import Data.Char as Char
          ( isAlpha, toLower )
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary)
 
+
 data ReportLevel = NoReports | AnonymousReports | DetailedReports
-  deriving (Eq, Ord, Show)
+  deriving (Eq, Ord, Enum, Show, Generic)
+
+instance Binary ReportLevel
 
 instance Text.Text ReportLevel where
   disp NoReports        = Disp.text "none"
diff --git a/cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs b/cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs
--- a/cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs
+++ b/cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs
@@ -27,22 +27,24 @@
 import Distribution.Verbosity (Verbosity)
 import Distribution.Simple.Utils (die)
 import Distribution.Client.HttpUtils
+import Distribution.Client.Setup
+         ( RepoContext(..) )
 
 type BuildReportId = URI
 type BuildLog = String
 
-uploadReports :: Verbosity -> (String, String) -> URI -> [(BuildReport, Maybe BuildLog)] -> IO ()
-uploadReports verbosity auth uri reports = do
+uploadReports :: Verbosity -> RepoContext -> (String, String) -> URI -> [(BuildReport, Maybe BuildLog)] -> IO ()
+uploadReports verbosity repoCtxt auth uri reports = do
   forM_ reports $ \(report, mbBuildLog) -> do
-     buildId <- postBuildReport verbosity auth uri report
+     buildId <- postBuildReport verbosity repoCtxt auth uri report
      case mbBuildLog of
-       Just buildLog -> putBuildLog verbosity auth buildId buildLog
+       Just buildLog -> putBuildLog verbosity repoCtxt auth buildId buildLog
        Nothing       -> return ()
 
-postBuildReport :: Verbosity -> (String, String) -> URI -> BuildReport -> IO BuildReportId
-postBuildReport verbosity auth uri buildReport = do
+postBuildReport :: Verbosity -> RepoContext -> (String, String) -> URI -> BuildReport -> IO BuildReportId
+postBuildReport verbosity repoCtxt auth uri buildReport = do
   let fullURI = uri { uriPath = "/package" </> display (BuildReport.package buildReport) </> "reports" }
-  transport <- configureTransport verbosity Nothing
+  transport <- repoContextGetTransport repoCtxt
   res <- postHttp transport verbosity fullURI (BuildReport.show buildReport) (Just auth)
   case res of
     (303, redir) -> return $ undefined redir --TODO parse redir
@@ -78,12 +80,12 @@
 
 -- TODO force this to be a PUT?
 
-putBuildLog :: Verbosity -> (String, String)
+putBuildLog :: Verbosity -> RepoContext -> (String, String)
             -> BuildReportId -> BuildLog
             -> IO ()
-putBuildLog verbosity auth reportId buildLog = do
+putBuildLog verbosity repoCtxt auth reportId buildLog = do
   let fullURI = reportId {uriPath = uriPath reportId </> "log"}
-  transport <- configureTransport verbosity Nothing
+  transport <- repoContextGetTransport repoCtxt
   res <- postHttp transport verbosity fullURI buildLog (Just auth)
   case res of
     (200, _) -> return ()
diff --git a/cabal/cabal-install/Distribution/Client/Compat/Time.hs b/cabal/cabal-install/Distribution/Client/Compat/Time.hs
--- a/cabal/cabal-install/Distribution/Client/Compat/Time.hs
+++ b/cabal/cabal-install/Distribution/Client/Compat/Time.hs
@@ -1,74 +1,69 @@
-{-# LANGUAGE CPP, ForeignFunctionInterface #-}
+{-# LANGUAGE CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving #-}
 module Distribution.Client.Compat.Time
-       (EpochTime, getModTime, getFileAge, getCurTime)
+       ( ModTime(..) -- Needed for testing
+       , getModTime, getFileAge, getCurTime
+       , posixSecondsToModTime )
        where
 
-import Data.Int (Int64)
-import System.Directory (getModificationTime)
+import Control.Arrow    ( first )
+import Data.Int         ( Int64 )
+import Data.Word        ( Word64 )
+import System.Directory ( getModificationTime )
 
+import Distribution.Compat.Binary ( Binary )
+
+import Data.Time.Clock.POSIX ( POSIXTime, getPOSIXTime )
 #if MIN_VERSION_directory(1,2,0)
-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixDayLength)
-import Data.Time (getCurrentTime, diffUTCTime)
+import Data.Time.Clock.POSIX ( posixDayLength )
+import Data.Time             ( diffUTCTime, getCurrentTime )
 #else
-import System.Time (ClockTime(..), getClockTime
-                   ,diffClockTimes, normalizeTimeDiff, tdDay, tdHour)
+import System.Time ( getClockTime, diffClockTimes
+                   , normalizeTimeDiff, tdDay, tdHour )
 #endif
 
 #if defined mingw32_HOST_OS
 
+import Data.Bits          ((.|.), unsafeShiftL)
 #if MIN_VERSION_base(4,7,0)
-import Data.Bits          ((.|.), finiteBitSize, unsafeShiftL)
-#else
-import Data.Bits          ((.|.), bitSize, unsafeShiftL)
-#endif
-import Data.Int           (Int32)
-import Data.Word          (Word64)
-import Foreign            (allocaBytes, peekByteOff)
-import System.IO.Error    (mkIOError, doesNotExistErrorType)
-import System.Win32.Types (BOOL, DWORD, LPCTSTR, LPVOID, withTString)
-
-#ifdef x86_64_HOST_ARCH
-#define CALLCONV ccall
+import Data.Bits          (finiteBitSize)
 #else
-#define CALLCONV stdcall
+import Data.Bits          (bitSize)
 #endif
 
-foreign import CALLCONV "windows.h GetFileAttributesExW"
-  c_getFileAttributesEx :: LPCTSTR -> Int32 -> LPVOID -> IO BOOL
-
-getFileAttributesEx :: String -> LPVOID -> IO BOOL
-getFileAttributesEx path lpFileInformation =
-  withTString path $ \c_path ->
-      c_getFileAttributesEx c_path getFileExInfoStandard lpFileInformation
-
-getFileExInfoStandard :: Int32
-getFileExInfoStandard = 0
-
-size_WIN32_FILE_ATTRIBUTE_DATA :: Int
-size_WIN32_FILE_ATTRIBUTE_DATA = 36
+import Data.Int           ( Int32 )
+import Foreign            ( allocaBytes, peekByteOff )
+import System.IO.Error    ( mkIOError, doesNotExistErrorType )
+import System.Win32.Types ( BOOL, DWORD, LPCTSTR, LPVOID, withTString )
 
-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime :: Int
-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20
+#else
 
-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime :: Int
-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime = 24
+import System.Posix.Files ( FileStatus, getFileStatus )
 
+#if MIN_VERSION_unix(2,6,0)
+import System.Posix.Files ( modificationTimeHiRes )
 #else
-
-import Foreign.C.Types    (CTime(..))
-import System.Posix.Files (getFileStatus, modificationTime)
+import System.Posix.Files ( modificationTime )
+#endif
 
 #endif
 
--- | The number of seconds since the UNIX epoch.
-type EpochTime = Int64
+-- | An opaque type representing a file's modification time, represented
+-- internally as a 64-bit unsigned integer in the Windows UTC format.
+newtype ModTime = ModTime Word64
+                deriving (Binary, Bounded, Eq, Ord)
 
--- | Return modification time of given file. Works around the low clock
+instance Show ModTime where
+  show (ModTime x) = show x
+
+instance Read ModTime where
+  readsPrec p str = map (first ModTime) (readsPrec p str)
+
+-- | Return modification time of the given file. Works around the low clock
 -- resolution problem that 'getModificationTime' has on GHC < 7.8.
 --
--- This is a modified version of the code originally written for OpenShake by
--- Neil Mitchell. See module Development.Shake.FileTime.
-getModTime :: FilePath -> IO EpochTime
+-- This is a modified version of the code originally written for Shake by Neil
+-- Mitchell. See module Development.Shake.FileInfo.
+getModTime :: FilePath -> IO ModTime
 
 #if defined mingw32_HOST_OS
 
@@ -86,39 +81,74 @@
                 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
       dwHigh <- peekByteOff info
                 index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime
-      return $! windowsTimeToPOSIXSeconds dwLow dwHigh
-        where
-          windowsTimeToPOSIXSeconds :: DWORD -> DWORD -> EpochTime
-          windowsTimeToPOSIXSeconds dwLow dwHigh =
-            let wINDOWS_TICK      = 10000000
-                sEC_TO_UNIX_EPOCH = 11644473600
 #if MIN_VERSION_base(4,7,0)
-                qwTime = (fromIntegral dwHigh `unsafeShiftL` finiteBitSize dwHigh)
-                         .|. (fromIntegral dwLow)
+      let qwTime =
+            (fromIntegral (dwHigh :: DWORD) `unsafeShiftL` finiteBitSize dwHigh)
+            .|. (fromIntegral (dwLow :: DWORD))
 #else
-                qwTime = (fromIntegral dwHigh `unsafeShiftL` bitSize dwHigh)
-                         .|. (fromIntegral dwLow)
+      let qwTime =
+            (fromIntegral (dwHigh :: DWORD) `unsafeShiftL` bitSize dwHigh)
+            .|. (fromIntegral (dwLow :: DWORD))
 #endif
-                res    = ((qwTime :: Word64) `div` wINDOWS_TICK)
-                         - sEC_TO_UNIX_EPOCH
-            -- TODO: What if the result is not representable as POSIX seconds?
-            -- Probably fine to return garbage.
-            in fromIntegral res
+      return $! ModTime (qwTime :: Word64)
+
+#ifdef x86_64_HOST_ARCH
+#define CALLCONV ccall
 #else
+#define CALLCONV stdcall
+#endif
 
+foreign import CALLCONV "windows.h GetFileAttributesExW"
+  c_getFileAttributesEx :: LPCTSTR -> Int32 -> LPVOID -> IO BOOL
+
+getFileAttributesEx :: String -> LPVOID -> IO BOOL
+getFileAttributesEx path lpFileInformation =
+  withTString path $ \c_path ->
+      c_getFileAttributesEx c_path getFileExInfoStandard lpFileInformation
+
+getFileExInfoStandard :: Int32
+getFileExInfoStandard = 0
+
+size_WIN32_FILE_ATTRIBUTE_DATA :: Int
+size_WIN32_FILE_ATTRIBUTE_DATA = 36
+
+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime :: Int
+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20
+
+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime :: Int
+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime = 24
+
+#else
+
 -- Directly against the unix library.
 getModTime path = do
-    -- CTime is Int32 in base 4.5, Int64 in base >= 4.6, and an abstract type in
-    -- base < 4.5.
-    t <- fmap modificationTime $ getFileStatus path
-#if MIN_VERSION_base(4,5,0)
-    let CTime i = t
-    return (fromIntegral i)
+    st <- getFileStatus path
+    return $! (extractFileTime st)
+
+extractFileTime :: FileStatus -> ModTime
+#if MIN_VERSION_unix(2,6,0)
+extractFileTime x = posixTimeToModTime (modificationTimeHiRes x)
 #else
-    return (read . show $ t)
+extractFileTime x = posixSecondsToModTime $ fromIntegral $ fromEnum $
+                    modificationTime x
 #endif
+
 #endif
 
+windowsTick, secToUnixEpoch :: Word64
+windowsTick    = 10000000
+secToUnixEpoch = 11644473600
+
+-- | Convert POSIX seconds to ModTime.
+posixSecondsToModTime :: Int64 -> ModTime
+posixSecondsToModTime s =
+  ModTime $ ((fromIntegral s :: Word64) + secToUnixEpoch) * windowsTick
+
+-- | Convert 'POSIXTime' to 'ModTime'.
+posixTimeToModTime :: POSIXTime -> ModTime
+posixTimeToModTime p = ModTime $ (ceiling $ p * 1e7) -- 100 ns precision
+                       + (secToUnixEpoch * windowsTick)
+
 -- | Return age of given file in days.
 getFileAge :: FilePath -> IO Double
 getFileAge file = do
@@ -132,11 +162,6 @@
   return $ fromIntegral ((24 * tdDay dt) + tdHour dt) / 24.0
 #endif
 
-getCurTime :: IO EpochTime
-getCurTime =  do
-#if MIN_VERSION_directory(1,2,0)
-  (truncate . utcTimeToPOSIXSeconds) `fmap` getCurrentTime
-#else
-  (TOD s _) <- getClockTime
-  return $! fromIntegral s
-#endif
+-- | Return the current time as 'ModTime'.
+getCurTime :: IO ModTime
+getCurTime = posixTimeToModTime `fmap` getPOSIXTime -- Uses 'gettimeofday'.
diff --git a/cabal/cabal-install/Distribution/Client/ComponentDeps.hs b/cabal/cabal-install/Distribution/Client/ComponentDeps.hs
--- a/cabal/cabal-install/Distribution/Client/ComponentDeps.hs
+++ b/cabal/cabal-install/Distribution/Client/ComponentDeps.hs
@@ -10,6 +10,7 @@
 -- > import qualified Distribution.Client.ComponentDeps as CD
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
 module Distribution.Client.ComponentDeps (
     -- * Fine-grained package dependencies
     Component(..)
@@ -20,6 +21,7 @@
   , fromList
   , singleton
   , insert
+  , filterDeps
   , fromLibraryDeps
   , fromSetupDeps
   , fromInstalled
@@ -34,6 +36,9 @@
 
 import Data.Map (Map)
 import qualified Data.Map as Map
+import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Semigroup (Semigroup((<>)))
+import GHC.Generics
 import Data.Foldable (fold)
 
 #if !MIN_VERSION_base(4,8,0)
@@ -46,34 +51,44 @@
   Types
 -------------------------------------------------------------------------------}
 
--- | Component of a package
+-- | Component of a package.
 data Component =
-    ComponentLib
+    ComponentLib   String
   | ComponentExe   String
   | ComponentTest  String
   | ComponentBench String
   | ComponentSetup
-  deriving (Show, Eq, Ord)
+  deriving (Show, Eq, Ord, Generic)
 
--- | Dependency for a single component
+instance Binary Component
+
+-- | Dependency for a single component.
 type ComponentDep a = (Component, a)
 
--- | Fine-grained dependencies for a package
+-- | Fine-grained dependencies for a package.
+--
+-- Typically used as @ComponentDeps [Dependency]@, to represent the list of
+-- dependencies for each named component within a package.
+--
 newtype ComponentDeps a = ComponentDeps { unComponentDeps :: Map Component a }
-  deriving (Show, Functor, Eq, Ord)
+  deriving (Show, Functor, Eq, Ord, Generic)
 
-instance Monoid a => Monoid (ComponentDeps a) where
-  mempty =
-    ComponentDeps Map.empty
-  (ComponentDeps d) `mappend` (ComponentDeps d') =
-    ComponentDeps (Map.unionWith mappend d d')
+instance Semigroup a => Monoid (ComponentDeps a) where
+  mempty = ComponentDeps Map.empty
+  mappend = (<>)
 
+instance Semigroup a => Semigroup (ComponentDeps a) where
+  ComponentDeps d <> ComponentDeps d' =
+      ComponentDeps (Map.unionWith (<>) d d')
+
 instance Foldable ComponentDeps where
   foldMap f = foldMap f . unComponentDeps
 
 instance Traversable ComponentDeps where
   traverse f = fmap ComponentDeps . traverse f . unComponentDeps
 
+instance Binary a => Binary (ComponentDeps a)
+
 {-------------------------------------------------------------------------------
   Construction
 -------------------------------------------------------------------------------}
@@ -93,18 +108,22 @@
     aux Nothing   = Just a
     aux (Just a') = Just $ a `mappend` a'
 
+-- | Keep only selected components (and their associated deps info).
+filterDeps :: (Component -> a -> Bool) -> ComponentDeps a -> ComponentDeps a
+filterDeps p = ComponentDeps . Map.filterWithKey p . unComponentDeps
+
 -- | ComponentDeps containing library dependencies only
-fromLibraryDeps :: a -> ComponentDeps a
-fromLibraryDeps = singleton ComponentLib
+fromLibraryDeps :: String -> a -> ComponentDeps a
+fromLibraryDeps n = singleton (ComponentLib n)
 
--- | ComponentDeps containing setup dependencies only
+-- | ComponentDeps containing setup dependencies only.
 fromSetupDeps :: a -> ComponentDeps a
 fromSetupDeps = singleton ComponentSetup
 
--- | ComponentDeps for installed packages
+-- | ComponentDeps for installed packages.
 --
--- We assume that installed packages only record their library dependencies
-fromInstalled :: a -> ComponentDeps a
+-- We assume that installed packages only record their library dependencies.
+fromInstalled :: String -> a -> ComponentDeps a
 fromInstalled = fromLibraryDeps
 
 {-------------------------------------------------------------------------------
@@ -114,7 +133,7 @@
 toList :: ComponentDeps a -> [ComponentDep a]
 toList = Map.toList . unComponentDeps
 
--- | All dependencies of a package
+-- | All dependencies of a package.
 --
 -- This is just a synonym for 'fold', but perhaps a use of 'flatDeps' is more
 -- obvious than a use of 'fold', and moreover this avoids introducing lots of
@@ -122,21 +141,22 @@
 flatDeps :: Monoid a => ComponentDeps a -> a
 flatDeps = fold
 
--- | All dependencies except the setup dependencies
+-- | All dependencies except the setup dependencies.
 --
--- Prior to the introduction of setup dependencies (TODO: Version? 1.23) this
--- would have been _all_ dependencies
+-- Prior to the introduction of setup dependencies in version 1.24 this
+-- would have been _all_ dependencies.
 nonSetupDeps :: Monoid a => ComponentDeps a -> a
 nonSetupDeps = select (/= ComponentSetup)
 
--- | Library dependencies proper only
+-- | Library dependencies proper only.
 libraryDeps :: Monoid a => ComponentDeps a -> a
-libraryDeps = select (== ComponentLib)
+libraryDeps = select (\c -> case c of ComponentLib _ -> True
+                                      _ -> False)
 
--- | Setup dependencies
+-- | Setup dependencies.
 setupDeps :: Monoid a => ComponentDeps a -> a
 setupDeps = select (== ComponentSetup)
 
--- | Select dependencies satisfying a given predicate
+-- | Select dependencies satisfying a given predicate.
 select :: Monoid a => (Component -> Bool) -> ComponentDeps a -> a
 select p = foldMap snd . filter (p . fst) . toList
diff --git a/cabal/cabal-install/Distribution/Client/Config.hs b/cabal/cabal-install/Distribution/Client/Config.hs
--- a/cabal/cabal-install/Distribution/Client/Config.hs
+++ b/cabal/cabal-install/Distribution/Client/Config.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoMonoLocalBinds #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -16,6 +17,7 @@
 module Distribution.Client.Config (
     SavedConfig(..),
     loadConfig,
+    getConfigFilePath,
 
     showConfig,
     showConfigWithComments,
@@ -37,7 +39,10 @@
     withProgramsFields,
     withProgramOptionsFields,
     userConfigDiff,
-    userConfigUpdate
+    userConfigUpdate,
+    createDefaultConfigFile,
+
+    remoteRepoFields
   ) where
 
 import Distribution.Client.Types
@@ -60,6 +65,7 @@
          ( DebugInfoLevel(..), OptimisationLevel(..) )
 import Distribution.Simple.Setup
          ( ConfigFlags(..), configureOptions, defaultConfigFlags
+         , AllowNewer(..)
          , HaddockFlags(..), haddockOptions, defaultHaddockFlags
          , installDirsOptions, optionDistPref
          , programConfigurationPaths', programConfigurationOptions
@@ -73,9 +79,11 @@
          , locatedErrorMsg, showPWarning
          , readFields, warning, lineNo
          , simpleField, listField, spaceListField
-         , parseFilePathQ, parseTokenQ )
+         , parseFilePathQ, parseOptCommaList, parseTokenQ )
 import Distribution.Client.ParseUtils
          ( parseFields, ppFields, ppSection )
+import Distribution.Client.HttpUtils
+         ( isOldHackageURI )
 import qualified Distribution.ParseUtils as ParseUtils
          ( Field(..) )
 import qualified Distribution.Text as Text
@@ -96,14 +104,11 @@
          ( partition, find, foldl' )
 import Data.Maybe
          ( fromMaybe )
-#if !MIN_VERSION_base(4,8,0)
-import Data.Monoid
-         ( Monoid(..) )
-#endif
 import Control.Monad
-         ( unless, foldM, liftM, liftM2 )
+         ( when, unless, foldM, liftM, liftM2 )
 import qualified Distribution.Compat.ReadP as Parse
-         ( option )
+         ( (<++), option )
+import Distribution.Compat.Semigroup
 import qualified Text.PrettyPrint as Disp
          ( render, text, empty )
 import Text.PrettyPrint
@@ -133,6 +138,7 @@
          ( on )
 import Data.List
          ( nubBy )
+import GHC.Generics ( Generic )
 
 --
 -- * Configuration saved in the config file
@@ -148,21 +154,14 @@
     savedUploadFlags       :: UploadFlags,
     savedReportFlags       :: ReportFlags,
     savedHaddockFlags      :: HaddockFlags
-  }
+  } deriving Generic
 
 instance Monoid SavedConfig where
-  mempty = SavedConfig {
-    savedGlobalFlags       = mempty,
-    savedInstallFlags      = mempty,
-    savedConfigureFlags    = mempty,
-    savedConfigureExFlags  = mempty,
-    savedUserInstallDirs   = mempty,
-    savedGlobalInstallDirs = mempty,
-    savedUploadFlags       = mempty,
-    savedReportFlags       = mempty,
-    savedHaddockFlags      = mempty
-  }
-  mappend a b = SavedConfig {
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup SavedConfig where
+  a <> b = SavedConfig {
     savedGlobalFlags       = combinedSavedGlobalFlags,
     savedInstallFlags      = combinedSavedInstallFlags,
     savedConfigureFlags    = combinedSavedConfigureFlags,
@@ -195,6 +194,11 @@
       combine'        field subfield =
         (subfield . field $ a) `mappend` (subfield . field $ b)
 
+      combineMonoid :: Monoid mon => (SavedConfig -> flags) -> (flags -> mon)
+                    -> mon
+      combineMonoid field subfield =
+        (subfield . field $ a) `mappend` (subfield . field $ b)
+
       lastNonEmpty' :: (SavedConfig -> flags) -> (flags -> [a]) -> [a]
       lastNonEmpty'   field subfield =
         let a' = subfield . field $ a
@@ -215,6 +219,7 @@
         globalNumericVersion    = combine globalNumericVersion,
         globalConfigFile        = combine globalConfigFile,
         globalSandboxConfigFile = combine globalSandboxConfigFile,
+        globalConstraintsFile   = combine globalConstraintsFile,
         globalRemoteRepos       = lastNonEmptyNL globalRemoteRepos,
         globalCacheDir          = combine globalCacheDir,
         globalLocalRepos        = lastNonEmptyNL globalLocalRepos,
@@ -222,6 +227,7 @@
         globalWorldFile         = combine globalWorldFile,
         globalRequireSandbox    = combine globalRequireSandbox,
         globalIgnoreSandbox     = combine globalIgnoreSandbox,
+        globalIgnoreExpiry      = combine globalIgnoreExpiry,
         globalHttpTransport     = combine globalHttpTransport
         }
         where
@@ -259,7 +265,7 @@
           lastNonEmptyNL = lastNonEmptyNL' savedInstallFlags
 
       combinedSavedConfigureFlags = ConfigFlags {
-        configPrograms            = configPrograms . savedConfigureFlags $ b,
+        configPrograms_           = configPrograms_ . savedConfigureFlags $ b,
         -- TODO: NubListify
         configProgramPaths        = lastNonEmpty configProgramPaths,
         -- TODO: NubListify
@@ -290,7 +296,10 @@
         -- TODO: NubListify
         configExtraLibDirs        = lastNonEmpty configExtraLibDirs,
         -- TODO: NubListify
+        configExtraFrameworkDirs  = lastNonEmpty configExtraFrameworkDirs,
+        -- TODO: NubListify
         configExtraIncludeDirs    = lastNonEmpty configExtraIncludeDirs,
+        configIPID                = combine configIPID,
         configDistPref            = combine configDistPref,
         configVerbosity           = combine configVerbosity,
         configUserInstall         = combine configUserInstall,
@@ -304,7 +313,6 @@
         configConstraints         = lastNonEmpty configConstraints,
         -- TODO: NubListify
         configDependencies        = lastNonEmpty configDependencies,
-        configInstantiateWith     = lastNonEmpty configInstantiateWith,
         -- TODO: NubListify
         configConfigurationsFlags = lastNonEmpty configConfigurationsFlags,
         configTests               = combine configTests,
@@ -313,7 +321,9 @@
         configLibCoverage         = combine configLibCoverage,
         configExactConfiguration  = combine configExactConfiguration,
         configFlagError           = combine configFlagError,
-        configRelocatable         = combine configRelocatable
+        configRelocatable         = combine configRelocatable,
+        configAllowNewer          = combineMonoid savedConfigureFlags
+                                    configAllowNewer
         }
         where
           combine        = combine'        savedConfigureFlags
@@ -326,8 +336,7 @@
         configExConstraints = lastNonEmpty configExConstraints,
         -- TODO: NubListify
         configPreferences   = lastNonEmpty configPreferences,
-        configSolver        = combine configSolver,
-        configAllowNewer    = combine configAllowNewer
+        configSolver        = combine configSolver
         }
         where
           combine      = combine' savedConfigureExFlags
@@ -343,6 +352,7 @@
 
       combinedSavedUploadFlags = UploadFlags {
         uploadCheck       = combine uploadCheck,
+        uploadDoc         = combine uploadDoc,
         uploadUsername    = combine uploadUsername,
         uploadPassword    = combine uploadPassword,
         uploadPasswordCmd = combine uploadPasswordCmd,
@@ -367,6 +377,7 @@
         haddockHoogle        = combine haddockHoogle,
         haddockHtml          = combine haddockHtml,
         haddockHtmlLocation  = combine haddockHtmlLocation,
+        haddockForHackage    = combine haddockForHackage,
         haddockExecutables   = combine haddockExecutables,
         haddockTestSuites    = combine haddockTestSuites,
         haddockBenchmarks    = combine haddockBenchmarks,
@@ -481,7 +492,7 @@
 -- global installs on Windows but that no longer works on Windows Vista or 7.
 
 defaultRemoteRepo :: RemoteRepo
-defaultRemoteRepo = RemoteRepo name uri () False
+defaultRemoteRepo = RemoteRepo name uri Nothing [] 0 False
   where
     name = "hackage.haskell.org"
     uri  = URI "http:" (Just (URIAuth "" name "")) "/" "" ""
@@ -490,10 +501,6 @@
     -- but new config files can use the new url (without the /packages/archive)
     -- and avoid having to do a http redirect
 
-    -- Use this as a source for crypto credentials when finding old remote-repo
-    -- entries that match repo name and url (not only be used for generating
-    -- fresh config files).
-
 -- For the default repo we know extra information, fill this in.
 --
 -- We need this because the 'defaultRemoteRepo' above is only used for the
@@ -501,12 +508,15 @@
 -- we might have only have older info. This lets us fill that in even for old
 -- config files.
 --
+-- TODO: Once we migrate from opt-in to opt-out security for the central
+-- Hackage repository, we should enable security and specify keys and threshold
+-- for repositories that have their security setting as 'Nothing' (default).
 addInfoForKnownRepos :: RemoteRepo -> RemoteRepo
 addInfoForKnownRepos repo@RemoteRepo{ remoteRepoName = "hackage.haskell.org" } =
-    repo {
-      --remoteRepoRootKeys --TODO: when this list is empty, fill in known crypto credentials
-      remoteRepoShouldTryHttps = True
-    }
+      tryHttps
+    $ if isOldHackageURI (remoteRepoURI repo) then defaultRemoteRepo else repo
+  where
+    tryHttps r = r { remoteRepoShouldTryHttps = True }
 addInfoForKnownRepos other = other
 
 --
@@ -515,26 +525,14 @@
 
 loadConfig :: Verbosity -> Flag FilePath -> IO SavedConfig
 loadConfig verbosity configFileFlag = addBaseConf $ do
-  let sources = [
-        ("commandline option",   return . flagToMaybe $ configFileFlag),
-        ("env var CABAL_CONFIG", lookup "CABAL_CONFIG" `liftM` getEnvironment),
-        ("default config file",  Just `liftM` defaultConfigFile) ]
-
-      getSource [] = error "no config file path candidate found."
-      getSource ((msg,action): xs) =
-                        action >>= maybe (getSource xs) (return . (,) msg)
-
-  (source, configFile) <- getSource sources
+  (source, configFile) <- getConfigFilePathAndSource configFileFlag
   minp <- readConfigFile mempty configFile
   case minp of
     Nothing -> do
-      notice verbosity $ "Config file path source is " ++ source ++ "."
+      notice verbosity $ "Config file path source is " ++ sourceMsg source ++ "."
       notice verbosity $ "Config file " ++ configFile ++ " not found."
-      notice verbosity $ "Writing default configuration to " ++ configFile
-      commentConf <- commentSavedConfig
-      initialConf <- initialSavedConfig
-      writeConfigFile configFile commentConf initialConf
-      return initialConf
+      createDefaultConfigFile verbosity configFile
+      loadConfig verbosity configFileFlag
     Just (ParseOk ws conf) -> do
       unless (null ws) $ warn verbosity $
         unlines (map (showPWarning configFile) ws)
@@ -551,6 +549,32 @@
       extra <- body
       return (base `mappend` extra)
 
+    sourceMsg CommandlineOption =   "commandline option"
+    sourceMsg EnvironmentVariable = "env var CABAL_CONFIG"
+    sourceMsg Default =             "default config file"
+
+data ConfigFileSource = CommandlineOption
+                      | EnvironmentVariable
+                      | Default
+
+-- | Returns the config file path, without checking that the file exists.
+-- The order of precedence is: input flag, CABAL_CONFIG, default location.
+getConfigFilePath :: Flag FilePath -> IO FilePath
+getConfigFilePath = fmap snd . getConfigFilePathAndSource
+
+getConfigFilePathAndSource :: Flag FilePath -> IO (ConfigFileSource, FilePath)
+getConfigFilePathAndSource configFileFlag =
+    getSource sources
+  where
+    sources =
+      [ (CommandlineOption,   return . flagToMaybe $ configFileFlag)
+      , (EnvironmentVariable, lookup "CABAL_CONFIG" `liftM` getEnvironment)
+      , (Default,             Just `liftM` defaultConfigFile) ]
+
+    getSource [] = error "no config file path candidate found."
+    getSource ((source,action): xs) =
+                      action >>= maybe (getSource xs) (return . (,) source)
+
 readConfigFile :: SavedConfig -> FilePath -> IO (Maybe (ParseResult SavedConfig))
 readConfigFile initial file = handleNotExists $
   fmap (Just . parseConfig (ConstraintSourceMainConfig file) initial)
@@ -562,6 +586,13 @@
         then return Nothing
         else ioError ioe
 
+createDefaultConfigFile :: Verbosity -> FilePath -> IO ()
+createDefaultConfigFile verbosity filePath = do
+  commentConf <- commentSavedConfig
+  initialConf <- initialSavedConfig
+  notice verbosity $ "Writing default configuration to " ++ filePath
+  writeConfigFile filePath commentConf initialConf
+
 writeConfigFile :: FilePath -> SavedConfig -> SavedConfig -> IO ()
 writeConfigFile file comments vals = do
   let tmpFile = file <.> "tmp"
@@ -598,7 +629,8 @@
     savedInstallFlags      = defaultInstallFlags,
     savedConfigureExFlags  = defaultConfigExFlags,
     savedConfigureFlags    = (defaultConfigFlags defaultProgramConfiguration) {
-      configUserInstall    = toFlag defaultUserInstall
+      configUserInstall    = toFlag defaultUserInstall,
+      configAllowNewer     = Just AllowNewerNone
     },
     savedUserInstallDirs   = fmap toFlag userInstallDirs,
     savedGlobalInstallDirs = fmap toFlag globalInstallDirs,
@@ -618,21 +650,34 @@
 
   ++ toSavedConfig liftConfigFlag
        (configureOptions ParseArgs)
-       (["builddir", "constraint", "dependency"]
+       (["builddir", "constraint", "dependency", "ipid"]
         ++ map fieldName installDirsFields)
 
-        --FIXME: this is only here because viewAsFieldDescr gives us a parser
+        -- This is only here because viewAsFieldDescr gives us a parser
         -- that only recognises 'ghc' etc, the case-sensitive flag names, not
         -- what the normal case-insensitive parser gives us.
        [simpleField "compiler"
           (fromFlagOrDefault Disp.empty . fmap Text.disp) (optional Text.parse)
           configHcFlavor (\v flags -> flags { configHcFlavor = v })
+       ,let showAllowNewer Nothing               = mempty
+            showAllowNewer (Just AllowNewerNone) = Disp.text "False"
+            showAllowNewer (Just _)              = Disp.text "True"
+
+            toAllowNewer True  = Just AllowNewerAll
+            toAllowNewer False = Just AllowNewerNone
+
+            pkgs = (Just . AllowNewerSome) `fmap` parseOptCommaList Text.parse
+            parseAllowNewer = (toAllowNewer `fmap` Text.parse) Parse.<++ pkgs in
+        simpleField "allow-newer"
+        showAllowNewer parseAllowNewer
+        configAllowNewer (\v flags -> flags { configAllowNewer = v })
         -- TODO: The following is a temporary fix. The "optimization"
         -- and "debug-info" fields are OptArg, and viewAsFieldDescr
         -- fails on that. Instead of a hand-written hackaged parser
         -- and printer, we should handle this case properly in the
         -- library.
-       ,liftField configOptimization (\v flags -> flags { configOptimization = v }) $
+       ,liftField configOptimization (\v flags ->
+                                       flags { configOptimization = v }) $
         let name = "optimization" in
         FieldDescr name
           (\f -> case f of
@@ -652,7 +697,8 @@
              where
                lstr = lowercase str
                caseWarning = PWarning $
-                 "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+                 "The '" ++ name
+                 ++ "' field is case sensitive, use 'True' or 'False'.")
        ,liftField configDebugInfo (\v flags -> flags { configDebugInfo = v }) $
         let name = "debug-info" in
         FieldDescr name
@@ -675,7 +721,8 @@
              where
                lstr = lowercase str
                caseWarning = PWarning $
-                 "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+                 "The '" ++ name
+                 ++ "' field is case sensitive, use 'True' or 'False'.")
        ]
 
   ++ toSavedConfig liftConfigExFlag
@@ -688,7 +735,7 @@
 
   ++ toSavedConfig liftUploadFlag
        (commandOptions uploadCommand ParseArgs)
-       ["verbose", "check"] []
+       ["verbose", "check", "documentation"] []
 
   ++ toSavedConfig liftReportFlag
        (commandOptions reportCommand ParseArgs)
@@ -703,8 +750,10 @@
        (configDistPref . savedConfigureFlags)
        (\distPref config ->
           config
-          { savedConfigureFlags = (savedConfigureFlags config) { configDistPref = distPref }
-          , savedHaddockFlags = (savedHaddockFlags config) { haddockDistPref = distPref }
+          { savedConfigureFlags = (savedConfigureFlags config) {
+               configDistPref = distPref }
+          , savedHaddockFlags = (savedHaddockFlags config) {
+               haddockDistPref = distPref }
           }
        )
        ParseArgs
@@ -825,7 +874,7 @@
   }
 
   where
-    isKnownSection (ParseUtils.Section _ "remote-repo" _ _)             = True
+    isKnownSection (ParseUtils.Section _ "repository" _ _)              = True
     isKnownSection (ParseUtils.F _ "remote-repo" _)                     = True
     isKnownSection (ParseUtils.Section _ "haddock" _ _)                 = True
     isKnownSection (ParseUtils.Section _ "install-dirs" _ _)            = True
@@ -837,8 +886,15 @@
                       ++ deprecatedFieldDescriptions) initial
 
     parseSections (rs, h, u, g, p, a)
-                 (ParseUtils.Section _ "remote-repo" name fs) = do
+                 (ParseUtils.Section _ "repository" name fs) = do
       r' <- parseFields remoteRepoFields (emptyRemoteRepo name) fs
+      when (remoteRepoKeyThreshold r' > length (remoteRepoRootKeys r')) $
+        warning $ "'key-threshold' for repository " ++ show (remoteRepoName r')
+               ++ " higher than number of keys"
+      when (not (null (remoteRepoRootKeys r'))
+            && remoteRepoSecure r' /= Just True) $
+        warning $ "'root-keys' for repository " ++ show (remoteRepoName r')
+               ++ " non-empty, but 'secure' not set to True."
       return (r':rs, h, u, g, p, a)
 
     parseSections (rs, h, u, g, p, a)
@@ -887,7 +943,8 @@
 
 showConfigWithComments :: SavedConfig -> SavedConfig -> String
 showConfigWithComments comment vals = Disp.render $
-      case fmap ppRemoteRepoSection . fromNubList . globalRemoteRepos . savedGlobalFlags $ vals of
+      case fmap ppRemoteRepoSection . fromNubList . globalRemoteRepos
+           . savedGlobalFlags $ vals of
         [] -> Disp.text ""
         (x:xs) -> foldl' (\ r r' -> r $+$ Disp.text "" $+$ r') x xs
   $+$ Disp.text ""
@@ -925,20 +982,43 @@
 installDirsFields = map viewAsFieldDescr installDirsOptions
 
 ppRemoteRepoSection :: RemoteRepo -> Doc
-ppRemoteRepoSection vals = ppSection "remote-repo" (remoteRepoName vals)
-        remoteRepoFields Nothing vals
+ppRemoteRepoSection vals = ppSection "repository" (remoteRepoName vals)
+        remoteRepoFields def vals
+  where
+    def = Just (emptyRemoteRepo "ignored") { remoteRepoSecure = Just False }
 
 remoteRepoFields :: [FieldDescr RemoteRepo]
 remoteRepoFields =
-    [ FieldDescr { fieldName = "url",
-                   fieldGet = text . show . remoteRepoURI,
-                   fieldSet = \ _ uriString remoteRepo -> maybe
-                          (fail $ "remote-repo: no parse on " ++ show uriString)
-                          (\ uri -> return $ remoteRepo { remoteRepoURI = uri })
-                          (parseURI uriString)
-                 }
+    [ simpleField "url"
+        (text . show)            (parseTokenQ >>= parseURI')
+        remoteRepoURI            (\x repo -> repo { remoteRepoURI = x })
+    , simpleField "secure"
+        showSecure               (Just `fmap` Text.parse)
+        remoteRepoSecure         (\x repo -> repo { remoteRepoSecure = x })
+    , listField "root-keys"
+        text                     parseTokenQ
+        remoteRepoRootKeys       (\x repo -> repo { remoteRepoRootKeys = x })
+    , simpleField "key-threshold"
+        showThreshold            Text.parse
+        remoteRepoKeyThreshold   (\x repo -> repo { remoteRepoKeyThreshold = x })
     ]
+  where
+    parseURI' uriString =
+      case parseURI uriString of
+        Nothing  -> fail $ "remote-repo: no parse on " ++ show uriString
+        Just uri -> return uri
 
+    showSecure  Nothing      = mempty       -- default 'secure' setting
+    showSecure  (Just True)  = text "True"  -- user explicitly enabled it
+    showSecure  (Just False) = text "False" -- user explicitly disabled it
+
+    -- If the key-threshold is set to 0, we omit it as this is the default
+    -- and it looks odd to have a value for key-threshold but not for 'secure'
+    -- (note that an empty list of keys is already omitted by default, since
+    -- that is what we do for all list fields)
+    showThreshold 0 = mempty
+    showThreshold t = text (show t)
+
 -- | Fields for the 'haddock' section.
 haddockFlagsFields :: [FieldDescr HaddockFlags]
 haddockFlagsFields = [ field
@@ -978,12 +1058,14 @@
 
     combine (Nothing, Just b) (Just a, Nothing) = (Just a, Just b)
     combine (Just a, Nothing) (Nothing, Just b) = (Just a, Just b)
-    combine x y = error $ "Can't happen : userConfigDiff " ++ show x ++ " " ++ show y
+    combine x y = error $ "Can't happen : userConfigDiff "
+                  ++ show x ++ " " ++ show y
 
     createDiff :: [String] -> (String, (Maybe String, Maybe String)) -> [String]
     createDiff acc (key, (Just a, Just b))
         | a == b = acc
-        | otherwise = ("+ " ++ key ++ ": " ++ b) : ("- " ++ key ++ ": " ++ a) : acc
+        | otherwise = ("+ " ++ key ++ ": " ++ b)
+                      : ("- " ++ key ++ ": " ++ a) : acc
     createDiff acc (key, (Nothing, Just b)) = ("+ " ++ key ++ ": " ++ b) : acc
     createDiff acc (key, (Just a, Nothing)) = ("- " ++ key ++ ": " ++ a) : acc
     createDiff acc (_, (Nothing, Nothing)) = acc
@@ -1012,7 +1094,7 @@
   userConfig <- loadConfig normal (globalConfigFile globalFlags)
   newConfig <- liftM2 mappend baseSavedConfig initialSavedConfig
   commentConf <- commentSavedConfig
-  cabalFile <- defaultConfigFile
+  cabalFile <- getConfigFilePath $ globalConfigFile globalFlags
   let backup = cabalFile ++ ".backup"
   notice verbosity $ "Renaming " ++ cabalFile ++ " to " ++ backup ++ "."
   renameFile cabalFile backup
diff --git a/cabal/cabal-install/Distribution/Client/Configure.hs b/cabal/cabal-install/Distribution/Client/Configure.hs
--- a/cabal/cabal-install/Distribution/Client/Configure.hs
+++ b/cabal/cabal-install/Distribution/Client/Configure.hs
@@ -20,15 +20,17 @@
 
 import Distribution.Client.Dependency
 import Distribution.Client.Dependency.Types
-         ( AllowNewer(..), isAllowNewer, ConstraintSource(..)
+         ( ConstraintSource(..)
          , LabeledPackageConstraint(..), showConstraintSource )
 import qualified Distribution.Client.InstallPlan as InstallPlan
 import Distribution.Client.InstallPlan (InstallPlan)
 import Distribution.Client.IndexUtils as IndexUtils
          ( getSourcePackages, getInstalledPackages )
 import Distribution.Client.PackageIndex ( PackageIndex, elemByPackageName )
+import Distribution.Client.PkgConfigDb (PkgConfigDb, readPkgConfigDb)
 import Distribution.Client.Setup
-         ( ConfigExFlags(..), configureCommand, filterConfigureFlags )
+         ( ConfigExFlags(..), configureCommand, filterConfigureFlags
+         , RepoContext(..) )
 import Distribution.Client.Types as Source
 import Distribution.Client.SetupWrapper
          ( setupWrapper, SetupScriptOptions(..), defaultSetupScriptOptions )
@@ -42,14 +44,14 @@
          ( Compiler, CompilerInfo, compilerInfo, PackageDB(..), PackageDBStack )
 import Distribution.Simple.Program (ProgramConfiguration )
 import Distribution.Simple.Setup
-         ( ConfigFlags(..), fromFlag, toFlag, flagToMaybe, fromFlagOrDefault )
+         ( ConfigFlags(..), AllowNewer(..)
+         , fromFlag, toFlag, flagToMaybe, fromFlagOrDefault )
 import Distribution.Simple.PackageIndex
          ( InstalledPackageIndex, lookupPackageName )
 import Distribution.Simple.Utils
          ( defaultPackageDesc )
-import qualified Distribution.InstalledPackageInfo as Installed
 import Distribution.Package
-         ( Package(..), InstalledPackageId, packageName
+         ( Package(..), UnitId, packageName
          , Dependency(..), thisPackageVersion
          )
 import qualified Distribution.PackageDescription as PkgDesc
@@ -60,7 +62,9 @@
 import Distribution.Version
          ( anyVersion, thisVersion )
 import Distribution.Simple.Utils as Utils
-         ( warn, notice, info, debug, die )
+         ( warn, notice, debug, die )
+import Distribution.Simple.Setup
+         ( isAllowNewer )
 import Distribution.System
          ( Platform )
 import Distribution.Text ( display )
@@ -77,14 +81,14 @@
 
 -- | Choose the Cabal version such that the setup scripts compiled against this
 -- version will support the given command-line flags.
-chooseCabalVersion :: ConfigExFlags -> Maybe Version -> VersionRange
-chooseCabalVersion configExFlags maybeVersion =
+chooseCabalVersion :: ConfigFlags -> Maybe Version -> VersionRange
+chooseCabalVersion configFlags maybeVersion =
   maybe defaultVersionRange thisVersion maybeVersion
   where
     -- Cabal < 1.19.2 doesn't support '--exact-configuration' which is needed
     -- for '--allow-newer' to work.
-    allowNewer = fromFlagOrDefault False $
-                 fmap isAllowNewer (configAllowNewer configExFlags)
+    allowNewer = isAllowNewer
+                 (fromMaybe AllowNewerNone $ configAllowNewer configFlags)
 
     defaultVersionRange = if allowNewer
                           then orLaterVersion (Version [1,19,2] [])
@@ -93,7 +97,7 @@
 -- | Configure the package found in the local directory
 configure :: Verbosity
           -> PackageDBStack
-          -> [Repo]
+          -> RepoContext
           -> Compiler
           -> Platform
           -> ProgramConfiguration
@@ -101,24 +105,26 @@
           -> ConfigExFlags
           -> [String]
           -> IO ()
-configure verbosity packageDBs repos comp platform conf
+configure verbosity packageDBs repoCtxt comp platform conf
   configFlags configExFlags extraArgs = do
 
   installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-  sourcePkgDb       <- getSourcePackages    verbosity repos
+  sourcePkgDb       <- getSourcePackages    verbosity repoCtxt
+  pkgConfigDb       <- readPkgConfigDb      verbosity conf
+
   checkConfigExFlags verbosity installedPkgIndex
                      (packageIndex sourcePkgDb) configExFlags
 
   progress <- planLocalPackage verbosity comp platform configFlags configExFlags
-                               installedPkgIndex sourcePkgDb
+                               installedPkgIndex sourcePkgDb pkgConfigDb
 
   notice verbosity "Resolving dependencies..."
   maybePlan <- foldProgress logMsg (return . Left) (return . Right)
                             progress
   case maybePlan of
     Left message -> do
-      info verbosity $
-           "Warning: solver failed to find a solution:\n"
+      warn verbosity $
+           "solver failed to find a solution:\n"
         ++ message
         ++ "Trying configure anyway."
       setupWrapper verbosity (setupScriptOptions installedPkgIndex Nothing)
@@ -127,8 +133,7 @@
     Right installPlan -> case InstallPlan.ready installPlan of
       [pkg@(ReadyPackage
              (ConfiguredPackage (SourcePackage _ _ (LocalUnpackedPackage _) _)
-                                 _ _ _)
-             _)] -> do
+                                 _ _ _))] -> do
         configurePackage verbosity
           platform (compilerInfo comp)
           (setupScriptOptions installedPkgIndex (Just pkg))
@@ -151,7 +156,7 @@
            (useDistPref defaultSetupScriptOptions)
            (configDistPref configFlags))
         (chooseCabalVersion
-           configExFlags
+           configFlags
            (flagToMaybe (configCabalVersion configExFlags)))
         Nothing
         False
@@ -181,6 +186,7 @@
                      mpkg
   = SetupScriptOptions {
       useCabalVersion   = cabalVersion
+    , useCabalSpecVersion = Nothing
     , useCompiler       = Just comp
     , usePlatform       = Just platform
     , usePackageDB      = packageDBs'
@@ -200,6 +206,7 @@
       -- Therefore, for now, we just leave this blank.
     , useDependencies          = fromMaybe [] explicitSetupDeps
     , useDependenciesExclusive = isJust explicitSetupDeps
+    , useVersionMacros         = isJust explicitSetupDeps
     }
   where
     -- When we are compiling a legacy setup script without an explicit
@@ -217,17 +224,15 @@
         -- but if the user is using an odd db stack, don't touch it
         _otherwise -> (packageDBs, Just index)
 
-    explicitSetupDeps :: Maybe [(InstalledPackageId, PackageId)]
+    explicitSetupDeps :: Maybe [(UnitId, PackageId)]
     explicitSetupDeps = do
-      ReadyPackage (ConfiguredPackage (SourcePackage _ gpkg _ _) _ _ _) deps
-                 <- mpkg
+      ReadyPackage cpkg <- mpkg
+      let gpkg = packageDescription (confPkgSource cpkg)
       -- Check if there is an explicit setup stanza
       _buildInfo <- PkgDesc.setupBuildInfo (PkgDesc.packageDescription gpkg)
       -- Return the setup dependencies computed by the solver
-      return [ ( Installed.installedPackageId deppkg
-               , Installed.sourcePackageId    deppkg
-               )
-             | deppkg <- CD.setupDeps deps
+      return [ ( uid, srcid )
+             | ConfiguredId srcid uid <- CD.setupDeps (confPkgDeps cpkg)
              ]
 
 -- | Warn if any constraints or preferences name packages that are not in the
@@ -263,10 +268,10 @@
                  -> ConfigFlags -> ConfigExFlags
                  -> InstalledPackageIndex
                  -> SourcePackageDb
+                 -> PkgConfigDb
                  -> IO (Progress String String InstallPlan)
 planLocalPackage verbosity comp platform configFlags configExFlags
-  installedPkgIndex
-  (SourcePackageDb _ packagePrefs) = do
+  installedPkgIndex (SourcePackageDb _ packagePrefs) pkgConfigDb = do
   pkg <- readPackageDescription verbosity =<< defaultPackageDesc verbosity
   solver <- chooseSolver verbosity (fromFlag $ configSolver configExFlags)
             (compilerInfo comp)
@@ -284,8 +289,8 @@
         fromFlagOrDefault False $ configBenchmarks configFlags
 
       resolverParams =
-          removeUpperBounds (fromFlagOrDefault AllowNewerNone $
-                             configAllowNewer configExFlags)
+          removeUpperBounds
+          (fromMaybe AllowNewerNone $ configAllowNewer configFlags)
 
         . addPreferences
             -- preferences from the config file or command line
@@ -320,7 +325,7 @@
             (SourcePackageDb mempty packagePrefs)
             [SpecificSourcePackage localPkg]
 
-  return (resolveDependencies platform (compilerInfo comp) solver resolverParams)
+  return (resolveDependencies platform (compilerInfo comp) pkgConfigDb solver resolverParams)
 
 
 -- | Call an installer for an 'SourcePackage' but override the configure
@@ -339,25 +344,23 @@
                  -> [String]
                  -> IO ()
 configurePackage verbosity platform comp scriptOptions configFlags
-                 (ReadyPackage (ConfiguredPackage (SourcePackage _ gpkg _ _)
-                                                  flags stanzas _)
-                               deps)
+                 (ReadyPackage (ConfiguredPackage spkg flags stanzas deps))
                  extraArgs =
 
   setupWrapper verbosity
     scriptOptions (Just pkg) configureCommand configureFlags extraArgs
 
   where
+    gpkg = packageDescription spkg
     configureFlags   = filterConfigureFlags configFlags {
       configConfigurationsFlags = flags,
       -- We generate the legacy constraints as well as the new style precise
       -- deps.  In the end only one set gets passed to Setup.hs configure,
       -- depending on the Cabal version we are talking to.
-      configConstraints  = [ thisPackageVersion (packageId deppkg)
-                           | deppkg <- CD.nonSetupDeps deps ],
-      configDependencies = [ (packageName (Installed.sourcePackageId deppkg),
-                              Installed.installedPackageId deppkg)
-                           | deppkg <- CD.nonSetupDeps deps ],
+      configConstraints  = [ thisPackageVersion srcid
+                           | ConfiguredId srcid _uid <- CD.nonSetupDeps deps ],
+      configDependencies = [ (packageName srcid, uid)
+                           | ConfiguredId srcid uid <- CD.nonSetupDeps deps ],
       -- Use '--exact-configuration' if supported.
       configExactConfiguration = toFlag True,
       configVerbosity          = toFlag verbosity,
diff --git a/cabal/cabal-install/Distribution/Client/Dependency.hs b/cabal/cabal-install/Distribution/Client/Dependency.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency.hs
@@ -53,10 +53,11 @@
     setStrongFlags,
     setMaxBackjumps,
     addSourcePackages,
-    hideInstalledPackagesSpecificByInstalledPackageId,
+    hideInstalledPackagesSpecificByUnitId,
     hideInstalledPackagesSpecificBySourcePackageId,
     hideInstalledPackagesAllVersions,
-    removeUpperBounds
+    removeUpperBounds,
+    addDefaultSetupDependencies,
   ) where
 
 import Distribution.Client.Dependency.TopDown
@@ -68,15 +69,18 @@
 import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex
 import qualified Distribution.Client.InstallPlan as InstallPlan
 import Distribution.Client.InstallPlan (InstallPlan)
+import Distribution.Client.PkgConfigDb (PkgConfigDb)
 import Distribution.Client.Types
          ( SourcePackageDb(SourcePackageDb), SourcePackage(..)
-         , ConfiguredPackage(..), ConfiguredId(..), enableStanzas )
+         , ConfiguredPackage(..), ConfiguredId(..)
+         , UnresolvedPkgLoc, UnresolvedSourcePackage
+         , OptionalStanza(..), enableStanzas )
 import Distribution.Client.Dependency.Types
          ( PreSolver(..), Solver(..), DependencyResolver, ResolverPackage(..)
          , PackageConstraint(..), showPackageConstraint
          , LabeledPackageConstraint(..), unlabelPackageConstraint
          , ConstraintSource(..), showConstraintSource
-         , AllowNewer(..), PackagePreferences(..), InstalledPreference(..)
+         , PackagePreferences(..), InstalledPreference(..)
          , PackagesPreferenceDefault(..)
          , Progress(..), foldProgress )
 import Distribution.Client.Sandbox.Types
@@ -88,20 +92,18 @@
 import Distribution.Package
          ( PackageName(..), PackageIdentifier(PackageIdentifier), PackageId
          , Package(..), packageName, packageVersion
-         , InstalledPackageId, Dependency(Dependency))
+         , UnitId, Dependency(Dependency))
 import qualified Distribution.PackageDescription as PD
-         ( PackageDescription(..), Library(..), Executable(..)
-         , TestSuite(..), Benchmark(..), SetupBuildInfo(..)
-         , GenericPackageDescription(..), CondTree
+         ( PackageDescription(..), SetupBuildInfo(..)
+         , GenericPackageDescription(..)
          , Flag(flagName), FlagName(..) )
-import Distribution.PackageDescription (BuildInfo(targetBuildDepends))
 import Distribution.PackageDescription.Configuration
-         ( mapCondTree, finalizePackageDescription )
+         ( finalizePackageDescription )
 import Distribution.Client.PackageUtils
          ( externalBuildDepends )
 import Distribution.Version
          ( VersionRange, anyVersion, thisVersion, withinRange
-         , removeUpperBound, simplifyVersionRange )
+         , simplifyVersionRange )
 import Distribution.Compiler
          ( CompilerInfo(..) )
 import Distribution.System
@@ -110,13 +112,17 @@
          ( duplicates, duplicatesBy, mergeBy, MergeResult(..) )
 import Distribution.Simple.Utils
          ( comparing, warn, info )
+import Distribution.Simple.Configure
+         ( relaxPackageDeps )
+import Distribution.Simple.Setup
+         ( AllowNewer(..) )
 import Distribution.Text
          ( display )
 import Distribution.Verbosity
          ( Verbosity )
 
 import Data.List
-         ( foldl', sort, sortBy, nubBy, maximumBy, intercalate )
+         ( foldl', sort, sortBy, nubBy, maximumBy, intercalate, nub )
 import Data.Function (on)
 import Data.Maybe (fromMaybe)
 import qualified Data.Map as Map
@@ -140,7 +146,7 @@
        depResolverPreferences       :: [PackagePreference],
        depResolverPreferenceDefault :: PackagesPreferenceDefault,
        depResolverInstalledPkgIndex :: InstalledPackageIndex,
-       depResolverSourcePkgIndex    :: PackageIndex.PackageIndex SourcePackage,
+       depResolverSourcePkgIndex    :: PackageIndex.PackageIndex UnresolvedSourcePackage,
        depResolverReorderGoals      :: Bool,
        depResolverIndependentGoals  :: Bool,
        depResolverAvoidReinstalls   :: Bool,
@@ -158,7 +164,14 @@
   ++ "\npreferences: "
   ++   concatMap (("\n  " ++) . showPackagePreference)
        (depResolverPreferences p)
-  ++ "\nstrategy: " ++ show (depResolverPreferenceDefault p)
+  ++ "\nstrategy: "          ++ show (depResolverPreferenceDefault p)
+  ++ "\nreorder goals: "     ++ show (depResolverReorderGoals      p)
+  ++ "\nindependent goals: " ++ show (depResolverIndependentGoals  p)
+  ++ "\navoid reinstalls: "  ++ show (depResolverAvoidReinstalls   p)
+  ++ "\nshadow packages: "   ++ show (depResolverShadowPkgs        p)
+  ++ "\nstrong flags: "      ++ show (depResolverStrongFlags       p)
+  ++ "\nmax backjumps: "     ++ maybe "infinite" show
+                                     (depResolverMaxBackjumps      p)
   where
     showLabeledConstraint :: LabeledPackageConstraint -> String
     showLabeledConstraint (LabeledPackageConstraint pc src) =
@@ -178,6 +191,11 @@
      -- | If we prefer versions of packages that are already installed.
    | PackageInstalledPreference PackageName InstalledPreference
 
+     -- | If we would prefer to enable these optional stanzas
+     -- (i.e. test suites and/or benchmarks)
+   | PackageStanzasPreference   PackageName [OptionalStanza]
+
+
 -- | Provide a textual representation of a package preference
 -- for debugging purposes.
 --
@@ -186,9 +204,11 @@
   display pn ++ " " ++ display (simplifyVersionRange vr)
 showPackagePreference (PackageInstalledPreference pn ip) =
   display pn ++ " " ++ show ip
+showPackagePreference (PackageStanzasPreference pn st) =
+  display pn ++ " " ++ show st
 
 basicDepResolverParams :: InstalledPackageIndex
-                       -> PackageIndex.PackageIndex SourcePackage
+                       -> PackageIndex.PackageIndex UnresolvedSourcePackage
                        -> DepResolverParams
 basicDepResolverParams installedPkgIndex sourcePkgIndex =
     DepResolverParams {
@@ -282,7 +302,7 @@
       [ LabeledPackageConstraint
         (PackageConstraintInstalled pkgname)
         ConstraintSourceNonUpgradeablePackage
-      | all (/=PackageName "base") (depResolverTargets params)
+      | notElem (PackageName "base") (depResolverTargets params)
       , pkgname <- map PackageName [ "base", "ghc-prim", "integer-gmp"
                                    , "integer-simple" ]
       , isInstalled pkgname ]
@@ -293,7 +313,7 @@
                 . InstalledPackageIndex.lookupPackageName
                                  (depResolverInstalledPkgIndex params)
 
-addSourcePackages :: [SourcePackage]
+addSourcePackages :: [UnresolvedSourcePackage]
                   -> DepResolverParams -> DepResolverParams
 addSourcePackages pkgs params =
     params {
@@ -302,14 +322,14 @@
               (depResolverSourcePkgIndex params) pkgs
     }
 
-hideInstalledPackagesSpecificByInstalledPackageId :: [InstalledPackageId]
+hideInstalledPackagesSpecificByUnitId :: [UnitId]
                                                      -> DepResolverParams
                                                      -> DepResolverParams
-hideInstalledPackagesSpecificByInstalledPackageId pkgids params =
+hideInstalledPackagesSpecificByUnitId pkgids params =
     --TODO: this should work using exclude constraints instead
     params {
       depResolverInstalledPkgIndex =
-        foldl' (flip InstalledPackageIndex.deleteInstalledPackageId)
+        foldl' (flip InstalledPackageIndex.deleteUnitId)
                (depResolverInstalledPkgIndex params) pkgids
     }
 
@@ -337,96 +357,68 @@
 
 hideBrokenInstalledPackages :: DepResolverParams -> DepResolverParams
 hideBrokenInstalledPackages params =
-    hideInstalledPackagesSpecificByInstalledPackageId pkgids params
+    hideInstalledPackagesSpecificByUnitId pkgids params
   where
-    pkgids = map Installed.installedPackageId
+    pkgids = map Installed.installedUnitId
            . InstalledPackageIndex.reverseDependencyClosure
                             (depResolverInstalledPkgIndex params)
-           . map (Installed.installedPackageId . fst)
+           . map (Installed.installedUnitId . fst)
            . InstalledPackageIndex.brokenPackages
            $ depResolverInstalledPkgIndex params
 
 -- | Remove upper bounds in dependencies using the policy specified by the
 -- 'AllowNewer' argument (all/some/none).
+--
+-- Note: It's important to apply 'removeUpperBounds' after
+-- 'addSourcePackages'. Otherwise, the packages inserted by
+-- 'addSourcePackages' won't have upper bounds in dependencies relaxed.
+--
 removeUpperBounds :: AllowNewer -> DepResolverParams -> DepResolverParams
-removeUpperBounds allowNewer params =
+removeUpperBounds AllowNewerNone params = params
+removeUpperBounds allowNewer     params =
     params {
-      -- NB: It's important to apply 'removeUpperBounds' after
-      -- 'addSourcePackages'. Otherwise, the packages inserted by
-      -- 'addSourcePackages' won't have upper bounds in dependencies relaxed.
-
       depResolverSourcePkgIndex = sourcePkgIndex'
     }
   where
-    sourcePkgIndex  = depResolverSourcePkgIndex params
-    sourcePkgIndex' = case allowNewer of
-      AllowNewerNone      -> sourcePkgIndex
-      AllowNewerAll       -> fmap relaxAllPackageDeps         sourcePkgIndex
-      AllowNewerSome pkgs -> fmap (relaxSomePackageDeps pkgs) sourcePkgIndex
-
-    relaxAllPackageDeps :: SourcePackage -> SourcePackage
-    relaxAllPackageDeps = onAllBuildDepends doRelax
-      where
-        doRelax (Dependency pkgName verRange) =
-          Dependency pkgName (removeUpperBound verRange)
-
-    relaxSomePackageDeps :: [PackageName] -> SourcePackage -> SourcePackage
-    relaxSomePackageDeps pkgNames = onAllBuildDepends doRelax
-      where
-        doRelax d@(Dependency pkgName verRange)
-          | pkgName `elem` pkgNames = Dependency pkgName
-                                      (removeUpperBound verRange)
-          | otherwise               = d
-
-    -- Walk a 'GenericPackageDescription' and apply 'f' to all 'build-depends'
-    -- fields.
-    onAllBuildDepends :: (Dependency -> Dependency)
-                      -> SourcePackage -> SourcePackage
-    onAllBuildDepends f srcPkg = srcPkg'
-      where
-        gpd        = packageDescription srcPkg
-        pd         = PD.packageDescription gpd
-        condLib    = PD.condLibrary        gpd
-        condExes   = PD.condExecutables    gpd
-        condTests  = PD.condTestSuites     gpd
-        condBenchs = PD.condBenchmarks     gpd
-
-        f' = onBuildInfo f
-        onBuildInfo g bi = bi
-          { targetBuildDepends = map g (targetBuildDepends bi) }
+    sourcePkgIndex' = fmap relaxDeps $ depResolverSourcePkgIndex params
 
-        onLibrary    lib  = lib { PD.libBuildInfo  = f' $ PD.libBuildInfo  lib }
-        onExecutable exe  = exe { PD.buildInfo     = f' $ PD.buildInfo     exe }
-        onTestSuite  tst  = tst { PD.testBuildInfo = f' $ PD.testBuildInfo tst }
-        onBenchmark  bmk  = bmk { PD.benchmarkBuildInfo =
-                                     f' $ PD.benchmarkBuildInfo bmk }
+    relaxDeps :: UnresolvedSourcePackage -> UnresolvedSourcePackage
+    relaxDeps srcPkg = srcPkg {
+      packageDescription = relaxPackageDeps allowNewer
+                           (packageDescription srcPkg)
+      }
 
-        srcPkg' = srcPkg { packageDescription = gpd' }
-        gpd'    = gpd {
-          PD.packageDescription = pd',
-          PD.condLibrary        = condLib',
-          PD.condExecutables    = condExes',
-          PD.condTestSuites     = condTests',
-          PD.condBenchmarks     = condBenchs'
-          }
-        pd' = pd {
-          PD.buildDepends = map  f            (PD.buildDepends pd),
-          PD.library      = fmap onLibrary    (PD.library pd),
-          PD.executables  = map  onExecutable (PD.executables pd),
-          PD.testSuites   = map  onTestSuite  (PD.testSuites pd),
-          PD.benchmarks   = map  onBenchmark  (PD.benchmarks pd)
+-- | Supply defaults for packages without explicit Setup dependencies
+--
+-- Note: It's important to apply 'addDefaultSetupDepends' after
+-- 'addSourcePackages'. Otherwise, the packages inserted by
+-- 'addSourcePackages' won't have upper bounds in dependencies relaxed.
+--
+addDefaultSetupDependencies :: (UnresolvedSourcePackage -> [Dependency])
+                            -> DepResolverParams -> DepResolverParams
+addDefaultSetupDependencies defaultSetupDeps params =
+    params {
+      depResolverSourcePkgIndex =
+        fmap applyDefaultSetupDeps (depResolverSourcePkgIndex params)
+    }
+  where
+    applyDefaultSetupDeps :: UnresolvedSourcePackage -> UnresolvedSourcePackage
+    applyDefaultSetupDeps srcpkg =
+        srcpkg {
+          packageDescription = gpkgdesc {
+            PD.packageDescription = pkgdesc {
+              PD.setupBuildInfo =
+                case PD.setupBuildInfo pkgdesc of
+                  Just sbi -> Just sbi
+                  Nothing  -> Just PD.SetupBuildInfo {
+                                PD.setupDepends = defaultSetupDeps srcpkg
+                              }
+            }
           }
-        condLib'    = fmap (onCondTree onLibrary)             condLib
-        condExes'   = map  (mapSnd $ onCondTree onExecutable) condExes
-        condTests'  = map  (mapSnd $ onCondTree onTestSuite)  condTests
-        condBenchs' = map  (mapSnd $ onCondTree onBenchmark)  condBenchs
-
-        mapSnd :: (a -> b) -> (c,a) -> (c,b)
-        mapSnd = fmap
-
-        onCondTree :: (a -> b) -> PD.CondTree v [Dependency] a
-                   -> PD.CondTree v [Dependency] b
-        onCondTree g = mapCondTree g (map f) id
+        }
+      where
+        gpkgdesc = packageDescription srcpkg
+        pkgdesc  = PD.packageDescription gpkgdesc
 
 
 upgradeDependencies :: DepResolverParams -> DepResolverParams
@@ -440,7 +432,7 @@
 
 standardInstallPolicy :: InstalledPackageIndex
                       -> SourcePackageDb
-                      -> [PackageSpecifier SourcePackage]
+                      -> [PackageSpecifier UnresolvedSourcePackage]
                       -> DepResolverParams
 standardInstallPolicy
     installedPkgIndex (SourcePackageDb sourcePkgIndex sourcePkgPrefs)
@@ -521,7 +513,7 @@
         info verbosity "Choosing modular solver."
         return Modular
 
-runSolver :: Solver -> SolverConfig -> DependencyResolver
+runSolver :: Solver -> SolverConfig -> DependencyResolver UnresolvedPkgLoc
 runSolver TopDown = const topDownResolver -- TODO: warn about unsupported options
 runSolver Modular = modularResolver
 
@@ -533,25 +525,26 @@
 --
 resolveDependencies :: Platform
                     -> CompilerInfo
+                    -> PkgConfigDb
                     -> Solver
                     -> DepResolverParams
                     -> Progress String String InstallPlan
 
     --TODO: is this needed here? see dontUpgradeNonUpgradeablePackages
-resolveDependencies platform comp _solver params
+resolveDependencies platform comp _pkgConfigDB _solver params
   | null (depResolverTargets params)
   = return (validateSolverResult platform comp indGoals [])
   where
     indGoals = depResolverIndependentGoals params
 
-resolveDependencies platform comp  solver params =
+resolveDependencies platform comp pkgConfigDB solver params =
 
     Step (showDepResolverParams finalparams)
   $ fmap (validateSolverResult platform comp indGoals)
   $ runSolver solver (SolverConfig reorderGoals indGoals noReinstalls
                       shadowing strFlags maxBkjumps)
                      platform comp installedPkgIndex sourcePkgIndex
-                     preferences constraints targets
+                     pkgConfigDB preferences constraints targets
   where
 
     finalparams @ (DepResolverParams
@@ -586,14 +579,15 @@
                             -> [PackagePreference]
                             -> (PackageName -> PackagePreferences)
 interpretPackagesPreference selected defaultPref prefs =
-  \pkgname -> PackagePreferences (versionPref pkgname) (installPref pkgname)
-
+  \pkgname -> PackagePreferences (versionPref pkgname)
+                                 (installPref pkgname)
+                                 (stanzasPref pkgname)
   where
     versionPref pkgname =
-      fromMaybe anyVersion (Map.lookup pkgname versionPrefs)
-    versionPrefs = Map.fromList
-      [ (pkgname, pref)
-      | PackageVersionPreference pkgname pref <- prefs ]
+      fromMaybe [anyVersion] (Map.lookup pkgname versionPrefs)
+    versionPrefs = Map.fromListWith (++)
+                   [(pkgname, [pref])
+                   | PackageVersionPreference pkgname pref <- prefs]
 
     installPref pkgname =
       fromMaybe (installPrefDefault pkgname) (Map.lookup pkgname installPrefs)
@@ -601,14 +595,21 @@
       [ (pkgname, pref)
       | PackageInstalledPreference pkgname pref <- prefs ]
     installPrefDefault = case defaultPref of
-      PreferAllLatest         -> \_       -> PreferLatest
-      PreferAllInstalled      -> \_       -> PreferInstalled
+      PreferAllLatest         -> const PreferLatest
+      PreferAllInstalled      -> const PreferInstalled
       PreferLatestForSelected -> \pkgname ->
         -- When you say cabal install foo, what you really mean is, prefer the
         -- latest version of foo, but the installed version of everything else
         if pkgname `Set.member` selected then PreferLatest
                                          else PreferInstalled
 
+    stanzasPref pkgname =
+      fromMaybe [] (Map.lookup pkgname stanzasPrefs)
+    stanzasPrefs = Map.fromListWith (\a b -> nub (a ++ b))
+      [ (pkgname, pref)
+      | PackageStanzasPreference pkgname pref <- prefs ]
+
+
 -- ------------------------------------------------------------
 -- * Checking the result of the solver
 -- ------------------------------------------------------------
@@ -619,7 +620,7 @@
 validateSolverResult :: Platform
                      -> CompilerInfo
                      -> Bool
-                     -> [ResolverPackage]
+                     -> [ResolverPackage UnresolvedPkgLoc]
                      -> InstallPlan
 validateSolverResult platform comp indepGoals pkgs =
     case planPackagesProblems platform comp pkgs of
@@ -637,7 +638,7 @@
     formatPkgProblems  = formatProblemMessage . map showPlanPackageProblem
     formatPlanProblems = formatProblemMessage . map InstallPlan.showPlanProblem
 
-    formatProblemMessage problems = 
+    formatProblemMessage problems =
       unlines $
         "internal error: could not construct a valid install plan."
       : "The proposed (invalid) plan contained the following problems:"
@@ -647,7 +648,7 @@
 
 
 data PlanPackageProblem =
-       InvalidConfiguredPackage ConfiguredPackage [PackageProblem]
+       InvalidConfiguredPackage (ConfiguredPackage UnresolvedPkgLoc) [PackageProblem]
 
 showPlanPackageProblem :: PlanPackageProblem -> String
 showPlanPackageProblem (InvalidConfiguredPackage pkg packageProblems) =
@@ -657,7 +658,7 @@
              | problem <- packageProblems ]
 
 planPackagesProblems :: Platform -> CompilerInfo
-                     -> [ResolverPackage]
+                     -> [ResolverPackage UnresolvedPkgLoc]
                      -> [PlanPackageProblem]
 planPackagesProblems platform cinfo pkgs =
      [ InvalidConfiguredPackage pkg packageProblems
@@ -706,7 +707,7 @@
 -- dependencies are satisfied by the specified packages.
 --
 configuredPackageProblems :: Platform -> CompilerInfo
-                          -> ConfiguredPackage -> [PackageProblem]
+                          -> ConfiguredPackage UnresolvedPkgLoc -> [PackageProblem]
 configuredPackageProblems platform cinfo
   (ConfiguredPackage pkg specifiedFlags stanzas specifiedDeps') =
      [ DuplicateFlag flag | ((flag,_):_) <- duplicates specifiedFlags ]
@@ -787,14 +788,14 @@
 -- It simply means preferences for installed packages will be ignored.
 --
 resolveWithoutDependencies :: DepResolverParams
-                           -> Either [ResolveNoDepsError] [SourcePackage]
+                           -> Either [ResolveNoDepsError] [UnresolvedSourcePackage]
 resolveWithoutDependencies (DepResolverParams targets constraints
                               prefs defpref installedPkgIndex sourcePkgIndex
                               _reorderGoals _indGoals _avoidReinstalls
                               _shadowing _strFlags _maxBjumps) =
     collectEithers (map selectPackage targets)
   where
-    selectPackage :: PackageName -> Either ResolveNoDepsError SourcePackage
+    selectPackage :: PackageName -> Either ResolveNoDepsError UnresolvedSourcePackage
     selectPackage pkgname
       | null choices = Left  $! ResolveUnsatisfiable pkgname requiredVersions
       | otherwise    = Right $! maximumBy bestByPrefs choices
@@ -807,7 +808,7 @@
                                                          pkgDependency
 
         -- Preferences
-        PackagePreferences preferredVersions preferInstalled
+        PackagePreferences preferredVersions preferInstalled _
           = packagePreferences pkgname
 
         bestByPrefs   = comparing $ \pkg ->
@@ -818,7 +819,8 @@
                            . InstalledPackageIndex.lookupSourcePackageId
                                                      installedPkgIndex
                            . packageId
-        versionPref   pkg = packageVersion pkg `withinRange` preferredVersions
+        versionPref pkg = length . filter (packageVersion pkg `withinRange`) $
+                          preferredVersions
 
     packageConstraints :: PackageName -> VersionRange
     packageConstraints pkgname =
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular.hs
@@ -12,9 +12,7 @@
 import Data.Map as M
          ( fromListWith )
 import Distribution.Client.Dependency.Modular.Assignment
-         ( Assignment, toCPs )
-import Distribution.Client.Dependency.Modular.Dependency
-         ( RevDepMap )
+         ( toCPs )
 import Distribution.Client.Dependency.Modular.ConfiguredConversion
          ( convCP )
 import Distribution.Client.Dependency.Modular.IndexConversion
@@ -26,18 +24,18 @@
 import Distribution.Client.Dependency.Modular.Solver
          ( SolverConfig(..), solve )
 import Distribution.Client.Dependency.Types
-         ( DependencyResolver, ResolverPackage
+         ( DependencyResolver
          , PackageConstraint(..), unlabelPackageConstraint )
 import Distribution.System
          ( Platform(..) )
 
 -- | Ties the two worlds together: classic cabal-install vs. the modular
 -- solver. Performs the necessary translations before and after.
-modularResolver :: SolverConfig -> DependencyResolver
-modularResolver sc (Platform arch os) cinfo iidx sidx pprefs pcs pns =
+modularResolver :: SolverConfig -> DependencyResolver loc
+modularResolver sc (Platform arch os) cinfo iidx sidx pkgConfigDB pprefs pcs pns =
   fmap (uncurry postprocess)      $ -- convert install plan
   logToProgress (maxBackjumps sc) $ -- convert log format into progress format
-  solve sc idx pprefs gcs pns
+  solve sc cinfo idx pkgConfigDB pprefs gcs pns
     where
       -- Indices have to be converted into solver-specific uniform index.
       idx    = convPIs os arch cinfo (shadowPkgs sc) (strongFlags sc) iidx sidx
@@ -47,7 +45,6 @@
           pair lpc = (pcName $ unlabelPackageConstraint lpc, [lpc])
 
       -- Results have to be converted into an install plan.
-      postprocess :: Assignment -> RevDepMap -> [ResolverPackage]
       postprocess a rdm = map (convCP iidx sidx) (toCPs a rdm)
 
       -- Helper function to extract the PN from a constraint.
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
@@ -1,4 +1,11 @@
-module Distribution.Client.Dependency.Modular.Assignment where
+module Distribution.Client.Dependency.Modular.Assignment
+    ( Assignment(..)
+    , FAssignment
+    , SAssignment
+    , PreAssignment(..)
+    , extend
+    , toCPs
+    ) where
 
 import Control.Applicative
 import Control.Monad
@@ -8,6 +15,8 @@
 import Data.Maybe
 import Prelude hiding (pi)
 
+import Language.Haskell.Extension (Extension, Language)
+
 import Distribution.PackageDescription (FlagAssignment) -- from Cabal
 import Distribution.Client.Types (OptionalStanza)
 import Distribution.Client.Utils.LabeledGraph
@@ -17,7 +26,6 @@
 import Distribution.Client.Dependency.Modular.Configured
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Flag
-import Distribution.Client.Dependency.Modular.Index
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Version
 
@@ -53,14 +61,31 @@
 --
 -- Either returns a witness of the conflict that would arise during the merge,
 -- or the successfully extended assignment.
-extend :: Var QPN -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
-extend var pa qa = foldM (\ a (Dep qpn ci) ->
-                     let ci' = M.findWithDefault (Constrained []) qpn a
-                     in  case (\ x -> M.insert qpn x a) <$> merge ci' ci of
-                           Left (c, (d, d')) -> Left  (c, L.map (Dep qpn) (simplify (P qpn) d d'))
-                           Right x           -> Right x)
-                    pa qa
+extend :: (Extension -> Bool) -- ^ is a given extension supported
+       -> (Language  -> Bool) -- ^ is a given language supported
+       -> (PN -> VR  -> Bool) -- ^ is a given pkg-config requirement satisfiable
+       -> Goal QPN
+       -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
+extend extSupported langSupported pkgPresent goal@(Goal var _) = foldM extendSingle
   where
+
+    extendSingle :: PPreAssignment -> Dep QPN
+                 -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
+    extendSingle a (Ext  ext )  =
+      if extSupported  ext  then Right a
+                            else Left (toConflictSet goal, [Ext ext])
+    extendSingle a (Lang lang)  =
+      if langSupported lang then Right a
+                            else Left (toConflictSet goal, [Lang lang])
+    extendSingle a (Pkg pn vr)  =
+      if pkgPresent pn vr then Right a
+                          else Left (toConflictSet goal, [Pkg pn vr])
+    extendSingle a (Dep qpn ci) =
+      let ci' = M.findWithDefault (Constrained []) qpn a
+      in  case (\ x -> M.insert qpn x a) <$> merge ci' ci of
+            Left (c, (d, d')) -> Left  (c, L.map (Dep qpn) (simplify (P qpn) d d'))
+            Right x           -> Right x
+
     -- We're trying to remove trivial elements of the conflict. If we're just
     -- making a choice pkg == instance, and pkg => pkg == instance is a part
     -- of the conflict, then this info is clear from the context and does not
@@ -123,37 +148,3 @@
                                  (M.findWithDefault [] qpn sapp)
                                  (depp' qpn))
           ps
-
--- | Finalize an assignment and a reverse dependency map.
---
--- This is preliminary, and geared towards output right now.
-finalize :: Index -> Assignment -> RevDepMap -> IO ()
-finalize idx (A pa fa _) rdm =
-  let
-    -- get hold of the graph
-    g  :: Graph Component
-    vm :: Vertex -> ((), QPN, [(Component, QPN)])
-    (g, vm) = graphFromEdges' (L.map (\ (x, xs) -> ((), x, xs)) (M.toList rdm))
-    -- topsort the dependency graph, yielding a list of pkgs in the right order
-    f :: [PI QPN]
-    f = L.filter (not . instPI) (L.map ((\ (_, x, _) -> PI x (pa M.! x)) . vm) (topSort g))
-    fapp :: Map QPN [(QFN, Bool)] -- flags per package
-    fapp = M.fromListWith (++) $
-           L.map (\ (qfn@(FN (PI qpn _) _), b) -> (qpn, [(qfn, b)])) $ M.toList $ fa
-    -- print one instance
-    ppi pi@(PI qpn _) = showPI pi ++ status pi ++ " " ++ pflags (M.findWithDefault [] qpn fapp)
-    -- print install status
-    status :: PI QPN -> String
-    status (PI (Q _ pn) _) =
-      case insts of
-        [] -> " (new)"
-        vs -> " (" ++ intercalate ", " (L.map showVer vs) ++ ")"
-      where insts = L.map (\ (I v _) -> v) $ L.filter isInstalled $
-                    M.keys (M.findWithDefault M.empty pn idx)
-            isInstalled (I _ (Inst _ )) = True
-            isInstalled _               = False
-    -- print flag assignment
-    pflags = unwords . L.map (uncurry showFBool)
-  in
-    -- show packages with associated flag assignments
-    putStr (unlines (L.map ppi f))
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
@@ -24,7 +24,8 @@
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Index
 import Distribution.Client.Dependency.Modular.Package
-import Distribution.Client.Dependency.Modular.PSQ as P
+import Distribution.Client.Dependency.Modular.PSQ (PSQ)
+import qualified Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
 
 import Distribution.Client.ComponentDeps (Component)
@@ -58,8 +59,11 @@
       | qpn `M.member` g  = go (M.adjust ((c, qpn'):) qpn g)              o  ngs
       | otherwise         = go (M.insert qpn [(c, qpn')]  g) (cons' ng () o) ngs
           -- code above is correct; insert/adjust have different arg order
+    go g o (   (OpenGoal (Simple (Ext _ext ) _) _gr) : ngs) = go g o ngs
+    go g o (   (OpenGoal (Simple (Lang _lang)_) _gr) : ngs) = go g o ngs
+    go g o (   (OpenGoal (Simple (Pkg _pn _vr)_) _gr) : ngs)= go g o ngs
 
-    cons' = cons . forgetCompOpenGoal
+    cons' = P.cons . forgetCompOpenGoal
 
 -- | Given the current scope, qualify all the package names in the given set of
 -- dependencies and then extend the set of open goals accordingly.
@@ -114,6 +118,12 @@
     --
     -- For a package, we look up the instances available in the global info,
     -- and then handle each instance in turn.
+    go    (BS { index = _  , next = OneGoal (OpenGoal (Simple (Ext _             ) _) _ ) }) =
+      error "Distribution.Client.Dependency.Modular.Builder: build.go called with Ext goal"
+    go    (BS { index = _  , next = OneGoal (OpenGoal (Simple (Lang _            ) _) _ ) }) =
+      error "Distribution.Client.Dependency.Modular.Builder: build.go called with Lang goal"
+    go    (BS { index = _  , next = OneGoal (OpenGoal (Simple (Pkg _ _          ) _) _ ) }) =
+      error "Distribution.Client.Dependency.Modular.Builder: build.go called with Pkg goal"
     go bs@(BS { index = idx, next = OneGoal (OpenGoal (Simple (Dep qpn@(Q _ pn) _) _) gr) }) =
       case M.lookup pn idx of
         Nothing  -> FailF (toConflictSet (Goal (P qpn) gr)) (BuildFailureNotInIndex pn)
@@ -135,6 +145,11 @@
         reorder False = reverse
         trivial = L.null t && L.null f
 
+    -- For a stanza, we also create only two subtrees. The order is initially
+    -- False, True. This can be changed later by constraints (force enabling
+    -- the stanza by replacing the False branch with failure) or preferences
+    -- (try enabling the stanza if possible by moving the True branch first).
+
     go bs@(BS { next = OneGoal (OpenGoal (Stanza qsn@(SN (PI qpn _) _) t) gr) }) =
       SChoiceF qsn gr trivial (P.fromList
         [(False,                                                                  bs  { next = Goals }),
@@ -165,4 +180,4 @@
     topLevelGoal qpn = OpenGoal (Simple (Dep qpn (Constrained [])) ()) [UserGoal]
 
     qpns | ind       = makeIndependent igs
-         | otherwise = L.map (Q None) igs
+         | otherwise = L.map (Q (PP DefaultNamespace Unqualified)) igs
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs
@@ -1,4 +1,6 @@
-module Distribution.Client.Dependency.Modular.Configured where
+module Distribution.Client.Dependency.Modular.Configured
+    ( CP(..)
+    ) where
 
 import Distribution.PackageDescription (FlagAssignment) -- from Cabal
 import Distribution.Client.Types (OptionalStanza)
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs
@@ -1,8 +1,12 @@
-module Distribution.Client.Dependency.Modular.ConfiguredConversion where
+module Distribution.Client.Dependency.Modular.ConfiguredConversion
+    ( convCP
+    ) where
 
 import Data.Maybe
 import Prelude hiding (pi)
 
+import Distribution.Package (UnitId)
+
 import Distribution.Client.Types
 import Distribution.Client.Dependency.Types (ResolverPackage(..))
 import qualified Distribution.Client.PackageIndex as CI
@@ -13,23 +17,28 @@
 
 import Distribution.Client.ComponentDeps (ComponentDeps)
 
-
-convCP :: SI.InstalledPackageIndex -> CI.PackageIndex SourcePackage ->
-          CP QPN -> ResolverPackage
+-- | Converts from the solver specific result @CP QPN@ into
+-- a 'ResolverPackage', which can then be converted into
+-- the install plan.
+convCP :: SI.InstalledPackageIndex ->
+          CI.PackageIndex (SourcePackage loc) ->
+          CP QPN -> ResolverPackage loc
 convCP iidx sidx (CP qpi fa es ds) =
   case convPI qpi of
     Left  pi -> PreExisting
-                  (fromJust $ SI.lookupInstalledPackageId iidx pi)
+                  (fromJust $ SI.lookupUnitId iidx pi)
     Right pi -> Configured $ ConfiguredPackage
-                  (fromJust $ CI.lookupPackageId sidx pi)
+                  srcpkg
                   fa
                   es
                   ds'
+      where
+        Just srcpkg = CI.lookupPackageId sidx pi
   where
     ds' :: ComponentDeps [ConfiguredId]
     ds' = fmap (map convConfId) ds
 
-convPI :: PI QPN -> Either InstalledPackageId PackageId
+convPI :: PI QPN -> Either UnitId PackageId
 convPI (PI _ (I _ (Inst pi))) = Left pi
 convPI qpi                    = Right $ confSrcId $ convConfId qpi
 
@@ -42,4 +51,4 @@
     sourceId    = PackageIdentifier pn v
     installedId = case loc of
                     Inst pi    -> pi
-                    _otherwise -> fakeInstalledPackageId sourceId
+                    _otherwise -> fakeUnitId sourceId
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Cycles.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Cycles.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Cycles.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE CPP #-}
+module Distribution.Client.Dependency.Modular.Cycles (
+    detectCyclesPhase
+  ) where
+
+import Prelude hiding (cycle)
+import Control.Monad
+import Control.Monad.Reader
+import Data.Graph (SCC)
+import Data.Set   (Set)
+import qualified Data.Graph       as Gr
+import qualified Data.Map         as Map
+import qualified Data.Set         as Set
+import qualified Data.Traversable as T
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative ((<$>))
+#endif
+
+import Distribution.Client.Dependency.Modular.Dependency
+import Distribution.Client.Dependency.Modular.Flag
+import Distribution.Client.Dependency.Modular.Package
+import Distribution.Client.Dependency.Modular.Tree
+
+type DetectCycles = Reader (ConflictSet QPN)
+
+-- | Find and reject any solutions that are cyclic
+detectCyclesPhase :: Tree QGoalReasonChain -> Tree QGoalReasonChain
+detectCyclesPhase = (`runReader` Set.empty) .  cata go
+  where
+    -- Most cases are simple; we just need to remember which choices we made
+    go :: TreeF QGoalReasonChain (DetectCycles (Tree QGoalReasonChain)) -> DetectCycles (Tree QGoalReasonChain)
+    go (PChoiceF qpn gr     cs) = PChoice qpn gr     <$> local (extendConflictSet $ P qpn) (T.sequence cs)
+    go (FChoiceF qfn gr w m cs) = FChoice qfn gr w m <$> local (extendConflictSet $ F qfn) (T.sequence cs)
+    go (SChoiceF qsn gr w   cs) = SChoice qsn gr w   <$> local (extendConflictSet $ S qsn) (T.sequence cs)
+    go (GoalChoiceF         cs) = GoalChoice         <$>                                   (T.sequence cs)
+    go (FailF cs reason)        = return $ Fail cs reason
+
+    -- We check for cycles only if we have actually found a solution
+    -- This minimizes the number of cycle checks we do as cycles are rare
+    go (DoneF revDeps) = do
+      fullSet <- ask
+      return $ case findCycles fullSet revDeps of
+        Nothing     -> Done revDeps
+        Just relSet -> Fail relSet CyclicDependencies
+
+-- | Given the reverse dependency map from a 'Done' node in the tree, as well
+-- as the full conflict set containing all decisions that led to that 'Done'
+-- node, check if the solution is cyclic. If it is, return the conflict set
+-- containing all decisions that could potentially break the cycle.
+findCycles :: ConflictSet QPN -> RevDepMap -> Maybe (ConflictSet QPN)
+findCycles fullSet revDeps = do
+    guard $ not (null cycles)
+    return $ relevantConflictSet (Set.fromList (concat cycles)) fullSet
+  where
+    cycles :: [[QPN]]
+    cycles = [vs | Gr.CyclicSCC vs <- scc]
+
+    scc :: [SCC QPN]
+    scc = Gr.stronglyConnComp . map aux . Map.toList $ revDeps
+
+    aux :: (QPN, [(comp, QPN)]) -> (QPN, QPN, [QPN])
+    aux (fr, to) = (fr, fr, map snd to)
+
+-- | Construct the relevant conflict set given the full conflict set that
+-- lead to this decision and the set of packages involved in the cycle
+relevantConflictSet :: Set QPN -> ConflictSet QPN -> ConflictSet QPN
+relevantConflictSet cycle = Set.filter isRelevant
+  where
+    isRelevant :: Var QPN -> Bool
+    isRelevant (P qpn)                  = qpn `Set.member` cycle
+    isRelevant (F (FN (PI qpn _i) _fn)) = qpn `Set.member` cycle
+    isRelevant (S (SN (PI qpn _i) _sn)) = qpn `Set.member` cycle
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
@@ -4,20 +4,16 @@
     -- * Variables
     Var(..)
   , simplifyVar
-  , showVar
   , varPI
     -- * Conflict sets
   , ConflictSet
   , showCS
     -- * Constrained instances
   , CI(..)
-  , showCI
   , merge
     -- * Flagged dependencies
   , FlaggedDeps
   , FlaggedDep(..)
-  , TrueFlaggedDeps
-  , FalseFlaggedDeps
   , Dep(..)
   , showDep
   , flattenFlaggedDeps
@@ -26,39 +22,32 @@
     -- ** Setting/forgetting components
   , forgetCompOpenGoal
   , setCompFlaggedDeps
-    -- ** Selecting subsets
-  , nonSetupDeps
-  , setupDeps
-  , select
     -- * Reverse dependency map
   , RevDepMap
     -- * Goals
   , Goal(..)
   , GoalReason(..)
-  , GoalReasonChain
   , QGoalReasonChain
   , ResetGoal(..)
   , toConflictSet
-  , goalReasonToVars
-  , goalReasonChainToVars
-  , goalReasonChainsToVars
+  , extendConflictSet
     -- * Open goals
   , OpenGoal(..)
   , close
-    -- * Version ranges pairsed with origins (goals)
-  , VROrigin
-  , collapse
   ) where
 
 import Prelude hiding (pi)
 
 import Data.List (intercalate)
 import Data.Map (Map)
-import Data.Maybe (mapMaybe)
 import Data.Set (Set)
 import qualified Data.List as L
 import qualified Data.Set  as S
 
+import Language.Haskell.Extension (Extension(..), Language(..))
+
+import Distribution.Text
+
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Version
@@ -180,10 +169,15 @@
 -- | Flagged dependencies can either be plain dependency constraints,
 -- or flag-dependent dependency trees.
 data FlaggedDep comp qpn =
+    -- | Dependencies which are conditional on a flag choice.
     Flagged (FN qpn) FInfo (TrueFlaggedDeps qpn) (FalseFlaggedDeps qpn)
+    -- | Dependencies which are conditional on whether or not a stanza
+    -- (e.g., a test suite or benchmark) is enabled.
   | Stanza  (SN qpn)       (TrueFlaggedDeps qpn)
+    -- | Dependencies for which are always enabled, for the component
+    -- 'comp' (or requested for the user, if comp is @()@).
   | Simple (Dep qpn) comp
-  deriving (Eq, Show, Functor)
+  deriving (Eq, Show)
 
 -- | Conversatively flatten out flagged dependencies
 --
@@ -201,8 +195,16 @@
 
 -- | A dependency (constraint) associates a package name with a
 -- constrained instance.
-data Dep qpn = Dep qpn (CI qpn)
-  deriving (Eq, Show, Functor)
+--
+-- 'Dep' intentionally has no 'Functor' instance because the type variable
+-- is used both to record the dependencies as well as who's doing the
+-- depending; having a 'Functor' instance makes bugs where we don't distinguish
+-- these two far too likely. (By rights 'Dep' ought to have two type variables.)
+data Dep qpn = Dep  qpn (CI qpn)  -- dependency on a package
+             | Ext  Extension     -- dependency on a language extension
+             | Lang Language      -- dependency on a language version
+             | Pkg  PN VR         -- dependency on a pkg-config package
+  deriving (Eq, Show)
 
 showDep :: Dep QPN -> String
 showDep (Dep qpn (Fixed i (Goal v _))          ) =
@@ -212,6 +214,11 @@
   showVar v ++ " => " ++ showQPN qpn ++ showVR vr
 showDep (Dep qpn ci                            ) =
   showQPN qpn ++ showCI ci
+showDep (Ext ext)   = "requires " ++ display ext
+showDep (Lang lang) = "requires " ++ display lang
+showDep (Pkg pn vr) = "requires pkg-config package "
+                      ++ display pn ++ display vr
+                      ++ ", not found in the pkg-config database"
 
 -- | Options for goal qualification (used in 'qualifyDeps')
 --
@@ -230,12 +237,8 @@
 -- NOTE: It's the _dependencies_ of a package that may or may not be independent
 -- from the package itself. Package flag choices must of course be consistent.
 qualifyDeps :: QualifyOptions -> QPN -> FlaggedDeps Component PN -> FlaggedDeps Component QPN
-qualifyDeps QO{..} (Q pp' pn) = go
+qualifyDeps QO{..} (Q pp@(PP ns q) pn) = go
   where
-    -- The Base qualifier does not get inherited
-    pp :: PP
-    pp = (if qoBaseShim then stripBase else id) pp'
-
     go :: FlaggedDeps Component PN -> FlaggedDeps Component QPN
     go = map go1
 
@@ -244,17 +247,41 @@
     go1 (Stanza  sn     t)   = Stanza  (fmap (Q pp) sn)     (go t)
     go1 (Simple dep comp)    = Simple (goD dep comp) comp
 
+    -- Suppose package B has a setup dependency on package A.
+    -- This will be recorded as something like
+    --
+    -- > Dep "A" (Constrained [(AnyVersion, Goal (P "B") reason])
+    --
+    -- Observe that when we qualify this dependency, we need to turn that
+    -- @"A"@ into @"B-setup.A"@, but we should not apply that same qualifier
+    -- to the goal or the goal reason chain.
     goD :: Dep PN -> Component -> Dep QPN
-    goD dep comp
-      | qBase  dep  = fmap (Q (Base  pn pp)) dep
-      | qSetup comp = fmap (Q (Setup pn pp)) dep
-      | otherwise   = fmap (Q           pp ) dep
+    goD (Ext  ext)    _    = Ext  ext
+    goD (Lang lang)   _    = Lang lang
+    goD (Pkg pkn vr)  _    = Pkg pkn vr
+    goD (Dep  dep ci) comp
+      | qBase  dep  = Dep (Q (PP ns (Base  pn)) dep) (fmap (Q pp) ci)
+      | qSetup comp = Dep (Q (PP ns (Setup pn)) dep) (fmap (Q pp) ci)
+      | otherwise   = Dep (Q (PP ns inheritedQ) dep) (fmap (Q pp) ci)
 
+    -- If P has a setup dependency on Q, and Q has a regular dependency on R, then
+    -- we say that the 'Setup' qualifier is inherited: P has an (indirect) setup
+    -- dependency on R. We do not do this for the base qualifier however.
+    --
+    -- The inherited qualifier is only used for regular dependencies; for setup
+    -- and base deppendencies we override the existing qualifier. See #3160 for
+    -- a detailed discussion.
+    inheritedQ :: Qualifier
+    inheritedQ = case q of
+                   Setup _     -> q
+                   Unqualified -> q
+                   Base _      -> Unqualified
+
     -- Should we qualify this goal with the 'Base' package path?
-    qBase :: Dep PN -> Bool
-    qBase (Dep dep _ci) = qoBaseShim && unPackageName dep == "base"
+    qBase :: PN -> Bool
+    qBase dep = qoBaseShim && unPackageName dep == "base"
 
-    -- Should we qualify this goal with the 'Setup' packaeg path?
+    -- Should we qualify this goal with the 'Setup' package path?
     qSetup :: Component -> Bool
     qSetup comp = qoSetupIndependent && comp == ComponentSetup
 
@@ -288,60 +315,6 @@
 mapCompFlaggedDep g (Simple  pn a      ) = Simple  pn (g a)
 
 {-------------------------------------------------------------------------------
-  Selecting FlaggedDeps subsets
-
-  (Correspond to the functions with the same names in ComponentDeps).
--------------------------------------------------------------------------------}
-
-nonSetupDeps :: FlaggedDeps Component a -> FlaggedDeps Component a
-nonSetupDeps = select (/= ComponentSetup)
-
-setupDeps :: FlaggedDeps Component a -> FlaggedDeps Component a
-setupDeps = select (== ComponentSetup)
-
--- | Select the dependencies of a given component
---
--- The modular solver kind of flattens the dependency trees from the .cabal
--- file, putting the component of each dependency at the leaves, rather than
--- indexing per component. For instance, package C might have flagged deps that
--- look something like
---
--- > Flagged <flagName> ..
--- >   [Simple <package A> ComponentLib]
--- >   [Simple <package B> ComponentLib]
---
--- indicating that the library component of C relies on either A or B, depending
--- on the flag. This makes it somewhat awkward however to extract certain kinds
--- of dependencies. In particular, extracting, say, the setup dependencies from
--- the above set of dependencies could either return the empty list, or else
---
--- > Flagged <flagName> ..
--- >   []
--- >   []
---
--- Both answers are reasonable; we opt to return the empty list in this
--- case, as it results in simpler search trees in the builder.
---
--- (Note that the builder already introduces separate goals for all flags of a
--- package, independently of whether or not they are used in any component, so
--- we don't have to worry about preserving flags here.)
-select :: (Component -> Bool) -> FlaggedDeps Component a -> FlaggedDeps Component a
-select p = mapMaybe go
-  where
-    go :: FlaggedDep Component a -> Maybe (FlaggedDep Component a)
-    go (Flagged fn nfo  t f) = let t' = mapMaybe go t
-                                   f' = mapMaybe go f
-                               in if null t' && null f'
-                                     then Nothing
-                                     else Just $ Flagged fn nfo t' f'
-    go (Stanza  sn      t  ) = let t' = mapMaybe go t
-                               in if null t'
-                                     then Nothing
-                                     else Just $ Stanza  sn     t'
-    go (Simple  pn comp    ) = if p comp then Just $ Simple pn comp
-                                         else Nothing
-
-{-------------------------------------------------------------------------------
   Reverse dependency map
 -------------------------------------------------------------------------------}
 
@@ -381,15 +354,22 @@
 
 instance ResetGoal Dep where
   resetGoal g (Dep qpn ci) = Dep qpn (resetGoal g ci)
+  resetGoal _ (Ext ext)    = Ext ext
+  resetGoal _ (Lang lang)  = Lang lang
+  resetGoal _ (Pkg pn vr)  = Pkg pn vr
 
 instance ResetGoal Goal where
   resetGoal = const
 
--- | Compute a conflic set from a goal. The conflict set contains the
--- closure of goal reasons as well as the variable of the goal itself.
+-- | Compute a conflict set from a goal. The conflict set contains the closure
+-- of goal reasons as well as the variable of the goal itself.
 toConflictSet :: Ord qpn => Goal qpn -> ConflictSet qpn
 toConflictSet (Goal g grs) = S.insert (simplifyVar g) (goalReasonChainToVars grs)
 
+-- | Add another variable into a conflict set
+extendConflictSet :: Ord qpn => Var qpn -> ConflictSet qpn -> ConflictSet qpn
+extendConflictSet = S.insert . simplifyVar
+
 goalReasonToVars :: GoalReason qpn -> ConflictSet qpn
 goalReasonToVars UserGoal                 = S.empty
 goalReasonToVars (PDependency (PI qpn _)) = S.singleton (P qpn)
@@ -399,9 +379,6 @@
 goalReasonChainToVars :: Ord qpn => GoalReasonChain qpn -> ConflictSet qpn
 goalReasonChainToVars = S.unions . L.map goalReasonToVars
 
-goalReasonChainsToVars :: Ord qpn => [GoalReasonChain qpn] -> ConflictSet qpn
-goalReasonChainsToVars = S.unions . L.map goalReasonChainToVars
-
 {-------------------------------------------------------------------------------
   Open goals
 -------------------------------------------------------------------------------}
@@ -415,6 +392,12 @@
 -- need only during the build phase.
 close :: OpenGoal comp -> Goal QPN
 close (OpenGoal (Simple (Dep qpn _) _) gr) = Goal (P qpn) gr
+close (OpenGoal (Simple (Ext     _) _) _ ) =
+  error "Distribution.Client.Dependency.Modular.Dependency.close: called on Ext goal"
+close (OpenGoal (Simple (Lang    _) _) _ ) =
+  error "Distribution.Client.Dependency.Modular.Dependency.close: called on Lang goal"
+close (OpenGoal (Simple (Pkg   _ _) _) _ ) =
+  error "Distribution.Client.Dependency.Modular.Dependency.close: called on Pkg goal"
 close (OpenGoal (Flagged qfn _ _ _ )   gr) = Goal (F qfn) gr
 close (OpenGoal (Stanza  qsn _)        gr) = Goal (S qsn) gr
 
@@ -427,4 +410,4 @@
 -- | Helper function to collapse a list of version ranges with origins into
 -- a single, simplified, version range.
 collapse :: [VROrigin qpn] -> VR
-collapse = simplifyVR . L.foldr (.&&.) anyVR . L.map fst
+collapse = simplifyVR . L.foldr ((.&&.) . fst) anyVR
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs
@@ -1,8 +1,9 @@
-module Distribution.Client.Dependency.Modular.Explore where
+module Distribution.Client.Dependency.Modular.Explore
+    ( backjump
+    , backjumpAndExplore
+    ) where
 
-import Control.Applicative as A
-import Data.Foldable
-import Data.List as L
+import Data.Foldable as F
 import Data.Map as M
 import Data.Set as S
 
@@ -11,139 +12,77 @@
 import Distribution.Client.Dependency.Modular.Log
 import Distribution.Client.Dependency.Modular.Message
 import Distribution.Client.Dependency.Modular.Package
-import Distribution.Client.Dependency.Modular.PSQ as P
+import qualified Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
-
--- | Backjumping.
---
--- A tree traversal that tries to propagate conflict sets
--- up the tree from the leaves, and thereby cut branches.
--- All the tricky things are done in the function 'combine'.
-backjump :: Tree a -> Tree (Maybe (ConflictSet QPN))
-backjump = snd . cata go
-  where
-    go (FailF c fr) = (Just c, Fail c fr)
-    go (DoneF rdm ) = (Nothing, Done rdm)
-    go (PChoiceF qpn _     ts) = (c, PChoice qpn c     (P.fromList ts'))
-      where
-        ~(c, ts') = combine (P qpn) (P.toList ts) S.empty
-    go (FChoiceF qfn _ b m ts) = (c, FChoice qfn c b m (P.fromList ts'))
-      where
-        ~(c, ts') = combine (F qfn) (P.toList ts) S.empty
-    go (SChoiceF qsn _ b   ts) = (c, SChoice qsn c b   (P.fromList ts'))
-      where
-        ~(c, ts') = combine (S qsn) (P.toList ts) S.empty
-    go (GoalChoiceF        ts) = (c, GoalChoice        (P.fromList ts'))
-      where
-        ~(cs, ts') = unzip $ L.map (\ (k, (x, v)) -> (x, (k, v))) $ P.toList ts
-        c          = case cs of []    -> Nothing
-                                d : _ -> d
+import qualified Distribution.Client.Dependency.Types as T
 
--- | The 'combine' function is at the heart of backjumping. It takes
--- the variable we're currently considering, and a list of children
--- annotated with their respective conflict sets, and an accumulator
--- for the result conflict set. It returns a combined conflict set
--- for the parent node, and a (potentially shortened) list of children
--- with the annotations removed.
---
--- It is *essential* that we produce the results as early as possible.
--- In particular, we have to produce the list of children prior to
--- traversing the entire list -- otherwise we lose the desired behaviour
--- of being able to traverse the tree from left to right incrementally.
+-- | This function takes the variable we're currently considering and a
+-- list of children's logs. Each log yields either a solution or a
+-- conflict set. The result is a combined log for the parent node that
+-- has explored a prefix of the children.
 --
--- We can shorten the list of children if we find an individual conflict
--- set that does not contain the current variable. In this case, we can
--- just lift the conflict set to the current level, because the current
--- level cannot possibly have contributed to this conflict, so no other
--- choice at the current level would avoid the conflict.
+-- We can stop traversing the children's logs if we find an individual
+-- conflict set that does not contain the current variable. In this
+-- case, we can just lift the conflict set to the current level,
+-- because the current level cannot possibly have contributed to this
+-- conflict, so no other choice at the current level would avoid the
+-- conflict.
 --
--- If any of the children might contain a successful solution
--- (indicated by Nothing), then Nothing will be the combined
--- conflict set. If all children contain conflict sets, we can
+-- If any of the children might contain a successful solution, we can
+-- return it immediately. If all children contain conflict sets, we can
 -- take the union as the combined conflict set.
-combine :: Var QPN -> [(a, (Maybe (ConflictSet QPN), b))] ->
-           ConflictSet QPN -> (Maybe (ConflictSet QPN), [(a, b)])
-combine _   []                      c = (Just c, [])
-combine var ((k, (     d, v)) : xs) c = (\ ~(e, ys) -> (e, (k, v) : ys)) $
-                                        case d of
-                                          Just e | not (simplifyVar var `S.member` e) -> (Just e, [])
-                                                 | otherwise                          -> combine var xs (e `S.union` c)
-                                          Nothing                                     -> (Nothing, snd $ combine var xs S.empty)
-
--- | Naive backtracking exploration of the search tree. This will yield correct
--- assignments only once the tree itself is validated.
-explore :: Alternative m => Tree a -> (Assignment -> m (Assignment, RevDepMap))
-explore = cata go
+backjump :: F.Foldable t => Var QPN -> t (ConflictSetLog a) -> ConflictSetLog a
+backjump var xs = F.foldr combine logBackjump xs S.empty
   where
-    go (FailF _ _)           _           = A.empty
-    go (DoneF rdm)           a           = pure (a, rdm)
-    go (PChoiceF qpn _     ts) (A pa fa sa)   =
-      asum $                                      -- try children in order,
-      P.mapWithKey                                -- when descending ...
-        (\ (POption k _) r -> r (A (M.insert qpn k pa) fa sa)) -- record the pkg choice
-      ts
-    go (FChoiceF qfn _ _ _ ts) (A pa fa sa)   =
-      asum $                                      -- try children in order,
-      P.mapWithKey                                -- when descending ...
-        (\ k r -> r (A pa (M.insert qfn k fa) sa)) -- record the flag choice
-      ts
-    go (SChoiceF qsn _ _   ts) (A pa fa sa)   =
-      asum $                                      -- try children in order,
-      P.mapWithKey                                -- when descending ...
-        (\ k r -> r (A pa fa (M.insert qsn k sa))) -- record the flag choice
-      ts
-    go (GoalChoiceF        ts) a              =
-      casePSQ ts A.empty                      -- empty goal choice is an internal error
-        (\ _k v _xs -> v a)                   -- commit to the first goal choice
+    combine :: ConflictSetLog a
+            -> (ConflictSet QPN -> ConflictSetLog a)
+            ->  ConflictSet QPN -> ConflictSetLog a
+    combine (T.Done x)    _ _               = T.Done x
+    combine (T.Fail cs)   f csAcc
+      | not (simplifyVar var `S.member` cs) = logBackjump cs
+      | otherwise                           = f (csAcc `S.union` cs)
+    combine (T.Step m ms) f cs              = T.Step m (combine ms f cs)
 
--- | Version of 'explore' that returns a 'Log'.
-exploreLog :: Tree (Maybe (ConflictSet QPN)) ->
-              (Assignment -> Log Message (Assignment, RevDepMap))
+    logBackjump :: ConflictSet QPN -> ConflictSetLog a
+    logBackjump cs = failWith (Failure cs Backjump) cs
+
+type ConflictSetLog = T.Progress Message (ConflictSet QPN)
+
+-- | A tree traversal that simultaneously propagates conflict sets up
+-- the tree from the leaves and creates a log.
+exploreLog :: Tree a -> (Assignment -> ConflictSetLog (Assignment, RevDepMap))
 exploreLog = cata go
   where
-    go (FailF c fr)          _           = failWith (Failure c fr)
+    go :: TreeF a (Assignment -> ConflictSetLog (Assignment, RevDepMap))
+               -> (Assignment -> ConflictSetLog (Assignment, RevDepMap))
+    go (FailF c fr)          _           = failWith (Failure c fr) c
     go (DoneF rdm)           a           = succeedWith Success (a, rdm)
-    go (PChoiceF qpn c     ts) (A pa fa sa)   =
-      backjumpInfo c $
-      asum $                                      -- try children in order,
+    go (PChoiceF qpn _     ts) (A pa fa sa)   =
+      backjump (P qpn) $                          -- try children in order,
       P.mapWithKey                                -- when descending ...
-        (\ i@(POption k _) r -> tryWith (TryP qpn i) $     -- log and ...
+        (\ i@(POption k _) r -> tryWith (TryP qpn i) $ -- log and ...
                     r (A (M.insert qpn k pa) fa sa)) -- record the pkg choice
       ts
-    go (FChoiceF qfn c _ _ ts) (A pa fa sa)   =
-      backjumpInfo c $
-      asum $                                      -- try children in order,
+    go (FChoiceF qfn _ _ _ ts) (A pa fa sa)   =
+      backjump (F qfn) $                          -- try children in order,
       P.mapWithKey                                -- when descending ...
         (\ k r -> tryWith (TryF qfn k) $          -- log and ...
                     r (A pa (M.insert qfn k fa) sa)) -- record the pkg choice
       ts
-    go (SChoiceF qsn c _   ts) (A pa fa sa)   =
-      backjumpInfo c $
-      asum $                                      -- try children in order,
+    go (SChoiceF qsn _ _   ts) (A pa fa sa)   =
+      backjump (S qsn) $                          -- try children in order,
       P.mapWithKey                                -- when descending ...
         (\ k r -> tryWith (TryS qsn k) $          -- log and ...
                     r (A pa fa (M.insert qsn k sa))) -- record the pkg choice
       ts
     go (GoalChoiceF        ts) a           =
-      casePSQ ts
-        (failWith (Failure S.empty EmptyGoalChoice))   -- empty goal choice is an internal error
-        (\ k v _xs -> continueWith (Next (close k)) (v a))     -- commit to the first goal choice
-
--- | Add in information about pruned trees.
---
--- TODO: This isn't quite optimal, because we do not merely report the shape of the
--- tree, but rather make assumptions about where that shape originated from. It'd be
--- better if the pruning itself would leave information that we could pick up at this
--- point.
-backjumpInfo :: Maybe (ConflictSet QPN) -> Log Message a -> Log Message a
-backjumpInfo c m = m <|> case c of -- important to produce 'm' before matching on 'c'!
-                           Nothing -> A.empty
-                           Just cs -> failWith (Failure cs Backjump)
-
--- | Interface.
-exploreTree :: Alternative m => Tree a -> m (Assignment, RevDepMap)
-exploreTree t = explore t (A M.empty M.empty M.empty)
+      P.casePSQ ts
+        (failWith (Failure S.empty EmptyGoalChoice) S.empty) -- empty goal choice is an internal error
+        (\ k v _xs -> continueWith (Next (close k)) (v a))   -- commit to the first goal choice
 
 -- | Interface.
-exploreTreeLog :: Tree (Maybe (ConflictSet QPN)) -> Log Message (Assignment, RevDepMap)
-exploreTreeLog t = exploreLog t (A M.empty M.empty M.empty)
+backjumpAndExplore :: Tree a -> Log Message (Assignment, RevDepMap)
+backjumpAndExplore t = toLog $ exploreLog t (A M.empty M.empty M.empty)
+  where
+    toLog :: T.Progress step fail done -> Log step done
+    toLog = T.foldProgress T.Step (const (T.Fail ())) T.Done
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs
@@ -1,5 +1,19 @@
 {-# LANGUAGE DeriveFunctor #-}
-module Distribution.Client.Dependency.Modular.Flag where
+module Distribution.Client.Dependency.Modular.Flag
+    ( FInfo(..)
+    , Flag
+    , FlagInfo
+    , FN(..)
+    , QFN
+    , QSN
+    , SN(..)
+    , mkFlag
+    , showFBool
+    , showQFN
+    , showQFNBool
+    , showQSN
+    , showQSNBool
+    ) where
 
 import Data.Map as M
 import Prelude hiding (pi)
@@ -12,10 +26,6 @@
 -- | Flag name. Consists of a package instance and the flag identifier itself.
 data FN qpn = FN (PI qpn) Flag
   deriving (Eq, Ord, Show, Functor)
-
--- | Extract the package name from a flag name.
-getPN :: FN qpn -> qpn
-getPN (FN (PI qpn _) _) = qpn
 
 -- | Flag identifier. Just a string.
 type Flag = FlagName
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
@@ -1,4 +1,9 @@
-module Distribution.Client.Dependency.Modular.Index where
+module Distribution.Client.Dependency.Modular.Index
+    ( Index
+    , PInfo(..)
+    , defaultQualifyOptions
+    , mkIndex
+    ) where
 
 import Data.List as L
 import Data.Map as M
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
@@ -1,14 +1,12 @@
-module Distribution.Client.Dependency.Modular.IndexConversion (
-    convPIs
-    -- * TODO: The following don't actually seem to be used anywhere?
-  , convIPI
-  , convSPI
-  , convPI
-  ) where
+module Distribution.Client.Dependency.Modular.IndexConversion
+    ( convPIs
+    ) where
 
 import Data.List as L
 import Data.Map as M
 import Data.Maybe
+import Data.Monoid as Mon
+import Data.Set as S
 import Prelude hiding (pi)
 
 import qualified Distribution.Client.PackageIndex as CI
@@ -18,6 +16,7 @@
 import Distribution.InstalledPackageInfo as IPI
 import Distribution.Package                          -- from Cabal
 import Distribution.PackageDescription as PD         -- from Cabal
+import Distribution.PackageDescription.Configuration as PDC
 import qualified Distribution.Simple.PackageIndex as SI
 import Distribution.System
 
@@ -40,7 +39,7 @@
 -- fix the problem there, so for now, shadowing is only activated if
 -- explicitly requested.
 convPIs :: OS -> Arch -> CompilerInfo -> Bool -> Bool ->
-           SI.InstalledPackageIndex -> CI.PackageIndex SourcePackage -> Index
+           SI.InstalledPackageIndex -> CI.PackageIndex (SourcePackage loc) -> Index
 convPIs os arch comp sip strfl iidx sidx =
   mkIndex (convIPI' sip iidx ++ convSPI' os arch comp strfl sidx)
 
@@ -59,21 +58,18 @@
     shadow (pn, i, PInfo fdeps fds _) | sip = (pn, i, PInfo fdeps fds (Just Shadowed))
     shadow x                                = x
 
-convIPI :: Bool -> SI.InstalledPackageIndex -> Index
-convIPI sip = mkIndex . convIPI' sip
-
 -- | Convert a single installed package into the solver-specific format.
 convIP :: SI.InstalledPackageIndex -> InstalledPackageInfo -> (PN, I, PInfo)
 convIP idx ipi =
-  let ipid = IPI.installedPackageId ipi
-      i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)
-      pn = pkgName (sourcePackageId ipi)
-  in  case mapM (convIPId pn idx) (IPI.depends ipi) of
+  case mapM (convIPId pn idx) (IPI.depends ipi) of
         Nothing  -> (pn, i, PInfo []            M.empty (Just Broken))
         Just fds -> (pn, i, PInfo (setComp fds) M.empty Nothing)
  where
   -- We assume that all dependencies of installed packages are _library_ deps
-  setComp = setCompFlaggedDeps ComponentLib
+  ipid = IPI.installedUnitId ipi
+  i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)
+  pn = pkgName (sourcePackageId ipi)
+  setComp = setCompFlaggedDeps (ComponentLib (unPackageName pn))
 -- TODO: Installed packages should also store their encapsulations!
 
 -- | Convert dependencies specified by an installed package id into
@@ -82,9 +78,9 @@
 -- May return Nothing if the package can't be found in the index. That
 -- indicates that the original package having this dependency is broken
 -- and should be ignored.
-convIPId :: PN -> SI.InstalledPackageIndex -> InstalledPackageId -> Maybe (FlaggedDep () PN)
+convIPId :: PN -> SI.InstalledPackageIndex -> UnitId -> Maybe (FlaggedDep () PN)
 convIPId pn' idx ipid =
-  case SI.lookupInstalledPackageId idx ipid of
+  case SI.lookupUnitId idx ipid of
     Nothing  -> Nothing
     Just ipi -> let i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)
                     pn = pkgName (sourcePackageId ipi)
@@ -93,15 +89,11 @@
 -- | Convert a cabal-install source package index to the simpler,
 -- more uniform index format of the solver.
 convSPI' :: OS -> Arch -> CompilerInfo -> Bool ->
-            CI.PackageIndex SourcePackage -> [(PN, I, PInfo)]
+            CI.PackageIndex (SourcePackage loc) -> [(PN, I, PInfo)]
 convSPI' os arch cinfo strfl = L.map (convSP os arch cinfo strfl) . CI.allPackages
 
-convSPI :: OS -> Arch -> CompilerInfo -> Bool ->
-           CI.PackageIndex SourcePackage -> Index
-convSPI os arch cinfo strfl = mkIndex . convSPI' os arch cinfo strfl
-
 -- | Convert a single source package into the solver-specific format.
-convSP :: OS -> Arch -> CompilerInfo -> Bool -> SourcePackage -> (PN, I, PInfo)
+convSP :: OS -> Arch -> CompilerInfo -> Bool -> SourcePackage loc -> (PN, I, PInfo)
 convSP os arch cinfo strfl (SourcePackage (PackageIdentifier pn pv) gpd _ _pl) =
   let i = I pv InRepo
   in  (pn, i, convGPD os arch cinfo strfl (PI pn i) gpd)
@@ -113,24 +105,84 @@
 -- | Convert a generic package description to a solver-specific 'PInfo'.
 convGPD :: OS -> Arch -> CompilerInfo -> Bool ->
            PI PN -> GenericPackageDescription -> PInfo
-convGPD os arch comp strfl pi
+convGPD os arch cinfo strfl pi@(PI pn _)
         (GenericPackageDescription pkg flags libs exes tests benchs) =
   let
     fds  = flagInfo strfl flags
-    conv = convCondTree os arch comp pi fds (const True)
+
+    -- | We have to be careful to filter out dependencies on
+    -- internal libraries, since they don't refer to real packages
+    -- and thus cannot actually be solved over.  We'll do this
+    -- by creating a set of package names which are "internal"
+    -- and dropping them as we convert.
+    ipns = S.fromList [ PackageName nm
+                      | (nm, _) <- libs
+                      -- Don't include the true package name;
+                      -- qualification could make this relevant.
+                      -- TODO: Can we qualify over internal
+                      -- dependencies? Not for now!
+                      , PackageName nm /= pn ]
+
+    conv :: Mon.Monoid a => Component -> (a -> BuildInfo) ->
+            CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN
+    conv comp getInfo = convCondTree os arch cinfo pi fds comp getInfo ipns .
+                        PDC.addBuildableCondition getInfo
+
+    flagged_deps
+        = concatMap (\(nm, ds) -> conv (ComponentLib nm)   libBuildInfo       ds) libs
+       ++ concatMap (\(nm, ds) -> conv (ComponentExe nm)   buildInfo          ds) exes
+       ++ prefix (Stanza (SN pi TestStanzas))
+            (L.map  (\(nm, ds) -> conv (ComponentTest nm)  testBuildInfo      ds) tests)
+       ++ prefix (Stanza (SN pi BenchStanzas))
+            (L.map  (\(nm, ds) -> conv (ComponentBench nm) benchmarkBuildInfo ds) benchs)
+       ++ maybe []    (convSetupBuildInfo pi)    (setupBuildInfo pkg)
+
   in
-    PInfo
-      (maybe []    (conv ComponentLib                       ) libs    ++
-       maybe []    (convSetupBuildInfo pi)    (setupBuildInfo pkg)    ++
-       concatMap   (\(nm, ds) -> conv (ComponentExe nm)   ds) exes    ++
-      prefix (Stanza (SN pi TestStanzas))
-        (L.map     (\(nm, ds) -> conv (ComponentTest nm)  ds) tests)  ++
-      prefix (Stanza (SN pi BenchStanzas))
-        (L.map     (\(nm, ds) -> conv (ComponentBench nm) ds) benchs))
-      fds
-      Nothing
+    PInfo flagged_deps fds Nothing
 
-prefix :: (FlaggedDeps comp qpn -> FlaggedDep comp' qpn) -> [FlaggedDeps comp qpn] -> FlaggedDeps comp' qpn
+-- With convenience libraries, we have to do some work.  Imagine you
+-- have the following Cabal file:
+--
+--      name: foo
+--      library foo-internal
+--          build-depends: external-a
+--      library
+--          build-depends: foo-internal, external-b
+--      library foo-helper
+--          build-depends: foo, external-c
+--      test-suite foo-tests
+--          build-depends: foo-helper, external-d
+--
+-- What should the final flagged dependency tree be?  Ideally, it
+-- should look like this:
+--
+--      [ Simple (Dep external-a) (Library foo-internal)
+--      , Simple (Dep external-b) (Library foo)
+--      , Stanza (SN foo TestStanzas) $
+--          [ Simple (Dep external-c) (Library foo-helper)
+--          , Simple (Dep external-d) (TestSuite foo-tests) ]
+--      ]
+--
+-- There are two things to note:
+--
+--      1. First, we eliminated the "local" dependencies foo-internal
+--      and foo-helper.  This are implicitly assumed to refer to "foo"
+--      so we don't need to have them around.  If you forget this,
+--      Cabal will then try to pick a version for "foo-helper" but
+--      no such package exists (this is the cost of overloading
+--      build-depends to refer to both packages and components.)
+--
+--      2. Second, it is more precise to have external-c be qualified
+--      by a test stanza, since foo-helper only needs to be built if
+--      your are building the test suite (and not the main library).
+--      If you omit it, Cabal will always attempt to depsolve for
+--      foo-helper even if you aren't building the test suite.
+
+-- | Create a flagged dependency tree from a list @fds@ of flagged
+-- dependencies, using @f@ to form the tree node (@f@ will be
+-- something like @Stanza sn@).
+prefix :: (FlaggedDeps comp qpn -> FlaggedDep comp' qpn)
+       -> [FlaggedDeps comp qpn] -> FlaggedDeps comp' qpn
 prefix _ []  = []
 prefix f fds = [f (concat fds)]
 
@@ -139,17 +191,37 @@
 flagInfo :: Bool -> [PD.Flag] -> FlagInfo
 flagInfo strfl = M.fromList . L.map (\ (MkFlag fn _ b m) -> (fn, FInfo b m (not (strfl || m))))
 
--- | Convert condition trees to flagged dependencies.
+-- | Internal package names, which should not be interpreted as true
+-- dependencies.
+type IPNs = Set PN
+
+-- | Convenience function to delete a 'FlaggedDep' if it's
+-- for a 'PN' that isn't actually real.
+filterIPNs :: IPNs -> Dependency -> FlaggedDep Component PN -> FlaggedDeps Component PN
+filterIPNs ipns (Dependency pn _) fd
+    | S.notMember pn ipns = [fd]
+    | otherwise           = []
+
+-- | Convert condition trees to flagged dependencies.  Mutually
+-- recursive with 'convBranch'.  See 'convBranch' for an explanation
+-- of all arguments preceeding the input 'CondTree'.
 convCondTree :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->
-                (a -> Bool) -> -- how to detect if a branch is active
                 Component ->
+                (a -> BuildInfo) ->
+                IPNs ->
                 CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN
-convCondTree os arch cinfo pi@(PI pn _) fds p comp (CondNode info ds branches)
-  | p info    = L.map (\d -> D.Simple (convDep pn d) comp) ds  -- unconditional dependencies
-              ++ concatMap (convBranch os arch cinfo pi fds p comp) branches
-  | otherwise = []
+convCondTree os arch cinfo pi@(PI pn _) fds comp getInfo ipns (CondNode info ds branches) =
+                 concatMap
+                    (\d -> filterIPNs ipns d (D.Simple (convDep pn d) comp))
+                    ds  -- unconditional package dependencies
+              ++ L.map (\e -> D.Simple (Ext  e) comp) (PD.allExtensions bi) -- unconditional extension dependencies
+              ++ L.map (\l -> D.Simple (Lang l) comp) (PD.allLanguages  bi) -- unconditional language dependencies
+              ++ L.map (\(Dependency pkn vr) -> D.Simple (Pkg pkn vr) comp) (PD.pkgconfigDepends bi) -- unconditional pkg-config dependencies
+              ++ concatMap (convBranch os arch cinfo pi fds comp getInfo ipns) branches
+  where
+    bi = getInfo info
 
--- | Branch interpreter.
+-- | Branch interpreter.  Mutually recursive with 'convCondTree'.
 --
 -- Here, we try to simplify one of Cabal's condition tree branches into the
 -- solver's flagged dependency format, which is weaker. Condition trees can
@@ -157,16 +229,40 @@
 -- flags (such as architecture, or compiler flavour). We try to evaluate the
 -- special flags and subsequently simplify to a tree that only depends on
 -- simple flag choices.
+--
+-- This function takes a number of arguments:
+--
+--      1. Some pre dependency-solving known information ('OS', 'Arch',
+--         'CompilerInfo') for @os()@, @arch()@ and @impl()@ variables,
+--
+--      2. The package instance @'PI' 'PN'@ which this condition tree
+--         came from, so that we can correctly associate @flag()@
+--         variables with the correct package name qualifier,
+--
+--      3. The flag defaults 'FlagInfo' so that we can populate
+--         'Flagged' dependencies with 'FInfo',
+--
+--      4. The name of the component 'Component' so we can record where
+--         the fine-grained information about where the component came
+--         from (see 'convCondTree'), and
+--
+--      5. A selector to extract the 'BuildInfo' from the leaves of
+--         the 'CondTree' (which actually contains the needed
+--         dependency information.)
+--
+--      6. The set of package names which should be considered internal
+--         dependencies, and thus not handled as dependencies.
 convBranch :: OS -> Arch -> CompilerInfo ->
               PI PN -> FlagInfo ->
-              (a -> Bool) -> -- how to detect if a branch is active
               Component ->
+              (a -> BuildInfo) ->
+              IPNs ->
               (Condition ConfVar,
                CondTree ConfVar [Dependency] a,
                Maybe (CondTree ConfVar [Dependency] a)) -> FlaggedDeps Component PN
-convBranch os arch cinfo pi fds p comp (c', t', mf') =
-  go c' (          convCondTree os arch cinfo pi fds p comp   t')
-        (maybe [] (convCondTree os arch cinfo pi fds p comp) mf')
+convBranch os arch cinfo pi@(PI pn _) fds comp getInfo ipns (c', t', mf') =
+  go c' (          convCondTree os arch cinfo pi fds comp getInfo ipns   t')
+        (maybe [] (convCondTree os arch cinfo pi fds comp getInfo ipns) mf')
   where
     go :: Condition ConfVar ->
           FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN
@@ -198,20 +294,21 @@
     -- with deferring flag choices will then usually first resolve this package,
     -- and try an already installed version before imposing a default flag choice
     -- that might not be what we want.
+    --
+    -- Note that we make assumptions here on the form of the dependencies that
+    -- can occur at this point. In particular, no occurrences of Fixed, and no
+    -- occurrences of multiple version ranges, as all dependencies below this
+    -- point have been generated using 'convDep'.
     extractCommon :: FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN
-    extractCommon ps ps' = [ D.Simple (Dep pn (Constrained [])) comp
-                           | D.Simple (Dep pn  _) _ <- ps
-                           , D.Simple (Dep pn' _) _ <- ps'
-                           , pn == pn'
+    extractCommon ps ps' = [ D.Simple (Dep pn1 (Constrained [(vr1 .||. vr2, Goal (P pn) [])])) comp
+                           | D.Simple (Dep pn1 (Constrained [(vr1, _)])) _ <- ps
+                           , D.Simple (Dep pn2 (Constrained [(vr2, _)])) _ <- ps'
+                           , pn1 == pn2
                            ]
 
 -- | Convert a Cabal dependency to a solver-specific dependency.
 convDep :: PN -> Dependency -> Dep PN
 convDep pn' (Dependency pn vr) = Dep pn (Constrained [(vr, Goal (P pn') [])])
-
--- | Convert a Cabal package identifier to a solver-specific dependency.
-convPI :: PN -> PackageIdentifier -> Dep PN
-convPI pn' (PackageIdentifier pn v) = Dep pn (Constrained [(eqVR v, Goal (P pn') [])])
 
 -- | Convert setup dependencies
 convSetupBuildInfo :: PI PN -> SetupBuildInfo -> FlaggedDeps Component PN
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
@@ -274,6 +274,12 @@
           lg'  = M.findWithDefault (lgSingleton qpn' Nothing) qpn' $ vsLinks vs
       lg'' <- lift' $ lgMerge parents lg lg'
       updateLinkGroup lg''
+    -- For extensions and language dependencies, there is nothing to do.
+    -- No choice is involved, just checking, so there is nothing to link.
+    go (Simple (Ext  _)             _) = return ()
+    go (Simple (Lang _)             _) = return ()
+    -- Similarly for pkg-config constraints
+    go (Simple (Pkg  _ _)           _) = return ()
     go (Flagged fn _ t f) = do
       vs <- get
       case M.lookup fn (vsFlags vs) of
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs
@@ -1,7 +1,15 @@
-module Distribution.Client.Dependency.Modular.Log where
+module Distribution.Client.Dependency.Modular.Log
+    ( Log
+    , continueWith
+    , failWith
+    , logToProgress
+    , succeedWith
+    , tryWith
+    ) where
 
 import Control.Applicative
 import Data.List as L
+import Data.Maybe (isNothing)
 import Data.Set as S
 
 import Distribution.Client.Dependency.Types -- from Cabal
@@ -18,107 +26,81 @@
 -- Parameterized over the type of actual messages and the final result.
 type Log m a = Progress m () a
 
--- | Turns a log into a list of messages paired with a final result. A final result
--- of 'Nothing' indicates failure. A final result of 'Just' indicates success.
--- Keep in mind that forcing the second component of the returned pair will force the
--- entire log.
-runLog :: Log m a -> ([m], Maybe a)
-runLog (Done x)       = ([], Just x)
-runLog (Fail _)       = ([], Nothing)
-runLog (Step m p)     = let
-                          (ms, r) = runLog p
-                        in
-                          (m : ms, r)
+messages :: Progress step fail done -> [step]
+messages = foldProgress (:) (const []) (const [])
 
 -- | Postprocesses a log file. Takes as an argument a limit on allowed backjumps.
 -- If the limit is 'Nothing', then infinitely many backjumps are allowed. If the
 -- limit is 'Just 0', backtracking is completely disabled.
 logToProgress :: Maybe Int -> Log Message a -> Progress String String a
 logToProgress mbj l = let
-                        (ms, s) = runLog l
-                        -- 'Nothing' for 's' means search tree exhaustively searched and failed
-                        (es, e) = proc 0 ms -- catch first error (always)
-                        -- 'Nothing' in 'e' means no backjump found
-                        (ns, t) = case mbj of
-                                     Nothing -> (ms, Nothing)
-                                     Just n  -> proc n ms
-                        -- 'Nothing' in 't' means backjump limit not reached
-                        -- prefer first error over later error
-                        (exh, r) = case t of
-                                     -- backjump limit not reached
-                                     Nothing -> case s of
-                                                  Nothing -> (True, e) -- failed after exhaustive search
-                                                  Just _  -> (True, Nothing) -- success
-                                     -- backjump limit reached; prefer first error
-                                     Just _  -> (False, e) -- failed after backjump limit was reached
+                        es = proc (Just 0) l -- catch first error (always)
+                        ms = useFirstError (proc mbj l)
                       in go es es -- trace for first error
-                            (showMessages (const True) True ns) -- shortened run
-                            r s exh
-  where
-    -- Proc takes the allowed number of backjumps and a list of messages and explores the
-    -- message list until the maximum number of backjumps has been reached. The log until
-    -- that point as well as whether we have encountered an error or not are returned.
-    proc :: Int -> [Message] -> ([Message], Maybe (ConflictSet QPN))
-    proc _ []                             = ([], Nothing)
-    proc n (   Failure cs Backjump  : xs@(Leave : Failure cs' Backjump : _))
-      | cs == cs'                         = proc n xs -- repeated backjumps count as one
-    proc 0 (   Failure cs Backjump  : _ ) = ([], Just cs)
-    proc n (x@(Failure _  Backjump) : xs) = (\ ~(ys, r) -> (x : ys, r)) (proc (n - 1) xs)
-    proc n (x                       : xs) = (\ ~(ys, r) -> (x : ys, r)) (proc  n      xs)
-
-    -- This function takes a lot of arguments. The first two are both supposed to be
-    -- the log up to the first error. That's the error that will always be printed in
-    -- case we do not find a solution. We pass this log twice, because we evaluate it
-    -- in parallel with the full log, but we also want to retain the reference to its
-    -- beginning for when we print it. This trick prevents a space leak!
-    --
-    -- The third argument is the full log, the fifth and six error conditions.
-    -- The seventh argument indicates whether the search was exhaustive.
-    --
-    -- The order of arguments is important! In particular 's' must not be evaluated
-    -- unless absolutely necessary. It contains the final result, and if we shortcut
-    -- with an error due to backjumping, evaluating 's' would still require traversing
-    -- the entire tree.
-    go ms (_ : ns) (x : xs) r         s        exh = Step x (go ms ns xs r s exh)
-    go ms []       (x : xs) r         s        exh = Step x (go ms [] xs r s exh)
-    go ms _        []       (Just cs) _        exh = Fail $
-                                                     "Could not resolve dependencies:\n" ++
-                                                     unlines (showMessages (L.foldr (\ v _ -> v `S.member` cs) True) False ms) ++
-                                                     (if exh then "Dependency tree exhaustively searched.\n"
-                                                             else "Backjump limit reached (" ++ currlimit mbj ++
-                                                                      "change with --max-backjumps or try to run with --reorder-goals).\n")
-                                                         where currlimit (Just n) = "currently " ++ show n ++ ", "
-                                                               currlimit Nothing  = ""
-    go _  _        []       _         (Just s) _   = Done s
-    go _  _        []       _         _        _   = Fail ("Could not resolve dependencies; something strange happened.") -- should not happen
-
-logToProgress' :: Log Message a -> Progress String String a
-logToProgress' l = let
-                    (ms, r) = runLog l
-                    xs      = showMessages (const True) True ms
-                  in go xs r
+                            (showMessages (const True) True ms) -- run with backjump limit applied
   where
-    go [x]    Nothing  = Fail x
-    go []     Nothing  = Fail ""
-    go []     (Just r) = Done r
-    go (x:xs) r        = Step x (go xs r)
+    -- Proc takes the allowed number of backjumps and a 'Progress' and explores the
+    -- messages until the maximum number of backjumps has been reached. It filters out
+    -- and ignores repeated backjumps. If proc reaches the backjump limit, it truncates
+    -- the 'Progress' and ends it with the last conflict set. Otherwise, it leaves the
+    -- original success result or replaces the original failure with 'Nothing'.
+    proc :: Maybe Int -> Progress Message a b -> Progress Message (Maybe (ConflictSet QPN)) b
+    proc _        (Done x)                          = Done x
+    proc _        (Fail _)                          = Fail Nothing
+    proc mbj'     (Step   (Failure cs Backjump) xs@(Step Leave (Step (Failure cs' Backjump) _)))
+      | cs == cs'                                   = proc mbj' xs -- repeated backjumps count as one
+    proc (Just 0) (Step   (Failure cs Backjump)  _) = Fail (Just cs)
+    proc (Just n) (Step x@(Failure _  Backjump) xs) = Step x (proc (Just (n - 1)) xs)
+    proc mbj'     (Step x                       xs) = Step x (proc mbj'           xs)
 
+    -- Sets the conflict set from the first backjump as the final error, and records
+    -- whether the search was exhaustive.
+    useFirstError :: Progress Message (Maybe (ConflictSet QPN)) b
+                  -> Progress Message (Bool, Maybe (ConflictSet QPN)) b
+    useFirstError = replace Nothing
+      where
+        replace _       (Done x)                          = Done x
+        replace cs'     (Fail cs)                         = -- 'Nothing' means backjump limit not reached.
+                                                            -- Prefer first error over later error.
+                                                            Fail (isNothing cs, cs' <|> cs)
+        replace Nothing (Step x@(Failure cs Backjump) xs) = Step x $ replace (Just cs) xs
+        replace cs'     (Step x                       xs) = Step x $ replace cs' xs
 
-runLogIO :: Log Message a -> IO (Maybe a)
-runLogIO x =
-  do
-    let (ms, r) = runLog x
-    putStr (unlines $ showMessages (const True) True ms)
-    return r
+    -- The first two arguments are both supposed to be the log up to the first error.
+    -- That's the error that will always be printed in case we do not find a solution.
+    -- We pass this log twice, because we evaluate it in parallel with the full log,
+    -- but we also want to retain the reference to its beginning for when we print it.
+    -- This trick prevents a space leak!
+    --
+    -- The third argument is the full log, ending with either the solution or the
+    -- exhaustiveness and first conflict set.
+    go :: Progress Message a b
+       -> Progress Message a b
+       -> Progress String (Bool, Maybe (ConflictSet QPN)) b
+       -> Progress String String b
+    go ms (Step _ ns) (Step x xs)           = Step x (go ms ns xs)
+    go ms r           (Step x xs)           = Step x (go ms r  xs)
+    go ms _           (Fail (exh, Just cs)) = Fail $
+                                              "Could not resolve dependencies:\n" ++
+                                              unlines (messages $ showMessages (L.foldr (\ v _ -> v `S.member` cs) True) False ms) ++
+                                              (if exh then "Dependency tree exhaustively searched.\n"
+                                                      else "Backjump limit reached (" ++ currlimit mbj ++
+                                                               "change with --max-backjumps or try to run with --reorder-goals).\n")
+                                                  where currlimit (Just n) = "currently " ++ show n ++ ", "
+                                                        currlimit Nothing  = ""
+    go _  _           (Done s)              = Done s
+    go _  _           (Fail (_, Nothing))   = Fail ("Could not resolve dependencies; something strange happened.") -- should not happen
 
-failWith :: m -> Log m a
-failWith m = Step m (Fail ())
+failWith :: step -> fail -> Progress step fail done
+failWith s f = Step s (Fail f)
 
-succeedWith :: m -> a -> Log m a
-succeedWith m x = Step m (Done x)
+succeedWith :: step -> done -> Progress step fail done
+succeedWith s d = Step s (Done d)
 
-continueWith :: m -> Log m a -> Log m a
+continueWith :: step -> Progress step fail done -> Progress step fail done
 continueWith = Step
 
-tryWith :: Message -> Log Message a -> Log Message a
-tryWith m x = Step m (Step Enter x) <|> failWith Leave
+tryWith :: Message
+        -> Progress Message fail done
+        -> Progress Message fail done
+tryWith m = Step m . Step Enter . foldProgress Step (failWith Leave) Done
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE BangPatterns #-}
+
 module Distribution.Client.Dependency.Modular.Message (
     Message(..),
     showMessages
@@ -12,8 +14,9 @@
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Tree
+         ( FailReason(..), POption(..) )
 import Distribution.Client.Dependency.Types
-         ( ConstraintSource(..), showConstraintSource )
+         ( ConstraintSource(..), showConstraintSource, Progress(..) )
 
 data Message =
     Enter           -- ^ increase indentation level
@@ -37,41 +40,73 @@
 -- The second argument indicates if the level numbers should be shown. This is
 -- recommended for any trace that involves backtracking, because only the level
 -- numbers will allow to keep track of backjumps.
-showMessages :: ([Var QPN] -> Bool) -> Bool -> [Message] -> [String]
+showMessages :: ([Var QPN] -> Bool) -> Bool -> Progress Message a b -> Progress String a b
 showMessages p sl = go [] 0
   where
-    go :: [Var QPN] -> Int -> [Message] -> [String]
-    go _ _ []                            = []
+    -- The stack 'v' represents variables that are currently assigned by the
+    -- solver.  'go' pushes a variable for a recursive call when it encounters
+    -- 'TryP', 'TryF', or 'TryS' and pops a variable when it encounters 'Leave'.
+    -- When 'go' processes a package goal, or a package goal followed by a
+    -- 'Failure', it calls 'atLevel' with the goal variable at the head of the
+    -- stack so that the predicate can also select messages relating to package
+    -- goal choices.
+    go :: [Var QPN] -> Int -> Progress Message a b -> Progress String a b
+    go !_ !_ (Done x)                           = Done x
+    go !_ !_ (Fail x)                           = Fail x
     -- complex patterns
-    go v l (TryP qpn i : Enter : Failure c fr : Leave : ms) = goPReject v l qpn [i] c fr ms
-    go v l (TryF qfn b : Enter : Failure c fr : Leave : ms) = (atLevel (add (F qfn) v) l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go v l ms)
-    go v l (TryS qsn b : Enter : Failure c fr : Leave : ms) = (atLevel (add (S qsn) v) l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go v l ms)
-    go v l (Next (Goal (P qpn) gr) : TryP qpn' i : ms@(Enter : Next _ : _)) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn' i ++ showGRs gr) (go (add (P qpn) v) l ms)
-    go v l (Failure c Backjump : ms@(Leave : Failure c' Backjump : _)) | c == c' = go v l ms
+    go !v !l (Step (TryP qpn i) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =
+        goPReject v l qpn [i] c fr ms
+    go !v !l (Step (TryF qfn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =
+        (atLevel (add (F qfn) v) l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go v l ms)
+    go !v !l (Step (TryS qsn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =
+        (atLevel (add (S qsn) v) l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go v l ms)
+    go !v !l (Step (Next (Goal (P qpn) gr)) (Step (TryP qpn' i) ms@(Step Enter (Step (Next _) _)))) =
+        (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn' i ++ showGRs gr) (go (add (P qpn) v) l ms)
+    go !v !l (Step (Next (Goal (P qpn) gr)) (Step (Failure c fr) ms)) =
+        let v' = add (P qpn) v
+        in (atLevel v' l $ showPackageGoal qpn gr) $ (atLevel v' l $ showFailure c fr) (go v l ms)
+    go !v !l (Step (Failure c Backjump) ms@(Step Leave (Step (Failure c' Backjump) _)))
+        | c == c' = go v l ms
     -- standard display
-    go v l (Enter                  : ms) = go v          (l+1) ms
-    go v l (Leave                  : ms) = go (drop 1 v) (l-1) ms
-    go v l (TryP qpn i             : ms) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn i) (go (add (P qpn) v) l ms)
-    go v l (TryF qfn b             : ms) = (atLevel (add (F qfn) v) l $ "trying: " ++ showQFNBool qfn b) (go (add (F qfn) v) l ms)
-    go v l (TryS qsn b             : ms) = (atLevel (add (S qsn) v) l $ "trying: " ++ showQSNBool qsn b) (go (add (S qsn) v) l ms)
-    go v l (Next (Goal (P qpn) gr) : ms) = (atLevel (add (P qpn) v) l $ "next goal: " ++ showQPN qpn ++ showGRs gr) (go v l ms)
-    go v l (Next _                 : ms) = go v l ms -- ignore flag goals in the log
-    go v l (Success                : ms) = (atLevel v l $ "done") (go v l ms)
-    go v l (Failure c fr           : ms) = (atLevel v l $ "fail" ++ showFR c fr) (go v l ms)
+    go !v !l (Step Enter                    ms) = go v          (l+1) ms
+    go !v !l (Step Leave                    ms) = go (drop 1 v) (l-1) ms
+    go !v !l (Step (TryP qpn i)             ms) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn i) (go (add (P qpn) v) l ms)
+    go !v !l (Step (TryF qfn b)             ms) = (atLevel (add (F qfn) v) l $ "trying: " ++ showQFNBool qfn b) (go (add (F qfn) v) l ms)
+    go !v !l (Step (TryS qsn b)             ms) = (atLevel (add (S qsn) v) l $ "trying: " ++ showQSNBool qsn b) (go (add (S qsn) v) l ms)
+    go !v !l (Step (Next (Goal (P qpn) gr)) ms) = (atLevel (add (P qpn) v) l $ showPackageGoal qpn gr) (go v l ms)
+    go !v !l (Step (Next _)                 ms) = go v l ms -- ignore flag goals in the log
+    go !v !l (Step (Success)                ms) = (atLevel v l $ "done") (go v l ms)
+    go !v !l (Step (Failure c fr)           ms) = (atLevel v l $ showFailure c fr) (go v l ms)
 
+    showPackageGoal :: QPN -> QGoalReasonChain -> String
+    showPackageGoal qpn gr = "next goal: " ++ showQPN qpn ++ showGRs gr
+
+    showFailure :: ConflictSet QPN -> FailReason -> String
+    showFailure c fr = "fail" ++ showFR c fr
+
     add :: Var QPN -> [Var QPN] -> [Var QPN]
     add v vs = simplifyVar v : vs
 
     -- special handler for many subsequent package rejections
-    goPReject :: [Var QPN] -> Int -> QPN -> [POption] -> ConflictSet QPN -> FailReason -> [Message] -> [String]
-    goPReject v l qpn is c fr (TryP qpn' i : Enter : Failure _ fr' : Leave : ms) | qpn == qpn' && fr == fr' = goPReject v l qpn (i : is) c fr ms
-    goPReject v l qpn is c fr ms = (atLevel (P qpn : v) l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go v l ms)
+    goPReject :: [Var QPN]
+              -> Int
+              -> QPN
+              -> [POption]
+              -> ConflictSet QPN
+              -> FailReason
+              -> Progress Message a b
+              -> Progress String a b
+    goPReject v l qpn is c fr (Step (TryP qpn' i) (Step Enter (Step (Failure _ fr') (Step Leave ms))))
+      | qpn == qpn' && fr == fr' = goPReject v l qpn (i : is) c fr ms
+    goPReject v l qpn is c fr ms =
+        (atLevel (P qpn : v) l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go v l ms)
 
     -- write a message, but only if it's relevant; we can also enable or disable the display of the current level
+    atLevel :: [Var QPN] -> Int -> String -> Progress String a b -> Progress String a b
     atLevel v l x xs
       | sl && p v = let s = show l
-                    in  ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) : xs
-      | p v       = x : xs
+                    in  Step ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) xs
+      | p v       = Step x xs
       | otherwise = xs
 
 showQPNPOpt :: QPN -> POption -> String
@@ -106,6 +141,7 @@
 showFR c Backjump                         = " (backjumping, conflict set: " ++ showCS c ++ ")"
 showFR _ MultipleInstances                = " (multiple instances)"
 showFR c (DependenciesNotLinked msg)      = " (dependencies not linked: " ++ msg ++ "; conflict set: " ++ showCS c ++ ")"
+showFR c CyclicDependencies               = " (cyclic dependencies; conflict set: " ++ showCS c ++ ")"
 -- The following are internal failures. They should not occur. In the
 -- interest of not crashing unnecessarily, we still just print an error
 -- message though.
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs
@@ -1,5 +1,36 @@
-{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveFoldable, DeriveTraversable #-}
-module Distribution.Client.Dependency.Modular.PSQ where
+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
+module Distribution.Client.Dependency.Modular.PSQ
+    ( PSQ(..)  -- Unit test needs constructor access
+    , Degree(..)
+    , casePSQ
+    , cons
+    , degree
+    , delete
+    , dminimumBy
+    , length
+    , lookup
+    , filter
+    , filterKeys
+    , firstOnly
+    , fromList
+    , isZeroOrOne
+    , keys
+    , map
+    , mapKeys
+    , mapWithKey
+    , mapWithKeyState
+    , minimumBy
+    , null
+    , prefer
+    , preferByKeys
+    , preferOrElse
+    , snoc
+    , sortBy
+    , sortByKeys
+    , splits
+    , toList
+    , union
+    ) where
 
 -- Priority search queues.
 --
@@ -8,14 +39,17 @@
 -- (inefficiently implemented) lookup, because I think that queue-based
 -- operations and sorting turn out to be more efficiency-critical in practice.
 
-import Data.Foldable
+import Control.Arrow (first, second)
+
+import qualified Data.Foldable as F
 import Data.Function
-import Data.List as S hiding (foldr)
+import qualified Data.List as S
+import Data.Ord (comparing)
 import Data.Traversable
-import Prelude hiding (foldr)
+import Prelude hiding (foldr, length, lookup, filter, null, map)
 
 newtype PSQ k v = PSQ [(k, v)]
-  deriving (Eq, Show, Functor, Foldable, Traversable)
+  deriving (Eq, Show, Functor, F.Foldable, Traversable) -- Qualified Foldable to avoid issues with FTP
 
 keys :: PSQ k v -> [k]
 keys (PSQ xs) = fmap fst xs
@@ -24,22 +58,22 @@
 lookup k (PSQ xs) = S.lookup k xs
 
 map :: (v1 -> v2) -> PSQ k v1 -> PSQ k v2
-map f (PSQ xs) = PSQ (fmap (\ (k, v) -> (k, f v)) xs)
+map f (PSQ xs) = PSQ (fmap (second f) xs)
 
 mapKeys :: (k1 -> k2) -> PSQ k1 v -> PSQ k2 v
-mapKeys f (PSQ xs) = PSQ (fmap (\ (k, v) -> (f k, v)) xs)
+mapKeys f (PSQ xs) = PSQ (fmap (first f) xs)
 
 mapWithKey :: (k -> a -> b) -> PSQ k a -> PSQ k b
 mapWithKey f (PSQ xs) = PSQ (fmap (\ (k, v) -> (k, f k v)) xs)
 
 mapWithKeyState :: (s -> k -> a -> (b, s)) -> PSQ k a -> s -> PSQ k b
 mapWithKeyState p (PSQ xs) s0 =
-  PSQ (foldr (\ (k, v) r s -> case p s k v of
-                                (w, n) -> (k, w) : (r n))
-             (const []) xs s0)
+  PSQ (F.foldr (\ (k, v) r s -> case p s k v of
+                                  (w, n) -> (k, w) : (r n))
+               (const []) xs s0)
 
 delete :: Eq k => k -> PSQ k a -> PSQ k a
-delete k (PSQ xs) = PSQ (snd (partition ((== k) . fst) xs))
+delete k (PSQ xs) = PSQ (snd (S.partition ((== k) . fst) xs))
 
 fromList :: [(k, a)] -> PSQ k a
 fromList = PSQ
@@ -69,6 +103,62 @@
 sortByKeys :: (k -> k -> Ordering) -> PSQ k a -> PSQ k a
 sortByKeys cmp (PSQ xs) = PSQ (S.sortBy (cmp `on` fst) xs)
 
+-- | Given a measure in form of a pseudo-peano-natural number,
+-- determine the approximate minimum. This is designed to stop
+-- even traversing the list as soon as we find any element with
+-- measure 'ZeroOrOne'.
+--
+-- Always returns a one-element queue (except if the queue is
+-- empty, then we return an empty queue again).
+--
+dminimumBy :: (a -> Degree) -> PSQ k a -> PSQ k a
+dminimumBy _   (PSQ [])       = PSQ []
+dminimumBy sel (PSQ (x : xs)) = go (sel (snd x)) x xs
+  where
+    go ZeroOrOne v _ = PSQ [v]
+    go _ v []        = PSQ [v]
+    go c v (y : ys)  = case compare c d of
+      LT -> go c v ys
+      EQ -> go c v ys
+      GT -> go d y ys
+      where
+        d = sel (snd y)
+
+minimumBy :: (a -> Int) -> PSQ k a -> PSQ k a
+minimumBy sel (PSQ xs) =
+  PSQ [snd (S.minimumBy (comparing fst) (S.map (\ x -> (sel (snd x), x)) xs))]
+
+-- | Will partition the list according to the predicate. If
+-- there is any element that satisfies the precidate, then only
+-- the elements satisfying the predicate are returned.
+-- Otherwise, the rest is returned.
+--
+prefer :: (a -> Bool) -> PSQ k a -> PSQ k a
+prefer p (PSQ xs) =
+  let
+    (pro, con) = S.partition (p . snd) xs
+  in
+    if S.null pro then PSQ con else PSQ pro
+
+-- | Variant of 'prefer' that takes a continuation for the case
+-- that there are none of the desired elements.
+preferOrElse :: (a -> Bool) -> (PSQ k a -> PSQ k a) -> PSQ k a -> PSQ k a
+preferOrElse p k (PSQ xs) =
+  let
+    (pro, con) = S.partition (p . snd) xs
+  in
+    if S.null pro then k (PSQ con) else PSQ pro
+
+-- | Variant of 'prefer' that takes a predicate on the keys
+-- rather than on the values.
+--
+preferByKeys :: (k -> Bool) -> PSQ k a -> PSQ k a
+preferByKeys p (PSQ xs) =
+  let
+    (pro, con) = S.partition (p . fst) xs
+  in
+    if S.null pro then PSQ con else PSQ pro
+
 filterKeys :: (k -> Bool) -> PSQ k a -> PSQ k a
 filterKeys p (PSQ xs) = PSQ (S.filter (p . fst) xs)
 
@@ -78,17 +168,43 @@
 length :: PSQ k a -> Int
 length (PSQ xs) = S.length xs
 
--- | "Lazy length".
+-- | Approximation of the branching degree.
 --
--- Only approximates the length, but doesn't force the list.
-llength :: PSQ k a -> Int
-llength (PSQ [])       = 0
-llength (PSQ (_:[]))   = 1
-llength (PSQ (_:_:[])) = 2
-llength (PSQ _)        = 3
+-- This is designed for computing the branching degree of a goal choice
+-- node. If the degree is 0 or 1, it is always good to take that goal,
+-- because we can either abort immediately, or have no other choice anyway.
+--
+-- So we do not actually want to compute the full degree (which is
+-- somewhat costly) in cases where we have such an easy choice.
+--
+data Degree = ZeroOrOne | Two | Other
+  deriving (Show, Eq)
 
+instance Ord Degree where
+  compare ZeroOrOne _         = LT -- lazy approximation
+  compare _         ZeroOrOne = GT -- approximation
+  compare Two       Two       = EQ
+  compare Two       Other     = LT
+  compare Other     Two       = GT
+  compare Other     Other     = EQ
+
+degree :: PSQ k a -> Degree
+degree (PSQ [])     = ZeroOrOne
+degree (PSQ [_])    = ZeroOrOne
+degree (PSQ [_, _]) = Two
+degree (PSQ _)      = Other
+
 null :: PSQ k a -> Bool
 null (PSQ xs) = S.null xs
+
+isZeroOrOne :: PSQ k a -> Bool
+isZeroOrOne (PSQ [])  = True
+isZeroOrOne (PSQ [_]) = True
+isZeroOrOne _         = False
+
+firstOnly :: PSQ k a -> PSQ k a
+firstOnly (PSQ [])      = PSQ []
+firstOnly (PSQ (x : _)) = PSQ [x]
 
 toList :: PSQ k a -> [(k, a)]
 toList (PSQ xs) = xs
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs
@@ -1,7 +1,26 @@
 {-# LANGUAGE DeriveFunctor #-}
 module Distribution.Client.Dependency.Modular.Package
-  (module Distribution.Client.Dependency.Modular.Package,
-   module Distribution.Package) where
+  ( I(..)
+  , Loc(..)
+  , PackageId
+  , PackageIdentifier(..)
+  , PackageName(..)
+  , PI(..)
+  , PN
+  , PP(..)
+  , Namespace(..)
+  , Qualifier(..)
+  , QPN
+  , QPV
+  , Q(..)
+  , instI
+  , makeIndependent
+  , primaryPP
+  , showI
+  , showPI
+  , showQPN
+  , unPN
+  ) where
 
 import Data.List as L
 
@@ -24,7 +43,7 @@
 type QPV = Q PV
 
 -- | Package id. Currently just a black-box string.
-type PId = InstalledPackageId
+type PId = UnitId
 
 -- | Location. Info about whether a package is installed or not, and where
 -- exactly it is located. For installed packages, uniquely identifies the
@@ -41,11 +60,13 @@
 -- | String representation of an instance.
 showI :: I -> String
 showI (I v InRepo)   = showVer v
-showI (I v (Inst (InstalledPackageId i))) = showVer v ++ "/installed" ++ shortId i
+showI (I v (Inst uid)) = showVer v ++ "/installed" ++ shortId uid
   where
     -- A hack to extract the beginning of the package ABI hash
-    shortId = snip (splitAt 4) (++ "...") .
-              snip ((\ (x, y) -> (reverse x, y)) . break (=='-') . reverse) ('-':)
+    shortId (SimpleUnitId (ComponentId i))
+            = snip (splitAt 4) (++ "...")
+            . snip ((\ (x, y) -> (reverse x, y)) . break (=='-') . reverse) ('-':)
+            $ i
     snip p f xs = case p xs of
                     (ys, zs) -> (if L.null zs then id else f) ys
 
@@ -57,46 +78,79 @@
 showPI :: PI QPN -> String
 showPI (PI qpn i) = showQPN qpn ++ "-" ++ showI i
 
--- | Checks if a package instance corresponds to an installed package.
-instPI :: PI qpn -> Bool
-instPI (PI _ (I _ (Inst _))) = True
-instPI _                     = False
-
 instI :: I -> Bool
 instI (I _ (Inst _)) = True
 instI _              = False
 
--- | Package path.
+-- | A package path consists of a namespace and a package path inside that
+-- namespace.
+data PP = PP Namespace Qualifier
+  deriving (Eq, Ord, Show)
+
+-- | Top-level namespace
 --
--- Stored in reverse order
-data PP =
-    -- User-specified independent goal
-    Independent Int PP
-    -- Setup dependencies are always considered independent from their package
-  | Setup PN PP
-    -- Any dependency on base is considered independent (allows for base shims)
-  | Base PN PP
-    -- Unqualified
-  | None
+-- Package choices in different namespaces are considered completely independent
+-- by the solver.
+data Namespace =
+    -- | The default namespace
+    DefaultNamespace
+
+    -- | Independent namespace
+    --
+    -- For now we just number these (rather than giving them more structure).
+  | Independent Int
   deriving (Eq, Ord, Show)
 
--- | Strip any 'Base' qualifiers from a PP
+-- | Qualifier of a package within a namespace (see 'PP')
+data Qualifier =
+    -- | Top-level dependency in this namespace
+    Unqualified
+
+    -- | Any dependency on base is considered independent
+    --
+    -- This makes it possible to have base shims.
+  | Base PN
+
+    -- | Setup dependency
+    --
+    -- By rights setup dependencies ought to be nestable; after all, the setup
+    -- dependencies of a package might themselves have setup dependencies, which
+    -- are independent from everything else. However, this very quickly leads to
+    -- infinite search trees in the solver. Therefore we limit ourselves to
+    -- a single qualifier (within a given namespace).
+  | Setup PN
+  deriving (Eq, Ord, Show)
+
+-- | Is the package in the primary group of packages. In particular this
+-- does not include packages pulled in as setup deps.
 --
--- (the Base qualifier does not get inherited)
-stripBase :: PP -> PP
-stripBase (Independent i pp) = Independent i (stripBase pp)
-stripBase (Setup pn      pp) = Setup pn      (stripBase pp)
-stripBase (Base _pn      pp) =                stripBase pp
-stripBase None               = None
+primaryPP :: PP -> Bool
+primaryPP (PP _ns q) = go q
+  where
+    go Unqualified = True
+    go (Base  _)   = True
+    go (Setup _)   = False
 
 -- | String representation of a package path.
 --
--- NOTE: This always ends in a period
+-- NOTE: The result of 'showPP' is either empty or results in a period, so that
+-- it can be prepended to a package name.
 showPP :: PP -> String
-showPP (Independent i pp) = show i                 ++ "." ++ showPP pp
-showPP (Setup pn      pp) = display pn ++ "-setup" ++ "." ++ showPP pp
-showPP (Base  pn      pp) = display pn             ++ "." ++ showPP pp
-showPP None               = ""
+showPP (PP ns q) =
+    case ns of
+      DefaultNamespace -> go q
+      Independent i    -> show i ++ "." ++ go q
+  where
+    -- Print the qualifier
+    --
+    -- NOTE: the base qualifier is for a dependency _on_ base; the qualifier is
+    -- there to make sure different dependencies on base are all independent.
+    -- So we want to print something like @"A.base"@, where the @"A."@ part
+    -- is the qualifier and @"base"@ is the actual dependency (which, for the
+    -- 'Base' qualifier, will always be @base@).
+    go Unqualified = ""
+    go (Setup pn)  = display pn ++ "-setup."
+    go (Base  pn)  = display pn ++ "."
 
 -- | A qualified entity. Pairs a package path with the entity.
 data Q a = Q PP a
@@ -104,8 +158,7 @@
 
 -- | Standard string representation of a qualified entity.
 showQ :: (a -> String) -> (Q a -> String)
-showQ showa (Q None x) = showa x
-showQ showa (Q pp   x) = showPP pp ++ showa x
+showQ showa (Q pp x) = showPP pp ++ showa x
 
 -- | Qualified package name.
 type QPN = Q PN
@@ -118,8 +171,5 @@
 -- them all independent.
 makeIndependent :: [PN] -> [QPN]
 makeIndependent ps = [ Q pp pn | (pn, i) <- zip ps [0::Int ..]
-                               , let pp = Independent i None
+                               , let pp = PP (Independent i) Unqualified
                      ]
-
-unQualify :: Q a -> a
-unQualify (Q _ x) = x
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
@@ -1,5 +1,19 @@
 {-# LANGUAGE CPP #-}
-module Distribution.Client.Dependency.Modular.Preference where
+module Distribution.Client.Dependency.Modular.Preference
+    ( avoidReinstalls
+    , deferSetupChoices
+    , deferWeakFlagChoices
+    , enforceManualFlags
+    , enforcePackageConstraints
+    , enforceSingleInstanceRestriction
+    , firstGoal
+    , preferBaseGoalChoice
+    , preferEasyGoalChoices
+    , preferLinked
+    , preferPackagePreferences
+    , preferReallyEasyGoalChoices
+    , requireInstalled
+    ) where
 
 -- Reordering or pruning the tree in order to prefer or make certain choices.
 
@@ -12,12 +26,11 @@
 import qualified Data.Set as S
 import Prelude hiding (sequence)
 import Control.Monad.Reader hiding (sequence)
-import Data.Ord
 import Data.Map (Map)
 import Data.Traversable (sequence)
 
 import Distribution.Client.Dependency.Types
-  ( PackageConstraint(..), LabeledPackageConstraint(..)
+  ( PackageConstraint(..), LabeledPackageConstraint(..), ConstraintSource(..)
   , PackagePreferences(..), InstalledPreference(..) )
 import Distribution.Client.Types
   ( OptionalStanza(..) )
@@ -25,7 +38,7 @@
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
-import Distribution.Client.Dependency.Modular.PSQ as P
+import qualified Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
 import Distribution.Client.Dependency.Modular.Version
 
@@ -56,21 +69,23 @@
     cmpL (Just _) Nothing  = LT
     cmpL (Just _) (Just _) = EQ
 
-
--- | Ordering that treats preferred versions as greater than non-preferred
--- versions.
-preferredVersionsOrdering :: VR -> Ver -> Ver -> Ordering
-preferredVersionsOrdering vr v1 v2 =
-  compare (checkVR vr v1) (checkVR vr v2)
+-- | Ordering that treats versions satisfying more preferred ranges as greater
+--   than versions satisfying less preferred ranges.
+preferredVersionsOrdering :: [VR] -> Ver -> Ver -> Ordering
+preferredVersionsOrdering vrs v1 v2 = compare (check v1) (check v2)
+  where
+     check v = Prelude.length . Prelude.filter (==True) .
+               Prelude.map (flip checkVR v) $ vrs
 
 -- | Traversal that tries to establish package preferences (not constraints).
--- Works by reordering choice nodes.
+-- Works by reordering choice nodes. Also applies stanza preferences.
 preferPackagePreferences :: (PN -> PackagePreferences) -> Tree a -> Tree a
-preferPackagePreferences pcs = packageOrderFor (const True) preference
+preferPackagePreferences pcs = preferPackageStanzaPreferences pcs
+                             . packageOrderFor (const True) preference
   where
     preference pn i1@(I v1 _) i2@(I v2 _) =
-      let PackagePreferences vr ipref = pcs pn
-      in  preferredVersionsOrdering vr v1 v2 `mappend` -- combines lexically
+      let PackagePreferences vrs ipref _ = pcs pn
+      in  preferredVersionsOrdering vrs v1 v2 `mappend` -- combines lexically
           locationsOrdering ipref i1 i2
 
     -- Note that we always rank installed before uninstalled, and later
@@ -92,16 +107,36 @@
 preferLatestOrdering :: I -> I -> Ordering
 preferLatestOrdering (I v1 _) (I v2 _) = compare v1 v2
 
+-- | Traversal that tries to establish package stanza enable\/disable
+-- preferences. Works by reordering the branches of stanza choices.
+preferPackageStanzaPreferences :: (PN -> PackagePreferences) -> Tree a -> Tree a
+preferPackageStanzaPreferences pcs = trav go
+  where
+    go (SChoiceF qsn@(SN (PI (Q pp pn) _) s) gr _tr ts) | primaryPP pp =
+        let PackagePreferences _ _ spref = pcs pn
+            enableStanzaPref = s `elem` spref
+                  -- move True case first to try enabling the stanza
+            ts' | enableStanzaPref = P.sortByKeys (flip compare) ts
+                | otherwise        = ts
+         in SChoiceF qsn gr True ts'   -- True: now weak choice
+    go x = x
+
 -- | Helper function that tries to enforce a single package constraint on a
 -- given instance for a P-node. Translates the constraint into a
 -- tree-transformer that either leaves the subtree untouched, or replaces it
 -- with an appropriate failure node.
-processPackageConstraintP :: ConflictSet QPN
+processPackageConstraintP :: PP
+                          -> ConflictSet QPN
                           -> I
                           -> LabeledPackageConstraint
                           -> Tree a
                           -> Tree a
-processPackageConstraintP c i (LabeledPackageConstraint pc src) r = go i pc
+processPackageConstraintP pp _ _ (LabeledPackageConstraint _ src) r
+  | src == ConstraintSourceUserTarget && not (primaryPP pp)         = r
+    -- the constraints arising from targets, like "foo-1.0" only apply to
+    -- the main packages in the solution, they don't constrain setup deps
+
+processPackageConstraintP _ c i (LabeledPackageConstraint pc src) r = go i pc
   where
     go (I v _) (PackageConstraintVersion _ vr)
         | checkVR vr v  = r
@@ -158,10 +193,10 @@
                           -> Tree QGoalReasonChain
 enforcePackageConstraints pcs = trav go
   where
-    go (PChoiceF qpn@(Q _ pn)               gr      ts) =
+    go (PChoiceF qpn@(Q pp pn)              gr      ts) =
       let c = toConflictSet (Goal (P qpn) gr)
           -- compose the transformation functions for each of the relevant constraint
-          g = \ (POption i _) -> foldl (\ h pc -> h . processPackageConstraintP   c i pc) id
+          g = \ (POption i _) -> foldl (\ h pc -> h . processPackageConstraintP pp c i pc) id
                            (M.findWithDefault [] pn pcs)
       in PChoiceF qpn gr      (P.mapWithKey g ts)
     go (FChoiceF qfn@(FN (PI (Q _ pn) _) f) gr tr m ts) =
@@ -196,22 +231,6 @@
         isDisabled _                                    = False
     go x                                                   = x
 
--- | Prefer installed packages over non-installed packages, generally.
--- All installed packages or non-installed packages are treated as
--- equivalent.
-preferInstalled :: Tree a -> Tree a
-preferInstalled = packageOrderFor (const True) (const preferInstalledOrdering)
-
--- | Prefer packages with higher version numbers over packages with
--- lower version numbers, for certain packages.
-preferLatestFor :: (PN -> Bool) -> Tree a -> Tree a
-preferLatestFor p = packageOrderFor p (const preferLatestOrdering)
-
--- | Prefer packages with higher version numbers over packages with
--- lower version numbers, for all packages.
-preferLatest :: Tree a -> Tree a
-preferLatest = preferLatestFor (const True)
-
 -- | Require installed packages.
 requireInstalled :: (PN -> Bool) -> Tree QGoalReasonChain -> Tree QGoalReasonChain
 requireInstalled p = trav go
@@ -245,7 +264,7 @@
       | otherwise = PChoiceF qpn gr cs
       where
         disableReinstalls =
-          let installed = [ v | (POption (I v (Inst _)) _, _) <- toList cs ]
+          let installed = [ v | (POption (I v (Inst _)) _, _) <- P.toList cs ]
           in  P.mapWithKey (notReinstall installed) cs
 
         notReinstall vs (POption (I v InRepo) _) _ | v `elem` vs =
@@ -263,8 +282,7 @@
 firstGoal :: Tree a -> Tree a
 firstGoal = trav go
   where
-    go (GoalChoiceF xs) = -- casePSQ xs (GoalChoiceF xs) (\ _ t _ -> out t) -- more space efficient, but removes valuable debug info
-                          casePSQ xs (GoalChoiceF (fromList [])) (\ g t _ -> GoalChoiceF (fromList [(g, t)]))
+    go (GoalChoiceF xs) = GoalChoiceF (P.firstOnly xs)
     go x                = x
     -- Note that we keep empty choice nodes, because they mean success.
 
@@ -274,37 +292,24 @@
 preferBaseGoalChoice :: Tree a -> Tree a
 preferBaseGoalChoice = trav go
   where
-    go (GoalChoiceF xs) = GoalChoiceF (P.sortByKeys preferBase xs)
+    go (GoalChoiceF xs) = GoalChoiceF (P.preferByKeys isBase xs)
     go x                = x
 
-    preferBase :: OpenGoal comp -> OpenGoal comp -> Ordering
-    preferBase (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) _ | unPN pn == "base" = LT
-    preferBase _ (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) | unPN pn == "base" = GT
-    preferBase _ _                                                              = EQ
+    isBase :: OpenGoal comp -> Bool
+    isBase (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) | unPN pn == "base" = True
+    isBase _                                                              = False
 
 -- | Deal with setup dependencies after regular dependencies, so that we can
 -- will link setup depencencies against package dependencies when possible
 deferSetupChoices :: Tree a -> Tree a
 deferSetupChoices = trav go
   where
-    go (GoalChoiceF xs) = GoalChoiceF (P.sortByKeys deferSetup xs)
+    go (GoalChoiceF xs) = GoalChoiceF (P.preferByKeys noSetup xs)
     go x                = x
 
-    deferSetup :: OpenGoal comp -> OpenGoal comp -> Ordering
-    deferSetup (OpenGoal (Simple (Dep (Q (Setup _ _) _) _) _) _) _ = GT
-    deferSetup _ (OpenGoal (Simple (Dep (Q (Setup _ _) _) _) _) _) = LT
-    deferSetup _ _                                                 = EQ
-
--- | Transformation that sorts choice nodes so that
--- child nodes with a small branching degree are preferred. As a
--- special case, choices with 0 branches will be preferred (as they
--- are immediately considered inconsistent), and choices with 1
--- branch will also be preferred (as they don't involve choice).
-preferEasyGoalChoices :: Tree a -> Tree a
-preferEasyGoalChoices = trav go
-  where
-    go (GoalChoiceF xs) = GoalChoiceF (P.sortBy (comparing choices) xs)
-    go x                = x
+    noSetup :: OpenGoal comp -> Bool
+    noSetup (OpenGoal (Simple (Dep (Q (PP _ns (Setup _)) _) _) _) _) = False
+    noSetup _                                                        = True
 
 -- | Transformation that tries to avoid making weak flag choices early.
 -- Weak flags are trivial flags (not influencing dependencies) or such
@@ -312,33 +317,45 @@
 deferWeakFlagChoices :: Tree a -> Tree a
 deferWeakFlagChoices = trav go
   where
-    go (GoalChoiceF xs) = GoalChoiceF (P.sortBy defer xs)
+    go (GoalChoiceF xs) = GoalChoiceF (P.prefer noWeakStanza (P.prefer noWeakFlag xs))
     go x                = x
 
-    defer :: Tree a -> Tree a -> Ordering
-    defer (FChoice _ _ True _ _) _ = GT
-    defer _ (FChoice _ _ True _ _) = LT
-    defer _ _                      = EQ
+    noWeakStanza :: Tree a -> Bool
+    noWeakStanza (SChoice _ _ True _) = False
+    noWeakStanza _                    = True
 
--- | Variant of 'preferEasyGoalChoices'.
+    noWeakFlag :: Tree a -> Bool
+    noWeakFlag (FChoice _ _ True _ _) = False
+    noWeakFlag _                      = True
+
+-- | Transformation that sorts choice nodes so that
+-- child nodes with a small branching degree are preferred.
 --
--- Only approximates the number of choices in the branches. Less accurate,
--- more efficient.
-lpreferEasyGoalChoices :: Tree a -> Tree a
-lpreferEasyGoalChoices = trav go
+-- Only approximates the number of choices in the branches.
+-- In particular, we try to take any goal immediately if it has
+-- a branching degree of 0 (guaranteed failure) or 1 (no other
+-- choice possible).
+--
+-- Returns at most one choice.
+--
+preferEasyGoalChoices :: Tree a -> Tree a
+preferEasyGoalChoices = trav go
   where
-    go (GoalChoiceF xs) = GoalChoiceF (P.sortBy (comparing lchoices) xs)
+    go (GoalChoiceF xs) = GoalChoiceF (P.dminimumBy dchoices xs)
+      -- (a different implementation that seems slower):
+      -- GoalChoiceF (P.firstOnly (P.preferOrElse zeroOrOneChoices (P.minimumBy choices) xs))
     go x                = x
 
--- | Variant of 'preferEasyGoalChoices'.
+-- | A variant of 'preferEasyGoalChoices' that just keeps the
+-- ones with a branching degree of 0 or 1. Note that unlike
+-- 'preferEasyGoalChoices', this may return more than one
+-- choice.
 --
--- I first thought that using a paramorphism might be faster here,
--- but it doesn't seem to make any difference.
-preferEasyGoalChoices' :: Tree a -> Tree a
-preferEasyGoalChoices' = para (inn . go)
+preferReallyEasyGoalChoices :: Tree a -> Tree a
+preferReallyEasyGoalChoices = trav go
   where
-    go (GoalChoiceF xs) = GoalChoiceF (P.map fst (P.sortBy (comparing (choices . snd)) xs))
-    go x                = fmap fst x
+    go (GoalChoiceF xs) = GoalChoiceF (P.prefer zeroOrOneChoices xs)
+    go x                = x
 
 -- | Monad used internally in enforceSingleInstanceRestriction
 type EnforceSIR = Reader (Map (PI PN) QPN)
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
@@ -1,11 +1,19 @@
-module Distribution.Client.Dependency.Modular.Solver where
+module Distribution.Client.Dependency.Modular.Solver
+    ( SolverConfig(..)
+    , solve
+    ) where
 
 import Data.Map as M
 
+import Distribution.Compiler (CompilerInfo)
+
+import Distribution.Client.PkgConfigDb (PkgConfigDb)
+
 import Distribution.Client.Dependency.Types
 
 import Distribution.Client.Dependency.Modular.Assignment
 import Distribution.Client.Dependency.Modular.Builder
+import Distribution.Client.Dependency.Modular.Cycles
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Explore
 import Distribution.Client.Dependency.Modular.Index
@@ -26,35 +34,62 @@
   maxBackjumps          :: Maybe Int
 }
 
-solve :: SolverConfig ->          -- solver parameters
-         Index ->                 -- all available packages as an index
-         (PN -> PackagePreferences) ->        -- preferences
-         Map PN [LabeledPackageConstraint] -> -- global constraints
-         [PN] ->                              -- global goals
+-- | Run all solver phases.
+--
+-- In principle, we have a valid tree after 'validationPhase', which
+-- means that every 'Done' node should correspond to valid solution.
+--
+-- There is one exception, though, and that is cycle detection, which
+-- has been added relatively recently. Cycles are only removed directly
+-- before exploration.
+--
+-- Semantically, there is no difference. Cycle detection, as implemented
+-- now, only occurs for 'Done' nodes we encounter during exploration,
+-- and cycle detection itself does not change the shape of the tree,
+-- it only marks some 'Done' nodes as 'Fail', if they contain cyclic
+-- solutions.
+--
+-- There is a tiny performance impact, however, in doing cycle detection
+-- directly after validation. Probably because cycle detection maintains
+-- some information, and the various reorderings implemented by
+-- 'preferencesPhase' and 'heuristicsPhase' are ever so slightly more
+-- costly if that information is already around during the reorderings.
+--
+-- With the current positioning directly before the 'explorePhase', there
+-- seems to be no statistically significant performance impact of cycle
+-- detection in the common case where there are no cycles.
+--
+solve :: SolverConfig ->                      -- ^ solver parameters
+         CompilerInfo ->
+         Index ->                             -- ^ all available packages as an index
+         PkgConfigDb ->                       -- ^ available pkg-config pkgs
+         (PN -> PackagePreferences) ->        -- ^ preferences
+         Map PN [LabeledPackageConstraint] -> -- ^ global constraints
+         [PN] ->                              -- ^ global goals
          Log Message (Assignment, RevDepMap)
-solve sc idx userPrefs userConstraints userGoals =
+solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals =
   explorePhase     $
+  detectCyclesPhase$
   heuristicsPhase  $
   preferencesPhase $
   validationPhase  $
   prunePhase       $
   buildPhase
   where
-    explorePhase     = exploreTreeLog . backjump
-    heuristicsPhase  = P.firstGoal . -- after doing goal-choice heuristics, commit to the first choice (saves space)
-                       P.deferSetupChoices .
+    explorePhase     = backjumpAndExplore
+    heuristicsPhase  = (if preferEasyGoalChoices sc
+                         then P.preferEasyGoalChoices -- also leaves just one choice
+                         else P.firstGoal) . -- after doing goal-choice heuristics, commit to the first choice (saves space)
                        P.deferWeakFlagChoices .
+                       P.deferSetupChoices .
                        P.preferBaseGoalChoice .
-                       if preferEasyGoalChoices sc
-                         then P.lpreferEasyGoalChoices
-                         else id .
                        P.preferLinked
     preferencesPhase = P.preferPackagePreferences userPrefs
     validationPhase  = P.enforceManualFlags . -- can only be done after user constraints
                        P.enforcePackageConstraints userConstraints .
                        P.enforceSingleInstanceRestriction .
                        validateLinking idx .
-                       validateTree idx
+                       validateTree cinfo idx pkgConfigDB
     prunePhase       = (if avoidReinstalls sc then P.avoidReinstalls (const True) else id) .
                        -- packages that can never be "upgraded":
                        P.requireInstalled (`elem` [ PackageName "base"
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
@@ -1,5 +1,19 @@
 {-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
-module Distribution.Client.Dependency.Modular.Tree where
+module Distribution.Client.Dependency.Modular.Tree
+    ( FailReason(..)
+    , POption(..)
+    , Tree(..)
+    , TreeF(..)
+    , ana
+    , cata
+    , choices
+    , dchoices
+    , inn
+    , innM
+    , para
+    , trav
+    , zeroOrOneChoices
+    ) where
 
 import Control.Monad hiding (mapM, sequence)
 import Data.Foldable
@@ -9,7 +23,8 @@
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
-import Distribution.Client.Dependency.Modular.PSQ as P
+import Distribution.Client.Dependency.Modular.PSQ (PSQ)
+import qualified Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Version
 import Distribution.Client.Dependency.Types ( ConstraintSource(..) )
 
@@ -42,7 +57,7 @@
 --
 -- Linking is an essential part of this story. In addition to picking a specific
 -- version for @1.P@, the solver can also decide to link @1.P@ to @0.P@ (or
--- vice versa). Teans that @1.P@ and @0.P@ really must be the very same package
+-- vice versa). It means that @1.P@ and @0.P@ really must be the very same package
 -- (and hence must have the same build time configuration, and their
 -- dependencies must also be the exact same).
 --
@@ -68,6 +83,7 @@
                 | Backjump
                 | MultipleInstances
                 | DependenciesNotLinked String
+                | CyclicDependencies
   deriving (Eq, Show)
 
 -- | Functor for the tree type.
@@ -119,16 +135,24 @@
 choices (Done       _         ) = 1
 choices (Fail       _ _       ) = 0
 
--- | Variant of 'choices' that only approximates the number of choices,
--- using 'llength'.
-lchoices :: Tree a -> Int
-lchoices (PChoice    _ _     ts) = P.llength (P.filter active ts)
-lchoices (FChoice    _ _ _ _ ts) = P.llength (P.filter active ts)
-lchoices (SChoice    _ _ _   ts) = P.llength (P.filter active ts)
-lchoices (GoalChoice         _ ) = 1
-lchoices (Done       _         ) = 1
-lchoices (Fail       _ _       ) = 0
+-- | Variant of 'choices' that only approximates the number of choices.
+dchoices :: Tree a -> P.Degree
+dchoices (PChoice    _ _     ts) = P.degree (P.filter active ts)
+dchoices (FChoice    _ _ _ _ ts) = P.degree (P.filter active ts)
+dchoices (SChoice    _ _ _   ts) = P.degree (P.filter active ts)
+dchoices (GoalChoice         _ ) = P.ZeroOrOne
+dchoices (Done       _         ) = P.ZeroOrOne
+dchoices (Fail       _ _       ) = P.ZeroOrOne
 
+-- | Variant of 'choices' that only approximates the number of choices.
+zeroOrOneChoices :: Tree a -> Bool
+zeroOrOneChoices (PChoice    _ _     ts) = P.isZeroOrOne (P.filter active ts)
+zeroOrOneChoices (FChoice    _ _ _ _ ts) = P.isZeroOrOne (P.filter active ts)
+zeroOrOneChoices (SChoice    _ _ _   ts) = P.isZeroOrOne (P.filter active ts)
+zeroOrOneChoices (GoalChoice         _ ) = True
+zeroOrOneChoices (Done       _         ) = True
+zeroOrOneChoices (Fail       _ _       ) = True
+
 -- | Catamorphism on trees.
 cata :: (TreeF a b -> b) -> Tree a -> b
 cata phi x = (phi . fmap (cata phi) . out) x
@@ -140,12 +164,6 @@
 para :: (TreeF a (b, Tree a) -> b) -> Tree a -> b
 para phi = phi . fmap (\ x -> (para phi x, x)) . out
 
-cataM :: Monad m => (TreeF a b -> m b) -> Tree a -> m b
-cataM phi = phi <=< mapM (cataM phi) <=< return . out
-
 -- | Anamorphism on trees.
 ana :: (b -> TreeF a b) -> b -> Tree a
 ana psi = inn . fmap (ana psi) . psi
-
-anaM :: Monad m => (b -> m (TreeF a b)) -> b -> m (Tree a)
-anaM psi = return . inn <=< mapM (anaM psi) <=< psi
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
@@ -10,18 +10,25 @@
 import Control.Monad.Reader hiding (sequence)
 import Data.List as L
 import Data.Map as M
+import Data.Set as S
 import Data.Traversable
 import Prelude hiding (sequence)
 
+import Language.Haskell.Extension (Extension, Language)
+
+import Distribution.Compiler (CompilerInfo(..))
+
 import Distribution.Client.Dependency.Modular.Assignment
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Index
 import Distribution.Client.Dependency.Modular.Package
-import Distribution.Client.Dependency.Modular.PSQ as P
+import qualified Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
+import Distribution.Client.Dependency.Modular.Version (VR)
 
 import Distribution.Client.ComponentDeps (Component)
+import Distribution.Client.PkgConfigDb (PkgConfigDb, pkgConfigPkgIsPresent)
 
 -- In practice, most constraints are implication constraints (IF we have made
 -- a number of choices, THEN we also have to ensure that). We call constraints
@@ -75,6 +82,9 @@
 
 -- | The state needed during validation.
 data ValidateState = VS {
+  supportedExt  :: Extension -> Bool,
+  supportedLang :: Language  -> Bool,
+  presentPkgs   :: PN -> VR  -> Bool,
   index :: Index,
   saved :: Map QPN (FlaggedDeps Component QPN), -- saved, scoped, dependencies
   pa    :: PreAssignment,
@@ -123,6 +133,9 @@
     goP :: QPN -> QGoalReasonChain -> POption -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goP qpn@(Q _pp pn) gr (POption i _) r = do
       PA ppa pfa psa <- asks pa    -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
+      pkgPresent     <- asks presentPkgs -- obtain the present pkg-config pkgs
       idx            <- asks index -- obtain the index
       svd            <- asks saved -- obtain saved dependencies
       qo             <- asks qualifyOptions
@@ -135,7 +148,7 @@
       let goal = Goal (P qpn) gr
       let newactives = Dep qpn (Fixed i goal) : L.map (resetGoal goal) (extractDeps pfa psa qdeps)
       -- We now try to extend the partial assignment with the new active constraints.
-      let mnppa = extend (P qpn) ppa newactives
+      let mnppa = extend extSupported langSupported pkgPresent goal ppa newactives
       -- In case we continue, we save the scoped dependencies
       let nsvd = M.insert qpn qdeps svd
       case mfr of
@@ -151,6 +164,9 @@
     goF :: QFN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goF qfn@(FN (PI qpn _i) _f) gr b r = do
       PA ppa pfa psa <- asks pa -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
+      pkgPresent     <- asks presentPkgs   -- obtain the present pkg-config pkgs
       svd <- asks saved         -- obtain saved dependencies
       -- Note that there should be saved dependencies for the package in question,
       -- because while building, we do not choose flags before we see the packages
@@ -165,7 +181,7 @@
       -- we have chosen a new flag.
       let newactives = extractNewDeps (F qfn) gr b npfa psa qdeps
       -- As in the package case, we try to extend the partial assignment.
-      case extend (F qfn) ppa newactives of
+      case extend extSupported langSupported pkgPresent (Goal (F qfn) gr) ppa newactives of
         Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found
         Right nppa  -> local (\ s -> s { pa = PA nppa npfa psa }) r
 
@@ -173,6 +189,9 @@
     goS :: QSN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goS qsn@(SN (PI qpn _i) _f) gr b r = do
       PA ppa pfa psa <- asks pa -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
+      pkgPresent     <- asks presentPkgs -- obtain the present pkg-config pkgs
       svd <- asks saved         -- obtain saved dependencies
       -- Note that there should be saved dependencies for the package in question,
       -- because while building, we do not choose flags before we see the packages
@@ -187,7 +206,7 @@
       -- we have chosen a new flag.
       let newactives = extractNewDeps (S qsn) gr b pfa npsa qdeps
       -- As in the package case, we try to extend the partial assignment.
-      case extend (S qsn) ppa newactives of
+      case extend extSupported langSupported pkgPresent (Goal (S qsn) gr) ppa newactives of
         Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found
         Right nppa  -> local (\ s -> s { pa = PA nppa pfa npsa }) r
 
@@ -235,10 +254,17 @@
                                   Just False -> []
 
 -- | Interface.
-validateTree :: Index -> Tree QGoalReasonChain -> Tree QGoalReasonChain
-validateTree idx t = runReader (validate t) VS {
-    index = idx
-  , saved = M.empty
-  , pa    = PA M.empty M.empty M.empty
+validateTree :: CompilerInfo -> Index -> PkgConfigDb -> Tree QGoalReasonChain -> Tree QGoalReasonChain
+validateTree cinfo idx pkgConfigDb t = runReader (validate t) VS {
+    supportedExt   = maybe (const True) -- if compiler has no list of extensions, we assume everything is supported
+                           (\ es -> let s = S.fromList es in \ x -> S.member x s)
+                           (compilerInfoExtensions cinfo)
+  , supportedLang  = maybe (const True)
+                           (flip L.elem) -- use list lookup because language list is small and no Ord instance
+                           (compilerInfoLanguages  cinfo)
+  , presentPkgs    = pkgConfigPkgIsPresent pkgConfigDb
+  , index          = idx
+  , saved          = M.empty
+  , pa             = PA M.empty M.empty M.empty
   , qualifyOptions = defaultQualifyOptions idx
   }
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs
@@ -1,4 +1,15 @@
-module Distribution.Client.Dependency.Modular.Version where
+module Distribution.Client.Dependency.Modular.Version
+    ( Ver
+    , VR
+    , anyVR
+    , checkVR
+    , eqVR
+    , showVer
+    , showVR
+    , simplifyVR
+    , (.&&.)
+    , (.||.)
+    ) where
 
 import qualified Distribution.Version as CV -- from Cabal
 import Distribution.Text -- from Cabal
@@ -29,6 +40,10 @@
 (.&&.) :: VR -> VR -> VR
 (.&&.) = CV.intersectVersionRanges
 
+-- | Union of two version ranges.
+(.||.) :: VR -> VR -> VR
+(.||.) = CV.unionVersionRanges
+
 -- | Simplify a version range.
 simplifyVR :: VR -> VR
 simplifyVR = CV.simplifyVersionRange
@@ -36,7 +51,3 @@
 -- | Checking a version against a version range.
 checkVR :: VR -> Ver -> Bool
 checkVR = flip CV.withinRange
-
--- | Make a version number.
-mkV :: [Int] -> Ver
-mkV xs = CV.Version xs []
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs b/cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs
@@ -21,7 +21,8 @@
          ( Satisfiable(..) )
 import Distribution.Client.Types
          ( SourcePackage(..), ConfiguredPackage(..)
-         , enableStanzas, ConfiguredId(..), fakeInstalledPackageId )
+         , UnresolvedPkgLoc, UnresolvedSourcePackage
+         , enableStanzas, ConfiguredId(..), fakeUnitId )
 import Distribution.Client.Dependency.Types
          ( DependencyResolver, ResolverPackage(..)
          , PackageConstraint(..), unlabelPackageConstraint
@@ -39,7 +40,7 @@
          ( PackageIndex )
 import Distribution.Package
          ( PackageName(..), PackageId, PackageIdentifier(..)
-         , InstalledPackageId(..)
+         , UnitId(..), ComponentId(..)
          , Package(..), packageVersion, packageName
          , Dependency(Dependency), thisPackageVersion, simplifyDependency )
 import Distribution.PackageDescription
@@ -127,8 +128,10 @@
       where
         isInstalled (SourceOnly _) = False
         isInstalled _              = True
-        isPreferred p = packageVersion p `withinRange` preferredVersions
-        (PackagePreferences preferredVersions packageInstalledPreference)
+        isPreferred p = length . filter (packageVersion p `withinRange`) $
+                        preferredVersions
+
+        (PackagePreferences preferredVersions packageInstalledPreference _)
           = pref pkgname
 
     logInfo node = Select selected discarded
@@ -248,8 +251,8 @@
 -- | The main exported resolver, with string logging and failure types to fit
 -- the standard 'DependencyResolver' interface.
 --
-topDownResolver :: DependencyResolver
-topDownResolver platform cinfo installedPkgIndex sourcePkgIndex
+topDownResolver :: DependencyResolver UnresolvedPkgLoc
+topDownResolver platform cinfo installedPkgIndex sourcePkgIndex _pkgConfigDB
                 preferences constraints targets =
     mapMessages $ topDownResolver'
                     platform cinfo
@@ -266,11 +269,11 @@
 --
 topDownResolver' :: Platform -> CompilerInfo
                  -> PackageIndex InstalledPackage
-                 -> PackageIndex SourcePackage
+                 -> PackageIndex UnresolvedSourcePackage
                  -> (PackageName -> PackagePreferences)
                  -> [PackageConstraint]
                  -> [PackageName]
-                 -> Progress Log Failure [ResolverPackage]
+                 -> Progress Log Failure [ResolverPackage UnresolvedPkgLoc]
 topDownResolver' platform cinfo installedPkgIndex sourcePkgIndex
                  preferences constraints targets =
       fmap (uncurry finalise)
@@ -298,7 +301,7 @@
       . PackageIndex.fromList
       $ finaliseSelectedPackages preferences selected' constraints'
 
-    toResolverPackage :: FinalSelectedPackage -> ResolverPackage
+    toResolverPackage :: FinalSelectedPackage -> ResolverPackage UnresolvedPkgLoc
     toResolverPackage (SelectedInstalled (InstalledPackage pkg _))
                                               = PreExisting pkg
     toResolverPackage (SelectedSource    pkg) = Configured  pkg
@@ -444,7 +447,7 @@
 --
 annotateSourcePackages :: [PackageConstraint]
                        -> (PackageName -> TopologicalSortNumber)
-                       -> PackageIndex SourcePackage
+                       -> PackageIndex UnresolvedSourcePackage
                        -> PackageIndex UnconfiguredPackage
 annotateSourcePackages constraints dfsNumber sourcePkgIndex =
     PackageIndex.fromList
@@ -481,7 +484,7 @@
 -- heuristic.
 --
 topologicalSortNumbering :: PackageIndex InstalledPackage
-                         -> PackageIndex SourcePackage
+                         -> PackageIndex UnresolvedSourcePackage
                          -> (PackageName -> TopologicalSortNumber)
 topologicalSortNumbering installedPkgIndex sourcePkgIndex =
     \pkgname -> let Just vertex = toVertex pkgname
@@ -508,17 +511,17 @@
 -- and looking at the names of all possible dependencies.
 --
 selectNeededSubset :: PackageIndex InstalledPackage
-                   -> PackageIndex SourcePackage
+                   -> PackageIndex UnresolvedSourcePackage
                    -> Set PackageName
                    -> (PackageIndex InstalledPackage
-                      ,PackageIndex SourcePackage)
+                      ,PackageIndex UnresolvedSourcePackage)
 selectNeededSubset installedPkgIndex sourcePkgIndex = select mempty mempty
   where
     select :: PackageIndex InstalledPackage
-           -> PackageIndex SourcePackage
+           -> PackageIndex UnresolvedSourcePackage
            -> Set PackageName
            -> (PackageIndex InstalledPackage
-              ,PackageIndex SourcePackage)
+              ,PackageIndex UnresolvedSourcePackage)
     select installedPkgIndex' sourcePkgIndex' remaining
       | Set.null remaining = (installedPkgIndex', sourcePkgIndex')
       | otherwise = select installedPkgIndex'' sourcePkgIndex'' remaining''
@@ -567,9 +570,9 @@
     | (_,ipkg:_) <- InstalledPackageIndex.allPackagesBySourcePackageId index' ]
   where
     -- The InstalledPackageInfo only lists dependencies by the
-    -- InstalledPackageId, which means we do not directly know the corresponding
+    -- UnitId, which means we do not directly know the corresponding
     -- source dependency. The only way to find out is to lookup the
-    -- InstalledPackageId to get the InstalledPackageInfo and look at its
+    -- UnitId to get the InstalledPackageInfo and look at its
     -- source PackageId. But if the package is broken because it depends on
     -- other packages that do not exist then we have a problem we cannot find
     -- the original source package id. Instead we make up a bogus package id.
@@ -578,10 +581,10 @@
     sourceDepsOf index ipkg =
       [ maybe (brokenPackageId depid) packageId mdep
       | let depids = InstalledPackageInfo.depends ipkg
-            getpkg = InstalledPackageIndex.lookupInstalledPackageId index
+            getpkg = InstalledPackageIndex.lookupUnitId index
       , (depid, mdep) <- zip depids (map getpkg depids) ]
 
-    brokenPackageId (InstalledPackageId str) =
+    brokenPackageId (SimpleUnitId (ComponentId str)) =
       PackageIdentifier (PackageName (str ++ "-broken")) (Version [] [])
 
 -- ------------------------------------------------------------
@@ -614,7 +617,8 @@
         -- We cheat in the cabal solver, and classify all dependencies as
         -- library dependencies.
         deps' :: ComponentDeps [ConfiguredId]
-        deps' = CD.fromLibraryDeps $ map (confId . pickRemaining mipkg) deps
+        deps' = CD.fromLibraryDeps (unPackageName (packageName pkg))
+                                   (map (confId . pickRemaining mipkg) deps)
 
     -- InstalledOrSource indicates that we either have a source package
     -- available, or an installed one, or both. In the case that we have both
@@ -644,7 +648,7 @@
     confId :: InstalledOrSource InstalledPackageEx UnconfiguredPackage -> ConfiguredId
     confId pkg = ConfiguredId {
         confSrcId  = packageId pkg
-      , confInstId = fakeInstalledPackageId (packageId pkg)
+      , confInstId = fakeUnitId (packageId pkg)
       }
 
     pickRemaining mipkg dep@(Dependency _name versionRange) =
@@ -669,9 +673,11 @@
         -- version constraints. TODO: distinguish hacks from prefs
         bounded = boundedAbove versionRange
         isPreferred p
-          | bounded   = True -- any constant will do
-          | otherwise = packageVersion p `withinRange` preferredVersions
-          where (PackagePreferences preferredVersions _) = pref (packageName p)
+          | bounded   = boundedRank -- this is a dummy constant
+          | otherwise = length . filter (packageVersion p `withinRange`) $
+                        preferredVersions
+          where (PackagePreferences preferredVersions _ _) = pref (packageName p)
+        boundedRank = 0 -- any value will do
 
         boundedAbove :: VersionRange -> Bool
         boundedAbove vr = case asVersionIntervals vr of
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs b/cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs
@@ -14,7 +14,8 @@
 module Distribution.Client.Dependency.TopDown.Types where
 
 import Distribution.Client.Types
-         ( SourcePackage(..), ConfiguredPackage(..)
+         ( ConfiguredPackage(..)
+         , UnresolvedPkgLoc, UnresolvedSourcePackage
          , OptionalStanza, ConfiguredId(..) )
 import Distribution.InstalledPackageInfo
          ( InstalledPackageInfo )
@@ -44,7 +45,7 @@
 
 data FinalSelectedPackage
    = SelectedInstalled InstalledPackage
-   | SelectedSource    ConfiguredPackage
+   | SelectedSource    (ConfiguredPackage UnresolvedPkgLoc)
 
 type TopologicalSortNumber = Int
 
@@ -62,18 +63,18 @@
 
 data UnconfiguredPackage
    = UnconfiguredPackage
-       SourcePackage
+       UnresolvedSourcePackage
        !TopologicalSortNumber
        FlagAssignment
        [OptionalStanza]
 
 data SemiConfiguredPackage
    = SemiConfiguredPackage
-       SourcePackage     -- package info
-       FlagAssignment    -- total flag assignment for the package
-       [OptionalStanza]  -- enabled optional stanzas
-       [Dependency]      -- dependencies we end up with when we apply
-                         -- the flag assignment
+       UnresolvedSourcePackage           -- package info
+       FlagAssignment                    -- total flag assignment for the package
+       [OptionalStanza]                  -- enabled optional stanzas
+       [Dependency]                      -- dependencies we end up with when we apply
+                                         -- the flag assignment
 
 instance Package InstalledPackage where
   packageId (InstalledPackage pkg _) = packageId pkg
@@ -113,9 +114,9 @@
 --
 -- The top-down solver uses its down type class for package dependencies,
 -- because it wants to know these dependencies as PackageIds, rather than as
--- InstalledPackageIds (so it cannot use PackageFixedDeps).
+-- ComponentIds (so it cannot use PackageFixedDeps).
 --
--- Ideally we would switch the top-down solver over to use InstalledPackageIds
+-- Ideally we would switch the top-down solver over to use ComponentIds
 -- throughout; that means getting rid of this type class, and changing over the
 -- package index type to use Cabal's rather than cabal-install's. That will
 -- avoid the need for the local definitions of dependencyGraph and
@@ -131,8 +132,8 @@
 instance PackageSourceDeps InstalledPackageEx where
   sourceDeps (InstalledPackageEx _ _ deps) = deps
 
-instance PackageSourceDeps ConfiguredPackage where
-  sourceDeps (ConfiguredPackage _ _ _ deps) = map confSrcId $ CD.nonSetupDeps deps
+instance PackageSourceDeps (ConfiguredPackage loc) where
+  sourceDeps cpkg = map confSrcId $ CD.nonSetupDeps (confPkgDeps cpkg)
 
 instance PackageSourceDeps InstalledPackage where
   sourceDeps (InstalledPackage _ deps) = deps
diff --git a/cabal/cabal-install/Distribution/Client/Dependency/Types.hs b/cabal/cabal-install/Distribution/Client/Dependency/Types.hs
--- a/cabal/cabal-install/Distribution/Client/Dependency/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/Dependency/Types.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Dependency.Types
@@ -18,7 +19,6 @@
     DependencyResolver,
     ResolverPackage(..),
 
-    AllowNewer(..), isAllowNewer,
     PackageConstraint(..),
     showPackageConstraint,
     PackagePreferences(..),
@@ -32,6 +32,7 @@
     ConstraintSource(..),
     unlabelPackageConstraint,
     showConstraintSource
+
   ) where
 
 #if !MIN_VERSION_base(4,8,0)
@@ -48,6 +49,8 @@
          ( Monoid(..) )
 #endif
 
+import Distribution.Client.PkgConfigDb
+         ( PkgConfigDb )
 import Distribution.Client.Types
          ( OptionalStanza(..), SourcePackage(..), ConfiguredPackage )
 
@@ -73,18 +76,23 @@
 
 import Text.PrettyPrint
          ( text )
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary(..))
 
 import Prelude hiding (fail)
 
 
 -- | All the solvers that can be selected.
 data PreSolver = AlwaysTopDown | AlwaysModular | Choose
-  deriving (Eq, Ord, Show, Bounded, Enum)
+  deriving (Eq, Ord, Show, Bounded, Enum, Generic)
 
 -- | All the solvers that can be used.
 data Solver = TopDown | Modular
-  deriving (Eq, Ord, Show, Bounded, Enum)
+  deriving (Eq, Ord, Show, Bounded, Enum, Generic)
 
+instance Binary PreSolver
+instance Binary Solver
+
 instance Text PreSolver where
   disp AlwaysTopDown = text "topdown"
   disp AlwaysModular = text "modular"
@@ -105,22 +113,23 @@
 -- solving the package dependency problem and we want to make it easy to swap
 -- in alternatives.
 --
-type DependencyResolver = Platform
-                       -> CompilerInfo
-                       -> InstalledPackageIndex
-                       ->          PackageIndex.PackageIndex SourcePackage
-                       -> (PackageName -> PackagePreferences)
-                       -> [LabeledPackageConstraint]
-                       -> [PackageName]
-                       -> Progress String String [ResolverPackage]
+type DependencyResolver loc = Platform
+                           -> CompilerInfo
+                           -> InstalledPackageIndex
+                           -> PackageIndex.PackageIndex (SourcePackage loc)
+                           -> PkgConfigDb
+                           -> (PackageName -> PackagePreferences)
+                           -> [LabeledPackageConstraint]
+                           -> [PackageName]
+                           -> Progress String String [ResolverPackage loc]
 
 -- | The dependency resolver picks either pre-existing installed packages
 -- or it picks source packages along with package configuration.
 --
 -- This is like the 'InstallPlan.PlanPackage' but with fewer cases.
 --
-data ResolverPackage = PreExisting InstalledPackageInfo
-                     | Configured  ConfiguredPackage
+data ResolverPackage loc = PreExisting InstalledPackageInfo
+                         | Configured  (ConfiguredPackage loc)
 
 -- | Per-package constraints. Package constraints must be respected by the
 -- solver. Multiple constraints for each package can be given, though obviously
@@ -133,8 +142,10 @@
    | PackageConstraintSource    PackageName
    | PackageConstraintFlags     PackageName FlagAssignment
    | PackageConstraintStanzas   PackageName [OptionalStanza]
-  deriving (Show,Eq)
+  deriving (Eq,Show,Generic)
 
+instance Binary PackageConstraint
+
 -- | Provide a textual representation of a package constraint
 -- for debugging purposes.
 --
@@ -156,17 +167,20 @@
     showStanza TestStanzas  = "test"
     showStanza BenchStanzas = "bench"
 
--- | A per-package preference on the version. It is a soft constraint that the
+-- | Per-package preferences on the version. It is a soft constraint that the
 -- 'DependencyResolver' should try to respect where possible. It consists of
--- a 'InstalledPreference' which says if we prefer versions of packages
--- that are already installed. It also has a 'PackageVersionPreference' which
--- is a suggested constraint on the version number. The resolver should try to
--- use package versions that satisfy the suggested version constraint.
+-- an 'InstalledPreference' which says if we prefer versions of packages
+-- that are already installed. It also has (possibly multiple)
+-- 'PackageVersionPreference's which are suggested constraints on the version
+-- number. The resolver should try to use package versions that satisfy
+-- the maximum number of the suggested version constraints.
 --
 -- It is not specified if preferences on some packages are more important than
 -- others.
 --
-data PackagePreferences = PackagePreferences VersionRange InstalledPreference
+data PackagePreferences = PackagePreferences [VersionRange]
+                                             InstalledPreference
+                                             [OptionalStanza]
 
 -- | Whether we prefer an installed version of a package or simply the latest
 -- version.
@@ -199,28 +213,6 @@
    | PreferLatestForSelected
   deriving Show
 
--- | Policy for relaxing upper bounds in dependencies. For example, given
--- 'build-depends: array >= 0.3 && < 0.5', are we allowed to relax the upper
--- bound and choose a version of 'array' that is greater or equal to 0.5? By
--- default the upper bounds are always strictly honored.
-data AllowNewer =
-
-  -- | Default: honor the upper bounds in all dependencies, never choose
-  -- versions newer than allowed.
-  AllowNewerNone
-
-  -- | Ignore upper bounds in dependencies on the given packages.
-  | AllowNewerSome [PackageName]
-
-  -- | Ignore upper bounds in dependencies on all packages.
-  | AllowNewerAll
-
--- | Convert 'AllowNewer' to a boolean.
-isAllowNewer :: AllowNewer -> Bool
-isAllowNewer AllowNewerNone     = False
-isAllowNewer (AllowNewerSome _) = True
-isAllowNewer AllowNewerAll      = True
-
 -- | A type to represent the unfolding of an expensive long running
 -- calculation that may fail. We may get intermediate steps before the final
 -- result which may be used to indicate progress and\/or logging messages.
@@ -245,7 +237,7 @@
         fold (Done r)   = done r
 
 instance Monad (Progress step fail) where
-  return a = Done a
+  return   = pure
   p >>= f  = foldProgress Step Fail f p
 
 instance Applicative (Progress step fail) where
@@ -269,11 +261,14 @@
   -- | Main config file, which is ~/.cabal/config by default.
   ConstraintSourceMainConfig FilePath
 
+  -- | Local cabal.project file
+  | ConstraintSourceProjectConfig FilePath
+
   -- | Sandbox config file, which is ./cabal.sandbox.config by default.
   | ConstraintSourceSandboxConfig FilePath
 
-  -- | ./cabal.config.
-  | ConstraintSourceUserConfig
+  -- | User config file, which is ./cabal.config by default.
+  | ConstraintSourceUserConfig FilePath
 
   -- | Flag specified on the command line.
   | ConstraintSourceCommandlineFlag
@@ -298,15 +293,19 @@
 
   -- | The source of the constraint is not specified.
   | ConstraintSourceUnknown
-  deriving (Eq, Show)
+  deriving (Eq, Show, Generic)
 
+instance Binary ConstraintSource
+
 -- | Description of a 'ConstraintSource'.
 showConstraintSource :: ConstraintSource -> String
 showConstraintSource (ConstraintSourceMainConfig path) =
     "main config " ++ path
+showConstraintSource (ConstraintSourceProjectConfig path) =
+    "project config " ++ path
 showConstraintSource (ConstraintSourceSandboxConfig path) =
     "sandbox config " ++ path
-showConstraintSource ConstraintSourceUserConfig = "cabal.config"
+showConstraintSource (ConstraintSourceUserConfig path)= "user config " ++ path
 showConstraintSource ConstraintSourceCommandlineFlag = "command line flag"
 showConstraintSource ConstraintSourceUserTarget = "user target"
 showConstraintSource ConstraintSourceNonUpgradeablePackage =
diff --git a/cabal/cabal-install/Distribution/Client/DistDirLayout.hs b/cabal/cabal-install/Distribution/Client/DistDirLayout.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/DistDirLayout.hs
@@ -0,0 +1,134 @@
+{-# LANGUAGE RecordWildCards #-}
+
+-- | 
+--
+-- The layout of the .\/dist\/ directory where cabal keeps all of it's state
+-- and build artifacts.
+--
+module Distribution.Client.DistDirLayout where
+
+import System.FilePath
+import Distribution.Package
+         ( PackageId )
+import Distribution.Compiler
+import Distribution.Simple.Compiler (PackageDB(..))
+import Distribution.Text
+import Distribution.Client.Types
+         ( InstalledPackageId )
+
+
+
+-- | The layout of the project state directory. Traditionally this has been
+-- called the @dist@ directory.
+--
+data DistDirLayout = DistDirLayout {
+
+        -- | The dist directory, which is the root of where cabal keeps all its
+       -- state including the build artifacts from each package we build.
+       --
+       distDirectory                :: FilePath,
+
+       -- | 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
+       -- we can have multiple instances of the same package in a solution
+       -- (e.g. setup deps).
+       --
+       distBuildDirectory           :: PackageId -> FilePath,
+       distBuildRootDirectory       :: FilePath,
+
+       -- | The directory under dist where we put the unpacked sources of
+       -- packages, in those cases where it makes sense to keep the build
+       -- artifacts to reduce rebuild times. These can be tarballs or could be
+       -- scm repos.
+       --
+       distUnpackedSrcDirectory     :: PackageId -> FilePath,
+       distUnpackedSrcRootDirectory :: FilePath,
+
+       -- | The location for project-wide cache files (e.g. state used in
+       -- incremental rebuilds).
+       --
+       distProjectCacheFile         :: String -> FilePath,
+       distProjectCacheDirectory    :: FilePath,
+
+       -- | The location for package-specific cache files (e.g. state used in
+       -- incremental rebuilds).
+       --
+       distPackageCacheFile         :: PackageId -> String -> FilePath,
+       distPackageCacheDirectory    :: PackageId -> FilePath,
+
+       distTempDirectory            :: FilePath,
+       distBinDirectory             :: FilePath,
+
+       distPackageDB                :: CompilerId -> PackageDB
+     }
+
+
+
+--TODO: move to another module, e.g. CabalDirLayout?
+data CabalDirLayout = CabalDirLayout {
+       cabalStoreDirectory        :: CompilerId -> FilePath,
+       cabalStorePackageDirectory :: CompilerId -> InstalledPackageId
+                                                -> FilePath,
+       cabalStorePackageDBPath    :: CompilerId -> FilePath,
+       cabalStorePackageDB        :: CompilerId -> PackageDB,
+
+       cabalPackageCacheDirectory :: FilePath,
+       cabalLogsDirectory         :: FilePath,
+       cabalWorldFile             :: FilePath
+     }
+
+
+defaultDistDirLayout :: FilePath -> DistDirLayout
+defaultDistDirLayout projectRootDirectory =
+    DistDirLayout {..}
+  where
+    distDirectory = projectRootDirectory </> "dist-newstyle"
+    --TODO: switch to just dist at some point, or some other new name
+
+    distBuildRootDirectory   = distDirectory </> "build"
+    distBuildDirectory pkgid = distBuildRootDirectory </> display pkgid
+
+    distUnpackedSrcRootDirectory   = distDirectory </> "src"
+    distUnpackedSrcDirectory pkgid = distUnpackedSrcRootDirectory
+                                      </> display pkgid
+
+    distProjectCacheDirectory = distDirectory </> "cache"
+    distProjectCacheFile name = distProjectCacheDirectory </> name
+
+    distPackageCacheDirectory pkgid = distBuildDirectory pkgid </> "cache"
+    distPackageCacheFile pkgid name = distPackageCacheDirectory pkgid </> name
+
+    distTempDirectory = distDirectory </> "tmp"
+
+    distBinDirectory = distDirectory </> "bin"
+
+    distPackageDBPath compid = distDirectory </> "packagedb" </> display compid
+    distPackageDB = SpecificPackageDB . distPackageDBPath
+
+
+
+defaultCabalDirLayout :: FilePath -> CabalDirLayout
+defaultCabalDirLayout cabalDir =
+    CabalDirLayout {..}
+  where
+
+    cabalStoreDirectory compid =
+      cabalDir </> "store" </> display compid
+
+    cabalStorePackageDirectory compid ipkgid = 
+      cabalStoreDirectory compid </> display ipkgid
+
+    cabalStorePackageDBPath compid =
+      cabalStoreDirectory compid </> "package.db"
+
+    cabalStorePackageDB =
+      SpecificPackageDB . cabalStorePackageDBPath
+
+    cabalPackageCacheDirectory = cabalDir </> "packages"
+
+    cabalLogsDirectory = cabalDir </> "logs"
+
+    cabalWorldFile = cabalDir </> "world"
+
diff --git a/cabal/cabal-install/Distribution/Client/Exec.hs b/cabal/cabal-install/Distribution/Client/Exec.hs
--- a/cabal/cabal-install/Distribution/Client/Exec.hs
+++ b/cabal/cabal-install/Distribution/Client/Exec.hs
@@ -14,8 +14,6 @@
 
 import Control.Monad (unless)
 
-import Data.Foldable (forM_)
-
 import qualified Distribution.Simple.GHC   as GHC
 import qualified Distribution.Simple.GHCJS as GHCJS
 
@@ -65,7 +63,7 @@
 
         [] -> die "Please specify an executable to run"
   where
-    environmentOverrides = 
+    environmentOverrides =
         case useSandbox of
             NoSandbox -> return []
             (UseSandbox sandboxDir) ->
@@ -91,21 +89,18 @@
         let Just program = lookupProgram hcProgram programDb
         gDb <- getGlobalPackageDB verbosity program
         sandboxConfigFilePath <- getSandboxConfigFilePath mempty
-        let compilerPackagePath = hcPackagePath gDb
+        let sandboxPackagePath   = sandboxPackageDBPath sandboxDir comp platform
+            compilerPackagePaths = prependToSearchPath gDb sandboxPackagePath
         -- Packages database must exist, otherwise things will start
         -- failing in mysterious ways.
-        forM_ compilerPackagePath $ \fp -> do
-          exists <- doesDirectoryExist fp
-          unless exists $ warn verbosity $ "Package database is not a directory: " ++ fp
+        exists <- doesDirectoryExist sandboxPackagePath
+        unless exists $ warn verbosity $ "Package database is not a directory: "
+                                           ++ sandboxPackagePath
         -- Build the environment
-        return [ (packagePathEnvVar, compilerPackagePath)
-               , ("CABAL_SANDBOX_PACKAGE_PATH", compilerPackagePath)
+        return [ (packagePathEnvVar, Just compilerPackagePaths)
+               , ("CABAL_SANDBOX_PACKAGE_PATH", Just compilerPackagePaths)
                , ("CABAL_SANDBOX_CONFIG", Just sandboxConfigFilePath)
                ]
-
-    hcPackagePath gDb =
-        let s = sandboxPackageDBPath sandboxDir comp platform
-            in Just $ prependToSearchPath gDb s
 
     prependToSearchPath path newValue =
         newValue ++ [searchPathSeparator] ++ path
diff --git a/cabal/cabal-install/Distribution/Client/Fetch.hs b/cabal/cabal-install/Distribution/Client/Fetch.hs
--- a/cabal/cabal-install/Distribution/Client/Fetch.hs
+++ b/cabal/cabal-install/Distribution/Client/Fetch.hs
@@ -21,11 +21,11 @@
 import Distribution.Client.Dependency
 import Distribution.Client.IndexUtils as IndexUtils
          ( getSourcePackages, getInstalledPackages )
-import Distribution.Client.HttpUtils
-         ( configureTransport, HttpTransport(..) )
 import qualified Distribution.Client.InstallPlan as InstallPlan
+import Distribution.Client.PkgConfigDb
+         ( PkgConfigDb, readPkgConfigDb )
 import Distribution.Client.Setup
-         ( GlobalFlags(..), FetchFlags(..) )
+         ( GlobalFlags(..), FetchFlags(..), RepoContext(..) )
 
 import Distribution.Package
          ( packageId )
@@ -35,7 +35,7 @@
 import Distribution.Simple.Program
          ( ProgramConfiguration )
 import Distribution.Simple.Setup
-         ( fromFlag, flagToMaybe )
+         ( fromFlag )
 import Distribution.Simple.Utils
          ( die, notice, debug )
 import Distribution.System
@@ -66,7 +66,7 @@
 --
 fetch :: Verbosity
       -> PackageDBStack
-      -> [Repo]
+      -> RepoContext
       -> Compiler
       -> Platform
       -> ProgramConfiguration
@@ -77,24 +77,23 @@
 fetch verbosity _ _ _ _ _ _ _ [] =
     notice verbosity "No packages requested. Nothing to do."
 
-fetch verbosity packageDBs repos comp platform conf
+fetch verbosity packageDBs repoCtxt comp platform conf
       globalFlags fetchFlags userTargets = do
 
     mapM_ checkTarget userTargets
 
     installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-    sourcePkgDb       <- getSourcePackages    verbosity repos
-
-    transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))
+    sourcePkgDb       <- getSourcePackages    verbosity repoCtxt
+    pkgConfigDb       <- readPkgConfigDb      verbosity conf
 
-    pkgSpecifiers <- resolveUserTargets verbosity transport
+    pkgSpecifiers <- resolveUserTargets verbosity repoCtxt
                        (fromFlag $ globalWorldFile globalFlags)
                        (packageIndex sourcePkgDb)
                        userTargets
 
     pkgs  <- planPackages
                verbosity comp platform fetchFlags
-               installedPkgIndex sourcePkgDb pkgSpecifiers
+               installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers
 
     pkgs' <- filterM (fmap not . isFetched . packageSource) pkgs
     if null pkgs'
@@ -109,7 +108,7 @@
                      "The following packages would be fetched:"
                    : map (display . packageId) pkgs'
 
-             else mapM_ (fetchPackage transport verbosity . packageSource) pkgs'
+             else mapM_ (fetchPackage verbosity repoCtxt . packageSource) pkgs'
 
   where
     dryRun = fromFlag (fetchDryRun fetchFlags)
@@ -120,10 +119,11 @@
              -> FetchFlags
              -> InstalledPackageIndex
              -> SourcePackageDb
-             -> [PackageSpecifier SourcePackage]
-             -> IO [SourcePackage]
+             -> PkgConfigDb
+             -> [PackageSpecifier UnresolvedSourcePackage]
+             -> IO [UnresolvedSourcePackage]
 planPackages verbosity comp platform fetchFlags
-             installedPkgIndex sourcePkgDb pkgSpecifiers
+             installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers
 
   | includeDependencies = do
       solver <- chooseSolver verbosity
@@ -131,15 +131,15 @@
       notice verbosity "Resolving dependencies..."
       installPlan <- foldProgress logMsg die return $
                        resolveDependencies
-                         platform (compilerInfo comp)
+                         platform (compilerInfo comp) pkgConfigDb
                          solver
                          resolverParams
 
       -- The packages we want to fetch are those packages the 'InstallPlan'
       -- that are in the 'InstallPlan.Configured' state.
       return
-        [ pkg
-        | (InstallPlan.Configured (ConfiguredPackage pkg _ _ _))
+        [ confPkgSource cpkg
+        | (InstallPlan.Configured cpkg)
             <- InstallPlan.toList installPlan ]
 
   | otherwise =
@@ -185,8 +185,8 @@
             ++ "In the meantime you can use the 'unpack' commands."
     _ -> return ()
 
-fetchPackage :: HttpTransport -> Verbosity -> PackageLocation a -> IO ()
-fetchPackage transport verbosity pkgsrc = case pkgsrc of
+fetchPackage :: Verbosity -> RepoContext -> PackageLocation a -> IO ()
+fetchPackage verbosity repoCtxt pkgsrc = case pkgsrc of
     LocalUnpackedPackage _dir  -> return ()
     LocalTarballPackage  _file -> return ()
 
@@ -195,5 +195,5 @@
          ++ "In the meantime you can use the 'unpack' commands."
 
     RepoTarballPackage repo pkgid _ -> do
-      _ <- fetchRepoTarball transport verbosity repo pkgid
+      _ <- fetchRepoTarball verbosity repoCtxt repo pkgid
       return ()
diff --git a/cabal/cabal-install/Distribution/Client/FetchUtils.hs b/cabal/cabal-install/Distribution/Client/FetchUtils.hs
--- a/cabal/cabal-install/Distribution/Client/FetchUtils.hs
+++ b/cabal/cabal-install/Distribution/Client/FetchUtils.hs
@@ -11,6 +11,7 @@
 --
 -- Functions for fetching packages
 -----------------------------------------------------------------------------
+{-# LANGUAGE RecordWildCards #-}
 module Distribution.Client.FetchUtils (
 
     -- * fetching packages
@@ -38,6 +39,8 @@
          ( display )
 import Distribution.Verbosity
          ( Verbosity )
+import Distribution.Client.GlobalFlags
+         ( RepoContext(..) )
 
 import Data.Maybe
 import System.Directory
@@ -51,6 +54,8 @@
 import Network.URI
          ( URI(uriPath) )
 
+import qualified Hackage.Security.Client as Sec
+
 -- ------------------------------------------------------------
 -- * Actually fetch things
 -- ------------------------------------------------------------
@@ -58,7 +63,7 @@
 -- | Returns @True@ if the package has already been fetched
 -- or does not need fetching.
 --
-isFetched :: PackageLocation (Maybe FilePath) -> IO Bool
+isFetched :: UnresolvedPkgLoc -> IO Bool
 isFetched loc = case loc of
     LocalUnpackedPackage _dir       -> return True
     LocalTarballPackage  _file      -> return True
@@ -66,8 +71,8 @@
     RepoTarballPackage repo pkgid _ -> doesFileExist (packageFile repo pkgid)
 
 
-checkFetched :: PackageLocation (Maybe FilePath)
-             -> IO (Maybe (PackageLocation FilePath))
+checkFetched :: UnresolvedPkgLoc
+             -> IO (Maybe ResolvedPkgLoc)
 checkFetched loc = case loc of
     LocalUnpackedPackage dir  ->
       return (Just $ LocalUnpackedPackage dir)
@@ -89,11 +94,11 @@
 
 -- | Fetch a package if we don't have it already.
 --
-fetchPackage :: HttpTransport
-             -> Verbosity
-             -> PackageLocation (Maybe FilePath)
-             -> IO (PackageLocation FilePath)
-fetchPackage transport verbosity loc = case loc of
+fetchPackage :: Verbosity
+             -> RepoContext
+             -> UnresolvedPkgLoc
+             -> IO ResolvedPkgLoc
+fetchPackage verbosity repoCtxt loc = case loc of
     LocalUnpackedPackage dir  ->
       return (LocalUnpackedPackage dir)
     LocalTarballPackage  file ->
@@ -107,10 +112,11 @@
       path <- downloadTarballPackage uri
       return (RemoteTarballPackage uri path)
     RepoTarballPackage repo pkgid Nothing -> do
-      local <- fetchRepoTarball transport verbosity repo pkgid
+      local <- fetchRepoTarball verbosity repoCtxt repo pkgid
       return (RepoTarballPackage repo pkgid local)
   where
     downloadTarballPackage uri = do
+      transport <- repoContextGetTransport repoCtxt
       transportCheckHttps transport uri
       notice verbosity ("Downloading " ++ show uri)
       tmpdir <- getTemporaryDirectory
@@ -122,8 +128,8 @@
 
 -- | Fetch a repo package if we don't have it already.
 --
-fetchRepoTarball :: HttpTransport -> Verbosity -> Repo -> PackageId -> IO FilePath
-fetchRepoTarball transport verbosity repo pkgid = do
+fetchRepoTarball :: Verbosity -> RepoContext -> Repo -> PackageId -> IO FilePath
+fetchRepoTarball verbosity repoCtxt repo pkgid = do
   fetched <- doesFileExist (packageFile repo pkgid)
   if fetched
     then do info verbosity $ display pkgid ++ " has already been downloaded."
@@ -131,16 +137,26 @@
     else do setupMessage verbosity "Downloading" pkgid
             downloadRepoPackage
   where
-    downloadRepoPackage = case repoKind repo of
-      Right LocalRepo -> return (packageFile repo pkgid)
+    downloadRepoPackage = case repo of
+      RepoLocal{..} -> return (packageFile repo pkgid)
 
-      Left remoteRepo -> do
-        remoteRepoCheckHttps transport remoteRepo
-        let uri  = packageURI remoteRepo pkgid
-            dir  = packageDir       repo pkgid
-            path = packageFile      repo pkgid
+      RepoRemote{..} -> do
+        transport <- repoContextGetTransport repoCtxt
+        remoteRepoCheckHttps transport repoRemote
+        let uri  = packageURI  repoRemote pkgid
+            dir  = packageDir  repo       pkgid
+            path = packageFile repo       pkgid
         createDirectoryIfMissing True dir
         _ <- downloadURI transport verbosity uri path
+        return path
+
+      RepoSecure{} -> repoContextWithSecureRepo repoCtxt repo $ \rep -> do
+        let dir  = packageDir  repo pkgid
+            path = packageFile repo pkgid
+        createDirectoryIfMissing True dir
+        Sec.uncheckClientErrors $ do
+          info verbosity ("writing " ++ path)
+          Sec.downloadPackage' rep pkgid path
         return path
 
 -- | Downloads an index file to [config-dir/packages/serv-id].
diff --git a/cabal/cabal-install/Distribution/Client/FileMonitor.hs b/cabal/cabal-install/Distribution/Client/FileMonitor.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/FileMonitor.hs
@@ -0,0 +1,1101 @@
+{-# LANGUAGE CPP, DeriveGeneric, DeriveFunctor, GeneralizedNewtypeDeriving,
+             NamedFieldPuns, BangPatterns #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+-- | An abstraction to help with re-running actions when files or other
+-- input values they depend on have changed.
+--
+module Distribution.Client.FileMonitor (
+
+  -- * Declaring files to monitor
+  MonitorFilePath(..),
+  MonitorKindFile(..),
+  MonitorKindDir(..),
+  FilePathGlob(..),
+  monitorFile,
+  monitorFileHashed,
+  monitorNonExistentFile,
+  monitorDirectory,
+  monitorDirectoryExistence,
+  monitorFileOrDirectory,
+  monitorFileGlob,
+  monitorFileSearchPath,
+  monitorFileHashedSearchPath,
+
+  -- * Creating and checking sets of monitored files
+  FileMonitor(..),
+  newFileMonitor,
+  MonitorChanged(..),
+  MonitorChangedReason(..),
+  checkFileMonitorChanged,
+  updateFileMonitor,
+  MonitorTimestamp,
+  beginUpdateFileMonitor,
+  ) where
+
+
+#if MIN_VERSION_containers(0,5,0)
+import           Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+#else
+import           Data.Map        (Map)
+import qualified Data.Map        as Map
+#endif
+import qualified Data.ByteString.Lazy as BS
+import           Distribution.Compat.Binary
+import qualified Distribution.Compat.Binary as Binary
+import qualified Data.Hashable as Hashable
+import           Data.List (sort)
+
+#if !MIN_VERSION_base(4,8,0)
+import           Control.Applicative
+#endif
+import           Control.Monad
+import           Control.Monad.Trans (MonadIO, liftIO)
+import           Control.Monad.State (StateT, mapStateT)
+import qualified Control.Monad.State as State
+import           Control.Monad.Except (ExceptT, runExceptT, withExceptT,
+                                       throwError)
+import           Control.Exception
+
+import           Distribution.Client.Compat.Time
+import           Distribution.Client.Glob
+import           Distribution.Simple.Utils (handleDoesNotExist, writeFileAtomic)
+import           Distribution.Client.Utils (mergeBy, MergeResult(..))
+
+import           System.FilePath
+import           System.Directory
+import           System.IO
+import           GHC.Generics (Generic)
+
+
+------------------------------------------------------------------------------
+-- Types for specifying files to monitor
+--
+
+
+-- | A description of a file (or set of files) to monitor for changes.
+--
+-- Where file paths are relative they are relative to a common directory
+-- (e.g. project root), not necessarily the process current directory.
+--
+data MonitorFilePath =
+     MonitorFile {
+       monitorKindFile :: !MonitorKindFile,
+       monitorKindDir  :: !MonitorKindDir,
+       monitorPath     :: !FilePath
+     }
+   | MonitorFileGlob {
+       monitorKindFile :: !MonitorKindFile,
+       monitorKindDir  :: !MonitorKindDir,
+       monitorPathGlob :: !FilePathGlob
+     }
+  deriving (Eq, Show, Generic)
+
+data MonitorKindFile = FileExists
+                     | FileModTime
+                     | FileHashed
+                     | FileNotExists
+  deriving (Eq, Show, Generic)
+
+data MonitorKindDir  = DirExists
+                     | DirModTime
+                     | DirNotExists
+  deriving (Eq, Show, Generic)
+
+instance Binary MonitorFilePath
+instance Binary MonitorKindFile
+instance Binary MonitorKindDir
+
+-- | Monitor a single file for changes, based on its modification time.
+-- The monitored file is considered to have changed if it no longer
+-- exists or if its modification time has changed.
+--
+monitorFile :: FilePath -> MonitorFilePath
+monitorFile = MonitorFile FileModTime DirNotExists
+
+-- | Monitor a single file for changes, based on its modification time
+-- and content hash. The monitored file is considered to have changed if
+-- it no longer exists or if its modification time and content hash have
+-- changed.
+--
+monitorFileHashed :: FilePath -> MonitorFilePath
+monitorFileHashed = MonitorFile FileHashed DirNotExists
+
+-- | Monitor a single non-existent file for changes. The monitored file
+-- is considered to have changed if it exists.
+--
+monitorNonExistentFile :: FilePath -> MonitorFilePath
+monitorNonExistentFile = MonitorFile FileNotExists DirNotExists
+
+-- | Monitor a single directory for changes, based on its modification
+-- time. The monitored directory is considered to have changed if it no
+-- longer exists or if its modification time has changed.
+--
+monitorDirectory :: FilePath -> MonitorFilePath
+monitorDirectory = MonitorFile FileNotExists DirModTime
+
+-- | Monitor a single directory for existence. The monitored directory is
+-- considered to have changed only if it no longer exists.
+--
+monitorDirectoryExistence :: FilePath -> MonitorFilePath
+monitorDirectoryExistence = MonitorFile FileNotExists DirExists
+
+-- | Monitor a single file or directory for changes, based on its modification
+-- time. The monitored file is considered to have changed if it no longer
+-- exists or if its modification time has changed.
+--
+monitorFileOrDirectory :: FilePath -> MonitorFilePath
+monitorFileOrDirectory = MonitorFile FileModTime DirModTime
+
+-- | Monitor a set of files (or directories) identified by a file glob.
+-- The monitored glob is considered to have changed if the set of files
+-- matching the glob changes (i.e. creations or deletions), or for files if the
+-- modification time and content hash of any matching file has changed.
+--
+monitorFileGlob :: FilePathGlob -> MonitorFilePath
+monitorFileGlob = MonitorFileGlob FileHashed DirExists
+
+-- | Creates a list of files to monitor when you search for a file which
+-- unsuccessfully looked in @notFoundAtPaths@ before finding it at
+-- @foundAtPath@.
+monitorFileSearchPath :: [FilePath] -> FilePath -> [MonitorFilePath]
+monitorFileSearchPath notFoundAtPaths foundAtPath =
+    monitorFile foundAtPath
+  : map monitorNonExistentFile notFoundAtPaths
+
+-- | Similar to 'monitorFileSearchPath', but also instructs us to
+-- monitor the hash of the found file.
+monitorFileHashedSearchPath :: [FilePath] -> FilePath -> [MonitorFilePath]
+monitorFileHashedSearchPath notFoundAtPaths foundAtPath =
+    monitorFileHashed foundAtPath
+  : map monitorNonExistentFile notFoundAtPaths
+
+
+------------------------------------------------------------------------------
+-- Implementation types, files status
+--
+
+-- | The state necessary to determine whether a set of monitored
+-- files has changed.  It consists of two parts: a set of specific
+-- files to be monitored (index by their path), and a list of
+-- globs, which monitor may files at once.
+data MonitorStateFileSet
+   = MonitorStateFileSet !(Map FilePath MonitorStateFile)
+                         ![MonitorStateGlob]
+  deriving Show
+
+type Hash = Int
+
+-- | The state necessary to determine whether a monitored file has changed.
+--
+-- This covers all the cases of 'MonitorFilePath' except for globs which is
+-- covered separately by 'MonitorStateGlob'.
+--
+-- The @Maybe ModTime@ is to cover the case where we already consider the
+-- file to have changed, either because it had already changed by the time we
+-- did the snapshot (i.e. too new, changed since start of update process) or it
+-- no longer exists at all.
+--
+data MonitorStateFile = MonitorStateFile !MonitorKindFile !MonitorKindDir
+                                         !MonitorStateFileStatus
+  deriving (Show, Generic)
+
+data MonitorStateFileStatus
+   = MonitorStateFileExists
+   | MonitorStateFileModTime !ModTime        -- ^ cached file mtime
+   | MonitorStateFileHashed  !ModTime !Hash  -- ^ cached mtime and content hash
+   | MonitorStateDirExists
+   | MonitorStateDirModTime  !ModTime        -- ^ cached dir mtime
+   | MonitorStateNonExistent
+   | MonitorStateAlreadyChanged
+  deriving (Show, Generic)
+
+instance Binary MonitorStateFile
+instance Binary MonitorStateFileStatus
+
+-- | The state necessary to determine whether the files matched by a globbing
+-- match have changed.
+--
+data MonitorStateGlob = MonitorStateGlob !MonitorKindFile !MonitorKindDir
+                                         !FilePathRoot !MonitorStateGlobRel
+  deriving (Show, Generic)
+
+data MonitorStateGlobRel
+   = MonitorStateGlobDirs
+       !Glob !FilePathGlobRel
+       !ModTime
+       ![(FilePath, MonitorStateGlobRel)] -- invariant: sorted
+
+   | MonitorStateGlobFiles
+       !Glob
+       !ModTime
+       ![(FilePath, MonitorStateFileStatus)] -- invariant: sorted
+
+   | MonitorStateGlobDirTrailing
+  deriving (Show, Generic)
+
+instance Binary MonitorStateGlob
+instance Binary MonitorStateGlobRel
+
+-- | We can build a 'MonitorStateFileSet' from a set of 'MonitorFilePath' by
+-- inspecting the state of the file system, and we can go in the reverse
+-- direction by just forgetting the extra info.
+--
+reconstructMonitorFilePaths :: MonitorStateFileSet -> [MonitorFilePath]
+reconstructMonitorFilePaths (MonitorStateFileSet singlePaths globPaths) =
+    Map.foldrWithKey (\k x r -> getSinglePath k x : r)
+                     (map getGlobPath globPaths)
+                     singlePaths
+  where
+    getSinglePath filepath (MonitorStateFile kindfile kinddir _) =
+      MonitorFile kindfile kinddir filepath
+
+    getGlobPath (MonitorStateGlob kindfile kinddir root gstate) =
+      MonitorFileGlob kindfile kinddir $ FilePathGlob root $
+        case gstate of
+          MonitorStateGlobDirs  glob globs _ _ -> GlobDir  glob globs
+          MonitorStateGlobFiles glob       _ _ -> GlobFile glob
+          MonitorStateGlobDirTrailing          -> GlobDirTrailing
+
+------------------------------------------------------------------------------
+-- Checking the status of monitored files
+--
+
+-- | A monitor for detecting changes to a set of files. It can be used to
+-- efficiently test if any of a set of files (specified individually or by
+-- glob patterns) has changed since some snapshot. In addition, it also checks
+-- for changes in a value (of type @a@), and when there are no changes in
+-- either it returns a saved value (of type @b@).
+--
+-- The main use case looks like this: suppose we have some expensive action
+-- that depends on certain pure inputs and reads some set of files, and
+-- produces some pure result. We want to avoid re-running this action when it
+-- would produce the same result. So we need to monitor the files the action
+-- looked at, the other pure input values, and we need to cache the result.
+-- Then at some later point, if the input value didn't change, and none of the
+-- files changed, then we can re-use the cached result rather than re-running
+-- the action.
+--
+-- This can be achieved using a 'FileMonitor'. Each 'FileMonitor' instance
+-- saves state in a disk file, so the file for that has to be specified,
+-- making sure it is unique. The pattern is to use 'checkFileMonitorChanged'
+-- to see if there's been any change. If there is, re-run the action, keeping
+-- track of the files, then use 'updateFileMonitor' to record the current
+-- set of files to monitor, the current input value for the action, and the
+-- result of the action.
+--
+-- The typical occurrence of this pattern is captured by 'rerunIfChanged'
+-- and the 'Rebuild' monad. More complicated cases may need to use
+-- 'checkFileMonitorChanged' and 'updateFileMonitor' directly.
+--
+data FileMonitor a b
+   = FileMonitor {
+
+       -- | The file where this 'FileMonitor' should store its state.
+       --
+       fileMonitorCacheFile :: FilePath,
+
+       -- | Compares a new cache key with old one to determine if a
+       -- corresponding cached value is still valid.
+       --
+       -- Typically this is just an equality test, but in some
+       -- circumstances it can make sense to do things like subset
+       -- comparisons.
+       --
+       -- The first arg is the new value, the second is the old cached value.
+       --
+       fileMonitorKeyValid :: a -> a -> Bool,
+
+       -- | When this mode is enabled, if 'checkFileMonitorChanged' returns
+       -- 'MonitoredValueChanged' then we have the guarantee that no files
+       -- changed, that the value change was the only change. In the default
+       -- mode no such guarantee is provided which is slightly faster.
+       --
+       fileMonitorCheckIfOnlyValueChanged :: Bool
+  }
+
+-- | Define a new file monitor.
+--
+-- It's best practice to define file monitor values once, and then use the
+-- same value for 'checkFileMonitorChanged' and 'updateFileMonitor' as this
+-- ensures you get the same types @a@ and @b@ for reading and writing.
+--
+-- The path of the file monitor itself must be unique because it keeps state
+-- on disk and these would clash.
+--
+newFileMonitor :: Eq a => FilePath -- ^ The file to cache the state of the
+                                   -- file monitor. Must be unique.
+                       -> FileMonitor a b
+newFileMonitor path = FileMonitor path (==) False
+
+-- | The result of 'checkFileMonitorChanged': either the monitored files or
+-- value changed (and it tells us which it was) or nothing changed and we get
+-- the cached result.
+--
+data MonitorChanged a b =
+     -- | The monitored files and value did not change. The cached result is
+     -- @b@.
+     --
+     -- The set of monitored files is also returned. This is useful
+     -- for composing or nesting 'FileMonitor's.
+     MonitorUnchanged b [MonitorFilePath]
+
+     -- | The monitor found that something changed. The reason is given.
+     --
+   | MonitorChanged (MonitorChangedReason a)
+  deriving Show
+
+-- | What kind of change 'checkFileMonitorChanged' detected.
+--
+data MonitorChangedReason a =
+
+     -- | One of the files changed (existence, file type, mtime or file
+     -- content, depending on the 'MonitorFilePath' in question)
+     MonitoredFileChanged FilePath
+
+     -- | The pure input value changed.
+     --
+     -- The previous cached key value is also returned. This is sometimes
+     -- useful when using a 'fileMonitorKeyValid' function that is not simply
+     -- '(==)', when invalidation can be partial. In such cases it can make
+     -- sense to 'updateFileMonitor' with a key value that's a combination of
+     -- the new and old (e.g. set union).
+   | MonitoredValueChanged a
+
+     -- | There was no saved monitor state, cached value etc. Ie the file
+     -- for the 'FileMonitor' does not exist.
+   | MonitorFirstRun
+
+     -- | There was existing state, but we could not read it. This typically
+     -- happens when the code has changed compared to an existing 'FileMonitor'
+     -- cache file and type of the input value or cached value has changed such
+     -- that we cannot decode the values. This is completely benign as we can
+     -- treat is just as if there were no cache file and re-run.
+   | MonitorCorruptCache
+  deriving (Eq, Show, Functor)
+
+-- | Test if the input value or files monitored by the 'FileMonitor' have
+-- changed. If not, return the cached value.
+--
+-- See 'FileMonitor' for a full explanation.
+--
+checkFileMonitorChanged
+  :: (Binary a, Binary b)
+  => FileMonitor a b            -- ^ cache file path
+  -> FilePath                   -- ^ root directory
+  -> a                          -- ^ guard or key value
+  -> IO (MonitorChanged a b)    -- ^ did the key or any paths change?
+checkFileMonitorChanged
+    monitor@FileMonitor { fileMonitorKeyValid,
+                          fileMonitorCheckIfOnlyValueChanged }
+    root currentKey =
+
+    -- Consider it a change if the cache file does not exist,
+    -- or we cannot decode it. Sadly ErrorCall can still happen, despite
+    -- using decodeFileOrFail, e.g. Data.Char.chr errors
+
+    handleDoesNotExist (MonitorChanged MonitorFirstRun) $
+    handleErrorCall    (MonitorChanged MonitorCorruptCache) $
+          readCacheFile monitor
+      >>= either (\_ -> return (MonitorChanged MonitorCorruptCache))
+                 checkStatusCache
+
+  where
+    checkStatusCache (cachedFileStatus, cachedKey, cachedResult) = do
+        change <- checkForChanges
+        case change of
+          Just reason -> return (MonitorChanged reason)
+          Nothing     -> return (MonitorUnchanged cachedResult monitorFiles)
+            where monitorFiles = reconstructMonitorFilePaths cachedFileStatus
+      where
+        -- In fileMonitorCheckIfOnlyValueChanged mode we want to guarantee that
+        -- if we return MonitoredValueChanged that only the value changed.
+        -- We do that by checkin for file changes first. Otherwise it makes
+        -- more sense to do the cheaper test first.
+        checkForChanges
+          | fileMonitorCheckIfOnlyValueChanged
+          = checkFileChange cachedFileStatus cachedKey cachedResult
+              `mplusMaybeT`
+            checkValueChange cachedKey
+
+          | otherwise
+          = checkValueChange cachedKey
+              `mplusMaybeT`
+            checkFileChange cachedFileStatus cachedKey cachedResult
+
+    mplusMaybeT :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
+    mplusMaybeT ma mb = do
+      mx <- ma
+      case mx of
+        Nothing -> mb
+        Just x  -> return (Just x)
+
+    -- Check if the guard value has changed
+    checkValueChange cachedKey
+      | not (fileMonitorKeyValid currentKey cachedKey)
+      = return (Just (MonitoredValueChanged cachedKey))
+      | otherwise
+      = return Nothing
+
+    -- Check if any file has changed
+    checkFileChange cachedFileStatus cachedKey cachedResult = do
+      res <- probeFileSystem root cachedFileStatus
+      case res of
+        -- Some monitored file has changed
+        Left changedPath ->
+          return (Just (MonitoredFileChanged (normalise changedPath)))
+
+        -- No monitored file has changed
+        Right (cachedFileStatus', cacheStatus) -> do
+
+          -- But we might still want to update the cache
+          whenCacheChanged cacheStatus $
+            rewriteCacheFile monitor cachedFileStatus' cachedKey cachedResult
+
+          return Nothing
+
+-- | Helper for reading the cache file.
+--
+-- This determines the type and format of the binary cache file.
+--
+readCacheFile :: (Binary a, Binary b)
+              => FileMonitor a b
+              -> IO (Either String (MonitorStateFileSet, a, b))
+readCacheFile FileMonitor {fileMonitorCacheFile} =
+    withBinaryFile fileMonitorCacheFile ReadMode $ \hnd ->
+      Binary.decodeOrFailIO =<< BS.hGetContents hnd
+
+-- | Helper for writing the cache file.
+--
+-- This determines the type and format of the binary cache file.
+--
+rewriteCacheFile :: (Binary a, Binary b)
+                 => FileMonitor a b
+                 -> MonitorStateFileSet -> a -> b -> IO ()
+rewriteCacheFile FileMonitor {fileMonitorCacheFile} fileset key result =
+    writeFileAtomic fileMonitorCacheFile $
+      Binary.encode (fileset, key, result)
+
+-- | Probe the file system to see if any of the monitored files have changed.
+--
+-- It returns Nothing if any file changed, or returns a possibly updated
+-- file 'MonitorStateFileSet' plus an indicator of whether it actually changed.
+--
+-- We may need to update the cache since there may be changes in the filesystem
+-- state which don't change any of our affected files.
+--
+-- Consider the glob @{proj1,proj2}\/\*.cabal@. Say we first run and find a
+-- @proj1@ directory containing @proj1.cabal@ yet no @proj2@. If we later run
+-- and find @proj2@ was created, yet contains no files matching @*.cabal@ then
+-- we want to update the cache despite no changes in our relevant file set.
+-- Specifically, we should add an mtime for this directory so we can avoid
+-- re-traversing the directory in future runs.
+--
+probeFileSystem :: FilePath -> MonitorStateFileSet
+                -> IO (Either FilePath (MonitorStateFileSet, CacheChanged))
+probeFileSystem root (MonitorStateFileSet singlePaths globPaths) =
+  runChangedM $ do
+    sequence_
+      [ probeMonitorStateFileStatus root file status
+      | (file, MonitorStateFile _ _ status) <- Map.toList singlePaths ]
+    -- The glob monitors can require state changes
+    globPaths' <-
+      sequence
+        [ probeMonitorStateGlob root globPath
+        | globPath <- globPaths ]
+    return (MonitorStateFileSet singlePaths globPaths')
+
+
+-----------------------------------------------
+-- Monad for checking for file system changes
+--
+-- We need to be able to bail out if we detect a change (using ExceptT),
+-- but if there's no change we need to be able to rebuild the monitor
+-- state. And we want to optimise that rebuilding by keeping track if
+-- anything actually changed (using StateT), so that in the typical case
+-- we can avoid rewriting the state file.
+
+newtype ChangedM a = ChangedM (StateT CacheChanged (ExceptT FilePath IO) a)
+  deriving (Functor, Applicative, Monad, MonadIO)
+
+runChangedM :: ChangedM a -> IO (Either FilePath (a, CacheChanged))
+runChangedM (ChangedM action) =
+  runExceptT $ State.runStateT action CacheUnchanged
+
+somethingChanged :: FilePath -> ChangedM a
+somethingChanged path = ChangedM $ throwError path
+
+cacheChanged :: ChangedM ()
+cacheChanged = ChangedM $ State.put CacheChanged
+
+mapChangedFile :: (FilePath -> FilePath) -> ChangedM a -> ChangedM a
+mapChangedFile adjust (ChangedM a) =
+    ChangedM (mapStateT (withExceptT adjust) a)
+
+data CacheChanged = CacheChanged | CacheUnchanged
+
+whenCacheChanged :: Monad m => CacheChanged -> m () -> m ()
+whenCacheChanged CacheChanged action = action
+whenCacheChanged CacheUnchanged _    = return ()
+
+----------------------
+
+-- | Probe the file system to see if a single monitored file has changed.
+--
+probeMonitorStateFileStatus :: FilePath -> FilePath
+                            -> MonitorStateFileStatus
+                            -> ChangedM ()
+probeMonitorStateFileStatus root file status =
+    case status of
+      MonitorStateFileExists ->
+        probeFileExistence root file
+
+      MonitorStateFileModTime mtime ->
+        probeFileModificationTime root file mtime
+
+      MonitorStateFileHashed  mtime hash ->
+        probeFileModificationTimeAndHash root file mtime hash
+
+      MonitorStateDirExists ->
+        probeDirExistence root file
+
+      MonitorStateDirModTime mtime ->
+        probeFileModificationTime root file mtime
+
+      MonitorStateNonExistent ->
+        probeFileNonExistence root file
+
+      MonitorStateAlreadyChanged ->
+        somethingChanged file
+
+
+-- | Probe the file system to see if a monitored file glob has changed.
+--
+probeMonitorStateGlob :: FilePath      -- ^ root path
+                      -> MonitorStateGlob
+                      -> ChangedM MonitorStateGlob
+probeMonitorStateGlob relroot
+                      (MonitorStateGlob kindfile kinddir globroot glob) = do
+    root <- liftIO $ getFilePathRootDirectory globroot relroot
+    case globroot of
+      FilePathRelative ->
+        MonitorStateGlob kindfile kinddir globroot <$>
+        probeMonitorStateGlobRel kindfile kinddir root "." glob
+
+      -- for absolute cases, make the changed file we report absolute too
+      _ ->
+        mapChangedFile (root </>) $
+        MonitorStateGlob kindfile kinddir globroot <$>
+        probeMonitorStateGlobRel kindfile kinddir root "" glob
+
+probeMonitorStateGlobRel :: MonitorKindFile -> MonitorKindDir
+                         -> FilePath      -- ^ root path
+                         -> FilePath      -- ^ path of the directory we are
+                                          --   looking in relative to @root@
+                         -> MonitorStateGlobRel
+                         -> ChangedM MonitorStateGlobRel
+probeMonitorStateGlobRel kindfile kinddir root dirName
+                        (MonitorStateGlobDirs glob globPath mtime children) = do
+    change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime
+    case change of
+      Nothing -> do
+        children' <- sequence
+          [ do fstate' <- probeMonitorStateGlobRel
+                            kindfile kinddir root
+                            (dirName </> fname) fstate
+               return (fname, fstate')
+          | (fname, fstate) <- children ]
+        return $! MonitorStateGlobDirs glob globPath mtime children'
+
+      Just mtime' -> do
+        -- directory modification time changed:
+        -- a matching subdir may have been added or deleted
+        matches <- filterM (\entry -> let subdir = root </> dirName </> entry
+                                       in liftIO $ doesDirectoryExist subdir)
+                 . filter (matchGlob glob)
+               =<< liftIO (getDirectoryContents (root </> dirName))
+
+        children' <- mapM probeMergeResult $
+                          mergeBy (\(path1,_) path2 -> compare path1 path2)
+                                  children
+                                  (sort matches)
+        return $! MonitorStateGlobDirs glob globPath mtime' children'
+        -- Note that just because the directory has changed, we don't force
+        -- a cache rewrite with 'cacheChanged' since that has some cost, and
+        -- all we're saving is scanning the directory. But we do rebuild the
+        -- cache with the new mtime', so that if the cache is rewritten for
+        -- some other reason, we'll take advantage of that.
+
+  where
+    probeMergeResult :: MergeResult (FilePath, MonitorStateGlobRel) FilePath
+                     -> ChangedM (FilePath, MonitorStateGlobRel)
+
+    -- Only in cached (directory deleted)
+    probeMergeResult (OnlyInLeft (path, fstate)) = do
+      case allMatchingFiles (dirName </> path) fstate of
+        [] -> return (path, fstate)
+        -- Strictly speaking we should be returning 'CacheChanged' above
+        -- as we should prune the now-missing 'MonitorStateGlobRel'. However
+        -- we currently just leave these now-redundant entries in the
+        -- cache as they cost no IO and keeping them allows us to avoid
+        -- rewriting the cache.
+        (file:_) -> somethingChanged file
+
+    -- Only in current filesystem state (directory added)
+    probeMergeResult (OnlyInRight path) = do
+      fstate <- liftIO $ buildMonitorStateGlobRel Nothing Map.empty
+                           kindfile kinddir root (dirName </> path) globPath
+      case allMatchingFiles (dirName </> path) fstate of
+        (file:_) -> somethingChanged file
+        -- This is the only case where we use 'cacheChanged' because we can
+        -- have a whole new dir subtree (of unbounded size and cost), so we
+        -- need to save the state of that new subtree in the cache.
+        [] -> cacheChanged >> return (path, fstate)
+
+    -- Found in path
+    probeMergeResult (InBoth (path, fstate) _) = do
+      fstate' <- probeMonitorStateGlobRel kindfile kinddir
+                                          root (dirName </> path) fstate
+      return (path, fstate')
+
+    -- | Does a 'MonitorStateGlob' have any relevant files within it?
+    allMatchingFiles :: FilePath -> MonitorStateGlobRel -> [FilePath]
+    allMatchingFiles dir (MonitorStateGlobFiles _ _   entries) =
+      [ dir </> fname | (fname, _) <- entries ]
+    allMatchingFiles dir (MonitorStateGlobDirs  _ _ _ entries) =
+      [ res
+      | (subdir, fstate) <- entries
+      , res <- allMatchingFiles (dir </> subdir) fstate ]
+    allMatchingFiles dir MonitorStateGlobDirTrailing =
+      [dir]
+
+probeMonitorStateGlobRel _ _ root dirName
+                         (MonitorStateGlobFiles glob mtime children) = do
+    change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime
+    mtime' <- case change of
+      Nothing     -> return mtime
+      Just mtime' -> do
+        -- directory modification time changed:
+        -- a matching file may have been added or deleted
+        matches <- return . filter (matchGlob glob)
+               =<< liftIO (getDirectoryContents (root </> dirName))
+
+        mapM_ probeMergeResult $
+              mergeBy (\(path1,_) path2 -> compare path1 path2)
+                      children
+                      (sort matches)
+        return mtime'
+
+    -- Check that none of the children have changed
+    forM_ children $ \(file, status) ->
+      probeMonitorStateFileStatus root (dirName </> file) status
+
+
+    return (MonitorStateGlobFiles glob mtime' children)
+    -- Again, we don't force a cache rewite with 'cacheChanged', but we do use
+    -- the new mtime' if any.
+  where
+    probeMergeResult :: MergeResult (FilePath, MonitorStateFileStatus) FilePath
+                     -> ChangedM ()
+    probeMergeResult mr = case mr of
+      InBoth _ _            -> return ()
+    -- this is just to be able to accurately report which file changed:
+      OnlyInLeft  (path, _) -> somethingChanged (dirName </> path)
+      OnlyInRight path      -> somethingChanged (dirName </> path)
+
+probeMonitorStateGlobRel _ _ _ _ MonitorStateGlobDirTrailing =
+    return MonitorStateGlobDirTrailing
+
+------------------------------------------------------------------------------
+
+-- | Update the input value and the set of files monitored by the
+-- 'FileMonitor', plus the cached value that may be returned in future.
+--
+-- This takes a snapshot of the state of the monitored files right now, so
+-- 'checkFileMonitorChanged' will look for file system changes relative to
+-- this snapshot.
+--
+-- This is typically done once the action has been completed successfully and
+-- we have the action's result and we know what files it looked at. See
+-- 'FileMonitor' for a full explanation.
+--
+-- If we do take the snapshot after the action has completed then we have a
+-- problem. The problem is that files might have changed /while/ the action was
+-- running but /after/ the action read them. If we take the snapshot after the
+-- action completes then we will miss these changes. The solution is to record
+-- a timestamp before beginning execution of the action and then we make the
+-- conservative assumption that any file that has changed since then has
+-- already changed, ie the file monitor state for these files will be such that
+-- 'checkFileMonitorChanged' will report that they have changed.
+--
+-- So if you do use 'updateFileMonitor' after the action (so you can discover
+-- the files used rather than predicting them in advance) then use
+-- 'beginUpdateFileMonitor' to get a timestamp and pass that. Alternatively,
+-- if you take the snapshot in advance of the action, or you're not monitoring
+-- any files then you can use @Nothing@ for the timestamp parameter.
+--
+updateFileMonitor
+  :: (Binary a, Binary b)
+  => FileMonitor a b          -- ^ cache file path
+  -> FilePath                 -- ^ root directory
+  -> Maybe MonitorTimestamp   -- ^ timestamp when the update action started
+  -> [MonitorFilePath]        -- ^ files of interest relative to root
+  -> a                        -- ^ the current key value
+  -> b                        -- ^ the current result value
+  -> IO ()
+updateFileMonitor monitor root startTime monitorFiles
+                  cachedKey cachedResult = do
+    hashcache <- readCacheFileHashes monitor
+    msfs <- buildMonitorStateFileSet startTime hashcache root monitorFiles
+    rewriteCacheFile monitor msfs cachedKey cachedResult
+
+-- | A timestamp to help with the problem of file changes during actions.
+-- See 'updateFileMonitor' for details.
+--
+newtype MonitorTimestamp = MonitorTimestamp ModTime
+
+-- | Record a timestamp at the beginning of an action, and when the action
+-- completes call 'updateFileMonitor' passing it the timestamp.
+-- See 'updateFileMonitor' for details.
+--
+beginUpdateFileMonitor :: IO MonitorTimestamp
+beginUpdateFileMonitor = MonitorTimestamp <$> getCurTime
+
+-- | Take the snapshot of the monitored files. That is, given the
+-- specification of the set of files we need to monitor, inspect the state
+-- of the file system now and collect the information we'll need later to
+-- determine if anything has changed.
+--
+buildMonitorStateFileSet :: Maybe MonitorTimestamp -- ^ optional: timestamp
+                                              -- of the start of the action
+                         -> FileHashCache     -- ^ existing file hashes
+                         -> FilePath          -- ^ root directory
+                         -> [MonitorFilePath] -- ^ patterns of interest
+                                              --   relative to root
+                         -> IO MonitorStateFileSet
+buildMonitorStateFileSet mstartTime hashcache root =
+    go Map.empty []
+  where
+    go :: Map FilePath MonitorStateFile -> [MonitorStateGlob]
+       -> [MonitorFilePath] -> IO MonitorStateFileSet
+    go !singlePaths !globPaths [] =
+      return (MonitorStateFileSet singlePaths globPaths)
+
+    go !singlePaths !globPaths
+       (MonitorFile kindfile kinddir path : monitors) = do
+      monitorState <- MonitorStateFile kindfile kinddir
+                  <$> buildMonitorStateFile mstartTime hashcache
+                                            kindfile kinddir root path
+      go (Map.insert path monitorState singlePaths) globPaths monitors
+
+    go !singlePaths !globPaths
+       (MonitorFileGlob kindfile kinddir globPath : monitors) = do
+      monitorState <- buildMonitorStateGlob mstartTime hashcache
+                                            kindfile kinddir root globPath
+      go singlePaths (monitorState : globPaths) monitors
+
+
+buildMonitorStateFile :: Maybe MonitorTimestamp -- ^ start time of update
+                      -> FileHashCache          -- ^ existing file hashes
+                      -> MonitorKindFile -> MonitorKindDir
+                      -> FilePath               -- ^ the root directory
+                      -> FilePath
+                      -> IO MonitorStateFileStatus
+buildMonitorStateFile mstartTime hashcache kindfile kinddir root path = do
+    let abspath = root </> path
+    isFile <- doesFileExist abspath
+    isDir  <- doesDirectoryExist abspath
+    case (isFile, kindfile, isDir, kinddir) of
+      (_, FileNotExists, _, DirNotExists) ->
+        -- we don't need to care if it exists now, since we check at probe time
+        return MonitorStateNonExistent
+
+      (False, _, False, _) ->
+        return MonitorStateAlreadyChanged
+
+      (True, FileExists, _, _)  ->
+        return MonitorStateFileExists
+
+      (True, FileModTime, _, _) ->
+        handleIOException MonitorStateAlreadyChanged $ do
+          mtime <- getModTime abspath
+          if changedDuringUpdate mstartTime mtime
+            then return MonitorStateAlreadyChanged
+            else return (MonitorStateFileModTime mtime)
+
+      (True, FileHashed, _, _) ->
+        handleIOException MonitorStateAlreadyChanged $ do
+          mtime <- getModTime abspath
+          if changedDuringUpdate mstartTime mtime
+            then return MonitorStateAlreadyChanged
+            else do hash <- getFileHash hashcache abspath abspath mtime
+                    return (MonitorStateFileHashed mtime hash)
+
+      (_, _, True, DirExists) ->
+        return MonitorStateDirExists
+
+      (_, _, True, DirModTime) ->
+        handleIOException MonitorStateAlreadyChanged $ do
+          mtime <- getModTime abspath
+          if changedDuringUpdate mstartTime mtime
+            then return MonitorStateAlreadyChanged
+            else return (MonitorStateDirModTime mtime)
+
+      (False, _, True,  DirNotExists) -> return MonitorStateAlreadyChanged
+      (True, FileNotExists, False, _) -> return MonitorStateAlreadyChanged
+
+-- | If we have a timestamp for the beginning of the update, then any file
+-- mtime later than this means that it changed during the update and we ought
+-- to consider the file as already changed.
+--
+changedDuringUpdate :: Maybe MonitorTimestamp -> ModTime -> Bool
+changedDuringUpdate (Just (MonitorTimestamp startTime)) mtime
+                        = mtime > startTime
+changedDuringUpdate _ _ = False
+
+-- | Much like 'buildMonitorStateFileSet' but for the somewhat complicated case
+-- of a file glob.
+--
+-- This gets used both by 'buildMonitorStateFileSet' when we're taking the
+-- file system snapshot, but also by 'probeGlobStatus' as part of checking
+-- the monitored (globed) files for changes when we find a whole new subtree.
+--
+buildMonitorStateGlob :: Maybe MonitorTimestamp -- ^ start time of update
+                      -> FileHashCache     -- ^ existing file hashes
+                      -> MonitorKindFile -> MonitorKindDir
+                      -> FilePath     -- ^ the root directory
+                      -> FilePathGlob -- ^ the matching glob
+                      -> IO MonitorStateGlob
+buildMonitorStateGlob mstartTime hashcache kindfile kinddir relroot
+                      (FilePathGlob globroot globPath) = do
+    root <- liftIO $ getFilePathRootDirectory globroot relroot
+    MonitorStateGlob kindfile kinddir globroot <$>
+      buildMonitorStateGlobRel
+        mstartTime hashcache kindfile kinddir root "." globPath
+
+buildMonitorStateGlobRel :: Maybe MonitorTimestamp -- ^ start time of update
+                         -> FileHashCache   -- ^ existing file hashes
+                         -> MonitorKindFile -> MonitorKindDir
+                         -> FilePath        -- ^ the root directory
+                         -> FilePath        -- ^ directory we are examining
+                                            --   relative to the root
+                         -> FilePathGlobRel -- ^ the matching glob
+                         -> IO MonitorStateGlobRel
+buildMonitorStateGlobRel mstartTime hashcache kindfile kinddir root
+                         dir globPath = do
+    let absdir = root </> dir
+    dirEntries <- getDirectoryContents absdir
+    dirMTime   <- getModTime absdir
+    case globPath of
+      GlobDir glob globPath' -> do
+        subdirs <- filterM (\subdir -> doesDirectoryExist (absdir </> subdir))
+                 $ filter (matchGlob glob) dirEntries
+        subdirStates <-
+          forM (sort subdirs) $ \subdir -> do
+            fstate <- buildMonitorStateGlobRel
+                        mstartTime hashcache kindfile kinddir root
+                        (dir </> subdir) globPath'
+            return (subdir, fstate)
+        return $! MonitorStateGlobDirs glob globPath' dirMTime subdirStates
+
+      GlobFile glob -> do
+        let files = filter (matchGlob glob) dirEntries
+        filesStates <-
+          forM (sort files) $ \file -> do
+            fstate <- buildMonitorStateFile
+                        mstartTime hashcache kindfile kinddir root
+                        (dir </> file)
+            return (file, fstate)
+        return $! MonitorStateGlobFiles glob dirMTime filesStates
+
+      GlobDirTrailing ->
+        return MonitorStateGlobDirTrailing
+
+
+-- | We really want to avoid re-hashing files all the time. We already make
+-- the assumption that if a file mtime has not changed then we don't need to
+-- bother checking if the content hash has changed. We can apply the same
+-- assumption when updating the file monitor state. In the typical case of
+-- updating a file monitor the set of files is the same or largely the same so
+-- we can grab the previously known content hashes with their corresponding
+-- mtimes.
+--
+type FileHashCache = Map FilePath (ModTime, Hash)
+
+-- | We declare it a cache hit if the mtime of a file is the same as before.
+--
+lookupFileHashCache :: FileHashCache -> FilePath -> ModTime -> Maybe Hash
+lookupFileHashCache hashcache file mtime = do
+    (mtime', hash) <- Map.lookup file hashcache
+    guard (mtime' == mtime)
+    return hash
+
+-- | Either get it from the cache or go read the file
+getFileHash :: FileHashCache -> FilePath -> FilePath -> ModTime -> IO Hash
+getFileHash hashcache relfile absfile mtime =
+    case lookupFileHashCache hashcache relfile mtime of
+      Just hash -> return hash
+      Nothing   -> readFileHash absfile
+
+-- | Build a 'FileHashCache' from the previous 'MonitorStateFileSet'. While
+-- in principle we could preserve the structure of the previous state, given
+-- that the set of files to monitor can change then it's simpler just to throw
+-- away the structure and use a finite map.
+--
+readCacheFileHashes :: (Binary a, Binary b)
+                    => FileMonitor a b -> IO FileHashCache
+readCacheFileHashes monitor =
+    handleDoesNotExist Map.empty $
+    handleErrorCall    Map.empty $ do
+      res <- readCacheFile monitor
+      case res of
+        Left _             -> return Map.empty
+        Right (msfs, _, _) -> return (mkFileHashCache msfs)
+  where
+    mkFileHashCache :: MonitorStateFileSet -> FileHashCache
+    mkFileHashCache (MonitorStateFileSet singlePaths globPaths) =
+                    collectAllFileHashes singlePaths
+        `Map.union` collectAllGlobHashes globPaths
+
+    collectAllFileHashes =
+      Map.mapMaybe $ \(MonitorStateFile _ _ fstate) -> case fstate of
+        MonitorStateFileHashed mtime hash -> Just (mtime, hash)
+        _                                 -> Nothing
+
+    collectAllGlobHashes globPaths =
+      Map.fromList [ (fpath, hash)
+                   | MonitorStateGlob _ _ _ gstate <- globPaths
+                   , (fpath, hash) <- collectGlobHashes "" gstate ]
+
+    collectGlobHashes dir (MonitorStateGlobDirs _ _ _ entries) =
+      [ res
+      | (subdir, fstate) <- entries
+      , res <- collectGlobHashes (dir </> subdir) fstate ]
+
+    collectGlobHashes dir (MonitorStateGlobFiles  _ _ entries) =
+      [ (dir </> fname, (mtime, hash))
+      | (fname, MonitorStateFileHashed mtime hash) <- entries ]
+
+    collectGlobHashes _dir MonitorStateGlobDirTrailing =
+      []
+
+
+------------------------------------------------------------------------------
+-- Utils
+--
+
+-- | Within the @root@ directory, check if @file@ has its 'ModTime' is
+-- the same as @mtime@, short-circuiting if it is different.
+probeFileModificationTime :: FilePath -> FilePath -> ModTime -> ChangedM ()
+probeFileModificationTime root file mtime = do
+    unchanged <- liftIO $ checkModificationTimeUnchanged root file mtime
+    unless unchanged (somethingChanged file)
+
+-- | Within the @root@ directory, check if @file@ has its 'ModTime' and
+-- 'Hash' is the same as @mtime@ and @hash@, short-circuiting if it is
+-- different.
+probeFileModificationTimeAndHash :: FilePath -> FilePath -> ModTime -> Hash
+                                 -> ChangedM ()
+probeFileModificationTimeAndHash root file mtime hash = do
+    unchanged <- liftIO $
+      checkFileModificationTimeAndHashUnchanged root file mtime hash
+    unless unchanged (somethingChanged file)
+
+-- | Within the @root@ directory, check if @file@ still exists as a file.
+-- If it *does not* exist, short-circuit.
+probeFileExistence :: FilePath -> FilePath -> ChangedM ()
+probeFileExistence root file = do
+    existsFile <- liftIO $ doesFileExist (root </> file)
+    unless existsFile (somethingChanged file)
+
+-- | Within the @root@ directory, check if @dir@ still exists.
+-- If it *does not* exist, short-circuit.
+probeDirExistence :: FilePath -> FilePath -> ChangedM ()
+probeDirExistence root dir = do
+    existsDir  <- liftIO $ doesDirectoryExist (root </> dir)
+    unless existsDir (somethingChanged dir)
+
+-- | Within the @root@ directory, check if @file@ still does not exist.
+-- If it *does* exist, short-circuit.
+probeFileNonExistence :: FilePath -> FilePath -> ChangedM ()
+probeFileNonExistence root file = do
+    existsFile <- liftIO $ doesFileExist (root </> file)
+    existsDir  <- liftIO $ doesDirectoryExist (root </> file)
+    when (existsFile || existsDir) (somethingChanged file)
+
+-- | Returns @True@ if, inside the @root@ directory, @file@ has the same
+-- 'ModTime' as @mtime@.
+checkModificationTimeUnchanged :: FilePath -> FilePath
+                               -> ModTime -> IO Bool
+checkModificationTimeUnchanged root file mtime =
+  handleIOException False $ do
+    mtime' <- getModTime (root </> file)
+    return (mtime == mtime')
+
+-- | Returns @True@ if, inside the @root@ directory, @file@ has the
+-- same 'ModTime' and 'Hash' as @mtime and @chash@.
+checkFileModificationTimeAndHashUnchanged :: FilePath -> FilePath
+                                          -> ModTime -> Hash -> IO Bool
+checkFileModificationTimeAndHashUnchanged root file mtime chash =
+  handleIOException False $ do
+    mtime' <- getModTime (root </> file)
+    if mtime == mtime'
+      then return True
+      else do
+        chash' <- readFileHash (root </> file)
+        return (chash == chash')
+
+-- | Read a non-cryptographic hash of a @file@.
+readFileHash :: FilePath -> IO Hash
+readFileHash file =
+    withBinaryFile file ReadMode $ \hnd ->
+      evaluate . Hashable.hash =<< BS.hGetContents hnd
+
+-- | Given a directory @dir@, return @Nothing@ if its 'ModTime'
+-- is the same as @mtime@, and the new 'ModTime' if it is not.
+checkDirectoryModificationTime :: FilePath -> ModTime -> IO (Maybe ModTime)
+checkDirectoryModificationTime dir mtime =
+  handleIOException Nothing $ do
+    mtime' <- getModTime dir
+    if mtime == mtime'
+      then return Nothing
+      else return (Just mtime')
+
+-- | Run an IO computation, returning @e@ if there is an 'error'
+-- call. ('ErrorCall')
+handleErrorCall :: a -> IO a -> IO a
+handleErrorCall e =
+    handle (\(ErrorCall _) -> return e)
+
+-- | Run an IO computation, returning @e@ if there is any 'IOException'.
+--
+-- This policy is OK in the file monitor code because it just causes the
+-- monitor to report that something changed, and then code reacting to that
+-- will normally encounter the same IO exception when it re-runs the action
+-- that uses the file.
+--
+handleIOException :: a -> IO a -> IO a
+handleIOException e =
+    handle (anyIOException e)
+  where
+    anyIOException :: a -> IOException -> IO a
+    anyIOException x _ = return x
+
+
+------------------------------------------------------------------------------
+-- Instances
+--
+
+instance Binary MonitorStateFileSet where
+  put (MonitorStateFileSet singlePaths globPaths) = do
+    put (1 :: Int) -- version
+    put singlePaths
+    put globPaths
+  get = do
+    ver <- get
+    if ver == (1 :: Int)
+      then do singlePaths <- get
+              globPaths   <- get
+              return $! MonitorStateFileSet singlePaths globPaths
+      else fail "MonitorStateFileSet: wrong version"
diff --git a/cabal/cabal-install/Distribution/Client/Freeze.hs b/cabal/cabal-install/Distribution/Client/Freeze.hs
--- a/cabal/cabal-install/Distribution/Client/Freeze.hs
+++ b/cabal/cabal-install/Distribution/Client/Freeze.hs
@@ -13,7 +13,7 @@
 -- The cabal freeze command
 -----------------------------------------------------------------------------
 module Distribution.Client.Freeze (
-    freeze,
+    freeze, getFreezePkgs
   ) where
 
 import Distribution.Client.Config ( SavedConfig(..) )
@@ -27,10 +27,11 @@
 import Distribution.Client.InstallPlan
          ( InstallPlan, PlanPackage )
 import qualified Distribution.Client.InstallPlan as InstallPlan
+import Distribution.Client.PkgConfigDb
+         ( PkgConfigDb, readPkgConfigDb )
 import Distribution.Client.Setup
-         ( GlobalFlags(..), FreezeFlags(..), ConfigExFlags(..) )
-import Distribution.Client.HttpUtils
-         ( configureTransport )
+         ( GlobalFlags(..), FreezeFlags(..), ConfigExFlags(..)
+         , RepoContext(..) )
 import Distribution.Client.Sandbox.PackageEnvironment
          ( loadUserConfig, pkgEnvSavedConfig, showPackageEnvironment,
            userPackageEnvironmentFile )
@@ -42,7 +43,6 @@
 import Distribution.Simple.Compiler
          ( Compiler, compilerInfo, PackageDBStack )
 import Distribution.Simple.PackageIndex (InstalledPackageIndex)
-import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.Program
          ( ProgramConfiguration )
 import Distribution.Simple.Setup
@@ -77,7 +77,7 @@
 --
 freeze :: Verbosity
       -> PackageDBStack
-      -> [Repo]
+      -> RepoContext
       -> Compiler
       -> Platform
       -> ProgramConfiguration
@@ -85,24 +85,12 @@
       -> GlobalFlags
       -> FreezeFlags
       -> IO ()
-freeze verbosity packageDBs repos comp platform conf mSandboxPkgInfo
+freeze verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo
       globalFlags freezeFlags = do
 
-    installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-    sourcePkgDb       <- getSourcePackages    verbosity repos
-
-    transport <- configureTransport verbosity
-                 (flagToMaybe (globalHttpTransport globalFlags))
-
-    pkgSpecifiers <- resolveUserTargets verbosity transport
-                       (fromFlag $ globalWorldFile globalFlags)
-                       (packageIndex sourcePkgDb)
-                       [UserTargetLocalDir "."]
-
-    sanityCheck pkgSpecifiers
-    pkgs  <- planPackages
-               verbosity comp platform mSandboxPkgInfo freezeFlags
-               installedPkgIndex sourcePkgDb pkgSpecifiers
+    pkgs  <- getFreezePkgs
+               verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo
+               globalFlags freezeFlags
 
     if null pkgs
       then notice verbosity $ "No packages to be frozen. "
@@ -112,11 +100,40 @@
                      "The following packages would be frozen:"
                    : formatPkgs pkgs
 
-             else freezePackages verbosity pkgs
+             else freezePackages verbosity globalFlags pkgs
 
   where
     dryRun = fromFlag (freezeDryRun freezeFlags)
 
+-- | Get the list of packages whose versions would be frozen by the @freeze@
+-- command.
+getFreezePkgs :: Verbosity
+              -> PackageDBStack
+              -> RepoContext
+              -> Compiler
+              -> Platform
+              -> ProgramConfiguration
+              -> Maybe SandboxPackageInfo
+              -> GlobalFlags
+              -> FreezeFlags
+              -> IO [PlanPackage]
+getFreezePkgs verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo
+      globalFlags freezeFlags = do
+
+    installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
+    sourcePkgDb       <- getSourcePackages    verbosity repoCtxt
+    pkgConfigDb       <- readPkgConfigDb      verbosity conf
+
+    pkgSpecifiers <- resolveUserTargets verbosity repoCtxt
+                       (fromFlag $ globalWorldFile globalFlags)
+                       (packageIndex sourcePkgDb)
+                       [UserTargetLocalDir "."]
+
+    sanityCheck pkgSpecifiers
+    planPackages
+               verbosity comp platform mSandboxPkgInfo freezeFlags
+               installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers
+  where
     sanityCheck pkgSpecifiers = do
       when (not . null $ [n | n@(NamedPackage _ _) <- pkgSpecifiers]) $
         die $ "internal error: 'resolveUserTargets' returned "
@@ -132,10 +149,11 @@
              -> FreezeFlags
              -> InstalledPackageIndex
              -> SourcePackageDb
-             -> [PackageSpecifier SourcePackage]
+             -> PkgConfigDb
+             -> [PackageSpecifier UnresolvedSourcePackage]
              -> IO [PlanPackage]
 planPackages verbosity comp platform mSandboxPkgInfo freezeFlags
-             installedPkgIndex sourcePkgDb pkgSpecifiers = do
+             installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers = do
 
   solver <- chooseSolver verbosity
             (fromFlag (freezeSolver freezeFlags)) (compilerInfo comp)
@@ -143,7 +161,7 @@
 
   installPlan <- foldProgress logMsg die return $
                    resolveDependencies
-                     platform (compilerInfo comp)
+                     platform (compilerInfo comp) pkgConfigDb
                      solver
                      resolverParams
 
@@ -175,10 +193,8 @@
 
     logMsg message rest = debug verbosity message >> rest
 
-    stanzas = concat
-        [ if testsEnabled      then [TestStanzas]  else []
-        , if benchmarksEnabled then [BenchStanzas] else []
-        ]
+    stanzas = [ TestStanzas | testsEnabled ]
+           ++ [ BenchStanzas | benchmarksEnabled ]
     testsEnabled      = fromFlagOrDefault False $ freezeTests freezeFlags
     benchmarksEnabled = fromFlagOrDefault False $ freezeBenchmarks freezeFlags
 
@@ -199,24 +215,24 @@
 --    freezing.  This is useful for removing previously installed packages
 --    which are no longer required from the install plan.
 pruneInstallPlan :: InstallPlan
-                 -> [PackageSpecifier SourcePackage]
+                 -> [PackageSpecifier UnresolvedSourcePackage]
                  -> [PlanPackage]
 pruneInstallPlan installPlan pkgSpecifiers =
-    either (const brokenPkgsErr)
-           (removeSelf pkgIds . PackageIndex.allPackages) $
-    InstallPlan.dependencyClosure installPlan pkgIds
+    removeSelf pkgIds $
+    InstallPlan.dependencyClosure installPlan (map fakeUnitId pkgIds)
   where
-    pkgIds = [ packageId pkg | SpecificSourcePackage pkg <- pkgSpecifiers ]
-    removeSelf [thisPkg] = filter (\pp -> packageId pp /= thisPkg)
+    pkgIds = [ packageId pkg
+             | SpecificSourcePackage pkg <- pkgSpecifiers ]
+    removeSelf [thisPkg] = filter (\pp -> packageId pp /= packageId thisPkg)
     removeSelf _  = error $ "internal error: 'pruneInstallPlan' given "
                          ++ "unexpected package specifiers!"
-    brokenPkgsErr = error "planPackages: installPlan contains broken packages"
 
 
-freezePackages :: Package pkg => Verbosity -> [pkg] -> IO ()
-freezePackages verbosity pkgs = do
+freezePackages :: Package pkg => Verbosity -> GlobalFlags -> [pkg] -> IO ()
+freezePackages verbosity globalFlags pkgs = do
+
     pkgEnv <- fmap (createPkgEnv . addFrozenConstraints) $
-                   loadUserConfig verbosity ""
+                   loadUserConfig verbosity ""  (flagToMaybe . globalConstraintsFile $ globalFlags)
     writeFileAtomic userPackageEnvironmentFile $ showPkgEnv pkgEnv
   where
     addFrozenConstraints config =
@@ -226,7 +242,7 @@
             }
         }
     constraint pkg =
-        (pkgIdToConstraint $ packageId pkg, ConstraintSourceUserConfig)
+        (pkgIdToConstraint $ packageId pkg, ConstraintSourceUserConfig userPackageEnvironmentFile)
       where
         pkgIdToConstraint pkgId =
             UserConstraintVersion (packageName pkgId)
diff --git a/cabal/cabal-install/Distribution/Client/GenBounds.hs b/cabal/cabal-install/Distribution/Client/GenBounds.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/GenBounds.hs
@@ -0,0 +1,159 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Client.GenBounds
+-- Copyright   :  (c) Doug Beardsley 2015
+-- License     :  BSD-like
+--
+-- Maintainer  :  cabal-devel@gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-- The cabal gen-bounds command for generating PVP-compliant version bounds.
+-----------------------------------------------------------------------------
+module Distribution.Client.GenBounds (
+    genBounds
+  ) where
+
+import Data.Version
+         ( Version(..), showVersion )
+import Distribution.Client.Init
+         ( incVersion )
+import Distribution.Client.Freeze
+         ( getFreezePkgs )
+import Distribution.Client.Sandbox.Types
+         ( SandboxPackageInfo(..) )
+import Distribution.Client.Setup
+         ( GlobalFlags(..), FreezeFlags(..), RepoContext )
+import Distribution.Package
+         ( Package(..), Dependency(..), PackageName(..)
+         , packageName, packageVersion )
+import Distribution.PackageDescription
+         ( buildDepends )
+import Distribution.PackageDescription.Configuration
+         ( finalizePackageDescription )
+import Distribution.PackageDescription.Parse
+         ( readPackageDescription )
+import Distribution.Simple.Compiler
+         ( Compiler, PackageDBStack, compilerInfo )
+import Distribution.Simple.Program
+         ( ProgramConfiguration )
+import Distribution.Simple.Utils
+         ( tryFindPackageDesc )
+import Distribution.System
+         ( Platform )
+import Distribution.Verbosity
+         ( Verbosity )
+import Distribution.Version
+         ( LowerBound(..), UpperBound(..), VersionRange(..), asVersionIntervals
+         , orLaterVersion, earlierVersion, intersectVersionRanges )
+import System.Directory
+         ( getCurrentDirectory )
+
+-- | Does this version range have an upper bound?
+hasUpperBound :: VersionRange -> Bool
+hasUpperBound vr =
+    case asVersionIntervals vr of
+      [] -> False
+      is -> if snd (last is) == NoUpperBound then False else True
+
+-- | Given a version, return an API-compatible (according to PVP) version range.
+--
+-- Example: @0.4.1.2@ produces the version range @>= 0.4.1 && < 0.5@.
+--
+-- This version is slightly different than the one in
+-- 'Distribution.Client.Init'.  This one uses a.b.c as the lower bound because
+-- the user could be using a new function introduced in a.b.c which would make
+-- ">= a.b" incorrect.
+pvpize :: Version -> VersionRange
+pvpize v = orLaterVersion (vn 3)
+           `intersectVersionRanges`
+           earlierVersion (incVersion 1 (vn 2))
+  where
+    vn n = (v { versionBranch = take n (versionBranch v) })
+
+-- | Show the PVP-mandated version range for this package. The @padTo@ parameter
+-- specifies the width of the package name column.
+showBounds :: Package pkg => Int -> pkg -> String
+showBounds padTo p = unwords $
+    (padAfter padTo $ unPackageName $ packageName p) :
+    map showInterval (asVersionIntervals $ pvpize $ packageVersion p)
+  where
+    padAfter :: Int -> String -> String
+    padAfter n str = str ++ replicate (n - length str) ' '
+
+    showInterval :: (LowerBound, UpperBound) -> String
+    showInterval (LowerBound _ _, NoUpperBound) =
+      error "Error: expected upper bound...this should never happen!"
+    showInterval (LowerBound l _, UpperBound u _) =
+      unwords [">=", showVersion l, "&& <", showVersion u]
+
+-- | Entry point for the @gen-bounds@ command.
+genBounds
+    :: Verbosity
+    -> PackageDBStack
+    -> RepoContext
+    -> Compiler
+    -> Platform
+    -> ProgramConfiguration
+    -> Maybe SandboxPackageInfo
+    -> GlobalFlags
+    -> FreezeFlags
+    -> IO ()
+genBounds verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo
+      globalFlags freezeFlags = do
+
+    let cinfo = compilerInfo comp
+
+    cwd <- getCurrentDirectory
+    path <- tryFindPackageDesc cwd
+    gpd <- readPackageDescription verbosity path
+    let epd = finalizePackageDescription [] (const True) platform cinfo [] gpd
+    case epd of
+      Left _ -> putStrLn "finalizePackageDescription failed"
+      Right (pd,_) -> do
+        let needBounds = filter (not . hasUpperBound . depVersion) $
+                         buildDepends pd
+
+        if (null needBounds)
+          then putStrLn
+               "Congratulations, all your dependencies have upper bounds!"
+          else go needBounds
+  where
+     go needBounds = do
+       pkgs  <- getFreezePkgs
+                  verbosity packageDBs repoCtxt comp platform conf
+                  mSandboxPkgInfo globalFlags freezeFlags
+
+       putStrLn boundsNeededMsg
+
+       let isNeeded pkg = unPackageName (packageName pkg)
+                          `elem` map depName needBounds
+       let thePkgs = filter isNeeded pkgs
+
+       let padTo = maximum $ map (length . unPackageName . packageName) pkgs
+       mapM_ (putStrLn . (++",") . showBounds padTo) thePkgs
+
+     depName :: Dependency -> String
+     depName (Dependency (PackageName nm) _) = nm
+
+     depVersion :: Dependency -> VersionRange
+     depVersion (Dependency _ vr) = vr
+
+-- | The message printed when some dependencies are found to be lacking proper
+-- PVP-mandated bounds.
+boundsNeededMsg :: String
+boundsNeededMsg = unlines
+  [ ""
+  , "The following packages need bounds and here is a suggested starting point."
+  , "You can copy and paste this into the build-depends section in your .cabal"
+  , "file and it should work (with the appropriate removal of commas)."
+  , ""
+  , "Note that version bounds are a statement that you've successfully built and"
+  , "tested your package and expect it to work with any of the specified package"
+  , "versions (PROVIDED that those packages continue to conform with the PVP)."
+  , "Therefore, the version bounds generated here are the most conservative"
+  , "based on the versions that you are currently building with.  If you know"
+  , "your package will work with versions outside the ranges generated here,"
+  , "feel free to widen them."
+  , ""
+  ]
diff --git a/cabal/cabal-install/Distribution/Client/Get.hs b/cabal/cabal-install/Distribution/Client/Get.hs
--- a/cabal/cabal-install/Distribution/Client/Get.hs
+++ b/cabal/cabal-install/Distribution/Client/Get.hs
@@ -21,7 +21,7 @@
 import Distribution.Package
          ( PackageId, packageId, packageName )
 import Distribution.Simple.Setup
-         ( Flag(..), fromFlag, fromFlagOrDefault, flagToMaybe )
+         ( Flag(..), fromFlag, fromFlagOrDefault )
 import Distribution.Simple.Utils
          ( notice, die, info, writeFileAtomic )
 import Distribution.Verbosity
@@ -30,13 +30,11 @@
 import qualified Distribution.PackageDescription as PD
 
 import Distribution.Client.Setup
-         ( GlobalFlags(..), GetFlags(..) )
+         ( GlobalFlags(..), GetFlags(..), RepoContext(..) )
 import Distribution.Client.Types
 import Distribution.Client.Targets
 import Distribution.Client.Dependency
 import Distribution.Client.FetchUtils
-import Distribution.Client.HttpUtils
-        ( configureTransport, HttpTransport(..) )
 import qualified Distribution.Client.Tar as Tar (extractTarGzFile)
 import Distribution.Client.IndexUtils as IndexUtils
         ( getSourcePackages )
@@ -74,7 +72,7 @@
 
 -- | Entry point for the 'cabal get' command.
 get :: Verbosity
-    -> [Repo]
+    -> RepoContext
     -> GlobalFlags
     -> GetFlags
     -> [UserTarget]
@@ -82,7 +80,7 @@
 get verbosity _ _ _ [] =
     notice verbosity "No packages requested. Nothing to do."
 
-get verbosity repos globalFlags getFlags userTargets = do
+get verbosity repoCtxt globalFlags getFlags userTargets = do
   let useFork = case (getSourceRepository getFlags) of
         NoFlag -> False
         _      -> True
@@ -90,11 +88,9 @@
   unless useFork $
     mapM_ checkTarget userTargets
 
-  sourcePkgDb <- getSourcePackages verbosity repos
-
-  transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))
+  sourcePkgDb <- getSourcePackages verbosity repoCtxt
 
-  pkgSpecifiers <- resolveUserTargets verbosity transport
+  pkgSpecifiers <- resolveUserTargets verbosity repoCtxt
                    (fromFlag $ globalWorldFile globalFlags)
                    (packageIndex sourcePkgDb)
                    userTargets
@@ -108,7 +104,7 @@
 
   if useFork
     then fork pkgs
-    else unpack transport pkgs
+    else unpack pkgs
 
   where
     resolverParams sourcePkgDb pkgSpecifiers =
@@ -117,16 +113,16 @@
 
     prefix = fromFlagOrDefault "" (getDestDir getFlags)
 
-    fork :: [SourcePackage] -> IO ()
+    fork :: [UnresolvedSourcePackage] -> IO ()
     fork pkgs = do
       let kind = fromFlag . getSourceRepository $ getFlags
       branchers <- findUsableBranchers
       mapM_ (forkPackage verbosity branchers prefix kind) pkgs
 
-    unpack :: HttpTransport -> [SourcePackage] -> IO ()
-    unpack transport pkgs = do
+    unpack :: [UnresolvedSourcePackage] -> IO ()
+    unpack pkgs = do
       forM_ pkgs $ \pkg -> do
-        location <- fetchPackage transport verbosity (packageSource pkg)
+        location <- fetchPackage verbosity repoCtxt (packageSource pkg)
         let pkgid = packageId pkg
             descOverride | usePristine = Nothing
                          | otherwise   = packageDescrOverride pkg
@@ -230,7 +226,7 @@
                -- be created.
             -> (Maybe PD.RepoKind)
                -- ^ Which repo to choose.
-            -> SourcePackage
+            -> SourcePackage loc
                -- ^ The package to fork.
             -> IO ()
 forkPackage verbosity branchers prefix kind src = do
diff --git a/cabal/cabal-install/Distribution/Client/Glob.hs b/cabal/cabal-install/Distribution/Client/Glob.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/Glob.hs
@@ -0,0 +1,276 @@
+{-# LANGUAGE CPP, DeriveGeneric #-}
+
+--TODO: [code cleanup] plausibly much of this module should be merged with
+-- similar functionality in Cabal.
+module Distribution.Client.Glob
+    ( FilePathGlob(..)
+    , FilePathRoot(..)
+    , FilePathGlobRel(..)
+    , Glob
+    , GlobPiece(..)
+    , matchFileGlob
+    , matchFileGlobRel
+    , matchGlob
+    , isTrivialFilePathGlob
+    , getFilePathRootDirectory
+    ) where
+
+import           Data.Char (toUpper)
+import           Data.List (stripPrefix)
+#if !MIN_VERSION_base(4,8,0)
+import           Control.Applicative
+#endif
+import           Control.Monad
+import           Distribution.Compat.Binary
+import           GHC.Generics (Generic)
+
+import           Distribution.Text
+import           Distribution.Compat.ReadP (ReadP, (<++), (+++))
+import qualified Distribution.Compat.ReadP as Parse
+import qualified Text.PrettyPrint as Disp
+
+import           System.FilePath
+import           System.Directory
+
+
+-- | A file path specified by globbing
+--
+data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel
+  deriving (Eq, Show, Generic)
+
+data FilePathGlobRel
+   = GlobDir  !Glob !FilePathGlobRel
+   | GlobFile !Glob
+   | GlobDirTrailing                -- ^ trailing dir, a glob ending in @/@
+  deriving (Eq, Show, Generic)
+
+-- | A single directory or file component of a globbed path
+type Glob = [GlobPiece]
+
+-- | A piece of a globbing pattern
+data GlobPiece = WildCard
+               | Literal String
+               | Union [Glob]
+  deriving (Eq, Show, Generic)
+
+data FilePathRoot
+   = FilePathRelative
+   | FilePathUnixRoot
+   | FilePathWinDrive Char
+   | FilePathHomeDir
+  deriving (Eq, Show, Generic)
+
+instance Binary FilePathGlob
+instance Binary FilePathRoot
+instance Binary FilePathGlobRel
+instance Binary GlobPiece
+
+
+-- | Check if a 'FilePathGlob' doesn't actually make use of any globbing and
+-- is in fact equivalent to a non-glob 'FilePath'.
+--
+-- If it is trivial in this sense then the result is the equivalent constant
+-- 'FilePath'. On the other hand if it is not trivial (so could in principle
+-- match more than one file) then the result is @Nothing@.
+--
+isTrivialFilePathGlob :: FilePathGlob -> Maybe FilePath
+isTrivialFilePathGlob (FilePathGlob root pathglob) =
+    case root of
+      FilePathRelative       -> go []          pathglob
+      FilePathUnixRoot       -> go ["/"]       pathglob
+      FilePathWinDrive drive -> go [drive:":"] pathglob
+      FilePathHomeDir        -> Nothing
+  where
+    go paths (GlobDir  [Literal path] globs) = go (path:paths) globs
+    go paths (GlobFile [Literal path]) = Just (joinPath (reverse (path:paths)))
+    go paths  GlobDirTrailing          = Just (addTrailingPathSeparator
+                                                 (joinPath (reverse paths)))
+    go _ _ = Nothing
+
+-- | Get the 'FilePath' corresponding to a 'FilePathRoot'.
+--
+-- The 'FilePath' argument is required to supply the path for the
+-- 'FilePathRelative' case.
+--
+getFilePathRootDirectory :: FilePathRoot
+                         -> FilePath      -- ^ root for relative paths
+                         -> IO FilePath
+getFilePathRootDirectory  FilePathRelative     root = return root
+getFilePathRootDirectory  FilePathUnixRoot        _ = return "/"
+getFilePathRootDirectory (FilePathWinDrive drive) _ = return (drive:":")
+getFilePathRootDirectory  FilePathHomeDir         _ = getHomeDirectory
+
+
+------------------------------------------------------------------------------
+-- Matching
+--
+
+-- | Match a 'FilePathGlob' against the file system, starting from a given
+-- root directory for relative paths. The results of relative globs are
+-- relative to the given root. Matches for absolute globs are absolute.
+--
+matchFileGlob :: FilePath -> FilePathGlob -> IO [FilePath]
+matchFileGlob relroot (FilePathGlob globroot glob) = do
+    root <- getFilePathRootDirectory globroot relroot
+    matches <- matchFileGlobRel root glob
+    case globroot of
+      FilePathRelative -> return matches
+      _                -> return (map (root </>) matches)
+
+-- | Match a 'FilePathGlobRel' against the file system, starting from a
+-- given root directory. The results are all relative to the given root.
+--
+matchFileGlobRel :: FilePath -> FilePathGlobRel -> IO [FilePath]
+matchFileGlobRel root glob0 = go glob0 ""
+  where
+    go (GlobFile glob) dir = do
+      entries <- getDirectoryContents (root </> dir)
+      let files = filter (matchGlob glob) entries
+      return (map (dir </>) files)
+
+    go (GlobDir glob globPath) dir = do
+      entries <- getDirectoryContents (root </> dir)
+      subdirs <- filterM (\subdir -> doesDirectoryExist
+                                       (root </> dir </> subdir))
+               $ filter (matchGlob glob) entries
+      concat <$> mapM (\subdir -> go globPath (dir </> subdir)) subdirs
+
+    go GlobDirTrailing dir = return [dir]
+
+
+-- | Match a globbing pattern against a file path component
+--
+matchGlob :: Glob -> String -> Bool
+matchGlob = goStart
+  where
+    -- From the man page, glob(7):
+    --   "If a filename starts with a '.', this character must be
+    --    matched explicitly."
+
+    go, goStart :: [GlobPiece] -> String -> Bool
+
+    goStart (WildCard:_) ('.':_)  = False
+    goStart (Union globs:rest) cs = any (\glob -> goStart (glob ++ rest) cs)
+                                        globs
+    goStart rest               cs = go rest cs
+
+    go []                 ""    = True
+    go (Literal lit:rest) cs
+      | Just cs' <- stripPrefix lit cs
+                                = go rest cs'
+      | otherwise               = False
+    go [WildCard]         ""    = True
+    go (WildCard:rest)   (c:cs) = go rest (c:cs) || go (WildCard:rest) cs
+    go (Union globs:rest)   cs  = any (\glob -> go (glob ++ rest) cs) globs
+    go []                (_:_)  = False
+    go (_:_)              ""    = False
+
+
+------------------------------------------------------------------------------
+-- Parsing & printing
+--
+
+instance Text FilePathGlob where
+  disp (FilePathGlob root pathglob) = disp root Disp.<> disp pathglob
+  parse =
+    parse >>= \root ->
+        (FilePathGlob root <$> parse)
+    <++ (when (root == FilePathRelative) Parse.pfail >>
+         return (FilePathGlob root GlobDirTrailing))
+
+instance Text FilePathRoot where
+  disp  FilePathRelative    = Disp.empty
+  disp  FilePathUnixRoot    = Disp.char '/'
+  disp (FilePathWinDrive c) = Disp.char c
+                      Disp.<> Disp.char ':'
+                      Disp.<> Disp.char '\\'
+  disp FilePathHomeDir      = Disp.char '~'
+                      Disp.<> Disp.char '/'
+
+  parse =
+        (     (Parse.char '/' >> return FilePathUnixRoot)
+          +++ (Parse.char '~' >> Parse.char '/' >> return FilePathHomeDir)
+          +++ (do drive <- Parse.satisfy (\c -> (c >= 'a' && c <= 'z')
+                                             || (c >= 'A' && c <= 'Z'))
+                  _ <- Parse.char ':'
+                  _ <- Parse.char '/' +++ Parse.char '\\'
+                  return (FilePathWinDrive (toUpper drive)))
+        )
+    <++ return FilePathRelative
+
+instance Text FilePathGlobRel where
+  disp (GlobDir  glob pathglob) = dispGlob glob
+                          Disp.<> Disp.char '/'
+                          Disp.<> disp pathglob
+  disp (GlobFile glob)          = dispGlob glob
+  disp  GlobDirTrailing         = Disp.empty
+
+  parse = parsePath
+    where
+      parsePath :: ReadP r FilePathGlobRel
+      parsePath =
+        parseGlob >>= \globpieces ->
+            asDir globpieces
+        <++ asTDir globpieces
+        <++ asFile globpieces
+
+      asDir  glob = do dirSep
+                       globs <- parsePath
+                       return (GlobDir glob globs)
+      asTDir glob = do dirSep
+                       return (GlobDir glob GlobDirTrailing)
+      asFile glob = return (GlobFile glob)
+
+      dirSep = (Parse.char '/' >> return ())
+           +++ (do _ <- Parse.char '\\'
+                   -- check this isn't an escape code
+                   following <- Parse.look
+                   case following of
+                     (c:_) | isGlobEscapedChar c -> Parse.pfail
+                     _                           -> return ())
+
+
+dispGlob :: Glob -> Disp.Doc
+dispGlob = Disp.hcat . map dispPiece
+  where
+    dispPiece WildCard      = Disp.char '*'
+    dispPiece (Literal str) = Disp.text (escape str)
+    dispPiece (Union globs) = Disp.braces
+                                (Disp.hcat (Disp.punctuate
+                                             (Disp.char ',')
+                                             (map dispGlob globs)))
+    escape []               = []
+    escape (c:cs)
+      | isGlobEscapedChar c = '\\' : c : escape cs
+      | otherwise           =        c : escape cs
+
+parseGlob :: ReadP r Glob
+parseGlob = Parse.many1 parsePiece
+  where
+    parsePiece = literal +++ wildcard +++ union
+
+    wildcard = Parse.char '*' >> return WildCard
+
+    union = Parse.between (Parse.char '{') (Parse.char '}') $
+              fmap Union (Parse.sepBy1 parseGlob (Parse.char ','))
+
+    literal = Literal `fmap` litchars1
+
+    litchar = normal +++ escape
+
+    normal  = Parse.satisfy (\c -> not (isGlobEscapedChar c)
+                                && c /= '/' && c /= '\\')
+    escape  = Parse.char '\\' >> Parse.satisfy isGlobEscapedChar
+
+    litchars1 :: ReadP r [Char]
+    litchars1 = liftM2 (:) litchar litchars
+
+    litchars :: ReadP r [Char]
+    litchars = litchars1 <++ return []
+
+isGlobEscapedChar :: Char -> Bool
+isGlobEscapedChar '*'  = True
+isGlobEscapedChar '{'  = True
+isGlobEscapedChar '}'  = True
+isGlobEscapedChar ','  = True
+isGlobEscapedChar _    = False
diff --git a/cabal/cabal-install/Distribution/Client/GlobalFlags.hs b/cabal/cabal-install/Distribution/Client/GlobalFlags.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/GlobalFlags.hs
@@ -0,0 +1,260 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE RecordWildCards #-}
+module Distribution.Client.GlobalFlags (
+    GlobalFlags(..)
+  , defaultGlobalFlags
+  , RepoContext(..)
+  , withRepoContext
+  , withRepoContext'
+  ) where
+
+import Distribution.Client.Types
+         ( Repo(..), RemoteRepo(..) )
+import Distribution.Compat.Semigroup
+import Distribution.Simple.Setup
+         ( Flag(..), fromFlag, flagToMaybe )
+import Distribution.Utils.NubList
+         ( NubList, fromNubList )
+import Distribution.Client.HttpUtils
+         ( HttpTransport, configureTransport )
+import Distribution.Verbosity
+         ( Verbosity )
+import Distribution.Simple.Utils
+         ( info )
+
+import Data.Maybe
+         ( fromMaybe )
+import Control.Concurrent
+         ( MVar, newMVar, modifyMVar )
+import Control.Exception
+         ( throwIO )
+import Control.Monad
+         ( when )
+import System.FilePath
+         ( (</>) )
+import Network.URI
+         ( uriScheme, uriPath )
+import Data.Map
+         ( Map )
+import qualified Data.Map as Map
+import GHC.Generics ( Generic )
+
+import qualified Hackage.Security.Client                    as Sec
+import qualified Hackage.Security.Util.Path                 as Sec
+import qualified Hackage.Security.Util.Pretty               as Sec
+import qualified Hackage.Security.Client.Repository.Cache   as Sec
+import qualified Hackage.Security.Client.Repository.Local   as Sec.Local
+import qualified Hackage.Security.Client.Repository.Remote  as Sec.Remote
+import qualified Distribution.Client.Security.HTTP          as Sec.HTTP
+
+-- ------------------------------------------------------------
+-- * Global flags
+-- ------------------------------------------------------------
+
+-- | Flags that apply at the top level, not to any sub-command.
+data GlobalFlags = GlobalFlags {
+    globalVersion           :: Flag Bool,
+    globalNumericVersion    :: Flag Bool,
+    globalConfigFile        :: Flag FilePath,
+    globalSandboxConfigFile :: Flag FilePath,
+    globalConstraintsFile   :: Flag FilePath,
+    globalRemoteRepos       :: NubList RemoteRepo,     -- ^ Available Hackage servers.
+    globalCacheDir          :: Flag FilePath,
+    globalLocalRepos        :: NubList FilePath,
+    globalLogsDir           :: Flag FilePath,
+    globalWorldFile         :: Flag FilePath,
+    globalRequireSandbox    :: Flag Bool,
+    globalIgnoreSandbox     :: Flag Bool,
+    globalIgnoreExpiry      :: Flag Bool,    -- ^ Ignore security expiry dates
+    globalHttpTransport     :: Flag String
+  } deriving Generic
+
+defaultGlobalFlags :: GlobalFlags
+defaultGlobalFlags  = GlobalFlags {
+    globalVersion           = Flag False,
+    globalNumericVersion    = Flag False,
+    globalConfigFile        = mempty,
+    globalSandboxConfigFile = mempty,
+    globalConstraintsFile   = mempty,
+    globalRemoteRepos       = mempty,
+    globalCacheDir          = mempty,
+    globalLocalRepos        = mempty,
+    globalLogsDir           = mempty,
+    globalWorldFile         = mempty,
+    globalRequireSandbox    = Flag False,
+    globalIgnoreSandbox     = Flag False,
+    globalIgnoreExpiry      = Flag False,
+    globalHttpTransport     = mempty
+  }
+
+instance Monoid GlobalFlags where
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup GlobalFlags where
+  (<>) = gmappend
+
+-- ------------------------------------------------------------
+-- * Repo context
+-- ------------------------------------------------------------
+
+-- | Access to repositories
+data RepoContext = RepoContext {
+    -- | All user-specified repositories
+    repoContextRepos :: [Repo]
+
+    -- | Get the HTTP transport
+    --
+    -- The transport will be initialized on the first call to this function.
+    --
+    -- NOTE: It is important that we don't eagerly initialize the transport.
+    -- Initializing the transport is not free, and especially in contexts where
+    -- we don't know a-priori whether or not we need the transport (for instance
+    -- when using cabal in "nix mode") incurring the overhead of transport
+    -- initialization on _every_ invocation (eg @cabal build@) is undesirable.
+  , repoContextGetTransport :: IO HttpTransport
+
+    -- | Get the (initialized) secure repo
+    --
+    -- (the 'Repo' type itself is stateless and must remain so, because it
+    -- must be serializable)
+  , repoContextWithSecureRepo :: forall a.
+                                 Repo
+                              -> (forall down. Sec.Repository down -> IO a)
+                              -> IO a
+
+    -- | Should we ignore expiry times (when checking security)?
+  , repoContextIgnoreExpiry :: Bool
+  }
+
+-- | Wrapper around 'Repository', hiding the type argument
+data SecureRepo = forall down. SecureRepo (Sec.Repository down)
+
+withRepoContext :: Verbosity -> GlobalFlags -> (RepoContext -> IO a) -> IO a
+withRepoContext verbosity globalFlags =
+    withRepoContext'
+      verbosity
+      (fromNubList (globalRemoteRepos   globalFlags))
+      (fromNubList (globalLocalRepos    globalFlags))
+      (fromFlag    (globalCacheDir      globalFlags))
+      (flagToMaybe (globalHttpTransport globalFlags))
+      (flagToMaybe (globalIgnoreExpiry  globalFlags))
+
+withRepoContext' :: Verbosity -> [RemoteRepo] -> [FilePath]
+                 -> FilePath  -> Maybe String -> Maybe Bool
+                 -> (RepoContext -> IO a)
+                 -> IO a
+withRepoContext' verbosity remoteRepos localRepos
+                 sharedCacheDir httpTransport ignoreExpiry = \callback -> do
+    transportRef <- newMVar Nothing
+    let httpLib = Sec.HTTP.transportAdapter
+                    verbosity
+                    (getTransport transportRef)
+    initSecureRepos verbosity httpLib secureRemoteRepos $ \secureRepos' ->
+      callback RepoContext {
+          repoContextRepos          = allRemoteRepos
+                                   ++ map RepoLocal localRepos
+        , repoContextGetTransport   = getTransport transportRef
+        , repoContextWithSecureRepo = withSecureRepo secureRepos'
+        , repoContextIgnoreExpiry   = fromMaybe False ignoreExpiry
+        }
+  where
+    secureRemoteRepos =
+      [ (remote, cacheDir) | RepoSecure remote cacheDir <- allRemoteRepos ]
+    allRemoteRepos =
+      [ (if isSecure then RepoSecure else RepoRemote) remote cacheDir
+      | remote <- remoteRepos
+      , let cacheDir = sharedCacheDir </> remoteRepoName remote
+            isSecure = remoteRepoSecure remote == Just True
+      ]
+
+    getTransport :: MVar (Maybe HttpTransport) -> IO HttpTransport
+    getTransport transportRef =
+      modifyMVar transportRef $ \mTransport -> do
+        transport <- case mTransport of
+          Just tr -> return tr
+          Nothing -> configureTransport verbosity httpTransport
+        return (Just transport, transport)
+
+    withSecureRepo :: Map Repo SecureRepo
+                   -> Repo
+                   -> (forall down. Sec.Repository down -> IO a)
+                   -> IO a
+    withSecureRepo secureRepos repo callback =
+      case Map.lookup repo secureRepos of
+        Just (SecureRepo secureRepo) -> callback secureRepo
+        Nothing -> throwIO $ userError "repoContextWithSecureRepo: unknown repo"
+
+-- | Initialize the provided secure repositories
+--
+-- Assumed invariant: `remoteRepoSecure` should be set for all these repos.
+initSecureRepos :: forall a. Verbosity
+                -> Sec.HTTP.HttpLib
+                -> [(RemoteRepo, FilePath)]
+                -> (Map Repo SecureRepo -> IO a)
+                -> IO a
+initSecureRepos verbosity httpLib repos callback = go Map.empty repos
+  where
+    go :: Map Repo SecureRepo -> [(RemoteRepo, FilePath)] -> IO a
+    go !acc [] = callback acc
+    go !acc ((r,cacheDir):rs) = do
+      cachePath <- Sec.makeAbsolute $ Sec.fromFilePath cacheDir
+      initSecureRepo verbosity httpLib r cachePath $ \r' ->
+        go (Map.insert (RepoSecure r cacheDir) r' acc) rs
+
+-- | Initialize the given secure repo
+--
+-- The security library has its own concept of a "local" repository, distinct
+-- from @cabal-install@'s; these are secure repositories, but live in the local
+-- file system. We use the convention that these repositories are identified by
+-- URLs of the form @file:/path/to/local/repo@.
+initSecureRepo :: Verbosity
+               -> Sec.HTTP.HttpLib
+               -> RemoteRepo  -- ^ Secure repo ('remoteRepoSecure' assumed)
+               -> Sec.Path Sec.Absolute -- ^ Cache dir
+               -> (SecureRepo -> IO a)  -- ^ Callback
+               -> IO a
+initSecureRepo verbosity httpLib RemoteRepo{..} cachePath = \callback -> do
+    withRepo $ \r -> do
+      requiresBootstrap <- Sec.requiresBootstrap r
+      when requiresBootstrap $ Sec.uncheckClientErrors $
+        Sec.bootstrap r
+          (map Sec.KeyId    remoteRepoRootKeys)
+          (Sec.KeyThreshold (fromIntegral remoteRepoKeyThreshold))
+      callback $ SecureRepo r
+  where
+    -- Initialize local or remote repo depending on the URI
+    withRepo :: (forall down. Sec.Repository down -> IO a) -> IO a
+    withRepo callback | uriScheme remoteRepoURI == "file:" = do
+      dir <- Sec.makeAbsolute $ Sec.fromFilePath (uriPath remoteRepoURI)
+      Sec.Local.withRepository dir
+                               cache
+                               Sec.hackageRepoLayout
+                               Sec.hackageIndexLayout
+                               logTUF
+                               callback
+    withRepo callback =
+      Sec.Remote.withRepository httpLib
+                                [remoteRepoURI]
+                                Sec.Remote.defaultRepoOpts
+                                cache
+                                Sec.hackageRepoLayout
+                                Sec.hackageIndexLayout
+                                logTUF
+                                callback
+
+    cache :: Sec.Cache
+    cache = Sec.Cache {
+        cacheRoot   = cachePath
+      , cacheLayout = Sec.cabalCacheLayout
+      }
+
+    -- We display any TUF progress only in verbose mode, including any transient
+    -- verification errors. If verification fails, then the final exception that
+    -- is thrown will of course be shown.
+    logTUF :: Sec.LogMessage -> IO ()
+    logTUF = info verbosity . Sec.pretty
diff --git a/cabal/cabal-install/Distribution/Client/Haddock.hs b/cabal/cabal-install/Distribution/Client/Haddock.hs
--- a/cabal/cabal-install/Distribution/Client/Haddock.hs
+++ b/cabal/cabal-install/Distribution/Client/Haddock.hs
@@ -17,6 +17,7 @@
     where
 
 import Data.List (maximumBy)
+import Data.Foldable (forM_)
 import System.Directory (createDirectoryIfMissing, renameFile)
 import System.FilePath ((</>), splitFileName)
 import Distribution.Package
@@ -40,9 +41,7 @@
 regenerateHaddockIndex verbosity pkgs conf index = do
       (paths, warns) <- haddockPackagePaths pkgs' Nothing
       let paths' = [ (interface, html) | (interface, Just html) <- paths]
-      case warns of
-        Nothing -> return ()
-        Just m  -> debug verbosity m
+      forM_ warns (debug verbosity)
 
       (confHaddock, _, _) <-
           requireProgramVersion verbosity haddockProgram
diff --git a/cabal/cabal-install/Distribution/Client/HttpUtils.hs b/cabal/cabal-install/Distribution/Client/HttpUtils.hs
--- a/cabal/cabal-install/Distribution/Client/HttpUtils.hs
+++ b/cabal/cabal-install/Distribution/Client/HttpUtils.hs
@@ -1,11 +1,12 @@
 {-# LANGUAGE CPP, BangPatterns #-}
 -----------------------------------------------------------------------------
--- | Separate module for HTTP actions, using a proxy server if one exists
+-- | Separate module for HTTP actions, using a proxy server if one exists.
 -----------------------------------------------------------------------------
 module Distribution.Client.HttpUtils (
     DownloadResult(..),
     configureTransport,
     HttpTransport(..),
+    HttpCode,
     downloadURI,
     transportCheckHttps,
     remoteRepoCheckHttps,
@@ -18,7 +19,7 @@
          , Header(..), HeaderName(..), lookupHeader )
 import Network.HTTP.Proxy ( Proxy(..), fetchProxy)
 import Network.URI
-         ( URI (..), URIAuth (..) )
+         ( URI (..), URIAuth (..), uriToString )
 import Network.Browser
          ( browse, setOutHandler, setErrHandler, setProxy
          , setAuthorityGen, request, setAllowBasicAuth, setUserAgent )
@@ -32,7 +33,7 @@
 import Data.List
          ( isPrefixOf, find, intercalate )
 import Data.Maybe
-         ( listToMaybe, maybeToList )
+         ( listToMaybe, maybeToList, fromMaybe )
 import qualified Paths_cabal_install (version)
 import Distribution.Verbosity (Verbosity)
 import Distribution.Simple.Utils
@@ -69,7 +70,7 @@
         ( IOEncoding(..), getEffectiveEnvironment )
 import Numeric (showHex)
 import System.Directory (canonicalizePath)
-import System.IO (hClose, hPutStr)
+import System.IO (hClose)
 import System.FilePath (takeFileName, takeDirectory)
 import System.Random (randomRIO)
 import System.Exit (ExitCode(..))
@@ -115,7 +116,7 @@
           = transport
 
     withTempFileName (takeDirectory path) (takeFileName path) $ \tmpFile -> do
-      result <- getHttp transport' verbosity uri etag tmpFile
+      result <- getHttp transport' verbosity uri etag tmpFile []
 
       -- Only write the etag if we get a 200 response code.
       -- A 304 still sends us an etag header.
@@ -196,7 +197,8 @@
 isOldHackageURI uri
     = case uriAuthority uri of
         Just (URIAuth {uriRegName = "hackage.haskell.org"}) ->
-            FilePath.Posix.splitDirectories (uriPath uri) == ["/","packages","archive"]
+            FilePath.Posix.splitDirectories (uriPath uri)
+            == ["/","packages","archive"]
         _ -> False
 
 
@@ -208,7 +210,7 @@
       -- | GET a URI, with an optional ETag (to do a conditional fetch),
       -- write the resource to the given file and return the HTTP status code,
       -- and optional ETag.
-      getHttp  :: Verbosity -> URI -> Maybe ETag -> FilePath
+      getHttp  :: Verbosity -> URI -> Maybe ETag -> FilePath -> [Header]
                -> IO (HttpCode, Maybe ETag),
 
       -- | POST a resource to a URI, with optional auth (username, password)
@@ -222,11 +224,17 @@
       postHttpFile :: Verbosity -> URI -> FilePath -> Maybe Auth
                    -> IO (HttpCode, String),
 
+      -- | PUT a file resource to a URI, with optional auth
+      -- (username, password), extra headers and return the HTTP status code
+      -- and any error string.
+      putHttpFile :: Verbosity -> URI -> FilePath -> Maybe Auth -> [Header]
+                  -> IO (HttpCode, String),
+
       -- | Whether this transport supports https or just http.
       transportSupportsHttps :: Bool,
 
       -- | Whether this transport implementation was specifically chosen by
-      -- the user via configuration, or whether it was automatically selected. 
+      -- the user via configuration, or whether it was automatically selected.
       -- Strictly speaking this is not a property of the transport itself but
       -- about how it was chosen. Nevertheless it's convenient to keep here.
       transportManuallySelected :: Bool
@@ -310,9 +318,9 @@
 
 curlTransport :: ConfiguredProgram -> HttpTransport
 curlTransport prog =
-    HttpTransport gethttp posthttp posthttpfile True False
+    HttpTransport gethttp posthttp posthttpfile puthttpfile True False
   where
-    gethttp verbosity uri etag destPath = do
+    gethttp verbosity uri etag destPath reqHeaders = do
         withTempFile (takeDirectory destPath)
                      "curl-headers.txt" $ \tmpFile tmpHandle -> do
           hClose tmpHandle
@@ -326,6 +334,9 @@
                 ++ concat
                    [ ["--header", "If-None-Match: " ++ t]
                    | t <- maybeToList etag ]
+                ++ concat
+                   [ ["--header", show name ++ ": " ++ value]
+                   | Header name value <- reqHeaders ]
 
           resp <- getProgramInvocationOutput verbosity
                     (programInvocation prog args)
@@ -335,17 +346,41 @@
 
     posthttp = noPostYet
 
+    addAuthConfig auth progInvocation = progInvocation
+      { progInvokeInput = do
+          (uname, passwd) <- auth
+          return $ unlines
+            [ "--digest"
+            , "--user " ++ uname ++ ":" ++ passwd
+            ]
+      , progInvokeArgs = ["--config", "-"] ++ progInvokeArgs progInvocation
+      }
+
     posthttpfile verbosity uri path auth = do
         let args = [ show uri
                    , "--form", "package=@"++path
-                   , "--write-out", "%{http_code}"
+                   , "--write-out", "\n%{http_code}"
                    , "--user-agent", userAgent
                    , "--silent", "--show-error"
-                   , "--header", "Accept: text/plain" ]
+                   , "--header", "Accept: text/plain"
+                   ]
+        resp <- getProgramInvocationOutput verbosity $ addAuthConfig auth
+                  (programInvocation prog args)
+        (code, err, _etag) <- parseResponse uri resp ""
+        return (code, err)
+
+    puthttpfile verbosity uri path auth headers = do
+        let args = [ show uri
+                   , "--request", "PUT", "--data-binary", "@"++path
+                   , "--write-out", "\n%{http_code}"
+                   , "--user-agent", userAgent
+                   , "--silent", "--show-error"
+                   , "--header", "Accept: text/plain"
+                   ]
                 ++ concat
-                   [ ["--digest", "--user", uname ++ ":" ++ passwd]
-                   | (uname,passwd) <- maybeToList auth ]
-        resp <- getProgramInvocationOutput verbosity
+                   [ ["--header", show name ++ ": " ++ value]
+                   | Header name value <- headers ]
+        resp <- getProgramInvocationOutput verbosity $ addAuthConfig auth
                   (programInvocation prog args)
         (code, err, _etag) <- parseResponse uri resp ""
         return (code, err)
@@ -357,11 +392,13 @@
             case reverse (lines resp) of
               (codeLine:rerrLines) ->
                 case readMaybe (trim codeLine) of
-                  Just i  -> let errstr = unlines (reverse rerrLines)
+                  Just i  -> let errstr = mkErrstr rerrLines
                               in Just (i, errstr)
                   Nothing -> Nothing
               []          -> Nothing
 
+          mkErrstr = unlines . reverse . dropWhile (all isSpace)
+
           mb_etag :: Maybe ETag
           mb_etag  = listToMaybe $ reverse
                      [ etag
@@ -374,15 +411,14 @@
 
 wgetTransport :: ConfiguredProgram -> HttpTransport
 wgetTransport prog =
-    HttpTransport gethttp posthttp posthttpfile True False
+    HttpTransport gethttp posthttp posthttpfile puthttpfile True False
   where
-    gethttp verbosity uri etag destPath = do
-        resp <- runWGet verbosity args
+    gethttp verbosity uri etag destPath reqHeaders = do
+        resp <- runWGet verbosity uri args
         (code, _err, etag') <- parseResponse uri resp
         return (code, etag')
       where
-        args = [ show uri
-               , "--output-document=" ++ destPath
+        args = [ "--output-document=" ++ destPath
                , "--user-agent=" ++ userAgent
                , "--tries=5"
                , "--timeout=15"
@@ -390,6 +426,8 @@
             ++ concat
                [ ["--header", "If-None-Match: " ++ t]
                | t <- maybeToList etag ]
+            ++ [ "--header=" ++ show name ++ ": " ++ value
+               | Header name value <- reqHeaders ]
 
     posthttp = noPostYet
 
@@ -400,25 +438,45 @@
           BS.hPut tmpHandle body
           BS.writeFile "wget.in" body
           hClose tmpHandle
-          let args = [ show uri
-                     , "--post-file=" ++ tmpFile
+          let args = [ "--post-file=" ++ tmpFile
                      , "--user-agent=" ++ userAgent
                      , "--server-response"
-                     , "--header=Content-type: multipart/form-data; " ++ 
+                     , "--header=Content-type: multipart/form-data; " ++
                                               "boundary=" ++ boundary ]
-                  ++ concat
-                     [ [ "--http-user=" ++ uname
-                       , "--http-password=" ++ passwd ]
-                     | (uname,passwd) <- maybeToList auth ]
-
-          resp <- runWGet verbosity args
+          resp <- runWGet verbosity (addUriAuth auth uri) args
           (code, err, _etag) <- parseResponse uri resp
           return (code, err)
 
-    runWGet verbosity args = do
+    puthttpfile verbosity uri path auth headers = do
+        let args = [ "--method=PUT", "--body-file="++path
+                   , "--user-agent=" ++ userAgent
+                   , "--server-response"
+                   , "--header=Accept: text/plain" ]
+                ++ [ "--header=" ++ show name ++ ": " ++ value
+                   | Header name value <- headers ]
+
+        resp <- runWGet verbosity (addUriAuth auth uri) args
+        (code, err, _etag) <- parseResponse uri resp
+        return (code, err)
+
+    addUriAuth Nothing uri = uri
+    addUriAuth (Just (user, pass)) uri = uri
+      { uriAuthority = Just a { uriUserInfo = user ++ ":" ++ pass ++ "@" }
+      }
+     where
+      a = fromMaybe (URIAuth "" "" "") (uriAuthority uri)
+
+    runWGet verbosity uri args = do
+        -- We pass the URI via STDIN because it contains the users' credentials
+        -- and sensitive data should not be passed via command line arguments.
+        let
+          invocation = (programInvocation prog ("--input-file=-" : args))
+            { progInvokeInput = Just (uriToString id uri "")
+            }
+
         -- wget returns its output on stderr rather than stdout
         (_, resp, exitCode) <- getProgramInvocationOutputAndErrors verbosity
-                                 (programInvocation prog args)
+                                 invocation
         -- wget returns exit code 8 for server "errors" like "304 not modified"
         if exitCode == ExitSuccess || exitCode == ExitFailure 8
           then return resp
@@ -450,95 +508,128 @@
 
 powershellTransport :: ConfiguredProgram -> HttpTransport
 powershellTransport prog =
-    HttpTransport gethttp posthttp posthttpfile True False
+    HttpTransport gethttp posthttp posthttpfile puthttpfile True False
   where
-    gethttp verbosity uri etag destPath =
-        withTempFile (takeDirectory destPath)
-                     "psScript.ps1" $ \tmpFile tmpHandle -> do
-           hPutStr tmpHandle script
-           hClose tmpHandle
-           let args = ["-InputFormat", "None", "-File", tmpFile]
-           resp <- getProgramInvocationOutput verbosity
-                     (programInvocation prog args)
-           parseResponse resp
+    gethttp verbosity uri etag destPath reqHeaders = do
+      resp <- runPowershellScript verbosity $
+        webclientScript
+          (setupHeaders ((useragentHeader : etagHeader) ++ reqHeaders))
+          [ "$wc.DownloadFile(" ++ escape (show uri)
+              ++ "," ++ escape destPath ++ ");"
+          , "Write-Host \"200\";"
+          , "Write-Host $wc.ResponseHeaders.Item(\"ETag\");"
+          ]
+      parseResponse resp
       where
-        script =
-          concatMap (++";\n") $
-            [ "$wc = new-object system.net.webclient"
-            , "$wc.Headers.Add(\"user-agent\","++escape userAgent++")"]
-         ++ [ "$wc.Headers.Add(\"If-None-Match\"," ++ t ++ ")"
-            | t <- maybeToList etag ]
-         ++ [ "Try {"
-            ,  "$wc.DownloadFile("++ escape (show uri) ++
-                              "," ++ escape destPath ++ ")"
-            , "} Catch {Write-Error $_; Exit(5);}"
-            , "Write-Host \"200\""
-            , "Write-Host $wc.ResponseHeaders.Item(\"ETag\")"
-            , "Exit" ]
-
-        escape x = '"' : x ++ "\"" --TODO write/find real escape.
-
         parseResponse x = case readMaybe . unlines . take 1 . lines $ trim x of
           Just i  -> return (i, Nothing) -- TODO extract real etag
           Nothing -> statusParseFail uri x
+        etagHeader = [ Header HdrIfNoneMatch t | t <- maybeToList etag ]
 
     posthttp = noPostYet
 
     posthttpfile verbosity uri path auth =
-        withTempFile (takeDirectory path)
-                     (takeFileName path) $ \tmpFile tmpHandle ->
-        withTempFile (takeDirectory path)
-                     "psScript.ps1" $ \tmpScriptFile tmpScriptHandle -> do
-          (body, boundary) <- generateMultipartBody path
-          BS.hPut tmpHandle body
-          hClose tmpHandle
+      withTempFile (takeDirectory path)
+                   (takeFileName path) $ \tmpFile tmpHandle -> do
+        (body, boundary) <- generateMultipartBody path
+        BS.hPut tmpHandle body
+        hClose tmpHandle
+        fullPath <- canonicalizePath tmpFile
 
-          fullPath <- canonicalizePath tmpFile
-          hPutStr tmpScriptHandle (script fullPath boundary)
-          hClose tmpScriptHandle
-          let args = ["-InputFormat", "None", "-File", tmpScriptFile]
-          resp <- getProgramInvocationOutput verbosity
-                    (programInvocation prog args)
-          parseResponse resp
-      where
-        script fullPath boundary = 
-          concatMap (++";\n") $
-            [ "$wc = new-object system.net.webclient"
-            , "$wc.Headers.Add(\"user-agent\","++escape userAgent++")"
-            , "$wc.Headers.Add(\"Content-type\"," ++
-                              "\"multipart/form-data; " ++
-                              "boundary="++boundary++"\")" ]
-         ++ [ "$wc.Credentials = new-object System.Net.NetworkCredential("
-              ++ escape uname ++ "," ++ escape passwd ++ ",\"\")"
-            | (uname,passwd) <- maybeToList auth ]
-         ++ [ "Try {"
-            , "$bytes = [System.IO.File]::ReadAllBytes("++escape fullPath++")"
-            , "$wc.UploadData("++ escape (show uri) ++ ",$bytes)"
-            , "} Catch {Write-Error $_; Exit(1);}"
-            , "Write-Host \"200\""
-            , "Exit" ]
+        let contentHeader = Header HdrContentType
+              ("multipart/form-data; boundary=" ++ boundary)
+        resp <- runPowershellScript verbosity $ webclientScript
+          (setupHeaders (contentHeader : extraHeaders) ++ setupAuth auth)
+          (uploadFileAction "POST" uri fullPath)
+        parseUploadResponse uri resp
 
-        escape x = show x
+    puthttpfile verbosity uri path auth headers = do
+      fullPath <- canonicalizePath path
+      resp <- runPowershellScript verbosity $ webclientScript
+        (setupHeaders (extraHeaders ++ headers) ++ setupAuth auth)
+        (uploadFileAction "PUT" uri fullPath)
+      parseUploadResponse uri resp
 
-        parseResponse x = case readMaybe . unlines . take 1 . lines $ trim x of
-          Just i -> return (i, x) -- TODO extract real etag
-          Nothing -> statusParseFail uri x
+    runPowershellScript verbosity script = do
+      let args =
+            [ "-InputFormat", "None"
+            -- the default execution policy doesn't allow running
+            -- unsigned scripts, so we need to tell powershell to bypass it
+            , "-ExecutionPolicy", "bypass"
+            , "-NoProfile", "-NonInteractive"
+            , "-Command", "-"
+            ]
+      getProgramInvocationOutput verbosity (programInvocation prog args)
+        { progInvokeInput = Just (script ++ "\nExit(0);")
+        }
 
+    escape = show
 
+    useragentHeader = Header HdrUserAgent userAgent
+    extraHeaders = [Header HdrAccept "text/plain", useragentHeader]
+
+    setupHeaders headers =
+      [ "$wc.Headers.Add(" ++ escape (show name) ++ "," ++ escape value ++ ");"
+      | Header name value <- headers
+      ]
+
+    setupAuth auth =
+      [ "$wc.Credentials = new-object System.Net.NetworkCredential("
+          ++ escape uname ++ "," ++ escape passwd ++ ",\"\");"
+      | (uname,passwd) <- maybeToList auth
+      ]
+
+    uploadFileAction method uri fullPath =
+      [ "$fileBytes = [System.IO.File]::ReadAllBytes(" ++ escape fullPath ++ ");"
+      , "$bodyBytes = $wc.UploadData(" ++ escape (show uri) ++ ","
+        ++ show method ++ ", $fileBytes);"
+      , "Write-Host \"200\";"
+      , "Write-Host (-join [System.Text.Encoding]::UTF8.GetChars($bodyBytes));"
+      ]
+
+    parseUploadResponse uri resp = case lines (trim resp) of
+      (codeStr : message)
+        | Just code <- readMaybe codeStr -> return (code, unlines message)
+      _ -> statusParseFail uri resp
+
+    webclientScript setup action = unlines
+      [ "$wc = new-object system.net.webclient;"
+      , unlines setup
+      , "Try {"
+      , unlines (map ("  " ++) action)
+      , "} Catch [System.Net.WebException] {"
+      , "  $exception = $_.Exception;"
+      , "  If ($exception.Status -eq "
+        ++ "[System.Net.WebExceptionStatus]::ProtocolError) {"
+      , "    $response = $exception.Response -as [System.Net.HttpWebResponse];"
+      , "    $reader = new-object "
+        ++ "System.IO.StreamReader($response.GetResponseStream());"
+      , "    Write-Host ($response.StatusCode -as [int]);"
+      , "    Write-Host $reader.ReadToEnd();"
+      , "  } Else {"
+      , "    Write-Host $exception.Message;"
+      , "  }"
+      , "} Catch {"
+      , "  Write-Host $_.Exception.Message;"
+      , "}"
+      ]
+
+
 ------------------------------------------------------------------------------
 -- The builtin plain HttpTransport
 --
 
 plainHttpTransport :: HttpTransport
 plainHttpTransport =
-    HttpTransport gethttp posthttp posthttpfile False False
+    HttpTransport gethttp posthttp posthttpfile puthttpfile False False
   where
-    gethttp verbosity uri etag destPath = do
+    gethttp verbosity uri etag destPath reqHeaders = do
       let req = Request{
                   rqURI     = uri,
                   rqMethod  = GET,
                   rqHeaders = [ Header HdrIfNoneMatch t
-                              | t <- maybeToList etag ],
+                              | t <- maybeToList etag ]
+                           ++ reqHeaders,
                   rqBody    = BS.empty
                 }
       (_, resp) <- cabalBrowse verbosity Nothing (request req)
@@ -561,6 +652,19 @@
                   rqURI     = uri,
                   rqMethod  = POST,
                   rqHeaders = headers,
+                  rqBody    = body
+                }
+      (_, resp) <- cabalBrowse verbosity auth (request req)
+      return (convertRspCode (rspCode resp), rspErrorString resp)
+
+    puthttpfile verbosity uri path auth headers = do
+      body <- BS.readFile path
+      let req = Request {
+                  rqURI     = uri,
+                  rqMethod  = PUT,
+                  rqHeaders = Header HdrContentLength (show (BS.length body))
+                            : Header HdrAccept "text/plain"
+                            : headers,
                   rqBody    = body
                 }
       (_, resp) <- cabalBrowse verbosity auth (request req)
diff --git a/cabal/cabal-install/Distribution/Client/IndexUtils.hs b/cabal/cabal-install/Distribution/Client/IndexUtils.hs
--- a/cabal/cabal-install/Distribution/Client/IndexUtils.hs
+++ b/cabal/cabal-install/Distribution/Client/IndexUtils.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE GADTs #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.IndexUtils
@@ -14,17 +17,23 @@
 module Distribution.Client.IndexUtils (
   getIndexFileAge,
   getInstalledPackages,
+  Configure.getInstalledPackagesMonitorFiles,
   getSourcePackages,
-  getSourcePackagesStrict,
+  getSourcePackagesMonitorFiles,
 
+  Index(..),
+  PackageEntry(..),
   parsePackageIndex,
-  readRepoIndex,
   updateRepoIndexCache,
   updatePackageIndexCacheFile,
+  readCacheStrict,
 
   BuildTreeRefType(..), refTypeFromTypeCode, typeCodeFromRefType
   ) where
 
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
+import qualified Codec.Archive.Tar.Index as Tar
 import qualified Distribution.Client.Tar as Tar
 import Distribution.Client.Types
 
@@ -45,7 +54,7 @@
 import Distribution.Simple.Program
          ( ProgramConfiguration )
 import qualified Distribution.Simple.Configure as Configure
-         ( getInstalledPackages )
+         ( getInstalledPackages, getInstalledPackagesMonitorFiles )
 import Distribution.ParseUtils
          ( ParseResult(..) )
 import Distribution.Version
@@ -56,9 +65,11 @@
          ( Verbosity, normal, lessVerbose )
 import Distribution.Simple.Utils
          ( die, warn, info, fromUTF8, ignoreBOM )
+import Distribution.Client.Setup
+         ( RepoContext(..) )
 
 import Data.Char   (isAlphaNum)
-import Data.Maybe  (mapMaybe)
+import Data.Maybe  (mapMaybe, catMaybes, maybeToList)
 import Data.List   (isPrefixOf)
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid (Monoid(..))
@@ -75,22 +86,25 @@
                                  , tryFindAddSourcePackageDesc )
 import Distribution.Compat.Exception (catchIO)
 import Distribution.Client.Compat.Time (getFileAge, getModTime)
-import System.Directory (doesFileExist)
-import System.FilePath ((</>), takeExtension, splitDirectories, normalise)
+import System.Directory (doesFileExist, doesDirectoryExist)
+import System.FilePath
+         ( (</>), takeExtension, replaceExtension, splitDirectories, normalise )
 import System.FilePath.Posix as FilePath.Posix
          ( takeFileName )
 import System.IO
 import System.IO.Unsafe (unsafeInterleaveIO)
 import System.IO.Error (isDoesNotExistError)
 
+import qualified Hackage.Security.Client    as Sec
+import qualified Hackage.Security.Util.Some as Sec
 
+-- | Reduced-verbosity version of 'Configure.getInstalledPackages'
 getInstalledPackages :: Verbosity -> Compiler
                      -> PackageDBStack -> ProgramConfiguration
                      -> IO InstalledPackageIndex
 getInstalledPackages verbosity comp packageDbs conf =
     Configure.getInstalledPackages verbosity' comp packageDbs conf
   where
-    --FIXME: make getInstalledPackages use sensible verbosity in the first place
     verbosity'  = lessVerbose verbosity
 
 ------------------------------------------------------------------------
@@ -104,31 +118,17 @@
 -- 'Repo'.
 --
 -- This is a higher level wrapper used internally in cabal-install.
---
-getSourcePackages :: Verbosity -> [Repo] -> IO SourcePackageDb
-getSourcePackages verbosity repos = getSourcePackages' verbosity repos
-                                    ReadPackageIndexLazyIO
-
--- | Like 'getSourcePackages', but reads the package index strictly. Useful if
--- you want to write to the package index after having read it.
-getSourcePackagesStrict :: Verbosity -> [Repo] -> IO SourcePackageDb
-getSourcePackagesStrict verbosity repos = getSourcePackages' verbosity repos
-                                          ReadPackageIndexStrict
-
--- | Common implementation used by getSourcePackages and
--- getSourcePackagesStrict.
-getSourcePackages' :: Verbosity -> [Repo] -> ReadPackageIndexMode
-                      -> IO SourcePackageDb
-getSourcePackages' verbosity [] _mode = do
+getSourcePackages :: Verbosity -> RepoContext -> IO SourcePackageDb
+getSourcePackages verbosity repoCtxt | null (repoContextRepos repoCtxt) = do
   warn verbosity $ "No remote package servers have been specified. Usually "
                 ++ "you would have one specified in the config file."
   return SourcePackageDb {
     packageIndex       = mempty,
     packagePreferences = mempty
   }
-getSourcePackages' verbosity repos mode = do
+getSourcePackages verbosity repoCtxt = do
   info verbosity "Reading available packages..."
-  pkgss <- mapM (\r -> readRepoIndex verbosity r mode) repos
+  pkgss <- mapM (\r -> readRepoIndex verbosity repoCtxt r) (repoContextRepos repoCtxt)
   let (pkgs, prefs) = mconcat pkgss
       prefs' = Map.fromListWith intersectVersionRanges
                  [ (name, range) | Dependency name range <- prefs ]
@@ -139,6 +139,13 @@
     packagePreferences = prefs'
   }
 
+readCacheStrict :: Verbosity -> Index -> (PackageEntry -> pkg) -> IO ([pkg], [Dependency])
+readCacheStrict verbosity index mkPkg = do
+    updateRepoIndexCache verbosity index
+    cache <- liftM readIndexCache $ BSS.readFile (cacheFile index)
+    withFile (indexFile index) ReadMode $ \indexHnd ->
+      packageListFromCache mkPkg indexHnd cache ReadPackageIndexStrict
+
 -- | Read a repository index from disk, from the local file specified by
 -- the 'Repo'.
 --
@@ -146,16 +153,13 @@
 --
 -- This is a higher level wrapper used internally in cabal-install.
 --
-readRepoIndex :: Verbosity -> Repo -> ReadPackageIndexMode
-              -> IO (PackageIndex SourcePackage, [Dependency])
-readRepoIndex verbosity repo mode =
-  let indexFile = repoLocalDir repo </> "00-index.tar"
-      cacheFile = repoLocalDir repo </> "00-index.cache"
-  in handleNotFound $ do
+readRepoIndex :: Verbosity -> RepoContext -> Repo
+              -> IO (PackageIndex UnresolvedSourcePackage, [Dependency])
+readRepoIndex verbosity repoCtxt repo =
+  handleNotFound $ do
     warnIfIndexIsOld =<< getIndexFileAge repo
-    whenCacheOutOfDate indexFile cacheFile $ do
-      updatePackageIndexCacheFile verbosity indexFile cacheFile
-    readPackageIndexCacheFile mkAvailablePackage indexFile cacheFile mode
+    updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)
+    readPackageIndexCacheFile mkAvailablePackage (RepoIndex repoCtxt repo)
 
   where
     mkAvailablePackage pkgEntry =
@@ -174,51 +178,59 @@
 
     handleNotFound action = catchIO action $ \e -> if isDoesNotExistError e
       then do
-        case repoKind repo of
-          Left  remoteRepo -> warn verbosity $
-               "The package list for '" ++ remoteRepoName remoteRepo
-            ++ "' does not exist. Run 'hackport update' to download it."
-          Right _localRepo -> warn verbosity $
-               "The package list for the local repo '" ++ repoLocalDir repo
+        case repo of
+          RepoRemote{..} -> warn verbosity $ errMissingPackageList repoRemote
+          RepoSecure{..} -> warn verbosity $ errMissingPackageList repoRemote
+          RepoLocal{..}  -> warn verbosity $
+               "The package list for the local repo '" ++ repoLocalDir
             ++ "' is missing. The repo is invalid."
         return mempty
       else ioError e
 
     isOldThreshold = 15 --days
     warnIfIndexIsOld dt = do
-      when (dt >= isOldThreshold) $ case repoKind repo of
-        Left  remoteRepo -> warn verbosity $
-             "The package list for '" ++ remoteRepoName remoteRepo
-          ++ "' is " ++ shows (floor dt :: Int) " days old.\nRun "
-          ++ "'hackport update' to get the latest list of available packages."
-        Right _localRepo -> return ()
+      when (dt >= isOldThreshold) $ case repo of
+        RepoRemote{..} -> warn verbosity $ errOutdatedPackageList repoRemote dt
+        RepoSecure{..} -> warn verbosity $ errOutdatedPackageList repoRemote dt
+        RepoLocal{..}  -> return ()
 
+    errMissingPackageList repoRemote =
+         "The package list for '" ++ remoteRepoName repoRemote
+      ++ "' does not exist. Run 'hackport update' to download it."
+    errOutdatedPackageList repoRemote dt =
+         "The package list for '" ++ remoteRepoName repoRemote
+      ++ "' is " ++ shows (floor dt :: Int) " days old.\nRun "
+      ++ "'cabal update' to get the latest list of available packages."
 
 -- | Return the age of the index file in days (as a Double).
 getIndexFileAge :: Repo -> IO Double
 getIndexFileAge repo = getFileAge $ repoLocalDir repo </> "00-index.tar"
 
+-- | A set of files (or directories) that can be monitored to detect when
+-- there might have been a change in the source packages.
+--
+getSourcePackagesMonitorFiles :: [Repo] -> [FilePath]
+getSourcePackagesMonitorFiles repos =
+    [ repoLocalDir repo </> "00-index.cache"
+    | repo <- repos ]
 
 -- | It is not necessary to call this, as the cache will be updated when the
 -- index is read normally. However you can do the work earlier if you like.
 --
-updateRepoIndexCache :: Verbosity -> Repo -> IO ()
-updateRepoIndexCache verbosity repo =
-    whenCacheOutOfDate indexFile cacheFile $ do
-      updatePackageIndexCacheFile verbosity indexFile cacheFile
-  where
-    indexFile = repoLocalDir repo </> "00-index.tar"
-    cacheFile = repoLocalDir repo </> "00-index.cache"
+updateRepoIndexCache :: Verbosity -> Index -> IO ()
+updateRepoIndexCache verbosity index =
+    whenCacheOutOfDate index $ do
+      updatePackageIndexCacheFile verbosity index
 
-whenCacheOutOfDate :: FilePath -> FilePath -> IO () -> IO ()
-whenCacheOutOfDate origFile cacheFile action = do
-  exists <- doesFileExist cacheFile
+whenCacheOutOfDate :: Index -> IO () -> IO ()
+whenCacheOutOfDate index action = do
+  exists <- doesFileExist $ cacheFile index
   if not exists
     then action
     else do
-      origTime  <- getModTime origFile
-      cacheTime <- getModTime cacheFile
-      when (origTime > cacheTime) action
+      indexTime <- getModTime $ indexFile index
+      cacheTime <- getModTime $ cacheFile index
+      when (indexTime > cacheTime) action
 
 ------------------------------------------------------------------------
 -- Reading the index file
@@ -245,8 +257,6 @@
 typeCodeFromRefType LinkRef     = Tar.buildTreeRefTypeCode
 typeCodeFromRefType SnapshotRef = Tar.buildTreeSnapshotTypeCode
 
-type MkPackageEntry = IO PackageEntry
-
 instance Package PackageEntry where
   packageId (NormalPackage  pkgid _ _ _) = pkgid
   packageId (BuildTreeRef _ pkgid _ _ _) = pkgid
@@ -261,38 +271,47 @@
 
 data PackageOrDep = Pkg PackageEntry | Dep Dependency
 
-parsePackageIndex :: ByteString
-                  -> [IO PackageOrDep]
-parsePackageIndex = accum 0 . Tar.read
+-- | Read @00-index.tar.gz@ and extract @.cabal@ and @preferred-versions@ files
+--
+-- We read the index using 'Tar.read', which gives us a lazily constructed
+-- 'TarEntries'. We translate it to a list of entries using  'tarEntriesList',
+-- which preserves the lazy nature of 'TarEntries', and finally 'concatMap' a
+-- function over this to translate it to a list of IO actions returning
+-- 'PackageOrDep's. We can use 'lazySequence' to turn this into a list of
+-- 'PackageOrDep's, still maintaining the lazy nature of the original tar read.
+parsePackageIndex :: ByteString -> [IO (Maybe PackageOrDep)]
+parsePackageIndex = concatMap (uncurry extract) . tarEntriesList . Tar.read
   where
-    accum blockNo es = case es of
-      Tar.Fail err   -> error ("parsePackageIndex: " ++ err)
-      Tar.Done       -> []
-      Tar.Next e es' -> ps ++ accum blockNo' es'
-        where
-          ps       = extract blockNo e
-          blockNo' = blockNo + Tar.entrySizeInBlocks e
-
+    extract :: BlockNo -> Tar.Entry -> [IO (Maybe PackageOrDep)]
     extract blockNo entry = tryExtractPkg ++ tryExtractPrefs
       where
-        maybeToList Nothing  = []
-        maybeToList (Just a) = [a]
-
         tryExtractPkg = do
           mkPkgEntry <- maybeToList $ extractPkg entry blockNo
-          return (fmap Pkg mkPkgEntry)
+          return $ fmap (fmap Pkg) mkPkgEntry
 
         tryExtractPrefs = do
-          (_,prefs') <- maybeToList $ extractPrefs entry
-          map (return . Dep) $ prefs'
+          prefs' <- maybeToList $ extractPrefs entry
+          fmap (return . Just . Dep) prefs'
 
-extractPkg :: Tar.Entry -> BlockNo -> Maybe MkPackageEntry
+-- | Turn the 'Entries' data structure from the @tar@ package into a list,
+-- and pair each entry with its block number.
+--
+-- NOTE: This preserves the lazy nature of 'Entries': the tar file is only read
+-- as far as the list is evaluated.
+tarEntriesList :: Show e => Tar.Entries e -> [(BlockNo, Tar.Entry)]
+tarEntriesList = go 0
+  where
+    go !_ Tar.Done         = []
+    go !_ (Tar.Fail e)     = error ("tarEntriesList: " ++ show e)
+    go !n (Tar.Next e es') = (n, e) : go (Tar.nextEntryOffset e n) es'
+
+extractPkg :: Tar.Entry -> BlockNo -> Maybe (IO (Maybe PackageEntry))
 extractPkg entry blockNo = case Tar.entryContent entry of
   Tar.NormalFile content _
      | takeExtension fileName == ".cabal"
     -> case splitDirectories (normalise fileName) of
         [pkgname,vers,_] -> case simpleParse vers of
-          Just ver -> Just $ return (NormalPackage pkgid descr content blockNo)
+          Just ver -> Just . return $ Just (NormalPackage pkgid descr content blockNo)
             where
               pkgid  = PackageIdentifier (PackageName pkgname) ver
               parsed = parsePackageDescription . ignoreBOM . fromUTF8 . BS.Char8.unpack
@@ -307,19 +326,22 @@
   Tar.OtherEntryType typeCode content _
     | Tar.isBuildTreeRefTypeCode typeCode ->
       Just $ do
-        let path   = byteStringToFilePath content
-            err = "Error reading package index."
-        cabalFile <- tryFindAddSourcePackageDesc path err
-        descr     <- PackageDesc.Parse.readPackageDescription normal cabalFile
-        return $ BuildTreeRef (refTypeFromTypeCode typeCode) (packageId descr)
-                              descr path blockNo
+        let path = byteStringToFilePath content
+        dirExists <- doesDirectoryExist path
+        result <- if not dirExists then return Nothing
+                  else do
+                    cabalFile <- tryFindAddSourcePackageDesc path "Error reading package index."
+                    descr     <- PackageDesc.Parse.readPackageDescription normal cabalFile
+                    return . Just $ BuildTreeRef (refTypeFromTypeCode typeCode) (packageId descr)
+                                                 descr path blockNo
+        return result
 
   _ -> Nothing
 
   where
     fileName = Tar.entryPath entry
 
-extractPrefs :: Tar.Entry -> Maybe (FilePath, [Dependency])
+extractPrefs :: Tar.Entry -> Maybe [Dependency]
 extractPrefs entry = case Tar.entryContent entry of
 {-
  -- get rid of hackage's preferred-versions
@@ -327,40 +349,105 @@
  -- broken packages with improper depends
   Tar.NormalFile content _
      | takeFileName entrypath == "preferred-versions"
-    -> Just (entrypath, prefs)
+    -> Just prefs
     where
       entrypath = Tar.entryPath entry
-      prefs     = parsePreferredVersions (BS.Char8.unpack content)
+      prefs     = parsePreferredVersions content
 -}
   _ -> Nothing
 
-parsePreferredVersions :: String -> [Dependency]
+parsePreferredVersions :: ByteString -> [Dependency]
 parsePreferredVersions = mapMaybe simpleParse
                        . filter (not . isPrefixOf "--")
                        . lines
+                       . BS.Char8.unpack -- TODO: Are we sure no unicode?
 
 ------------------------------------------------------------------------
 -- Reading and updating the index cache
 --
 
+-- | Variation on 'sequence' which evaluates the actions lazily
+--
+-- Pattern matching on the result list will execute just the first action;
+-- more generally pattern matching on the first @n@ '(:)' nodes will execute
+-- the first @n@ actions.
 lazySequence :: [IO a] -> IO [a]
-lazySequence [] = return []
-lazySequence (x:xs) = unsafeInterleaveIO $ do
-    x'  <- unsafeInterleaveIO x
-    xs' <- lazySequence xs
-    return (x':xs')
+lazySequence = unsafeInterleaveIO . go
+  where
+    go []     = return []
+    go (x:xs) = do x'  <- x
+                   xs' <- lazySequence xs
+                   return (x' : xs')
 
-updatePackageIndexCacheFile :: Verbosity -> FilePath -> FilePath -> IO ()
-updatePackageIndexCacheFile verbosity indexFile cacheFile = do
-    info verbosity ("Updating index cache file " ++ cacheFile)
-    pkgsOrPrefs <- return
-                 . parsePackageIndex
-                 . maybeDecompress
-               =<< BS.readFile indexFile
-    entries <- lazySequence pkgsOrPrefs
-    let cache = map toCache entries
-    writeFile cacheFile (showIndexCache cache)
+-- | Which index do we mean?
+data Index =
+    -- | The main index for the specified repository
+    RepoIndex RepoContext Repo
+
+    -- | A sandbox-local repository
+    -- Argument is the location of the index file
+  | SandboxIndex FilePath
+
+indexFile :: Index -> FilePath
+indexFile (RepoIndex _ctxt repo) = repoLocalDir repo </> "00-index.tar"
+indexFile (SandboxIndex index)   = index
+
+cacheFile :: Index -> FilePath
+cacheFile (RepoIndex _ctxt repo) = repoLocalDir repo </> "00-index.cache"
+cacheFile (SandboxIndex index)   = index `replaceExtension` "cache"
+
+updatePackageIndexCacheFile :: Verbosity -> Index -> IO ()
+updatePackageIndexCacheFile verbosity index = do
+    info verbosity ("Updating index cache file " ++ cacheFile index)
+    withIndexEntries index $ \entries -> do
+      let cache = Cache { cacheEntries = entries }
+      writeFile (cacheFile index) (showIndexCache cache)
+
+-- | Read the index (for the purpose of building a cache)
+--
+-- The callback is provided with list of cache entries, which is guaranteed to
+-- be lazily constructed. This list must ONLY be used in the scope of the
+-- callback; when the callback is terminated the file handle to the index will
+-- be closed and further attempts to read from the list will result in (pure)
+-- I/O exceptions.
+--
+-- In the construction of the index for a secure repo we take advantage of the
+-- index built by the @hackage-security@ library to avoid reading the @.tar@
+-- file as much as possible (we need to read it only to extract preferred
+-- versions). This helps performance, but is also required for correctness:
+-- the new @01-index.tar.gz@ may have multiple versions of preferred-versions
+-- files, and 'parsePackageIndex' does not correctly deal with that (see #2956);
+-- by reading the already-built cache from the security library we will be sure
+-- to only read the latest versions of all files.
+--
+-- TODO: It would be nicer if we actually incrementally updated @cabal@'s
+-- cache, rather than reconstruct it from zero on each update. However, this
+-- would require a change in the cache format.
+withIndexEntries :: Index -> ([IndexCacheEntry] -> IO a) -> IO a
+withIndexEntries (RepoIndex repoCtxt repo@RepoSecure{..}) callback =
+    repoContextWithSecureRepo repoCtxt repo $ \repoSecure ->
+      Sec.withIndex repoSecure $ \Sec.IndexCallbacks{..} -> do
+        let mk :: (Sec.DirectoryEntry, fp, Maybe (Sec.Some Sec.IndexFile))
+               -> IO [IndexCacheEntry]
+            mk (_, _fp, Nothing) =
+              return [] -- skip unrecognized file
+            mk (_, _fp, Just (Sec.Some (Sec.IndexPkgMetadata _pkgId))) =
+              return [] -- skip metadata
+            mk (dirEntry, _fp, Just (Sec.Some (Sec.IndexPkgCabal pkgId))) = do
+              let blockNo = fromIntegral (Sec.directoryEntryBlockNo dirEntry)
+              return [CachePackageId pkgId blockNo]
+            mk (dirEntry, _fp, Just (Sec.Some file@(Sec.IndexPkgPrefs _pkgName))) = do
+              content <- Sec.indexEntryContent `fmap` indexLookupFileEntry dirEntry file
+              return $ map CachePreference (parsePreferredVersions content)
+        entriess <- lazySequence $ map mk (Sec.directoryEntries indexDirectory)
+        callback $ concat entriess
+withIndexEntries index callback = do
+    withFile (indexFile index) ReadMode $ \h -> do
+      bs          <- maybeDecompress `fmap` BS.hGetContents h
+      pkgsOrPrefs <- lazySequence $ parsePackageIndex bs
+      callback $ map toCache (catMaybes pkgsOrPrefs)
   where
+    toCache :: PackageOrDep -> IndexCacheEntry
     toCache (Pkg (NormalPackage pkgid _ _ blockNo)) = CachePackageId pkgid blockNo
     toCache (Pkg (BuildTreeRef refType _ _ _ blockNo)) = CacheBuildTreeRef refType blockNo
     toCache (Dep d) = CachePreference d
@@ -370,35 +457,37 @@
 
 readPackageIndexCacheFile :: Package pkg
                           => (PackageEntry -> pkg)
-                          -> FilePath
-                          -> FilePath
-                          -> ReadPackageIndexMode
+                          -> Index
                           -> IO (PackageIndex pkg, [Dependency])
-readPackageIndexCacheFile mkPkg indexFile cacheFile mode = do
-  cache    <- liftM readIndexCache (BSS.readFile cacheFile)
-  myWithFile indexFile ReadMode $ \indexHnd ->
-    packageIndexFromCache mkPkg indexHnd cache mode
-  where
-    myWithFile f m act = case mode of
-      ReadPackageIndexStrict -> withFile f m act
-      ReadPackageIndexLazyIO -> do indexHnd <- openFile f m
-                                   act indexHnd
-
+readPackageIndexCacheFile mkPkg index = do
+  cache    <- liftM readIndexCache $ BSS.readFile (cacheFile index)
+  indexHnd <- openFile (indexFile index) ReadMode
+  packageIndexFromCache mkPkg indexHnd cache ReadPackageIndexLazyIO
 
 packageIndexFromCache :: Package pkg
                       => (PackageEntry -> pkg)
                       -> Handle
-                      -> [IndexCacheEntry]
+                      -> Cache
                       -> ReadPackageIndexMode
                       -> IO (PackageIndex pkg, [Dependency])
-packageIndexFromCache mkPkg hnd entrs mode = accum mempty [] entrs
+packageIndexFromCache mkPkg hnd cache mode = do
+     (pkgs, prefs) <- packageListFromCache mkPkg hnd cache mode
+     pkgIndex <- evaluate $ PackageIndex.fromList pkgs
+     return (pkgIndex, prefs)
+
+-- | Read package list
+--
+-- The result packages (though not the preferences) are guaranteed to be listed
+-- in the same order as they are in the tar file (because later entries in a tar
+-- file mask earlier ones).
+packageListFromCache :: (PackageEntry -> pkg)
+                     -> Handle
+                     -> Cache
+                     -> ReadPackageIndexMode
+                     -> IO ([pkg], [Dependency])
+packageListFromCache mkPkg hnd Cache{..} mode = accum mempty [] cacheEntries
   where
-    accum srcpkgs prefs [] = do
-      -- Have to reverse entries, since in a tar file, later entries mask
-      -- earlier ones, and PackageIndex.fromList does the same, but we
-      -- accumulate the list of entries in reverse order, so need to reverse.
-      pkgIndex <- evaluate $ PackageIndex.fromList (reverse srcpkgs)
-      return (pkgIndex, prefs)
+    accum srcpkgs prefs [] = return (reverse srcpkgs, prefs)
 
     accum srcpkgs prefs (CachePackageId pkgid blockno : entries) = do
       -- Given the cache entry, make a package index entry.
@@ -433,22 +522,13 @@
 
     getEntryContent :: BlockNo -> IO ByteString
     getEntryContent blockno = do
-      hSeek hnd AbsoluteSeek (fromIntegral (blockno * 512))
-      header  <- BS.hGet hnd 512
-      size    <- getEntrySize header
-      BS.hGet hnd (fromIntegral size)
-
-    getEntrySize :: ByteString -> IO Tar.FileSize
-    getEntrySize header =
-      case Tar.read header of
-        Tar.Next e _ ->
-          case Tar.entryContent e of
-            Tar.NormalFile _ size -> return size
-            Tar.OtherEntryType typecode _ size
-              | Tar.isBuildTreeRefTypeCode typecode
-                                  -> return size
-            _                     -> interror "unexpected tar entry type"
-        _ -> interror "could not read tar file entry"
+      entry <- Tar.hReadEntry hnd blockno
+      case Tar.entryContent entry of
+        Tar.NormalFile content _size -> return content
+        Tar.OtherEntryType typecode content _size
+          | Tar.isBuildTreeRefTypeCode typecode
+          -> return content
+        _ -> interror "unexpected tar entry type"
 
     readPackageDescription :: ByteString -> IO GenericPackageDescription
     readPackageDescription content =
@@ -467,15 +547,15 @@
 -- | Tar files are block structured with 512 byte blocks. Every header and file
 -- content starts on a block boundary.
 --
-type BlockNo = Int
+type BlockNo = Tar.TarEntryOffset
 
 data IndexCacheEntry = CachePackageId PackageId BlockNo
                      | CacheBuildTreeRef BuildTreeRefType BlockNo
                      | CachePreference Dependency
   deriving (Eq)
 
-packageKey, blocknoKey, buildTreeRefKey, preferredVersionKey :: String
-packageKey = "pkg:"
+installedUnitId, blocknoKey, buildTreeRefKey, preferredVersionKey :: String
+installedUnitId = "pkg:"
 blocknoKey = "b#"
 buildTreeRefKey     = "build-tree-ref:"
 preferredVersionKey = "pref-ver:"
@@ -484,7 +564,7 @@
 readIndexCacheEntry = \line ->
   case BSS.words line of
     [key, pkgnamestr, pkgverstr, sep, blocknostr]
-      | key == BSS.pack packageKey && sep == BSS.pack blocknoKey ->
+      | key == BSS.pack installedUnitId && sep == BSS.pack blocknoKey ->
       case (parseName pkgnamestr, parseVer pkgverstr [],
             parseBlockNo blocknostr) of
         (Just pkgname, Just pkgver, Just blockno)
@@ -515,8 +595,9 @@
 
     parseBlockNo str =
       case BSS.readInt str of
-        Just (blockno, remainder) | BSS.null remainder -> Just blockno
-        _                                              -> Nothing
+        Just (blockno, remainder)
+          | BSS.null remainder -> Just (fromIntegral blockno)
+        _                      -> Nothing
 
     parseRefType str =
       case BSS.uncons str of
@@ -527,7 +608,7 @@
 
 showIndexCacheEntry :: IndexCacheEntry -> String
 showIndexCacheEntry entry = unwords $ case entry of
-   CachePackageId pkgid b -> [ packageKey
+   CachePackageId pkgid b -> [ installedUnitId
                              , display (packageName pkgid)
                              , display (packageVersion pkgid)
                              , blocknoKey
@@ -541,8 +622,15 @@
                              , display dep
                              ]
 
-readIndexCache :: BSS.ByteString -> [IndexCacheEntry]
-readIndexCache = mapMaybe readIndexCacheEntry . BSS.lines
+-- | Cabal caches various information about the Hackage index
+data Cache = Cache {
+    cacheEntries :: [IndexCacheEntry]
+  }
 
-showIndexCache :: [IndexCacheEntry] -> String
-showIndexCache = unlines . map showIndexCacheEntry
+readIndexCache :: BSS.ByteString -> Cache
+readIndexCache bs = Cache {
+    cacheEntries = mapMaybe readIndexCacheEntry $ BSS.lines bs
+  }
+
+showIndexCache :: Cache -> String
+showIndexCache Cache{..} = unlines $ map showIndexCacheEntry cacheEntries
diff --git a/cabal/cabal-install/Distribution/Client/Init.hs b/cabal/cabal-install/Distribution/Client/Init.hs
--- a/cabal/cabal-install/Distribution/Client/Init.hs
+++ b/cabal/cabal-install/Distribution/Client/Init.hs
@@ -18,6 +18,8 @@
 
     -- * Commands
     initCabal
+  , pvpize
+  , incVersion
 
   ) where
 
@@ -98,19 +100,21 @@
 import Distribution.Client.IndexUtils
   ( getSourcePackages )
 import Distribution.Client.Types
-  ( SourcePackageDb(..), Repo )
+  ( SourcePackageDb(..) )
+import Distribution.Client.Setup
+  ( RepoContext(..) )
 
 initCabal :: Verbosity
           -> PackageDBStack
-          -> [Repo]
+          -> RepoContext
           -> Compiler
           -> ProgramConfiguration
           -> InitFlags
           -> IO ()
-initCabal verbosity packageDBs repos comp conf initFlags = do
+initCabal verbosity packageDBs repoCtxt comp conf initFlags = do
 
   installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-  sourcePkgDb <- getSourcePackages verbosity repos
+  sourcePkgDb <- getSourcePackages verbosity repoCtxt
 
   hSetBuffering stdout NoBuffering
 
@@ -359,7 +363,7 @@
 getSrcDir flags = do
   srcDirs <- return (sourceDirs flags)
              ?>> fmap (:[]) `fmap` guessSourceDir flags
-             ?>> fmap (fmap ((:[]) . either id id) . join) (maybePrompt
+             ?>> fmap (>>= fmap ((:[]) . either id id)) (maybePrompt
                       flags
                       (promptListOptional' "Source directory" ["src"] id))
 
@@ -465,12 +469,17 @@
       return $ P.Dependency (P.pkgName . head $ pids)
                             (pvpize . maximum . map P.pkgVersion $ pids)
 
-    pvpize :: Version -> VersionRange
-    pvpize v = orLaterVersion v'
-               `intersectVersionRanges`
-               earlierVersion (incVersion 1 v')
-      where v' = (v { versionBranch = take 2 (versionBranch v) })
+-- | Given a version, return an API-compatible (according to PVP) version range.
+--
+-- Example: @0.4.1@ produces the version range @>= 0.4 && < 0.5@ (which is the
+-- same as @0.4.*@).
+pvpize :: Version -> VersionRange
+pvpize v = orLaterVersion v'
+           `intersectVersionRanges`
+           earlierVersion (incVersion 1 v')
+  where v' = (v { versionBranch = take 2 (versionBranch v) })
 
+-- | Increment the nth version component (counting from 0).
 incVersion :: Int -> Version -> Version
 incVersion n (Version vlist tags) = Version (incVersion' n vlist) tags
   where
diff --git a/cabal/cabal-install/Distribution/Client/Init/Types.hs b/cabal/cabal-install/Distribution/Client/Init/Types.hs
--- a/cabal/cabal-install/Distribution/Client/Init/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/Init/Types.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
+
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Init.Types
@@ -17,6 +18,7 @@
 import Distribution.Simple.Setup
   ( Flag(..) )
 
+import Distribution.Compat.Semigroup
 import Distribution.Version
 import Distribution.Verbosity
 import qualified Distribution.Package as P
@@ -28,9 +30,7 @@
 import qualified Distribution.Compat.ReadP as Parse
 import Distribution.Text
 
-#if !MIN_VERSION_base(4,8,0)
-import Data.Monoid
-#endif
+import GHC.Generics ( Generic )
 
 -- | InitFlags is really just a simple type to represent certain
 --   portions of a .cabal file.  Rather than have a flag for EVERY
@@ -71,7 +71,7 @@
               , initVerbosity :: Flag Verbosity
               , overwrite     :: Flag Bool
               }
-  deriving (Show)
+  deriving (Show, Generic)
 
   -- the Monoid instance for Flag has later values override earlier
   -- ones, which is why we want Maybe [foo] for collecting foo values,
@@ -85,63 +85,11 @@
   parse = Parse.choice $ map (fmap read . Parse.string . show) [Library, Executable]
 
 instance Monoid InitFlags where
-  mempty = InitFlags
-    { nonInteractive = mempty
-    , quiet          = mempty
-    , packageDir     = mempty
-    , noComments     = mempty
-    , minimal        = mempty
-    , packageName    = mempty
-    , version        = mempty
-    , cabalVersion   = mempty
-    , license        = mempty
-    , author         = mempty
-    , email          = mempty
-    , homepage       = mempty
-    , synopsis       = mempty
-    , category       = mempty
-    , extraSrc       = mempty
-    , packageType    = mempty
-    , mainIs         = mempty
-    , language       = mempty
-    , exposedModules = mempty
-    , otherModules   = mempty
-    , otherExts      = mempty
-    , dependencies   = mempty
-    , sourceDirs     = mempty
-    , buildTools     = mempty
-    , initVerbosity  = mempty
-    , overwrite      = mempty
-    }
-  mappend  a b = InitFlags
-    { nonInteractive = combine nonInteractive
-    , quiet          = combine quiet
-    , packageDir     = combine packageDir
-    , noComments     = combine noComments
-    , minimal        = combine minimal
-    , packageName    = combine packageName
-    , version        = combine version
-    , cabalVersion   = combine cabalVersion
-    , license        = combine license
-    , author         = combine author
-    , email          = combine email
-    , homepage       = combine homepage
-    , synopsis       = combine synopsis
-    , category       = combine category
-    , extraSrc       = combine extraSrc
-    , packageType    = combine packageType
-    , mainIs         = combine mainIs
-    , language       = combine language
-    , exposedModules = combine exposedModules
-    , otherModules   = combine otherModules
-    , otherExts      = combine otherExts
-    , dependencies   = combine dependencies
-    , sourceDirs     = combine sourceDirs
-    , buildTools     = combine buildTools
-    , initVerbosity  = combine initVerbosity
-    , overwrite      = combine overwrite
-    }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup InitFlags where
+  (<>) = gmappend
 
 -- | Some common package categories.
 data Category
diff --git a/cabal/cabal-install/Distribution/Client/Install.hs b/cabal/cabal-install/Distribution/Client/Install.hs
--- a/cabal/cabal-install/Distribution/Client/Install.hs
+++ b/cabal/cabal-install/Distribution/Client/Install.hs
@@ -33,6 +33,7 @@
          ( traverse_ )
 import Data.List
          ( isPrefixOf, unfoldr, nub, sort, (\\) )
+import qualified Data.Map as Map
 import qualified Data.Set as S
 import Data.Maybe
          ( catMaybes, isJust, isNothing, fromMaybe, mapMaybe )
@@ -73,14 +74,14 @@
          ( Solver(..), ConstraintSource(..), LabeledPackageConstraint(..) )
 import Distribution.Client.FetchUtils
 import Distribution.Client.HttpUtils
-         ( configureTransport, HttpTransport (..) )
+         ( HttpTransport (..) )
 import qualified Distribution.Client.Haddock as Haddock (regenerateHaddockIndex)
 import Distribution.Client.IndexUtils as IndexUtils
          ( getSourcePackages, getInstalledPackages )
 import qualified Distribution.Client.InstallPlan as InstallPlan
 import Distribution.Client.InstallPlan (InstallPlan)
 import Distribution.Client.Setup
-         ( GlobalFlags(..)
+         ( GlobalFlags(..), RepoContext(..)
          , ConfigFlags(..), configureCommand, filterConfigureFlags
          , ConfigExFlags(..), InstallFlags(..) )
 import Distribution.Client.Config
@@ -118,9 +119,12 @@
 import qualified Distribution.Simple.InstallDirs as InstallDirs
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import Distribution.Simple.PackageIndex (InstalledPackageIndex)
+import Distribution.Simple.LocalBuildInfo (ComponentName(CLibName))
+import qualified Distribution.Simple.Configure as Configure
 import Distribution.Simple.Setup
          ( haddockCommand, HaddockFlags(..)
          , buildCommand, BuildFlags(..), emptyBuildFlags
+         , AllowNewer(..)
          , toFlag, fromFlag, fromFlagOrDefault, flagToMaybe, defaultDistPref )
 import qualified Distribution.Simple.Setup as Cabal
          ( Flag(..)
@@ -135,16 +139,18 @@
          , initialPathTemplateEnv, installDirsTemplateEnv )
 import Distribution.Package
          ( PackageIdentifier(..), PackageId, packageName, packageVersion
-         , Package(..), LibraryName
+         , Package(..)
          , Dependency(..), thisPackageVersion
-         , InstalledPackageId, installedPackageId
-         , HasInstalledPackageId(..) )
+         , UnitId(..), mkUnitId
+         , HasUnitId(..) )
 import qualified Distribution.PackageDescription as PackageDescription
 import Distribution.PackageDescription
          ( PackageDescription, GenericPackageDescription(..), Flag(..)
          , FlagName(..), FlagAssignment )
 import Distribution.PackageDescription.Configuration
          ( finalizePackageDescription )
+import Distribution.Client.PkgConfigDb
+         ( PkgConfigDb, readPkgConfigDb )
 import Distribution.ParseUtils
          ( showPWarning )
 import Distribution.Version
@@ -153,7 +159,7 @@
          ( notice, info, warn, debug, debugNoWrap, die
          , intercalate, withTempDirectory )
 import Distribution.Client.Utils
-         ( determineNumJobs, inDir, mergeBy, MergeResult(..)
+         ( determineNumJobs, inDir, logDirChange, mergeBy, MergeResult(..)
          , tryCanonicalizePath )
 import Distribution.System
          ( Platform, OS(Windows), buildOS )
@@ -184,7 +190,7 @@
 install
   :: Verbosity
   -> PackageDBStack
-  -> [Repo]
+  -> RepoContext
   -> Compiler
   -> Platform
   -> ProgramConfiguration
@@ -230,14 +236,15 @@
 -- TODO: Make InstallContext a proper data type with documented fields.
 -- | Common context for makeInstallPlan and processInstallPlan.
 type InstallContext = ( InstalledPackageIndex, SourcePackageDb
-                      , [UserTarget], [PackageSpecifier SourcePackage]
+                      , PkgConfigDb
+                      , [UserTarget], [PackageSpecifier UnresolvedSourcePackage]
                       , HttpTransport )
 
 -- TODO: Make InstallArgs a proper data type with documented fields or just get
 -- rid of it completely.
 -- | Initial arguments given to 'install' or 'makeInstallContext'.
 type InstallArgs = ( PackageDBStack
-                   , [Repo]
+                   , RepoContext
                    , Compiler
                    , Platform
                    , ProgramConfiguration
@@ -253,15 +260,16 @@
 makeInstallContext :: Verbosity -> InstallArgs -> Maybe [UserTarget]
                       -> IO InstallContext
 makeInstallContext verbosity
-  (packageDBs, repos, comp, _, conf,_,_,
+  (packageDBs, repoCtxt, comp, _, conf,_,_,
    globalFlags, _, configExFlags, _, _) mUserTargets = do
 
     installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-    sourcePkgDb       <- getSourcePackages    verbosity repos
+    sourcePkgDb       <- getSourcePackages    verbosity repoCtxt
+    pkgConfigDb       <- readPkgConfigDb      verbosity conf
+
     checkConfigExFlags verbosity installedPkgIndex
                        (packageIndex sourcePkgDb) configExFlags
-    transport <- configureTransport verbosity
-                 (flagToMaybe (globalHttpTransport globalFlags))
+    transport <- repoContextGetTransport repoCtxt
 
     (userTargets, pkgSpecifiers) <- case mUserTargets of
       Nothing           ->
@@ -275,13 +283,13 @@
         let userTargets | null userTargets0 = [UserTargetLocalDir "."]
                         | otherwise         = userTargets0
 
-        pkgSpecifiers <- resolveUserTargets verbosity transport
+        pkgSpecifiers <- resolveUserTargets verbosity repoCtxt
                          (fromFlag $ globalWorldFile globalFlags)
                          (packageIndex sourcePkgDb)
                          userTargets
         return (userTargets, pkgSpecifiers)
 
-    return (installedPkgIndex, sourcePkgDb, userTargets
+    return (installedPkgIndex, sourcePkgDb, pkgConfigDb, userTargets
            ,pkgSpecifiers, transport)
 
 -- | Make an install plan given install context and install arguments.
@@ -291,7 +299,7 @@
   (_, _, comp, platform, _, _, mSandboxPkgInfo,
    _, configFlags, configExFlags, installFlags,
    _)
-  (installedPkgIndex, sourcePkgDb,
+  (installedPkgIndex, sourcePkgDb, pkgConfigDb,
    _, pkgSpecifiers, _) = do
 
     solver <- chooseSolver verbosity (fromFlag (configSolver configExFlags))
@@ -299,7 +307,7 @@
     notice verbosity "Resolving dependencies..."
     return $ planPackages comp platform mSandboxPkgInfo solver
       configFlags configExFlags installFlags
-      installedPkgIndex sourcePkgDb pkgSpecifiers
+      installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers
 
 -- | Given an install plan, perform the actual installations.
 processInstallPlan :: Verbosity -> InstallArgs -> InstallContext
@@ -307,7 +315,7 @@
                    -> IO ()
 processInstallPlan verbosity
   args@(_,_, _, _, _, _, _, _, _, _, installFlags, _)
-  (installedPkgIndex, sourcePkgDb,
+  (installedPkgIndex, sourcePkgDb, _,
    userTargets, pkgSpecifiers, _) installPlan = do
     checkPrintPlan verbosity installedPkgIndex installPlan sourcePkgDb
       installFlags pkgSpecifiers
@@ -333,14 +341,15 @@
              -> InstallFlags
              -> InstalledPackageIndex
              -> SourcePackageDb
-             -> [PackageSpecifier SourcePackage]
+             -> PkgConfigDb
+             -> [PackageSpecifier UnresolvedSourcePackage]
              -> Progress String String InstallPlan
 planPackages comp platform mSandboxPkgInfo solver
              configFlags configExFlags installFlags
-             installedPkgIndex sourcePkgDb pkgSpecifiers =
+             installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers =
 
         resolveDependencies
-          platform (compilerInfo comp)
+          platform (compilerInfo comp) pkgConfigDb
           solver
           resolverParams
 
@@ -400,23 +409,22 @@
       $ standardInstallPolicy
         installedPkgIndex sourcePkgDb pkgSpecifiers
 
-    stanzas = concat
-        [ if testsEnabled then [TestStanzas] else []
-        , if benchmarksEnabled then [BenchStanzas] else []
-        ]
-    testsEnabled = fromFlagOrDefault False $ configTests configFlags
+    stanzas           = [ TestStanzas | testsEnabled ]
+                     ++ [ BenchStanzas | benchmarksEnabled ]
+    testsEnabled      = fromFlagOrDefault False $ configTests configFlags
     benchmarksEnabled = fromFlagOrDefault False $ configBenchmarks configFlags
 
-    reinstall        = fromFlag (installReinstall        installFlags)
-    reorderGoals     = fromFlag (installReorderGoals     installFlags)
-    independentGoals = fromFlag (installIndependentGoals installFlags)
-    avoidReinstalls  = fromFlag (installAvoidReinstalls  installFlags)
-    shadowPkgs       = fromFlag (installShadowPkgs       installFlags)
-    strongFlags      = fromFlag (installStrongFlags      installFlags)
-    maxBackjumps     = fromFlag (installMaxBackjumps     installFlags)
-    upgradeDeps      = fromFlag (installUpgradeDeps      installFlags)
-    onlyDeps         = fromFlag (installOnlyDeps         installFlags)
-    allowNewer       = fromFlag (configAllowNewer        configExFlags)
+    reinstall        = fromFlag (installOverrideReinstall installFlags) ||
+                       fromFlag (installReinstall         installFlags)
+    reorderGoals     = fromFlag (installReorderGoals      installFlags)
+    independentGoals = fromFlag (installIndependentGoals  installFlags)
+    avoidReinstalls  = fromFlag (installAvoidReinstalls   installFlags)
+    shadowPkgs       = fromFlag (installShadowPkgs        installFlags)
+    strongFlags      = fromFlag (installStrongFlags       installFlags)
+    maxBackjumps     = fromFlag (installMaxBackjumps      installFlags)
+    upgradeDeps      = fromFlag (installUpgradeDeps       installFlags)
+    onlyDeps         = fromFlag (installOnlyDeps          installFlags)
+    allowNewer       = fromMaybe AllowNewerNone (configAllowNewer configFlags)
 
 -- | Remove the provided targets from the install plan.
 pruneInstallPlan :: Package targetpkg
@@ -459,7 +467,7 @@
                -> InstallPlan
                -> SourcePackageDb
                -> InstallFlags
-               -> [PackageSpecifier SourcePackage]
+               -> [PackageSpecifier UnresolvedSourcePackage]
                -> IO ()
 checkPrintPlan verbosity installed installPlan sourcePkgDb
   installFlags pkgSpecifiers = do
@@ -483,16 +491,16 @@
   let reinstalledPkgs = concatMap (extractReinstalls . snd) lPlan
   -- Packages that are already broken.
   let oldBrokenPkgs =
-          map Installed.installedPackageId
+          map Installed.installedUnitId
         . PackageIndex.reverseDependencyClosure installed
-        . map (Installed.installedPackageId . fst)
+        . map (Installed.installedUnitId . fst)
         . PackageIndex.brokenPackages
         $ installed
   let excluded = reinstalledPkgs ++ oldBrokenPkgs
   -- Packages that are reverse dependencies of replaced packages are very
   -- likely to be broken. We exclude packages that are already broken.
   let newBrokenPkgs =
-        filter (\ p -> not (Installed.installedPackageId p `elem` excluded))
+        filter (\ p -> not (Installed.installedUnitId p `elem` excluded))
                (PackageIndex.reverseDependencyClosure installed reinstalledPkgs)
   let containsReinstalls = not (null reinstalledPkgs)
   let breaksPkgs         = not (null newBrokenPkgs)
@@ -529,9 +537,8 @@
   -- are already fetched.
   let offline = fromFlagOrDefault False (installOfflineMode installFlags)
   when offline $ do
-    let pkgs = [ sourcePkg
-               | InstallPlan.Configured (ConfiguredPackage sourcePkg _ _ _)
-                 <- InstallPlan.toList installPlan ]
+    let pkgs = [ confPkgSource cpkg
+               | InstallPlan.Configured cpkg <- InstallPlan.toList installPlan ]
     notFetched <- fmap (map packageInfoId)
                   . filterM (fmap isNothing . checkFetched . packageSource)
                   $ pkgs
@@ -547,7 +554,14 @@
     dryRun            = fromFlag (installDryRun            installFlags)
     overrideReinstall = fromFlag (installOverrideReinstall installFlags)
 
---TODO: this type is too specific
+-- | Given an 'InstallPlan', perform a dry run, producing the sequence
+-- of 'ReadyPackage's which would be compiled in order to carry
+-- out this plan.  This function is not actually used to execute a plan;
+-- presently, it is used only to (1) determine if the installation
+-- plan would cause reinstalls and (2) to print out what would be
+-- installed.
+--
+-- TODO: this type is too specific
 linearizeInstallPlan :: InstalledPackageIndex
                      -> InstallPlan
                      -> [(ReadyPackage, PackageStatus)]
@@ -558,11 +572,11 @@
       []      -> Nothing
       (pkg:_) -> Just ((pkg, status), plan'')
         where
-          pkgid  = installedPackageId pkg
+          pkgid  = installedUnitId pkg
           status = packageStatus installedPkgIndex pkg
           ipkg   = Installed.emptyInstalledPackageInfo {
-                     Installed.sourcePackageId    = packageId pkg,
-                     Installed.installedPackageId = pkgid
+                     Installed.sourcePackageId = packageId pkg,
+                     Installed.installedUnitId = pkgid
                    }
           plan'' = InstallPlan.completed pkgid (Just ipkg)
                      (BuildOk DocsNotTried TestsNotTried (Just ipkg))
@@ -570,15 +584,16 @@
           --FIXME: This is a bit of a hack,
           -- pretending that each package is installed
           -- It's doubly a hack because the installed package ID
-          -- didn't get updated...
+          -- didn't get updated.  But it doesn't really matter
+          -- because we're not going to use this for anything real.
 
 data PackageStatus = NewPackage
                    | NewVersion [Version]
-                   | Reinstall  [InstalledPackageId] [PackageChange]
+                   | Reinstall  [UnitId] [PackageChange]
 
 type PackageChange = MergeResult PackageIdentifier PackageIdentifier
 
-extractReinstalls :: PackageStatus -> [InstalledPackageId]
+extractReinstalls :: PackageStatus -> [UnitId]
 extractReinstalls (Reinstall ipids _) = ipids
 extractReinstalls _                   = []
 
@@ -592,7 +607,7 @@
     ps ->  case filter ((== packageId cpkg)
                         . Installed.sourcePackageId) (concatMap snd ps) of
       []           -> NewVersion (map fst ps)
-      pkgs@(pkg:_) -> Reinstall (map Installed.installedPackageId pkgs)
+      pkgs@(pkg:_) -> Reinstall (map Installed.installedUnitId pkgs)
                                 (changes pkg cpkg)
 
   where
@@ -608,13 +623,13 @@
         (resolveInstalledIds $ CD.nonSetupDeps (depends pkg'))
 
     -- convert to source pkg ids via index
-    resolveInstalledIds :: [InstalledPackageId] -> [PackageIdentifier]
+    resolveInstalledIds :: [UnitId] -> [PackageIdentifier]
     resolveInstalledIds =
         nub
       . sort
       . map Installed.sourcePackageId
       . catMaybes
-      . map (PackageIndex.lookupInstalledPackageId installedPkgIndex)
+      . map (PackageIndex.lookupUnitId installedPkgIndex)
 
     changed (InBoth    pkgid pkgid') = pkgid /= pkgid'
     changed _                        = True
@@ -627,7 +642,7 @@
 printPlan dryRun verbosity plan sourcePkgDb = case plan of
   []   -> return ()
   pkgs
-    | verbosity >= Verbosity.verbose -> notice verbosity $ unlines $
+    | verbosity >= Verbosity.verbose -> putStr $ unlines $
         ("In order, the following " ++ wouldWill ++ " be installed:")
       : map showPkgAndReason pkgs
     | otherwise -> notice verbosity $ unlines $
@@ -641,16 +656,18 @@
     showPkg (pkg, _) = display (packageId pkg) ++
                        showLatest (pkg)
 
-    showPkgAndReason (ReadyPackage pkg' _, pr) = display (packageId pkg') ++
+    showPkgAndReason (ReadyPackage pkg', pr) = display (packageId pkg') ++
           showLatest pkg' ++
           showFlagAssignment (nonDefaultFlags pkg') ++
-          showStanzas (stanzas pkg') ++ " " ++
+          showStanzas (confPkgStanzas pkg') ++
+          showDep pkg' ++
           case pr of
-            NewPackage     -> "(new package)"
-            NewVersion _   -> "(new version)"
-            Reinstall _ cs -> "(reinstall)" ++ case cs of
+            NewPackage     -> " (new package)"
+            NewVersion _   -> " (new version)"
+            Reinstall _ cs -> " (reinstall)" ++ case cs of
                 []   -> ""
-                diff -> " changes: "  ++ intercalate ", " (map change diff)
+                diff -> " (changes: "  ++ intercalate ", " (map change diff)
+                        ++ ")"
 
     showLatest :: Package srcpkg => srcpkg -> String
     showLatest pkg = case mLatestVersion of
@@ -670,22 +687,18 @@
     toFlagAssignment :: [Flag] -> FlagAssignment
     toFlagAssignment = map (\ f -> (flagName f, flagDefault f))
 
-    nonDefaultFlags :: ConfiguredPackage -> FlagAssignment
-    nonDefaultFlags (ConfiguredPackage spkg fa _ _) =
+    nonDefaultFlags :: ConfiguredPackage loc -> FlagAssignment
+    nonDefaultFlags cpkg =
       let defaultAssignment =
             toFlagAssignment
-             (genPackageFlags (Source.packageDescription spkg))
-      in  fa \\ defaultAssignment
-
-    stanzas :: ConfiguredPackage -> [OptionalStanza]
-    stanzas (ConfiguredPackage _ _ sts _) = sts
+             (genPackageFlags (Source.packageDescription (confPkgSource cpkg)))
+      in  confPkgFlags cpkg \\ defaultAssignment
 
     showStanzas :: [OptionalStanza] -> String
     showStanzas = concatMap ((' ' :) . showStanza)
     showStanza TestStanzas  = "*test"
     showStanza BenchStanzas = "*bench"
 
-    -- FIXME: this should be a proper function in a proper place
     showFlagAssignment :: FlagAssignment -> String
     showFlagAssignment = concatMap ((' ' :) . showFlagValue)
     showFlagValue (f, True)   = '+' : showFlagName f
@@ -697,6 +710,18 @@
                                     ++ display (packageVersion pkgid')
     change (OnlyInRight      pkgid') = display pkgid' ++ " added"
 
+    showDep pkg | Just rdeps <- Map.lookup (packageId pkg) revDeps
+                  = " (via: " ++ unwords (map display rdeps) ++  ")"
+                | otherwise = ""
+
+    revDepGraphEdges :: [(PackageId, PackageId)]
+    revDepGraphEdges = [ (rpid, packageId cpkg)
+                       | (ReadyPackage cpkg, _) <- plan
+                       , ConfiguredId rpid _ <- CD.flatDeps (confPkgDeps cpkg) ]
+
+    revDeps :: Map.Map PackageId [PackageId]
+    revDeps = Map.fromListWith (++) (map (fmap (:[])) revDepGraphEdges)
+
 -- ------------------------------------------------------------
 -- * Post installation stuff
 -- ------------------------------------------------------------
@@ -708,7 +733,7 @@
 reportPlanningFailure verbosity
   (_, _, comp, platform, _, _, _
   ,_, configFlags, _, installFlags, _)
-  (_, sourcePkgDb, _, pkgSpecifiers, _)
+  (_, sourcePkgDb, _, _, pkgSpecifiers, _)
   message = do
 
   when reportFailure $ do
@@ -736,7 +761,7 @@
     case logFile of
       Nothing -> return ()
       Just template -> forM_ pkgids $ \pkgid ->
-        let env = initialPathTemplateEnv pkgid dummyLibraryName
+        let env = initialPathTemplateEnv pkgid dummyIpid
                     (compilerInfo comp) platform
             path = fromPathTemplate $ substPathTemplate env template
         in  writeFile path message
@@ -745,10 +770,10 @@
     reportFailure = fromFlag (installReportPlanningFailure installFlags)
     logFile = flagToMaybe (installLogFile installFlags)
 
-    -- A LibraryName is calculated from the transitive closure of
+    -- A IPID is calculated from the transitive closure of
     -- dependencies, but when the solver fails we don't have that.
     -- So we fail.
-    dummyLibraryName = error "reportPlanningFailure: library name not available"
+    dummyIpid = error "reportPlanningFailure: installed package ID not available"
 
 -- | If a 'PackageSpecifier' refers to a single package, return Just that
 -- package.
@@ -836,7 +861,8 @@
          createDirectoryIfMissing True reportsDir -- FIXME
          writeFile reportFile (show (BuildReports.show report, buildLog))
 
-  | (report, Just Repo { repoKind = Left remoteRepo }) <- reports
+  | (report, Just repo) <- reports
+  , Just remoteRepo <- [maybeRepoRemote repo]
   , isLikelyToHaveLogFile (BuildReports.installOutcome report) ]
 
   where
@@ -1003,7 +1029,7 @@
   withUpdateTimestamps sandboxDir (compilerId comp) platform $ \_ -> do
     let allInstalled = [ pkg | InstallPlan.Installed pkg _ _
                             <- InstallPlan.toList installPlan ]
-        allSrcPkgs   = [ pkg | ReadyPackage (ConfiguredPackage pkg _ _ _) _
+        allSrcPkgs   = [ confPkgSource cpkg | ReadyPackage cpkg
                             <- allInstalled ]
         allPaths     = [ pth | LocalUnpackedPackage pth
                             <- map packageSource allSrcPkgs]
@@ -1023,7 +1049,7 @@
 
 -- | If logging is enabled, contains location of the log file and the verbosity
 -- level for logging.
-type UseLogFile = Maybe (PackageIdentifier -> LibraryName -> FilePath, Verbosity)
+type UseLogFile = Maybe (PackageIdentifier -> UnitId -> FilePath, Verbosity)
 
 performInstallations :: Verbosity
                      -> InstallArgs
@@ -1031,7 +1057,7 @@
                      -> InstallPlan
                      -> IO InstallPlan
 performInstallations verbosity
-  (packageDBs, _, comp, platform, conf, useSandbox, _,
+  (packageDBs, repoCtxt, comp, platform, conf, useSandbox, _,
    globalFlags, configFlags, configExFlags, installFlags, haddockFlags)
   installedPkgIndex installPlan = do
 
@@ -1048,23 +1074,18 @@
   installLock  <- newLock -- serialise installation
   cacheLock    <- newLock -- serialise access to setup exe cache
 
-  transport <- configureTransport verbosity
-               (flagToMaybe (globalHttpTransport globalFlags))
-
   executeInstallPlan verbosity comp jobControl useLogFile installPlan $ \rpkg ->
-    -- Calculate the package key (ToDo: Is this right for source install)
-    let libname = readyLibraryName comp rpkg in
     installReadyPackage platform cinfo configFlags
                         rpkg $ \configFlags' src pkg pkgoverride ->
-      fetchSourcePackage transport verbosity fetchLimit src $ \src' ->
+      fetchSourcePackage verbosity repoCtxt fetchLimit src $ \src' ->
         installLocalPackage verbosity buildLimit
                             (packageId pkg) src' distPref $ \mpath ->
-          installUnpackedPackage verbosity buildLimit installLock numJobs libname
+          installUnpackedPackage verbosity buildLimit installLock numJobs
                                  (setupScriptOptions installedPkgIndex
                                   cacheLock rpkg)
                                  miscOptions configFlags'
                                  installFlags haddockFlags
-                                 cinfo platform pkg pkgoverride mpath useLogFile
+                                 cinfo platform pkg rpkg pkgoverride mpath useLogFile
 
   where
     cinfo = compilerInfo comp
@@ -1082,7 +1103,7 @@
         platform
         conf
         distPref
-        (chooseCabalVersion configExFlags (libVersion miscOptions))
+        (chooseCabalVersion configFlags (libVersion miscOptions))
         (Just lock)
         parallelInstall
         index
@@ -1128,12 +1149,12 @@
           | parallelInstall                   = False
           | otherwise                         = False
 
-    substLogFileName :: PathTemplate -> PackageIdentifier -> LibraryName
-                     -> FilePath
-    substLogFileName template pkg libname = fromPathTemplate
-                                          . substPathTemplate env
-                                          $ template
-      where env = initialPathTemplateEnv (packageId pkg) libname
+    substLogFileName :: PathTemplate -> PackageIdentifier -> UnitId -> FilePath
+    substLogFileName template pkg ipid = fromPathTemplate
+                                  . substPathTemplate env
+                                  $ template
+      where env = initialPathTemplateEnv (packageId pkg)
+                  ipid
                   (compilerInfo comp) platform
 
     miscOptions  = InstallMisc {
@@ -1148,12 +1169,12 @@
 
 executeInstallPlan :: Verbosity
                    -> Compiler
-                   -> JobControl IO (PackageId, LibraryName, BuildResult)
+                   -> JobControl IO (PackageId, UnitId, BuildResult)
                    -> UseLogFile
                    -> InstallPlan
                    -> (ReadyPackage -> IO BuildResult)
                    -> IO InstallPlan
-executeInstallPlan verbosity comp jobCtl useLogFile plan0 installPkg =
+executeInstallPlan verbosity _comp jobCtl useLogFile plan0 installPkg =
     tryNewTasks 0 plan0
   where
     tryNewTasks taskCount plan = do
@@ -1165,10 +1186,13 @@
             [ do info verbosity $ "Ready to install " ++ display pkgid
                  spawnJob jobCtl $ do
                    buildResult <- installPkg pkg
-                   return (packageId pkg, libname, buildResult)
+                   let ipid = case buildResult of
+                                Right (BuildOk _ _ (Just ipi)) ->
+                                     Installed.installedUnitId ipi
+                                _ -> mkUnitId (display (packageId pkg))
+                   return (packageId pkg, ipid, buildResult)
             | pkg <- pkgs
-            , let pkgid = packageId pkg
-                  libname = readyLibraryName comp pkg ]
+            , let pkgid = packageId pkg ]
 
           let taskCount' = taskCount + length pkgs
               plan'      = InstallPlan.processing pkgs plan
@@ -1176,8 +1200,8 @@
 
     waitForTasks taskCount plan = do
       info verbosity $ "Waiting for install task to finish..."
-      (pkgid, libname, buildResult) <- collectJob jobCtl
-      printBuildResult pkgid libname buildResult
+      (pkgid, ipid, buildResult) <- collectJob jobCtl
+      printBuildResult pkgid ipid buildResult
       let taskCount' = taskCount-1
           plan'      = updatePlan pkgid buildResult plan
       tryNewTasks taskCount' plan'
@@ -1185,11 +1209,11 @@
     updatePlan :: PackageIdentifier -> BuildResult -> InstallPlan
                -> InstallPlan
     updatePlan pkgid (Right buildSuccess@(BuildOk _ _ mipkg)) =
-        InstallPlan.completed (Source.fakeInstalledPackageId pkgid)
+        InstallPlan.completed (Source.fakeUnitId pkgid)
                               mipkg buildSuccess
 
     updatePlan pkgid (Left buildFailure) =
-        InstallPlan.failed (Source.fakeInstalledPackageId pkgid)
+        InstallPlan.failed (Source.fakeUnitId pkgid)
                            buildFailure depsFailure
       where
         depsFailure = DependentFailed pkgid
@@ -1200,8 +1224,8 @@
 
     -- Print build log if something went wrong, and 'Installed $PKGID'
     -- otherwise.
-    printBuildResult :: PackageId -> LibraryName -> BuildResult -> IO ()
-    printBuildResult pkgid libname buildResult = case buildResult of
+    printBuildResult :: PackageId -> UnitId -> BuildResult -> IO ()
+    printBuildResult pkgid ipid buildResult = case buildResult of
         (Right _) -> notice verbosity $ "Installed " ++ display pkgid
         (Left _)  -> do
           notice verbosity $ "Failed to install " ++ display pkgid
@@ -1209,7 +1233,7 @@
             case useLogFile of
               Nothing                 -> return ()
               Just (mkLogFileName, _) -> do
-                let logName = mkLogFileName pkgid libname
+                let logName = mkLogFileName pkgid ipid
                 putStr $ "Build log ( " ++ logName ++ " ):\n"
                 printFile logName
 
@@ -1227,7 +1251,7 @@
 installReadyPackage :: Platform -> CompilerInfo
                     -> ConfigFlags
                     -> ReadyPackage
-                    -> (ConfigFlags -> PackageLocation (Maybe FilePath)
+                    -> (ConfigFlags -> UnresolvedPkgLoc
                                     -> PackageDescription
                                     -> PackageDescriptionOverride
                                     -> a)
@@ -1235,19 +1259,17 @@
 installReadyPackage platform cinfo configFlags
                     (ReadyPackage (ConfiguredPackage
                                     (SourcePackage _ gpkg source pkgoverride)
-                                    flags stanzas _)
-                                   deps)
+                                    flags stanzas deps))
                     installPkg =
   installPkg configFlags {
     configConfigurationsFlags = flags,
     -- We generate the legacy constraints as well as the new style precise deps.
     -- In the end only one set gets passed to Setup.hs configure, depending on
     -- the Cabal version we are talking to.
-    configConstraints  = [ thisPackageVersion (packageId deppkg)
-                         | deppkg <- CD.nonSetupDeps deps ],
-    configDependencies = [ (packageName (Installed.sourcePackageId deppkg),
-                            Installed.installedPackageId deppkg)
-                         | deppkg <- CD.nonSetupDeps deps ],
+    configConstraints  = [ thisPackageVersion srcid
+                         | ConfiguredId srcid _uid <- CD.nonSetupDeps deps ],
+    configDependencies = [ (packageName srcid, uid)
+                         | ConfiguredId srcid uid <- CD.nonSetupDeps deps ],
     -- Use '--exact-configuration' if supported.
     configExactConfiguration = toFlag True,
     configBenchmarks         = toFlag False,
@@ -1261,26 +1283,26 @@
       Right (desc, _) -> desc
 
 fetchSourcePackage
-  :: HttpTransport
-  -> Verbosity
+  :: Verbosity
+  -> RepoContext
   -> JobLimit
-  -> PackageLocation (Maybe FilePath)
-  -> (PackageLocation FilePath -> IO BuildResult)
+  -> UnresolvedPkgLoc
+  -> (ResolvedPkgLoc -> IO BuildResult)
   -> IO BuildResult
-fetchSourcePackage transport verbosity fetchLimit src installPkg = do
+fetchSourcePackage verbosity repoCtxt fetchLimit src installPkg = do
   fetched <- checkFetched src
   case fetched of
     Just src' -> installPkg src'
     Nothing   -> onFailure DownloadFailed $ do
                    loc <- withJobLimit fetchLimit $
-                            fetchPackage transport verbosity src
+                            fetchPackage verbosity repoCtxt src
                    installPkg loc
 
 
 installLocalPackage
   :: Verbosity
   -> JobLimit
-  -> PackageIdentifier -> PackageLocation FilePath -> FilePath
+  -> PackageIdentifier -> ResolvedPkgLoc -> FilePath
   -> (Maybe FilePath -> IO BuildResult)
   -> IO BuildResult
 installLocalPackage verbosity jobLimit pkgid location distPref installPkg =
@@ -1362,7 +1384,6 @@
   -> JobLimit
   -> Lock
   -> Int
-  -> LibraryName
   -> SetupScriptOptions
   -> InstallMisc
   -> ConfigFlags
@@ -1371,14 +1392,15 @@
   -> CompilerInfo
   -> Platform
   -> PackageDescription
+  -> ReadyPackage
   -> PackageDescriptionOverride
   -> Maybe FilePath -- ^ Directory to change to before starting the installation.
   -> UseLogFile -- ^ File to log output to (if any)
   -> IO BuildResult
-installUnpackedPackage verbosity buildLimit installLock numJobs libname
+installUnpackedPackage verbosity buildLimit installLock numJobs
                        scriptOptions miscOptions
                        configFlags installFlags haddockFlags
-                       cinfo platform pkg pkgoverride workingDir useLogFile = do
+                       cinfo platform pkg rpkg pkgoverride workingDir useLogFile = do
 
   -- Override the .cabal file if necessary
   case pkgoverride of
@@ -1391,9 +1413,20 @@
                     ++ " with the latest revision from the index."
       writeFileAtomic descFilePath pkgtxt
 
+  -- Compute the IPID of the *library*
+  let flags (ReadyPackage cpkg) = confPkgFlags cpkg
+      pkg_name = pkgName (PackageDescription.package pkg)
+      cid = Configure.computeComponentId
+                Cabal.NoFlag -- This would let us override the computation
+                (PackageDescription.package pkg)
+                (CLibName (display pkg_name))
+                (map (\(SimpleUnitId cid0) -> cid0) (CD.libraryDeps (depends rpkg)))
+                (flags rpkg)
+      ipid = SimpleUnitId cid
+
   -- Make sure that we pass --libsubdir etc to 'setup configure' (necessary if
   -- the setup script was compiled against an old version of the Cabal lib).
-  configFlags' <- addDefaultInstallDirs configFlags
+  configFlags' <- addDefaultInstallDirs ipid configFlags
   -- Filter out flags not supported by the old versions of the Cabal lib.
   let configureFlags :: Version -> ConfigFlags
       configureFlags  = filterConfigureFlags configFlags' {
@@ -1401,52 +1434,55 @@
       }
 
   -- Path to the optional log file.
-  mLogPath <- maybeLogPath
-
-  -- Configure phase
-  onFailure ConfigureFailed $ withJobLimit buildLimit $ do
-    when (numJobs > 1) $ notice verbosity $
-      "Configuring " ++ display pkgid ++ "..."
-    setup configureCommand configureFlags mLogPath
+  mLogPath <- maybeLogPath ipid
 
-  -- Build phase
-    onFailure BuildFailed $ do
+  logDirChange (maybe putStr appendFile mLogPath) workingDir $ do
+    -- Configure phase
+    onFailure ConfigureFailed $ withJobLimit buildLimit $ do
       when (numJobs > 1) $ notice verbosity $
-        "Building " ++ display pkgid ++ "..."
-      setup buildCommand' buildFlags mLogPath
+        "Configuring " ++ display pkgid ++ "..."
+      setup configureCommand configureFlags mLogPath
 
-  -- Doc generation phase
-      docsResult <- if shouldHaddock
-        then (do setup haddockCommand haddockFlags' mLogPath
-                 return DocsOk)
-               `catchIO`   (\_ -> return DocsFailed)
-               `catchExit` (\_ -> return DocsFailed)
-        else return DocsNotTried
+    -- Build phase
+      onFailure BuildFailed $ do
+        when (numJobs > 1) $ notice verbosity $
+          "Building " ++ display pkgid ++ "..."
+        setup buildCommand' buildFlags mLogPath
 
-  -- Tests phase
-      onFailure TestsFailed $ do
-        when (testsEnabled && PackageDescription.hasTests pkg) $
-            setup Cabal.testCommand testFlags mLogPath
+    -- Doc generation phase
+        docsResult <- if shouldHaddock
+          then (do setup haddockCommand haddockFlags' mLogPath
+                   return DocsOk)
+                 `catchIO`   (\_ -> return DocsFailed)
+                 `catchExit` (\_ -> return DocsFailed)
+          else return DocsNotTried
 
-        let testsResult | testsEnabled = TestsOk
-                        | otherwise = TestsNotTried
+    -- Tests phase
+        onFailure TestsFailed $ do
+          when (testsEnabled && PackageDescription.hasTests pkg) $
+              setup Cabal.testCommand testFlags mLogPath
 
-      -- Install phase
-        onFailure InstallFailed $ criticalSection installLock $ do
-          -- Capture installed package configuration file
-          maybePkgConf <- maybeGenPkgConf mLogPath
+          let testsResult | testsEnabled = TestsOk
+                          | otherwise = TestsNotTried
 
-          -- Actual installation
-          withWin32SelfUpgrade verbosity libname configFlags
-                               cinfo platform pkg $ do
-            case rootCmd miscOptions of
-              (Just cmd) -> reexec cmd
-              Nothing    -> do
-                setup Cabal.copyCommand copyFlags mLogPath
-                when shouldRegister $ do
-                  setup Cabal.registerCommand registerFlags mLogPath
-          return (Right (BuildOk docsResult testsResult maybePkgConf))
+        -- Install phase
+          onFailure InstallFailed $ criticalSection installLock $ do
+            -- Actual installation
+            withWin32SelfUpgrade verbosity ipid configFlags
+                                 cinfo platform pkg $ do
+              case rootCmd miscOptions of
+                (Just cmd) -> reexec cmd
+                Nothing    -> do
+                  setup Cabal.copyCommand copyFlags mLogPath
+                  when shouldRegister $ do
+                    setup Cabal.registerCommand registerFlags mLogPath
 
+            -- Capture installed package configuration file
+            -- TODO: Why do we need this?
+            maybePkgConf <- maybeGenPkgConf mLogPath
+
+            return (Right (BuildOk docsResult testsResult maybePkgConf))
+
   where
     pkgid            = packageId pkg
     buildCommand'    = buildCommand defaultProgramConfiguration
@@ -1477,8 +1513,8 @@
     verbosity' = maybe verbosity snd useLogFile
     tempTemplate name = name ++ "-" ++ display pkgid
 
-    addDefaultInstallDirs :: ConfigFlags -> IO ConfigFlags
-    addDefaultInstallDirs configFlags' = do
+    addDefaultInstallDirs :: UnitId -> ConfigFlags -> IO ConfigFlags
+    addDefaultInstallDirs ipid configFlags' = do
       defInstallDirs <- InstallDirs.defaultInstallDirs flavor userInstall False
       return $ configFlags' {
           configInstallDirs = fmap Cabal.Flag .
@@ -1488,7 +1524,7 @@
           }
         where
           CompilerId flavor _ = compilerInfoId cinfo
-          env         = initialPathTemplateEnv pkgid libname cinfo platform
+          env         = initialPathTemplateEnv pkgid ipid cinfo platform
           userInstall = fromFlagOrDefault defaultUserInstall
                         (configUserInstall configFlags')
 
@@ -1517,12 +1553,12 @@
       die $ "Couldn't parse the output of 'setup register --gen-pkg-config':"
             ++ show perror
 
-    maybeLogPath :: IO (Maybe FilePath)
-    maybeLogPath =
+    maybeLogPath :: UnitId -> IO (Maybe FilePath)
+    maybeLogPath ipid =
       case useLogFile of
          Nothing                 -> return Nothing
          Just (mkLogFileName, _) -> do
-           let logFileName = mkLogFileName (packageId pkg) libname
+           let logFileName = mkLogFileName (packageId pkg) ipid
                logDir      = takeDirectory logFileName
            unless (null logDir) $ createDirectoryIfMissing True logDir
            logFileExists <- doesFileExist logFileName
@@ -1570,14 +1606,14 @@
 -- ------------------------------------------------------------
 
 withWin32SelfUpgrade :: Verbosity
-                     -> LibraryName
+                     -> UnitId
                      -> ConfigFlags
                      -> CompilerInfo
                      -> Platform
                      -> PackageDescription
                      -> IO a -> IO a
 withWin32SelfUpgrade _ _ _ _ _ _ action | buildOS /= Windows = action
-withWin32SelfUpgrade verbosity libname configFlags cinfo platform pkg action = do
+withWin32SelfUpgrade verbosity ipid configFlags cinfo platform pkg action = do
 
   defaultDirs <- InstallDirs.defaultInstallDirs
                    compFlavor
@@ -1605,10 +1641,10 @@
         templateDirs   = InstallDirs.combineInstallDirs fromFlagOrDefault
                            defaultDirs (configInstallDirs configFlags)
         absoluteDirs   = InstallDirs.absoluteInstallDirs
-                           pkgid libname
+                           pkgid ipid
                            cinfo InstallDirs.NoCopyDest
                            platform templateDirs
         substTemplate  = InstallDirs.fromPathTemplate
                        . InstallDirs.substPathTemplate env
-          where env = InstallDirs.initialPathTemplateEnv pkgid libname
+          where env = InstallDirs.initialPathTemplateEnv pkgid ipid
                       cinfo platform
diff --git a/cabal/cabal-install/Distribution/Client/InstallPlan.hs b/cabal/cabal-install/Distribution/Client/InstallPlan.hs
--- a/cabal/cabal-install/Distribution/Client/InstallPlan.hs
+++ b/cabal/cabal-install/Distribution/Client/InstallPlan.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.InstallPlan
@@ -21,11 +22,16 @@
   -- * Operations on 'InstallPlan's
   new,
   toList,
+  mapPreservingGraph,
+
   ready,
   processing,
   completed,
   failed,
   remove,
+  preexisting,
+  preinstalled,
+
   showPlanIndex,
   showInstallPlan,
 
@@ -42,17 +48,21 @@
 
   -- ** Querying the install plan
   dependencyClosure,
+  reverseDependencyClosure,
+  topologicalOrder,
+  reverseTopologicalOrder,
   ) where
 
 import Distribution.InstalledPackageInfo
          ( InstalledPackageInfo )
 import Distribution.Package
          ( PackageIdentifier(..), PackageName(..), Package(..)
-         , InstalledPackageId, HasInstalledPackageId(..) )
+         , HasUnitId(..), UnitId(..) )
 import Distribution.Client.Types
          ( BuildSuccess, BuildFailure
          , PackageFixedDeps(..), ConfiguredPackage
-         , GenericReadyPackage(..), fakeInstalledPackageId )
+         , UnresolvedPkgLoc
+         , GenericReadyPackage(..), fakeUnitId )
 import Distribution.Version
          ( Version )
 import Distribution.Client.ComponentDeps (ComponentDeps)
@@ -67,14 +77,16 @@
          ( display )
 
 import Data.List
-         ( intercalate )
+         ( foldl', intercalate )
 import Data.Maybe
-         ( fromMaybe, maybeToList )
+         ( fromMaybe, catMaybes )
 import qualified Data.Graph as Graph
 import Data.Graph (Graph)
+import qualified Data.Tree as Tree
+import Distribution.Compat.Binary (Binary(..))
+import GHC.Generics
 import Control.Exception
          ( assert )
-import Data.Maybe (catMaybes)
 import qualified Data.Map as Map
 import qualified Data.Traversable as T
 
@@ -135,12 +147,16 @@
 data GenericPlanPackage ipkg srcpkg iresult ifailure
    = PreExisting ipkg
    | Configured  srcpkg
-   | Processing  (GenericReadyPackage srcpkg ipkg)
-   | Installed   (GenericReadyPackage srcpkg ipkg) (Maybe ipkg) iresult
+   | Processing  (GenericReadyPackage srcpkg)
+   | Installed   (GenericReadyPackage srcpkg) (Maybe ipkg) iresult
    | Failed      srcpkg ifailure
+  deriving (Eq, Show, Generic)
 
+instance (Binary ipkg, Binary srcpkg, Binary  iresult, Binary  ifailure)
+      => Binary (GenericPlanPackage ipkg srcpkg iresult ifailure)
+
 type PlanPackage = GenericPlanPackage
-                   InstalledPackageInfo ConfiguredPackage
+                   InstalledPackageInfo (ConfiguredPackage UnresolvedPkgLoc)
                    BuildSuccess BuildFailure
 
 instance (Package ipkg, Package srcpkg) =>
@@ -152,7 +168,7 @@
   packageId (Failed      spkg   _) = packageId spkg
 
 instance (PackageFixedDeps srcpkg,
-          PackageFixedDeps ipkg, HasInstalledPackageId ipkg) =>
+          PackageFixedDeps ipkg) =>
          PackageFixedDeps (GenericPlanPackage ipkg srcpkg iresult ifailure) where
   depends (PreExisting pkg)     = depends pkg
   depends (Configured  pkg)     = depends pkg
@@ -160,58 +176,114 @@
   depends (Installed   pkg _ _) = depends pkg
   depends (Failed      pkg   _) = depends pkg
 
-instance (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg) =>
-         HasInstalledPackageId
+instance (HasUnitId ipkg, HasUnitId srcpkg) =>
+         HasUnitId
          (GenericPlanPackage ipkg srcpkg iresult ifailure) where
-  installedPackageId (PreExisting ipkg ) = installedPackageId ipkg
-  installedPackageId (Configured  spkg)  = installedPackageId spkg
-  installedPackageId (Processing  rpkg)  = installedPackageId rpkg
+  installedUnitId (PreExisting ipkg ) = installedUnitId ipkg
+  installedUnitId (Configured  spkg)  = installedUnitId spkg
+  installedUnitId (Processing  rpkg)  = installedUnitId rpkg
   -- NB: defer to the actual installed package info in this case
-  installedPackageId (Installed _ (Just ipkg) _) = installedPackageId ipkg
-  installedPackageId (Installed rpkg _        _) = installedPackageId rpkg
-  installedPackageId (Failed      spkg        _) = installedPackageId spkg
+  installedUnitId (Installed _ (Just ipkg) _) = installedUnitId ipkg
+  installedUnitId (Installed rpkg _        _) = installedUnitId rpkg
+  installedUnitId (Failed      spkg        _) = installedUnitId spkg
 
 
 data GenericInstallPlan ipkg srcpkg iresult ifailure = GenericInstallPlan {
-    planIndex      :: (PlanIndex ipkg srcpkg iresult ifailure),
-    planFakeMap    :: FakeMap,
+    planIndex      :: !(PlanIndex ipkg srcpkg iresult ifailure),
+    planFakeMap    :: !FakeMap,
+    planIndepGoals :: !Bool,
+
+    -- | Cached (lazily) graph
+    --
+    -- The 'Graph' representation works in terms of integer node ids, so we
+    -- have to keep mapping to and from our meaningful nodes, which of course
+    -- are package ids.
+    --
     planGraph      :: Graph,
-    planGraphRev   :: Graph,
-    planPkgOf      :: Graph.Vertex
-                      -> GenericPlanPackage ipkg srcpkg iresult ifailure,
-    planVertexOf   :: InstalledPackageId -> Graph.Vertex,
-    planIndepGoals :: Bool
+    planGraphRev   :: Graph,  -- ^ Reverse deps, transposed
+    planPkgIdOf    :: Graph.Vertex -> UnitId, -- ^ mapping back to package ids
+    planVertexOf   :: UnitId -> Graph.Vertex  -- ^ mapping into node ids
   }
 
+-- | Much like 'planPkgIdOf', but mapping back to full packages.
+planPkgOf :: GenericInstallPlan ipkg srcpkg iresult ifailure
+          -> Graph.Vertex
+          -> GenericPlanPackage ipkg srcpkg iresult ifailure
+planPkgOf plan v =
+    case PackageIndex.lookupUnitId (planIndex plan)
+                                   (planPkgIdOf plan v) of
+      Just pkg -> pkg
+      Nothing  -> error "InstallPlan: internal error: planPkgOf lookup failed"
+
+
 -- | 'GenericInstallPlan' specialised to most commonly used types.
 type InstallPlan = GenericInstallPlan
-                   InstalledPackageInfo ConfiguredPackage
+                   InstalledPackageInfo (ConfiguredPackage UnresolvedPkgLoc)
                    BuildSuccess BuildFailure
 
 type PlanIndex ipkg srcpkg iresult ifailure =
      PackageIndex (GenericPlanPackage ipkg srcpkg iresult ifailure)
 
-invariant :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-              HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+invariant :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+              HasUnitId srcpkg, PackageFixedDeps srcpkg)
           => GenericInstallPlan ipkg srcpkg iresult ifailure -> Bool
 invariant plan =
     valid (planFakeMap plan)
           (planIndepGoals plan)
           (planIndex plan)
 
+-- | Smart constructor that deals with caching the 'Graph' representation.
+--
+mkInstallPlan :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+                  HasUnitId srcpkg, PackageFixedDeps srcpkg)
+              => PlanIndex ipkg srcpkg iresult ifailure
+              -> FakeMap
+              -> Bool
+              -> GenericInstallPlan ipkg srcpkg iresult ifailure
+mkInstallPlan index fakeMap indepGoals =
+    GenericInstallPlan {
+      planIndex      = index,
+      planFakeMap    = fakeMap,
+      planIndepGoals = indepGoals,
+
+      -- lazily cache the graph stuff:
+      planGraph      = graph,
+      planGraphRev   = Graph.transposeG graph,
+      planPkgIdOf    = vertexToPkgId,
+      planVertexOf   = fromMaybe noSuchPkgId . pkgIdToVertex
+    }
+  where
+    (graph, vertexToPkgId, pkgIdToVertex) =
+      PlanIndex.dependencyGraph fakeMap index
+    noSuchPkgId = internalError "package is not in the graph"
+
 internalError :: String -> a
 internalError msg = error $ "InstallPlan: internal error: " ++ msg
 
-showPlanIndex :: (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg)
+instance (HasUnitId ipkg,   PackageFixedDeps ipkg,
+          HasUnitId srcpkg, PackageFixedDeps srcpkg,
+          Binary ipkg, Binary srcpkg, Binary iresult, Binary ifailure)
+       => Binary (GenericInstallPlan ipkg srcpkg iresult ifailure) where
+    put GenericInstallPlan {
+              planIndex      = index,
+              planFakeMap    = fakeMap,
+              planIndepGoals = indepGoals
+        } = put (index, fakeMap, indepGoals)
+
+    get = do
+      (index, fakeMap, indepGoals) <- get
+      return $! mkInstallPlan index fakeMap indepGoals
+
+showPlanIndex :: (HasUnitId ipkg, HasUnitId srcpkg)
               => PlanIndex ipkg srcpkg iresult ifailure -> String
 showPlanIndex index =
     intercalate "\n" (map showPlanPackage (PackageIndex.allPackages index))
   where showPlanPackage p =
             showPlanPackageTag p ++ " "
                 ++ display (packageId p) ++ " ("
-                ++ display (installedPackageId p) ++ ")"
+                ++ display (installedUnitId p) ++ ")"
 
-showInstallPlan :: (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg)
+showInstallPlan :: (HasUnitId ipkg, HasUnitId srcpkg)
                 => GenericInstallPlan ipkg srcpkg iresult ifailure -> String
 showInstallPlan plan =
     showPlanIndex (planIndex plan) ++ "\n" ++
@@ -228,8 +300,8 @@
 
 -- | Build an installation plan from a valid set of resolved packages.
 --
-new :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-        HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+new :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+        HasUnitId srcpkg, PackageFixedDeps srcpkg)
     => Bool
     -> PlanIndex ipkg srcpkg iresult ifailure
     -> Either [PlanProblem ipkg srcpkg iresult ifailure]
@@ -240,23 +312,12 @@
   let isPreExisting (PreExisting _) = True
       isPreExisting _ = False
       fakeMap = Map.fromList
-              . map (\p -> (fakeInstalledPackageId (packageId p)
-                           ,installedPackageId p))
+              . map (\p -> (fakeUnitId (packageId p)
+                           ,installedUnitId p))
               . filter isPreExisting
               $ PackageIndex.allPackages index in
   case problems fakeMap indepGoals index of
-    [] -> Right GenericInstallPlan {
-            planIndex      = index,
-            planFakeMap    = fakeMap,
-            planGraph      = graph,
-            planGraphRev   = Graph.transposeG graph,
-            planPkgOf      = vertexToPkgId,
-            planVertexOf   = fromMaybe noSuchPkgId . pkgIdToVertex,
-            planIndepGoals = indepGoals
-          }
-      where (graph, vertexToPkgId, pkgIdToVertex) =
-              PlanIndex.dependencyGraph fakeMap index
-            noSuchPkgId = internalError "package is not in the graph"
+    []    -> Right (mkInstallPlan index fakeMap indepGoals)
     probs -> Left probs
 
 toList :: GenericInstallPlan ipkg srcpkg iresult ifailure
@@ -269,8 +330,8 @@
 -- the dependencies of a package or set of packages without actually
 -- installing the package itself, as when doing development.
 --
-remove :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-           HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+remove :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+           HasUnitId srcpkg, PackageFixedDeps srcpkg)
        => (GenericPlanPackage ipkg srcpkg iresult ifailure -> Bool)
        -> GenericInstallPlan ipkg srcpkg iresult ifailure
        -> Either [PlanProblem ipkg srcpkg iresult ifailure]
@@ -287,7 +348,7 @@
 --
 ready :: forall ipkg srcpkg iresult ifailure. PackageFixedDeps srcpkg
       => GenericInstallPlan ipkg srcpkg iresult ifailure
-      -> [GenericReadyPackage srcpkg ipkg]
+      -> [GenericReadyPackage srcpkg]
 ready plan = assert check readyPackages
   where
     check = if null readyPackages && null processingPackages
@@ -296,22 +357,27 @@
     configuredPackages = [ pkg | Configured pkg <- toList plan ]
     processingPackages = [ pkg | Processing pkg <- toList plan]
 
-    readyPackages :: [GenericReadyPackage srcpkg ipkg]
-    readyPackages =
-      [ ReadyPackage srcpkg deps
-      | srcpkg <- configuredPackages
-        -- select only the package that have all of their deps installed:
-      , deps <- maybeToList (hasAllInstalledDeps srcpkg)
-      ]
+    readyPackages :: [GenericReadyPackage srcpkg]
+    readyPackages = catMaybes (map (lookupReadyPackage plan) configuredPackages)
 
+lookupReadyPackage :: forall ipkg srcpkg iresult ifailure.
+                      PackageFixedDeps srcpkg
+                   => GenericInstallPlan ipkg srcpkg iresult ifailure
+                   -> srcpkg
+                   -> Maybe (GenericReadyPackage srcpkg)
+lookupReadyPackage plan pkg = do
+    _ <- hasAllInstalledDeps pkg
+    return (ReadyPackage pkg)
+  where
+
     hasAllInstalledDeps :: srcpkg -> Maybe (ComponentDeps [ipkg])
     hasAllInstalledDeps = T.mapM (mapM isInstalledDep) . depends
 
-    isInstalledDep :: InstalledPackageId -> Maybe ipkg
+    isInstalledDep :: UnitId -> Maybe ipkg
     isInstalledDep pkgid =
       -- NB: Need to check if the ID has been updated in planFakeMap, in which
       -- case we might be dealing with an old pointer
-      case PlanIndex.fakeLookupInstalledPackageId
+      case PlanIndex.fakeLookupUnitId
            (planFakeMap plan) (planIndex plan) pkgid
       of
         Just (PreExisting ipkg)            -> Just ipkg
@@ -329,9 +395,9 @@
 --
 -- * The package must exist in the graph and be in the configured state.
 --
-processing :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-               HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
-           => [GenericReadyPackage srcpkg ipkg]
+processing :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+               HasUnitId srcpkg, PackageFixedDeps srcpkg)
+           => [GenericReadyPackage srcpkg]
            -> GenericInstallPlan ipkg srcpkg iresult ifailure
            -> GenericInstallPlan ipkg srcpkg iresult ifailure
 processing pkgs plan = assert (invariant plan') plan'
@@ -347,9 +413,9 @@
 -- * The package must exist in the graph and be in the processing state.
 -- * The package must have had no uninstalled dependent packages.
 --
-completed :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-              HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
-          => InstalledPackageId
+completed :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+              HasUnitId srcpkg, PackageFixedDeps srcpkg)
+          => UnitId
           -> Maybe ipkg -> iresult
           -> GenericInstallPlan ipkg srcpkg iresult ifailure
           -> GenericInstallPlan ipkg srcpkg iresult ifailure
@@ -361,13 +427,13 @@
                   planFakeMap = insert_fake_mapping mipkg
                               $ planFakeMap plan,
                   planIndex = PackageIndex.insert installed
-                            . PackageIndex.deleteInstalledPackageId pkgid
+                            . PackageIndex.deleteUnitId pkgid
                             $ planIndex plan
                 }
     -- ...but be sure to use the *old* IPID for the lookup for the
     -- preexisting record
     installed = Installed (lookupProcessingPackage plan pkgid) mipkg buildResult
-    insert_fake_mapping (Just ipkg) = Map.insert pkgid (installedPackageId ipkg)
+    insert_fake_mapping (Just ipkg) = Map.insert pkgid (installedUnitId ipkg)
     insert_fake_mapping  _          = id
 
 -- | Marks a package in the graph as having failed. It also marks all the
@@ -376,9 +442,9 @@
 -- * The package must exist in the graph and be in the processing
 -- state.
 --
-failed :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-           HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
-       => InstalledPackageId -- ^ The id of the package that failed to install
+failed :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+           HasUnitId srcpkg, PackageFixedDeps srcpkg)
+       => UnitId         -- ^ The id of the package that failed to install
        -> ifailure           -- ^ The build result to use for the failed package
        -> ifailure           -- ^ The build result to use for its dependencies
        -> GenericInstallPlan ipkg srcpkg iresult ifailure
@@ -389,7 +455,7 @@
     plan'    = plan {
                  planIndex = PackageIndex.merge (planIndex plan) failures
                }
-    ReadyPackage srcpkg _deps = lookupProcessingPackage plan pkgid
+    ReadyPackage srcpkg = lookupProcessingPackage plan pkgid
     failures = PackageIndex.fromList
              $ Failed srcpkg buildResult
              : [ Failed pkg' buildResult'
@@ -399,7 +465,7 @@
 -- | Lookup the reachable packages in the reverse dependency graph.
 --
 packagesThatDependOn :: GenericInstallPlan ipkg srcpkg iresult ifailure
-                     -> InstalledPackageId
+                     -> UnitId
                      -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
 packagesThatDependOn plan pkgid = map (planPkgOf plan)
                           . tail
@@ -410,12 +476,12 @@
 -- | Lookup a package that we expect to be in the processing state.
 --
 lookupProcessingPackage :: GenericInstallPlan ipkg srcpkg iresult ifailure
-                        -> InstalledPackageId
-                        -> GenericReadyPackage srcpkg ipkg
+                        -> UnitId
+                        -> GenericReadyPackage srcpkg
 lookupProcessingPackage plan pkgid =
   -- NB: processing packages are guaranteed to not indirect through
   -- planFakeMap
-  case PackageIndex.lookupInstalledPackageId (planIndex plan) pkgid of
+  case PackageIndex.lookupUnitId (planIndex plan) pkgid of
     Just (Processing pkg) -> pkg
     _  -> internalError $ "not in processing state or no such pkg " ++
                           display pkgid
@@ -430,6 +496,97 @@
 checkConfiguredPackage pkg                =
   internalError $ "not configured or no such pkg " ++ display (packageId pkg)
 
+-- | Replace a ready package with a pre-existing one. The pre-existing one
+-- must have exactly the same dependencies as the source one was configured
+-- with.
+--
+preexisting :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+                HasUnitId srcpkg, PackageFixedDeps srcpkg)
+            => UnitId
+            -> ipkg
+            -> GenericInstallPlan ipkg srcpkg iresult ifailure
+            -> GenericInstallPlan ipkg srcpkg iresult ifailure
+preexisting pkgid ipkg plan = assert (invariant plan') plan'
+  where
+    plan' = plan {
+                    -- NB: installation can change the IPID, so better
+                    -- record it in the fake mapping...
+      planFakeMap = Map.insert pkgid
+                               (installedUnitId ipkg)
+                               (planFakeMap plan),
+      planIndex   = PackageIndex.insert (PreExisting ipkg)
+                    -- ...but be sure to use the *old* IPID for the lookup for
+                    -- the preexisting record
+                  . PackageIndex.deleteUnitId pkgid
+                  $ planIndex plan
+    }
+
+-- | Replace a ready package with an installed one. The installed one
+-- must have exactly the same dependencies as the source one was configured
+-- with.
+--
+preinstalled :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+                 HasUnitId srcpkg, PackageFixedDeps srcpkg)
+             => UnitId
+             -> Maybe ipkg -> iresult
+             -> GenericInstallPlan ipkg srcpkg iresult ifailure
+             -> GenericInstallPlan ipkg srcpkg iresult ifailure
+preinstalled pkgid mipkg buildResult plan = assert (invariant plan') plan'
+  where
+    plan' = plan { planIndex = PackageIndex.insert installed (planIndex plan) }
+    Just installed = do
+      Configured pkg <- PackageIndex.lookupUnitId (planIndex plan) pkgid
+      rpkg <- lookupReadyPackage plan pkg
+      return (Installed rpkg mipkg buildResult)
+
+-- | Transform an install plan by mapping a function over all the packages in
+-- the plan. It can consistently change the 'UnitId' of all the packages,
+-- while preserving the same overall graph structure.
+--
+-- The mapping function has a few constraints on it for correct operation.
+-- The mapping function /may/ change the 'UnitId' of the package, but it
+-- /must/ also remap the 'UnitId's of its dependencies using ths supplied
+-- remapping function. Apart from this consistent remapping it /may not/
+-- change the structure of the dependencies.
+--
+mapPreservingGraph :: (HasUnitId ipkg,
+                       HasUnitId srcpkg,
+                       HasUnitId ipkg',   PackageFixedDeps ipkg',
+                       HasUnitId srcpkg', PackageFixedDeps srcpkg')
+                   => (  (UnitId -> UnitId)
+                      -> GenericPlanPackage ipkg  srcpkg  iresult  ifailure
+                      -> GenericPlanPackage ipkg' srcpkg' iresult' ifailure')
+                   -> GenericInstallPlan ipkg  srcpkg  iresult  ifailure
+                   -> GenericInstallPlan ipkg' srcpkg' iresult' ifailure'
+mapPreservingGraph f plan =
+    mkInstallPlan (PackageIndex.fromList pkgs')
+                  Map.empty -- empty fakeMap
+                  (planIndepGoals plan)
+  where
+    -- The package mapping function may change the UnitId. So we
+    -- walk over the packages in dependency order keeping track of these
+    -- package id changes and use it to supply the correct set of package
+    -- dependencies as an extra input to the package mapping function.
+    --
+    -- Having fully remapped all the deps this also means we can use an empty
+    -- FakeMap for the resulting install plan.
+
+    (_, pkgs') = foldl' f' (Map.empty, []) (reverseTopologicalOrder plan)
+
+    f' (ipkgidMap, pkgs) pkg = (ipkgidMap', pkg' : pkgs)
+      where
+       pkg' = f (mapDep ipkgidMap) pkg
+
+       ipkgidMap'
+         | ipkgid /= ipkgid' = Map.insert ipkgid ipkgid' ipkgidMap
+         | otherwise         =                           ipkgidMap
+         where
+           ipkgid  = installedUnitId pkg
+           ipkgid' = installedUnitId pkg'
+
+    mapDep ipkgidMap ipkgid = Map.findWithDefault ipkgid ipkgid ipkgidMap
+
+
 -- ------------------------------------------------------------
 -- * Checking validity of plans
 -- ------------------------------------------------------------
@@ -440,8 +597,8 @@
 --
 -- * if the result is @False@ use 'problems' to get a detailed list.
 --
-valid :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-          HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+valid :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+          HasUnitId srcpkg, PackageFixedDeps srcpkg)
       => FakeMap -> Bool
       -> PlanIndex ipkg srcpkg iresult ifailure
       -> Bool
@@ -492,8 +649,8 @@
 -- error messages. This is mainly intended for debugging purposes.
 -- Use 'showPlanProblem' for a human readable explanation.
 --
-problems :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-             HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+problems :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+             HasUnitId srcpkg, PackageFixedDeps srcpkg)
          => FakeMap -> Bool
          -> PlanIndex ipkg srcpkg iresult ifailure
          -> [PlanProblem ipkg srcpkg iresult ifailure]
@@ -502,7 +659,7 @@
      [ PackageMissingDeps pkg
        (catMaybes
         (map
-         (fmap packageId . PlanIndex.fakeLookupInstalledPackageId fakeMap index)
+         (fmap packageId . PlanIndex.fakeLookupUnitId fakeMap index)
          missingDeps))
      | (pkg, missingDeps) <- PlanIndex.brokenPackages fakeMap index ]
 
@@ -515,8 +672,8 @@
 
   ++ [ PackageStateInvalid pkg pkg'
      | pkg <- PackageIndex.allPackages index
-     , Just pkg' <- map (PlanIndex.fakeLookupInstalledPackageId fakeMap index)
-                    (CD.nonSetupDeps (depends pkg))
+     , Just pkg' <- map (PlanIndex.fakeLookupUnitId fakeMap index)
+                    (CD.flatDeps (depends pkg))
      , not (stateDependencyRelation pkg pkg') ]
 
 -- | The graph of packages (nodes) and dependencies (edges) must be acyclic.
@@ -524,8 +681,8 @@
 -- * if the result is @False@ use 'PackageIndex.dependencyCycles' to find out
 --   which packages are involved in dependency cycles.
 --
-acyclic :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-            HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+acyclic :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+            HasUnitId srcpkg, PackageFixedDeps srcpkg)
         => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool
 acyclic fakeMap = null . PlanIndex.dependencyCycles fakeMap
 
@@ -536,7 +693,7 @@
 -- * if the result is @False@ use 'PackageIndex.brokenPackages' to find out
 --   which packages depend on packages not in the index.
 --
-closed :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,
+closed :: (PackageFixedDeps ipkg,
            PackageFixedDeps srcpkg)
        => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool
 closed fakeMap = null . PlanIndex.brokenPackages fakeMap
@@ -557,8 +714,8 @@
 -- * if the result is @False@ use 'PackageIndex.dependencyInconsistencies' to
 --   find out which packages are.
 --
-consistent :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-               HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
+consistent :: (HasUnitId ipkg,   PackageFixedDeps ipkg,
+               HasUnitId srcpkg, PackageFixedDeps srcpkg)
            => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool
 consistent fakeMap = null . PlanIndex.dependencyInconsistencies fakeMap False
 
@@ -594,22 +751,39 @@
 stateDependencyRelation _               _                 = False
 
 
--- | Compute the dependency closure of a _source_ package in a install plan
+-- | Compute the dependency closure of a package in a install plan
 --
--- See `Distribution.Client.PlanIndex.dependencyClosure`
-dependencyClosure :: (HasInstalledPackageId ipkg,   PackageFixedDeps ipkg,
-                      HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)
-                  => GenericInstallPlan ipkg srcpkg iresult ifailure
-                  -> [PackageIdentifier]
-                  -> Either [(GenericPlanPackage ipkg srcpkg iresult ifailure,
-                              [InstalledPackageId])]
-                            (PackageIndex
-                             (GenericPlanPackage ipkg srcpkg iresult ifailure))
-dependencyClosure installPlan pids =
-    PlanIndex.dependencyClosure
-      (planFakeMap installPlan)
-      (planIndex installPlan)
-      (map (resolveFakeId . fakeInstalledPackageId) pids)
-  where
-    resolveFakeId :: InstalledPackageId -> InstalledPackageId
-    resolveFakeId ipid = Map.findWithDefault ipid ipid (planFakeMap installPlan)
+dependencyClosure :: GenericInstallPlan ipkg srcpkg iresult ifailure
+                  -> [UnitId]
+                  -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
+dependencyClosure plan =
+    map (planPkgOf plan)
+  . concatMap Tree.flatten
+  . Graph.dfs (planGraph plan)
+  . map (planVertexOf plan)
+
+
+reverseDependencyClosure :: GenericInstallPlan ipkg srcpkg iresult ifailure
+                         -> [UnitId]
+                         -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
+reverseDependencyClosure plan =
+    map (planPkgOf plan)
+  . concatMap Tree.flatten
+  . Graph.dfs (planGraphRev plan)
+  . map (planVertexOf plan)
+
+
+topologicalOrder :: GenericInstallPlan ipkg srcpkg iresult ifailure
+                 -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
+topologicalOrder plan =
+    map (planPkgOf plan)
+  . Graph.topSort
+  $ planGraph plan
+
+
+reverseTopologicalOrder :: GenericInstallPlan ipkg srcpkg iresult ifailure
+                        -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
+reverseTopologicalOrder plan =
+    map (planPkgOf plan)
+  . Graph.topSort
+  $ planGraphRev plan
diff --git a/cabal/cabal-install/Distribution/Client/InstallSymlink.hs b/cabal/cabal-install/Distribution/Client/InstallSymlink.hs
--- a/cabal/cabal-install/Distribution/Client/InstallSymlink.hs
+++ b/cabal/cabal-install/Distribution/Client/InstallSymlink.hs
@@ -40,19 +40,17 @@
 import Distribution.Client.Types
          ( SourcePackage(..)
          , GenericReadyPackage(..), ReadyPackage, enableStanzas
-         , ConfiguredPackage(..) )
+         , ConfiguredPackage(..) , fakeUnitId)
 import Distribution.Client.Setup
          ( InstallFlags(installSymlinkBinDir) )
 import qualified Distribution.Client.InstallPlan as InstallPlan
 import Distribution.Client.InstallPlan (InstallPlan)
 
 import Distribution.Package
-         ( PackageIdentifier, Package(packageId), mkPackageKey
-         , packageKeyLibraryName, LibraryName )
+         ( PackageIdentifier, Package(packageId), UnitId(..) )
 import Distribution.Compiler
          ( CompilerId(..) )
 import qualified Distribution.PackageDescription as PackageDescription
-import qualified Distribution.Client.ComponentDeps as CD
 import Distribution.PackageDescription
          ( PackageDescription )
 import Distribution.PackageDescription.Configuration
@@ -60,9 +58,8 @@
 import Distribution.Simple.Setup
          ( ConfigFlags(..), fromFlag, fromFlagOrDefault, flagToMaybe )
 import qualified Distribution.Simple.InstallDirs as InstallDirs
-import qualified Distribution.InstalledPackageInfo as Installed
 import Distribution.Simple.Compiler
-         ( Compiler, compilerInfo, CompilerInfo(..), packageKeySupported )
+         ( Compiler, compilerInfo, CompilerInfo(..) )
 import Distribution.System
          ( Platform )
 
@@ -118,7 +115,7 @@
 --    TODO: do we want to do this here? :
 --      createDirectoryIfMissing True publicBinDir
       fmap catMaybes $ sequence
-        [ do privateBinDir <- pkgBinDir pkg libname
+        [ do privateBinDir <- pkgBinDir pkg ipid
              ok <- symlinkBinary
                      publicBinDir  privateBinDir
                      publicExeName privateExeName
@@ -126,16 +123,14 @@
                then return Nothing
                else return (Just (pkgid, publicExeName,
                                   privateBinDir </> privateExeName))
-        | (ReadyPackage (ConfiguredPackage _ _flags _ _) deps, pkg, exe) <- exes
+        | (ReadyPackage _cpkg, pkg, exe) <- exes
         , let pkgid  = packageId pkg
-              pkg_key = mkPackageKey (packageKeySupported comp) pkgid
-                                     (map Installed.libraryName
-                                      (CD.nonSetupDeps deps))
-              libname = packageKeyLibraryName pkgid pkg_key
+              -- This is a bit dodgy; probably won't work for Backpack packages
+              ipid = fakeUnitId pkgid
               publicExeName  = PackageDescription.exeName exe
               privateExeName = prefix ++ publicExeName ++ suffix
-              prefix = substTemplate pkgid libname prefixTemplate
-              suffix = substTemplate pkgid libname suffixTemplate ]
+              prefix = substTemplate pkgid ipid prefixTemplate
+              suffix = substTemplate pkgid ipid suffixTemplate ]
   where
     exes =
       [ (cpkg, pkg, exe)
@@ -147,8 +142,7 @@
     pkgDescription :: ReadyPackage -> PackageDescription
     pkgDescription (ReadyPackage (ConfiguredPackage
                                     (SourcePackage _ pkg _ _)
-                                    flags stanzas _)
-                                  _) =
+                                    flags stanzas _)) =
       case finalizePackageDescription flags
              (const True)
              platform cinfo [] (enableStanzas stanzas pkg) of
@@ -157,8 +151,8 @@
 
     -- This is sadly rather complicated. We're kind of re-doing part of the
     -- configuration for the package. :-(
-    pkgBinDir :: PackageDescription -> LibraryName -> IO FilePath
-    pkgBinDir pkg libname = do
+    pkgBinDir :: PackageDescription -> UnitId -> IO FilePath
+    pkgBinDir pkg ipid = do
       defaultDirs <- InstallDirs.defaultInstallDirs
                        compilerFlavor
                        (fromFlag (configUserInstall configFlags))
@@ -166,14 +160,14 @@
       let templateDirs = InstallDirs.combineInstallDirs fromFlagOrDefault
                            defaultDirs (configInstallDirs configFlags)
           absoluteDirs = InstallDirs.absoluteInstallDirs
-                           (packageId pkg) libname
+                           (packageId pkg) ipid
                            cinfo InstallDirs.NoCopyDest
                            platform templateDirs
       canonicalizePath (InstallDirs.bindir absoluteDirs)
 
-    substTemplate pkgid libname = InstallDirs.fromPathTemplate
-                                . InstallDirs.substPathTemplate env
-      where env = InstallDirs.initialPathTemplateEnv pkgid libname
+    substTemplate pkgid ipid = InstallDirs.fromPathTemplate
+                             . InstallDirs.substPathTemplate env
+      where env = InstallDirs.initialPathTemplateEnv pkgid ipid
                                                      cinfo platform
 
     fromFlagTemplate = fromFlagOrDefault (InstallDirs.toPathTemplate "")
diff --git a/cabal/cabal-install/Distribution/Client/List.hs b/cabal/cabal-install/Distribution/Client/List.hs
--- a/cabal/cabal-install/Distribution/Client/List.hs
+++ b/cabal/cabal-install/Distribution/Client/List.hs
@@ -16,7 +16,7 @@
 import Distribution.Package
          ( PackageName(..), Package(..), packageName, packageVersion
          , Dependency(..), simplifyDependency
-         , InstalledPackageId )
+         , UnitId )
 import Distribution.ModuleName (ModuleName)
 import Distribution.License (License)
 import qualified Distribution.InstalledPackageInfo as Installed
@@ -31,7 +31,7 @@
 import Distribution.Simple.Program (ProgramConfiguration)
 import Distribution.Simple.Utils
         ( equating, comparing, die, notice )
-import Distribution.Simple.Setup (fromFlag, flagToMaybe)
+import Distribution.Simple.Setup (fromFlag)
 import Distribution.Simple.PackageIndex (InstalledPackageIndex)
 import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex
 import qualified Distribution.Client.PackageIndex as PackageIndex
@@ -43,21 +43,21 @@
          ( Text(disp), display )
 
 import Distribution.Client.Types
-         ( SourcePackage(..), Repo, SourcePackageDb(..) )
+         ( SourcePackage(..), SourcePackageDb(..)
+         , UnresolvedSourcePackage )
 import Distribution.Client.Dependency.Types
          ( PackageConstraint(..) )
 import Distribution.Client.Targets
          ( UserTarget, resolveUserTargets, PackageSpecifier(..) )
 import Distribution.Client.Setup
-         ( GlobalFlags(..), ListFlags(..), InfoFlags(..) )
+         ( GlobalFlags(..), ListFlags(..), InfoFlags(..)
+         , RepoContext(..) )
 import Distribution.Client.Utils
          ( mergeBy, MergeResult(..) )
 import Distribution.Client.IndexUtils as IndexUtils
          ( getSourcePackages, getInstalledPackages )
 import Distribution.Client.FetchUtils
          ( isFetched )
-import Distribution.Client.HttpUtils
-        ( configureTransport )
 
 import Data.List
          ( sortBy, groupBy, sort, nub, intersperse, maximumBy, partition )
@@ -77,21 +77,21 @@
 -- | Return a list of packages matching given search strings.
 getPkgList :: Verbosity
            -> PackageDBStack
-           -> [Repo]
+           -> RepoContext
            -> Compiler
            -> ProgramConfiguration
            -> ListFlags
            -> [String]
            -> IO [PackageDisplayInfo]
-getPkgList verbosity packageDBs repos comp conf listFlags pats = do
+getPkgList verbosity packageDBs repoCtxt comp conf listFlags pats = do
     installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-    sourcePkgDb       <- getSourcePackages    verbosity repos
+    sourcePkgDb       <- getSourcePackages verbosity repoCtxt
     let sourcePkgIndex = packageIndex sourcePkgDb
         prefs name = fromMaybe anyVersion
                        (Map.lookup name (packagePreferences sourcePkgDb))
 
         pkgsInfo ::
-          [(PackageName, [Installed.InstalledPackageInfo], [SourcePackage])]
+          [(PackageName, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage])]
         pkgsInfo
             -- gather info for all packages
           | null pats = mergePackages
@@ -102,7 +102,7 @@
           | otherwise = pkgsInfoMatching
 
         pkgsInfoMatching ::
-          [(PackageName, [Installed.InstalledPackageInfo], [SourcePackage])]
+          [(PackageName, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage])]
         pkgsInfoMatching =
           let matchingInstalled = matchingPackages
                                   InstalledPackageIndex.searchByNameSubstring
@@ -133,7 +133,7 @@
 -- | Show information about packages.
 list :: Verbosity
      -> PackageDBStack
-     -> [Repo]
+     -> RepoContext
      -> Compiler
      -> ProgramConfiguration
      -> ListFlags
@@ -163,7 +163,7 @@
 
 info :: Verbosity
      -> PackageDBStack
-     -> [Repo]
+     -> RepoContext
      -> Compiler
      -> ProgramConfiguration
      -> GlobalFlags
@@ -173,11 +173,11 @@
 info verbosity _ _ _ _ _ _ [] =
     notice verbosity "No packages requested. Nothing to do."
 
-info verbosity packageDBs repos comp conf
+info verbosity packageDBs repoCtxt comp conf
      globalFlags _listFlags userTargets = do
 
     installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf
-    sourcePkgDb   <- getSourcePackages    verbosity repos
+    sourcePkgDb       <- getSourcePackages verbosity repoCtxt
     let sourcePkgIndex = packageIndex sourcePkgDb
         prefs name = fromMaybe anyVersion
                        (Map.lookup name (packagePreferences sourcePkgDb))
@@ -190,8 +190,7 @@
                       (InstalledPackageIndex.allPackages installedPkgIndex)
                    ++ map packageId
                       (PackageIndex.allPackages sourcePkgIndex)
-    transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))
-    pkgSpecifiers <- resolveUserTargets verbosity transport
+    pkgSpecifiers <- resolveUserTargets verbosity repoCtxt
                        (fromFlag $ globalWorldFile globalFlags)
                        sourcePkgs' userTargets
 
@@ -208,8 +207,8 @@
   where
     gatherPkgInfo :: (PackageName -> VersionRange) ->
                      InstalledPackageIndex ->
-                     PackageIndex.PackageIndex SourcePackage ->
-                     PackageSpecifier SourcePackage ->
+                     PackageIndex.PackageIndex UnresolvedSourcePackage ->
+                     PackageSpecifier UnresolvedSourcePackage ->
                      Either String PackageDisplayInfo
     gatherPkgInfo prefs installedPkgIndex sourcePkgIndex
       (NamedPackage name constraints)
@@ -253,8 +252,8 @@
   (PackageName -> VersionRange)
   -> PackageName
   -> InstalledPackageIndex
-  -> PackageIndex.PackageIndex SourcePackage
-  -> (VersionRange, [Installed.InstalledPackageInfo], [SourcePackage])
+  -> PackageIndex.PackageIndex UnresolvedSourcePackage
+  -> (VersionRange, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage])
 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex =
   (pref, installedPkgs, sourcePkgs)
   where
@@ -270,7 +269,7 @@
 data PackageDisplayInfo = PackageDisplayInfo {
     pkgName           :: PackageName,
     selectedVersion   :: Maybe Version,
-    selectedSourcePkg :: Maybe SourcePackage,
+    selectedSourcePkg :: Maybe UnresolvedSourcePackage,
     installedVersions :: [Version],
     sourceVersions    :: [Version],
     preferredVersions :: VersionRange,
@@ -296,7 +295,7 @@
 -- | Covers source dependencies and installed dependencies in
 -- one type.
 data ExtDependency = SourceDependency Dependency
-                   | InstalledDependency InstalledPackageId
+                   | InstalledDependency UnitId
 
 showPackageSummaryInfo :: PackageDisplayInfo -> String
 showPackageSummaryInfo pkginfo =
@@ -419,8 +418,8 @@
 --
 mergePackageInfo :: VersionRange
                  -> [Installed.InstalledPackageInfo]
-                 -> [SourcePackage]
-                 -> Maybe SourcePackage
+                 -> [UnresolvedSourcePackage]
+                 -> Maybe UnresolvedSourcePackage
                  -> Bool
                  -> PackageDisplayInfo
 mergePackageInfo versionPref installedPkgs sourcePkgs selectedPkg showVer =
@@ -458,13 +457,13 @@
     flags        = maybe [] Source.genPackageFlags sourceGeneric,
     hasLib       = isJust installed
                 || fromMaybe False
-                   (fmap (isJust . Source.condLibrary) sourceGeneric),
+                   (fmap (not . null . Source.condLibraries) sourceGeneric),
     hasExe       = fromMaybe False
                    (fmap (not . null . Source.condExecutables) sourceGeneric),
     executables  = map fst (maybe [] Source.condExecutables sourceGeneric),
     modules      = combine (map Installed.exposedName . Installed.exposedModules)
                            installed
-                           (maybe [] getListOfExposedModules . Source.library)
+                           (concatMap getListOfExposedModules . Source.libraries)
                            source,
     dependencies =
       combine (map (SourceDependency . simplifyDependency)
@@ -522,10 +521,10 @@
 -- both be empty.
 --
 mergePackages :: [Installed.InstalledPackageInfo]
-              -> [SourcePackage]
+              -> [UnresolvedSourcePackage]
               -> [( PackageName
                   , [Installed.InstalledPackageInfo]
-                  , [SourcePackage] )]
+                  , [UnresolvedSourcePackage] )]
 mergePackages installedPkgs sourcePkgs =
     map collect
   $ mergeBy (\i a -> fst i `compare` fst a)
diff --git a/cabal/cabal-install/Distribution/Client/Manpage.hs b/cabal/cabal-install/Distribution/Client/Manpage.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/Manpage.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE CPP #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Client.Manpage
+-- Copyright   :  (c) Maciek Makowski 2015
+-- License     :  BSD-like
+--
+-- Maintainer  :  cabal-devel@haskell.org
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-- Functions for building the manual page.
+
+module Distribution.Client.Manpage
+  ( -- * Manual page generation
+    manpage
+  ) where
+
+import Distribution.Simple.Command
+import Distribution.Client.Setup (globalCommand)
+
+import Data.Char (toUpper)
+import Data.List (intercalate)
+
+data FileInfo = FileInfo String String -- ^ path, description
+
+-- | A list of files that should be documented in the manual page.
+files :: [FileInfo]
+files =
+  [ (FileInfo "~/.cabal/config" "The defaults that can be overridden with command-line options.")
+  , (FileInfo "~/.cabal/world"  "A list of all packages whose installation has been explicitly requested.")
+  ]
+
+-- | Produces a manual page with @troff@ markup.
+manpage :: String -> [CommandSpec a] -> String
+manpage pname commands = unlines $
+  [ ".TH " ++ map toUpper pname ++ " 1"
+  , ".SH NAME"
+  , pname ++ " \\- a system for building and packaging Haskell libraries and programs"
+  , ".SH SYNOPSIS"
+  , ".B " ++ pname
+  , ".I command"
+  , ".RI < arguments |[ options ]>..."
+  , ""
+  , "Where the"
+  , ".I commands"
+  , "are"
+  , ""
+  ] ++
+  concatMap (commandSynopsisLines pname) commands ++
+  [ ".SH DESCRIPTION"
+  , "Cabal is the standard package system for Haskell software. It helps people to configure, "
+  , "build and install Haskell software and to distribute it easily to other users and developers."
+  , ""
+  , "The command line " ++ pname ++ " tool (also referred to as cabal-install) helps with "
+  , "installing existing packages and developing new packages. "
+  , "It can be used to work with local packages or to install packages from online package archives, "
+  , "including automatically installing dependencies. By default it is configured to use Hackage, "
+  , "which is Haskell’s central package archive that contains thousands of libraries and applications "
+  , "in the Cabal package format."
+  , ".SH OPTIONS"
+  , "Global options:"
+  , ""
+  ] ++
+  optionsLines (globalCommand []) ++
+  [ ".SH COMMANDS"
+  ] ++
+  concatMap (commandDetailsLines pname) commands ++
+  [ ".SH FILES"
+  ] ++
+  concatMap fileLines files ++
+  [ ".SH BUGS"
+  , "To browse the list of known issues or report a new one please see "
+  , "https://github.com/haskell/cabal/labels/cabal-install."
+  ]
+
+commandSynopsisLines :: String -> CommandSpec action -> [String]
+commandSynopsisLines pname (CommandSpec ui _ NormalCommand) =
+  [ ".B " ++ pname ++ " " ++ (commandName ui)
+  , ".R - " ++ commandSynopsis ui
+  , ".br"
+  ]
+commandSynopsisLines _ (CommandSpec _ _ HiddenCommand) = []
+
+commandDetailsLines :: String -> CommandSpec action -> [String]
+commandDetailsLines pname (CommandSpec ui _ NormalCommand) =
+  [ ".B " ++ pname ++ " " ++ (commandName ui)
+  , ""
+  , commandUsage ui pname
+  , ""
+  ] ++
+  optional commandDescription ++
+  optional commandNotes ++
+  [ "Flags:"
+  , ".RS"
+  ] ++
+  optionsLines ui ++
+  [ ".RE"
+  , ""
+  ]
+  where
+    optional field =
+      case field ui of
+        Just text -> [text pname, ""]
+        Nothing   -> []
+commandDetailsLines _ (CommandSpec _ _ HiddenCommand) = []
+
+optionsLines :: CommandUI flags -> [String]
+optionsLines command = concatMap optionLines (concatMap optionDescr (commandOptions command ParseArgs))
+
+data ArgumentRequired = Optional | Required
+type OptionArg = (ArgumentRequired, ArgPlaceHolder)
+
+optionLines :: OptDescr flags -> [String]
+optionLines (ReqArg description (optionChars, optionStrings) placeHolder _ _) =
+  argOptionLines description optionChars optionStrings (Required, placeHolder)
+optionLines (OptArg description (optionChars, optionStrings) placeHolder _ _ _) =
+  argOptionLines description optionChars optionStrings (Optional, placeHolder)
+optionLines (BoolOpt description (trueChars, trueStrings) (falseChars, falseStrings) _ _) =
+  optionLinesIfPresent trueChars trueStrings ++
+  optionLinesIfPresent falseChars falseStrings ++
+  optionDescriptionLines description
+optionLines (ChoiceOpt options) =
+  concatMap choiceLines options
+  where
+    choiceLines (description, (optionChars, optionStrings), _, _) =
+      [ optionsLine optionChars optionStrings ] ++
+      optionDescriptionLines description
+
+argOptionLines :: String -> [Char] -> [String] -> OptionArg -> [String]
+argOptionLines description optionChars optionStrings arg =
+  [ optionsLine optionChars optionStrings
+  , optionArgLine arg
+  ] ++
+  optionDescriptionLines description
+
+optionLinesIfPresent :: [Char] -> [String] -> [String]
+optionLinesIfPresent optionChars optionStrings =
+  if null optionChars && null optionStrings then []
+  else                                           [ optionsLine optionChars optionStrings, ".br" ]
+
+optionDescriptionLines :: String -> [String]
+optionDescriptionLines description =
+  [ ".RS"
+  , description
+  , ".RE"
+  , ""
+  ]
+
+optionsLine :: [Char] -> [String] -> String
+optionsLine optionChars optionStrings =
+  intercalate ", " (shortOptions optionChars ++ longOptions optionStrings)
+
+shortOptions :: [Char] -> [String]
+shortOptions = map (\c -> "\\-" ++ [c])
+
+longOptions :: [String] -> [String]
+longOptions = map (\s -> "\\-\\-" ++ s)
+
+optionArgLine :: OptionArg -> String
+optionArgLine (Required, placeHolder) = ".I " ++ placeHolder
+optionArgLine (Optional, placeHolder) = ".RI [ " ++ placeHolder ++ " ]"
+
+fileLines :: FileInfo -> [String]
+fileLines (FileInfo path description) =
+  [ path
+  , ".RS"
+  , description
+  , ".RE"
+  , ""
+  ]
diff --git a/cabal/cabal-install/Distribution/Client/PackageHash.hs b/cabal/cabal-install/Distribution/Client/PackageHash.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/PackageHash.hs
@@ -0,0 +1,226 @@
+{-# LANGUAGE RecordWildCards, NamedFieldPuns, GeneralizedNewtypeDeriving #-}
+
+-- | Functions to calculate nix-style hashes for package ids.
+--
+-- The basic idea is simple, hash the combination of:
+--
+--   * the package tarball
+--   * the ids of all the direct dependencies
+--   * other local configuration (flags, profiling, etc)
+--
+module Distribution.Client.PackageHash (
+    -- * Calculating package hashes
+    PackageHashInputs(..),
+    PackageHashConfigInputs(..),
+    PackageSourceHash,
+    hashedInstalledPackageId,
+    hashPackageHashInputs,
+    renderPackageHashInputs,
+
+    -- * Low level hash choice
+    HashValue,
+    hashValue,
+    showHashValue,
+    readFileHashValue
+  ) where
+
+import Distribution.Package
+         ( PackageId, mkUnitId )
+import Distribution.System
+         ( Platform )
+import Distribution.PackageDescription
+         ( FlagName(..), FlagAssignment )
+import Distribution.Simple.Compiler
+         ( CompilerId, OptimisationLevel(..), DebugInfoLevel(..)
+         , ProfDetailLevel(..), showProfDetailLevel )
+import Distribution.Simple.InstallDirs
+         ( PathTemplate, fromPathTemplate )
+import Distribution.Text
+         ( display )
+import Distribution.Client.Types
+         ( InstalledPackageId )
+
+import qualified Data.ByteString.Char8      as BS
+import qualified Data.ByteString.Lazy.Char8 as LBS
+import qualified Crypto.Hash                as Hash
+import qualified Data.Byteable              as Hash
+import qualified Data.Set as Set
+import           Data.Set (Set)
+
+import Data.Maybe        (catMaybes)
+import Data.List         (sortBy, intercalate)
+import Data.Function     (on)
+import Distribution.Compat.Binary (Binary(..))
+import Control.Exception (evaluate)
+import System.IO         (withBinaryFile, IOMode(..))
+
+
+-------------------------------
+-- Calculating package hashes
+--
+
+-- | Calculate a 'InstalledPackageId' for a package using our nix-style
+-- inputs hashing method.
+--
+hashedInstalledPackageId :: PackageHashInputs -> InstalledPackageId
+hashedInstalledPackageId pkghashinputs@PackageHashInputs{pkgHashPkgId} =
+    mkUnitId $
+         display pkgHashPkgId   -- to be a bit user friendly
+      ++ "-"
+      ++ showHashValue (hashPackageHashInputs pkghashinputs)
+
+-- | All the information that contribues to a package's hash, and thus its
+-- 'InstalledPackageId'.
+--
+data PackageHashInputs = PackageHashInputs {
+       pkgHashPkgId         :: PackageId,
+       pkgHashSourceHash    :: PackageSourceHash,
+       pkgHashDirectDeps    :: Set InstalledPackageId,
+       pkgHashOtherConfig   :: PackageHashConfigInputs
+     }
+
+type PackageSourceHash = HashValue
+
+-- | Those parts of the package configuration that contribute to the
+-- package hash.
+--
+data PackageHashConfigInputs = PackageHashConfigInputs {
+       pkgHashCompilerId          :: CompilerId,
+       pkgHashPlatform            :: Platform,
+       pkgHashFlagAssignment      :: FlagAssignment, -- complete not partial
+       pkgHashConfigureScriptArgs :: [String], -- just ./configure for build-type Configure
+       pkgHashVanillaLib          :: Bool,
+       pkgHashSharedLib           :: Bool,
+       pkgHashDynExe              :: Bool,
+       pkgHashGHCiLib             :: Bool,
+       pkgHashProfLib             :: Bool,
+       pkgHashProfExe             :: Bool,
+       pkgHashProfLibDetail       :: ProfDetailLevel,
+       pkgHashProfExeDetail       :: ProfDetailLevel,
+       pkgHashCoverage            :: Bool,
+       pkgHashOptimization        :: OptimisationLevel,
+       pkgHashSplitObjs           :: Bool,
+       pkgHashStripLibs           :: Bool,
+       pkgHashStripExes           :: Bool,
+       pkgHashDebugInfo           :: DebugInfoLevel,
+       pkgHashExtraLibDirs        :: [FilePath],
+       pkgHashExtraFrameworkDirs  :: [FilePath],
+       pkgHashExtraIncludeDirs    :: [FilePath],
+       pkgHashProgPrefix          :: Maybe PathTemplate,
+       pkgHashProgSuffix          :: Maybe PathTemplate
+
+--     TODO: [required eventually] pkgHashToolsVersions     ?
+--     TODO: [required eventually] pkgHashToolsExtraOptions ?
+--     TODO: [research required] and what about docs?
+     }
+  deriving Show
+
+
+-- | Calculate the overall hash to be used for an 'InstalledPackageId'.
+--
+hashPackageHashInputs :: PackageHashInputs -> HashValue
+hashPackageHashInputs = hashValue . renderPackageHashInputs
+
+-- | Render a textual representation of the 'PackageHashInputs'.
+--
+-- The 'hashValue' of this text is the overall package hash.
+--
+renderPackageHashInputs :: PackageHashInputs -> LBS.ByteString
+renderPackageHashInputs PackageHashInputs{
+                          pkgHashPkgId,
+                          pkgHashSourceHash,
+                          pkgHashDirectDeps,
+                          pkgHashOtherConfig =
+                            PackageHashConfigInputs{..}
+                        } =
+    -- The purpose of this somewhat laboured rendering (e.g. why not just
+    -- use show?) is so that existing package hashes do not change
+    -- unnecessarily when new configuration inputs are added into the hash.
+
+    -- In particular, the assumption is that when a new configuration input
+    -- is included into the hash, that existing packages will typically get
+    -- the default value for that feature. So if we avoid adding entries with
+    -- the default value then most of the time adding new features will not
+    -- change the hashes of existing packages and so fewer packages will need
+    -- to be rebuilt. 
+
+    --TODO: [nice to have] ultimately we probably want to put this config info
+    -- into the ghc-pkg db. At that point this should probably be changed to
+    -- use the config file infrastructure so it can be read back in again.
+    LBS.pack $ unlines $ catMaybes
+      [ entry "pkgid"       display pkgHashPkgId
+      , entry "src"         showHashValue pkgHashSourceHash
+      , entry "deps"        (intercalate ", " . map display
+                                              . Set.toList) pkgHashDirectDeps
+        -- and then all the config
+      , entry "compilerid"  display pkgHashCompilerId
+      , entry "platform"    display pkgHashPlatform
+      , opt   "flags" []    showFlagAssignment pkgHashFlagAssignment
+      , opt   "configure-script" [] unwords pkgHashConfigureScriptArgs
+      , opt   "vanilla-lib" True  display pkgHashVanillaLib
+      , opt   "shared-lib"  False display pkgHashSharedLib
+      , opt   "dynamic-exe" False display pkgHashDynExe
+      , opt   "ghci-lib"    False display pkgHashGHCiLib
+      , opt   "prof-lib"    False display pkgHashProfLib
+      , opt   "prof-exe"    False display pkgHashProfExe
+      , opt   "prof-lib-detail" ProfDetailDefault showProfDetailLevel pkgHashProfLibDetail 
+      , opt   "prof-exe-detail" ProfDetailDefault showProfDetailLevel pkgHashProfExeDetail 
+      , opt   "hpc"          False display pkgHashCoverage
+      , opt   "optimisation" NormalOptimisation (show . fromEnum) pkgHashOptimization
+      , opt   "split-objs"   False display pkgHashSplitObjs
+      , opt   "stripped-lib" False display pkgHashStripLibs
+      , opt   "stripped-exe" True  display pkgHashStripExes
+      , opt   "debug-info"   NormalDebugInfo (show . fromEnum) pkgHashDebugInfo
+      , opt   "extra-lib-dirs"     [] unwords pkgHashExtraLibDirs
+      , opt   "extra-framework-dirs" [] unwords pkgHashExtraFrameworkDirs
+      , opt   "extra-include-dirs" [] unwords pkgHashExtraIncludeDirs
+      , opt   "prog-prefix" Nothing (maybe "" fromPathTemplate) pkgHashProgPrefix
+      , opt   "prog-suffix" Nothing (maybe "" fromPathTemplate) pkgHashProgSuffix
+      ]
+  where
+    entry key     format value = Just (key ++ ": " ++ format value)
+    opt   key def format value
+         | value == def = Nothing
+         | otherwise    = entry key format value
+
+    showFlagAssignment = unwords . map showEntry . sortBy (compare `on` fst)
+      where
+        showEntry (FlagName name, False) = '-' : name
+        showEntry (FlagName name, True)  = '+' : name
+
+-----------------------------------------------
+-- The specific choice of hash implementation
+--
+
+-- Is a crypto hash necessary here? One thing to consider is who controls the
+-- inputs and what's the result of a hash collision. Obviously we should not
+-- install packages we don't trust because they can run all sorts of code, but
+-- if I've checked there's no TH, no custom Setup etc, is there still a
+-- problem? If someone provided us a tarball that hashed to the same value as
+-- some other package and we installed it, we could end up re-using that
+-- installed package in place of another one we wanted. So yes, in general
+-- there is some value in preventing intentional hash collisions in installed
+-- package ids.
+
+newtype HashValue = HashValue (Hash.Digest Hash.SHA256)
+  deriving (Eq, Show)
+
+instance Binary HashValue where
+  put (HashValue digest) = put (Hash.toBytes digest)
+  get = do
+    bs <- get
+    case Hash.digestFromByteString bs of
+      Nothing     -> fail "HashValue: bad digest"
+      Just digest -> return (HashValue digest)
+
+hashValue :: LBS.ByteString -> HashValue
+hashValue = HashValue . Hash.hashlazy
+
+showHashValue :: HashValue -> String
+showHashValue (HashValue digest) = BS.unpack (Hash.digestToHexByteString digest)
+
+readFileHashValue :: FilePath -> IO HashValue
+readFileHashValue tarball =
+    withBinaryFile tarball ReadMode $ \hnd ->
+      evaluate . hashValue =<< LBS.hGetContents hnd
+
diff --git a/cabal/cabal-install/Distribution/Client/PackageIndex.hs b/cabal/cabal-install/Distribution/Client/PackageIndex.hs
--- a/cabal/cabal-install/Distribution/Client/PackageIndex.hs
+++ b/cabal/cabal-install/Distribution/Client/PackageIndex.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.PackageIndex
@@ -54,6 +55,9 @@
 import Data.Monoid (Monoid(..))
 #endif
 import Data.Maybe (isJust, fromMaybe)
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Semigroup (Semigroup((<>)))
 
 import Distribution.Package
          ( PackageName(..), PackageIdentifier(..)
@@ -78,14 +82,20 @@
   --
   (Map PackageName [pkg])
 
-  deriving (Show, Read, Functor)
+  deriving (Eq, Show, Read, Functor, Generic)
+--FIXME: the Functor instance here relies on no package id changes
 
+instance Package pkg => Semigroup (PackageIndex pkg) where
+  (<>) = merge
+
 instance Package pkg => Monoid (PackageIndex pkg) where
   mempty  = PackageIndex Map.empty
-  mappend = merge
+  mappend = (<>)
   --save one mappend with empty in the common case:
   mconcat [] = mempty
   mconcat xs = foldr1 mappend xs
+
+instance Binary pkg => Binary (PackageIndex pkg)
 
 invariant :: Package pkg => PackageIndex pkg -> Bool
 invariant (PackageIndex m) = all (uncurry goodBucket) (Map.toList m)
diff --git a/cabal/cabal-install/Distribution/Client/PackageUtils.hs b/cabal/cabal-install/Distribution/Client/PackageUtils.hs
--- a/cabal/cabal-install/Distribution/Client/PackageUtils.hs
+++ b/cabal/cabal-install/Distribution/Client/PackageUtils.hs
@@ -15,11 +15,11 @@
   ) where
 
 import Distribution.Package
-         ( packageVersion, packageName, Dependency(..) )
+         ( packageVersion, packageName, Dependency(..), PackageName(..) )
 import Distribution.PackageDescription
-         ( PackageDescription(..) )
+         ( PackageDescription(..), libName )
 import Distribution.Version
-         ( withinRange )
+         ( withinRange, isAnyVersion )
 
 -- | The list of dependencies that refer to external packages
 -- rather than internal package components.
@@ -30,5 +30,7 @@
     -- True if this dependency is an internal one (depends on a library
     -- defined in the same package).
     internal (Dependency depName versionRange) =
-            depName == packageName pkg &&
-            packageVersion pkg `withinRange` versionRange
+           (depName == packageName pkg &&
+            packageVersion pkg `withinRange` versionRange) ||
+           (unPackageName depName `elem` map libName (libraries pkg) &&
+            isAnyVersion versionRange)
diff --git a/cabal/cabal-install/Distribution/Client/ParseUtils.hs b/cabal/cabal-install/Distribution/Client/ParseUtils.hs
--- a/cabal/cabal-install/Distribution/Client/ParseUtils.hs
+++ b/cabal/cabal-install/Distribution/Client/ParseUtils.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE ExistentialQuantification, NamedFieldPuns #-}
+
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.ParseUtils
@@ -7,13 +9,41 @@
 -- Parsing utilities.
 -----------------------------------------------------------------------------
 
-module Distribution.Client.ParseUtils ( parseFields, ppFields, ppSection )
+module Distribution.Client.ParseUtils (
+
+    -- * Fields and field utilities
+    FieldDescr(..),
+    liftField,
+    liftFields,
+    filterFields,
+    mapFieldNames,
+    commandOptionToField,
+    commandOptionsToFields,
+
+    -- * Sections and utilities
+    SectionDescr(..),
+    liftSection,
+
+    -- * Parsing and printing flat config
+    parseFields,
+    ppFields,
+    ppSection,
+
+    -- * Parsing and printing config with sections and subsections
+    parseFieldsAndSections,
+    ppFieldsAndSections,
+
+    -- ** Top level of config files
+    parseConfig,
+    showConfig,
+  )
        where
 
 import Distribution.ParseUtils
-         ( FieldDescr(..), ParseResult(..), warning, lineNo )
-import qualified Distribution.ParseUtils as ParseUtils
-         ( Field(..) )
+         ( FieldDescr(..), ParseResult(..), warning, LineNo, lineNo
+         , Field(..), liftField, readFieldsFlat )
+import Distribution.Simple.Command
+         ( OptionField, viewAsFieldDescr )
 
 import Control.Monad    ( foldM )
 import Text.PrettyPrint ( (<>), (<+>), ($+$) )
@@ -21,18 +51,101 @@
 import qualified Text.PrettyPrint as Disp
          ( Doc, text, colon, vcat, empty, isEmpty, nest )
 
---FIXME: replace this with something better
-parseFields :: [FieldDescr a] -> a -> [ParseUtils.Field] -> ParseResult a
-parseFields fields = foldM setField
+
+-------------------------
+-- FieldDescr utilities
+--
+
+liftFields :: (b -> a)
+           -> (a -> b -> b)
+           -> [FieldDescr a]
+           -> [FieldDescr b]
+liftFields get set = map (liftField get set)
+
+
+-- | Given a collection of field descriptions, keep only a given list of them,
+-- identified by name.
+--
+filterFields :: [String] -> [FieldDescr a] -> [FieldDescr a]
+filterFields includeFields = filter ((`elem` includeFields) . fieldName)
+
+-- | Apply a name mangling function to the field names of all the field
+-- descriptions. The typical use case is to apply some prefix.
+--
+mapFieldNames :: (String -> String) -> [FieldDescr a] -> [FieldDescr a]
+mapFieldNames mangleName =
+    map (\descr -> descr { fieldName = mangleName (fieldName descr) })
+
+
+-- | Reuse a command line 'OptionField' as a config file 'FieldDescr'.
+--
+commandOptionToField :: OptionField a -> FieldDescr a
+commandOptionToField = viewAsFieldDescr
+
+-- | Reuse a bunch of command line 'OptionField's as config file 'FieldDescr's.
+--
+commandOptionsToFields :: [OptionField a] -> [FieldDescr a]
+commandOptionsToFields = map viewAsFieldDescr
+
+
+------------------------------------------
+-- SectionDescr definition and utilities
+--
+
+-- | The description of a section in a config file. It can contains both
+-- fields and optionally further subsections. See also 'FieldDescr'.
+--
+data SectionDescr a = forall b. SectionDescr {
+       sectionName        :: String,
+       sectionFields      :: [FieldDescr b],
+       sectionSubsections :: [SectionDescr b],
+       sectionGet         :: a -> [(String, b)],
+       sectionSet         :: LineNo -> String -> b -> a -> ParseResult a,
+       sectionEmpty       :: b
+     }
+
+-- | To help construction of config file descriptions in a modular way it is
+-- useful to define fields and sections on local types and then hoist them
+-- into the parent types when combining them in bigger descriptions.
+--
+-- This is essentially a lens operation for 'SectionDescr' to help embedding
+-- one inside another.
+--
+liftSection :: (b -> a)
+            -> (a -> b -> b)
+            -> SectionDescr a
+            -> SectionDescr b
+liftSection get' set' (SectionDescr name fields sections get set empty) =
+    let sectionGet' = get . get'
+        sectionSet' lineno param x y = do
+          x' <- set lineno param x (get' y)
+          return (set' x' y)
+     in SectionDescr name fields sections sectionGet' sectionSet' empty
+
+
+-------------------------------------
+-- Parsing and printing flat config
+--
+
+-- | Parse a bunch of semi-parsed 'Field's according to a set of field
+-- descriptions. It accumulates the result on top of a given initial value.
+--
+-- This only covers the case of flat configuration without subsections. See
+-- also 'parseFieldsAndSections'.
+--
+parseFields :: [FieldDescr a] -> a -> [Field] -> ParseResult a
+parseFields fieldDescrs =
+    foldM setField
   where
-    fieldMap = Map.fromList
-      [ (name, f) | f@(FieldDescr name _ _) <- fields ]
-    setField accum (ParseUtils.F line name value) =
+    fieldMap = Map.fromList [ (fieldName f, f) | f <- fieldDescrs ]
+
+    setField accum (F line name value) =
       case Map.lookup name fieldMap of
         Just (FieldDescr _ _ set) -> set line value accum
         Nothing -> do
           warning $ "Unrecognized field " ++ name ++ " on line " ++ show line
           return accum
+
     setField accum f = do
       warning $ "Unrecognized stanza on line " ++ show (lineNo f)
       return accum
@@ -41,8 +154,9 @@
 -- that also optionally print default values for empty fields as comments.
 --
 ppFields :: [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc
-ppFields fields def cur = Disp.vcat [ ppField name (fmap getter def) (getter cur)
-                                    | FieldDescr name getter _ <- fields]
+ppFields fields def cur =
+    Disp.vcat [ ppField name (fmap getter def) (getter cur)
+              | FieldDescr name getter _ <- fields]
 
 ppField :: String -> (Maybe Disp.Doc) -> Disp.Doc -> Disp.Doc
 ppField name mdef cur
@@ -51,6 +165,11 @@
                                 <> Disp.colon <+> def) mdef
   | otherwise        = Disp.text name <> Disp.colon <+> cur
 
+-- | Pretty print a section.
+--
+-- Since 'ppFields' does not cover subsections you can use this to add them.
+-- Or alternatively use a 'SectionDescr' and use 'ppFieldsAndSections'.
+--
 ppSection :: String -> String -> [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc
 ppSection name arg fields def cur
   | Disp.isEmpty fieldsDoc = Disp.empty
@@ -60,3 +179,101 @@
     fieldsDoc = ppFields fields def cur
     argDoc | arg == "" = Disp.empty
            | otherwise = Disp.text arg
+
+
+-----------------------------------------
+-- Parsing and printing non-flat config
+--
+
+-- | Much like 'parseFields' but it also allows subsections. The permitted
+-- subsections are given by a list of 'SectionDescr's.
+--
+parseFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a
+                       -> [Field] -> ParseResult a
+parseFieldsAndSections fieldDescrs sectionDescrs =
+    foldM setField
+  where
+    fieldMap   = Map.fromList [ (fieldName   f, f) | f <- fieldDescrs   ]
+    sectionMap = Map.fromList [ (sectionName s, s) | s <- sectionDescrs ]
+
+    setField a (F line name value) =
+      case Map.lookup name fieldMap of
+        Just (FieldDescr _ _ set) -> set line value a
+        Nothing -> do
+          warning $ "Unrecognized field '" ++ name
+                 ++ "' on line " ++ show line
+          return a
+
+    setField a (Section line name param fields) =
+      case Map.lookup name sectionMap of
+        Just (SectionDescr _ fieldDescrs' sectionDescrs' _ set sectionEmpty) -> do
+          b <- parseFieldsAndSections fieldDescrs' sectionDescrs' sectionEmpty fields
+          set line param b a
+        Nothing -> do
+          warning $ "Unrecognized section '" ++ name
+                 ++ "' on line " ++ show line
+          return a
+
+    setField accum (block@IfBlock {}) = do
+      warning $ "Unrecognized stanza on line " ++ show (lineNo block)
+      return accum
+
+-- | Much like 'ppFields' but also pretty prints any subsections. Subsection
+-- are only shown if they are non-empty.
+--
+-- Note that unlike 'ppFields', at present it does not support printing
+-- default values. If needed, adding such support would be quite reasonable.
+--
+ppFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
+ppFieldsAndSections fieldDescrs sectionDescrs val =
+    ppFields fieldDescrs Nothing val
+      $+$
+    Disp.vcat
+      [ Disp.text "" $+$ sectionDoc
+      | SectionDescr {
+          sectionName, sectionGet,
+          sectionFields, sectionSubsections
+        } <- sectionDescrs
+      , (param, x) <- sectionGet val
+      , let sectionDoc = ppSectionAndSubsections
+                           sectionName param
+                           sectionFields sectionSubsections x
+      , not (Disp.isEmpty sectionDoc)
+      ]
+
+-- | Unlike 'ppSection' which has to be called directly, this gets used via
+-- 'ppFieldsAndSections' and so does not need to be exported.
+--
+ppSectionAndSubsections :: String -> String
+                        -> [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
+ppSectionAndSubsections name arg fields sections cur
+  | Disp.isEmpty fieldsDoc = Disp.empty
+  | otherwise              = Disp.text name <+> argDoc
+                             $+$ (Disp.nest 2 fieldsDoc)
+  where
+    fieldsDoc = showConfig fields sections cur
+    argDoc | arg == "" = Disp.empty
+           | otherwise = Disp.text arg
+
+
+-----------------------------------------------
+-- Top level config file parsing and printing
+--
+
+-- | Parse a string in the config file syntax into a value, based on a
+-- description of the configuration file in terms of its fields and sections.
+--
+-- It accumulates the result on top of a given initial (typically empty) value.
+--
+parseConfig :: [FieldDescr a] -> [SectionDescr a] -> a
+            -> String -> ParseResult a
+parseConfig fieldDescrs sectionDescrs empty str =
+      parseFieldsAndSections fieldDescrs sectionDescrs empty
+  =<< readFieldsFlat str
+
+-- | Render a value in the config file syntax, based on a description of the
+-- configuration file in terms of its fields and sections.
+--
+showConfig :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
+showConfig = ppFieldsAndSections
+
diff --git a/cabal/cabal-install/Distribution/Client/PkgConfigDb.hs b/cabal/cabal-install/Distribution/Client/PkgConfigDb.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/PkgConfigDb.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE CPP #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Client.PkgConfigDb
+-- Copyright   :  (c) Iñaki García Etxebarria 2016
+-- License     :  BSD-like
+--
+-- Maintainer  :  cabal-devel@haskell.org
+-- Portability :  portable
+--
+-- Read the list of packages available to pkg-config.
+-----------------------------------------------------------------------------
+module Distribution.Client.PkgConfigDb
+    (
+     PkgConfigDb
+    , readPkgConfigDb
+    , pkgConfigDbFromList
+    , pkgConfigPkgIsPresent
+    ) where
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative ((<$>))
+#endif
+
+import Control.Exception (IOException, handle)
+import Data.Char (isSpace)
+import qualified Data.Map as M
+import Data.Version (parseVersion)
+import Text.ParserCombinators.ReadP (readP_to_S)
+
+import Distribution.Package
+    ( PackageName(..) )
+import Distribution.Verbosity
+    ( Verbosity )
+import Distribution.Version
+    ( Version, VersionRange, withinRange )
+
+import Distribution.Simple.Program
+    ( ProgramConfiguration, pkgConfigProgram, getProgramOutput,
+      requireProgram )
+import Distribution.Simple.Utils
+    ( info )
+
+-- | The list of packages installed in the system visible to
+-- @pkg-config@. This is an opaque datatype, to be constructed with
+-- `readPkgConfigDb` and queried with `pkgConfigPkgPresent`.
+data PkgConfigDb =  PkgConfigDb (M.Map PackageName (Maybe Version))
+                 -- ^ If an entry is `Nothing`, this means that the
+                 -- package seems to be present, but we don't know the
+                 -- exact version (because parsing of the version
+                 -- number failed).
+                 | NoPkgConfigDb
+                 -- ^ For when we could not run pkg-config successfully.
+     deriving (Show)
+
+-- | Query pkg-config for the list of installed packages, together
+-- with their versions. Return a `PkgConfigDb` encapsulating this
+-- information.
+readPkgConfigDb :: Verbosity -> ProgramConfiguration -> IO PkgConfigDb
+readPkgConfigDb verbosity conf = handle ioErrorHandler $ do
+  (pkgConfig, _) <- requireProgram verbosity pkgConfigProgram conf
+  pkgList <- lines <$> getProgramOutput verbosity pkgConfig ["--list-all"]
+  -- The output of @pkg-config --list-all@ also includes a description
+  -- for each package, which we do not need.
+  let pkgNames = map (takeWhile (not . isSpace)) pkgList
+  pkgVersions <- lines <$> getProgramOutput verbosity pkgConfig
+                             ("--modversion" : pkgNames)
+  (return . pkgConfigDbFromList . zip pkgNames) pkgVersions
+      where
+        -- For when pkg-config invocation fails (possibly because of a
+        -- too long command line).
+        ioErrorHandler :: IOException -> IO PkgConfigDb
+        ioErrorHandler e = do
+          info verbosity ("Failed to query pkg-config, Cabal will continue"
+                          ++ " without solving for pkg-config constraints: "
+                          ++ show e)
+          return NoPkgConfigDb
+
+-- | Create a `PkgConfigDb` from a list of @(packageName, version)@ pairs.
+pkgConfigDbFromList :: [(String, String)] -> PkgConfigDb
+pkgConfigDbFromList pairs = (PkgConfigDb . M.fromList . map convert) pairs
+    where
+      convert :: (String, String) -> (PackageName, Maybe Version)
+      convert (n,vs) = (PackageName n,
+                        case (reverse . readP_to_S parseVersion) vs of
+                          (v, "") : _ -> Just v
+                          _           -> Nothing -- Version not (fully)
+                                                 -- understood.
+                       )
+
+-- | Check whether a given package range is satisfiable in the given
+-- @pkg-config@ database.
+pkgConfigPkgIsPresent :: PkgConfigDb -> PackageName -> VersionRange -> Bool
+pkgConfigPkgIsPresent (PkgConfigDb db) pn vr =
+    case M.lookup pn db of
+      Nothing       -> False    -- Package not present in the DB.
+      Just Nothing  -> True     -- Package present, but version unknown.
+      Just (Just v) -> withinRange v vr
+-- If we could not read the pkg-config database successfully we allow
+-- the check to succeed. The plan found by the solver may fail to be
+-- executed later on, but we have no grounds for rejecting the plan at
+-- this stage.
+pkgConfigPkgIsPresent NoPkgConfigDb _ _ = True
diff --git a/cabal/cabal-install/Distribution/Client/PlanIndex.hs b/cabal/cabal-install/Distribution/Client/PlanIndex.hs
--- a/cabal/cabal-install/Distribution/Client/PlanIndex.hs
+++ b/cabal/cabal-install/Distribution/Client/PlanIndex.hs
@@ -8,25 +8,20 @@
     -- * FakeMap and related operations
     FakeMap
   , fakeDepends
-  , fakeLookupInstalledPackageId
+  , fakeLookupUnitId
     -- * Graph traversal functions
   , brokenPackages
-  , dependencyClosure
   , dependencyCycles
   , dependencyGraph
   , dependencyInconsistencies
-  , reverseDependencyClosure
-  , reverseTopologicalOrder
-  , topologicalOrder
   ) where
 
 import Prelude hiding (lookup)
 import qualified Data.Map as Map
-import qualified Data.Tree  as Tree
 import qualified Data.Graph as Graph
 import Data.Array ((!))
 import Data.Map (Map)
-import Data.Maybe (isNothing, fromMaybe, fromJust)
+import Data.Maybe (isNothing)
 import Data.Either (rights)
 
 #if !MIN_VERSION_base(4,8,0)
@@ -34,7 +29,7 @@
 #endif
 
 import Distribution.Package
-         ( PackageName(..), PackageIdentifier(..), InstalledPackageId(..)
+         ( PackageName(..), PackageIdentifier(..), UnitId(..)
          , Package(..), packageName, packageVersion
          )
 import Distribution.Version
@@ -45,59 +40,54 @@
 import Distribution.Client.Types
          ( PackageFixedDeps(..) )
 import Distribution.Simple.PackageIndex
-         ( PackageIndex, allPackages, insert, lookupInstalledPackageId )
+         ( PackageIndex, allPackages, insert, lookupUnitId )
 import Distribution.Package
-         ( HasInstalledPackageId(..), PackageId )
+         ( HasUnitId(..), PackageId )
 
 -- Note [FakeMap]
 -----------------
--- We'd like to use the PackageIndex defined in this module for
--- cabal-install's InstallPlan.  However, at the moment, this
--- data structure is indexed by InstalledPackageId, which we don't
--- know until after we've compiled a package (whereas InstallPlan
--- needs to store not-compiled packages in the index.) Eventually,
--- an InstalledPackageId will be calculatable prior to actually
--- building the package (making it something of a misnomer), but
--- at the moment, the "fake installed package ID map" is a workaround
--- to solve this problem while reusing PackageIndex.  The basic idea
--- is that, since we don't know what an InstalledPackageId is
--- beforehand, we just fake up one based on the package ID (it only
--- needs to be unique for the particular install plan), and fill
--- it out with the actual generated InstalledPackageId after the
--- package is successfully compiled.
+-- We'd like to use the PackageIndex defined in this module for cabal-install's
+-- InstallPlan.  However, at the moment, this data structure is indexed by
+-- UnitId, which we don't know until after we've compiled a package
+-- (whereas InstallPlan needs to store not-compiled packages in the index.)
+-- Eventually, an UnitId will be calculatable prior to actually building
+-- the package, but at the moment, the "fake installed package ID map" is a
+-- workaround to solve this problem while reusing PackageIndex.  The basic idea
+-- is that, since we don't know what an UnitId is beforehand, we just fake
+-- up one based on the package ID (it only needs to be unique for the particular
+-- install plan), and fill it out with the actual generated UnitId after
+-- the package is successfully compiled.
 --
--- However, there is a problem: in the index there may be
--- references using the old package ID, which are now dangling if
--- we update the InstalledPackageId.  We could map over the entire
--- index to update these pointers as well (a costly operation), but
--- instead, we've chosen to parametrize a variety of important functions
--- by a FakeMap, which records what a fake installed package ID was
--- actually resolved to post-compilation.  If we do a lookup, we first
--- check and see if it's a fake ID in the FakeMap.
+-- However, there is a problem: in the index there may be references using the
+-- old package ID, which are now dangling if we update the UnitId.  We
+-- could map over the entire index to update these pointers as well (a costly
+-- operation), but instead, we've chosen to parametrize a variety of important
+-- functions by a FakeMap, which records what a fake installed package ID was
+-- actually resolved to post-compilation.  If we do a lookup, we first check and
+-- see if it's a fake ID in the FakeMap.
 --
--- It's a bit grungy, but we expect this to only be temporary anyway.
--- (Another possible workaround would have been to *not* update
--- the installed package ID, but I decided this would be hard to
--- understand.)
+-- It's a bit grungy, but we expect this to only be temporary anyway.  (Another
+-- possible workaround would have been to *not* update the installed package ID,
+-- but I decided this would be hard to understand.)
 
--- | Map from fake installed package IDs to real ones.  See Note [FakeMap]
-type FakeMap = Map InstalledPackageId InstalledPackageId
+-- | Map from fake package keys to real ones.  See Note [FakeMap]
+type FakeMap = Map UnitId UnitId
 
 -- | Variant of `depends` which accepts a `FakeMap`
 --
 -- Analogous to `fakeInstalledDepends`. See Note [FakeMap].
-fakeDepends :: PackageFixedDeps pkg => FakeMap -> pkg -> ComponentDeps [InstalledPackageId]
+fakeDepends :: PackageFixedDeps pkg => FakeMap -> pkg -> ComponentDeps [UnitId]
 fakeDepends fakeMap = fmap (map resolveFakeId) . depends
   where
-    resolveFakeId :: InstalledPackageId -> InstalledPackageId
+    resolveFakeId :: UnitId -> UnitId
     resolveFakeId ipid = Map.findWithDefault ipid ipid fakeMap
 
---- | Variant of 'lookupInstalledPackageId' which accepts a 'FakeMap'.  See Note
+--- | Variant of 'lookupUnitId' which accepts a 'FakeMap'.  See Note
 --- [FakeMap].
-fakeLookupInstalledPackageId :: FakeMap -> PackageIndex a -> InstalledPackageId
+fakeLookupUnitId :: FakeMap -> PackageIndex a -> UnitId
                              -> Maybe a
-fakeLookupInstalledPackageId fakeMap index pkg =
-  lookupInstalledPackageId index (Map.findWithDefault pkg pkg fakeMap)
+fakeLookupUnitId fakeMap index pkg =
+  lookupUnitId index (Map.findWithDefault pkg pkg fakeMap)
 
 -- | All packages that have dependencies that are not in the index.
 --
@@ -106,13 +96,13 @@
 brokenPackages :: (PackageFixedDeps pkg)
                => FakeMap
                -> PackageIndex pkg
-               -> [(pkg, [InstalledPackageId])]
+               -> [(pkg, [UnitId])]
 brokenPackages fakeMap index =
   [ (pkg, missing)
   | pkg  <- allPackages index
   , let missing =
-          [ pkg' | pkg' <- CD.nonSetupDeps (depends pkg)
-                 , isNothing (fakeLookupInstalledPackageId fakeMap index pkg') ]
+          [ pkg' | pkg' <- CD.flatDeps (depends pkg)
+                 , isNothing (fakeLookupUnitId fakeMap index pkg') ]
   , not (null missing) ]
 
 -- | Compute all roots of the install plan, and verify that the transitive
@@ -122,7 +112,7 @@
 -- may be absent from the subplans even if the larger plan contains a dependency
 -- cycle. Such cycles may or may not be an issue; either way, we don't check
 -- for them here.
-dependencyInconsistencies :: forall pkg. (PackageFixedDeps pkg, HasInstalledPackageId pkg)
+dependencyInconsistencies :: forall pkg. (PackageFixedDeps pkg, HasUnitId pkg)
                           => FakeMap
                           -> Bool
                           -> PackageIndex pkg
@@ -141,8 +131,8 @@
 -- This is the set of all top-level library roots (taken together normally, or
 -- as singletons sets if we are considering them as independent goals), along
 -- with all setup dependencies of all packages.
-rootSets :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
-         => FakeMap -> Bool -> PackageIndex pkg -> [[InstalledPackageId]]
+rootSets :: (PackageFixedDeps pkg, HasUnitId pkg)
+         => FakeMap -> Bool -> PackageIndex pkg -> [[UnitId]]
 rootSets fakeMap indepGoals index =
        if indepGoals then map (:[]) libRoots else [libRoots]
     ++ setupRoots index
@@ -153,10 +143,10 @@
 --
 -- The library roots are the set of packages with no reverse dependencies
 -- (no reverse library dependencies but also no reverse setup dependencies).
-libraryRoots :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
-             => FakeMap -> PackageIndex pkg -> [InstalledPackageId]
+libraryRoots :: (PackageFixedDeps pkg, HasUnitId pkg)
+             => FakeMap -> PackageIndex pkg -> [UnitId]
 libraryRoots fakeMap index =
-    map (installedPackageId . toPkgId) roots
+    map toPkgId roots
   where
     (graph, toPkgId, _) = dependencyGraph fakeMap index
     indegree = Graph.indegree graph
@@ -164,7 +154,7 @@
     isRoot v = indegree ! v == 0
 
 -- | The setup dependencies of each package in the plan
-setupRoots :: PackageFixedDeps pkg => PackageIndex pkg -> [[InstalledPackageId]]
+setupRoots :: PackageFixedDeps pkg => PackageIndex pkg -> [[UnitId]]
 setupRoots = filter (not . null)
            . map (CD.setupDeps . depends)
            . allPackages
@@ -180,7 +170,7 @@
 -- distinct.
 --
 dependencyInconsistencies' :: forall pkg.
-                              (PackageFixedDeps pkg, HasInstalledPackageId pkg)
+                              (PackageFixedDeps pkg, HasUnitId pkg)
                            => FakeMap
                            -> PackageIndex pkg
                            -> [(PackageName, [(PackageIdentifier, Version)])]
@@ -195,7 +185,7 @@
     --   and each installed ID of that that package
     --     the associated package instance
     --     and a list of reverse dependencies (as source IDs)
-    inverseIndex :: Map PackageName (Map InstalledPackageId (pkg, [PackageId]))
+    inverseIndex :: Map PackageName (Map UnitId (pkg, [PackageId]))
     inverseIndex = Map.fromListWith (Map.unionWith (\(a,b) (_,b') -> (a,b++b')))
       [ (packageName dep, Map.fromList [(ipid,(dep,[packageId pkg]))])
       | -- For each package @pkg@
@@ -203,7 +193,7 @@
         -- Find out which @ipid@ @pkg@ depends on
       , ipid <- CD.nonSetupDeps (fakeDepends fakeMap pkg)
         -- And look up those @ipid@ (i.e., @ipid@ is the ID of @dep@)
-      , Just dep <- [fakeLookupInstalledPackageId fakeMap index ipid]
+      , Just dep <- [fakeLookupUnitId fakeMap index ipid]
       ]
 
     -- If, in a single install plan, we depend on more than one version of a
@@ -215,15 +205,14 @@
     reallyIsInconsistent []       = False
     reallyIsInconsistent [_p]     = False
     reallyIsInconsistent [p1, p2] =
-      let pid1 = installedPackageId p1
-          pid2 = installedPackageId p2
+      let pid1 = installedUnitId p1
+          pid2 = installedUnitId p2
       in Map.findWithDefault pid1 pid1 fakeMap `notElem` CD.nonSetupDeps (fakeDepends fakeMap p2)
       && Map.findWithDefault pid2 pid2 fakeMap `notElem` CD.nonSetupDeps (fakeDepends fakeMap p1)
     reallyIsInconsistent _ = True
 
 
 
-
 -- | Find if there are any cycles in the dependency graph. If there are no
 -- cycles the result is @[]@.
 --
@@ -231,14 +220,15 @@
 -- list of groups of packages where within each group they all depend on each
 -- other, directly or indirectly.
 --
-dependencyCycles :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
+dependencyCycles :: (PackageFixedDeps pkg, HasUnitId pkg)
                  => FakeMap
                  -> PackageIndex pkg
                  -> [[pkg]]
 dependencyCycles fakeMap index =
   [ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ]
   where
-    adjacencyList = [ (pkg, installedPackageId pkg, CD.nonSetupDeps (fakeDepends fakeMap pkg))
+    adjacencyList = [ (pkg, installedUnitId pkg,
+                            CD.flatDeps (fakeDepends fakeMap pkg))
                     | pkg <- allPackages index ]
 
 
@@ -249,11 +239,11 @@
 --
 -- * Note that if the result is @Right []@ it is because at least one of
 -- the original given 'PackageIdentifier's do not occur in the index.
-dependencyClosure :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
+dependencyClosure :: (PackageFixedDeps pkg, HasUnitId pkg)
                   => FakeMap
                   -> PackageIndex pkg
-                  -> [InstalledPackageId]
-                  -> Either [(pkg, [InstalledPackageId])]
+                  -> [UnitId]
+                  -> Either [(pkg, [UnitId])]
                             (PackageIndex pkg)
 dependencyClosure fakeMap index pkgids0 = case closure mempty [] pkgids0 of
   (completed, []) -> Right completed
@@ -261,79 +251,39 @@
  where
     closure completed failed []             = (completed, failed)
     closure completed failed (pkgid:pkgids) =
-      case fakeLookupInstalledPackageId fakeMap index pkgid of
+      case fakeLookupUnitId fakeMap index pkgid of
         Nothing   -> closure completed (pkgid:failed) pkgids
         Just pkg  ->
-          case fakeLookupInstalledPackageId fakeMap completed
-               (installedPackageId pkg) of
+          case fakeLookupUnitId fakeMap completed
+               (installedUnitId pkg) of
             Just _  -> closure completed  failed pkgids
             Nothing -> closure completed' failed pkgids'
               where completed' = insert pkg completed
                     pkgids'    = CD.nonSetupDeps (depends pkg) ++ pkgids
 
 
-topologicalOrder :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
-                 => FakeMap -> PackageIndex pkg -> [pkg]
-topologicalOrder fakeMap index = map toPkgId
-                               . Graph.topSort
-                               $ graph
-  where (graph, toPkgId, _) = dependencyGraph fakeMap index
-
-
-reverseTopologicalOrder :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
-                        => FakeMap -> PackageIndex pkg -> [pkg]
-reverseTopologicalOrder fakeMap index = map toPkgId
-                                      . Graph.topSort
-                                      . Graph.transposeG
-                                      $ graph
-  where (graph, toPkgId, _) = dependencyGraph fakeMap index
-
-
--- | Takes the transitive closure of the packages reverse dependencies.
---
--- * The given 'PackageIdentifier's must be in the index.
---
-reverseDependencyClosure :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
-                         => FakeMap
-                         -> PackageIndex pkg
-                         -> [InstalledPackageId]
-                         -> [pkg]
-reverseDependencyClosure fakeMap index =
-    map vertexToPkg
-  . concatMap Tree.flatten
-  . Graph.dfs reverseDepGraph
-  . map (fromMaybe noSuchPkgId . pkgIdToVertex)
-
-  where
-    (depGraph, vertexToPkg, pkgIdToVertex) = dependencyGraph fakeMap index
-    reverseDepGraph = Graph.transposeG depGraph
-    noSuchPkgId = error "reverseDependencyClosure: package is not in the graph"
-
-
-
 -- | Builds a graph of the package dependencies.
 --
 -- Dependencies on other packages that are not in the index are discarded.
 -- You can check if there are any such dependencies with 'brokenPackages'.
 --
-dependencyGraph :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)
+dependencyGraph :: (PackageFixedDeps pkg, HasUnitId pkg)
                 => FakeMap
                 -> PackageIndex pkg
                 -> (Graph.Graph,
-                    Graph.Vertex -> pkg,
-                    InstalledPackageId -> Maybe Graph.Vertex)
+                    Graph.Vertex -> UnitId,
+                    UnitId -> Maybe Graph.Vertex)
 dependencyGraph fakeMap index = (graph, vertexToPkg, idToVertex)
   where
     (graph, vertexToPkg', idToVertex) = Graph.graphFromEdges edges
-    vertexToPkg = fromJust
-                . (\((), key, _targets) -> lookupInstalledPackageId index key)
-                . vertexToPkg'
+    vertexToPkg v = case vertexToPkg' v of
+                      ((), pkgid, _targets) -> pkgid
 
     pkgs  = allPackages index
     edges = map edgesFrom pkgs
 
     resolve   pid = Map.findWithDefault pid pid fakeMap
     edgesFrom pkg = ( ()
-                    , resolve (installedPackageId pkg)
-                    , CD.nonSetupDeps (fakeDepends fakeMap pkg)
+                    , resolve (installedUnitId pkg)
+                    , CD.flatDeps (fakeDepends fakeMap pkg)
                     )
diff --git a/cabal/cabal-install/Distribution/Client/ProjectBuilding.hs b/cabal/cabal-install/Distribution/Client/ProjectBuilding.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/ProjectBuilding.hs
@@ -0,0 +1,1284 @@
+{-# LANGUAGE CPP, BangPatterns, RecordWildCards, NamedFieldPuns,
+             DeriveGeneric, DeriveDataTypeable, GeneralizedNewtypeDeriving,
+             ScopedTypeVariables #-}
+
+-- | 
+--
+module Distribution.Client.ProjectBuilding (
+    BuildStatus(..),
+    BuildStatusMap,
+    BuildStatusRebuild(..),
+    BuildReason(..),
+    MonitorChangedReason(..),
+    rebuildTargetsDryRun,
+    rebuildTargets
+  ) where
+
+import           Distribution.Client.PackageHash (renderPackageHashInputs)
+import           Distribution.Client.RebuildMonad
+import           Distribution.Client.ProjectConfig
+import           Distribution.Client.ProjectPlanning
+
+import           Distribution.Client.Types
+                   ( PackageLocation(..), GenericReadyPackage(..)
+                   , PackageFixedDeps(..)
+                   , InstalledPackageId, installedPackageId )
+import           Distribution.Client.InstallPlan
+                   ( GenericInstallPlan, GenericPlanPackage )
+import qualified Distribution.Client.InstallPlan as InstallPlan
+import qualified Distribution.Client.ComponentDeps as CD
+import           Distribution.Client.ComponentDeps (ComponentDeps)
+import           Distribution.Client.DistDirLayout
+import           Distribution.Client.FileMonitor
+import           Distribution.Client.SetupWrapper
+import           Distribution.Client.JobControl
+import           Distribution.Client.FetchUtils
+import           Distribution.Client.GlobalFlags (RepoContext)
+import qualified Distribution.Client.Tar as Tar
+import           Distribution.Client.Setup (filterConfigureFlags)
+import           Distribution.Client.Utils (removeExistingFile)
+
+import           Distribution.Package hiding (InstalledPackageId, installedPackageId)
+import           Distribution.InstalledPackageInfo (InstalledPackageInfo)
+import qualified Distribution.InstalledPackageInfo as Installed
+import           Distribution.Simple.Program
+import qualified Distribution.Simple.Setup as Cabal
+import           Distribution.Simple.Command (CommandUI)
+import qualified Distribution.Simple.Register as Cabal
+import qualified Distribution.Simple.InstallDirs as InstallDirs
+import           Distribution.Simple.LocalBuildInfo (ComponentName)
+import qualified Distribution.Simple.Program.HcPkg as HcPkg
+
+import           Distribution.Simple.Utils hiding (matchFileGlob)
+import           Distribution.Version
+import           Distribution.Verbosity
+import           Distribution.Text
+import           Distribution.ParseUtils ( showPWarning )
+
+import           Data.Map (Map)
+import qualified Data.Map as Map
+import           Data.Set (Set)
+import qualified Data.Set as Set
+import qualified Data.ByteString.Lazy as LBS
+
+#if !MIN_VERSION_base(4,8,0)
+import           Control.Applicative
+#endif
+import           Control.Monad
+import           Control.Exception
+import           Control.Concurrent.Async
+import           Control.Concurrent.MVar
+import           Data.List
+import           Data.Maybe
+
+import           System.FilePath
+import           System.IO
+import           System.Directory
+import           System.Exit (ExitCode)
+
+
+------------------------------------------------------------------------------
+-- * Overall building strategy.
+------------------------------------------------------------------------------
+--
+-- We start with an 'ElaboratedInstallPlan' that has already been improved by
+-- reusing packages from the store. So the remaining packages in the
+-- 'InstallPlan.Configured' state are ones we either need to build or rebuild.
+--
+-- First, we do a preliminary dry run phase where we work out which packages
+-- we really need to (re)build, and for the ones we do need to build which
+-- build phase to start at.
+
+
+------------------------------------------------------------------------------
+-- * Dry run: what bits of the 'ElaboratedInstallPlan' will we execute?
+------------------------------------------------------------------------------
+
+-- We split things like this for a couple reasons. Firstly we need to be able
+-- to do dry runs, and these need to be reasonably accurate in terms of
+-- letting users know what (and why) things are going to be (re)built.
+--
+-- Given that we need to be able to do dry runs, it would not be great if
+-- we had to repeat all the same work when we do it for real. Not only is
+-- it duplicate work, but it's duplicate code which is likely to get out of
+-- sync. So we do things only once. We preserve info we discover in the dry
+-- run phase and rely on it later when we build things for real. This also
+-- somewhat simplifies the build phase. So this way the dry run can't so
+-- easily drift out of sync with the real thing since we're relying on the
+-- info it produces.
+--
+-- An additional advantage is that it makes it easier to debug rebuild
+-- errors (ie rebuilding too much or too little), since all the rebuild
+-- decisions are made without making any state changes at the same time
+-- (that would make it harder to reproduce the problem sitation).
+
+
+-- | The 'BuildStatus' of every package in the 'ElaboratedInstallPlan'
+--
+type BuildStatusMap = Map InstalledPackageId BuildStatus
+
+-- | The build status for an individual package. That is, the state that the
+-- package is in prior to initiating a (re)build.
+--
+-- It serves two purposes:
+--
+--  * For dry-run output, it lets us explain to the user if and why a package
+--    is going to be (re)built.
+--
+--  * It tell us what step to start or resume building from, and carries
+--    enough information for us to be able to do so.
+--
+data BuildStatus =
+
+     -- | The package is in the 'InstallPlan.PreExisting' state, so does not
+     --   need building.
+     BuildStatusPreExisting
+
+     -- | The package has not been downloaded yet, so it will have to be
+     --   downloaded, unpacked and built.
+   | BuildStatusDownload
+
+     -- | The package has not been unpacked yet, so it will have to be
+     --   unpacked and built.
+   | BuildStatusUnpack FilePath
+
+     -- | The package exists in a local dir already, and just needs building
+     --   or rebuilding. So this can only happen for 'BuildInplaceOnly' style
+     --   packages.
+   | BuildStatusRebuild FilePath BuildStatusRebuild
+
+     -- | The package exists in a local dir already, and is fully up to date.
+     --   So this package can be put into the 'InstallPlan.Installed' state
+     --   and it does not need to be built.
+   | BuildStatusUpToDate (Maybe InstalledPackageInfo) BuildSuccess
+
+-- | 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
+-- phases need to be run. The 'MonitorChangedReason' gives us details like
+-- which file changed, which is mainly for high verbosity debug output.
+--
+data BuildStatusRebuild =
+
+     -- | The package configuration changed, so the configure and build phases
+     --   needs to be (re)run.
+     BuildStatusConfigure (MonitorChangedReason ())
+
+     -- | The configuration has not changed but the build phase needs to be
+     -- rerun. We record the reason the (re)build is needed.
+     --
+     -- The optional registration info here tells us if we've registered the
+     -- package already, or if we stil need to do that after building.
+     --
+   | BuildStatusBuild (Maybe (Maybe InstalledPackageInfo)) BuildReason
+
+data BuildReason =
+     -- | The depencencies of this package have been (re)built so the build
+     -- phase needs to be rerun.
+     --
+     -- The optional registration info here tells us if we've registered the
+     -- package already, or if we stil need to do that after building.
+     --
+     BuildReasonDepsRebuilt
+
+     -- | Changes in files within the package (or first run or corrupt cache) 
+   | BuildReasonFilesChanged (MonitorChangedReason ())
+
+     -- | An important special case is that no files have changed but the
+     -- set of components the /user asked to build/ has changed. We track the
+     -- set of components /we have built/, which of course only grows (until
+     -- some other change resets it).
+     --
+     -- The @Set 'ComponentName'@ is the set of components we have built
+     -- previously. When we update the monitor we take the union of the ones
+     -- we have built previously with the ones the user has asked for this
+     -- time and save those. See 'updatePackageBuildFileMonitor'.
+     --
+   | BuildReasonExtraTargets (Set ComponentName)
+
+     -- | Although we're not going to build any additional targets as a whole,
+     -- we're going to build some part of a component or run a repl or any
+     -- other action that does not result in additional persistent artifacts.
+     -- 
+   | BuildReasonEphemeralTargets
+
+-- | Which 'BuildStatus' values indicate we'll have to do some build work of
+-- some sort. In particular we use this as part of checking if any of a
+-- package's deps have changed.
+--
+buildStatusRequiresBuild :: BuildStatus -> Bool
+buildStatusRequiresBuild BuildStatusPreExisting = False
+buildStatusRequiresBuild BuildStatusUpToDate {} = False
+buildStatusRequiresBuild _                      = True
+
+-- | Do the dry run pass. This is a prerequisite of 'rebuildTargets'.
+--
+-- It gives us the 'BuildStatusMap' and also gives us an improved version of
+-- the 'ElaboratedInstallPlan' with packages switched to the
+-- 'InstallPlan.Installed' state when we find that they're already up to date.
+--
+rebuildTargetsDryRun :: DistDirLayout
+                     -> ElaboratedInstallPlan
+                     -> IO (ElaboratedInstallPlan, BuildStatusMap)
+rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do
+
+    -- Do the various checks to work out the 'BuildStatus' of each package
+    pkgsBuildStatus <- foldMInstallPlanDepOrder installPlan dryRunPkg
+
+    -- For 'BuildStatusUpToDate' packages, improve the plan by marking them as
+    -- 'InstallPlan.Installed'.
+    let installPlan' = improveInstallPlanWithUpToDatePackages
+                         installPlan pkgsBuildStatus
+
+    return (installPlan', pkgsBuildStatus)
+  where
+    dryRunPkg :: ElaboratedPlanPackage
+              -> ComponentDeps [BuildStatus]
+              -> IO BuildStatus
+    dryRunPkg (InstallPlan.PreExisting _pkg) _depsBuildStatus =
+      return BuildStatusPreExisting
+
+    dryRunPkg (InstallPlan.Configured pkg) depsBuildStatus = do
+      mloc <- checkFetched (pkgSourceLocation pkg)
+      case mloc of
+        Nothing -> return BuildStatusDownload
+
+        Just (LocalUnpackedPackage srcdir) ->
+          -- For the case of a user-managed local dir, irrespective of the
+          -- build style, we build from that directory and put build
+          -- artifacts under the shared dist directory.
+          dryRunLocalPkg pkg depsBuildStatus srcdir
+
+        -- The three tarball cases are handled the same as each other,
+        -- though depending on the build style.
+        Just (LocalTarballPackage    tarball) ->
+          dryRunTarballPkg pkg depsBuildStatus tarball
+
+        Just (RemoteTarballPackage _ tarball) ->
+          dryRunTarballPkg pkg depsBuildStatus tarball
+
+        Just (RepoTarballPackage _ _ tarball) ->
+          dryRunTarballPkg pkg depsBuildStatus tarball
+
+    dryRunPkg (InstallPlan.Processing {}) _ = unexpectedState
+    dryRunPkg (InstallPlan.Installed  {}) _ = unexpectedState
+    dryRunPkg (InstallPlan.Failed     {}) _ = unexpectedState
+
+    unexpectedState = error "rebuildTargetsDryRun: unexpected package state"
+
+    dryRunTarballPkg :: ElaboratedConfiguredPackage
+                     -> ComponentDeps [BuildStatus]
+                     -> FilePath
+                     -> IO BuildStatus
+    dryRunTarballPkg pkg depsBuildStatus tarball =
+      case pkgBuildStyle pkg of
+        BuildAndInstall  -> return (BuildStatusUnpack tarball)
+        BuildInplaceOnly -> do
+          -- TODO: [nice to have] use a proper file monitor rather than this dir exists test
+          exists <- doesDirectoryExist srcdir
+          if exists
+            then dryRunLocalPkg pkg depsBuildStatus srcdir
+            else return (BuildStatusUnpack tarball)
+      where
+        srcdir = distUnpackedSrcDirectory (packageId pkg)
+
+    dryRunLocalPkg :: ElaboratedConfiguredPackage
+                   -> ComponentDeps [BuildStatus]
+                   -> FilePath
+                   -> IO BuildStatus
+    dryRunLocalPkg pkg depsBuildStatus srcdir = do
+        -- Go and do lots of I/O, reading caches and probing files to work out
+        -- if anything has changed
+        change <- checkPackageFileMonitorChanged
+                    packageFileMonitor pkg srcdir depsBuildStatus
+        case change of
+          -- It did change, giving us 'BuildStatusRebuild' info on why
+          Left rebuild ->
+            return (BuildStatusRebuild srcdir rebuild)
+
+          -- No changes, the package is up to date. Use the saved build results.
+          Right (mipkg, buildSuccess) ->
+            return (BuildStatusUpToDate mipkg buildSuccess)
+      where
+        packageFileMonitor =
+          newPackageFileMonitor distDirLayout (packageId pkg)
+
+
+-- | A specialised traversal over the packages in an install plan.
+--
+-- The packages are visited in dependency order, starting with packages with no
+-- depencencies. The result for each package is accumulated into a 'Map' and
+-- returned as the final result. In addition, when visting a package, the
+-- visiting function is passed the results for all the immediate package
+-- depencencies. This can be used to propagate information from depencencies.
+--
+foldMInstallPlanDepOrder
+  :: forall m ipkg srcpkg iresult ifailure b.
+     (Monad m,
+      HasUnitId ipkg,   PackageFixedDeps ipkg,
+      HasUnitId srcpkg, PackageFixedDeps srcpkg)
+  => GenericInstallPlan ipkg srcpkg iresult ifailure
+  -> (GenericPlanPackage ipkg srcpkg iresult ifailure ->
+      ComponentDeps [b] -> m b)
+  -> m (Map InstalledPackageId b)
+foldMInstallPlanDepOrder plan0 visit =
+    go Map.empty (InstallPlan.reverseTopologicalOrder plan0)
+  where
+    go :: Map InstalledPackageId b
+       -> [GenericPlanPackage ipkg srcpkg iresult ifailure]
+       -> m (Map InstalledPackageId b)
+    go !results [] = return results
+
+    go !results (pkg : pkgs) = do
+      -- we go in the right order so the results map has entries for all deps
+      let depresults :: ComponentDeps [b]
+          depresults =
+            fmap (map (\ipkgid -> let Just result = Map.lookup ipkgid results
+                                   in result))
+                 (depends pkg)
+      result <- visit pkg depresults
+      let results' = Map.insert (installedPackageId pkg) result results
+      go results' pkgs
+
+improveInstallPlanWithUpToDatePackages :: ElaboratedInstallPlan
+                                       -> BuildStatusMap
+                                       -> ElaboratedInstallPlan
+improveInstallPlanWithUpToDatePackages installPlan pkgsBuildStatus =
+    replaceWithPreInstalled installPlan
+      [ (installedPackageId pkg, mipkg, buildSuccess)
+      | InstallPlan.Configured pkg
+          <- InstallPlan.reverseTopologicalOrder installPlan
+      , let ipkgid = installedPackageId pkg
+            Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus
+      , BuildStatusUpToDate mipkg buildSuccess <- [pkgBuildStatus]
+      ]
+  where
+    replaceWithPreInstalled =
+      foldl' (\plan (ipkgid, mipkg, buildSuccess) ->
+                InstallPlan.preinstalled ipkgid mipkg buildSuccess plan)
+
+
+-----------------------------
+-- Package change detection
+--
+
+-- | As part of the dry run for local unpacked packages we have to check if the
+-- package config or files have changed. That is the purpose of
+-- 'PackageFileMonitor' and 'checkPackageFileMonitorChanged'.
+--
+-- When a package is (re)built, the monitor must be updated to reflect the new
+-- state of the package. Because we sometimes build without reconfiguring the
+-- state updates are split into two, one for package config changes and one
+-- for other changes. This is the purpose of 'updatePackageConfigFileMonitor'
+-- and 'updatePackageBuildFileMonitor'.
+--
+data PackageFileMonitor = PackageFileMonitor {
+       pkgFileMonitorConfig :: FileMonitor ElaboratedConfiguredPackage (),
+       pkgFileMonitorBuild  :: FileMonitor (Set ComponentName) BuildSuccess,
+       pkgFileMonitorReg    :: FileMonitor () (Maybe InstalledPackageInfo)
+     }
+
+newPackageFileMonitor :: DistDirLayout -> PackageId -> PackageFileMonitor
+newPackageFileMonitor DistDirLayout{distPackageCacheFile} pkgid =
+    PackageFileMonitor {
+      pkgFileMonitorConfig =
+        newFileMonitor (distPackageCacheFile pkgid "config"),
+
+      pkgFileMonitorBuild =
+        FileMonitor {
+          fileMonitorCacheFile = distPackageCacheFile pkgid "build",
+          fileMonitorKeyValid  = \componentsToBuild componentsAlreadyBuilt ->
+            componentsToBuild `Set.isSubsetOf` componentsAlreadyBuilt,
+          fileMonitorCheckIfOnlyValueChanged = True
+        },
+
+      pkgFileMonitorReg =
+        newFileMonitor (distPackageCacheFile pkgid "registration")
+    }
+
+-- | Helper function for 'checkPackageFileMonitorChanged',
+-- 'updatePackageConfigFileMonitor' and 'updatePackageBuildFileMonitor'.
+--
+-- It selects the info from a 'ElaboratedConfiguredPackage' that are used by
+-- the 'FileMonitor's (in the 'PackageFileMonitor') to detect value changes.
+--
+packageFileMonitorKeyValues :: ElaboratedConfiguredPackage
+                            -> (ElaboratedConfiguredPackage, Set ComponentName)
+packageFileMonitorKeyValues pkg =
+    (pkgconfig, 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.
+    -- The ElaboratedConfiguredPackage consists mostly (but not entirely) of
+    -- information that affects the (re)configure step. But those parts that
+    -- 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
+    }
+
+    -- 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
+
+-- | Do all the checks on whether a package has changed and thus needs either
+-- rebuilding or reconfiguring and rebuilding.
+--
+checkPackageFileMonitorChanged :: PackageFileMonitor
+                               -> ElaboratedConfiguredPackage
+                               -> FilePath
+                               -> ComponentDeps [BuildStatus]
+                               -> IO (Either BuildStatusRebuild
+                                            (Maybe InstalledPackageInfo,
+                                             BuildSuccess))
+checkPackageFileMonitorChanged PackageFileMonitor{..}
+                               pkg srcdir depsBuildStatus = do
+    --TODO: [nice to have] some debug-level message about file changes, like rerunIfChanged
+    configChanged <- checkFileMonitorChanged
+                       pkgFileMonitorConfig srcdir pkgconfig
+    case configChanged of
+      MonitorChanged monitorReason ->
+          return (Left (BuildStatusConfigure monitorReason'))
+        where
+          monitorReason' = fmap (const ()) monitorReason
+
+      MonitorUnchanged () _
+          -- 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
+            regChanged <- checkFileMonitorChanged pkgFileMonitorReg srcdir ()
+            let mreg = changedToMaybe regChanged
+            return (Left (BuildStatusBuild mreg BuildReasonDepsRebuilt))
+
+        | otherwise -> do
+            buildChanged  <- checkFileMonitorChanged
+                               pkgFileMonitorBuild srcdir buildComponents
+            regChanged    <- checkFileMonitorChanged
+                               pkgFileMonitorReg srcdir ()
+            let mreg = changedToMaybe regChanged
+            case (buildChanged, regChanged) of
+              (MonitorChanged (MonitoredValueChanged prevBuildComponents), _) ->
+                  return (Left (BuildStatusBuild mreg buildReason))
+                where
+                  buildReason = BuildReasonExtraTargets prevBuildComponents
+
+              (MonitorChanged monitorReason, _) ->
+                  return (Left (BuildStatusBuild mreg buildReason))
+                where
+                  buildReason    = BuildReasonFilesChanged monitorReason'
+                  monitorReason' = fmap (const ()) monitorReason
+
+              (MonitorUnchanged _ _, MonitorChanged monitorReason) ->
+                -- this should only happen if the file is corrupt or been
+                -- manually deleted. We don't want to bother with another
+                -- phase just for this, so we'll reregister by doing a build.
+                  return (Left (BuildStatusBuild Nothing buildReason))
+                where
+                  buildReason    = BuildReasonFilesChanged monitorReason'
+                  monitorReason' = fmap (const ()) monitorReason
+
+              (MonitorUnchanged _ _, MonitorUnchanged _ _)
+                | pkgHasEphemeralBuildTargets pkg ->
+                  return (Left (BuildStatusBuild mreg buildReason))
+                where
+                  buildReason = BuildReasonEphemeralTargets
+
+              (MonitorUnchanged buildSuccess _, MonitorUnchanged mipkg _) ->
+                return (Right (mipkg, buildSuccess))
+  where
+    (pkgconfig, buildComponents) = packageFileMonitorKeyValues pkg
+    changedToMaybe (MonitorChanged     _) = Nothing
+    changedToMaybe (MonitorUnchanged x _) = Just x
+
+
+updatePackageConfigFileMonitor :: PackageFileMonitor
+                               -> FilePath
+                               -> ElaboratedConfiguredPackage
+                               -> IO ()
+updatePackageConfigFileMonitor PackageFileMonitor{pkgFileMonitorConfig}
+                               srcdir pkg =
+    updateFileMonitor pkgFileMonitorConfig srcdir Nothing
+                      [] pkgconfig ()
+  where
+    (pkgconfig, _buildComponents) = packageFileMonitorKeyValues pkg
+
+updatePackageBuildFileMonitor :: PackageFileMonitor
+                              -> FilePath
+                              -> MonitorTimestamp
+                              -> ElaboratedConfiguredPackage
+                              -> BuildStatusRebuild
+                              -> [FilePath]
+                              -> BuildSuccess
+                              -> IO ()
+updatePackageBuildFileMonitor PackageFileMonitor{pkgFileMonitorBuild}
+                              srcdir timestamp pkg pkgBuildStatus
+                              allSrcFiles buildSuccess =
+    updateFileMonitor pkgFileMonitorBuild srcdir (Just timestamp)
+                      (map monitorFileHashed allSrcFiles)
+                      buildComponents' buildSuccess
+  where
+    (_pkgconfig, buildComponents) = packageFileMonitorKeyValues pkg
+
+    -- If the only thing that's changed is that we're now building extra
+    -- components, then we can avoid later unnecessary rebuilds by saving the
+    -- total set of components that have been built, namely the union of the
+    -- existing ones plus the new ones. If files also changed this would be
+    -- the wrong thing to do. Note that we rely on the
+    -- fileMonitorCheckIfOnlyValueChanged = True mode to get this guarantee
+    -- that it's /only/ the value that changed not any files that changed.
+    buildComponents' =
+      case pkgBuildStatus of
+        BuildStatusBuild _ (BuildReasonExtraTargets prevBuildComponents)
+          -> buildComponents `Set.union` prevBuildComponents
+        _ -> buildComponents
+
+updatePackageRegFileMonitor :: PackageFileMonitor
+                            -> FilePath
+                            -> Maybe InstalledPackageInfo
+                            -> IO ()
+updatePackageRegFileMonitor PackageFileMonitor{pkgFileMonitorReg}
+                            srcdir mipkg =
+    updateFileMonitor pkgFileMonitorReg srcdir Nothing
+                      [] () mipkg
+
+invalidatePackageRegFileMonitor :: PackageFileMonitor -> IO ()
+invalidatePackageRegFileMonitor PackageFileMonitor{pkgFileMonitorReg} =
+    removeExistingFile (fileMonitorCacheFile pkgFileMonitorReg)
+
+
+------------------------------------------------------------------------------
+-- * Doing it: executing an 'ElaboratedInstallPlan'
+------------------------------------------------------------------------------
+
+
+-- | Build things for real.
+--
+-- It requires the 'BuildStatusMap' gatthered by 'rebuildTargetsDryRun'.
+--
+rebuildTargets :: Verbosity
+               -> DistDirLayout
+               -> ElaboratedInstallPlan
+               -> ElaboratedSharedConfig
+               -> BuildStatusMap
+               -> BuildTimeSettings
+               -> IO ElaboratedInstallPlan
+rebuildTargets verbosity
+               distDirLayout@DistDirLayout{..}
+               installPlan
+               sharedPackageConfig
+               pkgsBuildStatus
+               buildSettings@BuildTimeSettings{buildSettingNumJobs} = do
+
+    -- Concurrency control: create the job controller and concurrency limits
+    -- for downloading, building and installing.
+    jobControl    <- if isParallelBuild then newParallelJobControl
+                                        else newSerialJobControl
+    buildLimit    <- newJobLimit buildSettingNumJobs
+    installLock   <- newLock -- serialise installation
+    cacheLock     <- newLock -- serialise access to setup exe cache
+                             --TODO: [code cleanup] eliminate setup exe cache
+
+    createDirectoryIfMissingVerbose verbosity False distBuildRootDirectory
+    createDirectoryIfMissingVerbose verbosity False distTempDirectory
+
+    -- Before traversing the install plan, pre-emptively find all packages that
+    -- will need to be downloaded and start downloading them.
+    asyncDownloadPackages verbosity withRepoCtx
+                          installPlan pkgsBuildStatus $ \downloadMap ->
+
+      -- For each package in the plan, in dependency order, but in parallel...
+      executeInstallPlan verbosity jobControl installPlan $ \pkg ->
+        handle (return . BuildFailure) $ --TODO: review exception handling
+
+        let ipkgid = installedPackageId pkg
+            Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus in
+
+        rebuildTarget
+          verbosity
+          distDirLayout
+          buildSettings downloadMap
+          buildLimit installLock cacheLock 
+          sharedPackageConfig
+          pkg
+          pkgBuildStatus
+  where
+    isParallelBuild = buildSettingNumJobs >= 2
+    withRepoCtx     = projectConfigWithBuilderRepoContext verbosity 
+                        buildSettings
+
+-- | Given all the context and resources, (re)build an individual package.
+--
+rebuildTarget :: Verbosity
+              -> DistDirLayout
+              -> BuildTimeSettings
+              -> AsyncDownloadMap
+              -> JobLimit -> Lock -> Lock
+              -> ElaboratedSharedConfig
+              -> ElaboratedReadyPackage
+              -> BuildStatus
+              -> IO BuildResult
+rebuildTarget verbosity
+              distDirLayout@DistDirLayout{distBuildDirectory}
+              buildSettings downloadMap
+              buildLimit installLock cacheLock
+              sharedPackageConfig
+              rpkg@(ReadyPackage pkg)
+              pkgBuildStatus =
+
+    -- We rely on the 'BuildStatus' to decide which phase to start from:
+    case pkgBuildStatus of
+      BuildStatusDownload              -> downloadPhase
+      BuildStatusUnpack tarball        -> unpackTarballPhase tarball
+      BuildStatusRebuild srcdir status -> rebuildPhase status srcdir
+
+      -- TODO: perhaps re-nest the types to make these impossible
+      BuildStatusPreExisting {} -> unexpectedState
+      BuildStatusUpToDate    {} -> unexpectedState
+  where
+    unexpectedState = error "rebuildTarget: unexpected package status"
+
+    downloadPhase = do
+        downsrcloc <- waitAsyncPackageDownload verbosity downloadMap pkg
+        case downsrcloc of
+          DownloadedTarball tarball -> unpackTarballPhase tarball
+          --TODO: [nice to have] git/darcs repos etc
+
+
+    unpackTarballPhase tarball =
+      withJobLimit buildLimit $
+        withTarballLocalDirectory
+          verbosity distDirLayout tarball
+          (packageId pkg) (pkgBuildStyle pkg)
+          (pkgDescriptionOverride pkg) $
+
+          case pkgBuildStyle pkg of
+            BuildAndInstall  -> buildAndInstall
+            BuildInplaceOnly -> buildInplace buildStatus
+              where
+                buildStatus = BuildStatusConfigure MonitorFirstRun
+
+    -- Note that this really is rebuild, not build. It can only happen for
+    -- 'BuildInplaceOnly' style packages. 'BuildAndInstall' style packages
+    -- would only start from download or unpack phases.
+    --
+    rebuildPhase buildStatus srcdir =
+        assert (pkgBuildStyle pkg == BuildInplaceOnly) $
+
+        withJobLimit buildLimit $
+          buildInplace buildStatus srcdir builddir
+      where
+        builddir = distBuildDirectory (packageId pkg)
+
+    buildAndInstall srcdir builddir =
+        buildAndInstallUnpackedPackage
+          verbosity distDirLayout
+          buildSettings installLock cacheLock
+          sharedPackageConfig
+          rpkg
+          srcdir builddir'
+      where
+        builddir' = makeRelative srcdir builddir
+        --TODO: [nice to have] ^^ do this relative stuff better
+
+    buildInplace buildStatus srcdir builddir =
+        --TODO: [nice to have] use a relative build dir rather than absolute
+        buildInplaceUnpackedPackage
+          verbosity distDirLayout
+          buildSettings cacheLock
+          sharedPackageConfig
+          rpkg
+          buildStatus
+          srcdir builddir
+
+--TODO: [nice to have] do we need to use a with-style for the temp files for downloading http
+-- packages, or are we going to cache them persistently?
+
+type AsyncDownloadMap = Map (PackageLocation (Maybe FilePath))
+                            (MVar DownloadedSourceLocation)
+
+data DownloadedSourceLocation = DownloadedTarball FilePath
+                              --TODO: [nice to have] git/darcs repos etc
+
+downloadedSourceLocation :: PackageLocation FilePath
+                         -> Maybe DownloadedSourceLocation
+downloadedSourceLocation pkgloc =
+    case pkgloc of
+      RemoteTarballPackage _ tarball -> Just (DownloadedTarball tarball)
+      RepoTarballPackage _ _ tarball -> Just (DownloadedTarball tarball)
+      _                              -> Nothing
+
+-- | Given the current 'InstallPlan' and 'BuildStatusMap', select all the
+-- packages we have to download and fork off an async action to download them.
+-- We download them in dependency order so that the one's we'll need
+-- first are the ones we will start downloading first.
+--
+-- The body action is passed a map from those packages (identified by their
+-- location) to a completion var for that package. So the body action should
+-- lookup the location and use 'waitAsyncPackageDownload' to get the result.
+--
+asyncDownloadPackages :: Verbosity
+                      -> ((RepoContext -> IO ()) -> IO ())
+                      -> ElaboratedInstallPlan
+                      -> BuildStatusMap
+                      -> (AsyncDownloadMap -> IO a)
+                      -> IO a
+asyncDownloadPackages verbosity withRepoCtx installPlan pkgsBuildStatus body
+  | null pkgsToDownload = body Map.empty
+  | otherwise = do
+    --TODO: [research required] use parallel downloads? if so, use the fetchLimit
+
+    asyncDownloadVars <- mapM (\loc -> (,) loc <$> newEmptyMVar) pkgsToDownload
+
+    let downloadAction :: IO ()
+        downloadAction =
+          withRepoCtx $ \repoctx ->
+            forM_ asyncDownloadVars $ \(pkgloc, var) -> do
+              Just scrloc <- downloadedSourceLocation <$>
+                             fetchPackage verbosity repoctx pkgloc
+              putMVar var scrloc
+
+    withAsync downloadAction $ \_ ->
+      body (Map.fromList asyncDownloadVars)
+  where
+    pkgsToDownload = 
+      [ pkgSourceLocation pkg
+      | InstallPlan.Configured pkg
+         <- InstallPlan.reverseTopologicalOrder installPlan
+      , let ipkgid = installedPackageId pkg
+            Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus
+      , BuildStatusDownload <- [pkgBuildStatus]
+      ]
+
+
+-- | Check if a package needs downloading, and if so expect to find a download
+-- in progress in the given 'AsyncDownloadMap' and wait on it to finish.
+--
+waitAsyncPackageDownload :: Verbosity
+                         -> AsyncDownloadMap
+                         -> ElaboratedConfiguredPackage
+                         -> IO DownloadedSourceLocation
+waitAsyncPackageDownload verbosity downloadMap pkg =
+    case Map.lookup (pkgSourceLocation pkg) downloadMap of
+      Just hnd -> do
+        debug verbosity $
+          "Waiting for download of " ++ display (packageId pkg) ++ " to finish"
+        --TODO: [required eventually] do the exception handling on download stuff
+        takeMVar hnd
+      Nothing ->
+        fail "waitAsyncPackageDownload: package not being download"
+
+
+executeInstallPlan
+  :: forall ipkg srcpkg iresult.
+     (HasUnitId ipkg,   PackageFixedDeps ipkg,
+      HasUnitId srcpkg, PackageFixedDeps srcpkg)
+  => Verbosity
+  -> JobControl IO ( GenericReadyPackage srcpkg
+                   , GenericBuildResult ipkg iresult BuildFailure )
+  -> GenericInstallPlan ipkg srcpkg iresult BuildFailure
+  -> (    GenericReadyPackage srcpkg
+       -> IO (GenericBuildResult ipkg iresult BuildFailure))
+  -> IO (GenericInstallPlan ipkg srcpkg iresult BuildFailure)
+executeInstallPlan verbosity jobCtl plan0 installPkg =
+    tryNewTasks 0 plan0
+  where
+    tryNewTasks taskCount plan = do
+      case InstallPlan.ready plan of
+        [] | taskCount == 0 -> return plan
+           | otherwise      -> waitForTasks taskCount plan
+        pkgs                -> do
+          sequence_
+            [ do debug verbosity $ "Ready to install " ++ display pkgid
+                 spawnJob jobCtl $ do
+                   buildResult <- installPkg pkg
+                   return (pkg, buildResult)
+            | pkg <- pkgs
+            , let pkgid = packageId pkg
+            ]
+
+          let taskCount' = taskCount + length pkgs
+              plan'      = InstallPlan.processing pkgs plan
+          waitForTasks taskCount' plan'
+
+    waitForTasks taskCount plan = do
+      debug verbosity $ "Waiting for install task to finish..."
+      (pkg, buildResult) <- collectJob jobCtl
+      let taskCount' = taskCount-1
+          plan'      = updatePlan pkg buildResult plan
+      tryNewTasks taskCount' plan'
+
+    updatePlan :: GenericReadyPackage srcpkg
+               -> GenericBuildResult ipkg iresult BuildFailure
+               -> GenericInstallPlan ipkg srcpkg iresult BuildFailure
+               -> GenericInstallPlan ipkg srcpkg iresult BuildFailure
+    updatePlan pkg (BuildSuccess mipkg buildSuccess) =
+        InstallPlan.completed (installedPackageId pkg) mipkg buildSuccess
+
+    updatePlan pkg (BuildFailure buildFailure) =
+        InstallPlan.failed (installedPackageId pkg) buildFailure depsFailure
+      where
+        depsFailure = DependentFailed (packageId pkg)
+        -- So this first pkgid failed for whatever reason (buildFailure).
+        -- All the other packages that depended on this pkgid, which we
+        -- now cannot build, we mark as failing due to 'DependentFailed'
+        -- which kind of means it was not their fault.
+
+
+-- | Ensure that the package is unpacked in an appropriate directory, either
+-- a temporary one or a persistent one under the shared dist directory. 
+--
+withTarballLocalDirectory
+  :: Verbosity
+  -> DistDirLayout
+  -> FilePath
+  -> PackageId
+  -> BuildStyle
+  -> Maybe CabalFileText
+  -> (FilePath -> FilePath -> IO a)
+  -> IO a
+withTarballLocalDirectory verbosity distDirLayout@DistDirLayout{..}
+                          tarball pkgid buildstyle pkgTextOverride
+                          buildPkg  =
+      case buildstyle of
+        -- In this case we make a temp dir, unpack the tarball to there and
+        -- build and install it from that temp dir.
+        BuildAndInstall ->
+          withTempDirectory verbosity distTempDirectory
+                            (display (packageName pkgid)) $ \tmpdir -> do
+            unpackPackageTarball verbosity tarball tmpdir
+                                 pkgid pkgTextOverride
+            let srcdir   = tmpdir </> display pkgid
+                builddir = srcdir </> "dist"
+            buildPkg srcdir builddir
+
+        -- In this case we make sure the tarball has been unpacked to the
+        -- appropriate location under the shared dist dir, and then build it
+        -- inplace there
+        BuildInplaceOnly -> do
+          let srcrootdir = distUnpackedSrcRootDirectory
+              srcdir     = distUnpackedSrcDirectory pkgid
+              builddir   = distBuildDirectory pkgid
+          -- 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
+            unpackPackageTarball verbosity tarball srcrootdir
+                                 pkgid pkgTextOverride
+            moveTarballShippedDistDirectory verbosity distDirLayout
+                                            srcrootdir pkgid
+          buildPkg srcdir builddir
+
+
+unpackPackageTarball :: Verbosity -> FilePath -> FilePath
+                     -> PackageId -> Maybe CabalFileText
+                     -> IO ()
+unpackPackageTarball verbosity tarball parentdir pkgid pkgTextOverride =
+    --TODO: [nice to have] switch to tar package and catch tar exceptions
+    annotateFailure UnpackFailed $ do
+
+      -- Unpack the tarball
+      --
+      info verbosity $ "Extracting " ++ tarball ++ " to " ++ parentdir ++ "..."
+      Tar.extractTarGzFile parentdir pkgsubdir tarball
+
+      -- Sanity check
+      --
+      exists <- doesFileExist cabalFile
+      when (not exists) $
+        die $ "Package .cabal file not found in the tarball: " ++ cabalFile
+
+      -- Overwrite the .cabal with the one from the index, when appropriate
+      --
+      case pkgTextOverride of
+        Nothing     -> return ()
+        Just pkgtxt -> do
+          info verbosity $ "Updating " ++ display pkgname <.> "cabal"
+                        ++ " with the latest revision from the index."
+          writeFileAtomic cabalFile pkgtxt
+
+  where
+    cabalFile = parentdir </> pkgsubdir
+                          </> display pkgname <.> "cabal"
+    pkgsubdir = display pkgid
+    pkgname   = packageName pkgid
+
+
+-- | This is a bit of a hacky workaround. A number of packages ship
+-- pre-processed .hs files in a dist directory inside the tarball. We don't
+-- use the standard 'dist' location so unless we move this dist dir to the
+-- right place then we'll miss the shipped pre-procssed files. This hacky
+-- approach to shipped pre-procssed files ought to be replaced by a proper
+-- system, though we'll still need to keep this hack for older packages.
+--
+moveTarballShippedDistDirectory :: Verbosity -> DistDirLayout
+                                -> FilePath -> PackageId -> IO ()
+moveTarballShippedDistDirectory verbosity DistDirLayout{distBuildDirectory}
+                                parentdir pkgid = do
+    distDirExists <- doesDirectoryExist tarballDistDir
+    when distDirExists $ do
+      debug verbosity $ "Moving '" ++ tarballDistDir ++ "' to '"
+                                   ++ targetDistDir ++ "'"
+      --TODO: [nice to have] or perhaps better to copy, and use a file monitor
+      renameDirectory tarballDistDir targetDistDir
+  where
+    tarballDistDir = parentdir </> display pkgid </> "dist"
+    targetDistDir  = distBuildDirectory pkgid
+
+
+buildAndInstallUnpackedPackage :: Verbosity
+                               -> DistDirLayout
+                               -> BuildTimeSettings -> Lock -> Lock
+                               -> ElaboratedSharedConfig
+                               -> ElaboratedReadyPackage
+                               -> FilePath -> FilePath
+                               -> IO BuildResult
+buildAndInstallUnpackedPackage verbosity
+                               DistDirLayout{distTempDirectory}
+                               BuildTimeSettings {
+                                 buildSettingNumJobs,
+                                 buildSettingLogFile
+                               }
+                               installLock cacheLock
+                               pkgshared@ElaboratedSharedConfig {
+                                 pkgConfigCompiler  = compiler,
+                                 pkgConfigPlatform  = platform,
+                                 pkgConfigProgramDb = progdb
+                               }
+                               rpkg@(ReadyPackage pkg)
+                               srcdir builddir = do
+
+    createDirectoryIfMissingVerbose verbosity False builddir
+    initLogFile
+
+    --TODO: [code cleanup] deal consistently with talking to older Setup.hs versions, much like
+    --      we do for ghc, with a proper options type and rendering step
+    --      which will also let us call directly into the lib, rather than always
+    --      going via the lib's command line interface, which would also allow
+    --      passing data like installed packages, compiler, and program db for a
+    --      quicker configure.
+
+    --TODO: [required feature] docs and tests
+    --TODO: [required feature] sudo re-exec
+
+    -- Configure phase
+    when isParallelBuild $
+      notice verbosity $ "Configuring " ++ display pkgid ++ "..."
+    annotateFailure ConfigureFailed $
+      setup configureCommand configureFlags
+
+    -- Build phase
+    when isParallelBuild $
+      notice verbosity $ "Building " ++ display pkgid ++ "..."
+    annotateFailure BuildFailed $
+      setup buildCommand buildFlags
+
+    -- Install phase
+    mipkg <-
+      criticalSection installLock $ 
+      annotateFailure InstallFailed $ do
+      --TODO: [research required] do we need the installLock for copying? can we not do that in
+      -- parallel? Isn't it just registering that we have to lock for?
+
+      --TODO: [required eventually] need to lock installing this ipkig so other processes don't
+      -- stomp on our files, since we don't have ABI compat, not safe to replace
+
+      -- TODO: [required eventually] note that for nix-style installations it is not necessary to do
+      -- the 'withWin32SelfUpgrade' dance, but it would be necessary for a
+      -- shared bin dir.
+
+      -- Actual installation
+      setup Cabal.copyCommand copyFlags
+      
+      LBS.writeFile
+        (InstallDirs.prefix (pkgInstallDirs pkg) </> "cabal-hash.txt") $
+        (renderPackageHashInputs (packageHashInputs pkgshared pkg))
+
+      -- here's where we could keep track of the installed files ourselves if
+      -- we wanted by calling copy to an image dir and then we would make a
+      -- manifest and move it to its final location
+
+      --TODO: [nice to have] we should actually have it make an image in store/incomming and
+      -- 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.
+
+      -- For libraries, grab the package configuration file
+      -- and register it ourselves
+      if pkgRequiresRegistration pkg
+        then do
+          ipkg <- generateInstalledPackageInfo
+          -- We register ourselves rather than via Setup.hs. We need to
+          -- grab and modify the InstalledPackageInfo. We decide what
+          -- the installed package id is, not the build system.
+          let ipkg' = ipkg { Installed.installedUnitId = ipkgid }
+          Cabal.registerPackage verbosity compiler progdb
+                                HcPkg.MultiInstance
+                                (pkgRegisterPackageDBStack pkg) ipkg'
+          return (Just ipkg')
+        else return Nothing
+
+    --TODO: [required feature] docs and test phases
+    let docsResult  = DocsNotTried
+        testsResult = TestsNotTried
+
+    return (BuildSuccess mipkg (BuildOk docsResult testsResult))
+
+  where
+    pkgid  = packageId rpkg
+    ipkgid = installedPackageId rpkg
+
+    isParallelBuild = buildSettingNumJobs >= 2
+
+    configureCommand = Cabal.configureCommand defaultProgramConfiguration
+    configureFlags v = flip filterConfigureFlags v $
+                       setupHsConfigureFlags rpkg pkgshared
+                                             verbosity builddir
+
+    buildCommand     = Cabal.buildCommand defaultProgramConfiguration
+    buildFlags   _   = setupHsBuildFlags pkg pkgshared verbosity builddir
+
+    generateInstalledPackageInfo :: IO InstalledPackageInfo
+    generateInstalledPackageInfo =
+      withTempInstalledPackageInfoFile
+        verbosity distTempDirectory $ \pkgConfFile -> do
+        -- make absolute since setup changes dir
+        pkgConfFile' <- canonicalizePath pkgConfFile
+        let registerFlags _ = setupHsRegisterFlags
+                                pkg pkgshared
+                                verbosity builddir
+                                pkgConfFile'
+        setup Cabal.registerCommand registerFlags
+
+    copyFlags _ = setupHsCopyFlags pkg pkgshared verbosity builddir
+
+    scriptOptions = setupHsScriptOptions rpkg pkgshared srcdir builddir
+                                         isParallelBuild cacheLock
+
+    setup :: CommandUI flags -> (Version -> flags) -> IO ()
+    setup cmd flags =
+      withLogging $ \mLogFileHandle -> 
+        setupWrapper
+          verbosity
+          scriptOptions { useLoggingHandle = mLogFileHandle }
+          (Just (pkgDescription pkg))
+          cmd flags []
+
+    mlogFile =
+      case buildSettingLogFile of
+        Nothing        -> Nothing
+        Just mkLogFile -> Just (mkLogFile compiler platform pkgid ipkgid)
+
+    initLogFile =
+      case mlogFile of
+        Nothing      -> return ()
+        Just logFile -> do
+          createDirectoryIfMissing True (takeDirectory logFile)
+          exists <- doesFileExist logFile
+          when exists $ removeFile logFile
+
+    withLogging action =
+      case mlogFile of
+        Nothing      -> action Nothing
+        Just logFile -> withFile logFile AppendMode (action . Just)
+
+
+buildInplaceUnpackedPackage :: Verbosity
+                            -> DistDirLayout
+                            -> BuildTimeSettings -> Lock
+                            -> ElaboratedSharedConfig
+                            -> ElaboratedReadyPackage
+                            -> BuildStatusRebuild
+                            -> FilePath -> FilePath
+                            -> IO BuildResult
+buildInplaceUnpackedPackage verbosity
+                            distDirLayout@DistDirLayout {
+                              distTempDirectory,
+                              distPackageCacheDirectory
+                            }
+                            BuildTimeSettings{buildSettingNumJobs}
+                            cacheLock
+                            pkgshared@ElaboratedSharedConfig {
+                              pkgConfigCompiler  = compiler,
+                              pkgConfigProgramDb = progdb
+                            }
+                            rpkg@(ReadyPackage pkg)
+                            buildStatus
+                            srcdir builddir = do
+
+        --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)
+        createPackageDBIfMissing verbosity compiler progdb (pkgBuildPackageDBStack pkg)
+
+        -- Configure phase
+        --
+        whenReConfigure $ do
+          setup configureCommand configureFlags []
+          invalidatePackageRegFileMonitor packageFileMonitor
+          updatePackageConfigFileMonitor packageFileMonitor srcdir pkg
+
+        -- Build phase
+        --
+        let docsResult  = DocsNotTried
+            testsResult = TestsNotTried
+
+            buildSuccess :: BuildSuccess
+            buildSuccess = BuildOk docsResult testsResult
+
+        whenRebuild $ do
+          timestamp <- beginUpdateFileMonitor
+          setup buildCommand buildFlags buildArgs
+
+          --TODO: [required eventually] temporary hack. We need to look at the package description
+          -- and work out the exact file monitors to use
+          allSrcFiles <- filter (not . ("dist-newstyle" `isPrefixOf`))
+                     <$> getDirectoryContentsRecursive srcdir
+
+          updatePackageBuildFileMonitor packageFileMonitor srcdir timestamp
+                                        pkg buildStatus
+                                        allSrcFiles buildSuccess
+
+        mipkg <- whenReRegister $ do
+          -- Register locally
+          mipkg <- if pkgRequiresRegistration pkg
+            then do
+                ipkg <- generateInstalledPackageInfo
+                -- We register ourselves rather than via Setup.hs. We need to
+                -- grab and modify the InstalledPackageInfo. We decide what
+                -- the installed package id is, not the build system.
+                let ipkg' = ipkg { Installed.installedUnitId = ipkgid }
+                Cabal.registerPackage verbosity compiler progdb HcPkg.NoMultiInstance
+                                      (pkgRegisterPackageDBStack pkg)
+                                      ipkg'
+                return (Just ipkg')
+
+           else return Nothing
+
+          updatePackageRegFileMonitor packageFileMonitor srcdir mipkg
+          return mipkg
+
+        -- Repl phase
+        --
+        whenRepl $
+          setup replCommand replFlags replArgs
+
+        -- Haddock phase
+        whenHaddock $
+          setup haddockCommand haddockFlags []
+
+        return (BuildSuccess mipkg buildSuccess)
+
+  where
+    pkgid  = packageId rpkg
+    ipkgid = installedPackageId rpkg
+
+    isParallelBuild = buildSettingNumJobs >= 2
+
+    packageFileMonitor = newPackageFileMonitor distDirLayout pkgid
+
+    whenReConfigure action = case buildStatus of
+      BuildStatusConfigure _ -> action
+      _                      -> return ()
+
+    whenRebuild action
+      | null (pkgBuildTargets pkg) = return ()
+      | otherwise                  = action
+
+    whenRepl action
+      | isNothing (pkgReplTarget pkg) = return ()
+      | otherwise                     = action
+
+    whenHaddock action
+      | pkgBuildHaddocks pkg = action
+      | otherwise            = return ()
+
+    whenReRegister  action = case buildStatus of
+      BuildStatusConfigure          _ -> action
+      BuildStatusBuild Nothing      _ -> action
+      BuildStatusBuild (Just mipkg) _ -> return mipkg
+
+    configureCommand = Cabal.configureCommand defaultProgramConfiguration
+    configureFlags v = flip filterConfigureFlags v $
+                       setupHsConfigureFlags rpkg pkgshared
+                                             verbosity builddir
+
+    buildCommand     = Cabal.buildCommand defaultProgramConfiguration
+    buildFlags   _   = setupHsBuildFlags pkg pkgshared
+                                         verbosity builddir
+    buildArgs        = setupHsBuildArgs  pkg
+
+    replCommand      = Cabal.replCommand defaultProgramConfiguration
+    replFlags _      = setupHsReplFlags pkg pkgshared
+                                        verbosity builddir
+    replArgs         = setupHsReplArgs  pkg
+
+    haddockCommand   = Cabal.haddockCommand
+    haddockFlags _   = setupHsHaddockFlags pkg pkgshared
+                                           verbosity builddir
+
+    scriptOptions    = setupHsScriptOptions rpkg pkgshared
+                                            srcdir builddir
+                                            isParallelBuild cacheLock
+
+    setup :: CommandUI flags -> (Version -> flags) -> [String] -> IO ()
+    setup cmd flags args =
+      setupWrapper verbosity
+                   scriptOptions
+                   (Just (pkgDescription pkg))
+                   cmd flags args
+
+    generateInstalledPackageInfo :: IO InstalledPackageInfo
+    generateInstalledPackageInfo =
+      withTempInstalledPackageInfoFile
+        verbosity distTempDirectory $ \pkgConfFile -> do
+        -- make absolute since setup changes dir
+        pkgConfFile' <- canonicalizePath pkgConfFile
+        let registerFlags _ = setupHsRegisterFlags
+                                pkg pkgshared
+                                verbosity builddir
+                                pkgConfFile'
+        setup Cabal.registerCommand registerFlags []
+
+
+-- helper
+annotateFailure :: (String -> BuildFailure) -> IO a -> IO a
+annotateFailure annotate action =
+  action `catches`
+    [ Handler $ \ioe  -> handler (ioe  :: IOException)
+    , Handler $ \exit -> handler (exit :: ExitCode)
+    ]
+  where
+    handler :: Exception e => e -> IO a
+    handler = throwIO . annotate . show
+                       --TODO: [nice to have] use displayException when available
+
+
+withTempInstalledPackageInfoFile :: Verbosity -> FilePath
+                                 -> (FilePath -> IO ())
+                                 -> IO InstalledPackageInfo
+withTempInstalledPackageInfoFile verbosity tempdir action =
+    withTempFile tempdir "package-registration-" $ \pkgConfFile hnd -> do
+      hClose hnd
+      action pkgConfFile
+
+      (warns, ipkg) <- withUTF8FileContents pkgConfFile $ \pkgConfStr ->
+        case Installed.parseInstalledPackageInfo pkgConfStr of
+          Installed.ParseFailed perror -> pkgConfParseFailed perror
+          Installed.ParseOk warns ipkg -> return (warns, ipkg)
+
+      unless (null warns) $
+        warn verbosity $ unlines (map (showPWarning pkgConfFile) warns)
+
+      return ipkg
+  where
+    pkgConfParseFailed :: Installed.PError -> IO a
+    pkgConfParseFailed perror =
+      die $ "Couldn't parse the output of 'setup register --gen-pkg-config':"
+            ++ show perror
+
diff --git a/cabal/cabal-install/Distribution/Client/ProjectConfig.hs b/cabal/cabal-install/Distribution/Client/ProjectConfig.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/ProjectConfig.hs
@@ -0,0 +1,676 @@
+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, DeriveDataTypeable #-}
+
+-- | Handling project configuration.
+--
+module Distribution.Client.ProjectConfig (
+
+    -- * Types for project config
+    ProjectConfig(..),
+    ProjectConfigBuildOnly(..),
+    ProjectConfigShared(..),
+    PackageConfig(..),
+
+    -- * Project config files
+    findProjectRoot,
+    readProjectConfig,
+    writeProjectLocalExtraConfig,
+    writeProjectConfigFile,
+    commandLineFlagsToProjectConfig,
+
+    -- * Packages within projects
+    ProjectPackageLocation(..),
+    BadPackageLocation(..),
+    BadPackageLocationMatch(..),
+    findProjectPackages,
+    readSourcePackage,
+
+    -- * Resolving configuration
+    lookupLocalPackageConfig,
+    projectConfigWithBuilderRepoContext,
+    projectConfigWithSolverRepoContext,
+    SolverSettings(..),
+    resolveSolverSettings,
+    BuildTimeSettings(..),
+    resolveBuildTimeSettings,
+  ) where
+
+import Distribution.Client.ProjectConfig.Types
+import Distribution.Client.ProjectConfig.Legacy
+import Distribution.Client.RebuildMonad
+import Distribution.Client.Glob
+         ( isTrivialFilePathGlob )
+
+import Distribution.Client.Types
+import Distribution.Client.DistDirLayout
+         ( CabalDirLayout(..) )
+import Distribution.Client.GlobalFlags
+         ( RepoContext(..), withRepoContext' )
+import Distribution.Client.BuildReports.Types
+         ( ReportLevel(..) )
+import Distribution.Client.Config
+         ( loadConfig, defaultConfigFile )
+
+import Distribution.Package
+         ( PackageName, PackageId, packageId, UnitId, Dependency )
+import Distribution.System
+         ( Platform )
+import Distribution.PackageDescription
+         ( SourceRepo(..) )
+import Distribution.PackageDescription.Parse
+         ( readPackageDescription )
+import Distribution.Simple.Compiler
+         ( Compiler, compilerInfo )
+import Distribution.Simple.Setup
+         ( Flag(Flag), toFlag, flagToMaybe, flagToList
+         , fromFlag, AllowNewer(..) )
+import Distribution.Client.Setup
+         ( defaultSolver, defaultMaxBackjumps, )
+import Distribution.Simple.InstallDirs
+         ( PathTemplate, fromPathTemplate
+         , toPathTemplate, substPathTemplate, initialPathTemplateEnv )
+import Distribution.Simple.Utils
+         ( die, warn )
+import Distribution.Client.Utils
+         ( determineNumJobs )
+import Distribution.Utils.NubList
+         ( fromNubList )
+import Distribution.Verbosity
+         ( Verbosity, verbose )
+import Distribution.Text
+import Distribution.ParseUtils
+         ( ParseResult(..), locatedErrorMsg, showPWarning )
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
+import Control.Monad
+import Control.Monad.Trans (liftIO)
+import Control.Exception
+import Data.Typeable
+import Data.Maybe
+import Data.Either
+import qualified Data.Map as Map
+import Distribution.Compat.Semigroup
+import System.FilePath hiding (combine)
+import System.Directory
+import Network.URI (URI(..), URIAuth(..), parseAbsoluteURI)
+
+
+----------------------------------------
+-- Resolving configuration to settings
+--
+
+-- | Look up a 'PackageConfig' field in the 'ProjectConfig' for a specific
+-- 'PackageName'. This returns the configuration that applies to all local
+-- packages plus any package-specific configuration for this package.
+--
+lookupLocalPackageConfig :: (Semigroup a, Monoid a)
+                         => (PackageConfig -> a)
+                         -> ProjectConfig
+                         -> PackageName -> a
+lookupLocalPackageConfig field ProjectConfig {
+                           projectConfigLocalPackages,
+                           projectConfigSpecificPackage
+                         } pkgname =
+    field projectConfigLocalPackages
+ <> maybe mempty field (Map.lookup pkgname projectConfigSpecificPackage)
+
+
+-- | Use a 'RepoContext' based on the 'BuildTimeSettings'.
+--
+projectConfigWithBuilderRepoContext :: Verbosity
+                                    -> BuildTimeSettings
+                                    -> (RepoContext -> IO a) -> IO a
+projectConfigWithBuilderRepoContext verbosity BuildTimeSettings{..} =
+    withRepoContext'
+      verbosity
+      buildSettingRemoteRepos
+      buildSettingLocalRepos
+      buildSettingCacheDir
+      buildSettingHttpTransport
+      (Just buildSettingIgnoreExpiry)
+
+
+-- | Use a 'RepoContext', but only for the solver. The solver does not use the
+-- full facilities of the 'RepoContext' so we can get away with making one
+-- that doesn't have an http transport. And that avoids having to have access
+-- to the 'BuildTimeSettings'
+--
+projectConfigWithSolverRepoContext :: Verbosity
+                                   -> FilePath
+                                   -> ProjectConfigShared
+                                   -> ProjectConfigBuildOnly
+                                   -> (RepoContext -> IO a) -> IO a
+projectConfigWithSolverRepoContext verbosity downloadCacheRootDir
+                                   ProjectConfigShared{..}
+                                   ProjectConfigBuildOnly{..} =
+    withRepoContext'
+      verbosity
+      (fromNubList projectConfigRemoteRepos)
+      (fromNubList projectConfigLocalRepos)
+      downloadCacheRootDir
+      (flagToMaybe projectConfigHttpTransport)
+      (flagToMaybe projectConfigIgnoreExpiry)
+
+
+-- | Resolve the project configuration, with all its optional fields, into
+-- 'SolverSettings' with no optional fields (by applying defaults).
+--
+resolveSolverSettings :: ProjectConfigShared -> SolverSettings
+resolveSolverSettings projectConfig =
+    SolverSettings {..}
+  where
+    solverSettingRemoteRepos       = fromNubList projectConfigRemoteRepos
+    solverSettingLocalRepos        = fromNubList projectConfigLocalRepos
+    solverSettingConstraints       = projectConfigConstraints
+    solverSettingPreferences       = projectConfigPreferences
+    solverSettingFlagAssignment    = projectConfigFlagAssignment
+    solverSettingCabalVersion      = flagToMaybe projectConfigCabalVersion
+    solverSettingSolver            = fromFlag projectConfigSolver
+    solverSettingAllowNewer        = fromJust projectConfigAllowNewer
+    solverSettingMaxBackjumps      = case fromFlag projectConfigMaxBackjumps of
+                                       n | n < 0     -> Nothing
+                                         | otherwise -> Just n
+    solverSettingReorderGoals      = fromFlag projectConfigReorderGoals
+    solverSettingStrongFlags       = fromFlag projectConfigStrongFlags
+  --solverSettingIndependentGoals  = fromFlag projectConfigIndependentGoals
+  --solverSettingShadowPkgs        = fromFlag projectConfigShadowPkgs
+  --solverSettingReinstall         = fromFlag projectConfigReinstall
+  --solverSettingAvoidReinstalls   = fromFlag projectConfigAvoidReinstalls
+  --solverSettingOverrideReinstall = fromFlag projectConfigOverrideReinstall
+  --solverSettingUpgradeDeps       = fromFlag projectConfigUpgradeDeps
+
+    ProjectConfigShared {..} = defaults <> projectConfig
+
+    defaults = mempty {
+       projectConfigSolver            = Flag defaultSolver,
+       projectConfigAllowNewer        = Just AllowNewerNone,
+       projectConfigMaxBackjumps      = Flag defaultMaxBackjumps,
+       projectConfigReorderGoals      = Flag False,
+       projectConfigStrongFlags       = Flag False
+     --projectConfigIndependentGoals  = Flag False,
+     --projectConfigShadowPkgs        = Flag False,
+     --projectConfigReinstall         = Flag False,
+     --projectConfigAvoidReinstalls   = Flag False,
+     --projectConfigOverrideReinstall = Flag False,
+     --projectConfigUpgradeDeps       = Flag False
+    }
+
+
+-- | Resolve the project configuration, with all its optional fields, into
+-- 'BuildTimeSettings' with no optional fields (by applying defaults).
+--
+resolveBuildTimeSettings :: Verbosity
+                         -> CabalDirLayout
+                         -> ProjectConfigShared
+                         -> ProjectConfigBuildOnly
+                         -> ProjectConfigBuildOnly
+                         -> BuildTimeSettings
+resolveBuildTimeSettings verbosity
+                         CabalDirLayout {
+                           cabalLogsDirectory,
+                           cabalPackageCacheDirectory
+                         }
+                         ProjectConfigShared {
+                           projectConfigRemoteRepos,
+                           projectConfigLocalRepos
+                         }
+                         fromProjectFile
+                         fromCommandLine =
+    BuildTimeSettings {..}
+  where
+    buildSettingDryRun        = fromFlag    projectConfigDryRun
+    buildSettingOnlyDeps      = fromFlag    projectConfigOnlyDeps
+    buildSettingSummaryFile   = fromNubList projectConfigSummaryFile
+    --buildSettingLogFile       -- defined below, more complicated
+    --buildSettingLogVerbosity  -- defined below, more complicated
+    buildSettingBuildReports  = fromFlag    projectConfigBuildReports
+    buildSettingSymlinkBinDir = flagToList  projectConfigSymlinkBinDir
+    buildSettingOneShot       = fromFlag    projectConfigOneShot
+    buildSettingNumJobs       = determineNumJobs projectConfigNumJobs
+    buildSettingOfflineMode   = fromFlag    projectConfigOfflineMode
+    buildSettingKeepTempFiles = fromFlag    projectConfigKeepTempFiles
+    buildSettingRemoteRepos   = fromNubList projectConfigRemoteRepos
+    buildSettingLocalRepos    = fromNubList projectConfigLocalRepos
+    buildSettingCacheDir      = cabalPackageCacheDirectory
+    buildSettingHttpTransport = flagToMaybe projectConfigHttpTransport
+    buildSettingIgnoreExpiry  = fromFlag    projectConfigIgnoreExpiry
+    buildSettingReportPlanningFailure
+                              = fromFlag projectConfigReportPlanningFailure
+    buildSettingRootCmd       = flagToMaybe projectConfigRootCmd
+
+    ProjectConfigBuildOnly{..} = defaults
+                              <> fromProjectFile
+                              <> fromCommandLine
+
+    defaults = mempty {
+      projectConfigDryRun                = toFlag False,
+      projectConfigOnlyDeps              = toFlag False,
+      projectConfigBuildReports          = toFlag NoReports,
+      projectConfigReportPlanningFailure = toFlag False,
+      projectConfigOneShot               = toFlag False,
+      projectConfigOfflineMode           = toFlag False,
+      projectConfigKeepTempFiles         = toFlag False,
+      projectConfigIgnoreExpiry          = toFlag False
+    }
+
+    -- The logging logic: what log file to use and what verbosity.
+    --
+    -- If the user has specified --remote-build-reporting=detailed, use the
+    -- default log file location. If the --build-log option is set, use the
+    -- provided location. Otherwise don't use logging, unless building in
+    -- parallel (in which case the default location is used).
+    --
+    buildSettingLogFile :: Maybe (Compiler -> Platform
+                               -> PackageId -> UnitId -> FilePath)
+    buildSettingLogFile
+      | useDefaultTemplate = Just (substLogFileName defaultTemplate)
+      | otherwise          = fmap  substLogFileName givenTemplate
+
+    defaultTemplate = toPathTemplate $
+                        cabalLogsDirectory </> "$pkgid" <.> "log"
+    givenTemplate   = flagToMaybe projectConfigLogFile
+
+    useDefaultTemplate
+      | buildSettingBuildReports == DetailedReports = True
+      | isJust givenTemplate                        = False
+      | isParallelBuild                             = True
+      | otherwise                                   = False
+
+    isParallelBuild = buildSettingNumJobs >= 2
+
+    substLogFileName :: PathTemplate
+                     -> Compiler -> Platform
+                     -> PackageId -> UnitId -> FilePath
+    substLogFileName template compiler platform pkgid uid =
+        fromPathTemplate (substPathTemplate env template)
+      where
+        env = initialPathTemplateEnv
+                pkgid uid (compilerInfo compiler) platform
+
+    -- If the user has specified --remote-build-reporting=detailed or
+    -- --build-log, use more verbose logging.
+    --
+    buildSettingLogVerbosity
+      | overrideVerbosity = max verbose verbosity
+      | otherwise         = verbosity
+
+    overrideVerbosity
+      | buildSettingBuildReports == DetailedReports = True
+      | isJust givenTemplate                        = True
+      | isParallelBuild                             = False
+      | otherwise                                   = False
+
+
+---------------------------------------------
+-- Reading and writing project config files
+--
+
+-- | Find the root of this project.
+--
+-- Searches for an explicit @cabal.project@ file, in the current directory or
+-- parent directories. If no project file is found then the current dir is the
+-- project root (and the project will use an implicit config).
+--
+findProjectRoot :: IO FilePath
+findProjectRoot = do
+
+    curdir  <- getCurrentDirectory
+    homedir <- getHomeDirectory
+
+    -- Search upwards. If we get to the users home dir or the filesystem root,
+    -- then use the current dir
+    let probe dir | isDrive dir || dir == homedir
+                  = return curdir -- implicit project root
+        probe dir = do
+          exists <- doesFileExist (dir </> "cabal.project")
+          if exists
+            then return dir       -- explicit project root
+            else probe (takeDirectory dir)
+
+    probe curdir
+   --TODO: [nice to have] add compat support for old style sandboxes
+
+
+-- | Read all the config relevant for a project. This includes the project
+-- file if any, plus other global config.
+--
+readProjectConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig
+readProjectConfig verbosity projectRootDir = do
+    global <- readGlobalConfig verbosity
+    local  <- readProjectLocalConfig      verbosity projectRootDir
+    extra  <- readProjectLocalExtraConfig verbosity projectRootDir
+    return (global <> local <> extra)
+
+
+-- | Reads an explicit @cabal.project@ file in the given project root dir,
+-- or returns the default project config for an implicitly defined project.
+--
+readProjectLocalConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig
+readProjectLocalConfig verbosity projectRootDir = do
+  usesExplicitProjectRoot <- liftIO $ doesFileExist projectFile
+  if usesExplicitProjectRoot
+    then do
+      monitorFiles [monitorFileHashed projectFile]
+      liftIO readProjectFile
+    else do
+      monitorFiles [monitorNonExistentFile projectFile]
+      return defaultImplicitProjectConfig
+
+  where
+    projectFile = projectRootDir </> "cabal.project"
+    readProjectFile =
+          reportParseResult verbosity "project file" projectFile
+        . parseProjectConfig
+      =<< readFile projectFile
+
+    defaultImplicitProjectConfig :: ProjectConfig
+    defaultImplicitProjectConfig =
+      mempty {
+        -- We expect a package in the current directory.
+        projectPackages         = [ "./*.cabal" ],
+
+        -- This is to automatically pick up deps that we unpack locally.
+        projectPackagesOptional = [ "./*/*.cabal" ]
+      }
+
+
+-- | Reads a @cabal.project.extra@ file in the given project root dir,
+-- or returns empty. This file gets written by @cabal configure@, or in
+-- principle can be edited manually or by other tools.
+--
+readProjectLocalExtraConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig
+readProjectLocalExtraConfig verbosity projectRootDir = do
+    hasExtraConfig <- liftIO $ doesFileExist projectExtraConfigFile
+    if hasExtraConfig
+      then do monitorFiles [monitorFileHashed projectExtraConfigFile]
+              liftIO readProjectExtraConfigFile
+      else do monitorFiles [monitorNonExistentFile projectExtraConfigFile]
+              return mempty
+  where
+    projectExtraConfigFile = projectRootDir </> "cabal.project.extra"
+
+    readProjectExtraConfigFile =
+          reportParseResult verbosity "project extra configuration file"
+                            projectExtraConfigFile
+        . parseProjectConfig
+      =<< readFile projectExtraConfigFile
+
+
+-- | Parse the 'ProjectConfig' format.
+--
+-- For the moment this is implemented in terms of parsers for legacy
+-- configuration types, plus a conversion.
+--
+parseProjectConfig :: String -> ParseResult ProjectConfig
+parseProjectConfig content =
+    convertLegacyProjectConfig <$>
+      parseLegacyProjectConfig content
+
+
+-- | Render the 'ProjectConfig' format.
+--
+-- For the moment this is implemented in terms of a pretty printer for the
+-- legacy configuration types, plus a conversion.
+--
+showProjectConfig :: ProjectConfig -> String
+showProjectConfig =
+    showLegacyProjectConfig . convertToLegacyProjectConfig
+
+
+-- | Write a @cabal.project.extra@ file in the given project root dir.
+--
+writeProjectLocalExtraConfig :: FilePath -> ProjectConfig -> IO ()
+writeProjectLocalExtraConfig projectRootDir =
+    writeProjectConfigFile projectExtraConfigFile
+  where
+    projectExtraConfigFile = projectRootDir </> "cabal.project.extra"
+
+
+-- | Write in the @cabal.project@ format to the given file.
+--
+writeProjectConfigFile :: FilePath -> ProjectConfig -> IO ()
+writeProjectConfigFile file =
+    writeFile file . showProjectConfig
+
+
+-- | Read the user's @~/.cabal/config@ file.
+--
+readGlobalConfig :: Verbosity -> Rebuild ProjectConfig
+readGlobalConfig verbosity = do
+    config     <- liftIO (loadConfig verbosity mempty)
+    configFile <- liftIO defaultConfigFile
+    monitorFiles [monitorFileHashed configFile]
+    return (convertLegacyGlobalConfig config)
+    --TODO: do this properly, there's several possible locations
+    -- and env vars, and flags for selecting the global config
+
+
+reportParseResult :: Verbosity -> String -> FilePath -> ParseResult a -> IO a
+reportParseResult verbosity _filetype filename (ParseOk warnings x) = do
+    unless (null warnings) $
+      let msg = unlines (map (showPWarning filename) warnings)
+       in warn verbosity msg
+    return x
+reportParseResult _verbosity filetype filename (ParseFailed err) =
+    let (line, msg) = locatedErrorMsg err
+     in die $ "Error parsing " ++ filetype ++ " " ++ filename
+           ++ maybe "" (\n -> ':' : show n) line ++ ":\n" ++ msg
+
+
+---------------------------------------------
+-- Reading packages in the project
+--
+
+data ProjectPackageLocation =
+     ProjectPackageLocalCabalFile FilePath
+   | ProjectPackageLocalDirectory FilePath FilePath -- dir and .cabal file
+   | ProjectPackageLocalTarball   FilePath
+   | ProjectPackageRemoteTarball  URI
+   | ProjectPackageRemoteRepo     SourceRepo
+   | ProjectPackageNamed          Dependency
+  deriving Show
+
+
+-- | Exception thrown by 'findProjectPackages'.
+--
+newtype BadPackageLocations = BadPackageLocations [BadPackageLocation]
+  deriving (Show, Typeable)
+
+instance Exception BadPackageLocations
+--TODO: [required eventually] displayException for nice rendering
+--TODO: [nice to have] custom exception subclass for Doc rendering, colour etc
+
+data BadPackageLocation
+   = BadPackageLocationFile    BadPackageLocationMatch
+   | BadLocGlobEmptyMatch      String
+   | BadLocGlobBadMatches      String [BadPackageLocationMatch]
+   | BadLocUnexpectedUriScheme String
+   | BadLocUnrecognisedUri     String
+   | BadLocUnrecognised        String
+  deriving Show
+
+data BadPackageLocationMatch
+   = BadLocUnexpectedFile      String
+   | BadLocNonexistantFile     String
+   | BadLocDirNoCabalFile      String
+   | BadLocDirManyCabalFiles   String
+  deriving Show
+
+
+-- | Given the project config,
+--
+-- Throws 'BadPackageLocations'.
+--
+findProjectPackages :: FilePath -> ProjectConfig
+                    -> Rebuild [ProjectPackageLocation]
+findProjectPackages projectRootDir ProjectConfig{..} = do
+
+    requiredPkgs <- findPackageLocations True    projectPackages
+    optionalPkgs <- findPackageLocations False   projectPackagesOptional
+    let repoPkgs  = map ProjectPackageRemoteRepo projectPackagesRepo
+        namedPkgs = map ProjectPackageNamed      projectPackagesNamed
+
+    return (concat [requiredPkgs, optionalPkgs, repoPkgs, namedPkgs])
+  where
+    findPackageLocations required pkglocstr = do
+      (problems, pkglocs) <-
+        partitionEithers <$> mapM (findPackageLocation required) pkglocstr
+      unless (null problems) $
+        liftIO $ throwIO $ BadPackageLocations problems
+      return (concat pkglocs)
+
+
+    findPackageLocation :: Bool -> String
+                        -> Rebuild (Either BadPackageLocation
+                                          [ProjectPackageLocation])
+    findPackageLocation _required@True pkglocstr =
+      -- strategy: try first as a file:// or http(s):// URL.
+      -- then as a file glob (usually encompassing single file)
+      -- finally as a single file, for files that fail to parse as globs
+                    checkIsUriPackage pkglocstr
+      `mplusMaybeT` checkIsFileGlobPackage pkglocstr
+      `mplusMaybeT` checkIsSingleFilePackage pkglocstr
+      >>= maybe (return (Left (BadLocUnrecognised pkglocstr))) return
+
+
+    findPackageLocation _required@False pkglocstr = do
+      -- just globs for optional case
+      res <- checkIsFileGlobPackage pkglocstr
+      case res of
+        Nothing              -> return (Left (BadLocUnrecognised pkglocstr))
+        Just (Left _)        -> return (Right []) -- it's optional
+        Just (Right pkglocs) -> return (Right pkglocs)
+
+
+    checkIsUriPackage, checkIsFileGlobPackage, checkIsSingleFilePackage
+      :: String -> Rebuild (Maybe (Either BadPackageLocation
+                                         [ProjectPackageLocation]))
+    checkIsUriPackage pkglocstr =
+      return $!
+      case parseAbsoluteURI pkglocstr of
+        Just uri@URI {
+            uriScheme    = scheme,
+            uriAuthority = Just URIAuth { uriRegName = host }
+          }
+          | recognisedScheme && not (null host) ->
+            Just (Right [ProjectPackageRemoteTarball uri])
+
+          | not recognisedScheme && not (null host) ->
+            Just (Left (BadLocUnexpectedUriScheme pkglocstr))
+
+          | recognisedScheme && null host ->
+            Just (Left (BadLocUnrecognisedUri pkglocstr))
+          where
+            recognisedScheme = scheme == "http:" || scheme == "https:"
+                            || scheme == "file:"
+
+        _ -> Nothing
+
+
+    checkIsFileGlobPackage pkglocstr =
+      case simpleParse pkglocstr of
+        Nothing   -> return Nothing
+        Just glob -> liftM Just $ do
+          matches <- matchFileGlob projectRootDir glob
+          case matches of
+            [] | isJust (isTrivialFilePathGlob glob)
+               -> return (Left (BadPackageLocationFile
+                                  (BadLocNonexistantFile pkglocstr)))
+
+            [] -> return (Left (BadLocGlobEmptyMatch pkglocstr))
+
+            _  -> do
+              (failures, pkglocs) <- partitionEithers <$>
+                                     mapM checkFilePackageMatch matches
+              if null pkglocs
+                then return (Left (BadLocGlobBadMatches pkglocstr failures))
+                else return (Right pkglocs)
+
+
+    checkIsSingleFilePackage pkglocstr = do
+      let filename = projectRootDir </> pkglocstr
+      isFile <- liftIO $ doesFileExist filename
+      isDir  <- liftIO $ doesDirectoryExist filename
+      if isFile || isDir
+        then checkFilePackageMatch pkglocstr
+         >>= either (return . Just . Left  . BadPackageLocationFile)
+                    (return . Just . Right . (\x->[x]))
+        else return Nothing
+
+
+    checkFilePackageMatch :: String -> Rebuild (Either BadPackageLocationMatch
+                                                       ProjectPackageLocation)
+    checkFilePackageMatch pkglocstr = do
+      let filename = projectRootDir </> pkglocstr
+      isDir  <- liftIO $ doesDirectoryExist filename
+      parentDirExists <- case takeDirectory filename of
+                           []  -> return False
+                           dir -> liftIO $ doesDirectoryExist dir
+      case () of
+        _ | isDir
+         -> do let dirname = filename -- now we know its a dir
+                   glob    = globStarDotCabal pkglocstr
+               matches <- matchFileGlob projectRootDir glob
+               case matches of
+                 [match]
+                     -> return (Right (ProjectPackageLocalDirectory
+                                         dirname cabalFile))
+                   where
+                     cabalFile = dirname </> match
+                 []  -> return (Left (BadLocDirNoCabalFile pkglocstr))
+                 _   -> return (Left (BadLocDirManyCabalFiles pkglocstr))
+
+          | extensionIsTarGz filename
+         -> return (Right (ProjectPackageLocalTarball filename))
+
+          | takeExtension filename == ".cabal"
+         -> return (Right (ProjectPackageLocalCabalFile filename))
+
+          | parentDirExists
+         -> return (Left (BadLocNonexistantFile pkglocstr))
+
+          | otherwise
+         -> return (Left (BadLocUnexpectedFile pkglocstr))
+
+
+    extensionIsTarGz f = takeExtension f                 == ".gz"
+                      && takeExtension (dropExtension f) == ".tar"
+
+
+globStarDotCabal :: FilePath -> FilePathGlob
+globStarDotCabal =
+    FilePathGlob FilePathRelative
+  . foldr (\dirpart -> GlobDir [Literal dirpart])
+          (GlobFile [WildCard, Literal ".cabal"])
+  . splitDirectories
+
+
+--TODO: [code cleanup] use sufficiently recent transformers package
+mplusMaybeT :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
+mplusMaybeT ma mb = do
+  mx <- ma
+  case mx of
+    Nothing -> mb
+    Just x  -> return (Just x)
+
+
+readSourcePackage :: Verbosity -> ProjectPackageLocation
+                  -> Rebuild UnresolvedSourcePackage
+readSourcePackage verbosity (ProjectPackageLocalCabalFile cabalFile) =
+    readSourcePackage verbosity (ProjectPackageLocalDirectory dir cabalFile)
+  where
+    dir = takeDirectory cabalFile
+
+readSourcePackage verbosity (ProjectPackageLocalDirectory dir cabalFile) = do
+    -- no need to monitorFiles because findProjectCabalFiles did it already
+    pkgdesc <- liftIO $ readPackageDescription verbosity cabalFile
+    return SourcePackage {
+      packageInfoId        = packageId pkgdesc,
+      packageDescription   = pkgdesc,
+      packageSource        = LocalUnpackedPackage dir,
+      packageDescrOverride = Nothing
+    }
+readSourcePackage _verbosity _ =
+    fail $ "TODO: add support for fetching and reading local tarballs, remote "
+        ++ "tarballs, remote repos and passing named packages through"
diff --git a/cabal/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs b/cabal/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs
@@ -0,0 +1,1251 @@
+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, DeriveGeneric #-}
+
+-- | Project configuration, implementation in terms of legacy types.
+--
+module Distribution.Client.ProjectConfig.Legacy (
+
+    -- * Project config in terms of legacy types
+    LegacyProjectConfig,
+    parseLegacyProjectConfig,
+    showLegacyProjectConfig,
+
+    -- * Conversion to and from legacy config types
+    commandLineFlagsToProjectConfig,
+    convertLegacyProjectConfig,
+    convertLegacyGlobalConfig,
+    convertToLegacyProjectConfig,
+
+    -- * Internals, just for tests
+    parsePackageLocationTokenQ,
+    renderPackageLocationToken,
+  ) where
+
+import Distribution.Client.ProjectConfig.Types
+import Distribution.Client.Types
+         ( RemoteRepo(..), emptyRemoteRepo )
+import Distribution.Client.Dependency.Types
+         ( ConstraintSource(..) )
+import Distribution.Client.Config
+         ( SavedConfig(..), remoteRepoFields )
+
+import Distribution.Package
+import Distribution.PackageDescription
+         ( SourceRepo(..), RepoKind(..) )
+import Distribution.PackageDescription.Parse
+         ( sourceRepoFieldDescrs )
+import Distribution.Simple.Compiler
+         ( OptimisationLevel(..), DebugInfoLevel(..) )
+import Distribution.Simple.Setup
+         ( Flag(Flag), toFlag, fromFlagOrDefault
+         , ConfigFlags(..), configureOptions
+         , HaddockFlags(..), haddockOptions, defaultHaddockFlags
+         , programConfigurationPaths', splitArgs
+         , AllowNewer(..) )
+import Distribution.Client.Setup
+         ( GlobalFlags(..), globalCommand
+         , ConfigExFlags(..), configureExOptions, defaultConfigExFlags
+         , InstallFlags(..), installOptions, defaultInstallFlags )
+import Distribution.Simple.Program
+         ( programName, knownPrograms )
+import Distribution.Simple.Program.Db
+         ( ProgramDb, defaultProgramDb )
+import Distribution.Client.Targets
+         ( dispFlagAssignment, parseFlagAssignment )
+import Distribution.Simple.Utils
+         ( lowercase )
+import Distribution.Utils.NubList
+         ( toNubList, fromNubList, overNubList )
+import Distribution.Simple.LocalBuildInfo
+         ( toPathTemplate, fromPathTemplate )
+
+import Distribution.Text
+import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Compat.ReadP
+         ( ReadP, (+++), (<++) )
+import qualified Text.Read as Read
+import qualified Text.PrettyPrint as Disp
+import Text.PrettyPrint
+         ( Doc, ($+$) )
+import qualified Distribution.ParseUtils as ParseUtils (field)
+import Distribution.ParseUtils
+         ( ParseResult(..), PError(..), syntaxError, PWarning(..), warning
+         , simpleField, commaNewLineListField
+         , showToken )
+import Distribution.Client.ParseUtils
+import Distribution.Simple.Command
+         ( CommandUI(commandOptions), ShowOrParseArgs(..)
+         , OptionField, option, reqArg' )
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
+import Control.Monad
+import Data.Map (Map)
+import qualified Data.Map as Map
+import Data.Char (isSpace)
+import Distribution.Compat.Semigroup
+import GHC.Generics (Generic)
+
+------------------------------------------------------------------
+-- Representing the project config file in terms of legacy types
+--
+
+-- | We already have parsers\/pretty-printers for almost all the fields in the
+-- project config file, but they're in terms of the types used for the command
+-- line flags for Setup.hs or cabal commands. We don't want to redefine them
+-- all, at least not yet so for the moment we use the parsers at the old types
+-- and use conversion functions.
+--
+-- Ultimately if\/when this project-based approach becomes the default then we
+-- can redefine the parsers directly for the new types.
+--
+data LegacyProjectConfig = LegacyProjectConfig {
+       legacyPackages          :: [String],
+       legacyPackagesOptional  :: [String],
+       legacyPackagesRepo      :: [SourceRepo],
+       legacyPackagesNamed     :: [Dependency],
+
+       legacySharedConfig      :: LegacySharedConfig,
+       legacyLocalConfig       :: LegacyPackageConfig,
+       legacySpecificConfig    :: Map PackageName LegacyPackageConfig
+     } deriving Generic
+
+instance Monoid LegacyProjectConfig where
+  mempty  = gmempty
+  mappend = (<>)
+
+instance Semigroup LegacyProjectConfig where
+  (<>) = gmappend
+
+data LegacyPackageConfig = LegacyPackageConfig {
+       legacyConfigureFlags    :: ConfigFlags,
+       legacyInstallPkgFlags   :: InstallFlags,
+       legacyHaddockFlags      :: HaddockFlags
+     } deriving Generic
+
+instance Monoid LegacyPackageConfig where
+  mempty  = gmempty
+  mappend = (<>)
+
+instance Semigroup LegacyPackageConfig where
+  (<>) = gmappend
+
+data LegacySharedConfig = LegacySharedConfig {
+       legacyGlobalFlags       :: GlobalFlags,
+       legacyConfigureShFlags  :: ConfigFlags,
+       legacyConfigureExFlags  :: ConfigExFlags,
+       legacyInstallFlags      :: InstallFlags
+     } deriving Generic
+
+instance Monoid LegacySharedConfig where
+  mempty  = gmempty
+  mappend = (<>)
+
+instance Semigroup LegacySharedConfig where
+  (<>) = gmappend
+
+
+------------------------------------------------------------------
+-- Converting from and to the legacy types
+--
+
+-- | Convert configuration from the @cabal configure@ or @cabal build@ command
+-- line into a 'ProjectConfig' value that can combined with configuration from
+-- other sources.
+--
+-- At the moment this uses the legacy command line flag types. See
+-- 'LegacyProjectConfig' for an explanation.
+--
+commandLineFlagsToProjectConfig :: GlobalFlags
+                                -> ConfigFlags  -> ConfigExFlags
+                                -> InstallFlags -> HaddockFlags
+                                -> ProjectConfig
+commandLineFlagsToProjectConfig globalFlags configFlags configExFlags
+                                installFlags haddockFlags =
+    mempty {
+      projectConfigBuildOnly     = convertLegacyBuildOnlyFlags
+                                     globalFlags configFlags
+                                     installFlags haddockFlags,
+      projectConfigShared        = convertLegacyAllPackageFlags
+                                     globalFlags configFlags
+                                     configExFlags installFlags,
+      projectConfigLocalPackages = convertLegacyPerPackageFlags
+                                     configFlags installFlags haddockFlags
+    }
+
+
+-- | Convert from the types currently used for the user-wide @~/.cabal/config@
+-- file into the 'ProjectConfig' type.
+--
+-- Only a subset of the 'ProjectConfig' can be represented in the user-wide
+-- config. In particular it does not include packages that are in the project,
+-- and it also doesn't support package-specific configuration (only
+-- configuration that applies to all packages).
+--
+convertLegacyGlobalConfig :: SavedConfig -> ProjectConfig
+convertLegacyGlobalConfig
+    SavedConfig {
+      savedGlobalFlags       = globalFlags,
+      savedInstallFlags      = installFlags,
+      savedConfigureFlags    = configFlags,
+      savedConfigureExFlags  = configExFlags,
+      savedUserInstallDirs   = _,
+      savedGlobalInstallDirs = _,
+      savedUploadFlags       = _,
+      savedReportFlags       = _,
+      savedHaddockFlags      = haddockFlags
+    } =
+    mempty {
+      projectConfigShared        = configAllPackages,
+      projectConfigLocalPackages = configLocalPackages,
+      projectConfigBuildOnly     = configBuildOnly
+    }
+  where
+    --TODO: [code cleanup] eliminate use of default*Flags here and specify the
+    -- defaults in the various resolve functions in terms of the new types.
+    configExFlags' = defaultConfigExFlags <> configExFlags
+    installFlags'  = defaultInstallFlags  <> installFlags
+    haddockFlags'  = defaultHaddockFlags  <> haddockFlags
+
+    configLocalPackages = convertLegacyPerPackageFlags
+                            configFlags installFlags' haddockFlags'
+    configAllPackages   = convertLegacyAllPackageFlags
+                            globalFlags configFlags
+                            configExFlags' installFlags'
+    configBuildOnly     = convertLegacyBuildOnlyFlags
+                            globalFlags configFlags
+                            installFlags' haddockFlags'
+
+
+-- | Convert the project config from the legacy types to the 'ProjectConfig'
+-- and associated types. See 'LegacyProjectConfig' for an explanation of the
+-- approach.
+--
+convertLegacyProjectConfig :: LegacyProjectConfig -> ProjectConfig
+convertLegacyProjectConfig
+  LegacyProjectConfig {
+    legacyPackages,
+    legacyPackagesOptional,
+    legacyPackagesRepo,
+    legacyPackagesNamed,
+    legacySharedConfig = LegacySharedConfig globalFlags configShFlags
+                                            configExFlags installSharedFlags,
+    legacyLocalConfig  = LegacyPackageConfig configFlags installPerPkgFlags
+                                             haddockFlags,
+    legacySpecificConfig
+  } =
+
+    ProjectConfig {
+      projectPackages              = legacyPackages,
+      projectPackagesOptional      = legacyPackagesOptional,
+      projectPackagesRepo          = legacyPackagesRepo,
+      projectPackagesNamed         = legacyPackagesNamed,
+
+      projectConfigBuildOnly       = configBuildOnly,
+      projectConfigShared          = configAllPackages,
+      projectConfigLocalPackages   = configLocalPackages,
+      projectConfigSpecificPackage = fmap perPackage legacySpecificConfig
+    }
+  where
+    configLocalPackages = convertLegacyPerPackageFlags
+                            configFlags installPerPkgFlags haddockFlags
+    configAllPackages   = convertLegacyAllPackageFlags
+                            globalFlags (configFlags <> configShFlags)
+                            configExFlags installSharedFlags
+    configBuildOnly     = convertLegacyBuildOnlyFlags
+                            globalFlags configShFlags
+                            installSharedFlags haddockFlags
+
+    perPackage (LegacyPackageConfig perPkgConfigFlags perPkgInstallFlags
+                                    perPkgHaddockFlags) =
+      convertLegacyPerPackageFlags
+        perPkgConfigFlags perPkgInstallFlags perPkgHaddockFlags
+
+
+-- | Helper used by other conversion functions that returns the
+-- 'ProjectConfigShared' subset of the 'ProjectConfig'.
+--
+convertLegacyAllPackageFlags :: GlobalFlags -> ConfigFlags
+                             -> ConfigExFlags -> InstallFlags
+                             -> ProjectConfigShared
+convertLegacyAllPackageFlags globalFlags configFlags
+                             configExFlags installFlags =
+    ProjectConfigShared{..}
+  where
+    GlobalFlags {
+      globalConfigFile        = _, -- TODO: [required feature]
+      globalSandboxConfigFile = _, -- ??
+      globalRemoteRepos       = projectConfigRemoteRepos,
+      globalLocalRepos        = projectConfigLocalRepos
+    } = globalFlags
+
+    ConfigFlags {
+      configProgramPaths,
+      configProgramArgs,
+      configProgramPathExtra    = projectConfigProgramPathExtra,
+      configHcFlavor            = projectConfigHcFlavor,
+      configHcPath              = projectConfigHcPath,
+      configHcPkg               = projectConfigHcPkg,
+    --configInstallDirs         = projectConfigInstallDirs,
+    --configUserInstall         = projectConfigUserInstall,
+    --configPackageDBs          = projectConfigPackageDBs,
+      configConfigurationsFlags = projectConfigFlagAssignment,
+      configAllowNewer          = projectConfigAllowNewer
+    } = configFlags
+    projectConfigProgramPaths   = Map.fromList configProgramPaths
+    projectConfigProgramArgs    = Map.fromList configProgramArgs
+
+    ConfigExFlags {
+      configCabalVersion        = projectConfigCabalVersion,
+      configExConstraints       = projectConfigConstraints,
+      configPreferences         = projectConfigPreferences,
+      configSolver              = projectConfigSolver
+    } = configExFlags
+
+    InstallFlags {
+      installHaddockIndex       = projectConfigHaddockIndex,
+    --installReinstall          = projectConfigReinstall,
+    --installAvoidReinstalls    = projectConfigAvoidReinstalls,
+    --installOverrideReinstall  = projectConfigOverrideReinstall,
+      installMaxBackjumps       = projectConfigMaxBackjumps,
+    --installUpgradeDeps        = projectConfigUpgradeDeps,
+      installReorderGoals       = projectConfigReorderGoals,
+    --installIndependentGoals   = projectConfigIndependentGoals,
+    --installShadowPkgs         = projectConfigShadowPkgs,
+      installStrongFlags        = projectConfigStrongFlags
+    } = installFlags
+
+
+
+-- | Helper used by other conversion functions that returns the
+-- 'PackageConfig' subset of the 'ProjectConfig'.
+--
+convertLegacyPerPackageFlags :: ConfigFlags -> InstallFlags -> HaddockFlags
+                             -> PackageConfig
+convertLegacyPerPackageFlags configFlags installFlags haddockFlags =
+    PackageConfig{..}
+  where
+    ConfigFlags {
+      configVanillaLib          = packageConfigVanillaLib,
+      configProfLib             = packageConfigProfLib,
+      configSharedLib           = packageConfigSharedLib,
+      configDynExe              = packageConfigDynExe,
+      configProfExe             = packageConfigProfExe,
+      configProf                = packageConfigProf,
+      configProfDetail          = packageConfigProfDetail,
+      configProfLibDetail       = packageConfigProfLibDetail,
+      configConfigureArgs       = packageConfigConfigureArgs,
+      configOptimization        = packageConfigOptimization,
+      configProgPrefix          = packageConfigProgPrefix,
+      configProgSuffix          = packageConfigProgSuffix,
+      configGHCiLib             = packageConfigGHCiLib,
+      configSplitObjs           = packageConfigSplitObjs,
+      configStripExes           = packageConfigStripExes,
+      configStripLibs           = packageConfigStripLibs,
+      configExtraLibDirs        = packageConfigExtraLibDirs,
+      configExtraFrameworkDirs  = packageConfigExtraFrameworkDirs,
+      configExtraIncludeDirs    = packageConfigExtraIncludeDirs,
+      configConfigurationsFlags = _projectConfigFlagAssignment, --TODO: should be per pkg
+      configTests               = packageConfigTests,
+      configBenchmarks          = packageConfigBenchmarks,
+      configCoverage            = coverage,
+      configLibCoverage         = libcoverage, --deprecated
+      configDebugInfo           = packageConfigDebugInfo,
+      configRelocatable         = packageConfigRelocatable
+    } = configFlags
+
+    packageConfigCoverage       = coverage <> libcoverage
+    --TODO: defer this merging to the resolve phase
+
+    InstallFlags {
+      installDocumentation      = packageConfigDocumentation,
+      installRunTests           = packageConfigRunTests
+    } = installFlags
+
+    HaddockFlags {
+      haddockHoogle             = packageConfigHaddockHoogle,
+      haddockHtml               = packageConfigHaddockHtml,
+      haddockHtmlLocation       = packageConfigHaddockHtmlLocation,
+      haddockExecutables        = packageConfigHaddockExecutables,
+      haddockTestSuites         = packageConfigHaddockTestSuites,
+      haddockBenchmarks         = packageConfigHaddockBenchmarks,
+      haddockInternal           = packageConfigHaddockInternal,
+      haddockCss                = packageConfigHaddockCss,
+      haddockHscolour           = packageConfigHaddockHscolour,
+      haddockHscolourCss        = packageConfigHaddockHscolourCss,
+      haddockContents           = packageConfigHaddockContents
+    } = haddockFlags
+
+
+
+-- | Helper used by other conversion functions that returns the
+-- 'ProjectConfigBuildOnly' subset of the 'ProjectConfig'.
+--
+convertLegacyBuildOnlyFlags :: GlobalFlags -> ConfigFlags
+                            -> InstallFlags -> HaddockFlags
+                            -> ProjectConfigBuildOnly
+convertLegacyBuildOnlyFlags globalFlags configFlags
+                              installFlags haddockFlags =
+    ProjectConfigBuildOnly{..}
+  where
+    GlobalFlags {
+      globalCacheDir          = projectConfigCacheDir,
+      globalLogsDir           = projectConfigLogsDir,
+      globalWorldFile         = projectConfigWorldFile,
+      globalHttpTransport     = projectConfigHttpTransport,
+      globalIgnoreExpiry      = projectConfigIgnoreExpiry
+    } = globalFlags
+
+    ConfigFlags {
+      configVerbosity           = projectConfigVerbosity
+    } = configFlags
+
+    InstallFlags {
+      installDryRun             = projectConfigDryRun,
+      installOnly               = _,
+      installOnlyDeps           = projectConfigOnlyDeps,
+      installRootCmd            = projectConfigRootCmd,
+      installSummaryFile        = projectConfigSummaryFile,
+      installLogFile            = projectConfigLogFile,
+      installBuildReports       = projectConfigBuildReports,
+      installReportPlanningFailure = projectConfigReportPlanningFailure,
+      installSymlinkBinDir      = projectConfigSymlinkBinDir,
+      installOneShot            = projectConfigOneShot,
+      installNumJobs            = projectConfigNumJobs,
+      installOfflineMode        = projectConfigOfflineMode
+    } = installFlags
+
+    HaddockFlags {
+      haddockKeepTempFiles      = projectConfigKeepTempFiles --TODO: this ought to live elsewhere
+    } = haddockFlags
+
+
+convertToLegacyProjectConfig :: ProjectConfig -> LegacyProjectConfig
+convertToLegacyProjectConfig
+    projectConfig@ProjectConfig {
+      projectPackages,
+      projectPackagesOptional,
+      projectPackagesRepo,
+      projectPackagesNamed,
+      projectConfigLocalPackages,
+      projectConfigSpecificPackage
+    } =
+    LegacyProjectConfig {
+      legacyPackages         = projectPackages,
+      legacyPackagesOptional = projectPackagesOptional,
+      legacyPackagesRepo     = projectPackagesRepo,
+      legacyPackagesNamed    = projectPackagesNamed,
+      legacySharedConfig     = convertToLegacySharedConfig projectConfig,
+      legacyLocalConfig      = convertToLegacyAllPackageConfig projectConfig
+                            <> convertToLegacyPerPackageConfig
+                                 projectConfigLocalPackages,
+      legacySpecificConfig   = fmap convertToLegacyPerPackageConfig
+                                    projectConfigSpecificPackage
+    }
+
+convertToLegacySharedConfig :: ProjectConfig -> LegacySharedConfig
+convertToLegacySharedConfig
+    ProjectConfig {
+      projectConfigBuildOnly     = ProjectConfigBuildOnly {..},
+      projectConfigShared        = ProjectConfigShared {..}
+    } =
+
+    LegacySharedConfig {
+      legacyGlobalFlags      = globalFlags,
+      legacyConfigureShFlags = configFlags,
+      legacyConfigureExFlags = configExFlags,
+      legacyInstallFlags     = installFlags
+    }
+  where
+    globalFlags = GlobalFlags {
+      globalVersion           = mempty,
+      globalNumericVersion    = mempty,
+      globalConfigFile        = mempty,
+      globalSandboxConfigFile = mempty,
+      globalConstraintsFile   = mempty,
+      globalRemoteRepos       = projectConfigRemoteRepos,
+      globalCacheDir          = projectConfigCacheDir,
+      globalLocalRepos        = projectConfigLocalRepos,
+      globalLogsDir           = projectConfigLogsDir,
+      globalWorldFile         = projectConfigWorldFile,
+      globalRequireSandbox    = mempty,
+      globalIgnoreSandbox     = mempty,
+      globalIgnoreExpiry      = projectConfigIgnoreExpiry,
+      globalHttpTransport     = projectConfigHttpTransport
+    }
+
+    configFlags = mempty {
+      configVerbosity     = projectConfigVerbosity,
+      configAllowNewer    = projectConfigAllowNewer
+    }
+
+    configExFlags = ConfigExFlags {
+      configCabalVersion  = projectConfigCabalVersion,
+      configExConstraints = projectConfigConstraints,
+      configPreferences   = projectConfigPreferences,
+      configSolver        = projectConfigSolver
+    }
+
+    installFlags = InstallFlags {
+      installDocumentation     = mempty,
+      installHaddockIndex      = projectConfigHaddockIndex,
+      installDryRun            = projectConfigDryRun,
+      installReinstall         = mempty, --projectConfigReinstall,
+      installAvoidReinstalls   = mempty, --projectConfigAvoidReinstalls,
+      installOverrideReinstall = mempty, --projectConfigOverrideReinstall,
+      installMaxBackjumps      = projectConfigMaxBackjumps,
+      installUpgradeDeps       = mempty, --projectConfigUpgradeDeps,
+      installReorderGoals      = projectConfigReorderGoals,
+      installIndependentGoals  = mempty, --projectConfigIndependentGoals,
+      installShadowPkgs        = mempty, --projectConfigShadowPkgs,
+      installStrongFlags       = projectConfigStrongFlags,
+      installOnly              = mempty,
+      installOnlyDeps          = projectConfigOnlyDeps,
+      installRootCmd           = projectConfigRootCmd,
+      installSummaryFile       = projectConfigSummaryFile,
+      installLogFile           = projectConfigLogFile,
+      installBuildReports      = projectConfigBuildReports,
+      installReportPlanningFailure = projectConfigReportPlanningFailure,
+      installSymlinkBinDir     = projectConfigSymlinkBinDir,
+      installOneShot           = projectConfigOneShot,
+      installNumJobs           = projectConfigNumJobs,
+      installRunTests          = mempty,
+      installOfflineMode       = projectConfigOfflineMode
+    }
+
+
+convertToLegacyAllPackageConfig :: ProjectConfig -> LegacyPackageConfig
+convertToLegacyAllPackageConfig
+    ProjectConfig {
+      projectConfigBuildOnly = ProjectConfigBuildOnly {..},
+      projectConfigShared    = ProjectConfigShared {..}
+    } =
+
+    LegacyPackageConfig {
+      legacyConfigureFlags = configFlags,
+      legacyInstallPkgFlags= mempty,
+      legacyHaddockFlags   = haddockFlags
+    }
+  where
+    configFlags = ConfigFlags {
+      configPrograms_           = mempty,
+      configProgramPaths        = Map.toList projectConfigProgramPaths,
+      configProgramArgs         = Map.toList projectConfigProgramArgs,
+      configProgramPathExtra    = projectConfigProgramPathExtra,
+      configHcFlavor            = projectConfigHcFlavor,
+      configHcPath              = projectConfigHcPath,
+      configHcPkg               = projectConfigHcPkg,
+      configVanillaLib          = mempty,
+      configProfLib             = mempty,
+      configSharedLib           = mempty,
+      configDynExe              = mempty,
+      configProfExe             = mempty,
+      configProf                = mempty,
+      configProfDetail          = mempty,
+      configProfLibDetail       = mempty,
+      configConfigureArgs       = mempty,
+      configOptimization        = mempty,
+      configProgPrefix          = mempty,
+      configProgSuffix          = mempty,
+      configInstallDirs         = mempty,
+      configScratchDir          = mempty,
+      configDistPref            = mempty,
+      configVerbosity           = mempty,
+      configUserInstall         = mempty, --projectConfigUserInstall,
+      configPackageDBs          = mempty, --projectConfigPackageDBs,
+      configGHCiLib             = mempty,
+      configSplitObjs           = mempty,
+      configStripExes           = mempty,
+      configStripLibs           = mempty,
+      configExtraLibDirs        = mempty,
+      configExtraFrameworkDirs  = mempty,
+      configConstraints         = mempty,
+      configDependencies        = mempty,
+      configExtraIncludeDirs    = mempty,
+      configIPID                = mempty,
+      configConfigurationsFlags = projectConfigFlagAssignment,
+      configTests               = mempty,
+      configCoverage            = mempty, --TODO: don't merge
+      configLibCoverage         = mempty, --TODO: don't merge
+      configExactConfiguration  = mempty,
+      configBenchmarks          = mempty,
+      configFlagError           = mempty,                --TODO: ???
+      configRelocatable         = mempty,
+      configDebugInfo           = mempty,
+      configAllowNewer          = mempty
+    }
+
+    haddockFlags = mempty {
+      haddockKeepTempFiles = projectConfigKeepTempFiles
+    }
+
+
+convertToLegacyPerPackageConfig :: PackageConfig -> LegacyPackageConfig
+convertToLegacyPerPackageConfig PackageConfig {..} =
+    LegacyPackageConfig {
+      legacyConfigureFlags  = configFlags,
+      legacyInstallPkgFlags = installFlags,
+      legacyHaddockFlags    = haddockFlags
+    }
+  where
+    configFlags = ConfigFlags {
+      configPrograms_           = configPrograms_ mempty,
+      configProgramPaths        = mempty,
+      configProgramArgs         = mempty,
+      configProgramPathExtra    = mempty,
+      configHcFlavor            = mempty,
+      configHcPath              = mempty,
+      configHcPkg               = mempty,
+      configVanillaLib          = packageConfigVanillaLib,
+      configProfLib             = packageConfigProfLib,
+      configSharedLib           = packageConfigSharedLib,
+      configDynExe              = packageConfigDynExe,
+      configProfExe             = packageConfigProfExe,
+      configProf                = packageConfigProf,
+      configProfDetail          = packageConfigProfDetail,
+      configProfLibDetail       = packageConfigProfLibDetail,
+      configConfigureArgs       = packageConfigConfigureArgs,
+      configOptimization        = packageConfigOptimization,
+      configProgPrefix          = packageConfigProgPrefix,
+      configProgSuffix          = packageConfigProgSuffix,
+      configInstallDirs         = mempty,
+      configScratchDir          = mempty,
+      configDistPref            = mempty,
+      configVerbosity           = mempty,
+      configUserInstall         = mempty,
+      configPackageDBs          = mempty,
+      configGHCiLib             = packageConfigGHCiLib,
+      configSplitObjs           = packageConfigSplitObjs,
+      configStripExes           = packageConfigStripExes,
+      configStripLibs           = packageConfigStripLibs,
+      configExtraLibDirs        = packageConfigExtraLibDirs,
+      configExtraFrameworkDirs  = packageConfigExtraFrameworkDirs,
+      configConstraints         = mempty,
+      configDependencies        = mempty,
+      configExtraIncludeDirs    = packageConfigExtraIncludeDirs,
+      configIPID                = mempty,
+      configConfigurationsFlags = mempty,
+      configTests               = packageConfigTests,
+      configCoverage            = packageConfigCoverage, --TODO: don't merge
+      configLibCoverage         = packageConfigCoverage, --TODO: don't merge
+      configExactConfiguration  = mempty,
+      configBenchmarks          = packageConfigBenchmarks,
+      configFlagError           = mempty,                --TODO: ???
+      configRelocatable         = packageConfigRelocatable,
+      configDebugInfo           = packageConfigDebugInfo,
+      configAllowNewer          = mempty
+    }
+
+    installFlags = mempty {
+      installDocumentation      = packageConfigDocumentation,
+      installRunTests           = packageConfigRunTests
+    }
+
+    haddockFlags = HaddockFlags {
+      haddockProgramPaths  = mempty,
+      haddockProgramArgs   = mempty,
+      haddockHoogle        = packageConfigHaddockHoogle,
+      haddockHtml          = packageConfigHaddockHtml,
+      haddockHtmlLocation  = packageConfigHaddockHtmlLocation,
+      haddockForHackage    = mempty, --TODO: added recently
+      haddockExecutables   = packageConfigHaddockExecutables,
+      haddockTestSuites    = packageConfigHaddockTestSuites,
+      haddockBenchmarks    = packageConfigHaddockBenchmarks,
+      haddockInternal      = packageConfigHaddockInternal,
+      haddockCss           = packageConfigHaddockCss,
+      haddockHscolour      = packageConfigHaddockHscolour,
+      haddockHscolourCss   = packageConfigHaddockHscolourCss,
+      haddockContents      = packageConfigHaddockContents,
+      haddockDistPref      = mempty,
+      haddockKeepTempFiles = mempty,
+      haddockVerbosity     = mempty
+    }
+
+
+------------------------------------------------
+-- Parsing and showing the project config file
+--
+
+parseLegacyProjectConfig :: String -> ParseResult LegacyProjectConfig
+parseLegacyProjectConfig =
+    parseConfig legacyProjectConfigFieldDescrs
+                legacyPackageConfigSectionDescrs
+                mempty
+
+showLegacyProjectConfig :: LegacyProjectConfig -> String
+showLegacyProjectConfig config =
+    Disp.render $
+    showConfig  legacyProjectConfigFieldDescrs
+                legacyPackageConfigSectionDescrs
+                config
+  $+$
+    Disp.text ""
+
+
+legacyProjectConfigFieldDescrs :: [FieldDescr LegacyProjectConfig]
+legacyProjectConfigFieldDescrs =
+
+    [ newLineListField "packages"
+        (Disp.text . renderPackageLocationToken) parsePackageLocationTokenQ
+        legacyPackages
+        (\v flags -> flags { legacyPackages = v })
+    , newLineListField "optional-packages"
+        (Disp.text . renderPackageLocationToken) parsePackageLocationTokenQ
+        legacyPackagesOptional
+        (\v flags -> flags { legacyPackagesOptional = v })
+    , commaNewLineListField "extra-packages"
+        disp parse
+        legacyPackagesNamed
+        (\v flags -> flags { legacyPackagesNamed = v })
+    ]
+
+ ++ map (liftField
+           legacySharedConfig
+           (\flags conf -> conf { legacySharedConfig = flags }))
+        legacySharedConfigFieldDescrs
+
+ ++ map (liftField
+           legacyLocalConfig
+           (\flags conf -> conf { legacyLocalConfig = flags }))
+        legacyPackageConfigFieldDescrs
+
+-- | This is a bit tricky since it has to cover globs which have embedded @,@
+-- chars. But we don't just want to parse strictly as a glob since we want to
+-- allow http urls which don't parse as globs, and possibly some
+-- system-dependent file paths. So we parse fairly liberally as a token, but
+-- we allow @,@ inside matched @{}@ braces.
+--
+parsePackageLocationTokenQ :: ReadP r String
+parsePackageLocationTokenQ = parseHaskellString
+                   Parse.<++ parsePackageLocationToken
+  where
+    parsePackageLocationToken :: ReadP r String
+    parsePackageLocationToken = fmap fst (Parse.gather outerTerm)
+      where
+        outerTerm   = alternateEither1 outerToken (braces innerTerm)
+        innerTerm   = alternateEither  innerToken (braces innerTerm)
+        outerToken  = Parse.munch1 outerChar >> return ()
+        innerToken  = Parse.munch1 innerChar >> return ()
+        outerChar c = not (isSpace c || c == '{' || c == '}' || c == ',')
+        innerChar c = not (isSpace c || c == '{' || c == '}')
+        braces      = Parse.between (Parse.char '{') (Parse.char '}')
+
+    alternateEither, alternateEither1,
+      alternatePQs, alternate1PQs, alternateQsP, alternate1QsP
+      :: ReadP r () -> ReadP r () -> ReadP r ()
+
+    alternateEither1 p q = alternate1PQs p q +++ alternate1QsP q p
+    alternateEither  p q = alternateEither1 p q +++ return ()
+    alternate1PQs    p q = p >> alternateQsP q p
+    alternatePQs     p q = alternate1PQs p q +++ return ()
+    alternate1QsP    q p = Parse.many1 q >> alternatePQs p q
+    alternateQsP     q p = alternate1QsP q p +++ return ()
+
+renderPackageLocationToken :: String -> String
+renderPackageLocationToken s | needsQuoting = show s
+                             | otherwise    = s
+  where
+    needsQuoting  = not (ok 0 s)
+                 || s == "." -- . on its own on a line has special meaning
+                 || take 2 s == "--" -- on its own line is comment syntax
+                 --TODO: [code cleanup] these "." and "--" escaping issues
+                 -- ought to be dealt with systematically in ParseUtils.
+    ok :: Int -> String -> Bool
+    ok n []       = n == 0
+    ok _ ('"':_)  = False
+    ok n ('{':cs) = ok (n+1) cs
+    ok n ('}':cs) = ok (n-1) cs
+    ok n (',':cs) = (n > 0) && ok n cs
+    ok _ (c:_)
+      | isSpace c = False
+    ok n (_  :cs) = ok n cs
+
+
+legacySharedConfigFieldDescrs :: [FieldDescr LegacySharedConfig]
+legacySharedConfigFieldDescrs =
+
+  ( liftFields
+      legacyGlobalFlags
+      (\flags conf -> conf { legacyGlobalFlags = flags })
+  . addFields
+      [ newLineListField "local-repo"
+          showTokenQ parseTokenQ
+          (fromNubList . globalLocalRepos)
+          (\v conf -> conf { globalLocalRepos = toNubList v })
+      ]
+  . filterFields
+      [ "remote-repo-cache"
+      , "logs-dir", "world-file", "ignore-expiry", "http-transport"
+      ]
+  . commandOptionsToFields
+  ) (commandOptions (globalCommand []) ParseArgs)
+ ++
+  ( liftFields
+      legacyConfigureShFlags
+      (\flags conf -> conf { legacyConfigureShFlags = flags })
+  . addFields
+      [ simpleField "allow-newer"
+        (maybe mempty dispAllowNewer) (fmap Just parseAllowNewer)
+        configAllowNewer (\v conf -> conf { configAllowNewer = v })
+      ]
+  . filterFields ["verbose"]
+  . commandOptionsToFields
+  ) (configureOptions ParseArgs)
+ ++
+  ( liftFields
+      legacyConfigureExFlags
+      (\flags conf -> conf { legacyConfigureExFlags = flags })
+  . addFields
+      [ commaNewLineListField "constraints"
+        (disp . fst) (fmap (\constraint -> (constraint, constraintSrc)) parse)
+        configExConstraints (\v conf -> conf { configExConstraints = v })
+
+      , commaNewLineListField "preferences"
+        disp parse
+        configPreferences (\v conf -> conf { configPreferences = v })
+      ]
+  . filterFields
+      [ "cabal-lib-version", "solver"
+        -- not "constraint" or "preference", we use our own plural ones above
+      ]
+  . commandOptionsToFields
+  ) (configureExOptions ParseArgs constraintSrc)
+ ++
+  ( liftFields
+      legacyInstallFlags
+      (\flags conf -> conf { legacyInstallFlags = flags })
+  . addFields
+      [ newLineListField "build-summary"
+          (showTokenQ . fromPathTemplate) (fmap toPathTemplate parseTokenQ)
+          (fromNubList . installSummaryFile)
+          (\v conf -> conf { installSummaryFile = toNubList v })
+      ]
+  . filterFields
+      [ "doc-index-file"
+      , "root-cmd", "symlink-bindir"
+      , "build-log"
+      , "remote-build-reporting", "report-planning-failure"
+      , "one-shot", "jobs", "offline"
+        -- solver flags:
+      , "max-backjumps", "reorder-goals", "strong-flags"
+      ]
+  . commandOptionsToFields
+  ) (installOptions ParseArgs)
+  where
+    constraintSrc = ConstraintSourceProjectConfig "TODO"
+
+parseAllowNewer :: ReadP r AllowNewer
+parseAllowNewer =
+     ((const AllowNewerNone <$> (Parse.string "none" +++ Parse.string "None"))
+  +++ (const AllowNewerAll  <$> (Parse.string "all"  +++ Parse.string "All")))
+  <++ (      AllowNewerSome <$> parseOptCommaList parse)
+
+dispAllowNewer :: AllowNewer -> Doc
+dispAllowNewer  AllowNewerNone       = Disp.text "None"
+dispAllowNewer (AllowNewerSome pkgs) = Disp.fsep . Disp.punctuate Disp.comma
+                                                 . map disp $ pkgs
+dispAllowNewer  AllowNewerAll        = Disp.text "All"
+
+
+legacyPackageConfigFieldDescrs :: [FieldDescr LegacyPackageConfig]
+legacyPackageConfigFieldDescrs =
+  ( liftFields
+      legacyConfigureFlags
+      (\flags conf -> conf { legacyConfigureFlags = flags })
+  . addFields
+      [ newLineListField "extra-include-dirs"
+          showTokenQ parseTokenQ
+          configExtraIncludeDirs
+          (\v conf -> conf { configExtraIncludeDirs = v })
+      , newLineListField "extra-lib-dirs"
+          showTokenQ parseTokenQ
+          configExtraLibDirs
+          (\v conf -> conf { configExtraLibDirs = v })
+      , newLineListField "extra-framework-dirs"
+          showTokenQ parseTokenQ
+          configExtraFrameworkDirs
+          (\v conf -> conf { configExtraFrameworkDirs = v })
+      , newLineListField "extra-prog-path"
+          showTokenQ parseTokenQ
+          (fromNubList . configProgramPathExtra)
+          (\v conf -> conf { configProgramPathExtra = toNubList v })
+      , newLineListField "configure-options"
+          showTokenQ parseTokenQ
+          configConfigureArgs
+          (\v conf -> conf { configConfigureArgs = v })
+      , simpleField "flags"
+          dispFlagAssignment parseFlagAssignment
+          configConfigurationsFlags
+          (\v conf -> conf { configConfigurationsFlags = v })
+      ]
+  . filterFields
+      [ "compiler", "with-compiler", "with-hc-pkg"
+      , "program-prefix", "program-suffix"
+      , "library-vanilla", "library-profiling"
+      , "shared", "executable-dynamic"
+      , "profiling", "executable-profiling"
+      , "profiling-detail", "library-profiling-detail"
+      , "optimization", "debug-info", "library-for-ghci", "split-objs"
+      , "executable-stripping", "library-stripping"
+      , "tests", "benchmarks"
+      , "coverage", "library-coverage"
+      , "relocatable"
+        -- not "extra-include-dirs", "extra-lib-dirs", "extra-framework-dirs"
+        -- or "extra-prog-path". We use corrected ones above that parse
+        -- as list fields.
+      ]
+  . commandOptionsToFields
+  ) (configureOptions ParseArgs)
+ ++
+    liftFields
+      legacyConfigureFlags
+      (\flags conf -> conf { legacyConfigureFlags = flags })
+    [ overrideFieldCompiler
+    , overrideFieldOptimization
+    , overrideFieldDebugInfo
+    ]
+ ++
+  ( liftFields
+      legacyInstallPkgFlags
+      (\flags conf -> conf { legacyInstallPkgFlags = flags })
+  . filterFields
+      [ "documentation", "run-tests"
+      ]
+  . commandOptionsToFields
+  ) (installOptions ParseArgs)
+ ++
+  ( liftFields
+      legacyHaddockFlags
+      (\flags conf -> conf { legacyHaddockFlags = flags })
+  . mapFieldNames
+      ("haddock-"++)
+  . filterFields
+      [ "hoogle", "html", "html-location"
+      , "executables", "tests", "benchmarks", "all", "internal", "css"
+      , "hyperlink-source", "hscolour-css"
+      , "contents-location", "keep-temp-files"
+      ]
+  . commandOptionsToFields
+  ) (haddockOptions ParseArgs)
+
+  where
+    overrideFieldCompiler =
+      simpleField "compiler"
+        (fromFlagOrDefault Disp.empty . fmap disp)
+        (Parse.option mempty (fmap toFlag parse))
+        configHcFlavor (\v flags -> flags { configHcFlavor = v })
+
+
+    -- TODO: [code cleanup] The following is a hack. The "optimization" and
+    -- "debug-info" fields are OptArg, and viewAsFieldDescr fails on that.
+    -- Instead of a hand-written parser and printer, we should handle this case
+    -- properly in the library.
+
+    overrideFieldOptimization =
+      liftField configOptimization
+                (\v flags -> flags { configOptimization = v }) $
+      let name = "optimization" in
+      FieldDescr name
+        (\f -> case f of
+                 Flag NoOptimisation      -> Disp.text "False"
+                 Flag NormalOptimisation  -> Disp.text "True"
+                 Flag MaximumOptimisation -> Disp.text "2"
+                 _                        -> Disp.empty)
+        (\line str _ -> case () of
+         _ |  str == "False" -> ParseOk [] (Flag NoOptimisation)
+           |  str == "True"  -> ParseOk [] (Flag NormalOptimisation)
+           |  str == "0"     -> ParseOk [] (Flag NoOptimisation)
+           |  str == "1"     -> ParseOk [] (Flag NormalOptimisation)
+           |  str == "2"     -> ParseOk [] (Flag MaximumOptimisation)
+           | lstr == "false" -> ParseOk [caseWarning] (Flag NoOptimisation)
+           | lstr == "true"  -> ParseOk [caseWarning] (Flag NormalOptimisation)
+           | otherwise       -> ParseFailed (NoParse name line)
+           where
+             lstr = lowercase str
+             caseWarning = PWarning $
+               "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+
+    overrideFieldDebugInfo =
+      liftField configDebugInfo (\v flags -> flags { configDebugInfo = v }) $
+      let name = "debug-info" in
+      FieldDescr name
+        (\f -> case f of
+                 Flag NoDebugInfo      -> Disp.text "False"
+                 Flag MinimalDebugInfo -> Disp.text "1"
+                 Flag NormalDebugInfo  -> Disp.text "True"
+                 Flag MaximalDebugInfo -> Disp.text "3"
+                 _                     -> Disp.empty)
+        (\line str _ -> case () of
+         _ |  str == "False" -> ParseOk [] (Flag NoDebugInfo)
+           |  str == "True"  -> ParseOk [] (Flag NormalDebugInfo)
+           |  str == "0"     -> ParseOk [] (Flag NoDebugInfo)
+           |  str == "1"     -> ParseOk [] (Flag MinimalDebugInfo)
+           |  str == "2"     -> ParseOk [] (Flag NormalDebugInfo)
+           |  str == "3"     -> ParseOk [] (Flag MaximalDebugInfo)
+           | lstr == "false" -> ParseOk [caseWarning] (Flag NoDebugInfo)
+           | lstr == "true"  -> ParseOk [caseWarning] (Flag NormalDebugInfo)
+           | otherwise       -> ParseFailed (NoParse name line)
+           where
+             lstr = lowercase str
+             caseWarning = PWarning $
+               "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+
+
+legacyPackageConfigSectionDescrs :: [SectionDescr LegacyProjectConfig]
+legacyPackageConfigSectionDescrs =
+    [ packageRepoSectionDescr
+    , packageSpecificOptionsSectionDescr
+    , liftSection
+        legacyLocalConfig
+        (\flags conf -> conf { legacyLocalConfig = flags })
+        programOptionsSectionDescr
+    , liftSection
+        legacyLocalConfig
+        (\flags conf -> conf { legacyLocalConfig = flags })
+        programLocationsSectionDescr
+    , liftSection
+        legacySharedConfig
+        (\flags conf -> conf { legacySharedConfig = flags }) $
+      liftSection
+        legacyGlobalFlags
+        (\flags conf -> conf { legacyGlobalFlags = flags })
+        remoteRepoSectionDescr
+    ]
+
+packageRepoSectionDescr :: SectionDescr LegacyProjectConfig
+packageRepoSectionDescr =
+    SectionDescr {
+      sectionName        = "source-repository-package",
+      sectionFields      = sourceRepoFieldDescrs,
+      sectionSubsections = [],
+      sectionGet         = map (\x->("", x))
+                         . legacyPackagesRepo,
+      sectionSet         =
+        \lineno unused pkgrepo projconf -> do
+          unless (null unused) $
+            syntaxError lineno "the section 'source-repository-package' takes no arguments"
+          return projconf {
+            legacyPackagesRepo = legacyPackagesRepo projconf ++ [pkgrepo]
+          },
+      sectionEmpty       = SourceRepo {
+                             repoKind     = RepoThis, -- hopefully unused
+                             repoType     = Nothing,
+                             repoLocation = Nothing,
+                             repoModule   = Nothing,
+                             repoBranch   = Nothing,
+                             repoTag      = Nothing,
+                             repoSubdir   = Nothing
+                           }
+    }
+
+packageSpecificOptionsSectionDescr :: SectionDescr LegacyProjectConfig
+packageSpecificOptionsSectionDescr =
+    SectionDescr {
+      sectionName        = "package",
+      sectionFields      = legacyPackageConfigFieldDescrs
+                        ++ programOptionsFieldDescrs
+                             (configProgramArgs . legacyConfigureFlags)
+                             (\args pkgconf -> pkgconf {
+                                 legacyConfigureFlags = (legacyConfigureFlags pkgconf) {
+                                   configProgramArgs  = args
+                                 }
+                               }
+                             ),
+      sectionSubsections = [],
+      sectionGet         = \projconf ->
+                             [ (display pkgname, pkgconf)
+                             | (pkgname, pkgconf) <- Map.toList (legacySpecificConfig projconf) ],
+      sectionSet         =
+        \lineno pkgnamestr pkgconf projconf -> do
+          pkgname <- case simpleParse pkgnamestr of
+            Just pkgname -> return pkgname
+            Nothing      -> syntaxError lineno $
+                                "a 'package' section requires a package name "
+                             ++ "as an argument"
+          return projconf {
+            legacySpecificConfig =
+              Map.insertWith mappend pkgname pkgconf
+                             (legacySpecificConfig projconf)
+          },
+      sectionEmpty       = mempty
+    }
+
+programOptionsFieldDescrs :: (a -> [(String, [String])])
+                          -> ([(String, [String])] -> a -> a)
+                          -> [FieldDescr a]
+programOptionsFieldDescrs get set =
+    commandOptionsToFields
+  $ programConfigurationOptions
+      defaultProgramDb
+      ParseArgs get set
+
+programOptionsSectionDescr :: SectionDescr LegacyPackageConfig
+programOptionsSectionDescr =
+    SectionDescr {
+      sectionName        = "program-options",
+      sectionFields      = programOptionsFieldDescrs
+                             configProgramArgs
+                             (\args conf -> conf { configProgramArgs = args }),
+      sectionSubsections = [],
+      sectionGet         = (\x->[("", x)])
+                         . legacyConfigureFlags,
+      sectionSet         =
+        \lineno unused confflags pkgconf -> do
+          unless (null unused) $
+            syntaxError lineno "the section 'program-options' takes no arguments"
+          return pkgconf {
+            legacyConfigureFlags = legacyConfigureFlags pkgconf <> confflags
+          },
+      sectionEmpty       = mempty
+    }
+
+programLocationsFieldDescrs :: [FieldDescr ConfigFlags]
+programLocationsFieldDescrs =
+     commandOptionsToFields
+   $ programConfigurationPaths'
+       (++ "-location")
+       defaultProgramDb
+       ParseArgs
+       configProgramPaths
+       (\paths conf -> conf { configProgramPaths = paths })
+
+programLocationsSectionDescr :: SectionDescr LegacyPackageConfig
+programLocationsSectionDescr =
+    SectionDescr {
+      sectionName        = "program-locations",
+      sectionFields      = programLocationsFieldDescrs,
+      sectionSubsections = [],
+      sectionGet         = (\x->[("", x)])
+                         . legacyConfigureFlags,
+      sectionSet         =
+        \lineno unused confflags pkgconf -> do
+          unless (null unused) $
+            syntaxError lineno "the section 'program-locations' takes no arguments"
+          return pkgconf {
+            legacyConfigureFlags = legacyConfigureFlags pkgconf <> confflags
+          },
+      sectionEmpty       = mempty
+    }
+
+
+-- | For each known program @PROG@ in 'progConf', produce a @PROG-options@
+-- 'OptionField'.
+programConfigurationOptions
+  :: ProgramDb
+  -> ShowOrParseArgs
+  -> (flags -> [(String, [String])])
+  -> ([(String, [String])] -> (flags -> flags))
+  -> [OptionField flags]
+programConfigurationOptions progConf showOrParseArgs get set =
+  case showOrParseArgs of
+    -- we don't want a verbose help text list so we just show a generic one:
+    ShowArgs  -> [programOptions  "PROG"]
+    ParseArgs -> map (programOptions . programName . fst)
+                 (knownPrograms progConf)
+  where
+    programOptions prog =
+      option "" [prog ++ "-options"]
+        ("give extra options to " ++ prog)
+        get set
+        (reqArg' "OPTS" (\args -> [(prog, splitArgs args)])
+           (\progArgs -> [ joinsArgs args
+                         | (prog', args) <- progArgs, prog==prog' ]))
+
+
+    joinsArgs = unwords . map escape
+    escape arg | any isSpace arg = "\"" ++ arg ++ "\""
+               | otherwise       = arg
+
+
+remoteRepoSectionDescr :: SectionDescr GlobalFlags
+remoteRepoSectionDescr =
+    SectionDescr {
+      sectionName        = "repository",
+      sectionFields      = remoteRepoFields,
+      sectionSubsections = [],
+      sectionGet         = map (\x->(remoteRepoName x, x)) . fromNubList
+                         . globalRemoteRepos,
+      sectionSet         =
+        \lineno reponame repo0 conf -> do
+          when (null reponame) $
+            syntaxError lineno $ "a 'repository' section requires the "
+                              ++ "repository name as an argument"
+          let repo = repo0 { remoteRepoName = reponame }
+          when (remoteRepoKeyThreshold repo
+                 > length (remoteRepoRootKeys repo)) $
+            warning $ "'key-threshold' for repository "
+                   ++ show (remoteRepoName repo)
+                   ++ " higher than number of keys"
+          when (not (null (remoteRepoRootKeys repo))
+                && remoteRepoSecure repo /= Just True) $
+            warning $ "'root-keys' for repository "
+                   ++ show (remoteRepoName repo)
+                   ++ " non-empty, but 'secure' not set to True."
+          return conf {
+            globalRemoteRepos = overNubList (++[repo]) (globalRemoteRepos conf)
+          },
+      sectionEmpty       = emptyRemoteRepo ""
+    }
+
+
+-------------------------------
+-- Local field utils
+--
+
+--TODO: [code cleanup] all these utils should move to Distribution.ParseUtils
+-- either augmenting or replacing the ones there
+
+--TODO: [code cleanup] this is a different definition from listField, like
+-- commaNewLineListField it pretty prints on multiple lines
+newLineListField :: String -> (a -> Doc) -> ReadP [a] a
+                 -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b
+newLineListField = listFieldWithSep Disp.sep
+
+--TODO: [code cleanup] local copy purely so we can use the fixed version
+-- of parseOptCommaList below
+listFieldWithSep :: ([Doc] -> Doc) -> String -> (a -> Doc) -> ReadP [a] a
+                 -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b
+listFieldWithSep separator name showF readF get set =
+  liftField get set' $
+    ParseUtils.field name showF' (parseOptCommaList readF)
+  where
+    set' xs b = set (get b ++ xs) b
+    showF'    = separator . map showF
+
+--TODO: [code cleanup] local redefinition that should replace the version in
+-- D.ParseUtils. This version avoid parse ambiguity for list element parsers
+-- that have multiple valid parses of prefixes.
+parseOptCommaList :: ReadP r a -> ReadP r [a]
+parseOptCommaList p = Parse.sepBy p sep
+  where
+    -- The separator must not be empty or it introduces ambiguity
+    sep = (Parse.skipSpaces >> Parse.char ',' >> Parse.skipSpaces)
+      +++ (Parse.satisfy isSpace >> Parse.skipSpaces)
+
+--TODO: [code cleanup] local redefinition that should replace the version in
+-- D.ParseUtils called showFilePath. This version escapes "." and "--" which
+-- otherwise are special syntax.
+showTokenQ :: String -> Doc
+showTokenQ ""            = Disp.empty
+showTokenQ x@('-':'-':_) = Disp.text (show x)
+showTokenQ x@('.':[])    = Disp.text (show x)
+showTokenQ x             = showToken x
+
+-- This is just a copy of parseTokenQ, using the fixed parseHaskellString
+parseTokenQ :: ReadP r String
+parseTokenQ = parseHaskellString
+          <++ Parse.munch1 (\x -> not (isSpace x) && x /= ',')
+
+--TODO: [code cleanup] use this to replace the parseHaskellString in
+-- Distribution.ParseUtils. It turns out Read instance for String accepts
+-- the ['a', 'b'] syntax, which we do not want. In particular it messes
+-- up any token starting with [].
+parseHaskellString :: ReadP r String
+parseHaskellString =
+  Parse.readS_to_P $
+    Read.readPrec_to_S (do Read.String s <- Read.lexP; return s) 0
+
+-- Handy util
+addFields :: [FieldDescr a]
+          -> ([FieldDescr a] -> [FieldDescr a])
+addFields = (++)
diff --git a/cabal/cabal-install/Distribution/Client/ProjectConfig/Types.hs b/cabal/cabal-install/Distribution/Client/ProjectConfig/Types.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/ProjectConfig/Types.hs
@@ -0,0 +1,333 @@
+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
+
+-- | Handling project configuration, types.
+--
+module Distribution.Client.ProjectConfig.Types (
+
+    -- * Types for project config
+    ProjectConfig(..),
+    ProjectConfigBuildOnly(..),
+    ProjectConfigShared(..),
+    PackageConfig(..),
+
+    -- * Resolving configuration
+    SolverSettings(..),
+    BuildTimeSettings(..),
+
+  ) where
+
+import Distribution.Client.Types
+         ( RemoteRepo )
+import Distribution.Client.Dependency.Types
+         ( PreSolver, ConstraintSource )
+import Distribution.Client.Targets
+         ( UserConstraint )
+import Distribution.Client.BuildReports.Types 
+         ( ReportLevel(..) )
+
+import Distribution.Package
+         ( PackageName, PackageId, UnitId, Dependency )
+import Distribution.Version
+         ( Version )
+import Distribution.System
+         ( Platform )
+import Distribution.PackageDescription
+         ( FlagAssignment, SourceRepo(..) )
+import Distribution.Simple.Compiler
+         ( Compiler, CompilerFlavor
+         , OptimisationLevel(..), ProfDetailLevel, DebugInfoLevel(..) )
+import Distribution.Simple.Setup
+         ( Flag, AllowNewer(..) )
+import Distribution.Simple.InstallDirs
+         ( PathTemplate )
+import Distribution.Utils.NubList
+         ( NubList )
+import Distribution.Verbosity
+         ( Verbosity )
+
+import Data.Map (Map)
+import Distribution.Compat.Binary (Binary)
+import Distribution.Compat.Semigroup
+import GHC.Generics (Generic)
+
+
+-------------------------------
+-- Project config types
+--
+
+-- | This type corresponds directly to what can be written in the
+-- @cabal.project@ file. Other sources of configuration can also be injected
+-- into this type, such as the user-wide @~/.cabal/config@ file and the
+-- command line of @cabal configure@ or @cabal build@.
+--
+-- Since it corresponds to the external project file it is an instance of
+-- 'Monoid' and all the fields can be empty. This also means there has to
+-- be a step where we resolve configuration. At a minimum resolving means
+-- applying defaults but it can also mean merging information from multiple
+-- sources. For example for package-specific configuration the project file
+-- can specify configuration that applies to all local packages, and then
+-- additional configuration for a specific package.
+--
+-- Future directions: multiple profiles, conditionals. If we add these
+-- features then the gap between configuration as written in the config file
+-- and resolved settings we actually use will become even bigger.
+--
+data ProjectConfig
+   = ProjectConfig {
+
+       -- | Packages in this project, including local dirs, local .cabal files
+       -- local and remote tarballs. Where these are file globs, they must
+       -- match something.
+       projectPackages              :: [String],
+
+       -- | Like 'projectConfigPackageGlobs' but /optional/ in the sense that
+       -- file globs are allowed to match nothing. The primary use case for
+       -- this is to be able to say @optional-packages: */@ to automagically
+       -- pick up deps that we unpack locally.
+       projectPackagesOptional      :: [String],
+
+       -- | Packages in this project from remote source repositories.
+       projectPackagesRepo          :: [SourceRepo],
+
+       -- | Packages in this project from hackage repositories.
+       projectPackagesNamed         :: [Dependency],
+
+       projectConfigBuildOnly       :: ProjectConfigBuildOnly,
+       projectConfigShared          :: ProjectConfigShared,
+       projectConfigLocalPackages   :: PackageConfig,
+       projectConfigSpecificPackage :: Map PackageName PackageConfig
+     }
+  deriving (Eq, Show, Generic)
+
+-- | That part of the project configuration that only affects /how/ we build
+-- and not the /value/ of the things we build. This means this information
+-- does not need to be tracked for changes since it does not affect the
+-- outcome.
+--
+data ProjectConfigBuildOnly
+   = ProjectConfigBuildOnly {
+       projectConfigVerbosity             :: Flag Verbosity,
+       projectConfigDryRun                :: Flag Bool,
+       projectConfigOnlyDeps              :: Flag Bool,
+       projectConfigSummaryFile           :: NubList PathTemplate,
+       projectConfigLogFile               :: Flag PathTemplate,
+       projectConfigBuildReports          :: Flag ReportLevel,
+       projectConfigReportPlanningFailure :: Flag Bool,
+       projectConfigSymlinkBinDir         :: Flag FilePath,
+       projectConfigOneShot               :: Flag Bool,
+       projectConfigNumJobs               :: Flag (Maybe Int),
+       projectConfigOfflineMode           :: Flag Bool,
+       projectConfigKeepTempFiles         :: Flag Bool,
+       projectConfigHttpTransport         :: Flag String,
+       projectConfigIgnoreExpiry          :: Flag Bool,
+       projectConfigCacheDir              :: Flag FilePath,
+       projectConfigLogsDir               :: Flag FilePath,
+       projectConfigWorldFile             :: Flag FilePath,
+       projectConfigRootCmd               :: Flag String
+     }
+  deriving (Eq, Show, Generic)
+
+
+-- | Project configuration that is shared between all packages in the project.
+-- In particular this includes configuration that affects the solver.
+--
+data ProjectConfigShared
+   = ProjectConfigShared {
+       projectConfigProgramPaths      :: Map String FilePath,
+       projectConfigProgramArgs       :: Map String [String],
+       projectConfigProgramPathExtra  :: NubList FilePath,
+       projectConfigHcFlavor          :: Flag CompilerFlavor,
+       projectConfigHcPath            :: Flag FilePath,
+       projectConfigHcPkg             :: Flag FilePath,
+       projectConfigHaddockIndex      :: Flag PathTemplate,
+
+       -- Things that only make sense for manual mode, not --local mode
+       -- too much control!
+     --projectConfigUserInstall       :: Flag Bool,
+     --projectConfigInstallDirs       :: InstallDirs (Flag PathTemplate),
+     --TODO: [required eventually] decide what to do with InstallDirs
+     -- currently we don't allow it to be specified in the config file
+     --projectConfigPackageDBs        :: [Maybe PackageDB],
+
+       -- configuration used both by the solver and other phases
+       projectConfigRemoteRepos       :: NubList RemoteRepo,     -- ^ Available Hackage servers.
+       projectConfigLocalRepos        :: NubList FilePath,
+
+       -- solver configuration
+       projectConfigConstraints       :: [(UserConstraint, ConstraintSource)],
+       projectConfigPreferences       :: [Dependency],
+       projectConfigFlagAssignment    :: FlagAssignment, --TODO: [required eventually] must be per-package, not global
+       projectConfigCabalVersion      :: Flag Version,  --TODO: [required eventually] unused
+       projectConfigSolver            :: Flag PreSolver,
+       projectConfigAllowNewer        :: Maybe AllowNewer,
+       projectConfigMaxBackjumps      :: Flag Int,
+       projectConfigReorderGoals      :: Flag Bool,
+       projectConfigStrongFlags       :: Flag Bool
+
+       -- More things that only make sense for manual mode, not --local mode
+       -- too much control!
+     --projectConfigIndependentGoals  :: Flag Bool,
+     --projectConfigShadowPkgs        :: Flag Bool,
+     --projectConfigReinstall         :: Flag Bool,
+     --projectConfigAvoidReinstalls   :: Flag Bool,
+     --projectConfigOverrideReinstall :: Flag Bool,
+     --projectConfigUpgradeDeps       :: Flag Bool
+     }
+  deriving (Eq, Show, Generic)
+
+
+-- | Project configuration that is specific to each package, that is where we
+-- can in principle have different values for different packages in the same
+-- project.
+--
+data PackageConfig
+   = PackageConfig {
+       packageConfigVanillaLib          :: Flag Bool,
+       packageConfigSharedLib           :: Flag Bool,
+       packageConfigDynExe              :: Flag Bool,
+       packageConfigProf                :: Flag Bool, --TODO: [code cleanup] sort out
+       packageConfigProfLib             :: Flag Bool, --      this duplication
+       packageConfigProfExe             :: Flag Bool, --      and consistency
+       packageConfigProfDetail          :: Flag ProfDetailLevel,
+       packageConfigProfLibDetail       :: Flag ProfDetailLevel,
+       packageConfigConfigureArgs       :: [String],
+       packageConfigOptimization        :: Flag OptimisationLevel,
+       packageConfigProgPrefix          :: Flag PathTemplate,
+       packageConfigProgSuffix          :: Flag PathTemplate,
+       packageConfigExtraLibDirs        :: [FilePath],
+       packageConfigExtraFrameworkDirs  :: [FilePath],
+       packageConfigExtraIncludeDirs    :: [FilePath],
+       packageConfigGHCiLib             :: Flag Bool,
+       packageConfigSplitObjs           :: Flag Bool,
+       packageConfigStripExes           :: Flag Bool,
+       packageConfigStripLibs           :: Flag Bool,
+       packageConfigTests               :: Flag Bool,
+       packageConfigBenchmarks          :: Flag Bool,
+       packageConfigCoverage            :: Flag Bool,
+       packageConfigRelocatable         :: Flag Bool,
+       packageConfigDebugInfo           :: Flag DebugInfoLevel,
+       packageConfigRunTests            :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigDocumentation       :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockHoogle       :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockHtml         :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockHtmlLocation :: Flag String, --TODO: [required eventually] use this
+       packageConfigHaddockExecutables  :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockTestSuites   :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockBenchmarks   :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockInternal     :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockCss          :: Flag FilePath, --TODO: [required eventually] use this
+       packageConfigHaddockHscolour     :: Flag Bool, --TODO: [required eventually] use this
+       packageConfigHaddockHscolourCss  :: Flag FilePath, --TODO: [required eventually] use this
+       packageConfigHaddockContents     :: Flag PathTemplate --TODO: [required eventually] use this
+     }
+  deriving (Eq, Show, Generic)
+
+instance Binary ProjectConfig
+instance Binary ProjectConfigBuildOnly
+instance Binary ProjectConfigShared
+instance Binary PackageConfig
+
+
+instance Monoid ProjectConfig where
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup ProjectConfig where
+  (<>) = gmappend
+
+
+instance Monoid ProjectConfigBuildOnly where
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup ProjectConfigBuildOnly where
+  (<>) = gmappend
+
+
+instance Monoid ProjectConfigShared where
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup ProjectConfigShared where
+  (<>) = gmappend
+
+
+instance Monoid PackageConfig where
+  mempty = gmempty
+  mappend = (<>)
+
+instance Semigroup PackageConfig where
+  (<>) = gmappend
+
+----------------------------------------
+-- Resolving configuration to settings
+--
+
+-- | Resolved configuration for the solver. The idea is that this is easier to
+-- use than the raw configuration because in the raw configuration everything
+-- is optional (monoidial). In the 'BuildTimeSettings' every field is filled
+-- in, if only with the defaults.
+--
+-- Use 'resolveSolverSettings' to make one from the project config (by
+-- applying defaults etc).
+--
+data SolverSettings
+   = SolverSettings {
+       solverSettingRemoteRepos       :: [RemoteRepo],     -- ^ Available Hackage servers.
+       solverSettingLocalRepos        :: [FilePath],
+       solverSettingConstraints       :: [(UserConstraint, ConstraintSource)],
+       solverSettingPreferences       :: [Dependency],
+       solverSettingFlagAssignment    :: FlagAssignment, --TODO: [required eventually] must be per-package, not global
+       solverSettingCabalVersion      :: Maybe Version,  --TODO: [required eventually] unused
+       solverSettingSolver            :: PreSolver,
+       solverSettingAllowNewer        :: AllowNewer,
+       solverSettingMaxBackjumps      :: Maybe Int,
+       solverSettingReorderGoals      :: Bool,
+       solverSettingStrongFlags       :: Bool
+       -- Things that only make sense for manual mode, not --local mode
+       -- too much control!
+     --solverSettingIndependentGoals  :: Bool,
+     --solverSettingShadowPkgs        :: Bool,
+     --solverSettingReinstall         :: Bool,
+     --solverSettingAvoidReinstalls   :: Bool,
+     --solverSettingOverrideReinstall :: Bool,
+     --solverSettingUpgradeDeps       :: Bool
+     }
+  deriving (Eq, Show, Generic)
+
+instance Binary SolverSettings
+
+
+-- | Resolved configuration for things that affect how we build and not the
+-- value of the things we build. The idea is that this is easier to use than
+-- the raw configuration because in the raw configuration everything is
+-- optional (monoidial). In the 'BuildTimeSettings' every field is filled in,
+-- if only with the defaults.
+--
+-- Use 'resolveBuildTimeSettings' to make one from the project config (by
+-- applying defaults etc).
+--
+data BuildTimeSettings
+   = BuildTimeSettings {
+       buildSettingDryRun                :: Bool,
+       buildSettingOnlyDeps              :: Bool,
+       buildSettingSummaryFile           :: [PathTemplate],
+       buildSettingLogFile               :: Maybe (Compiler  -> Platform
+                                                -> PackageId -> UnitId
+                                                             -> FilePath),
+       buildSettingLogVerbosity          :: Verbosity,
+       buildSettingBuildReports          :: ReportLevel,
+       buildSettingReportPlanningFailure :: Bool,
+       buildSettingSymlinkBinDir         :: [FilePath],
+       buildSettingOneShot               :: Bool,
+       buildSettingNumJobs               :: Int,
+       buildSettingOfflineMode           :: Bool,
+       buildSettingKeepTempFiles         :: Bool,
+       buildSettingRemoteRepos           :: [RemoteRepo],
+       buildSettingLocalRepos            :: [FilePath],
+       buildSettingCacheDir              :: FilePath,
+       buildSettingHttpTransport         :: Maybe String,
+       buildSettingIgnoreExpiry          :: Bool,
+       buildSettingRootCmd               :: Maybe String
+     }
+
diff --git a/cabal/cabal-install/Distribution/Client/ProjectPlanning.hs b/cabal/cabal-install/Distribution/Client/ProjectPlanning.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/ProjectPlanning.hs
@@ -0,0 +1,2333 @@
+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns,
+             DeriveGeneric, DeriveDataTypeable,
+             RankNTypes, ScopedTypeVariables #-}
+
+-- | Planning how to build everything in a project.
+--
+module Distribution.Client.ProjectPlanning (
+    -- * elaborated install plan types
+    ElaboratedInstallPlan,
+    ElaboratedConfiguredPackage(..),
+    ElaboratedPlanPackage,
+    ElaboratedSharedConfig(..),
+    ElaboratedReadyPackage,
+    BuildStyle(..),
+    CabalFileText,
+
+    --TODO: [code cleanup] these types should live with execution, not with
+    --      plan definition. Need to better separate InstallPlan definition.
+    GenericBuildResult(..),
+    BuildResult,
+    BuildSuccess(..),
+    BuildFailure(..),
+    DocsResult(..),
+    TestsResult(..),
+
+    -- * Producing the elaborated install plan
+    rebuildInstallPlan,
+
+    -- * Build targets
+    PackageTarget(..),
+    ComponentTarget(..),
+    SubComponentTarget(..),
+    showComponentTarget,
+
+    -- * Selecting a plan subset
+    pruneInstallPlanToTargets,
+
+    -- * Utils required for building
+    pkgHasEphemeralBuildTargets,
+    pkgBuildTargetWholeComponents,
+
+    -- * Setup.hs CLI flags for building
+    setupHsScriptOptions,
+    setupHsConfigureFlags,
+    setupHsBuildFlags,
+    setupHsBuildArgs,
+    setupHsReplFlags,
+    setupHsReplArgs,
+    setupHsCopyFlags,
+    setupHsRegisterFlags,
+    setupHsHaddockFlags,
+
+    packageHashInputs,
+
+    -- TODO: [code cleanup] utils that should live in some shared place?
+    createPackageDBIfMissing
+  ) where
+
+import           Distribution.Client.PackageHash
+import           Distribution.Client.RebuildMonad
+import           Distribution.Client.ProjectConfig
+
+import           Distribution.Client.Types hiding
+  (BuildResult,BuildSuccess(..), BuildFailure(..), DocsResult(..)
+  ,TestsResult(..))
+import           Distribution.Client.InstallPlan
+                   ( GenericInstallPlan, InstallPlan, GenericPlanPackage )
+import qualified Distribution.Client.InstallPlan as InstallPlan
+import           Distribution.Client.Dependency
+import           Distribution.Client.Dependency.Types
+import qualified Distribution.Client.ComponentDeps as CD
+import           Distribution.Client.ComponentDeps (ComponentDeps)
+import qualified Distribution.Client.IndexUtils as IndexUtils
+import qualified Distribution.Client.PackageIndex as SourcePackageIndex
+import           Distribution.Client.Targets (userToPackageConstraint)
+import           Distribution.Client.DistDirLayout
+import           Distribution.Client.SetupWrapper
+import           Distribution.Client.JobControl
+import           Distribution.Client.FetchUtils
+import           Distribution.Client.PkgConfigDb
+import           Distribution.Client.Setup hiding (packageName, cabalVersion)
+import           Distribution.Utils.NubList (toNubList)
+
+import           Distribution.Package hiding
+  (InstalledPackageId, installedPackageId)
+import           Distribution.System
+import qualified Distribution.PackageDescription as Cabal
+import qualified Distribution.PackageDescription as PD
+import qualified Distribution.PackageDescription.Configuration as PD
+import           Distribution.InstalledPackageInfo (InstalledPackageInfo)
+import           Distribution.Simple.PackageIndex (InstalledPackageIndex)
+import qualified Distribution.Simple.PackageIndex as PackageIndex
+import           Distribution.Simple.Compiler hiding (Flag)
+import qualified Distribution.Simple.GHC   as GHC   --TODO: [code cleanup] eliminate
+import qualified Distribution.Simple.GHCJS as GHCJS --TODO: [code cleanup] eliminate
+import           Distribution.Simple.Program
+import           Distribution.Simple.Program.Db
+import           Distribution.Simple.Program.Find
+import qualified Distribution.Simple.Setup as Cabal
+import           Distribution.Simple.Setup
+  (Flag, toFlag, flagToMaybe, flagToList, fromFlagOrDefault)
+import qualified Distribution.Simple.Configure as Cabal
+import           Distribution.ModuleName (ModuleName)
+import qualified Distribution.Simple.LocalBuildInfo as Cabal
+import           Distribution.Simple.LocalBuildInfo (ComponentName(..))
+import qualified Distribution.Simple.Register as Cabal
+import qualified Distribution.Simple.InstallDirs as InstallDirs
+import           Distribution.Simple.InstallDirs (PathTemplate)
+import qualified Distribution.Simple.BuildTarget as Cabal
+
+import           Distribution.Simple.Utils hiding (matchFileGlob)
+import           Distribution.Version
+import           Distribution.Verbosity
+import           Distribution.Text
+
+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 Graph
+import qualified Data.Tree  as Tree
+import qualified Data.ByteString.Lazy as LBS
+
+#if !MIN_VERSION_base(4,8,0)
+import           Control.Applicative
+#endif
+import           Control.Monad
+import           Control.Monad.State as State
+import           Control.Exception
+import           Data.List
+import           Data.Maybe
+import           Data.Monoid
+import           Data.Function
+
+import           Distribution.Compat.Binary
+import           GHC.Generics (Generic)
+import           Data.Typeable (Typeable)
+
+import           System.FilePath
+
+
+------------------------------------------------------------------------------
+-- * Elaborated install plan
+------------------------------------------------------------------------------
+
+-- "Elaborated" -- worked out with great care and nicety of detail;
+--                 executed with great minuteness: elaborate preparations;
+--                 elaborate care.
+--
+-- So here's the idea:
+--
+-- Rather than a miscellaneous collection of 'ConfigFlags', 'InstallFlags' etc
+-- all passed in as separate args and which are then further selected,
+-- transformed etc during the execution of the build. Instead we construct
+-- an elaborated install plan that includes everything we will need, and then
+-- during the execution of the plan we do as little transformation of this
+-- info as possible.
+--
+-- So we're trying to split the work into two phases: construction of the
+-- elaborated install plan (which as far as possible should be pure) and
+-- then simple execution of that plan without any smarts, just doing what the
+-- plan says to do.
+--
+-- So that means we need a representation of this fully elaborated install
+-- plan. The representation consists of two parts:
+--
+-- * A 'ElaboratedInstallPlan'. This is a 'GenericInstallPlan' with a
+--   representation of source packages that includes a lot more detail about
+--   that package's individual configuration
+--
+-- * A 'ElaboratedSharedConfig'. Some package configuration is the same for
+--   every package in a plan. Rather than duplicate that info every entry in
+--   the 'GenericInstallPlan' we keep that separately.
+--
+-- The division between the shared and per-package config is /not set in stone
+-- for all time/. For example if we wanted to generalise the install plan to
+-- describe a situation where we want to build some packages with GHC and some
+-- with GHCJS then the platform and compiler would no longer be shared between
+-- all packages but would have to be per-package (probably with some sanity
+-- condition on the graph structure).
+--
+
+-- | The combination of an elaborated install plan plus a
+-- 'ElaboratedSharedConfig' contains all the details necessary to be able
+-- to execute the plan without having to make further policy decisions.
+--
+-- It does not include dynamic elements such as resources (such as http
+-- connections).
+--
+type ElaboratedInstallPlan
+   = GenericInstallPlan InstalledPackageInfo
+                        ElaboratedConfiguredPackage
+                        BuildSuccess BuildFailure
+
+type ElaboratedPlanPackage
+   = GenericPlanPackage InstalledPackageInfo
+                        ElaboratedConfiguredPackage
+                        BuildSuccess BuildFailure
+
+type SolverInstallPlan
+   = InstallPlan --TODO: [code cleanup] redefine locally or move def to solver interface
+
+--TODO: [code cleanup] decide if we really need this, there's not much in it, and in principle
+--      even platform and compiler could be different if we're building things
+--      like a server + client with ghc + ghcjs
+data ElaboratedSharedConfig
+   = ElaboratedSharedConfig {
+
+       pkgConfigPlatform         :: Platform,
+       pkgConfigCompiler         :: Compiler, --TODO: [code cleanup] replace with CompilerInfo
+       pkgConfigProgramDb        :: ProgramDb --TODO: [code cleanup] no Eq instance
+       --TODO: [code cleanup] binary instance does not preserve the prog paths
+       --      perhaps should keep the configured progs separately
+     }
+  deriving (Show, Generic)
+
+instance Binary ElaboratedSharedConfig
+
+data ElaboratedConfiguredPackage
+   = ElaboratedConfiguredPackage {
+
+       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,
+
+       -- | A total flag assignment for the package
+       pkgFlagAssignment   :: Cabal.FlagAssignment,
+
+       -- | The original default flag assignment, used only for reporting.
+       pkgFlagDefaults     :: Cabal.FlagAssignment,
+
+       -- | The exact dependencies (on other plan packages)
+       --
+       pkgDependencies     :: ComponentDeps [ConfiguredId],
+
+       -- | 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.
+       pkgStanzasAvailable :: Set OptionalStanza,
+
+       -- | Which optional stanzas the user explicitly asked to enable or
+       -- to disable. This tells us which ones we build by default, and
+       -- helps with error messages when the user asks to build something
+       -- they explicitly disabled.
+       pkgStanzasRequested :: Map OptionalStanza Bool,
+
+       -- | Which optional stanzas (ie testsuites, benchmarks) will actually
+       -- be enabled during the package configure step.
+       pkgStanzasEnabled :: Set OptionalStanza,
+
+       -- | Where the package comes from, e.g. tarball, local dir etc. This
+       --   is not the same as where it may be unpacked to for the build.
+       pkgSourceLocation :: PackageLocation (Maybe FilePath),
+
+       -- | The hash of the source, e.g. the tarball. We don't have this for
+       -- local source dir packages.
+       pkgSourceHash     :: Maybe PackageSourceHash,
+
+       --pkgSourceDir ? -- currently passed in later because they can use temp locations
+       --pkgBuildDir  ? -- but could in principle still have it here, with optional instr to use temp loc
+
+       pkgBuildStyle             :: BuildStyle,
+
+       pkgSetupPackageDBStack    :: PackageDBStack,
+       pkgBuildPackageDBStack    :: PackageDBStack,
+       pkgRegisterPackageDBStack :: PackageDBStack,
+
+       -- | The package contains a library and so must be registered
+       pkgRequiresRegistration :: Bool,
+       pkgDescriptionOverride  :: Maybe CabalFileText,
+
+       pkgVanillaLib           :: Bool,
+       pkgSharedLib            :: Bool,
+       pkgDynExe               :: Bool,
+       pkgGHCiLib              :: Bool,
+       pkgProfLib              :: Bool,
+       pkgProfExe              :: Bool,
+       pkgProfLibDetail        :: ProfDetailLevel,
+       pkgProfExeDetail        :: ProfDetailLevel,
+       pkgCoverage             :: Bool,
+       pkgOptimization         :: OptimisationLevel,
+       pkgSplitObjs            :: Bool,
+       pkgStripLibs            :: Bool,
+       pkgStripExes            :: Bool,
+       pkgDebugInfo            :: DebugInfoLevel,
+
+       pkgConfigureScriptArgs   :: [String],
+       pkgExtraLibDirs          :: [FilePath],
+       pkgExtraFrameworkDirs    :: [FilePath],
+       pkgExtraIncludeDirs      :: [FilePath],
+       pkgProgPrefix            :: Maybe PathTemplate,
+       pkgProgSuffix            :: Maybe PathTemplate,
+
+       pkgInstallDirs           :: InstallDirs.InstallDirs FilePath,
+
+       pkgHaddockHoogle         :: Bool,
+       pkgHaddockHtml           :: Bool,
+       pkgHaddockHtmlLocation   :: Maybe String,
+       pkgHaddockExecutables    :: Bool,
+       pkgHaddockTestSuites     :: Bool,
+       pkgHaddockBenchmarks     :: Bool,
+       pkgHaddockInternal       :: Bool,
+       pkgHaddockCss            :: Maybe FilePath,
+       pkgHaddockHscolour       :: Bool,
+       pkgHaddockHscolourCss    :: Maybe FilePath,
+       pkgHaddockContents       :: Maybe PathTemplate,
+
+       -- Setup.hs related things:
+
+       -- | One of four modes for how we build and interact with the Setup.hs
+       -- script, based on whether it's a build-type Custom, with or without
+       -- explicit deps and the cabal spec version the .cabal file needs.
+       pkgSetupScriptStyle      :: SetupScriptStyle,
+
+       -- | The version of the Cabal command line interface that we are using
+       -- for this package. This is typically the version of the Cabal lib
+       -- that the Setup.hs is built against.
+       pkgSetupScriptCliVersion :: Version,
+
+       -- Build time related:
+       pkgBuildTargets          :: [ComponentTarget],
+       pkgReplTarget            :: Maybe ComponentTarget,
+       pkgBuildHaddocks         :: Bool
+     }
+  deriving (Eq, Show, Generic)
+
+instance Binary ElaboratedConfiguredPackage
+
+instance Package ElaboratedConfiguredPackage where
+  packageId = pkgSourceId
+
+instance HasUnitId ElaboratedConfiguredPackage where
+  installedUnitId = pkgInstalledId
+
+instance PackageFixedDeps ElaboratedConfiguredPackage where
+  depends = fmap (map installedPackageId) . pkgDependencies
+
+-- | This is used in the install plan to indicate how the package will be
+-- built.
+--
+data BuildStyle =
+    -- | The classic approach where the package is built, then the files
+    -- installed into some location and the result registered in a package db.
+    --
+    -- If the package came from a tarball then it's built in a temp dir and
+    -- the results discarded.
+    BuildAndInstall
+
+    -- | The package is built, but the files are not installed anywhere,
+    -- rather the build dir is kept and the package is registered inplace.
+    --
+    -- Such packages can still subsequently be installed.
+    --
+    -- Typically 'BuildAndInstall' packages will only depend on other
+    -- 'BuildAndInstall' style packages and not on 'BuildInplaceOnly' ones.
+    --
+  | BuildInplaceOnly
+  deriving (Eq, Show, Generic)
+
+instance Binary BuildStyle
+
+type CabalFileText = LBS.ByteString
+
+type ElaboratedReadyPackage = GenericReadyPackage ElaboratedConfiguredPackage
+
+--TODO: [code cleanup] this duplicates the InstalledPackageInfo quite a bit in an install plan
+-- because the same ipkg is used by many packages. So the binary file will be big.
+-- Could we keep just (ipkgid, deps) instead of the whole InstalledPackageInfo?
+-- or transform to a shared form when serialising / deserialising
+
+data GenericBuildResult ipkg iresult ifailure
+                  = BuildFailure ifailure
+                  | BuildSuccess (Maybe ipkg) iresult
+  deriving (Eq, Show, Generic)
+
+instance (Binary ipkg, Binary iresult, Binary ifailure) =>
+         Binary (GenericBuildResult ipkg iresult ifailure)
+
+type BuildResult  = GenericBuildResult InstalledPackageInfo
+                                       BuildSuccess BuildFailure
+
+data BuildSuccess = BuildOk DocsResult TestsResult
+  deriving (Eq, Show, Generic)
+
+data DocsResult  = DocsNotTried  | DocsFailed  | DocsOk
+  deriving (Eq, Show, Generic)
+
+data TestsResult = TestsNotTried | TestsOk
+  deriving (Eq, Show, Generic)
+
+data BuildFailure = PlanningFailed              --TODO: [required eventually] not yet used
+                  | DependentFailed PackageId
+                  | DownloadFailed  String      --TODO: [required eventually] not yet used
+                  | UnpackFailed    String      --TODO: [required eventually] not yet used
+                  | ConfigureFailed String
+                  | BuildFailed     String
+                  | TestsFailed     String      --TODO: [required eventually] not yet used
+                  | InstallFailed   String
+  deriving (Eq, Show, Typeable, Generic)
+
+instance Exception BuildFailure
+
+instance Binary BuildFailure
+instance Binary BuildSuccess
+instance Binary DocsResult
+instance Binary TestsResult
+
+
+sanityCheckElaboratedConfiguredPackage :: ElaboratedSharedConfig
+                                       -> ElaboratedConfiguredPackage
+                                       -> Bool
+sanityCheckElaboratedConfiguredPackage sharedConfig
+                                       pkg@ElaboratedConfiguredPackage{..} =
+
+    pkgStanzasEnabled `Set.isSubsetOf` pkgStanzasAvailable
+
+    -- the stanzas explicitly enabled should be available and enabled
+ && Map.keysSet (Map.filter id pkgStanzasRequested)
+      `Set.isSubsetOf` pkgStanzasEnabled
+
+    -- the stanzas explicitly disabled should not be available
+ && Set.null (Map.keysSet (Map.filter not pkgStanzasRequested)
+                `Set.intersection` pkgStanzasAvailable)
+
+ && (pkgBuildStyle == BuildInplaceOnly ||
+     installedPackageId pkg == hashedInstalledPackageId
+                                 (packageHashInputs sharedConfig pkg))
+
+ && (pkgBuildStyle == BuildInplaceOnly ||
+     Set.null pkgStanzasAvailable)
+
+
+------------------------------------------------------------------------------
+-- * Deciding what to do: making an 'ElaboratedInstallPlan'
+------------------------------------------------------------------------------
+
+rebuildInstallPlan :: Verbosity
+                   -> FilePath -> DistDirLayout -> CabalDirLayout
+                   -> ProjectConfig
+                   -> IO ( ElaboratedInstallPlan
+                         , ElaboratedSharedConfig
+                         , ProjectConfig )
+rebuildInstallPlan verbosity
+                   projectRootDir
+                   distDirLayout@DistDirLayout {
+                     distDirectory,
+                     distProjectCacheFile,
+                     distProjectCacheDirectory
+                   }
+                   cabalDirLayout@CabalDirLayout {
+                     cabalPackageCacheDirectory,
+                     cabalStoreDirectory,
+                     cabalStorePackageDB
+                   }
+                   cliConfig =
+    runRebuild $ do
+    progsearchpath <- liftIO $ getSystemSearchPath
+    let cliConfigPersistent = cliConfig { projectConfigBuildOnly = mempty }
+
+    -- The overall improved plan is cached
+    rerunIfChanged verbosity projectRootDir fileMonitorImprovedPlan
+                   -- react to changes in command line args and the path
+                   (cliConfigPersistent, progsearchpath) $ do
+
+      -- And so is the elaborated plan that the improved plan based on
+      (elaboratedPlan, elaboratedShared,
+       projectConfig) <-
+        rerunIfChanged verbosity projectRootDir fileMonitorElaboratedPlan
+                       (cliConfigPersistent, progsearchpath) $ do
+
+          (projectConfig, projectConfigTransient) <- phaseReadProjectConfig
+          localPackages <- phaseReadLocalPackages projectConfig
+          compilerEtc   <- phaseConfigureCompiler projectConfig
+          solverPlan    <- phaseRunSolver         projectConfigTransient
+                                                  compilerEtc localPackages
+          (elaboratedPlan,
+           elaboratedShared) <- phaseElaboratePlan projectConfigTransient
+                                                   compilerEtc
+                                                   solverPlan localPackages
+
+          return (elaboratedPlan, elaboratedShared,
+                  projectConfig)
+
+      -- The improved plan changes each time we install something, whereas
+      -- the underlying elaborated plan only changes when input config
+      -- changes, so it's worth caching them separately.
+      improvedPlan <- phaseImprovePlan elaboratedPlan elaboratedShared
+      return (improvedPlan, elaboratedShared, projectConfig)
+
+  where
+    fileMonitorCompiler       = newFileMonitorInCacheDir "compiler"
+    fileMonitorSolverPlan     = newFileMonitorInCacheDir "solver-plan"
+    fileMonitorSourceHashes   = newFileMonitorInCacheDir "source-hashes"
+    fileMonitorElaboratedPlan = newFileMonitorInCacheDir "elaborated-plan"
+    fileMonitorImprovedPlan   = newFileMonitorInCacheDir "improved-plan"
+
+    newFileMonitorInCacheDir :: Eq a => FilePath -> FileMonitor a b
+    newFileMonitorInCacheDir  = newFileMonitor . distProjectCacheFile
+
+    -- Read the cabal.project (or implicit config) and combine it with
+    -- arguments from the command line
+    --
+    phaseReadProjectConfig :: Rebuild (ProjectConfig, ProjectConfig)
+    phaseReadProjectConfig = do
+      liftIO $ do
+        info verbosity "Project settings changed, reconfiguring..."
+        createDirectoryIfMissingVerbose verbosity False distDirectory
+        createDirectoryIfMissingVerbose verbosity False distProjectCacheDirectory
+
+      projectConfig <- readProjectConfig verbosity projectRootDir
+
+      -- The project config comming from the command line includes "build only"
+      -- flags that we don't cache persistently (because like all "build only"
+      -- flags they do not affect the value of the outcome) but that we do
+      -- sometimes using during planning (in particular the http transport)
+      let projectConfigTransient  = projectConfig <> cliConfig
+          projectConfigPersistent = projectConfig
+                                 <> cliConfig {
+                                      projectConfigBuildOnly = mempty
+                                    }
+      liftIO $ writeProjectConfigFile (distProjectCacheFile "config")
+                                      projectConfigPersistent
+      return (projectConfigPersistent, projectConfigTransient)
+
+    -- Look for all the cabal packages in the project
+    -- some of which may be local src dirs, tarballs etc
+    --
+    phaseReadLocalPackages :: ProjectConfig
+                           -> Rebuild [UnresolvedSourcePackage]
+    phaseReadLocalPackages projectConfig = do
+
+      localCabalFiles <- findProjectPackages projectRootDir projectConfig
+      mapM (readSourcePackage verbosity) localCabalFiles
+
+
+    -- Configure the compiler we're using.
+    --
+    -- This is moderately expensive and doesn't change that often so we cache
+    -- it independently.
+    --
+    phaseConfigureCompiler :: ProjectConfig
+                           -> Rebuild (Compiler, Platform, ProgramDb)
+    phaseConfigureCompiler ProjectConfig{projectConfigShared} = do
+        progsearchpath <- liftIO $ getSystemSearchPath
+        rerunIfChanged verbosity projectRootDir fileMonitorCompiler
+                       (hcFlavor, hcPath, hcPkg, progsearchpath) $ do
+
+          liftIO $ info verbosity "Compiler settings changed, reconfiguring..."
+          result@(_, _, progdb) <- liftIO $
+            Cabal.configCompilerEx
+              hcFlavor hcPath hcPkg
+              defaultProgramDb verbosity
+
+          monitorFiles (programsMonitorFiles progdb)
+
+          return result
+      where
+        hcFlavor = flagToMaybe projectConfigHcFlavor
+        hcPath   = flagToMaybe projectConfigHcPath
+        hcPkg    = flagToMaybe projectConfigHcPkg
+        ProjectConfigShared {
+          projectConfigHcFlavor,
+          projectConfigHcPath,
+          projectConfigHcPkg
+        }        = projectConfigShared
+
+
+    -- Run the solver to get the initial install plan.
+    -- This is expensive so we cache it independently.
+    --
+    phaseRunSolver :: ProjectConfig
+                   -> (Compiler, Platform, ProgramDb)
+                   -> [UnresolvedSourcePackage]
+                   -> Rebuild (SolverInstallPlan, PackagesImplicitSetupDeps)
+    phaseRunSolver projectConfig@ProjectConfig {
+                     projectConfigShared,
+                     projectConfigBuildOnly
+                   }
+                   (compiler, platform, progdb)
+                   localPackages =
+        rerunIfChanged verbosity projectRootDir fileMonitorSolverPlan
+                       (solverSettings, cabalPackageCacheDirectory,
+                        localPackages, localPackagesEnabledStanzas,
+                        compiler, platform, programsDbSignature progdb) $ do
+
+          installedPkgIndex <- getInstalledPackages verbosity
+                                                    compiler progdb platform
+                                                    corePackageDbs
+          sourcePkgDb       <- getSourcePackages    verbosity withRepoCtx
+          pkgConfigDB       <- liftIO $
+                               readPkgConfigDb      verbosity progdb
+
+          liftIO $ do
+            solver <- chooseSolver verbosity
+                                   (solverSettingSolver solverSettings)
+                                   (compilerInfo compiler)
+
+            notice verbosity "Resolving dependencies..."
+            foldProgress logMsg die return $
+              planPackages compiler platform solver solverSettings
+                           installedPkgIndex sourcePkgDb pkgConfigDB
+                           localPackages localPackagesEnabledStanzas
+      where
+        corePackageDbs = [GlobalPackageDB]
+        withRepoCtx    = projectConfigWithSolverRepoContext verbosity
+                           cabalPackageCacheDirectory
+                           projectConfigShared
+                           projectConfigBuildOnly
+        solverSettings = resolveSolverSettings projectConfigShared
+        logMsg message rest = debugNoWrap verbosity message >> rest
+
+        localPackagesEnabledStanzas =
+          Map.fromList
+            [ (pkgname, stanzas)
+            | pkg <- localPackages
+            , let pkgname            = packageName pkg
+                  testsEnabled       = lookupLocalPackageConfig
+                                         packageConfigTests
+                                         projectConfig pkgname
+                  benchmarksEnabled  = lookupLocalPackageConfig
+                                         packageConfigBenchmarks
+                                         projectConfig pkgname
+                  stanzas =
+                    Map.fromList $
+                      [ (TestStanzas, enabled)
+                      | enabled <- flagToList testsEnabled ]
+                   ++ [ (BenchStanzas , enabled)
+                      | enabled <- flagToList benchmarksEnabled ]
+            ]
+
+    -- Elaborate the solver's install plan to get a fully detailed plan. This
+    -- version of the plan has the final nix-style hashed ids.
+    --
+    phaseElaboratePlan :: ProjectConfig
+                       -> (Compiler, Platform, ProgramDb)
+                       -> (SolverInstallPlan, PackagesImplicitSetupDeps)
+                       -> [SourcePackage loc]
+                       -> Rebuild ( ElaboratedInstallPlan
+                                  , ElaboratedSharedConfig )
+    phaseElaboratePlan ProjectConfig {
+                         projectConfigShared,
+                         projectConfigLocalPackages,
+                         projectConfigSpecificPackage,
+                         projectConfigBuildOnly
+                       }
+                       (compiler, platform, progdb)
+                       (solverPlan, pkgsImplicitSetupDeps)
+                       localPackages = do
+
+        liftIO $ debug verbosity "Elaborating the install plan..."
+
+        sourcePackageHashes <-
+          rerunIfChanged verbosity projectRootDir fileMonitorSourceHashes
+                         (map packageId $ InstallPlan.toList solverPlan) $
+            getPackageSourceHashes verbosity withRepoCtx solverPlan
+
+        defaultInstallDirs <- liftIO $ userInstallDirTemplates compiler
+        return $
+          elaborateInstallPlan
+            platform compiler progdb
+            distDirLayout
+            cabalDirLayout
+            solverPlan
+            pkgsImplicitSetupDeps
+            localPackages
+            sourcePackageHashes
+            defaultInstallDirs
+            projectConfigShared
+            projectConfigLocalPackages
+            projectConfigSpecificPackage
+      where
+        withRepoCtx = projectConfigWithSolverRepoContext verbosity
+                        cabalPackageCacheDirectory
+                        projectConfigShared
+                        projectConfigBuildOnly
+
+
+    -- Improve the elaborated install plan. The elaborated plan consists
+    -- mostly of source packages (with full nix-style hashed ids). Where
+    -- corresponding installed packages already exist in the store, replace
+    -- them in the plan.
+    --
+    -- Note that we do monitor the store's package db here, so we will redo
+    -- this improvement phase when the db changes -- including as a result of
+    -- executing a plan and installing things.
+    --
+    phaseImprovePlan :: ElaboratedInstallPlan
+                     -> ElaboratedSharedConfig
+                     -> Rebuild ElaboratedInstallPlan
+    phaseImprovePlan elaboratedPlan elaboratedShared = do
+
+        liftIO $ debug verbosity "Improving the install plan..."
+        recreateDirectory verbosity True storeDirectory
+        storePkgIndex <- getPackageDBContents verbosity
+                                              compiler progdb platform
+                                              storePackageDb
+        let improvedPlan = improveInstallPlanWithPreExistingPackages
+                             storePkgIndex
+                             elaboratedPlan
+        return improvedPlan
+
+      where
+        storeDirectory  = cabalStoreDirectory (compilerId compiler)
+        storePackageDb  = cabalStorePackageDB (compilerId compiler)
+        ElaboratedSharedConfig {
+          pkgConfigCompiler  = compiler,
+          pkgConfigPlatform  = platform,
+          pkgConfigProgramDb = progdb
+        } = elaboratedShared
+
+
+programsMonitorFiles :: ProgramDb -> [MonitorFilePath]
+programsMonitorFiles progdb =
+    [ monitor
+    | prog    <- configuredPrograms progdb
+    , monitor <- monitorFileSearchPath (programMonitorFiles prog)
+                                       (programPath prog)
+    ]
+
+-- | Select the bits of a 'ProgramDb' to monitor for value changes.
+-- Use 'programsMonitorFiles' for the files to monitor.
+--
+programsDbSignature :: ProgramDb -> [ConfiguredProgram]
+programsDbSignature progdb =
+    [ prog { programMonitorFiles = []
+           , programOverrideEnv  = filter ((/="PATH") . fst)
+                                          (programOverrideEnv prog) }
+    | prog <- configuredPrograms progdb ]
+
+getInstalledPackages :: Verbosity
+                     -> Compiler -> ProgramDb -> Platform
+                     -> PackageDBStack
+                     -> Rebuild InstalledPackageIndex
+getInstalledPackages verbosity compiler progdb platform packagedbs = do
+    monitorFiles . map monitorFileOrDirectory
+      =<< liftIO (IndexUtils.getInstalledPackagesMonitorFiles
+                    verbosity compiler
+                    packagedbs progdb platform)
+    liftIO $ IndexUtils.getInstalledPackages
+               verbosity compiler
+               packagedbs progdb
+
+getPackageDBContents :: Verbosity
+                     -> Compiler -> ProgramDb -> Platform
+                     -> PackageDB
+                     -> Rebuild InstalledPackageIndex
+getPackageDBContents verbosity compiler progdb platform packagedb = do
+    monitorFiles . map monitorFileOrDirectory
+      =<< liftIO (IndexUtils.getInstalledPackagesMonitorFiles
+                    verbosity compiler
+                    [packagedb] progdb platform)
+    liftIO $ do
+      createPackageDBIfMissing verbosity compiler
+                               progdb [packagedb]
+      Cabal.getPackageDBContents verbosity compiler
+                                 packagedb progdb
+
+getSourcePackages :: Verbosity -> (forall a. (RepoContext -> IO a) -> IO a)
+                  -> Rebuild SourcePackageDb
+getSourcePackages verbosity withRepoCtx = do
+    (sourcePkgDb, repos) <-
+      liftIO $
+        withRepoCtx $ \repoctx -> do
+          sourcePkgDb <- IndexUtils.getSourcePackages verbosity repoctx
+          return (sourcePkgDb, repoContextRepos repoctx)
+
+    monitorFiles . map monitorFile
+                 . IndexUtils.getSourcePackagesMonitorFiles
+                 $ repos
+    return sourcePkgDb
+
+createPackageDBIfMissing :: Verbosity -> Compiler -> ProgramDb
+                         -> PackageDBStack -> IO ()
+createPackageDBIfMissing verbosity compiler progdb packageDbs =
+  case reverse packageDbs of
+    SpecificPackageDB dbPath : _ -> do
+      exists <- liftIO $ Cabal.doesPackageDBExist dbPath
+      unless exists $ do
+        createDirectoryIfMissingVerbose verbosity False (takeDirectory dbPath)
+        Cabal.createPackageDB verbosity compiler progdb False dbPath
+    _ -> return ()
+
+
+recreateDirectory :: Verbosity -> Bool -> FilePath -> Rebuild ()
+recreateDirectory verbosity createParents dir = do
+    liftIO $ createDirectoryIfMissingVerbose verbosity createParents dir
+    monitorFiles [monitorDirectoryExistence dir]
+
+
+-- | Get the 'HashValue' for all the source packages where we use hashes,
+-- and download any packages required to do so.
+--
+-- Note that we don't get hashes for local unpacked packages.
+--
+getPackageSourceHashes :: Verbosity
+                       -> (forall a. (RepoContext -> IO a) -> IO a)
+                       -> SolverInstallPlan
+                       -> Rebuild (Map PackageId PackageSourceHash)
+getPackageSourceHashes verbosity withRepoCtx installPlan = do
+
+    -- Determine which packages need fetching, and which are present already
+    --
+    pkgslocs <- liftIO $ sequence
+      [ do let locm = packageSource pkg
+           mloc <- checkFetched locm
+           return (pkg, locm, mloc)
+      | InstallPlan.Configured
+          (ConfiguredPackage pkg _ _ _) <- InstallPlan.toList installPlan ]
+
+    let requireDownloading = [ (pkg, locm) | (pkg, locm, Nothing) <- pkgslocs ]
+        alreadyDownloaded  = [ (pkg, loc)  | (pkg, _, Just loc)   <- pkgslocs ]
+
+    -- Download the ones we need
+    --
+    newlyDownloaded <-
+      if null requireDownloading
+        then return []
+        else liftIO $
+              withRepoCtx $ \repoctx ->
+                sequence
+                  [ do loc <- fetchPackage verbosity repoctx locm
+                       return (pkg, loc)
+                  | (pkg, locm) <- requireDownloading ]
+
+    -- Get the hashes of all the tarball packages (i.e. not local dir pkgs)
+    --
+    let pkgsTarballs =
+          [ (packageId pkg, tarball)
+          | (pkg, srcloc) <- newlyDownloaded ++ alreadyDownloaded
+          , tarball <- maybeToList (tarballFileLocation srcloc) ]
+
+    monitorFiles [ monitorFile tarball | (_pkgid, tarball) <- pkgsTarballs ]
+
+    liftM Map.fromList $ liftIO $
+      sequence
+        [ do srchash <- readFileHashValue tarball
+             return (pkgid, srchash)
+        | (pkgid, tarball) <- pkgsTarballs ]
+  where
+    tarballFileLocation (LocalUnpackedPackage _dir)      = Nothing
+    tarballFileLocation (LocalTarballPackage    tarball) = Just tarball
+    tarballFileLocation (RemoteTarballPackage _ tarball) = Just tarball
+    tarballFileLocation (RepoTarballPackage _ _ tarball) = Just tarball
+
+
+-- ------------------------------------------------------------
+-- * Installation planning
+-- ------------------------------------------------------------
+
+planPackages :: Compiler
+             -> Platform
+             -> Solver -> SolverSettings
+             -> InstalledPackageIndex
+             -> SourcePackageDb
+             -> PkgConfigDb
+             -> [UnresolvedSourcePackage]
+             -> Map PackageName (Map OptionalStanza Bool)
+             -> Progress String String
+                         (SolverInstallPlan, PackagesImplicitSetupDeps)
+planPackages comp platform solver SolverSettings{..}
+             installedPkgIndex sourcePkgDb pkgConfigDB
+             localPackages pkgStanzasEnable =
+
+    rememberImplicitSetupDeps (depResolverSourcePkgIndex stdResolverParams) <$>
+
+    resolveDependencies
+      platform (compilerInfo comp)
+      pkgConfigDB solver
+      resolverParams
+
+  where
+
+    --TODO: [nice to have] disable multiple instances restriction in the solver, but then
+    -- make sure we can cope with that in the output.
+    resolverParams =
+
+        setMaxBackjumps solverSettingMaxBackjumps
+
+        --TODO: [required eventually] should only be configurable for custom installs
+   -- . setIndependentGoals solverSettingIndependentGoals
+
+      . setReorderGoals solverSettingReorderGoals
+
+        --TODO: [required eventually] should only be configurable for custom installs
+   -- . setAvoidReinstalls solverSettingAvoidReinstalls
+
+        --TODO: [required eventually] should only be configurable for custom installs
+   -- . setShadowPkgs solverSettingShadowPkgs
+
+      . setStrongFlags solverSettingStrongFlags
+
+        --TODO: [required eventually] decide if we need to prefer installed for
+        -- global packages, or prefer latest even for global packages. Perhaps
+        -- should be configurable but with a different name than "upgrade-dependencies".
+      . setPreferenceDefault PreferLatestForSelected
+                           {-(if solverSettingUpgradeDeps
+                                then PreferAllLatest
+                                else PreferLatestForSelected)-}
+
+      . removeUpperBounds solverSettingAllowNewer
+
+      . addDefaultSetupDependencies (defaultSetupDeps platform
+                                   . PD.packageDescription
+                                   . packageDescription)
+
+      . addPreferences
+          -- preferences from the config file or command line
+          [ PackageVersionPreference name ver
+          | Dependency name ver <- solverSettingPreferences ]
+
+      . addConstraints
+          -- version constraints from the config file or command line
+            [ LabeledPackageConstraint (userToPackageConstraint pc) src
+            | (pc, src) <- solverSettingConstraints ]
+
+      . addPreferences
+          -- enable stanza preference where the user did not specify
+          [ PackageStanzasPreference pkgname stanzas
+          | pkg <- localPackages
+          , let pkgname = packageName pkg
+                stanzaM = Map.findWithDefault Map.empty pkgname pkgStanzasEnable
+                stanzas = [ stanza | stanza <- [minBound..maxBound]
+                          , Map.lookup stanza stanzaM == Nothing ]
+          , not (null stanzas)
+          ]
+
+      . addConstraints
+          -- enable stanza constraints where the user asked to enable
+          [ LabeledPackageConstraint
+              (PackageConstraintStanzas pkgname stanzas)
+              ConstraintSourceConfigFlagOrTarget
+          | pkg <- localPackages
+          , let pkgname = packageName pkg
+                stanzaM = Map.findWithDefault Map.empty pkgname pkgStanzasEnable
+                stanzas = [ stanza | stanza <- [minBound..maxBound]
+                          , Map.lookup stanza stanzaM == Just True ]
+          , not (null stanzas)
+          ]
+
+      . addConstraints
+          --TODO: [nice to have] this just applies all flags to all targets which
+          -- is silly. We should check if the flags are appropriate
+          [ LabeledPackageConstraint
+              (PackageConstraintFlags pkgname flags)
+              ConstraintSourceConfigFlagOrTarget
+          | let flags = solverSettingFlagAssignment
+          , not (null flags)
+          , pkg <- localPackages
+          , let pkgname = packageName pkg ]
+
+      $ stdResolverParams
+
+    stdResolverParams =
+      standardInstallPolicy
+        installedPkgIndex sourcePkgDb
+        (map SpecificSourcePackage localPackages)
+
+
+------------------------------------------------------------------------------
+-- * Install plan post-processing
+------------------------------------------------------------------------------
+
+-- This phase goes from the InstallPlan we get from the solver and has to
+-- make an elaborated install plan.
+--
+-- We go in two steps:
+--
+--  1. elaborate all the source packages that the solver has chosen.
+--  2. swap source packages for pre-existing installed packages wherever
+--     possible.
+--
+-- We do it in this order, elaborating and then replacing, because the easiest
+-- way to calculate the installed package ids used for the replacement step is
+-- from the elaborated configuration for each package.
+
+
+
+
+------------------------------------------------------------------------------
+-- * Install plan elaboration
+------------------------------------------------------------------------------
+
+-- | Produce an elaborated install plan using the policy for local builds with
+-- a nix-style shared store.
+--
+-- In theory should be able to make an elaborated install plan with a policy
+-- matching that of the classic @cabal install --user@ or @--global@
+--
+elaborateInstallPlan
+  :: Platform -> Compiler -> ProgramDb
+  -> DistDirLayout
+  -> CabalDirLayout
+  -> SolverInstallPlan
+  -> PackagesImplicitSetupDeps
+  -> [SourcePackage loc]
+  -> Map PackageId PackageSourceHash
+  -> InstallDirs.InstallDirTemplates
+  -> ProjectConfigShared
+  -> PackageConfig
+  -> Map PackageName PackageConfig
+  -> (ElaboratedInstallPlan, ElaboratedSharedConfig)
+elaborateInstallPlan platform compiler progdb
+                     DistDirLayout{..}
+                     cabalDirLayout@CabalDirLayout{cabalStorePackageDB}
+                     solverPlan pkgsImplicitSetupDeps localPackages
+                     sourcePackageHashes
+                     defaultInstallDirs
+                     _sharedPackageConfig
+                     localPackagesConfig
+                     perPackageConfig =
+    (elaboratedInstallPlan, elaboratedSharedConfig)
+  where
+    elaboratedSharedConfig =
+      ElaboratedSharedConfig {
+        pkgConfigPlatform         = platform,
+        pkgConfigCompiler         = compiler,
+        pkgConfigProgramDb        = progdb
+      }
+
+    elaboratedInstallPlan =
+      flip InstallPlan.mapPreservingGraph solverPlan $ \mapDep planpkg ->
+        case planpkg of
+          InstallPlan.PreExisting pkg ->
+            InstallPlan.PreExisting pkg
+
+          InstallPlan.Configured  pkg ->
+            InstallPlan.Configured
+              (elaborateConfiguredPackage (fixupDependencies mapDep pkg))
+
+          _ -> error "elaborateInstallPlan: unexpected package state"
+
+    -- remap the installed package ids of the direct deps, since we're
+    -- changing the installed package ids of all the packages to use the
+    -- final nix-style hashed ids.
+    fixupDependencies mapDep
+       (ConfiguredPackage pkg flags stanzas deps) =
+        ConfiguredPackage pkg flags stanzas deps'
+      where
+        deps' = fmap (map (\d -> d { confInstId = mapDep (confInstId d) })) deps
+
+    elaborateConfiguredPackage :: ConfiguredPackage UnresolvedPkgLoc
+                               -> ElaboratedConfiguredPackage
+    elaborateConfiguredPackage
+        pkg@(ConfiguredPackage (SourcePackage pkgid gdesc srcloc descOverride)
+                               flags stanzas deps) =
+        elaboratedPackage
+      where
+        -- Knot tying: the final elaboratedPackage includes the
+        -- pkgInstalledId, which is calculated by hashing many
+        -- of the other fields of the elaboratedPackage.
+        --
+        elaboratedPackage = ElaboratedConfiguredPackage {..}
+
+        pkgInstalledId
+          | shouldBuildInplaceOnly pkg
+          = mkUnitId (display pkgid ++ "-inplace")
+
+          | otherwise
+          = assert (isJust pkgSourceHash) $
+            hashedInstalledPackageId
+              (packageHashInputs
+                elaboratedSharedConfig
+                elaboratedPackage)  -- recursive use of elaboratedPackage
+
+          | otherwise
+          = error $ "elaborateInstallPlan: non-inplace package "
+                 ++ " is missing a source hash: " ++ display pkgid
+
+        -- All the other fields of the ElaboratedConfiguredPackage
+        --
+        pkgSourceId         = pkgid
+        pkgDescription      = let Right (desc, _) =
+                                    PD.finalizePackageDescription
+                                    flags (const True)
+                                    platform (compilerInfo compiler)
+                                    [] gdesc
+                               in desc
+        pkgFlagAssignment   = flags
+        pkgFlagDefaults     = [ (Cabal.flagName flag, Cabal.flagDefault flag)
+                              | flag <- PD.genPackageFlags gdesc ]
+        pkgDependencies     = deps
+        pkgStanzasAvailable = Set.fromList stanzas
+        pkgStanzasRequested =
+            Map.fromList $ [ (TestStanzas,  v) | v <- maybeToList tests ]
+                        ++ [ (BenchStanzas, v) | v <- maybeToList benchmarks ]
+          where
+            tests, benchmarks :: Maybe Bool
+            tests      = perPkgOptionMaybe pkgid packageConfigTests
+            benchmarks = perPkgOptionMaybe pkgid packageConfigBenchmarks
+
+        -- These sometimes get adjusted later
+        pkgStanzasEnabled   = Set.empty
+        pkgBuildTargets     = []
+        pkgReplTarget       = Nothing
+        pkgBuildHaddocks    = False
+
+        pkgSourceLocation   = srcloc
+        pkgSourceHash       = Map.lookup pkgid sourcePackageHashes
+        pkgBuildStyle       = if shouldBuildInplaceOnly pkg
+                                then BuildInplaceOnly else BuildAndInstall
+        pkgBuildPackageDBStack    = buildAndRegisterDbs
+        pkgRegisterPackageDBStack = buildAndRegisterDbs
+        pkgRequiresRegistration   = PD.hasPublicLib (PD.packageDescription gdesc)
+
+        pkgSetupScriptStyle       = packageSetupScriptStylePostSolver
+                                      pkgsImplicitSetupDeps pkg pkgDescription
+        pkgSetupScriptCliVersion  = packageSetupScriptSpecVersion
+                                      pkgSetupScriptStyle pkgDescription deps
+        pkgSetupPackageDBStack    = buildAndRegisterDbs
+
+        buildAndRegisterDbs
+          | shouldBuildInplaceOnly pkg = inplacePackageDbs
+          | otherwise                  = storePackageDbs
+
+        pkgDescriptionOverride    = descOverride
+
+        pkgVanillaLib    = perPkgOptionFlag pkgid True packageConfigVanillaLib --TODO: [required feature]: also needs to be handled recursively
+        pkgSharedLib     = pkgid `Set.member` pkgsUseSharedLibrary
+        pkgDynExe        = perPkgOptionFlag pkgid False packageConfigDynExe
+        pkgGHCiLib       = perPkgOptionFlag pkgid False packageConfigGHCiLib --TODO: [required feature] needs to default to enabled on windows still
+
+        pkgProfExe       = perPkgOptionFlag pkgid False packageConfigProf
+        pkgProfLib       = pkgid `Set.member` pkgsUseProfilingLibrary
+
+        (pkgProfExeDetail,
+         pkgProfLibDetail) = perPkgOptionLibExeFlag pkgid ProfDetailDefault
+                               packageConfigProfDetail
+                               packageConfigProfLibDetail
+        pkgCoverage      = perPkgOptionFlag pkgid False packageConfigCoverage
+
+        pkgOptimization  = perPkgOptionFlag pkgid NormalOptimisation packageConfigOptimization
+        pkgSplitObjs     = perPkgOptionFlag pkgid False packageConfigSplitObjs
+        pkgStripLibs     = perPkgOptionFlag pkgid False packageConfigStripLibs
+        pkgStripExes     = perPkgOptionFlag pkgid False packageConfigStripExes
+        pkgDebugInfo     = perPkgOptionFlag pkgid NoDebugInfo packageConfigDebugInfo
+
+        pkgConfigureScriptArgs = perPkgOptionList pkgid packageConfigConfigureArgs
+        pkgExtraLibDirs        = perPkgOptionList pkgid packageConfigExtraLibDirs
+        pkgExtraFrameworkDirs  = perPkgOptionList pkgid packageConfigExtraFrameworkDirs
+        pkgExtraIncludeDirs    = perPkgOptionList pkgid packageConfigExtraIncludeDirs
+        pkgProgPrefix          = perPkgOptionMaybe pkgid packageConfigProgPrefix
+        pkgProgSuffix          = perPkgOptionMaybe pkgid packageConfigProgSuffix
+
+        pkgInstallDirs
+          | shouldBuildInplaceOnly pkg
+          -- use the ordinary default install dirs
+          = (InstallDirs.absoluteInstallDirs
+               pkgid
+               (installedUnitId pkg)
+               (compilerInfo compiler)
+               InstallDirs.NoCopyDest
+               platform
+               defaultInstallDirs) {
+
+              InstallDirs.libsubdir  = "", -- absoluteInstallDirs sets these as
+              InstallDirs.datasubdir = ""  -- 'undefined' but we have to use
+            }                              -- them as "Setup.hs configure" args
+
+          | otherwise
+          -- use special simplified install dirs
+          = storePackageInstallDirs
+              cabalDirLayout
+              (compilerId compiler)
+              pkgInstalledId
+
+        pkgHaddockHoogle       = perPkgOptionFlag pkgid False packageConfigHaddockHoogle
+        pkgHaddockHtml         = perPkgOptionFlag pkgid False packageConfigHaddockHtml
+        pkgHaddockHtmlLocation = perPkgOptionMaybe pkgid packageConfigHaddockHtmlLocation
+        pkgHaddockExecutables  = perPkgOptionFlag pkgid False packageConfigHaddockExecutables
+        pkgHaddockTestSuites   = perPkgOptionFlag pkgid False packageConfigHaddockTestSuites
+        pkgHaddockBenchmarks   = perPkgOptionFlag pkgid False packageConfigHaddockBenchmarks
+        pkgHaddockInternal     = perPkgOptionFlag pkgid False packageConfigHaddockInternal
+        pkgHaddockCss          = perPkgOptionMaybe pkgid packageConfigHaddockCss
+        pkgHaddockHscolour     = perPkgOptionFlag pkgid False packageConfigHaddockHscolour
+        pkgHaddockHscolourCss  = perPkgOptionMaybe pkgid packageConfigHaddockHscolourCss
+        pkgHaddockContents     = perPkgOptionMaybe pkgid packageConfigHaddockContents
+
+    perPkgOptionFlag  :: PackageId -> a ->  (PackageConfig -> Flag a) -> a
+    perPkgOptionMaybe :: PackageId ->       (PackageConfig -> Flag a) -> Maybe a
+    perPkgOptionList  :: PackageId ->       (PackageConfig -> [a])    -> [a]
+
+    perPkgOptionFlag  pkgid def f = fromFlagOrDefault def (lookupPerPkgOption pkgid f)
+    perPkgOptionMaybe pkgid     f = flagToMaybe (lookupPerPkgOption pkgid f)
+    perPkgOptionList  pkgid     f = lookupPerPkgOption pkgid f
+
+    perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib)
+      where
+        exe = fromFlagOrDefault def bothflag
+        lib = fromFlagOrDefault def (bothflag <> libflag)
+
+        bothflag = lookupPerPkgOption pkgid fboth
+        libflag  = lookupPerPkgOption pkgid flib
+
+    lookupPerPkgOption :: (Package pkg, Monoid m)
+                       => pkg -> (PackageConfig -> m) -> m
+    lookupPerPkgOption pkg f
+      -- the project config specifies values that apply to packages local to
+      -- but by default non-local packages get all default config values
+      -- the project, and can specify per-package values for any package,
+      | isLocalToProject pkg = local <> perpkg
+      | otherwise            =          perpkg
+      where
+        local  = f localPackagesConfig
+        perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig)
+
+    inplacePackageDbs = storePackageDbs
+                     ++ [ distPackageDB (compilerId compiler) ]
+
+    storePackageDbs   = [ GlobalPackageDB
+                        , cabalStorePackageDB (compilerId compiler) ]
+
+    -- For this local build policy, every package that lives in a local source
+    -- dir (as opposed to a tarball), or depends on such a package, will be
+    -- built inplace into a shared dist dir. Tarball packages that depend on
+    -- source dir packages will also get unpacked locally.
+    shouldBuildInplaceOnly :: HasUnitId pkg => pkg -> Bool
+    shouldBuildInplaceOnly pkg = Set.member (installedPackageId pkg)
+                                            pkgsToBuildInplaceOnly
+
+    pkgsToBuildInplaceOnly :: Set InstalledPackageId
+    pkgsToBuildInplaceOnly =
+        Set.fromList
+      $ map installedPackageId
+      $ InstallPlan.reverseDependencyClosure
+          solverPlan
+          [ fakeUnitId (packageId pkg)
+          | pkg <- localPackages ]
+
+    isLocalToProject :: Package pkg => pkg -> Bool
+    isLocalToProject pkg = Set.member (packageId pkg)
+                                      pkgsLocalToProject
+
+    pkgsLocalToProject :: Set PackageId
+    pkgsLocalToProject = Set.fromList [ packageId pkg | pkg <- localPackages ]
+
+    pkgsUseSharedLibrary :: Set PackageId
+    pkgsUseSharedLibrary =
+        packagesWithDownwardClosedProperty needsSharedLib
+      where
+        needsSharedLib pkg =
+            fromMaybe compilerShouldUseSharedLibByDefault
+                      (liftM2 (||) pkgSharedLib pkgDynExe)
+          where
+            pkgid        = packageId pkg
+            pkgSharedLib = perPkgOptionMaybe pkgid packageConfigSharedLib
+            pkgDynExe    = perPkgOptionMaybe pkgid packageConfigDynExe
+
+    --TODO: [code cleanup] move this into the Cabal lib. It's currently open
+    -- coded in Distribution.Simple.Configure, but should be made a proper
+    -- function of the Compiler or CompilerInfo.
+    compilerShouldUseSharedLibByDefault =
+      case compilerFlavor compiler of
+        GHC   -> GHC.isDynamic compiler
+        GHCJS -> GHCJS.isDynamic compiler
+        _     -> False
+
+    pkgsUseProfilingLibrary :: Set PackageId
+    pkgsUseProfilingLibrary =
+        packagesWithDownwardClosedProperty needsProfilingLib
+      where
+        needsProfilingLib pkg =
+            fromFlagOrDefault False (profBothFlag <> profLibFlag)
+          where
+            pkgid        = packageId pkg
+            profBothFlag = lookupPerPkgOption pkgid packageConfigProf
+            profLibFlag  = lookupPerPkgOption pkgid packageConfigProfLib
+            --TODO: [code cleanup] unused: the old deprecated packageConfigProfExe
+
+    packagesWithDownwardClosedProperty property =
+        Set.fromList
+      $ map packageId
+      $ InstallPlan.dependencyClosure
+          solverPlan
+          [ installedPackageId pkg
+          | pkg <- InstallPlan.toList solverPlan
+          , property pkg ] -- just the packages that satisfy the propety
+      --TODO: [nice to have] this does not check the config consistency,
+      -- e.g. a package explicitly turning off profiling, but something
+      -- depending on it that needs profiling. This really needs a separate
+      -- package config validation/resolution pass.
+
+      --TODO: [nice to have] config consistency checking:
+      -- * profiling libs & exes, exe needs lib, recursive
+      -- * shared libs & exes, exe needs lib, recursive
+      -- * vanilla libs & exes, exe needs lib, recursive
+      -- * ghci or shared lib needed by TH, recursive, ghc version dependent
+
+
+---------------------------
+-- Build targets
+--
+
+-- | The various targets within a package. This is more of a high level
+-- specification than a elaborated prescription.
+--
+data PackageTarget =
+     -- | Build the default components in this package. This usually means
+     -- just the lib and exes, but it can also mean the testsuites and
+     -- benchmarks if the user explicitly requested them.
+     BuildDefaultComponents
+     -- | Build a specific component in this package.
+   | BuildSpecificComponent ComponentTarget
+   | ReplDefaultComponent
+   | ReplSpecificComponent  ComponentTarget
+   | HaddockDefaultComponents
+  deriving (Eq, Show, Generic)
+
+data ComponentTarget = ComponentTarget ComponentName SubComponentTarget
+  deriving (Eq, Show, Generic)
+
+data SubComponentTarget = WholeComponent
+                        | ModuleTarget ModuleName
+                        | FileTarget   FilePath
+  deriving (Eq, Show, Generic)
+
+instance Binary PackageTarget
+instance Binary ComponentTarget
+instance Binary SubComponentTarget
+
+
+--TODO: this needs to report some user target/config errors
+elaboratePackageTargets :: ElaboratedConfiguredPackage -> [PackageTarget]
+                        -> ([ComponentTarget], Maybe ComponentTarget, Bool)
+elaboratePackageTargets ElaboratedConfiguredPackage{..} targets =
+    let buildTargets  = nubComponentTargets
+                      . map compatSubComponentTargets
+                      . concatMap elaborateBuildTarget
+                      $ targets
+        --TODO: instead of listToMaybe we should be reporting an error here
+        replTargets   = listToMaybe
+                      . nubComponentTargets
+                      . map compatSubComponentTargets
+                      . concatMap elaborateReplTarget
+                      $ targets
+        buildHaddocks = HaddockDefaultComponents `elem` targets
+
+     in (buildTargets, replTargets, buildHaddocks)
+  where
+    --TODO: need to report an error here if defaultComponents is empty
+    elaborateBuildTarget  BuildDefaultComponents    = pkgDefaultComponents
+    elaborateBuildTarget (BuildSpecificComponent t) = [t]
+    elaborateBuildTarget  _                         = []
+
+    --TODO: need to report an error here if defaultComponents is empty
+    elaborateReplTarget  ReplDefaultComponent     = take 1 pkgDefaultComponents
+    elaborateReplTarget (ReplSpecificComponent t) = [t]
+    elaborateReplTarget  _                        = []
+
+    pkgDefaultComponents =
+        [ ComponentTarget cname WholeComponent
+        | c <- Cabal.pkgComponents pkgDescription
+        , PD.buildable (Cabal.componentBuildInfo c)
+        , let cname = Cabal.componentName c
+        , enabledOptionalStanza cname
+        ]
+      where
+        enabledOptionalStanza cname =
+          case componentOptionalStanza cname of
+            Nothing     -> True
+            Just stanza -> Map.lookup stanza pkgStanzasRequested
+                        == Just True
+
+    -- Not all Cabal Setup.hs versions support sub-component targets, so switch
+    -- them over to the whole component
+    compatSubComponentTargets :: ComponentTarget -> ComponentTarget
+    compatSubComponentTargets target@(ComponentTarget cname _subtarget)
+      | not setupHsSupportsSubComponentTargets
+                  = ComponentTarget cname WholeComponent
+      | otherwise = target
+
+    -- Actually the reality is that no current version of Cabal's Setup.hs
+    -- build command actually support building specific files or modules.
+    setupHsSupportsSubComponentTargets = False
+    -- TODO: when that changes, adjust this test, e.g.
+    -- | pkgSetupScriptCliVersion >= Version [x,y] []
+
+    nubComponentTargets :: [ComponentTarget] -> [ComponentTarget]
+    nubComponentTargets =
+        concatMap (wholeComponentOverrides . map snd)
+      . groupBy ((==)    `on` fst)
+      . sortBy  (compare `on` fst)
+      . map (\t@(ComponentTarget cname _) -> (cname, t))
+
+    -- If we're building the whole component then that the only target all we
+    -- need, otherwise we can have several targets within the component.
+    wholeComponentOverrides :: [ComponentTarget] -> [ComponentTarget]
+    wholeComponentOverrides ts =
+      case [ t | t@(ComponentTarget _ WholeComponent) <- ts ] of
+        (t:_) -> [t]
+        []    -> ts
+
+
+pkgHasEphemeralBuildTargets :: ElaboratedConfiguredPackage -> Bool
+pkgHasEphemeralBuildTargets pkg =
+    isJust (pkgReplTarget pkg)
+ || (not . null) [ () | ComponentTarget _ subtarget <- pkgBuildTargets pkg
+                      , subtarget /= WholeComponent ]
+
+-- | The components that we'll build all of, meaning that after they're built
+-- we can skip building them again (unlike with building just some modules or
+-- other files within a component).
+--
+pkgBuildTargetWholeComponents :: ElaboratedConfiguredPackage
+                              -> Set ComponentName
+pkgBuildTargetWholeComponents pkg =
+    Set.fromList
+      [ cname | ComponentTarget cname WholeComponent <- pkgBuildTargets pkg ]
+
+
+------------------------------------------------------------------------------
+-- * Install plan pruning
+------------------------------------------------------------------------------
+
+-- | Given a set of package targets (and optionally component targets within
+-- those packages), take the subset of the install plan needed to build those
+-- targets. Also, update the package config to specify which optional stanzas
+-- to enable, and which targets within each package to build.
+--
+pruneInstallPlanToTargets :: Map InstalledPackageId [PackageTarget]
+                          -> ElaboratedInstallPlan -> ElaboratedInstallPlan
+pruneInstallPlanToTargets perPkgTargetsMap =
+    either (\_ -> assert False undefined) id
+  . InstallPlan.new False
+  . PackageIndex.fromList
+    -- We have to do this in two passes
+  . pruneInstallPlanPass2
+  . pruneInstallPlanPass1 perPkgTargetsMap
+  . InstallPlan.toList
+
+-- The first pass does three things:
+--
+-- * Set the build targets based on the user targets (but not rev deps yet).
+-- * A first go at determining which optional stanzas (testsuites, benchmarks)
+--   are needed. We have a second go in the next pass.
+-- * Take the dependency closure using pruned dependencies. We prune deps that
+--   are used only by unneeded optional stanzas. These pruned deps are only
+--   used for the dependency closure and are not persisted in this pass.
+--
+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)
+  where
+    -- 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
+    -- depencencies. Those comes in the second pass once we know the rev deps.
+    --
+    setBuildTargets pkg =
+        pkg {
+          pkgBuildTargets   = buildTargets,
+          pkgReplTarget     = replTarget,
+          pkgBuildHaddocks  = buildHaddocks
+        }
+      where
+        (buildTargets, replTarget, buildHaddocks)
+                = elaboratePackageTargets pkg targets
+        targets = fromMaybe []
+                $ Map.lookup (installedPackageId pkg) perPkgTargetsMap
+
+    -- Decide whether or not to enable testsuites and benchmarks
+    --
+    -- The testsuite and benchmark targets are somewhat special in that we need
+    -- to configure the packages with them enabled, and we need to do that even
+    -- if we only want to build one of several testsuites.
+    --
+    -- There are two cases in which we will enable the testsuites (or
+    -- benchmarks): if one of the targets is a testsuite, or if all of the
+    -- testsuite depencencies are already cached in the store. The rationale
+    -- for the latter is to minimise how often we have to reconfigure due to
+    -- the particular targets we choose to build. Otherwise choosing to build
+    -- a testsuite target, and then later choosing to build an exe target
+    -- would involve unnecessarily reconfiguring the package with testsuites
+    -- disabled. Technically this introduces a little bit of stateful
+    -- behaviour to make this "sticky", but it should be benign.
+    --
+    pruneOptionalStanzas pkg = pkg { pkgStanzasEnabled = stanzas }
+      where
+        stanzas :: Set OptionalStanza
+        stanzas = optionalStanzasRequiredByTargets  pkg
+               <> optionalStanzasRequestedByDefault pkg
+               <> optionalStanzasWithDepsAvailable availablePkgs pkg
+
+    -- Calculate package depencencies but cut out those needed only by
+    -- optional stanzas that we've determined we will not enable.
+    -- These pruned deps are not persisted in this pass since they're based on
+    -- 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)
+      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
+                                     -> Set OptionalStanza
+    optionalStanzasRequiredByTargets pkg =
+      Set.fromList
+        [ stanza
+        | ComponentTarget cname _ <- pkgBuildTargets pkg
+                                  ++ maybeToList (pkgReplTarget pkg)
+        , stanza <- maybeToList (componentOptionalStanza cname)
+        ]
+
+    optionalStanzasRequestedByDefault :: ElaboratedConfiguredPackage
+                                      -> Set OptionalStanza
+    optionalStanzasRequestedByDefault =
+        Map.keysSet
+      . Map.filter (id :: Bool -> Bool)
+      . pkgStanzasRequested
+
+    availablePkgs =
+      Set.fromList
+        [ installedPackageId pkg
+        | InstallPlan.PreExisting pkg <- pkgs ]
+
+optionalStanzasWithDepsAvailable :: Set InstalledPackageId
+                                 -> ElaboratedConfiguredPackage
+                                 -> Set OptionalStanza
+optionalStanzasWithDepsAvailable availablePkgs pkg =
+    Set.fromList
+      [ stanza
+      | stanza <- Set.toList (pkgStanzasAvailable pkg)
+      , let deps :: [InstalledPackageId]
+            deps = map installedPackageId
+                 $ CD.select (optionalStanzaDeps stanza)
+                             (pkgDependencies pkg)
+      , all (`Set.member` availablePkgs) deps
+      ]
+  where
+    optionalStanzaDeps TestStanzas  (CD.ComponentTest  _) = True
+    optionalStanzaDeps BenchStanzas (CD.ComponentBench _) = True
+    optionalStanzaDeps _            _                     = False
+
+
+-- The second pass does three things:
+--
+-- * A second go at deciding which optional stanzas to enable.
+-- * Prune the depencencies based on the final choice of optional stanzas.
+-- * Extend the targets within each package to build, now we know the reverse
+--   depencencies, ie we know which libs are needed as deps by other packages.
+--
+-- Achieving sticky behaviour with enabling\/disabling optional stanzas is
+-- tricky. The first approximation was handled by the first pass above, but
+-- it's not quite enough. That pass will enable stanzas if all of the deps
+-- of the optional stanza are already instaled /in the store/. That's important
+-- but it does not account for depencencies that get built inplace as part of
+-- the project. We cannot take those inplace build deps into account in the
+-- pruning pass however because we don't yet know which ones we're going to
+-- build. Once we do know, we can have another go and enable stanzas that have
+-- all their deps available. Now we can consider all packages in the pruned
+-- plan to be available, including ones we already decided to build from
+-- source.
+--
+-- Deciding which targets to build depends on knowing which packages have
+-- reverse dependencies (ie are needed). This requires the result of first
+-- pass, which is another reason we have to split it into two passes.
+--
+-- Note that just because we might enable testsuites or benchmarks (in the
+-- first or second pass) doesn't mean that we build all (or even any) of them.
+-- That depends on which targets we picked in the first pass.
+--
+pruneInstallPlanPass2 :: [ElaboratedPlanPackage]
+                      -> [ElaboratedPlanPackage]
+pruneInstallPlanPass2 pkgs =
+    map (mapConfiguredPackage setStanzasDepsAndTargets) pkgs
+  where
+    setStanzasDepsAndTargets pkg =
+        pkg {
+          pkgStanzasEnabled = stanzas,
+          pkgDependencies   = CD.filterDeps keepNeeded (pkgDependencies pkg),
+          pkgBuildTargets   = pkgBuildTargets pkg ++ targetsRequiredForRevDeps
+        }
+      where
+        stanzas :: Set OptionalStanza
+        stanzas = pkgStanzasEnabled pkg
+               <> optionalStanzasWithDepsAvailable availablePkgs pkg
+
+        keepNeeded (CD.ComponentTest  _) _ = TestStanzas  `Set.member` stanzas
+        keepNeeded (CD.ComponentBench _) _ = BenchStanzas `Set.member` stanzas
+        keepNeeded _                     _ = True
+
+        targetsRequiredForRevDeps =
+          [ ComponentTarget (Cabal.defaultLibName (pkgSourceId pkg)) WholeComponent
+          -- if anything needs this pkg, build the library component
+          | installedPackageId pkg `Set.member` hasReverseLibDeps
+          ]
+        --TODO: also need to track build-tool rev-deps for exes
+
+    availablePkgs :: Set InstalledPackageId
+    availablePkgs = Set.fromList (map installedPackageId pkgs)
+
+    hasReverseLibDeps :: Set InstalledPackageId
+    hasReverseLibDeps =
+      Set.fromList [ depid | pkg <- pkgs
+                           , depid <- CD.flatDeps (depends pkg) ]
+
+
+mapConfiguredPackage :: (ElaboratedConfiguredPackage -> ElaboratedConfiguredPackage)
+                     -> ElaboratedPlanPackage
+                     -> ElaboratedPlanPackage
+mapConfiguredPackage f (InstallPlan.Configured pkg) =
+  InstallPlan.Configured (f pkg)
+mapConfiguredPackage _ pkg = pkg
+
+componentOptionalStanza :: Cabal.ComponentName -> Maybe OptionalStanza
+componentOptionalStanza (Cabal.CTestName  _) = Just TestStanzas
+componentOptionalStanza (Cabal.CBenchName _) = Just BenchStanzas
+componentOptionalStanza _                    = Nothing
+
+
+dependencyClosure :: (pkg -> InstalledPackageId)
+                  -> (pkg -> [InstalledPackageId])
+                  -> [pkg]
+                  -> [InstalledPackageId]
+                  -> [pkg]
+dependencyClosure pkgid deps allpkgs =
+    map vertexToPkg
+  . concatMap Tree.flatten
+  . Graph.dfs graph
+  . map pkgidToVertex
+  where
+    (graph, vertexToPkg, pkgidToVertex) = dependencyGraph pkgid deps allpkgs
+
+dependencyGraph :: (pkg -> InstalledPackageId)
+                -> (pkg -> [InstalledPackageId])
+                -> [pkg]
+                -> (Graph.Graph,
+                    Graph.Vertex -> pkg,
+                    InstalledPackageId -> Graph.Vertex)
+dependencyGraph pkgid deps pkgs =
+    (graph, vertexToPkg', pkgidToVertex')
+  where
+    (graph, vertexToPkg, pkgidToVertex) =
+      Graph.graphFromEdges [ ( pkg, pkgid pkg, deps pkg )
+                           | pkg <- pkgs ]
+    vertexToPkg'   = (\(pkg,_,_) -> pkg)
+                   . vertexToPkg
+    pkgidToVertex' = fromMaybe (error "dependencyGraph: lookup failure")
+                   . pkgidToVertex
+
+
+---------------------------
+-- Setup.hs script policy
+--
+
+-- Handling for Setup.hs scripts is a bit tricky, part of it lives in the
+-- solver phase, and part in the elaboration phase. We keep the helper
+-- functions for both phases together here so at least you can see all of it
+-- in one place.
+
+-- | There are four major cases for Setup.hs handling:
+--
+--  1. @build-type@ Custom with a @custom-setup@ section
+--  2. @build-type@ Custom without a @custom-setup@ section
+--  3. @build-type@ not Custom with @cabal-version >  $our-cabal-version@
+--  4. @build-type@ not Custom with @cabal-version <= $our-cabal-version@
+--
+-- It's also worth noting that packages specifying @cabal-version: >= 1.23@
+-- or later that have @build-type@ Custom will always have a @custom-setup@
+-- section. Therefore in case 2, the specified @cabal-version@ will always be
+-- less than 1.23.
+--
+-- In cases 1 and 2 we obviously have to build an external Setup.hs script,
+-- while in case 4 we can use the internal library API. In case 3 we also have
+-- to build an external Setup.hs script because the package needs a later
+-- Cabal lib version than we can support internally.
+--
+data SetupScriptStyle = SetupCustomExplicitDeps
+                      | SetupCustomImplicitDeps
+                      | SetupNonCustomExternalLib
+                      | SetupNonCustomInternalLib
+  deriving (Eq, Show, Generic)
+
+instance Binary SetupScriptStyle
+
+
+-- | Work out the 'SetupScriptStyle' given the package description.
+--
+-- This only works on original packages before we give them to the solver,
+-- since after the solver some implicit setup deps are made explicit.
+--
+-- See 'rememberImplicitSetupDeps' and 'packageSetupScriptStylePostSolver'.
+--
+packageSetupScriptStylePreSolver :: PD.PackageDescription -> SetupScriptStyle
+packageSetupScriptStylePreSolver pkg
+  | buildType == PD.Custom
+  , isJust (PD.setupBuildInfo pkg)
+  = SetupCustomExplicitDeps
+
+  | buildType == PD.Custom
+  = SetupCustomImplicitDeps
+
+  | PD.specVersion pkg > cabalVersion -- one cabal-install is built against
+  = SetupNonCustomExternalLib
+
+  | otherwise
+  = SetupNonCustomInternalLib
+  where
+    buildType = fromMaybe PD.Custom (PD.buildType pkg)
+
+
+-- | Part of our Setup.hs handling policy is implemented by getting the solver
+-- to work out setup dependencies for packages. The solver already handles
+-- packages that explicitly specify setup dependencies, but we can also tell
+-- the solver to treat other packages as if they had setup dependencies.
+-- That's what this function does, it gets called by the solver for all
+-- packages that don't already have setup dependencies.
+--
+-- The dependencies we want to add is different for each 'SetupScriptStyle'.
+--
+-- Note that adding default deps means these deps are actually /added/ to the
+-- packages that we get out of the solver in the 'SolverInstallPlan'. Making
+-- implicit setup deps explicit is a problem in the post-solver stages because
+-- we still need to distinguish the case of explicit and implict setup deps.
+-- See 'rememberImplicitSetupDeps'.
+--
+defaultSetupDeps :: Platform -> PD.PackageDescription -> [Dependency]
+defaultSetupDeps platform pkg =
+    case packageSetupScriptStylePreSolver pkg of
+
+      -- For packages with build type custom that do not specify explicit
+      -- setup dependencies, we add a dependency on Cabal and a number
+      -- of other packages.
+      SetupCustomImplicitDeps ->
+        [ Dependency depPkgname anyVersion
+        | depPkgname <- legacyCustomSetupPkgs platform ] ++
+        -- The Cabal dep is slightly special:
+        --  * we omit the dep for the Cabal lib itself (since it bootstraps),
+        --  * we constrain it to be less than 1.23 since all packages
+        --    relying on later Cabal spec versions are supposed to use
+        --    explit setup deps. Having this constraint also allows later
+        --    Cabal lib versions to make breaking API changes without breaking
+        --    all old Setup.hs scripts.
+        [ Dependency cabalPkgname cabalConstraint
+        | packageName pkg /= cabalPkgname ]
+        where
+          cabalConstraint   = orLaterVersion (PD.specVersion pkg)
+                                `intersectVersionRanges`
+                              earlierVersion cabalCompatMaxVer
+        -- TODO/FIXME: turns out that constraining to less than 1.23 causes
+        --             problems with GHC8 as there's too many important packages
+        --             with Custom build-type, for which there wouldn't be any
+        --             install-plan (as GHC8 requires Cabal-1.24+). So let's
+        --             set an implicit upper bound `Cabal < 2` instead.
+          cabalCompatMaxVer = Version [2] []
+
+      -- For other build types (like Simple) if we still need to compile an
+      -- external Setup.hs, it'll be one of the simple ones that only depends
+      -- on Cabal and base.
+      SetupNonCustomExternalLib ->
+        [ Dependency cabalPkgname cabalConstraint
+        , Dependency basePkgname  anyVersion ]
+        where
+          cabalConstraint = orLaterVersion (PD.specVersion pkg)
+
+      -- The internal setup wrapper method has no deps at all.
+      SetupNonCustomInternalLib -> []
+
+      SetupCustomExplicitDeps ->
+        error $ "defaultSetupDeps: called for a package with explicit "
+             ++ "setup deps: " ++ display (packageId pkg)
+
+
+-- | See 'rememberImplicitSetupDeps' for details.
+type PackagesImplicitSetupDeps = Set InstalledPackageId
+
+-- | A consequence of using 'defaultSetupDeps' in 'planPackages' is that by
+-- making implicit setup deps explicit we loose track of which packages
+-- originally had implicit setup deps. That's important because we do still
+-- have different behaviour based on the setup style (in particular whether to
+-- compile a Setup.hs script with version macros).
+--
+-- So we remember the necessary information in an auxilliary set and use it
+-- in 'packageSetupScriptStylePreSolver' to recover the full info.
+--
+rememberImplicitSetupDeps :: SourcePackageIndex.PackageIndex (SourcePackage loc)
+                          -> SolverInstallPlan
+                          -> (SolverInstallPlan, PackagesImplicitSetupDeps)
+rememberImplicitSetupDeps sourcePkgIndex plan =
+    (plan, pkgsImplicitSetupDeps)
+  where
+    pkgsImplicitSetupDeps =
+      Set.fromList
+        [ installedPackageId pkg
+        | InstallPlan.Configured
+            pkg@(ConfiguredPackage newpkg _ _ _) <- InstallPlan.toList plan
+          -- has explicit setup deps now
+        , hasExplicitSetupDeps newpkg
+          -- but originally had no setup deps
+        , let Just origpkg = SourcePackageIndex.lookupPackageId
+                               sourcePkgIndex (packageId pkg)
+        , not (hasExplicitSetupDeps origpkg)
+        ]
+
+    hasExplicitSetupDeps =
+        (SetupCustomExplicitDeps==)
+      . packageSetupScriptStylePreSolver
+      . PD.packageDescription . packageDescription
+
+
+-- | Use the extra info saved by 'rememberImplicitSetupDeps' to let us work
+-- out the correct 'SetupScriptStyle'. This should give the same result as
+-- 'packageSetupScriptStylePreSolver' gave prior to munging the package info
+-- through the solver.
+--
+packageSetupScriptStylePostSolver :: Set InstalledPackageId
+                                  -> ConfiguredPackage loc
+                                  -> PD.PackageDescription
+                                  -> SetupScriptStyle
+packageSetupScriptStylePostSolver pkgsImplicitSetupDeps pkg pkgDescription =
+    case packageSetupScriptStylePreSolver pkgDescription of
+      SetupCustomExplicitDeps
+        | Set.member (installedPackageId pkg) pkgsImplicitSetupDeps
+            -> SetupCustomImplicitDeps
+      other -> other
+
+
+-- | Work out which version of the Cabal spec we will be using to talk to the
+-- Setup.hs interface for this package.
+--
+-- This depends somewhat on the 'SetupScriptStyle' but most cases are a result
+-- of what the solver picked for us, based on the explicit setup deps or the
+-- ones added implicitly by 'defaultSetupDeps'.
+--
+packageSetupScriptSpecVersion :: Package pkg
+                              => SetupScriptStyle
+                              -> PD.PackageDescription
+                              -> ComponentDeps [pkg]
+                              -> Version
+
+-- We're going to be using the internal Cabal library, so the spec version of
+-- that is simply the version of the Cabal library that cabal-install has been
+-- built with.
+packageSetupScriptSpecVersion SetupNonCustomInternalLib _ _ =
+    cabalVersion
+
+-- If we happen to be building the Cabal lib itself then because that
+-- bootstraps itself then we use the version of the lib we're building.
+packageSetupScriptSpecVersion SetupCustomImplicitDeps pkg _
+  | packageName pkg == cabalPkgname
+  = packageVersion pkg
+
+-- In all other cases we have a look at what version of the Cabal lib the
+-- solver picked. Or if it didn't depend on Cabal at all (which is very rare)
+-- then we look at the .cabal file to see what spec version it declares.
+packageSetupScriptSpecVersion _ pkg deps =
+    case find ((cabalPkgname ==) . packageName) (CD.setupDeps deps) of
+      Just dep -> packageVersion dep
+      Nothing  -> PD.specVersion pkg
+
+
+cabalPkgname, basePkgname :: PackageName
+cabalPkgname = PackageName "Cabal"
+basePkgname  = PackageName "base"
+
+
+legacyCustomSetupPkgs :: Platform -> [PackageName]
+legacyCustomSetupPkgs (Platform _ os) =
+    map PackageName $
+        [ "array", "base", "binary", "bytestring", "containers"
+        , "deepseq", "directory", "filepath", "pretty"
+        , "process", "time" ]
+     ++ [ "Win32" | os == Windows ]
+     ++ [ "unix"  | os /= Windows ]
+
+-- The other aspects of our Setup.hs policy lives here where we decide on
+-- the 'SetupScriptOptions'.
+--
+-- Our current policy for the 'SetupCustomImplicitDeps' case is that we
+-- try to make the implicit deps cover everything, and we don't allow the
+-- compiler to pick up other deps. This may or may not be sustainable, and
+-- we might have to allow the deps to be non-exclusive, but that itself would
+-- be tricky since we would have to allow the Setup access to all the packages
+-- in the store and local dbs.
+
+setupHsScriptOptions :: ElaboratedReadyPackage
+                     -> ElaboratedSharedConfig
+                     -> FilePath
+                     -> FilePath
+                     -> Bool
+                     -> Lock
+                     -> SetupScriptOptions
+setupHsScriptOptions (ReadyPackage ElaboratedConfiguredPackage{..})
+                     ElaboratedSharedConfig{..} srcdir builddir
+                     isParallelBuild cacheLock =
+    SetupScriptOptions {
+      useCabalVersion          = thisVersion pkgSetupScriptCliVersion,
+      useCabalSpecVersion      = Just pkgSetupScriptCliVersion,
+      useCompiler              = Just pkgConfigCompiler,
+      usePlatform              = Just pkgConfigPlatform,
+      usePackageDB             = pkgSetupPackageDBStack,
+      usePackageIndex          = Nothing,
+      useDependencies          = [ (uid, srcid)
+                                 | ConfiguredId srcid uid <- CD.setupDeps pkgDependencies ],
+      useDependenciesExclusive = True,
+      useVersionMacros         = pkgSetupScriptStyle == SetupCustomExplicitDeps,
+      useProgramConfig         = pkgConfigProgramDb,
+      useDistPref              = builddir,
+      useLoggingHandle         = Nothing, -- this gets set later
+      useWorkingDir            = Just srcdir,
+      useWin32CleanHack        = False,   --TODO: [required eventually]
+      forceExternalSetupMethod = isParallelBuild,
+      setupCacheLock           = Just cacheLock
+    }
+
+
+-- | To be used for the input for elaborateInstallPlan.
+--
+-- TODO: [code cleanup] make InstallDirs.defaultInstallDirs pure.
+--
+userInstallDirTemplates :: Compiler
+                        -> IO InstallDirs.InstallDirTemplates
+userInstallDirTemplates compiler = do
+    InstallDirs.defaultInstallDirs
+                  (compilerFlavor compiler)
+                  True  -- user install
+                  False -- unused
+
+storePackageInstallDirs :: CabalDirLayout
+                        -> CompilerId
+                        -> InstalledPackageId
+                        -> InstallDirs.InstallDirs FilePath
+storePackageInstallDirs CabalDirLayout{cabalStorePackageDirectory}
+                        compid ipkgid =
+    InstallDirs.InstallDirs {..}
+  where
+    prefix       = cabalStorePackageDirectory compid ipkgid
+    bindir       = prefix </> "bin"
+    libdir       = prefix </> "lib"
+    libsubdir    = ""
+    dynlibdir    = libdir
+    libexecdir   = prefix </> "libexec"
+    includedir   = libdir </> "include"
+    datadir      = prefix </> "share"
+    datasubdir   = ""
+    docdir       = datadir </> "doc"
+    mandir       = datadir </> "man"
+    htmldir      = docdir  </> "html"
+    haddockdir   = htmldir
+    sysconfdir   = prefix </> "etc"
+
+
+--TODO: [code cleanup] perhaps reorder this code
+-- based on the ElaboratedInstallPlan + ElaboratedSharedConfig,
+-- make the various Setup.hs {configure,build,copy} flags
+
+
+setupHsConfigureFlags :: ElaboratedReadyPackage
+                      -> ElaboratedSharedConfig
+                      -> Verbosity
+                      -> FilePath
+                      -> Cabal.ConfigFlags
+setupHsConfigureFlags (ReadyPackage
+                         pkg@ElaboratedConfiguredPackage{..})
+                      sharedConfig@ElaboratedSharedConfig{..}
+                      verbosity builddir =
+    assert (sanityCheckElaboratedConfiguredPackage sharedConfig pkg)
+    Cabal.ConfigFlags {..}
+  where
+    configDistPref            = toFlag builddir
+    configVerbosity           = toFlag verbosity
+
+    configIPID                = toFlag (display (installedUnitId pkg))
+
+    configProgramPaths        = programDbProgramPaths pkgConfigProgramDb
+    configProgramArgs         = programDbProgramArgs  pkgConfigProgramDb
+    configProgramPathExtra    = programDbPathExtra    pkgConfigProgramDb
+    configHcFlavor            = toFlag (compilerFlavor pkgConfigCompiler)
+    configHcPath              = mempty -- use configProgramPaths instead
+    configHcPkg               = mempty -- use configProgramPaths instead
+
+    configVanillaLib          = toFlag pkgVanillaLib
+    configSharedLib           = toFlag pkgSharedLib
+    configDynExe              = toFlag pkgDynExe
+    configGHCiLib             = toFlag pkgGHCiLib
+    configProfExe             = mempty
+    configProfLib             = toFlag pkgProfLib
+    configProf                = toFlag pkgProfExe
+
+    -- configProfDetail is for exe+lib, but overridden by configProfLibDetail
+    -- so we specify both so we can specify independently
+    configProfDetail          = toFlag pkgProfExeDetail
+    configProfLibDetail       = toFlag pkgProfLibDetail
+
+    configCoverage            = toFlag pkgCoverage
+    configLibCoverage         = mempty
+
+    configOptimization        = toFlag pkgOptimization
+    configSplitObjs           = toFlag pkgSplitObjs
+    configStripExes           = toFlag pkgStripExes
+    configStripLibs           = toFlag pkgStripLibs
+    configDebugInfo           = toFlag pkgDebugInfo
+    configAllowNewer          = mempty -- we use configExactConfiguration True
+
+    configConfigurationsFlags = pkgFlagAssignment
+    configConfigureArgs       = pkgConfigureScriptArgs
+    configExtraLibDirs        = pkgExtraLibDirs
+    configExtraFrameworkDirs  = pkgExtraFrameworkDirs
+    configExtraIncludeDirs    = pkgExtraIncludeDirs
+    configProgPrefix          = maybe mempty toFlag pkgProgPrefix
+    configProgSuffix          = maybe mempty toFlag pkgProgSuffix
+
+    configInstallDirs         = fmap (toFlag . InstallDirs.toPathTemplate)
+                                     pkgInstallDirs
+
+    -- we only use configDependencies, unless we're talking to an old Cabal
+    -- in which case we use configConstraints
+    configDependencies        = [ (packageName srcid, uid)
+                                | ConfiguredId srcid uid <- CD.nonSetupDeps pkgDependencies ]
+    configConstraints         = [ thisPackageVersion srcid
+                                | ConfiguredId srcid _uid <- CD.nonSetupDeps pkgDependencies ]
+
+    -- explicitly clear, then our package db stack
+    -- 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)
+
+    configExactConfiguration  = toFlag True
+    configFlagError           = mempty --TODO: [research required] appears not to be implemented
+    configRelocatable         = mempty --TODO: [research required] ???
+    configScratchDir          = mempty -- never use
+    configUserInstall         = mempty -- don't rely on defaults
+    configPrograms_           = mempty -- never use, shouldn't exist
+
+    programDbProgramPaths db =
+      [ (programId prog, programPath prog)
+      | prog <- configuredPrograms db ]
+
+    programDbProgramArgs db =
+      [ (programId prog, programOverrideArgs prog)
+      | prog <- configuredPrograms db ]
+
+    programDbPathExtra db =
+      case getProgramSearchPath db of
+        ProgramSearchPathDefault : extra ->
+          toNubList [ dir | ProgramSearchPathDir dir <- extra ]
+        _ -> error $ "setupHsConfigureFlags: we cannot currently cope with a "
+                  ++ "search path that does not start with the system path"
+                  -- the Setup.hs interface only has --extra-prog-path
+                  -- so we cannot put things before the $PATH, only after
+
+
+setupHsBuildFlags :: ElaboratedConfiguredPackage
+                  -> ElaboratedSharedConfig
+                  -> Verbosity
+                  -> FilePath
+                  -> Cabal.BuildFlags
+setupHsBuildFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+    Cabal.BuildFlags {
+      buildProgramPaths = mempty, --unused, set at configure time
+      buildProgramArgs  = mempty, --unused, set at configure time
+      buildVerbosity    = toFlag verbosity,
+      buildDistPref     = toFlag builddir,
+      buildNumJobs      = mempty, --TODO: [nice to have] sometimes want to use toFlag (Just numBuildJobs),
+      buildArgs         = mempty  -- unused, passed via args not flags
+    }
+
+
+setupHsBuildArgs :: ElaboratedConfiguredPackage -> [String]
+setupHsBuildArgs pkg =
+    map (showComponentTarget pkg) (pkgBuildTargets pkg)
+
+
+showComponentTarget :: ElaboratedConfiguredPackage -> ComponentTarget -> String
+showComponentTarget _pkg =
+    showBuildTarget . toBuildTarget
+  where
+    showBuildTarget t =
+      Cabal.showBuildTarget (qlBuildTarget t) t
+
+    qlBuildTarget Cabal.BuildTargetComponent{} = Cabal.QL2
+    qlBuildTarget _                            = Cabal.QL3
+
+    toBuildTarget :: ComponentTarget -> Cabal.BuildTarget
+    toBuildTarget (ComponentTarget cname subtarget) =
+      case subtarget of
+        WholeComponent     -> Cabal.BuildTargetComponent cname
+        ModuleTarget mname -> Cabal.BuildTargetModule    cname mname
+        FileTarget   fname -> Cabal.BuildTargetFile      cname fname
+
+
+setupHsReplFlags :: ElaboratedConfiguredPackage
+                 -> ElaboratedSharedConfig
+                 -> Verbosity
+                 -> FilePath
+                 -> Cabal.ReplFlags
+setupHsReplFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+    Cabal.ReplFlags {
+      replProgramPaths = mempty, --unused, set at configure time
+      replProgramArgs  = mempty, --unused, set at configure time
+      replVerbosity    = toFlag verbosity,
+      replDistPref     = toFlag builddir,
+      replReload       = mempty  --only used as callback from repl
+    }
+
+
+setupHsReplArgs :: ElaboratedConfiguredPackage -> [String]
+setupHsReplArgs pkg =
+    maybe [] (\t -> [showComponentTarget pkg t]) (pkgReplTarget pkg)
+    --TODO: should be able to give multiple modules in one component
+
+
+setupHsCopyFlags :: ElaboratedConfiguredPackage
+                 -> ElaboratedSharedConfig
+                 -> Verbosity
+                 -> FilePath
+                 -> Cabal.CopyFlags
+setupHsCopyFlags _ _ verbosity builddir =
+    Cabal.CopyFlags {
+      --TODO: [nice to have] we currently just rely on Setup.hs copy to always do the right
+      -- thing, but perhaps we ought really to copy into an image dir and do
+      -- some sanity checks and move into the final location ourselves
+      copyArgs      = [], -- TODO: could use this to only copy what we enabled
+      copyDest      = toFlag InstallDirs.NoCopyDest,
+      copyDistPref  = toFlag builddir,
+      copyVerbosity = toFlag verbosity
+    }
+
+setupHsRegisterFlags :: ElaboratedConfiguredPackage
+                     -> ElaboratedSharedConfig
+                     -> Verbosity
+                     -> FilePath
+                     -> FilePath
+                     -> Cabal.RegisterFlags
+setupHsRegisterFlags ElaboratedConfiguredPackage {pkgBuildStyle} _
+                     verbosity builddir pkgConfFile =
+    Cabal.RegisterFlags {
+      regPackageDB   = mempty,  -- misfeature
+      regGenScript   = mempty,  -- never use
+      regGenPkgConf  = toFlag (Just pkgConfFile),
+      regInPlace     = case pkgBuildStyle of
+                         BuildInplaceOnly -> toFlag True
+                         _                -> toFlag False,
+      regPrintId     = mempty,  -- never use
+      regDistPref    = toFlag builddir,
+      regVerbosity   = toFlag verbosity
+    }
+
+setupHsHaddockFlags :: ElaboratedConfiguredPackage
+                    -> ElaboratedSharedConfig
+                    -> Verbosity
+                    -> FilePath
+                    -> Cabal.HaddockFlags
+setupHsHaddockFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =
+    Cabal.HaddockFlags {
+      haddockProgramPaths  = mempty, --unused, set at configure time
+      haddockProgramArgs   = mempty, --unused, set at configure time
+      haddockHoogle        = toFlag pkgHaddockHoogle,
+      haddockHtml          = toFlag pkgHaddockHtml,
+      haddockHtmlLocation  = maybe mempty toFlag pkgHaddockHtmlLocation,
+      haddockForHackage    = mempty, --TODO: new flag
+      haddockExecutables   = toFlag pkgHaddockExecutables,
+      haddockTestSuites    = toFlag pkgHaddockTestSuites,
+      haddockBenchmarks    = toFlag pkgHaddockBenchmarks,
+      haddockInternal      = toFlag pkgHaddockInternal,
+      haddockCss           = maybe mempty toFlag pkgHaddockCss,
+      haddockHscolour      = toFlag pkgHaddockHscolour,
+      haddockHscolourCss   = maybe mempty toFlag pkgHaddockHscolourCss,
+      haddockContents      = maybe mempty toFlag pkgHaddockContents,
+      haddockDistPref      = toFlag builddir,
+      haddockKeepTempFiles = mempty, --TODO: from build settings
+      haddockVerbosity     = toFlag verbosity
+    }
+
+{-
+setupHsTestFlags :: ElaboratedConfiguredPackage
+                 -> ElaboratedSharedConfig
+                 -> Verbosity
+                 -> FilePath
+                 -> Cabal.TestFlags
+setupHsTestFlags _ _ verbosity builddir =
+    Cabal.TestFlags {
+    }
+-}
+
+------------------------------------------------------------------------------
+-- * Sharing installed packages
+------------------------------------------------------------------------------
+
+--
+-- Nix style store management for tarball packages
+--
+-- So here's our strategy:
+--
+-- We use a per-user nix-style hashed store, but /only/ for tarball packages.
+-- So that includes packages from hackage repos (and other http and local
+-- tarballs). For packages in local directories we do not register them into
+-- the shared store by default, we just build them locally inplace.
+--
+-- The reason we do it like this is that it's easy to make stable hashes for
+-- tarball packages, and these packages benefit most from sharing. By contrast
+-- unpacked dir packages are harder to hash and they tend to change more
+-- frequently so there's less benefit to sharing them.
+--
+-- When using the nix store approach we have to run the solver *without*
+-- looking at the packages installed in the store, just at the source packages
+-- (plus core\/global installed packages). Then we do a post-processing pass
+-- to replace configured packages in the plan with pre-existing ones, where
+-- possible. Where possible of course means where the nix-style package hash
+-- equals one that's already in the store.
+--
+-- One extra wrinkle is that unless we know package tarball hashes upfront, we
+-- will have to download the tarballs to find their hashes. So we have two
+-- options: delay replacing source with pre-existing installed packages until
+-- the point during the execution of the install plan where we have the
+-- tarball, or try to do as much up-front as possible and then check again
+-- during plan execution. The former isn't great because we would end up
+-- telling users we're going to re-install loads of packages when in fact we
+-- would just share them. It'd be better to give as accurate a prediction as
+-- we can. The latter is better for users, but we do still have to check
+-- during plan execution because it's important that we don't replace existing
+-- installed packages even if they have the same package hash, because we
+-- don't guarantee ABI stability.
+
+-- TODO: [required eventually] for safety of concurrent installs, we must make sure we register but
+-- not replace installed packages with ghc-pkg.
+
+packageHashInputs :: ElaboratedSharedConfig
+                  -> ElaboratedConfiguredPackage
+                  -> PackageHashInputs
+packageHashInputs
+    pkgshared
+    pkg@ElaboratedConfiguredPackage{
+      pkgSourceId,
+      pkgSourceHash = Just srchash,
+      pkgDependencies
+    } =
+    PackageHashInputs {
+      pkgHashPkgId       = pkgSourceId,
+      pkgHashSourceHash  = srchash,
+      pkgHashDirectDeps  = Set.fromList
+                             [ installedPackageId dep
+                             | dep <- CD.select relevantDeps pkgDependencies ],
+      pkgHashOtherConfig = packageHashConfigInputs pkgshared pkg
+    }
+  where
+    -- Obviously the main deps are relevant
+    relevantDeps (CD.ComponentLib _)   = True
+    relevantDeps (CD.ComponentExe _)   = True
+    -- Setup deps can affect the Setup.hs behaviour and thus what is built
+    relevantDeps  CD.ComponentSetup    = True
+    -- However testsuites and benchmarks do not get installed and should not
+    -- affect the result, so we do not include them.
+    relevantDeps (CD.ComponentTest  _) = False
+    relevantDeps (CD.ComponentBench _) = False
+
+packageHashInputs _ pkg =
+    error $ "packageHashInputs: only for packages with source hashes. "
+         ++ display (packageId pkg)
+
+packageHashConfigInputs :: ElaboratedSharedConfig
+                        -> ElaboratedConfiguredPackage
+                        -> PackageHashConfigInputs
+packageHashConfigInputs
+    ElaboratedSharedConfig{..}
+    ElaboratedConfiguredPackage{..} =
+
+    PackageHashConfigInputs {
+      pkgHashCompilerId          = compilerId pkgConfigCompiler,
+      pkgHashPlatform            = pkgConfigPlatform,
+      pkgHashFlagAssignment      = pkgFlagAssignment,
+      pkgHashConfigureScriptArgs = pkgConfigureScriptArgs,
+      pkgHashVanillaLib          = pkgVanillaLib,
+      pkgHashSharedLib           = pkgSharedLib,
+      pkgHashDynExe              = pkgDynExe,
+      pkgHashGHCiLib             = pkgGHCiLib,
+      pkgHashProfLib             = pkgProfLib,
+      pkgHashProfExe             = pkgProfExe,
+      pkgHashProfLibDetail       = pkgProfLibDetail,
+      pkgHashProfExeDetail       = pkgProfExeDetail,
+      pkgHashCoverage            = pkgCoverage,
+      pkgHashOptimization        = pkgOptimization,
+      pkgHashSplitObjs           = pkgSplitObjs,
+      pkgHashStripLibs           = pkgStripLibs,
+      pkgHashStripExes           = pkgStripExes,
+      pkgHashDebugInfo           = pkgDebugInfo,
+      pkgHashExtraLibDirs        = pkgExtraLibDirs,
+      pkgHashExtraFrameworkDirs  = pkgExtraFrameworkDirs,
+      pkgHashExtraIncludeDirs    = pkgExtraIncludeDirs,
+      pkgHashProgPrefix          = pkgProgPrefix,
+      pkgHashProgSuffix          = pkgProgSuffix
+    }
+
+
+-- | Given the 'InstalledPackageIndex' for a nix-style package store, and an
+-- 'ElaboratedInstallPlan', replace configured source packages by pre-existing
+-- installed packages whenever they exist.
+--
+improveInstallPlanWithPreExistingPackages :: InstalledPackageIndex
+                                          -> ElaboratedInstallPlan
+                                          -> ElaboratedInstallPlan
+improveInstallPlanWithPreExistingPackages installedPkgIndex installPlan =
+    replaceWithPreExisting installPlan
+      [ ipkg
+      | InstallPlan.Configured pkg
+          <- InstallPlan.reverseTopologicalOrder installPlan
+      , ipkg <- maybeToList (canPackageBeImproved pkg) ]
+  where
+    --TODO: sanity checks:
+    -- * the installed package must have the expected deps etc
+    -- * the installed package must not be broken, valid dep closure
+
+    --TODO: decide what to do if we encounter broken installed packages,
+    -- since overwriting is never safe.
+
+    canPackageBeImproved pkg =
+      PackageIndex.lookupUnitId
+        installedPkgIndex (installedPackageId pkg)
+
+    replaceWithPreExisting =
+      foldl' (\plan ipkg -> InstallPlan.preexisting
+                              (installedPackageId ipkg) ipkg plan)
diff --git a/cabal/cabal-install/Distribution/Client/RebuildMonad.hs b/cabal/cabal-install/Distribution/Client/RebuildMonad.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/RebuildMonad.hs
@@ -0,0 +1,135 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+-- | An abstraction for re-running actions if values or files have changed.
+--
+-- This is not a full-blown make-style incremental build system, it's a bit
+-- more ad-hoc than that, but it's easier to integrate with existing code.
+--
+-- It's a convenient interface to the "Distribution.Client.FileMonitor"
+-- functions.
+--
+module Distribution.Client.RebuildMonad (
+    -- * Rebuild monad
+    Rebuild,
+    runRebuild,
+
+    -- * Setting up file monitoring
+    monitorFiles,
+    MonitorFilePath,
+    monitorFile,
+    monitorFileHashed,
+    monitorNonExistentFile,
+    monitorDirectory,
+    monitorDirectoryExistence,
+    monitorFileOrDirectory,
+    monitorFileSearchPath,
+    monitorFileHashedSearchPath,
+    -- ** Monitoring file globs
+    monitorFileGlob,
+    FilePathGlob(..),
+    FilePathRoot(..),
+    FilePathGlobRel(..),
+    GlobPiece(..),
+
+    -- * Using a file monitor
+    FileMonitor(..),
+    newFileMonitor,
+    rerunIfChanged,
+
+    -- * Utils
+    matchFileGlob,
+  ) where
+
+import Distribution.Client.FileMonitor
+import Distribution.Client.Glob hiding (matchFileGlob)
+import qualified Distribution.Client.Glob as Glob (matchFileGlob)
+
+import Distribution.Simple.Utils (debug)
+import Distribution.Verbosity    (Verbosity)
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
+import Control.Monad.State as State
+import Distribution.Compat.Binary     (Binary)
+import System.FilePath (takeFileName)
+
+
+-- | A monad layered on top of 'IO' to help with re-running actions when the
+-- input files and values they depend on change. The crucial operations are
+-- 'rerunIfChanged' and 'monitorFiles'.
+--
+newtype Rebuild a = Rebuild (StateT [MonitorFilePath] IO a)
+  deriving (Functor, Applicative, Monad, MonadIO)
+
+-- | Use this wihin the body action of 'rerunIfChanged' to declare that the
+-- action depends on the given files. This can be based on what the action
+-- actually did. It is these files that will be checked for changes next
+-- time 'rerunIfChanged' is called for that 'FileMonitor'.
+--
+monitorFiles :: [MonitorFilePath] -> Rebuild ()
+monitorFiles filespecs = Rebuild (State.modify (filespecs++))
+
+-- | Run a 'Rebuild' IO action.
+unRebuild :: Rebuild a -> IO (a, [MonitorFilePath])
+unRebuild (Rebuild action) = runStateT action []
+
+-- | Run a 'Rebuild' IO action.
+runRebuild :: Rebuild a -> IO a
+runRebuild (Rebuild action) = evalStateT action []
+
+-- | This captures the standard use pattern for a 'FileMonitor': given a
+-- monitor, an action and the input value the action depends on, either
+-- re-run the action to get its output, or if the value and files the action
+-- depends on have not changed then return a previously cached action result.
+--
+-- The result is still in the 'Rebuild' monad, so these can be nested.
+--
+-- Do not share 'FileMonitor's between different uses of 'rerunIfChanged'.
+--
+rerunIfChanged :: (Binary a, Binary b)
+               => Verbosity
+               -> FilePath
+               -> FileMonitor a b
+               -> a
+               -> Rebuild b
+               -> Rebuild b
+rerunIfChanged verbosity rootDir monitor key action = do
+    changed <- liftIO $ checkFileMonitorChanged monitor rootDir key
+    case changed of
+      MonitorUnchanged result files -> do
+        liftIO $ debug verbosity $ "File monitor '" ++ monitorName
+                                                    ++ "' unchanged."
+        monitorFiles files
+        return result
+
+      MonitorChanged reason -> do
+        liftIO $ debug verbosity $ "File monitor '" ++ monitorName
+                                ++ "' changed: " ++ showReason reason
+        startTime <- liftIO $ beginUpdateFileMonitor
+        (result, files) <- liftIO $ unRebuild action
+        liftIO $ updateFileMonitor monitor rootDir
+                                   (Just startTime) files key result
+        monitorFiles files
+        return result
+  where
+    monitorName = takeFileName (fileMonitorCacheFile monitor)
+
+    showReason (MonitoredFileChanged file) = "file " ++ file
+    showReason (MonitoredValueChanged _)   = "monitor value changed"
+    showReason  MonitorFirstRun            = "first run"
+    showReason  MonitorCorruptCache        = "invalid cache file"
+
+
+-- | Utility to match a file glob against the file system, starting from a
+-- given root directory. The results are all relative to the given root.
+--
+-- Since this operates in the 'Rebuild' monad, it also monitrs the given glob
+-- for changes.
+--
+matchFileGlob :: FilePath -> FilePathGlob -> Rebuild [FilePath]
+matchFileGlob root glob = do
+    monitorFiles [monitorFileGlob glob]
+    liftIO $ Glob.matchFileGlob root glob
+
diff --git a/cabal/cabal-install/Distribution/Client/Sandbox.hs b/cabal/cabal-install/Distribution/Client/Sandbox.hs
--- a/cabal/cabal-install/Distribution/Client/Sandbox.hs
+++ b/cabal/cabal-install/Distribution/Client/Sandbox.hs
@@ -38,18 +38,17 @@
     updateSandboxConfigFileFlag,
     updateInstallDirs,
 
-    -- FIXME: move somewhere else
-    configPackageDB', configCompilerAux'
+    configPackageDB', configCompilerAux', getPersistOrConfigCompiler
   ) where
 
 import Distribution.Client.Setup
   ( SandboxFlags(..), ConfigFlags(..), ConfigExFlags(..), InstallFlags(..)
   , GlobalFlags(..), defaultConfigExFlags, defaultInstallFlags
-  , defaultSandboxLocation, globalRepos )
+  , defaultSandboxLocation, withRepoContext )
 import Distribution.Client.Sandbox.Timestamp  ( listModifiedDeps
                                               , maybeAddCompilerTimestampRecord
                                               , withAddTimestamps
-                                              , withRemoveTimestamps )
+                                              , removeTimestamps )
 import Distribution.Client.Config
   ( SavedConfig(..), defaultUserInstall, loadConfig )
 import Distribution.Client.Dependency         ( foldProgress )
@@ -65,7 +64,8 @@
   , createPackageEnvironmentFile, classifyPackageEnvironment
   , tryLoadSandboxPackageEnvironmentFile, loadUserConfig
   , commentPackageEnvironment, showPackageEnvironmentWithComments
-  , sandboxPackageEnvironmentFile, userPackageEnvironmentFile )
+  , sandboxPackageEnvironmentFile, userPackageEnvironmentFile
+  , sandboxPackageDBPath )
 import Distribution.Client.Sandbox.Types      ( SandboxPackageInfo(..)
                                               , UseSandbox(..) )
 import Distribution.Client.SetupWrapper
@@ -73,7 +73,7 @@
 import Distribution.Client.Types              ( PackageLocation(..)
                                               , SourcePackage(..) )
 import Distribution.Client.Utils              ( inDir, tryCanonicalizePath
-                                              , tryFindAddSourcePackageDesc )
+                                              , tryFindAddSourcePackageDesc)
 import Distribution.PackageDescription.Configuration
                                               ( flattenPackageDescription )
 import Distribution.PackageDescription.Parse  ( readPackageDescription )
@@ -82,11 +82,14 @@
 import Distribution.Simple.Configure          ( configCompilerAuxEx
                                               , interpretPackageDbFlags
                                               , getPackageDBContents
+                                              , maybeGetPersistBuildConfig
+                                              , findDistPrefOrDefault
                                               , findDistPref )
+import qualified Distribution.Simple.LocalBuildInfo as LocalBuildInfo
 import Distribution.Simple.PreProcess         ( knownSuffixHandlers )
 import Distribution.Simple.Program            ( ProgramConfiguration )
 import Distribution.Simple.Setup              ( Flag(..), HaddockFlags(..)
-                                              , fromFlagOrDefault )
+                                              , fromFlagOrDefault, flagToMaybe )
 import Distribution.Simple.SrcDist            ( prepareTree )
 import Distribution.Simple.Utils              ( die, debug, notice, info, warn
                                               , debugNoWrap, defaultPackageDesc
@@ -104,19 +107,25 @@
 import qualified Distribution.Simple.Register      as Register
 import qualified Data.Map                          as M
 import qualified Data.Set                          as S
+import Data.Either                            (partitionEithers)
 import Control.Exception                      ( assert, bracket_ )
-import Control.Monad                          ( forM, liftM2, unless, when )
+import Control.Monad                          ( forM, liftM, liftM2, unless, when )
 import Data.Bits                              ( shiftL, shiftR, xor )
 import Data.Char                              ( ord )
 import Data.IORef                             ( newIORef, writeIORef, readIORef )
-import Data.List                              ( delete, foldl' )
+import Data.List                              ( delete
+                                              , foldl'
+                                              , intersperse
+                                              , isPrefixOf
+                                              , groupBy )
 import Data.Maybe                             ( fromJust )
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid                            ( mempty, mappend )
 #endif
 import Data.Word                              ( Word32 )
 import Numeric                                ( showHex )
-import System.Directory                       ( createDirectory
+import System.Directory                       ( canonicalizePath
+                                              , createDirectory
                                               , doesDirectoryExist
                                               , doesFileExist
                                               , getCurrentDirectory
@@ -126,9 +135,9 @@
 import System.FilePath                        ( (</>), equalFilePath
                                               , getSearchPath
                                               , searchPathSeparator
+                                              , splitSearchPath
                                               , takeDirectory )
 
-
 --
 -- * Constants
 --
@@ -362,9 +371,28 @@
         ++ "'.\nAssuming a shared sandbox. Please delete '"
         ++ sandboxDir ++ "' manually."
 
-      notice verbosity $ "Deleting the sandbox located at " ++ sandboxDir
-      removeDirectoryRecursive sandboxDir
+      absSandboxDir <- canonicalizePath sandboxDir
+      notice verbosity $ "Deleting the sandbox located at " ++ absSandboxDir
+      removeDirectoryRecursive absSandboxDir
 
+      let
+        pathInsideSandbox = isPrefixOf absSandboxDir
+
+        -- Warn the user if deleting the sandbox deleted a package database
+        -- referenced in the current environment.
+        checkPackagePaths var = do
+          let
+            checkPath path = do
+              absPath <- canonicalizePath path
+              (when (pathInsideSandbox absPath) . warn verbosity)
+                (var ++ " refers to package database " ++ path
+                 ++ " inside the deleted sandbox.")
+          liftM (maybe [] splitSearchPath) (lookupEnv var) >>= mapM_ checkPath
+
+      checkPackagePaths "CABAL_SANDBOX_PACKAGE_PATH"
+      checkPackagePaths "GHC_PACKAGE_PATH"
+      checkPackagePaths "GHCJS_PACKAGE_PATH"
+
 -- Common implementation of 'sandboxAddSource' and 'sandboxAddSourceSnapshot'.
 doAddSource :: Verbosity -> [FilePath] -> FilePath -> PackageEnvironment
                -> BuildTreeRefType
@@ -380,7 +408,7 @@
     (compilerId comp) platform
 
   withAddTimestamps sandboxDir $ do
-    -- FIXME: path canonicalisation is done in addBuildTreeRefs, but we do it
+    -- Path canonicalisation is done in addBuildTreeRefs, but we do it
     -- twice because of the timestamps file.
     buildTreeRefs' <- mapM tryCanonicalizePath buildTreeRefs
     Index.addBuildTreeRefs verbosity indexFile buildTreeRefs' refType
@@ -448,14 +476,54 @@
   (sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags
   indexFile            <- tryGetIndexFilePath (pkgEnvSavedConfig pkgEnv)
 
-  withRemoveTimestamps sandboxDir $ do
+  (results, convDict) <-
     Index.removeBuildTreeRefs verbosity indexFile buildTreeRefs
 
+  let (failedPaths, removedPaths) = partitionEithers results
+      removedRefs = fmap convDict removedPaths
+
+  unless (null removedPaths) $ do
+    removeTimestamps sandboxDir removedPaths
+
+    notice verbosity $ "Success deleting sources: " ++
+      showL removedRefs ++ "\n\n"
+
+  unless (null failedPaths) $ do
+    let groupedFailures = groupBy errorType failedPaths
+    mapM_ handleErrors groupedFailures
+    die $ "The sources with the above errors were skipped. (" ++
+      showL (fmap getPath failedPaths) ++ ")"
+
   notice verbosity $ "Note: 'sandbox delete-source' only unregisters the " ++
     "source dependency, but does not remove the package " ++
     "from the sandbox package DB.\n\n" ++
     "Use 'sandbox hc-pkg -- unregister' to do that."
+  where
+    getPath (Index.ErrNonregisteredSource p) = p
+    getPath (Index.ErrNonexistentSource p) = p
 
+    showPaths f = concat . intersperse " " . fmap (show . f)
+
+    showL = showPaths id
+
+    showE [] = return ' '
+    showE errs = showPaths getPath errs
+
+    errorType Index.ErrNonregisteredSource{} Index.ErrNonregisteredSource{} =
+      True
+    errorType Index.ErrNonexistentSource{} Index.ErrNonexistentSource{} = True
+    errorType _ _ = False
+
+    handleErrors [] = return ()
+    handleErrors errs@(Index.ErrNonregisteredSource{}:_) =
+      warn verbosity ("Sources not registered: " ++ showE errs ++ "\n\n")
+    handleErrors errs@(Index.ErrNonexistentSource{}:_)   =
+      warn verbosity
+      ("Source directory not found for paths: " ++ showE errs ++ "\n"
+       ++ "If you are trying to delete a reference to a removed directory, "
+       ++ "please provide the full absolute path "
+       ++ "(as given by `sandbox list-sources`).\n\n")
+
 -- | Entry point for the 'cabal sandbox list-sources' command.
 sandboxListSources :: Verbosity -> SandboxFlags -> GlobalFlags
                       -> IO ()
@@ -479,11 +547,13 @@
 -- tool with provided arguments, restricted to the sandbox.
 sandboxHcPkg :: Verbosity -> SandboxFlags -> GlobalFlags -> [String] -> IO ()
 sandboxHcPkg verbosity _sandboxFlags globalFlags extraArgs = do
-  (_sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags
+  (sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags
   let configFlags = savedConfigureFlags . pkgEnvSavedConfig $ pkgEnv
-      dbStack     = configPackageDB' configFlags
-  (comp, _platform, conf) <- configCompilerAux' configFlags
-
+  -- Invoke hc-pkg for the most recently configured compiler (if any),
+  -- using the right package-db for the compiler (see #1935).
+  (comp, platform, conf) <- getPersistOrConfigCompiler configFlags
+  let dir         = sandboxPackageDBPath sandboxDir comp platform
+      dbStack     = [GlobalPackageDB, SpecificPackageDB dir]
   Register.invokeHcPkg verbosity comp conf dbStack extraArgs
 
 updateInstallDirs :: Flag Bool
@@ -517,7 +587,7 @@
 loadConfigOrSandboxConfig verbosity globalFlags = do
   let configFileFlag        = globalConfigFile        globalFlags
       sandboxConfigFileFlag = globalSandboxConfigFile globalFlags
-      ignoreSandboxFlag     = globalIgnoreSandbox globalFlags
+      ignoreSandboxFlag     = globalIgnoreSandbox     globalFlags
 
   pkgEnvDir  <- getPkgEnvDir sandboxConfigFileFlag
   pkgEnvType <- classifyPackageEnvironment pkgEnvDir sandboxConfigFileFlag
@@ -533,7 +603,7 @@
     -- Only @cabal.config@ is present.
     UserPackageEnvironment    -> do
       config <- loadConfig verbosity configFileFlag
-      userConfig <- loadUserConfig verbosity pkgEnvDir
+      userConfig <- loadUserConfig verbosity pkgEnvDir Nothing
       let config' = config `mappend` userConfig
       dieIfSandboxRequired config'
       return (NoSandbox, config')
@@ -541,8 +611,13 @@
     -- Neither @cabal.sandbox.config@ nor @cabal.config@ are present.
     AmbientPackageEnvironment -> do
       config <- loadConfig verbosity configFileFlag
+      let globalConstraintsOpt =
+            flagToMaybe . globalConstraintsFile . savedGlobalFlags $ config
+      globalConstraintConfig <-
+        loadUserConfig verbosity pkgEnvDir globalConstraintsOpt
+      let config' = config `mappend` globalConstraintConfig
       dieIfSandboxRequired config
-      return (NoSandbox, config)
+      return (NoSandbox, config')
 
   where
     -- Return the path to the package environment directory - either the
@@ -605,9 +680,10 @@
                          comp platform conf sandboxDir $ \sandboxPkgInfo ->
     unless (null $ modifiedAddSourceDependencies sandboxPkgInfo) $ do
 
+     withRepoContext verbosity globalFlags $ \repoContext -> do
       let args :: InstallArgs
           args = ((configPackageDB' configFlags)
-                 ,(globalRepos globalFlags)
+                 ,repoContext
                  ,comp, platform, conf
                  ,UseSandbox sandboxDir, Just sandboxPkgInfo
                  ,globalFlags, configFlags, configExFlags, installFlags
@@ -630,7 +706,9 @@
       die' message = die (message ++ installFailedInSandbox)
       -- TODO: use a better error message, remove duplication.
       installFailedInSandbox =
-        "Note: when using a sandbox, all packages are required to have consistent dependencies. Try reinstalling/unregistering the offending packages or recreating the sandbox."
+        "Note: when using a sandbox, all packages are required to have "
+        ++ "consistent dependencies. Try reinstalling/unregistering the "
+        ++ "offending packages or recreating the sandbox."
       logMsg message rest = debugNoWrap verbosity message >> rest
 
       topHandler' = topHandlerWith $ \_ -> do
@@ -694,8 +772,8 @@
     toSourcePackage (path, pkgDesc) = SourcePackage
       (packageId pkgDesc) pkgDesc (LocalUnpackedPackage path) Nothing
 
--- | Same as 'withSandboxPackageInfo' if we're inside a sandbox and a no-op
--- otherwise.
+-- | Same as 'withSandboxPackageInfo' if we're inside a sandbox and the
+-- identity otherwise.
 maybeWithSandboxPackageInfo :: Verbosity -> ConfigFlags -> GlobalFlags
                                -> Compiler -> Platform -> ProgramConfiguration
                                -> UseSandbox
@@ -791,3 +869,18 @@
   configCompilerAuxEx configFlags
     --FIXME: make configCompilerAux use a sensible verbosity
     { configVerbosity = fmap lessVerbose (configVerbosity configFlags) }
+
+-- | Try to read the most recently configured compiler from the
+-- 'localBuildInfoFile', falling back on 'configCompilerAuxEx' if it
+-- cannot be read.
+getPersistOrConfigCompiler :: ConfigFlags
+                           -> IO (Compiler, Platform, ProgramConfiguration)
+getPersistOrConfigCompiler configFlags = do
+  distPref <- findDistPrefOrDefault (configDistPref configFlags)
+  mlbi <- maybeGetPersistBuildConfig distPref
+  case mlbi of
+    Nothing  -> do configCompilerAux' configFlags
+    Just lbi -> return ( LocalBuildInfo.compiler lbi
+                       , LocalBuildInfo.hostPlatform lbi
+                       , LocalBuildInfo.withPrograms lbi
+                       )
diff --git a/cabal/cabal-install/Distribution/Client/Sandbox/Index.hs b/cabal/cabal-install/Distribution/Client/Sandbox/Index.hs
--- a/cabal/cabal-install/Distribution/Client/Sandbox/Index.hs
+++ b/cabal/cabal-install/Distribution/Client/Sandbox/Index.hs
@@ -12,42 +12,44 @@
     addBuildTreeRefs,
     removeBuildTreeRefs,
     ListIgnoredBuildTreeRefs(..), RefTypesToList(..),
+    DeleteSourceError(..),
     listBuildTreeRefs,
     validateIndexPath,
 
     defaultIndexFileName
   ) where
 
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
+import qualified Codec.Archive.Tar.Index as Tar
 import qualified Distribution.Client.Tar as Tar
 import Distribution.Client.IndexUtils ( BuildTreeRefType(..)
                                       , refTypeFromTypeCode
                                       , typeCodeFromRefType
                                       , updatePackageIndexCacheFile
-                                      , getSourcePackagesStrict )
-import Distribution.Client.PackageIndex ( allPackages )
-import Distribution.Client.Types ( Repo(..), LocalRepo(..)
-                                 , SourcePackageDb(..)
-                                 , SourcePackage(..), PackageLocation(..) )
+                                      , readCacheStrict
+                                      , Index(..) )
+import qualified Distribution.Client.IndexUtils as IndexUtils
 import Distribution.Client.Utils ( byteStringToFilePath, filePathToByteString
                                  , makeAbsoluteToCwd, tryCanonicalizePath
-                                 , canonicalizePathNoThrow
                                  , tryFindAddSourcePackageDesc  )
 
 import Distribution.Simple.Utils ( die, debug )
+import Distribution.Compat.Exception   ( tryIO )
 import Distribution.Verbosity    ( Verbosity )
 
 import qualified Data.ByteString.Lazy as BS
-import Control.Exception         ( evaluate )
+import Control.Exception         ( evaluate, throw, Exception )
 import Control.Monad             ( liftM, unless )
-import Data.List                 ( (\\), intersect, nub )
-import Data.Maybe                ( catMaybes )
+import Control.Monad.Writer.Lazy (WriterT(..), runWriterT, tell)
+import Data.List                 ( (\\), intersect, nub, find )
+import Data.Maybe                ( catMaybes, fromMaybe )
+import Data.Either               (partitionEithers)
 import System.Directory          ( createDirectoryIfMissing,
                                    doesDirectoryExist, doesFileExist,
-                                   renameFile )
-import System.FilePath           ( (</>), (<.>), takeDirectory, takeExtension
-                                 , replaceExtension )
-import System.IO                 ( IOMode(..), SeekMode(..)
-                                 , hSeek, withBinaryFile )
+                                   renameFile, canonicalizePath)
+import System.FilePath           ( (</>), (<.>), takeDirectory, takeExtension )
+import System.IO                 ( IOMode(..), withBinaryFile )
 
 -- | A reference to a local build tree.
 data BuildTreeRef = BuildTreeRef {
@@ -80,16 +82,27 @@
 
 -- | Given a sequence of tar archive entries, extract all references to local
 -- build trees.
-readBuildTreeRefs :: Tar.Entries -> [BuildTreeRef]
+readBuildTreeRefs :: Exception e => Tar.Entries e -> [BuildTreeRef]
 readBuildTreeRefs =
   catMaybes
-  . Tar.foldrEntries (\e r -> readBuildTreeRef e : r)
-  [] error
+  . Tar.foldEntries (\e r -> readBuildTreeRef e : r)
+                    [] throw
 
 -- | Given a path to a tar archive, extract all references to local build trees.
 readBuildTreeRefsFromFile :: FilePath -> IO [BuildTreeRef]
 readBuildTreeRefsFromFile = liftM (readBuildTreeRefs . Tar.read) . BS.readFile
 
+-- | Read build tree references from an index cache
+readBuildTreeRefsFromCache :: Verbosity -> FilePath -> IO [BuildTreeRef]
+readBuildTreeRefsFromCache verbosity indexPath = do
+    (mRefs, _prefs) <- readCacheStrict verbosity (SandboxIndex indexPath) buildTreeRef
+    return (catMaybes mRefs)
+  where
+    buildTreeRef pkgEntry =
+      case pkgEntry of
+         IndexUtils.NormalPackage _ _ _ _ -> Nothing
+         IndexUtils.BuildTreeRef typ _ _ path _ -> Just $ BuildTreeRef typ path
+
 -- | Given a local build tree ref, serialise it to a tar archive entry.
 writeBuildTreeRef :: BuildTreeRef -> Tar.Entry
 writeBuildTreeRef (BuildTreeRef refType path) = Tar.simpleEntry tarPath content
@@ -143,45 +156,76 @@
   treesToAdd <- mapM (buildTreeRefFromPath refType) (l \\ treesInIndex)
   let entries = map writeBuildTreeRef (catMaybes treesToAdd)
   unless (null entries) $ do
-    offset <-
-      fmap (Tar.foldrEntries (\e acc -> Tar.entrySizeInBytes e + acc) 0 error
-            . Tar.read) $ BS.readFile path
-    _ <- evaluate offset
-    debug verbosity $ "Writing at offset: " ++ show offset
     withBinaryFile path ReadWriteMode $ \h -> do
-      hSeek h AbsoluteSeek (fromIntegral offset)
+      block <- Tar.hSeekEndEntryOffset h Nothing
+      debug verbosity $ "Writing at tar block: " ++ show block
       BS.hPut h (Tar.write entries)
       debug verbosity $ "Successfully appended to '" ++ path ++ "'"
-    updatePackageIndexCacheFile verbosity path
-      (path `replaceExtension` "cache")
+    updatePackageIndexCacheFile verbosity $ SandboxIndex path
 
+data DeleteSourceError = ErrNonregisteredSource { nrPath :: FilePath }
+                       | ErrNonexistentSource   { nePath :: FilePath } deriving Show
+
 -- | Remove given local build tree references from the index.
-removeBuildTreeRefs :: Verbosity -> FilePath -> [FilePath] -> IO [FilePath]
+--
+-- Returns a tuple with either removed build tree refs or errors and a function
+-- that converts from a provided build tree ref to corresponding full directory path.
+removeBuildTreeRefs :: Verbosity -> FilePath -> [FilePath]
+                       -> IO ([Either DeleteSourceError FilePath],
+                              (FilePath -> FilePath))
 removeBuildTreeRefs _         _   [] =
   error "Distribution.Client.Sandbox.Index.removeBuildTreeRefs: unexpected"
-removeBuildTreeRefs verbosity path l' = do
-  checkIndexExists path
-  l <- mapM canonicalizePathNoThrow l'
-  let tmpFile = path <.> "tmp"
+removeBuildTreeRefs verbosity indexPath l = do
+  checkIndexExists indexPath
+  let tmpFile = indexPath <.> "tmp"
+
+  canonRes <- mapM (\btr -> do res <- tryIO $ canonicalizePath btr
+                               return $ case res of
+                                 Right pth -> Right (btr, pth)
+                                 Left _ -> Left $ ErrNonexistentSource btr) l
+  let (failures, convDict) = partitionEithers canonRes
+      allRefs = fmap snd convDict
+
   -- Performance note: on my system, it takes 'index --remove-source'
   -- approx. 3,5s to filter a 65M file. Real-life indices are expected to be
   -- much smaller.
-  BS.writeFile tmpFile . Tar.writeEntries . Tar.filterEntries (p l) . Tar.read
-    =<< BS.readFile path
-  renameFile tmpFile path
+  removedRefs <- doRemove convDict tmpFile
+
+  renameFile tmpFile indexPath
   debug verbosity $ "Successfully renamed '" ++ tmpFile
-    ++ "' to '" ++ path ++ "'"
-  updatePackageIndexCacheFile verbosity path (path `replaceExtension` "cache")
-  -- FIXME: return only the refs that vere actually removed.
-  return l
+    ++ "' to '" ++ indexPath ++ "'"
+
+  unless (null removedRefs) $
+    updatePackageIndexCacheFile verbosity $ SandboxIndex indexPath
+
+  let results = fmap Right removedRefs
+                ++ fmap Left failures
+                ++ fmap (Left . ErrNonregisteredSource)
+                        (fmap (convertWith convDict) (allRefs \\ removedRefs))
+
+  return (results, convertWith convDict)
+
     where
-      p l entry = case readBuildTreeRef entry of
-        Nothing                     -> True
+      doRemove :: [(FilePath, FilePath)] -> FilePath -> IO [FilePath]
+      doRemove srcRefs tmpFile = do
+        (newIdx, changedPaths) <-
+          Tar.read `fmap` BS.readFile indexPath
+          >>= runWriterT . Tar.filterEntriesM (p $ fmap snd srcRefs)
+        BS.writeFile tmpFile . Tar.write . Tar.entriesToList $ newIdx
+        return changedPaths
+
+      p :: [FilePath] -> Tar.Entry -> WriterT [FilePath] IO Bool
+      p refs entry = case readBuildTreeRef entry of
+        Nothing -> return True
         -- FIXME: removing snapshot deps is done with `delete-source
         -- .cabal-sandbox/snapshots/$SNAPSHOT_NAME`. Perhaps we also want to
         -- support removing snapshots by providing the original path.
-        (Just (BuildTreeRef _ pth)) -> pth `notElem` l
+        (Just (BuildTreeRef _ pth)) -> if pth `elem` refs
+                                       then tell [pth] >> return False
+                                       else return True
 
+      convertWith dict pth = fromMaybe pth $ fmap fst $ find ((==pth) . snd) dict
+
 -- | A build tree ref can become ignored if the user later adds a build tree ref
 -- with the same package ID. We display ignored build tree refs when the user
 -- runs 'cabal sandbox list-sources', but do not look at their timestamps in
@@ -222,16 +266,11 @@
         LinksAndSnapshots -> const True
 
       listWithIgnored :: IO [BuildTreeRef]
-      listWithIgnored = readBuildTreeRefsFromFile $ path
+      listWithIgnored = readBuildTreeRefsFromFile path
 
       listWithoutIgnored :: IO [FilePath]
-      listWithoutIgnored = do
-        let repo = Repo { repoKind = Right LocalRepo
-                        , repoLocalDir = takeDirectory path }
-        pkgIndex <- fmap packageIndex
-                    . getSourcePackagesStrict verbosity $ [repo]
-        return [ pkgPath | (LocalUnpackedPackage pkgPath) <-
-                    map packageSource . allPackages $ pkgIndex ]
+      listWithoutIgnored = fmap (map buildTreePath)
+                         $ readBuildTreeRefsFromCache verbosity path
 
 
 -- | Check that the package index file exists and exit with error if it does not.
diff --git a/cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs b/cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs
--- a/cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs
+++ b/cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
+
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Sandbox.PackageEnvironment
@@ -61,10 +62,8 @@
 import Control.Monad                   ( foldM, liftM2, when, unless )
 import Data.List                       ( partition )
 import Data.Maybe                      ( isJust )
-#if !MIN_VERSION_base(4,8,0)
-import Data.Monoid                     ( Monoid(..) )
-#endif
 import Distribution.Compat.Exception   ( catchIO )
+import Distribution.Compat.Semigroup
 import System.Directory                ( doesDirectoryExist, doesFileExist
                                        , renameFile )
 import System.FilePath                 ( (<.>), (</>), takeDirectory )
@@ -75,6 +74,7 @@
 import qualified Distribution.Compat.ReadP as Parse
 import qualified Distribution.ParseUtils   as ParseUtils ( Field(..) )
 import qualified Distribution.Text         as Text
+import GHC.Generics ( Generic )
 
 
 --
@@ -88,20 +88,14 @@
   -- for constructing nested sandboxes (see discussion in #1196).
   pkgEnvInherit       :: Flag FilePath,
   pkgEnvSavedConfig   :: SavedConfig
-}
+} deriving Generic
 
 instance Monoid PackageEnvironment where
-  mempty = PackageEnvironment {
-    pkgEnvInherit       = mempty,
-    pkgEnvSavedConfig   = mempty
-    }
+  mempty = gmempty
+  mappend = (<>)
 
-  mappend a b = PackageEnvironment {
-    pkgEnvInherit       = combine pkgEnvInherit,
-    pkgEnvSavedConfig   = combine pkgEnvSavedConfig
-    }
-    where
-      combine f = f a `mappend` f b
+instance Semigroup PackageEnvironment where
+  (<>) = gmappend
 
 -- | The automatically-created package environment file that should not be
 -- touched by the user.
@@ -280,27 +274,30 @@
       return $ mempty { pkgEnvSavedConfig = conf }
 
 -- | Load the user package environment if it exists (the optional "cabal.config"
--- file).
-userPackageEnvironment :: Verbosity -> FilePath -> IO PackageEnvironment
-userPackageEnvironment verbosity pkgEnvDir = do
-  let path = pkgEnvDir </> userPackageEnvironmentFile
-  minp <- readPackageEnvironmentFile ConstraintSourceUserConfig mempty path
-  case minp of
-    Nothing -> return mempty
-    Just (ParseOk warns parseResult) -> do
+-- file). If it does not exist locally, attempt to load an optional global one.
+userPackageEnvironment :: Verbosity -> FilePath -> Maybe FilePath -> IO PackageEnvironment
+userPackageEnvironment verbosity pkgEnvDir globalConfigLocation = do
+    let path = pkgEnvDir </> userPackageEnvironmentFile
+    minp <- readPackageEnvironmentFile (ConstraintSourceUserConfig path) mempty path
+    case (minp, globalConfigLocation) of
+      (Just parseRes, _)  -> processConfigParse path parseRes
+      (_, Just globalLoc) -> maybe (warn verbosity ("no constraints file found at " ++ globalLoc) >> return mempty) (processConfigParse globalLoc) =<< readPackageEnvironmentFile (ConstraintSourceUserConfig globalLoc) mempty globalLoc
+      _ -> return mempty
+  where
+    processConfigParse path (ParseOk warns parseResult) = do
       when (not $ null warns) $ warn verbosity $
         unlines (map (showPWarning path) warns)
       return parseResult
-    Just (ParseFailed err) -> do
+    processConfigParse path (ParseFailed err) = do
       let (line, msg) = locatedErrorMsg err
-      warn verbosity $ "Error parsing user package environment file " ++ path
+      warn verbosity $ "Error parsing package environment file " ++ path
         ++ maybe "" (\n -> ":" ++ show n) line ++ ":\n" ++ msg
       return mempty
 
 -- | Same as @userPackageEnvironmentFile@, but returns a SavedConfig.
-loadUserConfig :: Verbosity -> FilePath -> IO SavedConfig
-loadUserConfig verbosity pkgEnvDir = fmap pkgEnvSavedConfig
-                                     $ userPackageEnvironment verbosity pkgEnvDir
+loadUserConfig :: Verbosity -> FilePath -> Maybe FilePath -> IO SavedConfig
+loadUserConfig verbosity pkgEnvDir globalConfigLocation =
+    fmap pkgEnvSavedConfig $ userPackageEnvironment verbosity pkgEnvDir globalConfigLocation
 
 -- | Common error handling code used by 'tryLoadSandboxPackageEnvironment' and
 -- 'updatePackageEnvironment'.
@@ -347,7 +344,7 @@
 
   let base   = basePackageEnvironment
   let common = commonPackageEnvironment sandboxDir
-  user      <- userPackageEnvironment verbosity pkgEnvDir
+  user      <- userPackageEnvironment verbosity pkgEnvDir Nothing --TODO
   inherited <- inheritedPackageEnvironment verbosity user
 
   -- Layer the package environment settings over settings from ~/.cabal/config.
@@ -400,7 +397,6 @@
     (fromFlagOrDefault Disp.empty . fmap Disp.text) (optional parseFilePathQ)
     pkgEnvInherit (\v pkgEnv -> pkgEnv { pkgEnvInherit = v })
 
-    -- FIXME: Should we make these fields part of ~/.cabal/config ?
   , commaNewLineListField "constraints"
     (Text.disp . fst) ((\pc -> (pc, src)) `fmap` Text.parse)
     (configExConstraints . savedConfigureExFlags . pkgEnvSavedConfig)
diff --git a/cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs b/cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs
--- a/cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs
+++ b/cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs
@@ -10,10 +10,15 @@
 module Distribution.Client.Sandbox.Timestamp (
   AddSourceTimestamp,
   withAddTimestamps,
-  withRemoveTimestamps,
   withUpdateTimestamps,
   maybeAddCompilerTimestampRecord,
   listModifiedDeps,
+  removeTimestamps,
+
+  -- * For testing
+  TimestampFileRecord,
+  readTimestampFile,
+  writeTimestampFile
   ) where
 
 import Control.Exception                             (IOException)
@@ -50,12 +55,13 @@
   (inDir, removeExistingFile, tryCanonicalizePath, tryFindAddSourcePackageDesc)
 
 import Distribution.Compat.Exception                 (catchIO)
-import Distribution.Client.Compat.Time               (EpochTime, getCurTime,
-                                                      getModTime)
+import Distribution.Client.Compat.Time               (ModTime, getCurTime,
+                                                      getModTime,
+                                                      posixSecondsToModTime)
 
 
 -- | Timestamp of an add-source dependency.
-type AddSourceTimestamp  = (FilePath, EpochTime)
+type AddSourceTimestamp  = (FilePath, ModTime)
 -- | Timestamp file record - a string identifying the compiler & platform plus a
 -- list of add-source timestamps.
 type TimestampFileRecord = (String, [AddSourceTimestamp])
@@ -79,15 +85,37 @@
 readTimestampFile timestampFile = do
   timestampString <- readFile timestampFile `catchIO` \_ -> return "[]"
   case reads timestampString of
-    [(timestamps, s)] | all isSpace s -> return timestamps
-    _                                 ->
-      die $ "The timestamps file is corrupted. "
-      ++ "Please delete & recreate the sandbox."
+    [(version, s)]
+      | version == (2::Int) ->
+        case reads s of
+          [(timestamps, s')] | all isSpace s' -> return timestamps
+          _                                   -> dieCorrupted
+      | otherwise   -> dieWrongFormat
 
+    -- Old format (timestamps are POSIX seconds). Convert to new format.
+    [] ->
+      case reads timestampString of
+        [(timestamps, s)] | all isSpace s -> do
+          let timestamps' = map (\(i, ts) ->
+                                  (i, map (\(p, t) ->
+                                            (p, posixSecondsToModTime t)) ts))
+                            timestamps
+          writeTimestampFile timestampFile timestamps'
+          return timestamps'
+        _ -> dieCorrupted
+    _ -> dieCorrupted
+  where
+    dieWrongFormat    = die $ wrongFormat ++ deleteAndRecreate
+    dieCorrupted      = die $ corrupted ++ deleteAndRecreate
+    wrongFormat       = "The timestamps file is in the wrong format."
+    corrupted         = "The timestamps file is corrupted."
+    deleteAndRecreate = " Please delete and recreate the sandbox."
+
 -- | Write the timestamp file, atomically.
 writeTimestampFile :: FilePath -> [TimestampFileRecord] -> IO ()
 writeTimestampFile timestampFile timestamps = do
-  writeFile  timestampTmpFile (show timestamps)
+  writeFile  timestampTmpFile "2\n" -- version
+  appendFile timestampTmpFile (show timestamps ++ "\n")
   renameFile timestampTmpFile timestampFile
   where
     timestampTmpFile = timestampFile <.> "tmp"
@@ -105,7 +133,7 @@
 -- we've added and an initial timestamp, add an 'AddSourceTimestamp' to the list
 -- for each path. If a timestamp for a given path already exists in the list,
 -- update it.
-addTimestamps :: EpochTime -> [AddSourceTimestamp] -> [FilePath]
+addTimestamps :: ModTime -> [AddSourceTimestamp] -> [FilePath]
                  -> [AddSourceTimestamp]
 addTimestamps initial timestamps newPaths =
   [ (p, initial) | p <- newPaths ] ++ oldTimestamps
@@ -116,7 +144,7 @@
 -- | Given a list of 'AddSourceTimestamp's, a list of paths to add-source deps
 -- we've reinstalled and a new timestamp value, update the timestamp value for
 -- the deps in the list. If there are new paths in the list, ignore them.
-updateTimestamps :: [AddSourceTimestamp] -> [FilePath] -> EpochTime
+updateTimestamps :: [AddSourceTimestamp] -> [FilePath] -> ModTime
                     -> [AddSourceTimestamp]
 updateTimestamps timestamps pathsToUpdate newTimestamp =
   foldr updateTimestamp [] timestamps
@@ -127,8 +155,8 @@
 
 -- | Given a list of 'TimestampFileRecord's and a list of paths to add-source
 -- deps we've removed, remove those deps from the list.
-removeTimestamps :: [AddSourceTimestamp] -> [FilePath] -> [AddSourceTimestamp]
-removeTimestamps l pathsToRemove = foldr removeTimestamp [] l
+removeTimestamps' :: [AddSourceTimestamp] -> [FilePath] -> [AddSourceTimestamp]
+removeTimestamps' l pathsToRemove = foldr removeTimestamp [] l
   where
     removeTimestamp t@(path, _oldTimestamp) rest =
       if path `elem` pathsToRemove
@@ -156,13 +184,14 @@
 -- build tree refs to the timestamps file (for all compilers).
 withAddTimestamps :: FilePath -> IO [FilePath] -> IO ()
 withAddTimestamps sandboxDir act = do
-  let initialTimestamp = 0
+  let initialTimestamp = minBound
   withActionOnAllTimestamps (addTimestamps initialTimestamp) sandboxDir act
 
--- | Given an IO action that returns a list of build tree refs, remove those
+-- | Given a list of build tree refs, remove those
 -- build tree refs from the timestamps file (for all compilers).
-withRemoveTimestamps :: FilePath -> IO [FilePath] -> IO ()
-withRemoveTimestamps = withActionOnAllTimestamps removeTimestamps
+removeTimestamps :: FilePath -> [FilePath] -> IO ()
+removeTimestamps idxFile =
+  withActionOnAllTimestamps removeTimestamps' idxFile . return
 
 -- | Given an IO action that returns a list of build tree refs, update the
 -- timestamps of the returned build tree refs to the current time (only for the
@@ -191,7 +220,7 @@
 -- list of 'AddSourceTimestamp's for this compiler, applies 'f' to the result
 -- and then updates the timestamp file record. The IO action is run only once.
 withActionOnCompilerTimestamps :: ([AddSourceTimestamp]
-                                   -> [FilePath] -> EpochTime
+                                   -> [FilePath] -> ModTime
                                    -> [AddSourceTimestamp])
                                   -> FilePath
                                   -> CompilerId
@@ -249,7 +278,7 @@
   return ret
 
 -- | Has this dependency been modified since we have last looked at it?
-isDepModified :: Verbosity -> EpochTime -> AddSourceTimestamp -> IO Bool
+isDepModified :: Verbosity -> ModTime -> AddSourceTimestamp -> IO Bool
 isDepModified verbosity now (packageDir, timestamp) = do
   debug verbosity ("Checking whether the dependency is modified: " ++ packageDir)
   depSources <- allPackageSourceFiles verbosity packageDir
diff --git a/cabal/cabal-install/Distribution/Client/Sandbox/Types.hs b/cabal/cabal-install/Distribution/Client/Sandbox/Types.hs
--- a/cabal/cabal-install/Distribution/Client/Sandbox/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/Sandbox/Types.hs
@@ -14,10 +14,11 @@
   ) where
 
 import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex
-import Distribution.Client.Types (SourcePackage)
+import Distribution.Client.Types (UnresolvedSourcePackage)
+import Distribution.Compat.Semigroup (Semigroup((<>)))
 
 #if !MIN_VERSION_base(4,8,0)
-import Data.Monoid
+import Data.Monoid (Monoid(..))
 #endif
 import qualified Data.Set as S
 
@@ -26,10 +27,12 @@
 
 instance Monoid UseSandbox where
   mempty = NoSandbox
+  mappend = (<>)
 
-  NoSandbox        `mappend` s                  = s
-  u0@(UseSandbox _) `mappend` NoSandbox         = u0
-  (UseSandbox   _)  `mappend` u1@(UseSandbox _) = u1
+instance Semigroup UseSandbox where
+  NoSandbox         <> s                 = s
+  u0@(UseSandbox _) <> NoSandbox         = u0
+  (UseSandbox _)    <> u1@(UseSandbox _) = u1
 
 -- | Convert a @UseSandbox@ value to a boolean. Useful in conjunction with
 -- @when@.
@@ -46,11 +49,11 @@
 -- | Data about the packages installed in the sandbox that is passed from
 -- 'reinstallAddSourceDeps' to the solver.
 data SandboxPackageInfo = SandboxPackageInfo {
-  modifiedAddSourceDependencies :: ![SourcePackage],
+  modifiedAddSourceDependencies :: ![UnresolvedSourcePackage],
   -- ^ Modified add-source deps that we want to reinstall. These are guaranteed
   -- to be already installed in the sandbox.
 
-  otherAddSourceDependencies    :: ![SourcePackage],
+  otherAddSourceDependencies    :: ![UnresolvedSourcePackage],
   -- ^ Remaining add-source deps. Some of these may be not installed in the
   -- sandbox.
 
diff --git a/cabal/cabal-install/Distribution/Client/Security/HTTP.hs b/cabal/cabal-install/Distribution/Client/Security/HTTP.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/Distribution/Client/Security/HTTP.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+-- | Implementation of 'HttpLib' using cabal-install's own 'HttpTransport'
+module Distribution.Client.Security.HTTP (HttpLib, transportAdapter) where
+
+-- stdlibs
+import Control.Exception
+         ( Exception(..), IOException )
+import Data.List
+         ( intercalate )
+import Data.Typeable
+         ( Typeable )
+import System.Directory
+         ( getTemporaryDirectory )
+import Network.URI
+         ( URI )
+import qualified Data.ByteString.Lazy as BS.L
+import qualified Network.HTTP         as HTTP
+
+-- Cabal/cabal-install
+import Distribution.Verbosity
+         ( Verbosity )
+import Distribution.Client.HttpUtils
+         ( HttpTransport(..), HttpCode )
+import Distribution.Client.Utils
+         ( withTempFileName )
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Repository.HttpLib
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Pretty
+import qualified Hackage.Security.Util.Lens as Lens
+
+{-------------------------------------------------------------------------------
+  'HttpLib' implementation
+-------------------------------------------------------------------------------}
+
+-- | Translate from hackage-security's 'HttpLib' to cabal-install's 'HttpTransport'
+--
+-- NOTE: The match between these two APIs is currently not perfect:
+--
+-- * We don't get any response headers back from the 'HttpTransport', so we
+--   don't know if the server supports range requests. For now we optimistically
+--   assume that it does.
+-- * The 'HttpTransport' wants to know where to place the resulting file,
+--   whereas the 'HttpLib' expects an 'IO' action which streams the download;
+--   the security library then makes sure that the file gets written to a
+--   location which is suitable (in particular, to a temporary file in the
+--   directory where the file needs to end up, so that it can "finalize" the
+--   file simply by doing 'renameFile'). Right now we write the file to a
+--   temporary file in the system temp directory here and then read it again
+--   to pass it to the security library; this is a problem for two reasons: it
+--   is a source of inefficiency; and it means that the security library cannot
+--   insist on a minimum download rate (potential security attack).
+--   Fixing it however would require changing the 'HttpTransport'.
+transportAdapter :: Verbosity -> IO HttpTransport -> HttpLib
+transportAdapter verbosity getTransport = HttpLib{
+      httpGet      = \headers uri callback -> do
+                        transport <- getTransport
+                        get verbosity transport headers uri callback
+    , httpGetRange = \headers uri range callback -> do
+                        transport <- getTransport
+                        getRange verbosity transport headers uri range callback
+    }
+
+get :: Throws SomeRemoteError
+    => Verbosity
+    -> HttpTransport
+    -> [HttpRequestHeader] -> URI
+    -> ([HttpResponseHeader] -> BodyReader -> IO a)
+    -> IO a
+get verbosity transport reqHeaders uri callback = wrapCustomEx $ do
+  get' verbosity transport reqHeaders uri Nothing $ \code respHeaders br ->
+    case code of
+      200 -> callback respHeaders br
+      _   -> throwChecked $ UnexpectedResponse uri code
+
+getRange :: Throws SomeRemoteError
+         => Verbosity
+         -> HttpTransport
+         -> [HttpRequestHeader] -> URI -> (Int, Int)
+         -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)
+         -> IO a
+getRange verbosity transport reqHeaders uri range callback = wrapCustomEx $ do
+  get' verbosity transport reqHeaders uri (Just range) $ \code respHeaders br ->
+    case code of
+       200 -> callback HttpStatus200OK             respHeaders br
+       206 -> callback HttpStatus206PartialContent respHeaders br
+       _   -> throwChecked $ UnexpectedResponse uri code
+
+-- | Internal generalization of 'get' and 'getRange'
+get' :: Verbosity
+     -> HttpTransport
+     -> [HttpRequestHeader] -> URI -> Maybe (Int, Int)
+     -> (HttpCode -> [HttpResponseHeader] -> BodyReader -> IO a)
+     -> IO a
+get' verbosity transport reqHeaders uri mRange callback = do
+    tempDir <- getTemporaryDirectory
+    withTempFileName tempDir "transportAdapterGet" $ \temp -> do
+      (code, _etag) <- getHttp transport verbosity uri Nothing temp reqHeaders'
+      br <- bodyReaderFromBS =<< BS.L.readFile temp
+      callback code [HttpResponseAcceptRangesBytes] br
+  where
+    reqHeaders' = mkReqHeaders reqHeaders mRange
+
+{-------------------------------------------------------------------------------
+  Request headers
+-------------------------------------------------------------------------------}
+
+mkRangeHeader :: Int -> Int -> HTTP.Header
+mkRangeHeader from to = HTTP.Header HTTP.HdrRange rangeHeader
+  where
+    -- Content-Range header uses inclusive rather than exclusive bounds
+    -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>
+    rangeHeader = "bytes=" ++ show from ++ "-" ++ show (to - 1)
+
+mkReqHeaders :: [HttpRequestHeader] -> Maybe (Int, Int) -> [HTTP.Header]
+mkReqHeaders reqHeaders mRange = concat [
+      tr [] reqHeaders
+    , [mkRangeHeader fr to | Just (fr, to) <- [mRange]]
+    ]
+  where
+    tr :: [(HTTP.HeaderName, [String])] -> [HttpRequestHeader] -> [HTTP.Header]
+    tr acc [] =
+      concatMap finalize acc
+    tr acc (HttpRequestMaxAge0:os) =
+      tr (insert HTTP.HdrCacheControl ["max-age=0"] acc) os
+    tr acc (HttpRequestNoTransform:os) =
+      tr (insert HTTP.HdrCacheControl ["no-transform"] acc) os
+
+    -- Some headers are comma-separated, others need multiple headers for
+    -- multiple options.
+    --
+    -- TODO: Right we we just comma-separate all of them.
+    finalize :: (HTTP.HeaderName, [String]) -> [HTTP.Header]
+    finalize (name, strs) = [HTTP.Header name (intercalate ", " (reverse strs))]
+
+    insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]
+    insert x y = Lens.modify (Lens.lookupM x) (++ y)
+
+{-------------------------------------------------------------------------------
+  Custom exceptions
+-------------------------------------------------------------------------------}
+
+data UnexpectedResponse = UnexpectedResponse URI Int
+  deriving (Typeable)
+
+instance Pretty UnexpectedResponse where
+  pretty (UnexpectedResponse uri code) = "Unexpected response " ++ show code
+                                      ++ "for " ++ show uri
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show UnexpectedResponse
+instance Exception UnexpectedResponse where displayException = pretty
+#else
+instance Show UnexpectedResponse where show = pretty
+instance Exception UnexpectedResponse
+#endif
+
+wrapCustomEx :: ( ( Throws UnexpectedResponse
+                  , Throws IOException
+                  ) => IO a)
+             -> (Throws SomeRemoteError => IO a)
+wrapCustomEx act = handleChecked (\(ex :: UnexpectedResponse) -> go ex)
+                 $ handleChecked (\(ex :: IOException)        -> go ex)
+                 $ act
+  where
+    go ex = throwChecked (SomeRemoteError ex)
diff --git a/cabal/cabal-install/Distribution/Client/Setup.hs b/cabal/cabal-install/Distribution/Client/Setup.hs
--- a/cabal/cabal-install/Distribution/Client/Setup.hs
+++ b/cabal/cabal-install/Distribution/Client/Setup.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Setup
@@ -12,13 +15,15 @@
 --
 -----------------------------------------------------------------------------
 module Distribution.Client.Setup
-    ( globalCommand, GlobalFlags(..), defaultGlobalFlags, globalRepos
+    ( globalCommand, GlobalFlags(..), defaultGlobalFlags
+    , RepoContext(..), withRepoContext
     , configureCommand, ConfigFlags(..), filterConfigureFlags
     , configureExCommand, ConfigExFlags(..), defaultConfigExFlags
                         , configureExOptions
     , buildCommand, BuildFlags(..), BuildExFlags(..), SkipAddSourceDepsCheck(..)
     , replCommand, testCommand, benchmarkCommand
     , installCommand, InstallFlags(..), installOptions, defaultInstallFlags
+    , defaultSolver, defaultMaxBackjumps
     , listCommand, ListFlags(..)
     , updateCommand
     , upgradeCommand
@@ -26,6 +31,7 @@
     , infoCommand, InfoFlags(..)
     , fetchCommand, FetchFlags(..)
     , freezeCommand, FreezeFlags(..)
+    , genBoundsCommand
     , getCommand, unpackCommand, GetFlags(..)
     , checkCommand
     , formatCommand
@@ -39,6 +45,7 @@
     , sandboxCommand, defaultSandboxLocation, SandboxFlags(..)
     , execCommand, ExecFlags(..)
     , userConfigCommand, UserConfigFlags(..)
+    , manpageCommand
 
     , parsePackageArgs
     --TODO: stop exporting these:
@@ -48,11 +55,11 @@
     ) where
 
 import Distribution.Client.Types
-         ( Username(..), Password(..), Repo(..), RemoteRepo(..), LocalRepo(..) )
+         ( Username(..), Password(..), RemoteRepo(..) )
 import Distribution.Client.BuildReports.Types
          ( ReportLevel(..) )
 import Distribution.Client.Dependency.Types
-         ( AllowNewer(..), PreSolver(..), ConstraintSource(..) )
+         ( PreSolver(..), ConstraintSource(..) )
 import qualified Distribution.Client.Init.Types as IT
          ( InitFlags(..), PackageType(..) )
 import Distribution.Client.Targets
@@ -60,6 +67,7 @@
 import Distribution.Utils.NubList
          ( NubList, toNubList, fromNubList)
 
+
 import Distribution.Simple.Compiler (PackageDB)
 import Distribution.Simple.Program
          ( defaultProgramConfiguration )
@@ -71,8 +79,9 @@
          , TestFlags(..), BenchmarkFlags(..)
          , SDistFlags(..), HaddockFlags(..)
          , readPackageDbList, showPackageDbList
-         , Flag(..), toFlag, fromFlag, flagToMaybe, flagToList
-         , optionVerbosity, boolOpt, boolOpt', trueArg, falseArg, optionNumJobs )
+         , Flag(..), toFlag, flagToMaybe, flagToList
+         , optionVerbosity, boolOpt, boolOpt', trueArg, falseArg
+         , readPToMaybe, optionNumJobs )
 import Distribution.Simple.InstallDirs
          ( PathTemplate, InstallDirs(sysconfdir)
          , toPathTemplate, fromPathTemplate )
@@ -87,22 +96,25 @@
 import Distribution.ReadE
          ( ReadE(..), readP_to_E, succeedReadE )
 import qualified Distribution.Compat.ReadP as Parse
-         ( ReadP, readP_to_S, readS_to_P, char, munch1, pfail, sepBy1, (+++) )
+         ( ReadP, char, munch1, pfail,  (+++) )
+import Distribution.Compat.Semigroup
 import Distribution.Verbosity
          ( Verbosity, normal )
 import Distribution.Simple.Utils
          ( wrapText, wrapLine )
+import Distribution.Client.GlobalFlags
+         ( GlobalFlags(..), defaultGlobalFlags
+         , RepoContext(..), withRepoContext
+         )
 
 import Data.Char
-         ( isSpace, isAlphaNum )
+         ( isAlphaNum )
 import Data.List
          ( intercalate, deleteFirstsBy )
 import Data.Maybe
-         ( listToMaybe, maybeToList, fromMaybe )
-#if !MIN_VERSION_base(4,8,0)
-import Data.Monoid
-         ( Monoid(..) )
-#endif
+         ( maybeToList, fromMaybe )
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary)
 import Control.Monad
          ( liftM )
 import System.FilePath
@@ -110,42 +122,6 @@
 import Network.URI
          ( parseAbsoluteURI, uriToString )
 
--- ------------------------------------------------------------
--- * Global flags
--- ------------------------------------------------------------
-
--- | Flags that apply at the top level, not to any sub-command.
-data GlobalFlags = GlobalFlags {
-    globalVersion           :: Flag Bool,
-    globalNumericVersion    :: Flag Bool,
-    globalConfigFile        :: Flag FilePath,
-    globalSandboxConfigFile :: Flag FilePath,
-    globalRemoteRepos       :: NubList RemoteRepo,     -- ^ Available Hackage servers.
-    globalCacheDir          :: Flag FilePath,
-    globalLocalRepos        :: NubList FilePath,
-    globalLogsDir           :: Flag FilePath,
-    globalWorldFile         :: Flag FilePath,
-    globalRequireSandbox    :: Flag Bool,
-    globalIgnoreSandbox     :: Flag Bool,
-    globalHttpTransport     :: Flag String
-  }
-
-defaultGlobalFlags :: GlobalFlags
-defaultGlobalFlags  = GlobalFlags {
-    globalVersion           = Flag False,
-    globalNumericVersion    = Flag False,
-    globalConfigFile        = mempty,
-    globalSandboxConfigFile = mempty,
-    globalRemoteRepos       = mempty,
-    globalCacheDir          = mempty,
-    globalLocalRepos        = mempty,
-    globalLogsDir           = mempty,
-    globalWorldFile         = mempty,
-    globalRequireSandbox    = Flag False,
-    globalIgnoreSandbox     = Flag False,
-    globalHttpTransport     = mempty
-  }
-
 globalCommand :: [Command action] -> CommandUI GlobalFlags
 globalCommand commands = CommandUI {
     commandName         = "",
@@ -187,6 +163,7 @@
           , "upload"
           , "report"
           , "freeze"
+          , "gen-bounds"
           , "haddock"
           , "hscolour"
           , "copy"
@@ -237,6 +214,7 @@
         , addCmd "report"
         , par
         , addCmd "freeze"
+        , addCmd "gen-bounds"
         , addCmd "haddock"
         , addCmd "hscolour"
         , addCmd "copy"
@@ -261,9 +239,17 @@
       ++ "  " ++ pname ++ " update\n",
     commandNotes = Nothing,
     commandDefaultFlags = mempty,
-    commandOptions      = \showOrParseArgs ->
-      (case showOrParseArgs of ShowArgs -> take 7; ParseArgs -> id)
-      [option ['V'] ["version"]
+    commandOptions = args
+  }
+  where
+    args :: ShowOrParseArgs -> [OptionField GlobalFlags]
+    args ShowArgs  = argsShown
+    args ParseArgs = argsShown ++ argsNotShown
+
+    -- arguments we want to show in the help
+    argsShown :: [OptionField GlobalFlags]
+    argsShown = [
+       option ['V'] ["version"]
          "Print version information"
          globalVersion (\v flags -> flags { globalVersion = v })
          trueArg
@@ -280,9 +266,14 @@
 
       ,option [] ["sandbox-config-file"]
          "Set an alternate location for the sandbox config file (default: './cabal.sandbox.config')"
-         globalConfigFile (\v flags -> flags { globalSandboxConfigFile = v })
+         globalSandboxConfigFile (\v flags -> flags { globalSandboxConfigFile = v })
          (reqArgFlag "FILE")
 
+      ,option [] ["default-user-config"]
+         "Set a location for a cabal.config file for projects without their own cabal.config freeze file."
+         globalConstraintsFile (\v flags -> flags {globalConstraintsFile = v})
+         (reqArgFlag "FILE")
+
       ,option [] ["require-sandbox"]
          "requiring the presence of a sandbox for sandbox-aware commands"
          globalRequireSandbox (\v flags -> flags { globalRequireSandbox = v })
@@ -293,12 +284,21 @@
          globalIgnoreSandbox (\v flags -> flags { globalIgnoreSandbox = v })
          trueArg
 
+      ,option [] ["ignore-expiry"]
+         "Ignore expiry dates on signed metadata (use only in exceptional circumstances)"
+         globalIgnoreExpiry (\v flags -> flags { globalIgnoreExpiry = v })
+         trueArg
+
       ,option [] ["http-transport"]
          "Set a transport for http(s) requests. Accepts 'curl', 'wget', 'powershell', and 'plain-http'. (default: 'curl')"
-         globalConfigFile (\v flags -> flags { globalHttpTransport = v })
+         globalHttpTransport (\v flags -> flags { globalHttpTransport = v })
          (reqArgFlag "HttpTransport")
+      ]
 
-      ,option [] ["remote-repo"]
+    -- arguments we don't want shown in the help
+    argsNotShown :: [OptionField GlobalFlags]
+    argsNotShown = [
+       option [] ["remote-repo"]
          "The name and url for a remote repository"
          globalRemoteRepos (\v flags -> flags { globalRemoteRepos = v })
          (reqArg' "NAME:URL" (toNubList . maybeToList . readRepo) (map showRepo . fromNubList))
@@ -323,51 +323,7 @@
          globalWorldFile (\v flags -> flags { globalWorldFile = v })
          (reqArgFlag "FILE")
       ]
-  }
 
-instance Monoid GlobalFlags where
-  mempty = GlobalFlags {
-    globalVersion           = mempty,
-    globalNumericVersion    = mempty,
-    globalConfigFile        = mempty,
-    globalSandboxConfigFile = mempty,
-    globalRemoteRepos       = mempty,
-    globalCacheDir          = mempty,
-    globalLocalRepos        = mempty,
-    globalLogsDir           = mempty,
-    globalWorldFile         = mempty,
-    globalRequireSandbox    = mempty,
-    globalIgnoreSandbox     = mempty,
-    globalHttpTransport     = mempty
-  }
-  mappend a b = GlobalFlags {
-    globalVersion           = combine globalVersion,
-    globalNumericVersion    = combine globalNumericVersion,
-    globalConfigFile        = combine globalConfigFile,
-    globalSandboxConfigFile = combine globalConfigFile,
-    globalRemoteRepos       = combine globalRemoteRepos,
-    globalCacheDir          = combine globalCacheDir,
-    globalLocalRepos        = combine globalLocalRepos,
-    globalLogsDir           = combine globalLogsDir,
-    globalWorldFile         = combine globalWorldFile,
-    globalRequireSandbox    = combine globalRequireSandbox,
-    globalIgnoreSandbox     = combine globalIgnoreSandbox,
-    globalHttpTransport     = combine globalHttpTransport
-  }
-    where combine field = field a `mappend` field b
-
-globalRepos :: GlobalFlags -> [Repo]
-globalRepos globalFlags = remoteRepos ++ localRepos
-  where
-    remoteRepos =
-      [ Repo (Left remote) cacheDir
-      | remote <- fromNubList $ globalRemoteRepos globalFlags
-      , let cacheDir = fromFlag (globalCacheDir globalFlags)
-                   </> remoteRepoName remote ]
-    localRepos =
-      [ Repo (Right LocalRepo) local
-      | local <- fromNubList $ globalLocalRepos globalFlags ]
-
 -- ------------------------------------------------------------
 -- * Config flags
 -- ------------------------------------------------------------
@@ -397,6 +353,7 @@
   -- ^ NB: we expect the latest version to be the most common case.
   | cabalLibVersion <  Version [1,3,10] [] = flags_1_3_10
   | cabalLibVersion <  Version [1,10,0] [] = flags_1_10_0
+  | cabalLibVersion <  Version [1,12,0] [] = flags_1_12_0
   | cabalLibVersion <  Version [1,14,0] [] = flags_1_14_0
   | cabalLibVersion <  Version [1,18,0] [] = flags_1_18_0
   | cabalLibVersion <  Version [1,19,1] [] = flags_1_19_0
@@ -406,12 +363,18 @@
   | cabalLibVersion <  Version [1,23,0] [] = flags_1_22_0
   | otherwise = flags_latest
   where
-    -- Cabal >= 1.19.1 uses '--dependency' and does not need '--constraint'.
-    flags_latest = flags        { configConstraints = [] }
+    flags_latest = flags        {
+      -- Cabal >= 1.19.1 uses '--dependency' and does not need '--constraint'.
+      configConstraints = [],
+      -- Passing '--allow-newer' to Setup.hs is unnecessary, we use
+      -- '--exact-configuration' instead.
+      configAllowNewer  = Just Cabal.AllowNewerNone
+      }
 
     -- Cabal < 1.23 doesn't know about '--profiling-detail'.
     flags_1_22_0 = flags_latest { configProfDetail    = NoFlag
-                                , configProfLibDetail = NoFlag }
+                                , configProfLibDetail = NoFlag
+                                , configIPID          = NoFlag }
 
     -- Cabal < 1.22 doesn't know about '--disable-debug-info'.
     flags_1_21_0 = flags_1_22_0 { configDebugInfo = NoFlag }
@@ -440,8 +403,12 @@
     configInstallDirs_1_18_0 = (configInstallDirs flags) { sysconfdir = NoFlag }
     -- Cabal < 1.14.0 doesn't know about '--disable-benchmarks'.
     flags_1_14_0 = flags_1_18_0 { configBenchmarks  = NoFlag }
+    -- Cabal < 1.12.0 doesn't know about '--enable/disable-executable-dynamic'
+    -- and '--enable/disable-library-coverage'.
+    flags_1_12_0 = flags_1_14_0 { configLibCoverage = NoFlag
+                                , configDynExe      = NoFlag }
     -- Cabal < 1.10.0 doesn't know about '--disable-tests'.
-    flags_1_10_0 = flags_1_14_0 { configTests       = NoFlag }
+    flags_1_10_0 = flags_1_12_0 { configTests       = NoFlag }
     -- Cabal < 1.3.10 does not grok the '--constraints' flag.
     flags_1_3_10 = flags_1_10_0 { configConstraints = [] }
 
@@ -455,13 +422,12 @@
     configCabalVersion :: Flag Version,
     configExConstraints:: [(UserConstraint, ConstraintSource)],
     configPreferences  :: [Dependency],
-    configSolver       :: Flag PreSolver,
-    configAllowNewer   :: Flag AllowNewer
+    configSolver       :: Flag PreSolver
   }
+  deriving (Eq, Generic)
 
 defaultConfigExFlags :: ConfigExFlags
-defaultConfigExFlags = mempty { configSolver     = Flag defaultSolver
-                              , configAllowNewer = Flag AllowNewerNone }
+defaultConfigExFlags = mempty { configSolver     = Flag defaultSolver }
 
 configureExCommand :: CommandUI (ConfigFlags, ConfigExFlags)
 configureExCommand = configureCommand {
@@ -505,33 +471,15 @@
 
   , optionSolver configSolver (\v flags -> flags { configSolver = v })
 
-  , option [] ["allow-newer"]
-    ("Ignore upper bounds in all dependencies or " ++ allowNewerArgument)
-    configAllowNewer (\v flags -> flags { configAllowNewer = v})
-    (optArg allowNewerArgument
-     (fmap Flag allowNewerParser) (Flag AllowNewerAll)
-     allowNewerPrinter)
-
   ]
-  where allowNewerArgument = "DEPS"
 
 instance Monoid ConfigExFlags where
-  mempty = ConfigExFlags {
-    configCabalVersion = mempty,
-    configExConstraints= mempty,
-    configPreferences  = mempty,
-    configSolver       = mempty,
-    configAllowNewer   = mempty
-  }
-  mappend a b = ConfigExFlags {
-    configCabalVersion = combine configCabalVersion,
-    configExConstraints= combine configExConstraints,
-    configPreferences  = combine configPreferences,
-    configSolver       = combine configSolver,
-    configAllowNewer   = combine configAllowNewer
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup ConfigExFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Build flags
 -- ------------------------------------------------------------
@@ -542,7 +490,7 @@
 
 data BuildExFlags = BuildExFlags {
   buildOnly     :: Flag SkipAddSourceDepsCheck
-}
+} deriving Generic
 
 buildExOptions :: ShowOrParseArgs -> [OptionField BuildExFlags]
 buildExOptions _showOrParseArgs =
@@ -569,14 +517,12 @@
     parent = Cabal.buildCommand defaultProgramConfiguration
 
 instance Monoid BuildExFlags where
-  mempty = BuildExFlags {
-    buildOnly    = mempty
-  }
-  mappend a b = BuildExFlags {
-    buildOnly    = combine buildOnly
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup BuildExFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Repl command
 -- ------------------------------------------------------------
@@ -799,6 +745,22 @@
 
   }
 
+genBoundsCommand :: CommandUI FreezeFlags
+genBoundsCommand = CommandUI {
+    commandName         = "gen-bounds",
+    commandSynopsis     = "Generate dependency bounds.",
+    commandDescription  = Just $ \_ -> wrapText $
+         "Generates bounds for all dependencies that do not currently have them. "
+      ++ "Generated bounds are printed to stdout.  You can then paste them into your .cabal file.\n"
+      ++ "\n",
+    commandNotes        = Nothing,
+    commandUsage        = usageFlags "gen-bounds",
+    commandDefaultFlags = defaultFreezeFlags,
+    commandOptions      = \ _ -> [
+     optionVerbosity freezeVerbosity (\v flags -> flags { freezeVerbosity = v })
+     ]
+  }
+
 -- ------------------------------------------------------------
 -- * Other commands
 -- ------------------------------------------------------------
@@ -877,6 +839,18 @@
     commandOptions      = \_ -> []
   }
 
+manpageCommand :: CommandUI (Flag Verbosity)
+manpageCommand = CommandUI {
+    commandName         = "manpage",
+    commandSynopsis     = "Outputs manpage source.",
+    commandDescription  = Just $ \_ ->
+      "Output manpage source to STDOUT.\n",
+    commandNotes        = Nothing,
+    commandUsage        = usageFlags "manpage",
+    commandDefaultFlags = toFlag normal,
+    commandOptions      = \_ -> [optionVerbosity id const]
+  }
+
 runCommand :: CommandUI (BuildFlags, BuildExFlags)
 runCommand = CommandUI {
     commandName         = "run",
@@ -918,7 +892,7 @@
     reportUsername  :: Flag Username,
     reportPassword  :: Flag Password,
     reportVerbosity :: Flag Verbosity
-  }
+  } deriving Generic
 
 defaultReportFlags :: ReportFlags
 defaultReportFlags = ReportFlags {
@@ -954,18 +928,12 @@
   }
 
 instance Monoid ReportFlags where
-  mempty = ReportFlags {
-    reportUsername  = mempty,
-    reportPassword  = mempty,
-    reportVerbosity = mempty
-  }
-  mappend a b = ReportFlags {
-    reportUsername  = combine reportUsername,
-    reportPassword  = combine reportPassword,
-    reportVerbosity = combine reportVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup ReportFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Get flags
 -- ------------------------------------------------------------
@@ -975,7 +943,7 @@
     getPristine         :: Flag Bool,
     getSourceRepository :: Flag (Maybe RepoKind),
     getVerbosity        :: Flag Verbosity
-  }
+  } deriving Generic
 
 defaultGetFlags :: GetFlags
 defaultGetFlags = GetFlags {
@@ -1034,20 +1002,12 @@
   }
 
 instance Monoid GetFlags where
-  mempty = GetFlags {
-    getDestDir          = mempty,
-    getPristine         = mempty,
-    getSourceRepository = mempty,
-    getVerbosity        = mempty
-    }
-  mappend a b = GetFlags {
-    getDestDir          = combine getDestDir,
-    getPristine         = combine getPristine,
-    getSourceRepository = combine getSourceRepository,
-    getVerbosity        = combine getVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup GetFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * List flags
 -- ------------------------------------------------------------
@@ -1057,7 +1017,7 @@
     listSimpleOutput :: Flag Bool,
     listVerbosity    :: Flag Verbosity,
     listPackageDBs   :: [Maybe PackageDB]
-  }
+  } deriving Generic
 
 defaultListFlags :: ListFlags
 defaultListFlags = ListFlags {
@@ -1113,20 +1073,12 @@
   }
 
 instance Monoid ListFlags where
-  mempty = ListFlags {
-    listInstalled    = mempty,
-    listSimpleOutput = mempty,
-    listVerbosity    = mempty,
-    listPackageDBs   = mempty
-    }
-  mappend a b = ListFlags {
-    listInstalled    = combine listInstalled,
-    listSimpleOutput = combine listSimpleOutput,
-    listVerbosity    = combine listVerbosity,
-    listPackageDBs   = combine listPackageDBs
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup ListFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Info flags
 -- ------------------------------------------------------------
@@ -1134,7 +1086,7 @@
 data InfoFlags = InfoFlags {
     infoVerbosity  :: Flag Verbosity,
     infoPackageDBs :: [Maybe PackageDB]
-  }
+  } deriving Generic
 
 defaultInfoFlags :: InfoFlags
 defaultInfoFlags = InfoFlags {
@@ -1171,16 +1123,12 @@
   }
 
 instance Monoid InfoFlags where
-  mempty = InfoFlags {
-    infoVerbosity  = mempty,
-    infoPackageDBs = mempty
-    }
-  mappend a b = InfoFlags {
-    infoVerbosity  = combine infoVerbosity,
-    infoPackageDBs = combine infoPackageDBs
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup InfoFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Install flags
 -- ------------------------------------------------------------
@@ -1213,7 +1161,10 @@
     installRunTests         :: Flag Bool,
     installOfflineMode      :: Flag Bool
   }
+  deriving (Eq, Generic)
 
+instance Binary InstallFlags
+
 defaultInstallFlags :: InstallFlags
 defaultInstallFlags = InstallFlags {
     installDocumentation   = Flag False,
@@ -1245,27 +1196,6 @@
     docIndexFile = toPathTemplate ("$datadir" </> "doc"
                                    </> "$arch-$os-$compiler" </> "index.html")
 
-allowNewerParser :: ReadE AllowNewer
-allowNewerParser = ReadE $ \s ->
-  case s of
-    ""      -> Right AllowNewerNone
-    "False" -> Right AllowNewerNone
-    "True"  -> Right AllowNewerAll
-    _       ->
-      case readPToMaybe pkgsParser s of
-        Just pkgs -> Right . AllowNewerSome $ pkgs
-        Nothing   -> Left ("Cannot parse the list of packages: " ++ s)
-  where
-    pkgsParser = Parse.sepBy1 parse (Parse.char ',')
-
-allowNewerPrinter :: Flag AllowNewer -> [Maybe String]
-allowNewerPrinter (Flag AllowNewerNone)        = [Just "False"]
-allowNewerPrinter (Flag AllowNewerAll)         = [Just "True"]
-allowNewerPrinter (Flag (AllowNewerSome pkgs)) =
-  [Just . intercalate "," . map display $ pkgs]
-allowNewerPrinter NoFlag                       = []
-
-
 defaultMaxBackjumps :: Int
 defaultMaxBackjumps = 2000
 
@@ -1482,75 +1412,29 @@
 
 
 instance Monoid InstallFlags where
-  mempty = InstallFlags {
-    installDocumentation   = mempty,
-    installHaddockIndex    = mempty,
-    installDryRun          = mempty,
-    installReinstall       = mempty,
-    installAvoidReinstalls = mempty,
-    installOverrideReinstall = mempty,
-    installMaxBackjumps    = mempty,
-    installUpgradeDeps     = mempty,
-    installReorderGoals    = mempty,
-    installIndependentGoals= mempty,
-    installShadowPkgs      = mempty,
-    installStrongFlags     = mempty,
-    installOnly            = mempty,
-    installOnlyDeps        = mempty,
-    installRootCmd         = mempty,
-    installSummaryFile     = mempty,
-    installLogFile         = mempty,
-    installBuildReports    = mempty,
-    installReportPlanningFailure = mempty,
-    installSymlinkBinDir   = mempty,
-    installOneShot         = mempty,
-    installNumJobs         = mempty,
-    installRunTests        = mempty,
-    installOfflineMode     = mempty
-  }
-  mappend a b = InstallFlags {
-    installDocumentation   = combine installDocumentation,
-    installHaddockIndex    = combine installHaddockIndex,
-    installDryRun          = combine installDryRun,
-    installReinstall       = combine installReinstall,
-    installAvoidReinstalls = combine installAvoidReinstalls,
-    installOverrideReinstall = combine installOverrideReinstall,
-    installMaxBackjumps    = combine installMaxBackjumps,
-    installUpgradeDeps     = combine installUpgradeDeps,
-    installReorderGoals    = combine installReorderGoals,
-    installIndependentGoals= combine installIndependentGoals,
-    installShadowPkgs      = combine installShadowPkgs,
-    installStrongFlags     = combine installStrongFlags,
-    installOnly            = combine installOnly,
-    installOnlyDeps        = combine installOnlyDeps,
-    installRootCmd         = combine installRootCmd,
-    installSummaryFile     = combine installSummaryFile,
-    installLogFile         = combine installLogFile,
-    installBuildReports    = combine installBuildReports,
-    installReportPlanningFailure = combine installReportPlanningFailure,
-    installSymlinkBinDir   = combine installSymlinkBinDir,
-    installOneShot         = combine installOneShot,
-    installNumJobs         = combine installNumJobs,
-    installRunTests        = combine installRunTests,
-    installOfflineMode     = combine installOfflineMode
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup InstallFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Upload flags
 -- ------------------------------------------------------------
 
 data UploadFlags = UploadFlags {
     uploadCheck       :: Flag Bool,
+    uploadDoc         :: Flag Bool,
     uploadUsername    :: Flag Username,
     uploadPassword    :: Flag Password,
     uploadPasswordCmd :: Flag [String],
     uploadVerbosity   :: Flag Verbosity
-  }
+  } deriving Generic
 
 defaultUploadFlags :: UploadFlags
 defaultUploadFlags = UploadFlags {
     uploadCheck       = toFlag False,
+    uploadDoc         = toFlag False,
     uploadUsername    = mempty,
     uploadPassword    = mempty,
     uploadPasswordCmd = mempty,
@@ -1560,7 +1444,7 @@
 uploadCommand :: CommandUI UploadFlags
 uploadCommand = CommandUI {
     commandName         = "upload",
-    commandSynopsis     = "Uploads source packages to Hackage.",
+    commandSynopsis     = "Uploads source packages or documentation to Hackage.",
     commandDescription  = Nothing,
     commandNotes        = Just $ \_ ->
          "You can store your Hackage login in the ~/.cabal/config file\n"
@@ -1576,6 +1460,11 @@
         uploadCheck (\v flags -> flags { uploadCheck = v })
         trueArg
 
+      ,option ['d'] ["documentation"]
+        "Upload documentation instead of a source package. Cannot be used together with --check."
+        uploadDoc (\v flags -> flags { uploadDoc = v })
+        trueArg
+
       ,option ['u'] ["username"]
         "Hackage username."
         uploadUsername (\v flags -> flags { uploadUsername = v })
@@ -1596,22 +1485,12 @@
   }
 
 instance Monoid UploadFlags where
-  mempty = UploadFlags {
-    uploadCheck       = mempty,
-    uploadUsername    = mempty,
-    uploadPassword    = mempty,
-    uploadPasswordCmd = mempty,
-    uploadVerbosity   = mempty
-  }
-  mappend a b = UploadFlags {
-    uploadCheck       = combine uploadCheck,
-    uploadUsername    = combine uploadUsername,
-    uploadPassword    = combine uploadPassword,
-    uploadPasswordCmd = combine uploadPasswordCmd,
-    uploadVerbosity   = combine uploadVerbosity
-  }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup UploadFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Init flags
 -- ------------------------------------------------------------
@@ -1807,7 +1686,7 @@
 data SDistExFlags = SDistExFlags {
     sDistFormat    :: Flag ArchiveFormat
   }
-  deriving Show
+  deriving (Show, Generic)
 
 data ArchiveFormat = TargzFormat | ZipFormat -- | ...
   deriving (Show, Eq)
@@ -1840,22 +1719,19 @@
       ]
 
 instance Monoid SDistExFlags where
-  mempty = SDistExFlags {
-    sDistFormat  = mempty
-  }
-  mappend a b = SDistExFlags {
-    sDistFormat  = combine sDistFormat
-  }
-    where
-      combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup SDistExFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Win32SelfUpgrade flags
 -- ------------------------------------------------------------
 
 data Win32SelfUpgradeFlags = Win32SelfUpgradeFlags {
   win32SelfUpgradeVerbosity :: Flag Verbosity
-}
+} deriving Generic
 
 defaultWin32SelfUpgradeFlags :: Win32SelfUpgradeFlags
 defaultWin32SelfUpgradeFlags = Win32SelfUpgradeFlags {
@@ -1878,21 +1754,19 @@
 }
 
 instance Monoid Win32SelfUpgradeFlags where
-  mempty      = Win32SelfUpgradeFlags {
-    win32SelfUpgradeVerbosity = mempty
-    }
-  mappend a b = Win32SelfUpgradeFlags {
-    win32SelfUpgradeVerbosity = combine win32SelfUpgradeVerbosity
-    }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup Win32SelfUpgradeFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * ActAsSetup flags
 -- ------------------------------------------------------------
 
 data ActAsSetupFlags = ActAsSetupFlags {
     actAsSetupBuildType :: Flag BuildType
-}
+} deriving Generic
 
 defaultActAsSetupFlags :: ActAsSetupFlags
 defaultActAsSetupFlags = ActAsSetupFlags {
@@ -1919,14 +1793,12 @@
 }
 
 instance Monoid ActAsSetupFlags where
-  mempty      = ActAsSetupFlags {
-     actAsSetupBuildType = mempty
-    }
-  mappend a b = ActAsSetupFlags {
-    actAsSetupBuildType = combine actAsSetupBuildType
-    }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup ActAsSetupFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Sandbox-related flags
 -- ------------------------------------------------------------
@@ -1936,7 +1808,7 @@
   sandboxSnapshot  :: Flag Bool, -- FIXME: this should be an 'add-source'-only
                                  -- flag.
   sandboxLocation  :: Flag FilePath
-}
+} deriving Generic
 
 defaultSandboxLocation :: FilePath
 defaultSandboxLocation = ".cabal-sandbox"
@@ -2043,25 +1915,19 @@
   }
 
 instance Monoid SandboxFlags where
-  mempty = SandboxFlags {
-    sandboxVerbosity = mempty,
-    sandboxSnapshot  = mempty,
-    sandboxLocation  = mempty
-    }
-  mappend a b = SandboxFlags {
-    sandboxVerbosity = combine sandboxVerbosity,
-    sandboxSnapshot  = combine sandboxSnapshot,
-    sandboxLocation  = combine sandboxLocation
-    }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup SandboxFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * Exec Flags
 -- ------------------------------------------------------------
 
 data ExecFlags = ExecFlags {
   execVerbosity :: Flag Verbosity
-}
+} deriving Generic
 
 defaultExecFlags :: ExecFlags
 defaultExecFlags = ExecFlags {
@@ -2113,31 +1979,31 @@
   }
 
 instance Monoid ExecFlags where
-  mempty = ExecFlags {
-    execVerbosity = mempty
-    }
-  mappend a b = ExecFlags {
-    execVerbosity = combine execVerbosity
-    }
-    where combine field = field a `mappend` field b
+  mempty = gmempty
+  mappend = (<>)
 
+instance Semigroup ExecFlags where
+  (<>) = gmappend
+
 -- ------------------------------------------------------------
 -- * UserConfig flags
 -- ------------------------------------------------------------
 
 data UserConfigFlags = UserConfigFlags {
-  userConfigVerbosity :: Flag Verbosity
-}
+  userConfigVerbosity :: Flag Verbosity,
+  userConfigForce     :: Flag Bool
+} deriving Generic
 
 instance Monoid UserConfigFlags where
   mempty = UserConfigFlags {
-    userConfigVerbosity = toFlag normal
-    }
-  mappend a b = UserConfigFlags {
-    userConfigVerbosity = combine userConfigVerbosity
+    userConfigVerbosity = toFlag normal,
+    userConfigForce     = toFlag False
     }
-    where combine field = field a `mappend` field b
+  mappend = (<>)
 
+instance Semigroup UserConfigFlags where
+  (<>) = gmappend
+
 userConfigCommand :: CommandUI UserConfigFlags
 userConfigCommand = CommandUI {
   commandName         = "user-config",
@@ -2149,6 +2015,9 @@
     ++ " (i.e. all bindings that are actually defined and not commented out)"
     ++ " and the default config of the new version.\n"
     ++ "\n"
+    ++ "init: Creates a new config file at either ~/.cabal/config or as"
+    ++ " specified by --config-file, if given. An existing file won't be "
+    ++ " overwritten unless -f or --force is given.\n"
     ++ "diff: Shows a pseudo-diff of the user's ~/.cabal/config file and"
     ++ " the default configuration that would be created by cabal if the"
     ++ " config file did not exist.\n"
@@ -2156,10 +2025,14 @@
     ++ " created by default, and write the result back to ~/.cabal/config.",
 
   commandNotes        = Nothing,
-  commandUsage        = usageAlternatives "user-config" ["diff", "update"],
+  commandUsage        = usageAlternatives "user-config" ["init", "diff", "update"],
   commandDefaultFlags = mempty,
   commandOptions      = \ _ -> [
    optionVerbosity userConfigVerbosity (\v flags -> flags { userConfigVerbosity = v })
+ , option ['f'] ["force"]
+     "Overwrite the config file if it already exists."
+     userConfigForce (\v flags -> flags { userConfigForce = v })
+     trueArg
    ]
   }
 
@@ -2201,8 +2074,7 @@
   [ option [] ["max-backjumps"]
       ("Maximum number of backjumps allowed while solving (default: " ++ show defaultMaxBackjumps ++ "). Use a negative number to enable unlimited backtracking. Use 0 to disable backtracking completely.")
       getmbj setmbj
-      (reqArg "NUM" (readP_to_E ("Cannot parse number: "++)
-                                (fmap toFlag (Parse.readS_to_P reads)))
+      (reqArg "NUM" (readP_to_E ("Cannot parse number: "++) (fmap toFlag parse))
                     (map show . flagToList))
   , option [] ["reorder-goals"]
       "Try to reorder goals according to certain heuristics. Slows things down on average, but may make backtracking faster for some packages."
@@ -2250,10 +2122,6 @@
          show arg ++ " is not valid syntax for a package name or"
                   ++ " package dependency."
 
-readPToMaybe :: Parse.ReadP a a -> String -> Maybe a
-readPToMaybe p str = listToMaybe [ r | (r,s) <- Parse.readP_to_S p str
-                                     , all isSpace s ]
-
 parseDependencyOrPackageId :: Parse.ReadP r Dependency
 parseDependencyOrPackageId = parse Parse.+++ liftM pkgidToDependency parse
   where
@@ -2278,7 +2146,9 @@
   return RemoteRepo {
     remoteRepoName           = name,
     remoteRepoURI            = uri,
-    remoteRepoRootKeys       = (),
+    remoteRepoSecure         = Nothing,
+    remoteRepoRootKeys       = [],
+    remoteRepoKeyThreshold   = 0,
     remoteRepoShouldTryHttps = False
   }
 
@@ -2301,6 +2171,7 @@
 
 indentParagraph :: String -> String
 indentParagraph = unlines
+                . (flip (++)) [""]
                 . map (("  "++).unwords)
                 . wrapLine 77
                 . words
diff --git a/cabal/cabal-install/Distribution/Client/SetupWrapper.hs b/cabal/cabal-install/Distribution/Client/SetupWrapper.hs
--- a/cabal/cabal-install/Distribution/Client/SetupWrapper.hs
+++ b/cabal/cabal-install/Distribution/Client/SetupWrapper.hs
@@ -27,9 +27,9 @@
          ( Version(..), VersionRange, anyVersion
          , intersectVersionRanges, orLaterVersion
          , withinRange )
-import Distribution.InstalledPackageInfo (installedPackageId)
+import Distribution.InstalledPackageInfo (installedUnitId)
 import Distribution.Package
-         ( InstalledPackageId(..), PackageIdentifier(..), PackageId,
+         ( UnitId(..), PackageIdentifier(..), PackageId,
            PackageName(..), Package(..), packageName
          , packageVersion, Dependency(..) )
 import Distribution.PackageDescription
@@ -120,8 +120,37 @@
 import qualified System.Win32 as Win32
 #endif
 
+--TODO: The 'setupWrapper' and 'SetupScriptOptions' should be split into two
+-- parts: one that has no policy and just does as it's told with all the
+-- explicit options, and an optional initial part that applies certain
+-- policies (like if we should add the Cabal lib as a dep, and if so which
+-- version). This could be structured as an action that returns a fully
+-- elaborated 'SetupScriptOptions' containing no remaining policy choices.
+--
+-- See also the discussion at https://github.com/haskell/cabal/pull/3094
+
 data SetupScriptOptions = SetupScriptOptions {
+    -- | The version of the Cabal library to use (if 'useDependenciesExclusive'
+    -- is not set). A suitable version of the Cabal library must be installed
+    -- (or for some build-types be the one cabal-install was built with).
+    --
+    -- The version found also determines the version of the Cabal specification
+    -- that we us for talking to the Setup.hs, unless overridden by
+    -- 'useCabalSpecVersion'.
+    --
     useCabalVersion          :: VersionRange,
+
+    -- | This is the version of the Cabal specification that we believe that
+    -- this package uses. This affects the semantics and in particular the
+    -- Setup command line interface.
+    --
+    -- This is similar to 'useCabalVersion' but instead of probing the system
+    -- for a version of the /Cabal library/ you just say exactly which version
+    -- of the /spec/ we will use. Using this also avoid adding the Cabal
+    -- library as an additional dependency, so add it to 'useDependencies'
+    -- if needed.
+    --
+    useCabalSpecVersion      :: Maybe Version,
     useCompiler              :: Maybe Compiler,
     usePlatform              :: Maybe Platform,
     usePackageDB             :: PackageDBStack,
@@ -133,18 +162,29 @@
     forceExternalSetupMethod :: Bool,
 
     -- | List of dependencies to use when building Setup.hs
-    useDependencies :: [(InstalledPackageId, PackageId)],
+    useDependencies :: [(UnitId, PackageId)],
 
     -- | Is the list of setup dependencies exclusive?
     --
-    -- This is here for legacy reasons. Before the introduction of the explicit
-    -- setup stanza in .cabal files we compiled Setup.hs scripts with all
-    -- packages in the environment visible, but we will needed to restrict
-    -- _some_ packages; in particular, we need to restrict the version of Cabal
-    -- that the setup script gets linked against (this was the only "dependency
-    -- constraint" that we had previously for Setup scripts).
+    -- When this is @False@, if we compile the Setup.hs script we do so with
+    -- the list in 'useDependencies' but all other packages in the environment
+    -- are also visible. Additionally, a suitable version of @Cabal@ library
+    -- is added to the list of dependencies (see 'useCabalVersion').
+    --
+    -- When @True@, only the 'useDependencies' packages are used, with other
+    -- packages in the environment hidden.
+    --
+    -- This feature is here to support the setup stanza in .cabal files that
+    -- specifies explicit (and exclusive) dependencies, as well as the old
+    -- style with no dependencies.
     useDependenciesExclusive :: Bool,
 
+    -- | Should we build the Setup.hs with CPP version macros available?
+    -- We turn this on when we have a setup stanza in .cabal that declares
+    -- explicit setup dependencies.
+    --
+    useVersionMacros         :: Bool,
+
     -- Used only by 'cabal clean' on Windows.
     --
     -- Note: win32 clean hack
@@ -174,12 +214,14 @@
 defaultSetupScriptOptions :: SetupScriptOptions
 defaultSetupScriptOptions = SetupScriptOptions {
     useCabalVersion          = anyVersion,
+    useCabalSpecVersion      = Nothing,
     useCompiler              = Nothing,
     usePlatform              = Nothing,
     usePackageDB             = [GlobalPackageDB, UserPackageDB],
     usePackageIndex          = Nothing,
     useDependencies          = [],
     useDependenciesExclusive = False,
+    useVersionMacros         = False,
     useProgramConfig         = emptyProgramConfiguration,
     useDistPref              = defaultDistPref,
     useLoggingHandle         = Nothing,
@@ -235,7 +277,9 @@
     -- between the self and internal setup methods, which are
     -- consistent with each other.
   | buildType' == Custom             = externalSetupMethod
-  | not (cabalVersion `withinRange`
+  | maybe False (cabalVersion /=)
+        (useCabalSpecVersion options)
+ || not (cabalVersion `withinRange`
          useCabalVersion options)    = externalSetupMethod
   | isJust (useLoggingHandle options)
     -- Forcing is done to use an external process e.g. due to parallel
@@ -304,7 +348,8 @@
 externalSetupMethod :: SetupMethod
 externalSetupMethod verbosity options pkg bt mkargs = do
   debug verbosity $ "Using external setup method with build-type " ++ show bt
-  debug verbosity $ "Using explicit dependencies: " ++ show (useDependenciesExclusive options)
+  debug verbosity $ "Using explicit dependencies: "
+    ++ show (useDependenciesExclusive options)
   createDirectoryIfMissingVerbose verbosity True setupDir
   (cabalLibVersion, mCabalLibInstalledPkgId, options') <- cabalLibVersionToUse
   debug verbosity $ "Using Cabal library version " ++ display cabalLibVersion
@@ -335,20 +380,26 @@
       Nothing    -> getInstalledPackages verbosity
                     comp (usePackageDB options') conf
 
-  cabalLibVersionToUse :: IO (Version, (Maybe InstalledPackageId)
+  cabalLibVersionToUse :: IO (Version, (Maybe UnitId)
                              ,SetupScriptOptions)
-  cabalLibVersionToUse = do
-    savedVer <- savedVersion
-    case savedVer of
-      Just version | version `withinRange` useCabalVersion options
-        -> do updateSetupScript version bt
-              -- Does the previously compiled setup executable still exist and
-              -- is it up-to date?
-              useExisting <- canUseExistingSetup version
-              if useExisting
-                then return (version, Nothing, options)
-                else installedVersion
-      _ -> installedVersion
+  cabalLibVersionToUse =
+    case useCabalSpecVersion options of
+      Just version -> do
+        updateSetupScript version bt
+        writeFile setupVersionFile (show version ++ "\n")
+        return (version, Nothing, options)
+      Nothing  -> do
+        savedVer <- savedVersion
+        case savedVer of
+          Just version | version `withinRange` useCabalVersion options
+            -> do updateSetupScript version bt
+                  -- Does the previously compiled setup executable still exist
+                  -- and is it up-to date?
+                  useExisting <- canUseExistingSetup version
+                  if useExisting
+                    then return (version, Nothing, options)
+                    else installedVersion
+          _ -> installedVersion
     where
       -- This check duplicates the checks in 'getCachedSetupExecutable' /
       -- 'compileSetupExecutable'. Unfortunately, we have to perform it twice
@@ -364,7 +415,7 @@
           (&&) <$> setupProgFile `existsAndIsMoreRecentThan` setupHs
                <*> setupProgFile `existsAndIsMoreRecentThan` setupVersionFile
 
-      installedVersion :: IO (Version, Maybe InstalledPackageId
+      installedVersion :: IO (Version, Maybe UnitId
                              ,SetupScriptOptions)
       installedVersion = do
         (comp,    conf,    options')  <- configureCompiler options
@@ -411,10 +462,8 @@
     UnknownBuildType _ -> error "buildTypeScript UnknownBuildType"
 
   installedCabalVersion :: SetupScriptOptions -> Compiler -> ProgramConfiguration
-                        -> IO (Version, Maybe InstalledPackageId
+                        -> IO (Version, Maybe UnitId
                               ,SetupScriptOptions)
-  installedCabalVersion options' _ _ | packageName pkg == PackageName "Cabal" =
-    return (packageVersion pkg, Nothing, options')
   installedCabalVersion options' compiler conf = do
     index <- maybeGetInstalledPackages options' compiler conf
     let cabalDep   = Dependency (PackageName "Cabal") (useCabalVersion options')
@@ -426,7 +475,7 @@
                  ++ " but no suitable version is installed."
       pkgs -> let ipkginfo = head . snd . bestVersion fst $ pkgs
               in return (packageVersion ipkginfo
-                        ,Just . installedPackageId $ ipkginfo, options'')
+                        ,Just . installedUnitId $ ipkginfo, options'')
 
   bestVersion :: (a -> Version) -> [a] -> a
   bestVersion f = firstMaximumBy (comparing (preference . f))
@@ -499,7 +548,7 @@
   -- | Look up the setup executable in the cache; update the cache if the setup
   -- executable is not found.
   getCachedSetupExecutable :: SetupScriptOptions
-                           -> Version -> Maybe InstalledPackageId
+                           -> Version -> Maybe UnitId
                            -> IO FilePath
   getCachedSetupExecutable options' cabalLibVersion
                            maybeCabalLibInstalledPkgId = do
@@ -534,7 +583,7 @@
   -- Currently this is GHC/GHCJS only. It should really be generalised.
   --
   compileSetupExecutable :: SetupScriptOptions
-                         -> Version -> Maybe InstalledPackageId -> Bool
+                         -> Version -> Maybe UnitId -> Bool
                          -> IO FilePath
   compileSetupExecutable options' cabalLibVersion maybeCabalLibInstalledPkgId
                          forceCompile = do
@@ -552,14 +601,20 @@
           cabalDep = maybe [] (\ipkgid -> [(ipkgid, cabalPkgid)])
                               maybeCabalLibInstalledPkgId
 
-          -- We do a few things differently once packages opt-in and declare
-          -- a custom-settup stanza. In particular we then enforce the deps
-          -- specified, but also let the Setup.hs use the version macros.
-          newPedanticDeps     = useDependenciesExclusive options'
-          selectedDeps
-            | newPedanticDeps = useDependencies options'
-            | otherwise       = useDependencies options' ++ cabalDep
-          addRenaming (ipid, pid) = (ipid, pid, defaultRenaming)
+          -- With 'useDependenciesExclusive' we enforce the deps specified,
+          -- so only the given ones can be used. Otherwise we allow the use
+          -- of packages in the ambient environment, and add on a dep on the
+          -- Cabal library.
+          --
+          -- With 'useVersionMacros' we use a version CPP macros .h file.
+          --
+          -- Both of these options should be enabled for packages that have
+          -- opted-in and declared a custom-settup stanza.
+          --
+          selectedDeps | useDependenciesExclusive options'
+                                   = useDependencies options'
+                       | otherwise = useDependencies options' ++ cabalDep
+          addRenaming (ipid, _) = (ipid, defaultRenaming)
           cppMacrosFile = setupDir </> "setup_macros.h"
           ghcOptions = mempty {
               ghcOptVerbosity       = Flag verbosity
@@ -569,17 +624,19 @@
             , ghcOptObjDir          = Flag setupDir
             , ghcOptHiDir           = Flag setupDir
             , ghcOptSourcePathClear = Flag True
-            , ghcOptSourcePath      = toNubListR [workingDir]
+            , ghcOptSourcePath      = case bt of
+                                      Custom -> toNubListR [workingDir]
+                                      _      -> mempty
             , ghcOptPackageDBs      = usePackageDB options''
-            , ghcOptHideAllPackages = Flag newPedanticDeps
-            , ghcOptCabal           = Flag newPedanticDeps
+            , ghcOptHideAllPackages = Flag (useDependenciesExclusive options')
+            , ghcOptCabal           = Flag (useDependenciesExclusive options')
             , ghcOptPackages        = toNubListR $ map addRenaming selectedDeps
             , ghcOptCppIncludes     = toNubListR [ cppMacrosFile
-                                                 | newPedanticDeps ]
+                                                 | useVersionMacros options' ]
             , ghcOptExtra           = toNubListR extraOpts
             }
-      let ghcCmdLine = renderGhcOptions compiler ghcOptions
-      when newPedanticDeps $
+      let ghcCmdLine = renderGhcOptions compiler platform ghcOptions
+      when (useVersionMacros options') $
         rewriteFile cppMacrosFile (generatePackageVersionMacros
                                      [ pid | (_ipid, pid) <- selectedDeps ])
       case useLoggingHandle options of
diff --git a/cabal/cabal-install/Distribution/Client/SrcDist.hs b/cabal/cabal-install/Distribution/Client/SrcDist.hs
--- a/cabal/cabal-install/Distribution/Client/SrcDist.hs
+++ b/cabal/cabal-install/Distribution/Client/SrcDist.hs
@@ -33,7 +33,7 @@
 import Distribution.Version   (Version(..), orLaterVersion)
 
 import System.FilePath ((</>), (<.>))
-import Control.Monad (when, unless)
+import Control.Monad (when, unless, liftM)
 import System.Directory (doesFileExist, removeFile, canonicalizePath)
 import System.Process (runProcess, waitForProcess)
 import System.Exit    (ExitCode(..))
@@ -41,9 +41,8 @@
 -- |Create a source distribution.
 sdist :: SDistFlags -> SDistExFlags -> IO ()
 sdist flags exflags = do
-  pkg <- return . flattenPackageDescription
-         =<< readPackageDescription verbosity
-         =<< defaultPackageDesc verbosity
+  pkg <- liftM flattenPackageDescription
+    (readPackageDescription verbosity =<< defaultPackageDesc verbosity)
   let withDir = if not needMakeArchive then (\f -> f tmpTargetDir)
                 else withTempDirectory verbosity tmpTargetDir "sdist."
   -- 'withTempDir' fails if we don't create 'tmpTargetDir'...
diff --git a/cabal/cabal-install/Distribution/Client/Tar.hs b/cabal/cabal-install/Distribution/Client/Tar.hs
--- a/cabal/cabal-install/Distribution/Client/Tar.hs
+++ b/cabal/cabal-install/Distribution/Client/Tar.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE DeriveFunctor #-}
-{-# OPTIONS_GHC -fno-warn-unused-imports #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Tar
@@ -15,94 +15,27 @@
 --
 -----------------------------------------------------------------------------
 module Distribution.Client.Tar (
-  -- * High level \"all in one\" operations
+  -- * @tar.gz@ operations
   createTarGzFile,
   extractTarGzFile,
 
-  -- * Converting between internal and external representation
-  read,
-  write,
-  writeEntries,
-
-  -- * Packing and unpacking files to\/from internal representation
-  pack,
-  unpack,
-
-  -- * Tar entry and associated types
-  Entry(..),
-  entryPath,
-  EntryContent(..),
-  Ownership(..),
-  FileSize,
-  Permissions,
-  EpochTime,
-  DevMajor,
-  DevMinor,
-  TypeCode,
-  Format(..),
+  -- * Other local utils
   buildTreeRefTypeCode,
   buildTreeSnapshotTypeCode,
   isBuildTreeRefTypeCode,
-  entrySizeInBlocks,
-  entrySizeInBytes,
-
-  -- * Constructing simple entry values
-  simpleEntry,
-  fileEntry,
-  directoryEntry,
-
-  -- * TarPath type
-  TarPath,
-  toTarPath,
-  fromTarPath,
-
-  -- ** Sequences of tar entries
-  Entries(..),
-  foldrEntries,
-  foldlEntries,
-  unfoldrEntries,
-  mapEntries,
   filterEntries,
-  entriesIndex,
-
+  filterEntriesM,
+  entriesToList,
   ) where
 
-import Data.Char     (ord)
-import Data.Int      (Int64)
-import Data.Bits     (Bits, shiftL, testBit)
-import Data.List     (foldl')
-import Numeric       (readOct, showOct)
-import Control.Applicative (Applicative(..))
-import Control.Monad (MonadPlus(mplus), when, ap, liftM)
-import qualified Data.Map as Map
-import qualified Data.ByteString.Lazy as BS
-import qualified Data.ByteString.Lazy.Char8 as BS.Char8
-import Data.ByteString.Lazy (ByteString)
-import qualified Codec.Compression.GZip as GZip
+import qualified Data.ByteString.Lazy    as BS
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
+import qualified Codec.Archive.Tar.Check as Tar
+import qualified Codec.Compression.GZip  as GZip
 import qualified Distribution.Client.GZipUtils as GZipUtils
 
-import System.FilePath
-         ( (</>) )
-import qualified System.FilePath         as FilePath.Native
-import qualified System.FilePath.Windows as FilePath.Windows
-import qualified System.FilePath.Posix   as FilePath.Posix
-import System.Directory
-         ( getDirectoryContents, doesDirectoryExist
-         , getPermissions, createDirectoryIfMissing, copyFile )
-import qualified System.Directory as Permissions
-         ( Permissions(executable) )
-import Distribution.Client.Compat.FilePerms
-         ( setFileExecutable )
-import System.Posix.Types
-         ( FileMode )
-import Distribution.Client.Compat.Time
-         ( EpochTime, getModTime )
-import System.IO
-         ( IOMode(ReadMode), openBinaryFile, hFileSize )
-import System.IO.Unsafe (unsafeInterleaveIO)
-
-import Prelude hiding (read)
-
+import Control.Exception (Exception(..), throw)
 
 --
 -- * High level operations
@@ -113,839 +46,65 @@
                 -> FilePath  -- ^ Directory to archive, relative to base dir
                 -> IO ()
 createTarGzFile tar base dir =
-  BS.writeFile tar . GZip.compress . write =<< pack base [dir]
+  BS.writeFile tar . GZip.compress . Tar.write =<< Tar.pack base [dir]
 
 extractTarGzFile :: FilePath -- ^ Destination directory
                  -> FilePath -- ^ Expected subdir (to check for tarbombs)
                  -> FilePath -- ^ Tarball
                 -> IO ()
 extractTarGzFile dir expected tar =
-  unpack dir . checkTarbomb expected . read
+  Tar.unpack dir . Tar.checkTarbomb expected . Tar.read
   . GZipUtils.maybeDecompress =<< BS.readFile tar
 
---
--- * Entry type
---
+instance (Exception a, Exception b) => Exception (Either a b) where
+  toException (Left  e) = toException e
+  toException (Right e) = toException e
 
-type FileSize  = Int64
-type DevMajor  = Int
-type DevMinor  = Int
-type TypeCode  = Char
-type Permissions = FileMode
+  fromException e =
+    case fromException e of
+      Just e' -> Just (Left e')
+      Nothing -> case fromException e of
+                   Just e' -> Just (Right e')
+                   Nothing -> Nothing
 
--- | Tar archive entry.
---
-data Entry = Entry {
 
-    -- | The path of the file or directory within the archive. This is in a
-    -- tar-specific form. Use 'entryPath' to get a native 'FilePath'.
-    entryTarPath :: !TarPath,
-
-    -- | The real content of the entry. For 'NormalFile' this includes the
-    -- file data. An entry usually contains a 'NormalFile' or a 'Directory'.
-    entryContent :: !EntryContent,
-
-    -- | File permissions (Unix style file mode).
-    entryPermissions :: !Permissions,
-
-    -- | The user and group to which this file belongs.
-    entryOwnership :: !Ownership,
-
-    -- | The time the file was last modified.
-    entryTime :: !EpochTime,
-
-    -- | The tar format the archive is using.
-    entryFormat :: !Format
-  }
-
 -- | Type code for the local build tree reference entry type. We don't use the
 -- symbolic link entry type because it allows only 100 ASCII characters for the
 -- path.
-buildTreeRefTypeCode :: TypeCode
+buildTreeRefTypeCode :: Tar.TypeCode
 buildTreeRefTypeCode = 'C'
 
 -- | Type code for the local build tree snapshot entry type.
-buildTreeSnapshotTypeCode :: TypeCode
+buildTreeSnapshotTypeCode :: Tar.TypeCode
 buildTreeSnapshotTypeCode = 'S'
 
 -- | Is this a type code for a build tree reference?
-isBuildTreeRefTypeCode :: TypeCode -> Bool
+isBuildTreeRefTypeCode :: Tar.TypeCode -> Bool
 isBuildTreeRefTypeCode typeCode
   | (typeCode == buildTreeRefTypeCode
      || typeCode == buildTreeSnapshotTypeCode) = True
   | otherwise                                  = False
 
--- | Native 'FilePath' of the file or directory within the archive.
---
-entryPath :: Entry -> FilePath
-entryPath = fromTarPath . entryTarPath
-
--- | Return the size of an entry in bytes.
-entrySizeInBytes :: Entry -> FileSize
-entrySizeInBytes = (*512) . fromIntegral . entrySizeInBlocks
-
--- | Return the number of blocks in an entry.
-entrySizeInBlocks :: Entry -> Int
-entrySizeInBlocks entry = 1 + case entryContent entry of
-  NormalFile     _   size -> bytesToBlocks size
-  OtherEntryType _ _ size -> bytesToBlocks size
-  _                       -> 0
-  where
-    bytesToBlocks s = 1 + ((fromIntegral s - 1) `div` 512)
-
--- | The content of a tar archive entry, which depends on the type of entry.
---
--- Portable archives should contain only 'NormalFile' and 'Directory'.
---
-data EntryContent = NormalFile      ByteString !FileSize
-                  | Directory
-                  | SymbolicLink    !LinkTarget
-                  | HardLink        !LinkTarget
-                  | CharacterDevice !DevMajor !DevMinor
-                  | BlockDevice     !DevMajor !DevMinor
-                  | NamedPipe
-                  | OtherEntryType  !TypeCode ByteString !FileSize
-
-data Ownership = Ownership {
-    -- | The owner user name. Should be set to @\"\"@ if unknown.
-    ownerName :: String,
-
-    -- | The owner group name. Should be set to @\"\"@ if unknown.
-    groupName :: String,
-
-    -- | Numeric owner user id. Should be set to @0@ if unknown.
-    ownerId :: !Int,
-
-    -- | Numeric owner group id. Should be set to @0@ if unknown.
-    groupId :: !Int
-  }
-
--- | There have been a number of extensions to the tar file format over the
--- years. They all share the basic entry fields and put more meta-data in
--- different extended headers.
---
-data Format =
-
-     -- | This is the classic Unix V7 tar format. It does not support owner and
-     -- group names, just numeric Ids. It also does not support device numbers.
-     V7Format
-
-     -- | The \"USTAR\" format is an extension of the classic V7 format. It was
-     -- later standardised by POSIX. It has some restrictions but is the most
-     -- portable format.
-     --
-   | UstarFormat
-
-     -- | The GNU tar implementation also extends the classic V7 format, though
-     -- in a slightly different way from the USTAR format. In general for new
-     -- archives the standard USTAR/POSIX should be used.
-     --
-   | GnuFormat
-  deriving Eq
-
--- | @rw-r--r--@ for normal files
-ordinaryFilePermissions :: Permissions
-ordinaryFilePermissions   = 0o0644
-
--- | @rwxr-xr-x@ for executable files
-executableFilePermissions :: Permissions
-executableFilePermissions = 0o0755
-
--- | @rwxr-xr-x@ for directories
-directoryPermissions :: Permissions
-directoryPermissions  = 0o0755
-
-isExecutable :: Permissions -> Bool
-isExecutable p = testBit p 0 || testBit p 6 -- user or other executable
-
--- | An 'Entry' with all default values except for the file name and type. It
--- uses the portable USTAR/POSIX format (see 'UstarHeader').
---
--- You can use this as a basis and override specific fields, eg:
---
--- > (emptyEntry name HardLink) { linkTarget = target }
---
-simpleEntry :: TarPath -> EntryContent -> Entry
-simpleEntry tarpath content = Entry {
-    entryTarPath     = tarpath,
-    entryContent     = content,
-    entryPermissions = case content of
-                         Directory -> directoryPermissions
-                         _         -> ordinaryFilePermissions,
-    entryOwnership   = Ownership "" "" 0 0,
-    entryTime        = 0,
-    entryFormat      = UstarFormat
-  }
-
--- | A tar 'Entry' for a file.
---
--- Entry  fields such as file permissions and ownership have default values.
---
--- You can use this as a basis and override specific fields. For example if you
--- need an executable file you could use:
---
--- > (fileEntry name content) { fileMode = executableFileMode }
---
-fileEntry :: TarPath -> ByteString -> Entry
-fileEntry name fileContent =
-  simpleEntry name (NormalFile fileContent (BS.length fileContent))
-
--- | A tar 'Entry' for a directory.
---
--- Entry fields such as file permissions and ownership have default values.
---
-directoryEntry :: TarPath -> Entry
-directoryEntry name = simpleEntry name Directory
-
---
--- * Tar paths
---
-
--- | The classic tar format allowed just 100 characters for the file name. The
--- USTAR format extended this with an extra 155 characters, however it uses a
--- complex method of splitting the name between the two sections.
---
--- Instead of just putting any overflow into the extended area, it uses the
--- extended area as a prefix. The aggravating insane bit however is that the
--- prefix (if any) must only contain a directory prefix. That is the split
--- between the two areas must be on a directory separator boundary. So there is
--- no simple calculation to work out if a file name is too long. Instead we
--- have to try to find a valid split that makes the name fit in the two areas.
---
--- The rationale presumably was to make it a bit more compatible with old tar
--- programs that only understand the classic format. A classic tar would be
--- able to extract the file name and possibly some dir prefix, but not the
--- full dir prefix. So the files would end up in the wrong place, but that's
--- probably better than ending up with the wrong names too.
---
--- So it's understandable but rather annoying.
---
--- * Tar paths use POSIX format (ie @\'/\'@ directory separators), irrespective
---   of the local path conventions.
---
--- * The directory separator between the prefix and name is /not/ stored.
---
-data TarPath = TarPath FilePath -- path name, 100 characters max.
-                       FilePath -- path prefix, 155 characters max.
-  deriving (Eq, Ord)
-
--- | Convert a 'TarPath' to a native 'FilePath'.
---
--- The native 'FilePath' will use the native directory separator but it is not
--- otherwise checked for validity or sanity. In particular:
---
--- * The tar path may be invalid as a native path, eg the filename @\"nul\"@ is
---   not valid on Windows.
---
--- * The tar path may be an absolute path or may contain @\"..\"@ components.
---   For security reasons this should not usually be allowed, but it is your
---   responsibility to check for these conditions (eg using 'checkSecurity').
---
-fromTarPath :: TarPath -> FilePath
-fromTarPath (TarPath name prefix) = adjustDirectory $
-  FilePath.Native.joinPath $ FilePath.Posix.splitDirectories prefix
-                          ++ FilePath.Posix.splitDirectories name
-  where
-    adjustDirectory | FilePath.Posix.hasTrailingPathSeparator name
-                    = FilePath.Native.addTrailingPathSeparator
-                    | otherwise = id
-
--- | Convert a native 'FilePath' to a 'TarPath'.
---
--- The conversion may fail if the 'FilePath' is too long. See 'TarPath' for a
--- description of the problem with splitting long 'FilePath's.
---
-toTarPath :: Bool -- ^ Is the path for a directory? This is needed because for
-                  -- directories a 'TarPath' must always use a trailing @\/@.
-          -> FilePath -> Either String TarPath
-toTarPath isDir = splitLongPath
-                . addTrailingSep
-                . FilePath.Posix.joinPath
-                . FilePath.Native.splitDirectories
-  where
-    addTrailingSep | isDir     = FilePath.Posix.addTrailingPathSeparator
-                   | otherwise = id
-
--- | Take a sanitized path, split on directory separators and try to pack it
--- into the 155 + 100 tar file name format.
---
--- The strategy is this: take the name-directory components in reverse order
--- and try to fit as many components into the 100 long name area as possible.
--- If all the remaining components fit in the 155 name area then we win.
---
-splitLongPath :: FilePath -> Either String TarPath
-splitLongPath path =
-  case packName nameMax (reverse (FilePath.Posix.splitPath path)) of
-    Left err                 -> Left err
-    Right (name, [])         -> Right (TarPath name "")
-    Right (name, first:rest) -> case packName prefixMax remainder of
-      Left err               -> Left err
-      Right (_     , _ : _)  -> Left "File name too long (cannot split)"
-      Right (prefix, [])     -> Right (TarPath name prefix)
-      where
-        -- drop the '/' between the name and prefix:
-        remainder = init first : rest
-
-  where
-    nameMax, prefixMax :: Int
-    nameMax   = 100
-    prefixMax = 155
-
-    packName _      []     = Left "File name empty"
-    packName maxLen (c:cs)
-      | n > maxLen         = Left "File name too long"
-      | otherwise          = Right (packName' maxLen n [c] cs)
-      where n = length c
-
-    packName' maxLen n ok (c:cs)
-      | n' <= maxLen             = packName' maxLen n' (c:ok) cs
-                                     where n' = n + length c
-    packName' _      _ ok    cs  = (FilePath.Posix.joinPath ok, cs)
-
--- | The tar format allows just 100 ASCII characters for the 'SymbolicLink' and
--- 'HardLink' entry types.
---
-newtype LinkTarget = LinkTarget FilePath
-  deriving (Eq, Ord)
-
--- | Convert a tar 'LinkTarget' to a native 'FilePath'.
---
-fromLinkTarget :: LinkTarget -> FilePath
-fromLinkTarget (LinkTarget path) = adjustDirectory $
-  FilePath.Native.joinPath $ FilePath.Posix.splitDirectories path
-  where
-    adjustDirectory | FilePath.Posix.hasTrailingPathSeparator path
-                    = FilePath.Native.addTrailingPathSeparator
-                    | otherwise = id
-
---
--- * Entries type
---
-
--- | A tar archive is a sequence of entries.
-data Entries = Next Entry Entries
-             | Done
-             | Fail String
-
-unfoldrEntries :: (a -> Either String (Maybe (Entry, a))) -> a -> Entries
-unfoldrEntries f = unfold
-  where
-    unfold x = case f x of
-      Left err             -> Fail err
-      Right Nothing        -> Done
-      Right (Just (e, x')) -> Next e (unfold x')
-
-foldrEntries :: (Entry -> a -> a) -> a -> (String -> a) -> Entries -> a
-foldrEntries next done fail' = fold
-  where
-    fold (Next e es) = next e (fold es)
-    fold Done        = done
-    fold (Fail err)  = fail' err
-
-foldlEntries :: (a -> Entry -> a) -> a -> Entries -> Either String a
-foldlEntries f = fold
-  where
-    fold a (Next e es) = (fold $! f a e) es
-    fold a Done        = Right a
-    fold _ (Fail err)  = Left err
-
-mapEntries :: (Entry -> Entry) -> Entries -> Entries
-mapEntries f = foldrEntries (Next . f) Done Fail
-
-filterEntries :: (Entry -> Bool) -> Entries -> Entries
+filterEntries :: (Tar.Entry -> Bool) -> Tar.Entries e -> Tar.Entries e
 filterEntries p =
-  foldrEntries
-    (\entry rest -> if p entry
-                      then Next entry rest
-                      else rest)
-    Done Fail
-
-checkEntries :: (Entry -> Maybe String) -> Entries -> Entries
-checkEntries checkEntry =
-  foldrEntries
-    (\entry rest -> case checkEntry entry of
-                      Nothing  -> Next entry rest
-                      Just err -> Fail err)
-    Done Fail
-
-entriesIndex :: Entries -> Either String (Map.Map TarPath Entry)
-entriesIndex = foldlEntries (\m e -> Map.insert (entryTarPath e) e m) Map.empty
-
---
--- * Checking
---
-
--- | This function checks a sequence of tar entries for file name security
--- problems. It checks that:
---
--- * file paths are not absolute
---
--- * file paths do not contain any path components that are \"@..@\"
---
--- * file names are valid
---
--- These checks are from the perspective of the current OS. That means we check
--- for \"@C:\blah@\" files on Windows and \"\/blah\" files on Unix. For archive
--- entry types 'HardLink' and 'SymbolicLink' the same checks are done for the
--- link target. A failure in any entry terminates the sequence of entries with
--- an error.
---
-checkSecurity :: Entries -> Entries
-checkSecurity = checkEntries checkEntrySecurity
-
-checkTarbomb :: FilePath -> Entries -> Entries
-checkTarbomb expectedTopDir = checkEntries (checkEntryTarbomb expectedTopDir)
-
-checkEntrySecurity :: Entry -> Maybe String
-checkEntrySecurity entry = case entryContent entry of
-    HardLink     link -> check (entryPath entry)
-                 `mplus` check (fromLinkTarget link)
-    SymbolicLink link -> check (entryPath entry)
-                 `mplus` check (fromLinkTarget link)
-    _                 -> check (entryPath entry)
-
-  where
-    check name
-      | not (FilePath.Native.isRelative name)
-      = Just $ "Absolute file name in tar archive: " ++ show name
-
-      | not (FilePath.Native.isValid name)
-      = Just $ "Invalid file name in tar archive: " ++ show name
-
-      | ".." `elem` FilePath.Native.splitDirectories name
-      = Just $ "Invalid file name in tar archive: " ++ show name
-
-      | otherwise = Nothing
-
-checkEntryTarbomb :: FilePath -> Entry -> Maybe String
-checkEntryTarbomb _ entry | nonFilesystemEntry = Nothing
-  where
-    -- Ignore some special entries we will not unpack anyway
-    nonFilesystemEntry =
-      case entryContent entry of
-        OtherEntryType 'g' _ _ -> True --PAX global header
-        OtherEntryType 'x' _ _ -> True --PAX individual header
-        _                      -> False
-
-checkEntryTarbomb expectedTopDir entry =
-  case FilePath.Native.splitDirectories (entryPath entry) of
-    (topDir:_) | topDir == expectedTopDir -> Nothing
-    s -> Just $ "File in tar archive is not in the expected directory. "
-             ++ "Expected: " ++ show expectedTopDir
-             ++ " but got the following hierarchy: "
-             ++ show s
-
-
---
--- * Reading
---
-
-read :: ByteString -> Entries
-read = unfoldrEntries getEntry
-
-getEntry :: ByteString -> Either String (Maybe (Entry, ByteString))
-getEntry bs
-  | BS.length header < 512 = Left "truncated tar archive"
-
-  -- Tar files end with at least two blocks of all '0'. Checking this serves
-  -- two purposes. It checks the format but also forces the tail of the data
-  -- which is necessary to close the file if it came from a lazily read file.
-  | BS.head bs == 0 = case BS.splitAt 1024 bs of
-      (end, trailing)
-        | BS.length end /= 1024        -> Left "short tar trailer"
-        | not (BS.all (== 0) end)      -> Left "bad tar trailer"
-        | not (BS.all (== 0) trailing) -> Left "tar file has trailing junk"
-        | otherwise                    -> Right Nothing
-
-  | otherwise  = partial $ do
-
-  case (chksum_, format_) of
-    (Ok chksum, _   ) | correctChecksum header chksum -> return ()
-    (Ok _,      Ok _) -> fail "tar checksum error"
-    _                 -> fail "data is not in tar format"
-
-  -- These fields are partial, have to check them
-  format   <- format_;   mode     <- mode_;
-  uid      <- uid_;      gid      <- gid_;
-  size     <- size_;     mtime    <- mtime_;
-  devmajor <- devmajor_; devminor <- devminor_;
-
-  let content = BS.take size (BS.drop 512 bs)
-      padding = (512 - size) `mod` 512
-      bs'     = BS.drop (512 + size + padding) bs
-
-      entry = Entry {
-        entryTarPath     = TarPath name prefix,
-        entryContent     = case typecode of
-                   '\0' -> NormalFile      content size
-                   '0'  -> NormalFile      content size
-                   '1'  -> HardLink        (LinkTarget linkname)
-                   '2'  -> SymbolicLink    (LinkTarget linkname)
-                   '3'  -> CharacterDevice devmajor devminor
-                   '4'  -> BlockDevice     devmajor devminor
-                   '5'  -> Directory
-                   '6'  -> NamedPipe
-                   '7'  -> NormalFile      content size
-                   _    -> OtherEntryType  typecode content size,
-        entryPermissions = mode,
-        entryOwnership   = Ownership uname gname uid gid,
-        entryTime        = mtime,
-        entryFormat      = format
-    }
-
-  return (Just (entry, bs'))
-
-  where
-   header = BS.take 512 bs
-
-   name       = getString   0 100 header
-   mode_      = getOct    100   8 header
-   uid_       = getOct    108   8 header
-   gid_       = getOct    116   8 header
-   size_      = getOct    124  12 header
-   mtime_     = getOct    136  12 header
-   chksum_    = getOct    148   8 header
-   typecode   = getByte   156     header
-   linkname   = getString 157 100 header
-   magic      = getChars  257   8 header
-   uname      = getString 265  32 header
-   gname      = getString 297  32 header
-   devmajor_  = getOct    329   8 header
-   devminor_  = getOct    337   8 header
-   prefix     = getString 345 155 header
--- trailing   = getBytes  500  12 header
-
-   format_ = case magic of
-    "\0\0\0\0\0\0\0\0" -> return V7Format
-    "ustar\NUL00"      -> return UstarFormat
-    "ustar  \NUL"      -> return GnuFormat
-    _                  -> fail "tar entry not in a recognised format"
-
-correctChecksum :: ByteString -> Int -> Bool
-correctChecksum header checksum = checksum == checksum'
-  where
-    -- sum of all 512 bytes in the header block,
-    -- treating each byte as an 8-bit unsigned value
-    checksum' = BS.Char8.foldl' (\x y -> x + ord y) 0 header'
-    -- treating the 8 bytes of chksum as blank characters.
-    header'   = BS.concat [BS.take 148 header,
-                           BS.Char8.replicate 8 ' ',
-                           BS.drop 156 header]
-
--- * TAR format primitive input
-
-getOct :: (Integral a, Bits a) => Int64 -> Int64 -> ByteString -> Partial a
-getOct off len header
-  | BS.head bytes == 128 = parseBinInt (BS.unpack (BS.tail bytes))
-  | null octstr          = return 0
-  | otherwise            = case readOct octstr of
-               [(x,[])] -> return x
-               _        -> fail "tar header is malformed (bad numeric encoding)"
-  where
-    bytes  = getBytes off len header
-    octstr = BS.Char8.unpack
-           . BS.Char8.takeWhile (\c -> c /= '\NUL' && c /= ' ')
-           . BS.Char8.dropWhile (== ' ')
-           $ bytes
-
-    -- Some tar programs switch into a binary format when they try to represent
-    -- field values that will not fit in the required width when using the text
-    -- octal format. In particular, the UID/GID fields can only hold up to 2^21
-    -- while in the binary format can hold up to 2^32. The binary format uses
-    -- '\128' as the header which leaves 7 bytes. Only the last 4 are used.
-    parseBinInt [0, 0, 0, byte3, byte2, byte1, byte0] =
-      return $! shiftL (fromIntegral byte3) 24
-              + shiftL (fromIntegral byte2) 16
-              + shiftL (fromIntegral byte1) 8
-              + shiftL (fromIntegral byte0) 0
-    parseBinInt _ = fail "tar header uses non-standard number encoding"
-
-getBytes :: Int64 -> Int64 -> ByteString -> ByteString
-getBytes off len = BS.take len . BS.drop off
-
-getByte :: Int64 -> ByteString -> Char
-getByte off bs = BS.Char8.index bs off
-
-getChars :: Int64 -> Int64 -> ByteString -> String
-getChars off len = BS.Char8.unpack . getBytes off len
-
-getString :: Int64 -> Int64 -> ByteString -> String
-getString off len = BS.Char8.unpack . BS.Char8.takeWhile (/='\0')
-                    . getBytes off len
-
-data Partial a = Error String | Ok a
-  deriving Functor
-
-partial :: Partial a -> Either String a
-partial (Error msg) = Left msg
-partial (Ok x)      = Right x
-
-instance Applicative Partial where
-    pure          = return
-    (<*>)         = ap
-
-instance Monad Partial where
-    return        = Ok
-    Error m >>= _ = Error m
-    Ok    x >>= k = k x
-    fail          = Error
-
---
--- * Writing
---
-
--- | Create the external representation of a tar archive by serialising a list
--- of tar entries.
---
--- * The conversion is done lazily.
---
-write :: [Entry] -> ByteString
-write es = BS.concat $ map putEntry es ++ [BS.replicate (512*2) 0]
-
--- | Same as 'write', but for 'Entries'.
-writeEntries :: Entries -> ByteString
-writeEntries entries = BS.concat $ foldrEntries (\e res -> putEntry e : res)
-                       [BS.replicate (512*2) 0] error entries
-
-putEntry :: Entry -> ByteString
-putEntry entry = case entryContent entry of
-  NormalFile       content size -> BS.concat [ header, content, padding size ]
-  OtherEntryType _ content size -> BS.concat [ header, content, padding size ]
-  _                             -> header
-  where
-    header       = putHeader entry
-    padding size = BS.replicate paddingSize 0
-      where paddingSize = fromIntegral (negate size `mod` 512)
-
-putHeader :: Entry -> ByteString
-putHeader entry =
-     BS.concat [ BS.take 148 block
-               , BS.Char8.pack $ putOct 7 checksum
-               , BS.Char8.singleton ' '
-               , BS.drop 156 block ]
-  where
-    -- putHeaderNoChkSum returns a String, so we convert it to the final
-    -- representation before calculating the checksum.
-    block    = BS.Char8.pack . putHeaderNoChkSum $ entry
-    checksum = BS.Char8.foldl' (\x y -> x + ord y) 0 block
-
-putHeaderNoChkSum :: Entry -> String
-putHeaderNoChkSum Entry {
-    entryTarPath     = TarPath name prefix,
-    entryContent     = content,
-    entryPermissions = permissions,
-    entryOwnership   = ownership,
-    entryTime        = modTime,
-    entryFormat      = format
-  } =
-
-  concat
-    [ putString  100 $ name
-    , putOct       8 $ permissions
-    , putOct       8 $ ownerId ownership
-    , putOct       8 $ groupId ownership
-    , putOct      12 $ contentSize
-    , putOct      12 $ modTime
-    , fill         8 $ ' ' -- dummy checksum
-    , putChar8       $ typeCode
-    , putString  100 $ linkTarget
-    ] ++
-  case format of
-  V7Format    ->
-      fill 255 '\NUL'
-  UstarFormat -> concat
-    [ putString    8 $ "ustar\NUL00"
-    , putString   32 $ ownerName ownership
-    , putString   32 $ groupName ownership
-    , putOct       8 $ deviceMajor
-    , putOct       8 $ deviceMinor
-    , putString  155 $ prefix
-    , fill        12 $ '\NUL'
-    ]
-  GnuFormat -> concat
-    [ putString    8 $ "ustar  \NUL"
-    , putString   32 $ ownerName ownership
-    , putString   32 $ groupName ownership
-    , putGnuDev    8 $ deviceMajor
-    , putGnuDev    8 $ deviceMinor
-    , putString  155 $ prefix
-    , fill        12 $ '\NUL'
-    ]
-  where
-    (typeCode, contentSize, linkTarget,
-     deviceMajor, deviceMinor) = case content of
-       NormalFile      _ size            -> ('0' , size, [],   0,     0)
-       Directory                         -> ('5' , 0,    [],   0,     0)
-       SymbolicLink    (LinkTarget link) -> ('2' , 0,    link, 0,     0)
-       HardLink        (LinkTarget link) -> ('1' , 0,    link, 0,     0)
-       CharacterDevice major minor       -> ('3' , 0,    [],   major, minor)
-       BlockDevice     major minor       -> ('4' , 0,    [],   major, minor)
-       NamedPipe                         -> ('6' , 0,    [],   0,     0)
-       OtherEntryType  code _ size       -> (code, size, [],   0,     0)
-
-    putGnuDev w n = case content of
-      CharacterDevice _ _ -> putOct w n
-      BlockDevice     _ _ -> putOct w n
-      _                   -> replicate w '\NUL'
-
--- * TAR format primitive output
-
-type FieldWidth = Int
-
-putString :: FieldWidth -> String -> String
-putString n s = take n s ++ fill (n - length s) '\NUL'
-
---TODO: check integer widths, eg for large file sizes
-putOct :: (Show a, Integral a) => FieldWidth -> a -> String
-putOct n x =
-  let octStr = take (n-1) $ showOct x ""
-   in fill (n - length octStr - 1) '0'
-   ++ octStr
-   ++ putChar8 '\NUL'
-
-putChar8 :: Char -> String
-putChar8 c = [c]
-
-fill :: FieldWidth -> Char -> String
-fill n c = replicate n c
-
---
--- * Unpacking
---
-
-unpack :: FilePath -> Entries -> IO ()
-unpack baseDir entries = unpackEntries [] (checkSecurity entries)
-                     >>= emulateLinks
-
-  where
-    -- We're relying here on 'checkSecurity' to make sure we're not scribbling
-    -- files all over the place.
-
-    unpackEntries _     (Fail err)      = fail err
-    unpackEntries links Done            = return links
-    unpackEntries links (Next entry es) = case entryContent entry of
-      NormalFile file _ -> extractFile entry path file
-                        >> unpackEntries links es
-      Directory         -> extractDir path
-                        >> unpackEntries links es
-      HardLink     link -> (unpackEntries $! saveLink path link links) es
-      SymbolicLink link -> (unpackEntries $! saveLink path link links) es
-      _                 -> unpackEntries links es --ignore other file types
-      where
-        path = entryPath entry
-
-    extractFile entry path content = do
-      -- Note that tar archives do not make sure each directory is created
-      -- before files they contain, indeed we may have to create several
-      -- levels of directory.
-      createDirectoryIfMissing True absDir
-      BS.writeFile absPath content
-      when (isExecutable (entryPermissions entry))
-           (setFileExecutable absPath)
-      where
-        absDir  = baseDir </> FilePath.Native.takeDirectory path
-        absPath = baseDir </> path
-
-    extractDir path = createDirectoryIfMissing True (baseDir </> path)
-
-    saveLink path link links = seq (length path)
-                             $ seq (length link')
-                             $ (path, link'):links
-      where link' = fromLinkTarget link
-
-    emulateLinks = mapM_ $ \(relPath, relLinkTarget) ->
-      let absPath   = baseDir </> relPath
-          absTarget = FilePath.Native.takeDirectory absPath </> relLinkTarget
-       in copyFile absTarget absPath
-
---
--- * Packing
---
-
-pack :: FilePath   -- ^ Base directory
-     -> [FilePath] -- ^ Files and directories to pack, relative to the base dir
-     -> IO [Entry]
-pack baseDir paths0 = preparePaths baseDir paths0 >>= packPaths baseDir
-
-preparePaths :: FilePath -> [FilePath] -> IO [FilePath]
-preparePaths baseDir paths =
-  fmap concat $ interleave
-    [ do isDir  <- doesDirectoryExist (baseDir </> path)
-         if isDir
-           then do entries <- getDirectoryContentsRecursive (baseDir </> path)
-                   return (FilePath.Native.addTrailingPathSeparator path
-                         : map (path </>) entries)
-           else return [path]
-    | path <- paths ]
-
-packPaths :: FilePath -> [FilePath] -> IO [Entry]
-packPaths baseDir paths =
-  interleave
-    [ do tarpath <- either fail return (toTarPath isDir relpath)
-         if isDir then packDirectoryEntry filepath tarpath
-                  else packFileEntry      filepath tarpath
-    | relpath <- paths
-    , let isDir    = FilePath.Native.hasTrailingPathSeparator filepath
-          filepath = baseDir </> relpath ]
-
-interleave :: [IO a] -> IO [a]
-interleave = unsafeInterleaveIO . go
-  where
-    go []     = return []
-    go (x:xs) = do
-      x'  <- x
-      xs' <- interleave xs
-      return (x':xs')
-
-packFileEntry :: FilePath -- ^ Full path to find the file on the local disk
-              -> TarPath  -- ^ Path to use for the tar Entry in the archive
-              -> IO Entry
-packFileEntry filepath tarpath = do
-  mtime   <- getModTime filepath
-  perms   <- getPermissions filepath
-  file    <- openBinaryFile filepath ReadMode
-  size    <- hFileSize file
-  content <- BS.hGetContents file
-  return (simpleEntry tarpath (NormalFile content (fromIntegral size))) {
-    entryPermissions = if Permissions.executable perms
-                         then executableFilePermissions
-                         else ordinaryFilePermissions,
-    entryTime = mtime
-  }
-
-packDirectoryEntry :: FilePath -- ^ Full path to find the file on the local disk
-                   -> TarPath  -- ^ Path to use for the tar Entry in the archive
-                   -> IO Entry
-packDirectoryEntry filepath tarpath = do
-  mtime   <- getModTime filepath
-  return (directoryEntry tarpath) {
-    entryTime = mtime
-  }
-
-getDirectoryContentsRecursive :: FilePath -> IO [FilePath]
-getDirectoryContentsRecursive dir0 =
-  fmap tail (recurseDirectories dir0 [""])
-
-recurseDirectories :: FilePath -> [FilePath] -> IO [FilePath]
-recurseDirectories _    []         = return []
-recurseDirectories base (dir:dirs) = unsafeInterleaveIO $ do
-  (files, dirs') <- collect [] [] =<< getDirectoryContents (base </> dir)
+  Tar.foldEntries
+    (\e es -> if p e then Tar.Next e es else es)
+    Tar.Done
+    Tar.Fail
 
-  files' <- recurseDirectories base (dirs' ++ dirs)
-  return (dir : files ++ files')
+filterEntriesM :: Monad m => (Tar.Entry -> m Bool)
+               -> Tar.Entries e -> m (Tar.Entries e)
+filterEntriesM p =
+  Tar.foldEntries
+    (\entry rest -> do
+         keep <- p entry
+         xs   <- rest
+         if keep
+           then return (Tar.Next entry xs)
+           else return xs)
+    (return Tar.Done)
+    (return . Tar.Fail)
 
-  where
-    collect files dirs' []              = return (reverse files, reverse dirs')
-    collect files dirs' (entry:entries) | ignore entry
-                                        = collect files dirs' entries
-    collect files dirs' (entry:entries) = do
-      let dirEntry  = dir </> entry
-          dirEntry' = FilePath.Native.addTrailingPathSeparator dirEntry
-      isDirectory <- doesDirectoryExist (base </> dirEntry)
-      if isDirectory
-        then collect files (dirEntry':dirs') entries
-        else collect (dirEntry:files) dirs' entries
+entriesToList :: Exception e => Tar.Entries e -> [Tar.Entry]
+entriesToList = Tar.foldEntries (:) [] throw
 
-    ignore ['.']      = True
-    ignore ['.', '.'] = True
-    ignore _          = False
diff --git a/cabal/cabal-install/Distribution/Client/Targets.hs b/cabal/cabal-install/Distribution/Client/Targets.hs
--- a/cabal/cabal-install/Distribution/Client/Targets.hs
+++ b/cabal/cabal-install/Distribution/Client/Targets.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveGeneric #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Targets
@@ -42,7 +44,9 @@
   UserConstraint(..),
   userConstraintPackageName,
   readUserConstraint,
-  userToPackageConstraint
+  userToPackageConstraint,
+  dispFlagAssignment,
+  parseFlagAssignment,
 
   ) where
 
@@ -51,7 +55,8 @@
          , PackageIdentifier(..), packageName, packageVersion
          , Dependency(Dependency) )
 import Distribution.Client.Types
-         ( SourcePackage(..), PackageLocation(..), OptionalStanza(..) )
+         ( SourcePackage(..), PackageLocation(..), OptionalStanza(..)
+         , ResolvedPkgLoc, UnresolvedSourcePackage )
 import Distribution.Client.Dependency.Types
          ( PackageConstraint(..), ConstraintSource(..)
          , LabeledPackageConstraint(..) )
@@ -59,10 +64,13 @@
 import qualified Distribution.Client.World as World
 import Distribution.Client.PackageIndex (PackageIndex)
 import qualified Distribution.Client.PackageIndex as PackageIndex
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
 import qualified Distribution.Client.Tar as Tar
 import Distribution.Client.FetchUtils
-import Distribution.Client.HttpUtils ( HttpTransport(..) )
 import Distribution.Client.Utils ( tryFindPackageDesc )
+import Distribution.Client.GlobalFlags
+         ( RepoContext(..) )
 
 import Distribution.PackageDescription
          ( GenericPackageDescription, FlagName(..), FlagAssignment )
@@ -95,6 +103,8 @@
 import qualified Distribution.Compat.ReadP as Parse
 import Distribution.Compat.ReadP
          ( (+++), (<++) )
+import qualified Distribution.Compat.Semigroup as Semi
+         ( Semigroup((<>)) )
 import qualified Text.PrettyPrint as Disp
 import Text.PrettyPrint
          ( (<>), (<+>) )
@@ -106,6 +116,8 @@
          ( doesFileExist, doesDirectoryExist )
 import Network.URI
          ( URI(..), URIAuth(..), parseAbsoluteURI )
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary)
 
 -- ------------------------------------------------------------
 -- * User targets
@@ -182,8 +194,10 @@
      -- | A fully specified source package.
      --
    | SpecificSourcePackage pkg
-  deriving Show
+  deriving (Eq, Show, Generic)
 
+instance Binary pkg => Binary (PackageSpecifier pkg)
+
 pkgSpecifierTarget :: Package pkg => PackageSpecifier pkg -> PackageName
 pkgSpecifierTarget (NamedPackage name _)       = name
 pkgSpecifierTarget (SpecificSourcePackage pkg) = packageName pkg
@@ -357,17 +371,17 @@
 --
 resolveUserTargets :: Package pkg
                    => Verbosity
-                   -> HttpTransport
+                   -> RepoContext
                    -> FilePath
                    -> PackageIndex pkg
                    -> [UserTarget]
-                   -> IO [PackageSpecifier SourcePackage]
-resolveUserTargets verbosity transport worldFile available userTargets = do
+                   -> IO [PackageSpecifier UnresolvedSourcePackage]
+resolveUserTargets verbosity repoCtxt worldFile available userTargets = do
 
     -- given the user targets, get a list of fully or partially resolved
     -- package references
     packageTargets <- mapM (readPackageTarget verbosity)
-                  =<< mapM (fetchPackageTarget transport verbosity) . concat
+                  =<< mapM (fetchPackageTarget verbosity repoCtxt) . concat
                   =<< mapM (expandUserTarget worldFile) userTargets
 
     -- users are allowed to give package names case-insensitively, so we must
@@ -453,15 +467,15 @@
 
 -- | Fetch any remote targets so that they can be read.
 --
-fetchPackageTarget :: HttpTransport
-                   -> Verbosity
+fetchPackageTarget :: Verbosity
+                   -> RepoContext
                    -> PackageTarget (PackageLocation ())
-                   -> IO (PackageTarget (PackageLocation FilePath))
-fetchPackageTarget transport verbosity target = case target of
+                   -> IO (PackageTarget ResolvedPkgLoc)
+fetchPackageTarget verbosity repoCtxt target = case target of
     PackageTargetNamed      n cs ut -> return (PackageTargetNamed      n cs ut)
     PackageTargetNamedFuzzy n cs ut -> return (PackageTargetNamedFuzzy n cs ut)
     PackageTargetLocation location  -> do
-      location' <- fetchPackage transport verbosity (fmap (const Nothing) location)
+      location' <- fetchPackage verbosity repoCtxt (fmap (const Nothing) location)
       return (PackageTargetLocation location')
 
 
@@ -470,8 +484,8 @@
 -- This only affects targets given by location, named targets are unaffected.
 --
 readPackageTarget :: Verbosity
-                  -> PackageTarget (PackageLocation FilePath)
-                  -> IO (PackageTarget SourcePackage)
+                  -> PackageTarget ResolvedPkgLoc
+                  -> IO (PackageTarget UnresolvedSourcePackage)
 readPackageTarget verbosity target = case target of
 
     PackageTargetNamed pkgname constraints userTarget ->
@@ -524,7 +538,7 @@
     extractTarballPackageCabalFile tarballFile tarballOriginalLoc =
           either (die . formatErr) return
         . check
-        . Tar.entriesIndex
+        . accumEntryMap
         . Tar.filterEntries isCabalFile
         . Tar.read
         . GZipUtils.maybeDecompress
@@ -532,7 +546,11 @@
       where
         formatErr msg = "Error reading " ++ tarballOriginalLoc ++ ": " ++ msg
 
-        check (Left e)  = Left e
+        accumEntryMap = Tar.foldlEntries
+                          (\m e -> Map.insert (Tar.entryTarPath e) e m)
+                          Map.empty
+
+        check (Left e)  = Left (show e)
         check (Right m) = case Map.elems m of
             []     -> Left noCabalFile
             [file] -> case Tar.entryContent file of
@@ -662,7 +680,10 @@
 
 instance Monoid PackageNameEnv where
   mempty = PackageNameEnv (const [])
-  mappend (PackageNameEnv lookupA) (PackageNameEnv lookupB) =
+  mappend = (Semi.<>)
+
+instance Semi.Semigroup PackageNameEnv where
+  PackageNameEnv lookupA <> PackageNameEnv lookupB =
     PackageNameEnv (\name -> lookupA name ++ lookupB name)
 
 indexPackageNameEnv :: PackageIndex pkg -> PackageNameEnv
@@ -691,8 +712,10 @@
    | UserConstraintSource    PackageName
    | UserConstraintFlags     PackageName FlagAssignment
    | UserConstraintStanzas   PackageName [OptionalStanza]
-  deriving (Show,Eq)
+  deriving (Eq, Show, Generic)
 
+instance Binary UserConstraint
+
 userConstraintPackageName :: UserConstraint -> PackageName
 userConstraintPackageName uc = case uc of
   UserConstraintVersion   name _ -> name
@@ -728,7 +751,6 @@
          "expected a package name followed by a constraint, which is "
       ++ "either a version range, 'installed', 'source' or flags"
 
---FIXME: use Text instance for FlagName and FlagAssignment
 instance Text UserConstraint where
   disp (UserConstraintVersion   pkgname verrange) = disp pkgname
                                                     <+> disp verrange
@@ -738,12 +760,6 @@
                                                     <+> Disp.text "source"
   disp (UserConstraintFlags     pkgname flags)    = disp pkgname
                                                     <+> dispFlagAssignment flags
-    where
-      dispFlagAssignment = Disp.hsep . map dispFlagValue
-      dispFlagValue (f, True)   = Disp.char '+' <> dispFlagName f
-      dispFlagValue (f, False)  = Disp.char '-' <> dispFlagName f
-      dispFlagName (FlagName f) = Disp.text f
-
   disp (UserConstraintStanzas   pkgname stanzas)  = disp pkgname
                                                     <+> dispStanzas stanzas
     where
@@ -753,37 +769,51 @@
 
   parse = parse >>= parseConstraint
     where
-      spaces = Parse.satisfy isSpace >> Parse.skipSpaces
-
       parseConstraint pkgname =
             ((parse >>= return . UserConstraintVersion pkgname)
-        +++ (do spaces
+        +++ (do skipSpaces1
                 _ <- Parse.string "installed"
                 return (UserConstraintInstalled pkgname))
-        +++ (do spaces
+        +++ (do skipSpaces1
                 _ <- Parse.string "source"
                 return (UserConstraintSource pkgname))
-        +++ (do spaces
+        +++ (do skipSpaces1
                 _ <- Parse.string "test"
                 return (UserConstraintStanzas pkgname [TestStanzas]))
-        +++ (do spaces
+        +++ (do skipSpaces1
                 _ <- Parse.string "bench"
                 return (UserConstraintStanzas pkgname [BenchStanzas])))
-        <++ (parseFlagAssignment >>= (return . UserConstraintFlags pkgname))
+        <++ (do skipSpaces1
+                flags <- parseFlagAssignment
+                return (UserConstraintFlags pkgname flags))
 
-      parseFlagAssignment = Parse.many1 (spaces >> parseFlagValue)
-      parseFlagValue =
-            (do Parse.optional (Parse.char '+')
-                f <- parseFlagName
-                return (f, True))
-        +++ (do _ <- Parse.char '-'
-                f <- parseFlagName
-                return (f, False))
-      parseFlagName = liftM FlagName ident
+--TODO: [code cleanup] move these somewhere else
+dispFlagAssignment :: FlagAssignment -> Disp.Doc
+dispFlagAssignment = Disp.hsep . map dispFlagValue
+  where
+    dispFlagValue (f, True)   = Disp.char '+' <> dispFlagName f
+    dispFlagValue (f, False)  = Disp.char '-' <> dispFlagName f
+    dispFlagName (FlagName f) = Disp.text f
 
-      ident :: Parse.ReadP r String
-      ident = Parse.munch1 identChar >>= \s -> check s >> return s
-        where
-          identChar c   = isAlphaNum c || c == '_' || c == '-'
-          check ('-':_) = Parse.pfail
-          check _       = return ()
+parseFlagAssignment :: Parse.ReadP r FlagAssignment
+parseFlagAssignment = Parse.sepBy1 parseFlagValue skipSpaces1
+  where
+    parseFlagValue =
+          (do Parse.optional (Parse.char '+')
+              f <- parseFlagName
+              return (f, True))
+      +++ (do _ <- Parse.char '-'
+              f <- parseFlagName
+              return (f, False))
+    parseFlagName = liftM (FlagName . lowercase) ident
+
+    ident :: Parse.ReadP r String
+    ident = Parse.munch1 identChar >>= \s -> check s >> return s
+      where
+        identChar c   = isAlphaNum c || c == '_' || c == '-'
+        check ('-':_) = Parse.pfail
+        check _       = return ()
+
+skipSpaces1 :: Parse.ReadP r ()
+skipSpaces1 = Parse.satisfy isSpace >> Parse.skipSpaces
+
diff --git a/cabal/cabal-install/Distribution/Client/Types.hs b/cabal/cabal-install/Distribution/Client/Types.hs
--- a/cabal/cabal-install/Distribution/Client/Types.hs
+++ b/cabal/cabal-install/Distribution/Client/Types.hs
@@ -1,4 +1,9 @@
 {-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Distribution.Client.Types
@@ -16,9 +21,8 @@
 
 import Distribution.Package
          ( PackageName, PackageId, Package(..)
-         , mkPackageKey, PackageKey, InstalledPackageId(..)
-         , HasInstalledPackageId(..), PackageInstalled(..)
-         , LibraryName, packageKeyLibraryName )
+         , UnitId(..), mkUnitId, pkgName
+         , HasUnitId(..), PackageInstalled(..) )
 import Distribution.InstalledPackageInfo
          ( InstalledPackageInfo )
 import Distribution.PackageDescription
@@ -33,31 +37,50 @@
 import qualified Distribution.Client.ComponentDeps as CD
 import Distribution.Version
          ( VersionRange )
-import Distribution.Simple.Compiler
-         ( Compiler, packageKeySupported )
 import Distribution.Text (display)
-import qualified Distribution.InstalledPackageInfo as Info
 
 import Data.Map (Map)
-import Network.URI (URI, nullURI)
+import Network.URI (URI(..), URIAuth(..), nullURI)
 import Data.ByteString.Lazy (ByteString)
 import Control.Exception
          ( SomeException )
+import GHC.Generics (Generic)
+import Distribution.Compat.Binary (Binary(..))
 
+
 newtype Username = Username { unUsername :: String }
 newtype Password = Password { unPassword :: String }
 
 -- | This is the information we get from a @00-index.tar.gz@ hackage index.
 --
 data SourcePackageDb = SourcePackageDb {
-  packageIndex       :: PackageIndex SourcePackage,
+  packageIndex       :: PackageIndex UnresolvedSourcePackage,
   packagePreferences :: Map PackageName VersionRange
 }
+  deriving (Eq, Generic)
 
+instance Binary SourcePackageDb
+
 -- ------------------------------------------------------------
 -- * Various kinds of information about packages
 -- ------------------------------------------------------------
 
+-- | Within Cabal the library we no longer have a @InstalledPackageId@ type.
+-- That's because it deals with the compilers' notion of a registered library,
+-- and those really are libraries not packages. Those are now named units.
+--
+-- The package management layer does however deal with installed packages, as
+-- whole packages not just as libraries. So we do still need a type for
+-- installed package ids. At the moment however we track instaled packages via
+-- their primary library, which is a unit id. In future this may change
+-- slightly and we may distinguish these two types and have an explicit
+-- conversion when we register units with the compiler.
+--
+type InstalledPackageId = UnitId
+
+installedPackageId :: HasUnitId pkg => pkg -> InstalledPackageId
+installedPackageId = installedUnitId
+
 -- | Subclass of packages that have specific versioned dependencies.
 --
 -- So for example a not-yet-configured package has dependencies on version
@@ -66,44 +89,47 @@
 --  dependency graphs) only make sense on this subclass of package types.
 --
 class Package pkg => PackageFixedDeps pkg where
-  depends :: pkg -> ComponentDeps [InstalledPackageId]
+  depends :: pkg -> ComponentDeps [UnitId]
 
 instance PackageFixedDeps InstalledPackageInfo where
-  depends = CD.fromInstalled . installedDepends
+  depends pkg = CD.fromInstalled (display (pkgName (packageId pkg)))
+                                 (installedDepends pkg)
 
 
 -- | In order to reuse the implementation of PackageIndex which relies on
--- 'InstalledPackageId', we need to be able to synthesize these IDs prior
+-- 'UnitId', we need to be able to synthesize these IDs prior
 -- to installation.  Eventually, we'll move to a representation of
--- 'InstalledPackageId' which can be properly computed before compilation
+-- 'UnitId' which can be properly computed before compilation
 -- (of course, it's a bit of a misnomer since the packages are not actually
 -- installed yet.)  In any case, we'll synthesize temporary installed package
 -- IDs to use as keys during install planning.  These should never be written
 -- out!  Additionally, they need to be guaranteed unique within the install
 -- plan.
-fakeInstalledPackageId :: PackageId -> InstalledPackageId
-fakeInstalledPackageId = InstalledPackageId . (".fake."++) . display
+fakeUnitId :: PackageId -> UnitId
+fakeUnitId = mkUnitId . (".fake."++) . display
 
 -- | A 'ConfiguredPackage' is a not-yet-installed package along with the
 -- total configuration information. The configuration information is total in
 -- the sense that it provides all the configuration information and so the
 -- final configure process will be independent of the environment.
 --
-data ConfiguredPackage = ConfiguredPackage
-       SourcePackage       -- package info, including repo
-       FlagAssignment      -- complete flag assignment for the package
-       [OptionalStanza]    -- list of enabled optional stanzas for the package
-       (ComponentDeps [ConfiguredId])
-                           -- set of exact dependencies (installed or source).
-                           -- These must be consistent with the 'buildDepends'
-                           -- in the 'PackageDescription' that you'd get by
-                           -- applying the flag assignment and optional stanzas.
-  deriving Show
+data ConfiguredPackage loc = ConfiguredPackage {
+       confPkgSource :: SourcePackage loc, -- package info, including repo
+       confPkgFlags :: FlagAssignment,     -- complete flag assignment for the package
+       confPkgStanzas :: [OptionalStanza], -- list of enabled optional stanzas for the package
+       confPkgDeps :: ComponentDeps [ConfiguredId]
+                               -- set of exact dependencies (installed or source).
+                               -- These must be consistent with the 'buildDepends'
+                               -- in the 'PackageDescription' that you'd get by
+                               -- applying the flag assignment and optional stanzas.
+    }
+  deriving (Eq, Show, Generic)
 
+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 InstalledPackageId
--- (at least, in principle, even if we have to fake it currently). It is still
+-- Once we configure a source package we know it's UnitId. It is still
 -- however useful in lots of places to also know the source ID for the package.
 -- We therefore bundle the two.
 --
@@ -111,81 +137,69 @@
 -- configuration parameters and dependencies have been specified).
 --
 -- TODO: I wonder if it would make sense to promote this datatype to Cabal
--- and use it consistently instead of InstalledPackageIds?
+-- and use it consistently instead of UnitIds?
 data ConfiguredId = ConfiguredId {
     confSrcId  :: PackageId
-  , confInstId :: InstalledPackageId
+  , confInstId :: UnitId
   }
+  deriving (Eq, Generic)
 
+instance Binary ConfiguredId
+
 instance Show ConfiguredId where
   show = show . confSrcId
 
-instance Package ConfiguredPackage where
-  packageId (ConfiguredPackage pkg _ _ _) = packageId pkg
-
-instance PackageFixedDeps ConfiguredPackage where
-  depends (ConfiguredPackage _ _ _ deps) = fmap (map confInstId) deps
-
-instance HasInstalledPackageId ConfiguredPackage where
-  installedPackageId = fakeInstalledPackageId . packageId
-
--- | Like 'ConfiguredPackage', but with all dependencies guaranteed to be
--- installed already, hence itself ready to be installed.
-data GenericReadyPackage srcpkg ipkg
-   = ReadyPackage
-       srcpkg                  -- see 'ConfiguredPackage'.
-       (ComponentDeps [ipkg])  -- Installed dependencies.
-  deriving (Eq, Show)
-
-type ReadyPackage = GenericReadyPackage ConfiguredPackage InstalledPackageInfo
+instance Package ConfiguredId where
+  packageId = confSrcId
 
-instance Package srcpkg => Package (GenericReadyPackage srcpkg ipkg) where
-  packageId (ReadyPackage srcpkg _deps) = packageId srcpkg
+instance HasUnitId ConfiguredId where
+  installedUnitId = confInstId
 
-instance (Package srcpkg, HasInstalledPackageId ipkg) =>
-         PackageFixedDeps (GenericReadyPackage srcpkg ipkg) where
-  depends (ReadyPackage _ deps) = fmap (map installedPackageId) deps
+instance Package (ConfiguredPackage loc) where
+  packageId cpkg = packageId (confPkgSource cpkg)
 
-instance HasInstalledPackageId srcpkg =>
-         HasInstalledPackageId (GenericReadyPackage srcpkg ipkg) where
-  installedPackageId (ReadyPackage pkg _) = installedPackageId pkg
+instance PackageFixedDeps (ConfiguredPackage loc) where
+  depends cpkg = fmap (map installedUnitId) (confPkgDeps cpkg)
 
+instance HasUnitId (ConfiguredPackage loc) where
+  installedUnitId cpkg = fakeUnitId (packageId cpkg)
 
--- | Extracts a package key from ReadyPackage, a common operation needed
--- to calculate build paths.
-readyPackageKey :: Compiler -> ReadyPackage -> PackageKey
-readyPackageKey comp (ReadyPackage pkg deps) =
-    mkPackageKey (packageKeySupported comp) (packageId pkg)
-                 (map Info.libraryName (CD.nonSetupDeps deps))
+-- | 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)
 
--- | Extracts a library name from ReadyPackage, a common operation needed
--- to calculate build paths.
-readyLibraryName :: Compiler -> ReadyPackage -> LibraryName
-readyLibraryName comp ready@(ReadyPackage pkg _) =
-    packageKeyLibraryName (packageId pkg) (readyPackageKey comp ready)
+type ReadyPackage = GenericReadyPackage (ConfiguredPackage UnresolvedPkgLoc)
 
 
 -- | A package description along with the location of the package sources.
 --
-data SourcePackage = SourcePackage {
+data SourcePackage loc = SourcePackage {
     packageInfoId        :: PackageId,
     packageDescription   :: GenericPackageDescription,
-    packageSource        :: PackageLocation (Maybe FilePath),
+    packageSource        :: loc,
     packageDescrOverride :: PackageDescriptionOverride
   }
-  deriving Show
+  deriving (Eq, Show, Generic)
 
+instance (Binary loc) => Binary (SourcePackage loc)
+
+-- | Convenience alias for 'SourcePackage UnresolvedPkgLoc'.
+type UnresolvedSourcePackage = SourcePackage UnresolvedPkgLoc
+
 -- | We sometimes need to override the .cabal file in the tarball with
 -- the newer one from the package index.
 type PackageDescriptionOverride = Maybe ByteString
 
-instance Package SourcePackage where packageId = packageInfoId
+instance Package (SourcePackage a) where packageId = packageInfoId
 
 data OptionalStanza
     = TestStanzas
     | BenchStanzas
-  deriving (Eq, Ord, Show)
+  deriving (Eq, Ord, Enum, Bounded, Show, Generic)
 
+instance Binary OptionalStanza
+
 enableStanzas
     :: [OptionalStanza]
     -> GenericPackageDescription
@@ -204,6 +218,10 @@
 -- * Package locations and repositories
 -- ------------------------------------------------------------
 
+type UnresolvedPkgLoc = PackageLocation (Maybe FilePath)
+
+type ResolvedPkgLoc = PackageLocation FilePath
+
 data PackageLocation local =
 
     -- | An unpacked package in the given dir, or current dir
@@ -224,17 +242,40 @@
 --TODO:
 --  * add support for darcs and other SCM style remote repos with a local cache
 --  | ScmPackage
-  deriving (Show, Functor)
+  deriving (Show, Functor, Eq, Ord, Generic)
 
-data LocalRepo = LocalRepo
-  deriving (Show,Eq)
+instance Binary local => Binary (PackageLocation local)
 
+-- note, network-uri-2.6.0.3+ provide a Generic instance but earlier
+-- versions do not, so we use manual Binary instances here
+instance Binary URI where
+  put (URI a b c d e) = do put a; put b; put c; put d; put e
+  get = do !a <- get; !b <- get; !c <- get; !d <- get; !e <- get
+           return (URI a b c d e)
+
+instance Binary URIAuth where
+  put (URIAuth a b c) = do put a; put b; put c
+  get = do !a <- get; !b <- get; !c <- get; return (URIAuth a b c)
+
 data RemoteRepo =
     RemoteRepo {
       remoteRepoName     :: String,
       remoteRepoURI      :: URI,
-      remoteRepoRootKeys :: (),
 
+      -- | Enable secure access?
+      --
+      -- 'Nothing' here represents "whatever the default is"; this is important
+      -- to allow for a smooth transition from opt-in to opt-out security
+      -- (once we switch to opt-out, all access to the central Hackage
+      -- repository should be secure by default)
+      remoteRepoSecure :: Maybe Bool,
+
+      -- | Root key IDs (for bootstrapping)
+      remoteRepoRootKeys :: [String],
+
+      -- | Threshold for verification during bootstrapping
+      remoteRepoKeyThreshold :: Int,
+
       -- | Normally a repo just specifies an HTTP or HTTPS URI, but as a
       -- special case we may know a repo supports both and want to try HTTPS
       -- if we can, but still allow falling back to HTTP.
@@ -244,20 +285,51 @@
       remoteRepoShouldTryHttps :: Bool
     }
 
-  -- FIXME: discuss this type some more.
+  deriving (Show, Eq, Ord, Generic)
 
-  deriving (Show,Eq,Ord)
+instance Binary RemoteRepo
 
 -- | Construct a partial 'RemoteRepo' value to fold the field parser list over.
 emptyRemoteRepo :: String -> RemoteRepo
-emptyRemoteRepo name = RemoteRepo name nullURI () False
+emptyRemoteRepo name = RemoteRepo name nullURI Nothing [] 0 False
 
-data Repo = Repo {
-    repoKind     :: Either RemoteRepo LocalRepo,
-    repoLocalDir :: FilePath
-  }
-  deriving (Show,Eq)
+-- | Different kinds of repositories
+--
+-- NOTE: It is important that this type remains serializable.
+data Repo =
+    -- | Local repositories
+    RepoLocal {
+        repoLocalDir :: FilePath
+      }
 
+    -- | Standard (unsecured) remote repositores
+  | RepoRemote {
+        repoRemote   :: RemoteRepo
+      , repoLocalDir :: FilePath
+      }
+
+    -- | Secure repositories
+    --
+    -- Although this contains the same fields as 'RepoRemote', we use a separate
+    -- constructor to avoid confusing the two.
+    --
+    -- Not all access to a secure repo goes through the hackage-security
+    -- library currently; code paths that do not still make use of the
+    -- 'repoRemote' and 'repoLocalDir' fields directly.
+  | RepoSecure {
+        repoRemote   :: RemoteRepo
+      , repoLocalDir :: FilePath
+      }
+  deriving (Show, Eq, Ord, Generic)
+
+instance Binary Repo
+
+-- | Check if this is a remote repo
+maybeRepoRemote :: Repo -> Maybe RemoteRepo
+maybeRepoRemote (RepoLocal    _localDir) = Nothing
+maybeRepoRemote (RepoRemote r _localDir) = Just r
+maybeRepoRemote (RepoSecure r _localDir) = Just r
+
 -- ------------------------------------------------------------
 -- * Build results
 -- ------------------------------------------------------------
@@ -271,8 +343,22 @@
                   | BuildFailed     SomeException
                   | TestsFailed     SomeException
                   | InstallFailed   SomeException
+  deriving (Show, Generic)
 data BuildSuccess = BuildOk         DocsResult TestsResult
                                     (Maybe InstalledPackageInfo)
+  deriving (Show, Generic)
 
 data DocsResult  = DocsNotTried  | DocsFailed  | DocsOk
+  deriving (Show, Generic)
 data TestsResult = TestsNotTried | TestsOk
+  deriving (Show, Generic)
+
+instance Binary BuildFailure
+instance Binary BuildSuccess
+instance Binary DocsResult
+instance Binary TestsResult
+
+--FIXME: this is a total cheat
+instance Binary SomeException where
+  put _ = return ()
+  get = fail "cannot serialise exceptions"
diff --git a/cabal/cabal-install/Distribution/Client/Update.hs b/cabal/cabal-install/Distribution/Client/Update.hs
--- a/cabal/cabal-install/Distribution/Client/Update.hs
+++ b/cabal/cabal-install/Distribution/Client/Update.hs
@@ -10,39 +10,46 @@
 --
 --
 -----------------------------------------------------------------------------
+{-# LANGUAGE RecordWildCards #-}
 module Distribution.Client.Update
     ( update
     ) where
 
 import Distribution.Client.Types
-         ( Repo(..), RemoteRepo(..), LocalRepo(..) )
+         ( Repo(..), RemoteRepo(..), maybeRepoRemote )
 import Distribution.Client.HttpUtils
-         ( DownloadResult(..), HttpTransport(..) )
+         ( DownloadResult(..) )
 import Distribution.Client.FetchUtils
          ( downloadIndex )
 import Distribution.Client.IndexUtils
-         ( updateRepoIndexCache )
+         ( updateRepoIndexCache, Index(..) )
 import Distribution.Client.JobControl
          ( newParallelJobControl, spawnJob, collectJob )
+import Distribution.Client.Setup
+         ( RepoContext(..) )
+import Distribution.Verbosity
+         ( Verbosity )
 
 import Distribution.Simple.Utils
          ( writeFileAtomic, warn, notice )
-import Distribution.Verbosity
-         ( Verbosity )
 
 import qualified Data.ByteString.Lazy       as BS
 import Distribution.Client.GZipUtils (maybeDecompress)
 import System.FilePath (dropExtension)
-import Data.Either (lefts)
+import Data.Maybe (catMaybes)
+import Data.Time (getCurrentTime)
 
+import qualified Hackage.Security.Client as Sec
+
 -- | 'update' downloads the package list from all known servers
-update :: HttpTransport -> Verbosity -> [Repo] -> IO ()
-update _ verbosity [] =
+update :: Verbosity -> RepoContext -> IO ()
+update verbosity repoCtxt | null (repoContextRepos repoCtxt) = do
   warn verbosity $ "No remote package servers have been specified. Usually "
                 ++ "you would have one specified in the config file."
-update transport verbosity repos = do
+update verbosity repoCtxt = do
   jobCtrl <- newParallelJobControl
-  let remoteRepos = lefts (map repoKind repos)
+  let repos       = repoContextRepos repoCtxt
+      remoteRepos = catMaybes (map maybeRepoRemote repos)
   case remoteRepos of
     [] -> return ()
     [remoteRepo] ->
@@ -51,17 +58,31 @@
     _ -> notice verbosity . unlines
             $ "Downloading the latest package lists from: "
             : map (("- " ++) . remoteRepoName) remoteRepos
-  mapM_ (spawnJob jobCtrl . updateRepo transport verbosity) repos
+  mapM_ (spawnJob jobCtrl . updateRepo verbosity repoCtxt) repos
   mapM_ (\_ -> collectJob jobCtrl) repos
 
-updateRepo :: HttpTransport -> Verbosity -> Repo -> IO ()
-updateRepo transport verbosity repo = case repoKind repo of
-  Right LocalRepo -> return ()
-  Left remoteRepo -> do
-    downloadResult <- downloadIndex transport verbosity remoteRepo (repoLocalDir repo)
-    case downloadResult of
-      FileAlreadyInCache -> return ()
-      FileDownloaded indexPath -> do
-        writeFileAtomic (dropExtension indexPath) . maybeDecompress
-                                                =<< BS.readFile indexPath
-        updateRepoIndexCache verbosity repo
+updateRepo :: Verbosity -> RepoContext -> Repo -> IO ()
+updateRepo verbosity repoCtxt repo = do
+  transport <- repoContextGetTransport repoCtxt
+  case repo of
+    RepoLocal{..} -> return ()
+    RepoRemote{..} -> do
+      downloadResult <- downloadIndex transport verbosity repoRemote repoLocalDir
+      case downloadResult of
+        FileAlreadyInCache -> return ()
+        FileDownloaded indexPath -> do
+          writeFileAtomic (dropExtension indexPath) . maybeDecompress
+                                                  =<< BS.readFile indexPath
+          updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)
+    RepoSecure{} -> repoContextWithSecureRepo repoCtxt repo $ \repoSecure -> do
+      ce <- if repoContextIgnoreExpiry repoCtxt
+              then Just `fmap` getCurrentTime
+              else return Nothing
+      updated <- Sec.uncheckClientErrors $ Sec.checkForUpdates repoSecure ce
+      -- Update cabal's internal index as well so that it's not out of sync
+      -- (If all access to the cache goes through hackage-security this can go)
+      case updated of
+        Sec.NoUpdates  ->
+          return ()
+        Sec.HasUpdates ->
+          updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)
diff --git a/cabal/cabal-install/Distribution/Client/Upload.hs b/cabal/cabal-install/Distribution/Client/Upload.hs
--- a/cabal/cabal-install/Distribution/Client/Upload.hs
+++ b/cabal/cabal-install/Distribution/Client/Upload.hs
@@ -1,11 +1,11 @@
--- This is a quick hack for uploading packages to Hackage.
--- See http://hackage.haskell.org/trac/hackage/wiki/CabalUpload
-
-module Distribution.Client.Upload (check, upload, report) where
+module Distribution.Client.Upload (check, upload, uploadDoc, report) where
 
-import Distribution.Client.Types (Username(..), Password(..),Repo(..),RemoteRepo(..))
+import Distribution.Client.Types ( Username(..), Password(..)
+                                 , RemoteRepo(..), maybeRepoRemote )
 import Distribution.Client.HttpUtils
-         ( isOldHackageURI, HttpTransport(..), remoteRepoTryUpgradeToHttps )
+         ( HttpTransport(..), remoteRepoTryUpgradeToHttps )
+import Distribution.Client.Setup
+         ( RepoContext(..) )
 
 import Distribution.Simple.Utils (notice, warn, info, die)
 import Distribution.Verbosity (Verbosity)
@@ -16,45 +16,88 @@
 import qualified Distribution.Client.BuildReports.Upload as BuildReport
 
 import Network.URI (URI(uriPath), parseURI)
+import Network.HTTP (Header(..), HeaderName(..))
 
 import System.IO        (hFlush, stdin, stdout, hGetEcho, hSetEcho)
+import System.Exit      (exitFailure)
 import Control.Exception (bracket)
-import System.FilePath  ((</>), takeExtension)
+import System.FilePath  ((</>), takeExtension, takeFileName)
 import qualified System.FilePath.Posix as FilePath.Posix ((</>))
 import System.Directory
 import Control.Monad (forM_, when)
+import Data.Maybe (catMaybes)
 
 type Auth = Maybe (String, String)
 
---FIXME: how do we find this path for an arbitrary hackage server?
--- is it always at some fixed location relative to the server root?
-legacyUploadURI :: URI
-Just legacyUploadURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/protected/upload-pkg"
-
 checkURI :: URI
-Just checkURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/check-pkg"
+Just checkURI = parseURI $ "http://hackage.haskell.org/cgi-bin/"
+                           ++ "hackage-scripts/check-pkg"
 
-upload :: HttpTransport -> Verbosity -> [Repo] -> Maybe Username -> Maybe Password -> [FilePath] -> IO ()
-upload transport verbosity repos mUsername mPassword paths = do
+upload :: Verbosity -> RepoContext
+       -> Maybe Username -> Maybe Password -> [FilePath]
+       -> IO ()
+upload verbosity repoCtxt mUsername mPassword paths = do
+    let repos = repoContextRepos repoCtxt
+    transport  <- repoContextGetTransport repoCtxt
     targetRepo <-
-      case [ remoteRepo | Left remoteRepo <- map repoKind repos ] of
-        [] -> die $ "Cannot upload. No remote repositories are configured."
+      case [ remoteRepo | Just remoteRepo <- map maybeRepoRemote repos ] of
+        [] -> die "Cannot upload. No remote repositories are configured."
         rs -> remoteRepoTryUpgradeToHttps transport (last rs)
     let targetRepoURI = remoteRepoURI targetRepo
-        uploadURI
-          | isOldHackageURI targetRepoURI
-          = legacyUploadURI
-          | otherwise
-          = targetRepoURI {
-              uriPath = uriPath targetRepoURI FilePath.Posix.</> "upload"
-            }
+        rootIfEmpty x = if null x then "/" else x
+        uploadURI = targetRepoURI {
+            uriPath = rootIfEmpty (uriPath targetRepoURI)
+                      FilePath.Posix.</> "upload"
+        }
     Username username <- maybe promptUsername return mUsername
     Password password <- maybe promptPassword return mPassword
     let auth = Just (username,password)
-    flip mapM_ paths $ \path -> do
+    forM_ paths $ \path -> do
       notice verbosity $ "Uploading " ++ path ++ "... "
       handlePackage transport verbosity uploadURI auth path
 
+uploadDoc :: Verbosity -> RepoContext
+          -> Maybe Username -> Maybe Password -> FilePath
+          -> IO ()
+uploadDoc verbosity repoCtxt mUsername mPassword path = do
+    let repos = repoContextRepos repoCtxt
+    transport  <- repoContextGetTransport repoCtxt
+    targetRepo <-
+      case [ remoteRepo | Just remoteRepo <- map maybeRepoRemote repos ] of
+        [] -> die $ "Cannot upload. No remote repositories are configured."
+        rs -> remoteRepoTryUpgradeToHttps transport (last rs)
+    let targetRepoURI = remoteRepoURI targetRepo
+        rootIfEmpty x = if null x then "/" else x
+        uploadURI = targetRepoURI {
+            uriPath = rootIfEmpty (uriPath targetRepoURI)
+                      FilePath.Posix.</> "package/" ++ pkgid ++ "/docs"
+        }
+        (reverseSuffix, reversePkgid) = break (== '-')
+                                        (reverse (takeFileName path))
+        pkgid = reverse $ tail reversePkgid
+    when (reverse reverseSuffix /= "docs.tar.gz"
+          || null reversePkgid || head reversePkgid /= '-') $
+      die "Expected a file name matching the pattern <pkgid>-docs.tar.gz"
+    Username username <- maybe promptUsername return mUsername
+    Password password <- maybe promptPassword return mPassword
+
+    let auth = Just (username,password)
+        headers =
+          [ Header HdrContentType "application/x-tar"
+          , Header HdrContentEncoding "gzip"
+          ]
+    notice verbosity $ "Uploading documentation " ++ path ++ "... "
+    resp <- putHttpFile transport verbosity uploadURI path auth headers
+    case resp of
+      (200,_)     ->
+        notice verbosity "Ok"
+      (code,err)  -> do
+        notice verbosity $ "Error uploading documentation "
+                        ++ path ++ ": "
+                        ++ "http code " ++ show code ++ "\n"
+                        ++ err
+        exitFailure
+
 promptUsername :: IO Username
 promptUsername = do
   putStr "Hackage username: "
@@ -72,43 +115,49 @@
   putStrLn ""
   return passwd
 
-report :: Verbosity -> [Repo] -> Maybe Username -> Maybe Password -> IO ()
-report verbosity repos mUsername mPassword = do
-      Username username <- maybe promptUsername return mUsername
-      Password password <- maybe promptPassword return mPassword
-      let auth = (username,password)
-      forM_ repos $ \repo -> case repoKind repo of
-        Left remoteRepo
-            -> do dotCabal <- defaultCabalDir
-                  let srcDir = dotCabal </> "reports" </> remoteRepoName remoteRepo
-                  -- We don't want to bomb out just because we haven't built any packages from this repo yet
-                  srcExists <- doesDirectoryExist srcDir
-                  when srcExists $ do
-                    contents <- getDirectoryContents srcDir
-                    forM_ (filter (\c -> takeExtension c == ".log") contents) $ \logFile ->
-                        do inp <- readFile (srcDir </> logFile)
-                           let (reportStr, buildLog) = read inp :: (String,String)
-                           case BuildReport.parse reportStr of
-                             Left errs -> do warn verbosity $ "Errors: " ++ errs -- FIXME
-                             Right report' ->
-                                 do info verbosity $ "Uploading report for " ++ display (BuildReport.package report')
-                                    BuildReport.uploadReports verbosity auth (remoteRepoURI remoteRepo) [(report', Just buildLog)]
-                                    return ()
-        Right{} -> return ()
+report :: Verbosity -> RepoContext -> Maybe Username -> Maybe Password -> IO ()
+report verbosity repoCtxt mUsername mPassword = do
+  Username username <- maybe promptUsername return mUsername
+  Password password <- maybe promptPassword return mPassword
+  let auth        = (username, password)
+      repos       = repoContextRepos repoCtxt
+      remoteRepos = catMaybes (map maybeRepoRemote repos)
+  forM_ remoteRepos $ \remoteRepo ->
+      do dotCabal <- defaultCabalDir
+         let srcDir = dotCabal </> "reports" </> remoteRepoName remoteRepo
+         -- We don't want to bomb out just because we haven't built any packages
+         -- from this repo yet.
+         srcExists <- doesDirectoryExist srcDir
+         when srcExists $ do
+           contents <- getDirectoryContents srcDir
+           forM_ (filter (\c -> takeExtension c ==".log") contents) $ \logFile ->
+             do inp <- readFile (srcDir </> logFile)
+                let (reportStr, buildLog) = read inp :: (String,String)
+                case BuildReport.parse reportStr of
+                  Left errs -> warn verbosity $ "Errors: " ++ errs -- FIXME
+                  Right report' ->
+                    do info verbosity $ "Uploading report for "
+                         ++ display (BuildReport.package report')
+                       BuildReport.uploadReports verbosity repoCtxt auth
+                         (remoteRepoURI remoteRepo) [(report', Just buildLog)]
+                       return ()
 
-check :: HttpTransport -> Verbosity -> [FilePath] -> IO ()
-check transport verbosity paths = do
-          flip mapM_ paths $ \path -> do
-            notice verbosity $ "Checking " ++ path ++ "... "
-            handlePackage transport verbosity checkURI Nothing path
+check :: Verbosity -> RepoContext -> [FilePath] -> IO ()
+check verbosity repoCtxt paths = do
+    transport <- repoContextGetTransport repoCtxt
+    forM_ paths $ \path -> do
+      notice verbosity $ "Checking " ++ path ++ "... "
+      handlePackage transport verbosity checkURI Nothing path
 
 handlePackage :: HttpTransport -> Verbosity -> URI -> Auth
               -> FilePath -> IO ()
 handlePackage transport verbosity uri auth path =
   do resp <- postHttpFile transport verbosity uri path auth
      case resp of
-       (200,_)     -> do notice verbosity "Ok"
-       (code,err)  -> do notice verbosity $ "Error uploading " ++ path ++ ": "
-                                     ++ "http code " ++ show code ++ "\n"
-                                     ++ err
-
+       (200,_)     ->
+          notice verbosity "Ok"
+       (code,err)  -> do
+          notice verbosity $ "Error uploading " ++ path ++ ": "
+                          ++ "http code " ++ show code ++ "\n"
+                          ++ err
+          exitFailure
diff --git a/cabal/cabal-install/Distribution/Client/Utils.hs b/cabal/cabal-install/Distribution/Client/Utils.hs
--- a/cabal/cabal-install/Distribution/Client/Utils.hs
+++ b/cabal/cabal-install/Distribution/Client/Utils.hs
@@ -3,10 +3,13 @@
 module Distribution.Client.Utils ( MergeResult(..)
                                  , mergeBy, duplicates, duplicatesBy
                                  , readMaybe
-                                 , inDir, determineNumJobs, numberOfProcessors
+                                 , inDir, logDirChange
+                                 , determineNumJobs, numberOfProcessors
                                  , removeExistingFile
                                  , withTempFileName
-                                 , makeAbsoluteToCwd, filePathToByteString
+                                 , makeAbsoluteToCwd
+                                 , makeRelativeToCwd, makeRelativeToDir
+                                 , filePathToByteString
                                  , byteStringToFilePath, tryCanonicalizePath
                                  , canonicalizePathNoThrow
                                  , moreRecentFile, existsAndIsMoreRecentThan
@@ -20,6 +23,9 @@
 import Distribution.Simple.Setup       ( Flag(..) )
 import Distribution.Simple.Utils       ( die, findPackageDesc )
 import qualified Data.ByteString.Lazy as BS
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
 import Control.Monad
          ( when )
 import Data.Bits
@@ -41,7 +47,7 @@
          ( canonicalizePath, doesFileExist, getCurrentDirectory
          , removeFile, setCurrentDirectory )
 import System.FilePath
-         ( (</>), isAbsolute )
+         ( (</>), isAbsolute, takeDrive, splitPath, joinPath )
 import System.IO
          ( Handle, hClose, openTempFile
 #if MIN_VERSION_base(4,4,0)
@@ -57,7 +63,7 @@
          ( recoverEncode, CodingFailureMode(TransliterateCodingFailure) )
 #endif
 
-#if defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS) || MIN_VERSION_directory(1,2,3)
 import Prelude hiding (ioError)
 import Control.Monad (liftM2, unless)
 import System.Directory (doesDirectoryExist)
@@ -123,6 +129,9 @@
     (\(name, h) -> hClose h >> action name)
 
 -- | Executes the action in the specified directory.
+--
+-- Warning: This operation is NOT thread-safe, because current
+-- working directory is a process-global concept.
 inDir :: Maybe FilePath -> IO a -> IO a
 inDir Nothing m = m
 inDir (Just d) m = do
@@ -130,6 +139,14 @@
   setCurrentDirectory d
   m `Exception.finally` setCurrentDirectory old
 
+-- | Log directory change in 'make' compatible syntax
+logDirChange :: (String -> IO ()) -> Maybe FilePath -> IO a -> IO a
+logDirChange _ Nothing m = m
+logDirChange l (Just d) m = do
+  l $ "cabal: Entering directory '" ++ d ++ "'\n"
+  m `Exception.finally`
+    (l $ "cabal: Leaving directory '" ++ d ++ "'\n")
+
 foreign import ccall "getNumberOfProcessors" c_getNumberOfProcessors :: IO CInt
 
 -- The number of processors is not going to change during the duration of the
@@ -152,6 +169,30 @@
                        | otherwise       = do cwd <- getCurrentDirectory
                                               return $! cwd </> path
 
+-- | Given a path (relative or absolute), make it relative to the current
+-- directory, including using @../..@ if necessary.
+makeRelativeToCwd :: FilePath -> IO FilePath
+makeRelativeToCwd path =
+    makeRelativeCanonical <$> canonicalizePath path <*> getCurrentDirectory
+
+-- | Given a path (relative or absolute), make it relative to the given
+-- directory, including using @../..@ if necessary.
+makeRelativeToDir :: FilePath -> FilePath -> IO FilePath
+makeRelativeToDir path dir =
+    makeRelativeCanonical <$> canonicalizePath path <*> canonicalizePath dir
+
+-- | Given a canonical absolute path and canonical absolute dir, make the path
+-- relative to the directory, including using @../..@ if necessary. Returns
+-- the original absolute path if it is not on the same drive as the given dir.
+makeRelativeCanonical :: FilePath -> FilePath -> FilePath
+makeRelativeCanonical path dir
+  | takeDrive path /= takeDrive dir = path
+  | otherwise                       = go (splitPath path) (splitPath dir)
+  where
+    go (p:ps) (d:ds) | p == d = go ps ds
+    go    []     []           = "./"
+    go    ps     ds           = joinPath (replicate (length ds) ".." ++ ps)
+
 -- | Convert a 'FilePath' to a lazy 'ByteString'. Each 'Char' is
 -- encoded as a little-endian 'Word32'.
 filePathToByteString :: FilePath -> BS.ByteString
@@ -187,13 +228,12 @@
         b2 = fromIntegral $ BS.index bs (i + 2)
         b3 = fromIntegral $ BS.index bs (i + 3)
 
--- | Workaround for the inconsistent behaviour of 'canonicalizePath'. It throws
--- an error if the path refers to a non-existent file on *nix, but not on
--- Windows.
+-- | Workaround for the inconsistent behaviour of 'canonicalizePath'. Always
+-- throws an error if the path refers to a non-existent file.
 tryCanonicalizePath :: FilePath -> IO FilePath
 tryCanonicalizePath path = do
   ret <- canonicalizePath path
-#if defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS) || MIN_VERSION_directory(1,2,3)
   exists <- liftM2 (||) (doesFileExist ret) (doesDirectoryExist ret)
   unless exists $
     ioError $ mkIOError doesNotExistErrorType "canonicalizePath"
diff --git a/cabal/cabal-install/Main.hs b/cabal/cabal-install/Main.hs
--- a/cabal/cabal-install/Main.hs
+++ b/cabal/cabal-install/Main.hs
@@ -16,7 +16,7 @@
 module Main (main) where
 
 import Distribution.Client.Setup
-         ( GlobalFlags(..), globalCommand, globalRepos
+         ( GlobalFlags(..), globalCommand, withRepoContext
          , ConfigFlags(..)
          , ConfigExFlags(..), defaultConfigExFlags, configureExCommand
          , BuildFlags(..), BuildExFlags(..), SkipAddSourceDepsCheck(..)
@@ -25,6 +25,7 @@
          , installCommand, upgradeCommand, uninstallCommand
          , FetchFlags(..), fetchCommand
          , FreezeFlags(..), freezeCommand
+         , genBoundsCommand
          , GetFlags(..), getCommand, unpackCommand
          , checkCommand
          , formatCommand
@@ -42,6 +43,7 @@
          , ExecFlags(..), execCommand
          , UserConfigFlags(..), userConfigCommand
          , reportCommand
+         , manpageCommand
          )
 import Distribution.Simple.Setup
          ( HaddockFlags(..), haddockCommand, defaultHaddockFlags
@@ -59,23 +61,28 @@
          ( setupWrapper, SetupScriptOptions(..), defaultSetupScriptOptions )
 import Distribution.Client.Config
          ( SavedConfig(..), loadConfig, defaultConfigFile, userConfigDiff
-         , userConfigUpdate )
+         , userConfigUpdate, createDefaultConfigFile, getConfigFilePath )
 import Distribution.Client.Targets
          ( readUserTargets )
 import qualified Distribution.Client.List as List
          ( list, info )
 
+--TODO: temporary import, just to force these modules to be built.
+-- It will be replaced by import of new build command once merged.
+import Distribution.Client.ProjectPlanning ()
+import Distribution.Client.ProjectBuilding ()
+
 import Distribution.Client.Install            (install)
 import Distribution.Client.Configure          (configure)
 import Distribution.Client.Update             (update)
 import Distribution.Client.Exec               (exec)
 import Distribution.Client.Fetch              (fetch)
 import Distribution.Client.Freeze             (freeze)
+import Distribution.Client.GenBounds          (genBounds)
 import Distribution.Client.Check as Check     (check)
 --import Distribution.Client.Clean            (clean)
-import Distribution.Client.Upload as Upload   (upload, check, report)
+import qualified Distribution.Client.Upload as Upload
 import Distribution.Client.Run                (run, splitRunArgs)
-import Distribution.Client.HttpUtils          (configureTransport)
 import Distribution.Client.SrcDist            (sdist)
 import Distribution.Client.Get                (get)
 import Distribution.Client.Sandbox            (sandboxInit
@@ -100,14 +107,17 @@
                                               ,updateInstallDirs
 
                                               ,configCompilerAux'
+                                              ,getPersistOrConfigCompiler
                                               ,configPackageDB')
 import Distribution.Client.Sandbox.PackageEnvironment
                                               (setPackageDB
                                               ,userPackageEnvironmentFile)
 import Distribution.Client.Sandbox.Timestamp  (maybeAddCompilerTimestampRecord)
 import Distribution.Client.Sandbox.Types      (UseSandbox(..), whenUsingSandbox)
+import Distribution.Client.Tar                (createTarGzFile)
 import Distribution.Client.Types              (Password (..))
 import Distribution.Client.Init               (initCabal)
+import Distribution.Client.Manpage            (manpage)
 import qualified Distribution.Client.Win32SelfUpgrade as Win32SelfUpgrade
 import Distribution.Client.Utils              (determineNumJobs
 #if defined(mingw32_HOST_OS)
@@ -115,9 +125,9 @@
 #endif
                                               ,existsAndIsMoreRecentThan)
 
+import Distribution.Package (packageId)
 import Distribution.PackageDescription
-         ( BuildType(..), Executable(..), benchmarkName, benchmarkBuildInfo
-         , testName, testBuildInfo, buildable )
+         ( BuildType(..), Executable(..), buildable )
 import Distribution.PackageDescription.Parse
          ( readPackageDescription )
 import Distribution.PackageDescription.PrettyPrint
@@ -127,8 +137,9 @@
 import Distribution.Simple.Build
          ( startInterpreter )
 import Distribution.Simple.Command
-         ( CommandParse(..), CommandUI(..), Command
-         , commandsRun, commandAddAction, hiddenCommand )
+         ( CommandParse(..), CommandUI(..), Command, CommandSpec(..)
+         , CommandType(..), commandsRun, commandAddAction, hiddenCommand
+         , commandFromSpec)
 import Distribution.Simple.Compiler
          ( Compiler(..) )
 import Distribution.Simple.Configure
@@ -155,7 +166,8 @@
 
 import System.Environment       (getArgs, getProgName)
 import System.Exit              (exitFailure)
-import System.FilePath          (splitExtension, takeExtension)
+import System.FilePath          ( dropExtension, splitExtension
+                                , takeExtension, (</>), (<.>))
 import System.IO                ( BufferMode(LineBuffering), hSetBuffering
 #ifdef mingw32_HOST_OS
                                 , stderr
@@ -163,7 +175,7 @@
                                 , stdout )
 import System.Directory         (doesFileExist, getCurrentDirectory)
 import Data.List                (intercalate)
-import Data.Maybe               (mapMaybe)
+import Data.Maybe               (listToMaybe)
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid              (Monoid(..))
 import Control.Applicative      (pure, (<$>))
@@ -224,59 +236,71 @@
     printNumericVersion = putStrLn $ display Paths_cabal_install.version
     printVersion        = putStrLn $ "cabal-install version "
                                   ++ display Paths_cabal_install.version
-                                  ++ "\nusing version "
+                                  ++ "\ncompiled using version "
                                   ++ display cabalVersion
                                   ++ " of the Cabal library "
 
-    commands =
-      [installCommand         `commandAddAction` installAction
-      ,updateCommand          `commandAddAction` updateAction
-      ,listCommand            `commandAddAction` listAction
-      ,infoCommand            `commandAddAction` infoAction
-      ,fetchCommand           `commandAddAction` fetchAction
-      ,freezeCommand          `commandAddAction` freezeAction
-      ,getCommand             `commandAddAction` getAction
-      ,hiddenCommand $
-       unpackCommand          `commandAddAction` unpackAction
-      ,checkCommand           `commandAddAction` checkAction
-      ,sdistCommand           `commandAddAction` sdistAction
-      ,uploadCommand          `commandAddAction` uploadAction
-      ,reportCommand          `commandAddAction` reportAction
-      ,runCommand             `commandAddAction` runAction
-      ,initCommand            `commandAddAction` initAction
-      ,configureExCommand     `commandAddAction` configureAction
-      ,buildCommand           `commandAddAction` buildAction
-      ,replCommand            `commandAddAction` replAction
-      ,sandboxCommand         `commandAddAction` sandboxAction
-      ,haddockCommand         `commandAddAction` haddockAction
-      ,execCommand            `commandAddAction` execAction
-      ,userConfigCommand      `commandAddAction` userConfigAction
-      ,cleanCommand           `commandAddAction` cleanAction
-      ,wrapperAction copyCommand
-                     copyVerbosity     copyDistPref
-      ,wrapperAction hscolourCommand
-                     hscolourVerbosity hscolourDistPref
-      ,wrapperAction registerCommand
-                     regVerbosity      regDistPref
-      ,testCommand            `commandAddAction` testAction
-      ,benchmarkCommand       `commandAddAction` benchmarkAction
-      ,hiddenCommand $
-       uninstallCommand       `commandAddAction` uninstallAction
-      ,hiddenCommand $
-       formatCommand          `commandAddAction` formatAction
-      ,hiddenCommand $
-       upgradeCommand         `commandAddAction` upgradeAction
-      ,hiddenCommand $
-       win32SelfUpgradeCommand`commandAddAction` win32SelfUpgradeAction
-      ,hiddenCommand $
-       actAsSetupCommand`commandAddAction` actAsSetupAction
+    commands = map commandFromSpec commandSpecs
+    commandSpecs =
+      [ regularCmd installCommand installAction
+      , regularCmd updateCommand updateAction
+      , regularCmd listCommand listAction
+      , regularCmd infoCommand infoAction
+      , regularCmd fetchCommand fetchAction
+      , regularCmd freezeCommand freezeAction
+      , regularCmd getCommand getAction
+      , hiddenCmd  unpackCommand unpackAction
+      , regularCmd checkCommand checkAction
+      , regularCmd sdistCommand sdistAction
+      , regularCmd uploadCommand uploadAction
+      , regularCmd reportCommand reportAction
+      , regularCmd runCommand runAction
+      , regularCmd initCommand initAction
+      , regularCmd configureExCommand configureAction
+      , regularCmd buildCommand buildAction
+      , regularCmd replCommand replAction
+      , regularCmd sandboxCommand sandboxAction
+      , regularCmd haddockCommand haddockAction
+      , regularCmd execCommand execAction
+      , regularCmd userConfigCommand userConfigAction
+      , regularCmd cleanCommand cleanAction
+      , regularCmd genBoundsCommand genBoundsAction
+      , wrapperCmd copyCommand copyVerbosity copyDistPref
+      , wrapperCmd hscolourCommand hscolourVerbosity hscolourDistPref
+      , wrapperCmd registerCommand regVerbosity regDistPref
+      , regularCmd testCommand testAction
+      , regularCmd benchmarkCommand benchmarkAction
+      , hiddenCmd  uninstallCommand uninstallAction
+      , hiddenCmd  formatCommand formatAction
+      , hiddenCmd  upgradeCommand upgradeAction
+      , hiddenCmd  win32SelfUpgradeCommand win32SelfUpgradeAction
+      , hiddenCmd  actAsSetupCommand actAsSetupAction
+      , hiddenCmd  manpageCommand (manpageAction commandSpecs)
       ]
 
+type Action = GlobalFlags -> IO ()
+
+regularCmd :: CommandUI flags -> (flags -> [String] -> action)
+           -> CommandSpec action
+regularCmd ui action =
+  CommandSpec ui ((flip commandAddAction) action) NormalCommand
+
+hiddenCmd :: CommandUI flags -> (flags -> [String] -> action)
+          -> CommandSpec action
+hiddenCmd ui action =
+  CommandSpec ui (\ui' -> hiddenCommand (commandAddAction ui' action))
+  HiddenCommand
+
+wrapperCmd :: Monoid flags => CommandUI flags -> (flags -> Flag Verbosity)
+           -> (flags -> Flag String) -> CommandSpec Action
+wrapperCmd ui verbosity distPref =
+  CommandSpec ui (\ui' -> wrapperAction ui' verbosity distPref) NormalCommand
+
 wrapperAction :: Monoid flags
               => CommandUI flags
               -> (flags -> Flag Verbosity)
               -> (flags -> Flag String)
-              -> Command (GlobalFlags -> IO ())
+              -> Command Action
 wrapperAction command verbosityFlag distPrefFlag =
   commandAddAction command
     { commandDefaultFlags = mempty } $ \flags extraArgs globalFlags -> do
@@ -288,7 +312,7 @@
                  command (const flags) extraArgs
 
 configureAction :: (ConfigFlags, ConfigExFlags)
-                -> [String] -> GlobalFlags -> IO ()
+                -> [String] -> Action
 configureAction (configFlags, configExFlags) extraArgs globalFlags = do
   let verbosity = fromFlagOrDefault normal (configVerbosity configFlags)
 
@@ -319,12 +343,13 @@
       (compilerId comp) platform
 
   maybeWithSandboxDirOnSearchPath useSandbox $
+   withRepoContext verbosity globalFlags' $ \repoContext ->
     configure verbosity
               (configPackageDB' configFlags'')
-              (globalRepos globalFlags')
+              repoContext
               comp platform conf configFlags'' configExFlags' extraArgs
 
-buildAction :: (BuildFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()
+buildAction :: (BuildFlags, BuildExFlags) -> [String] -> Action
 buildAction (buildFlags, buildExFlags) extraArgs globalFlags = do
   let verbosity   = fromFlagOrDefault normal (buildVerbosity buildFlags)
       noAddSource = fromFlagOrDefault DontSkipAddSourceDepsCheck
@@ -378,7 +403,7 @@
     numJobsCmdLineFlag = buildNumJobs buildFlags
 
 
-replAction :: (ReplFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()
+replAction :: (ReplFlags, BuildExFlags) -> [String] -> Action
 replAction (replFlags, buildExFlags) extraArgs globalFlags = do
   cwd     <- getCurrentDirectory
   pkgDesc <- findPackageDesc cwd
@@ -418,12 +443,13 @@
     onNoPkgDesc = do
       (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags
       let configFlags = savedConfigureFlags config
-      (comp, _platform, programDb) <- configCompilerAux' configFlags
+      (comp, platform, programDb) <- configCompilerAux' configFlags
       programDb' <- reconfigurePrograms verbosity
                                         (replProgramPaths replFlags)
                                         (replProgramArgs replFlags)
                                         programDb
-      startInterpreter verbosity programDb' comp (configPackageDB' configFlags)
+      startInterpreter verbosity programDb' comp platform
+                       (configPackageDB' configFlags)
 
 -- | Re-configure the package in the current directory if needed. Deciding
 -- when to reconfigure and with which options is convoluted:
@@ -508,6 +534,9 @@
                                       ++ "to be corrupt."
             _ -> show err
       case err of
+        -- Note: the build config could have been generated by a custom setup
+        -- script built against a different Cabal version, so it's crucial that
+        -- we ignore the bad version error here.
         ConfigStateFileBadVersion _ _ _ -> info verbosity msg
         _                               -> do
           let distVerbFlags = mempty
@@ -656,7 +685,7 @@
         ++ configureManually
 
 installAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)
-              -> [String] -> GlobalFlags -> IO ()
+              -> [String] -> Action
 installAction (configFlags, _, installFlags, _) _ globalFlags
   | fromFlagOrDefault False (installOnly installFlags) = do
       let verbosity = fromFlagOrDefault normal (configVerbosity configFlags)
@@ -701,7 +730,8 @@
                         haddockFlags { haddockDistPref = toFlag distPref }
       globalFlags'    = savedGlobalFlags      config `mappend` globalFlags
   (comp, platform, conf) <- configCompilerAux' configFlags'
-  -- TODO: Redesign ProgramDB API to prevent such problems as #2241 in the future.
+  -- TODO: Redesign ProgramDB API to prevent such problems as #2241 in the
+  -- future.
   conf' <- configureAllKnownPrograms verbosity conf
 
   -- If we're working inside a sandbox and the user has set the -w option, we
@@ -709,7 +739,8 @@
   -- timestamp record for this compiler to the timestamp file.
   configFlags'' <- case useSandbox of
     NoSandbox               -> configAbsolutePaths $ configFlags'
-    (UseSandbox sandboxDir) -> return $ setPackageDB sandboxDir comp platform configFlags'
+    (UseSandbox sandboxDir) -> return $ setPackageDB sandboxDir comp platform
+                                                     configFlags'
 
   whenUsingSandbox useSandbox $ \sandboxDir -> do
     initPackageDBIfNeeded verbosity configFlags'' comp conf'
@@ -718,7 +749,7 @@
     maybeAddCompilerTimestampRecord verbosity sandboxDir indexFile
       (compilerId comp) platform
 
-  -- FIXME: Passing 'SandboxPackageInfo' to install unconditionally here means
+  -- TODO: Passing 'SandboxPackageInfo' to install unconditionally here means
   -- that 'cabal install some-package' inside a sandbox will sometimes reinstall
   -- modified add-source deps, even if they are not among the dependencies of
   -- 'some-package'. This can also prevent packages that depend on older
@@ -726,9 +757,10 @@
   maybeWithSandboxPackageInfo verbosity configFlags'' globalFlags'
                               comp platform conf useSandbox $ \mSandboxPkgInfo ->
                               maybeWithSandboxDirOnSearchPath useSandbox $
+    withRepoContext verbosity globalFlags' $ \repoContext ->
       install verbosity
               (configPackageDB' configFlags'')
-              (globalRepos globalFlags')
+              repoContext
               comp platform conf'
               useSandbox mSandboxPkgInfo
               globalFlags' configFlags'' configExFlags'
@@ -764,33 +796,47 @@
   let setupOptions   = defaultSetupScriptOptions { useDistPref = distPref }
       testFlags'     = testFlags { testDistPref = toFlag distPref }
 
-  -- the package was just configured, so the LBI must be available
-  lbi <- getPersistBuildConfig distPref
-  let pkgDescr = LBI.localPkgDescr lbi
-      nameTestsOnly =
-        LBI.foldComponent
-          (const Nothing)
-          (const Nothing)
-          (\t ->
-            if buildable (testBuildInfo t)
-              then Just (testName t)
-            else Nothing)
-          (const Nothing)
-      tests = mapMaybe nameTestsOnly $ LBI.pkgComponents pkgDescr
-      extraArgs'
-        | null extraArgs = tests
-        | otherwise = extraArgs
+  -- The package was just configured, so the LBI must be available.
+  names <- componentNamesFromLBI distPref "test suites"
+             (\c -> case c of { LBI.CTest{} -> True; _ -> False })
+  let extraArgs'
+        | null extraArgs = case names of
+          ComponentNamesUnknown -> []
+          ComponentNames names' -> [ name | LBI.CTestName name <- names' ]
+        | otherwise      = extraArgs
 
-  if null tests
-    then notice verbosity "Package has no buildable test suites."
-    else do
-      maybeWithSandboxDirOnSearchPath useSandbox $
-        build verbosity config distPref buildFlags' extraArgs'
+  maybeWithSandboxDirOnSearchPath useSandbox $
+    build verbosity config distPref buildFlags' extraArgs'
 
-      maybeWithSandboxDirOnSearchPath useSandbox $
-        setupWrapper verbosity setupOptions Nothing
-          Cabal.testCommand (const testFlags') extraArgs'
+  maybeWithSandboxDirOnSearchPath useSandbox $
+    setupWrapper verbosity setupOptions Nothing
+    Cabal.testCommand (const testFlags') extraArgs'
 
+data ComponentNames = ComponentNamesUnknown
+                    | ComponentNames [LBI.ComponentName]
+
+-- | Return the names of all buildable components matching a given predicate.
+componentNamesFromLBI :: FilePath -> String -> (LBI.Component -> Bool)
+                         -> IO ComponentNames
+componentNamesFromLBI distPref targetsDescr compPred = do
+  eLBI <- tryGetPersistBuildConfig distPref
+  case eLBI of
+    Left err -> case err of
+      -- Note: the build config could have been generated by a custom setup
+      -- script built against a different Cabal version, so it's crucial that
+      -- we ignore the bad version error here.
+      ConfigStateFileBadVersion _ _ _ -> return ComponentNamesUnknown
+      _                               -> die (show err)
+    Right lbi -> do
+      let pkgDescr = LBI.localPkgDescr lbi
+          names    = map LBI.componentName
+                     . filter (buildable . LBI.componentBuildInfo)
+                     . filter compPred $
+                     LBI.pkgComponents pkgDescr
+      if null names
+        then die $ "Package has no buildable " ++ targetsDescr ++ "."
+        else return $! (ComponentNames names)
+
 benchmarkAction :: (BenchmarkFlags, BuildFlags, BuildExFlags)
                    -> [String] -> GlobalFlags
                    -> IO ()
@@ -816,34 +862,23 @@
   let setupOptions   = defaultSetupScriptOptions { useDistPref = distPref }
       benchmarkFlags'= benchmarkFlags { benchmarkDistPref = toFlag distPref }
 
-  -- the package was just configured, so the LBI must be available
-  lbi <- getPersistBuildConfig distPref
-  let pkgDescr = LBI.localPkgDescr lbi
-      nameBenchsOnly =
-        LBI.foldComponent
-          (const Nothing)
-          (const Nothing)
-          (const Nothing)
-          (\b ->
-            if buildable (benchmarkBuildInfo b)
-              then Just (benchmarkName b)
-            else Nothing)
-      benchs = mapMaybe nameBenchsOnly $ LBI.pkgComponents pkgDescr
-      extraArgs'
-        | null extraArgs = benchs
-        | otherwise = extraArgs
+  -- The package was just configured, so the LBI must be available.
+  names <- componentNamesFromLBI distPref "benchmarks"
+           (\c -> case c of { LBI.CBench{} -> True; _ -> False; })
+  let extraArgs'
+        | null extraArgs = case names of
+          ComponentNamesUnknown -> []
+          ComponentNames names' -> [name | LBI.CBenchName name <- names']
+        | otherwise      = extraArgs
 
-  if null benchs
-    then notice verbosity "Package has no buildable benchmarks."
-    else do
-      maybeWithSandboxDirOnSearchPath useSandbox $
-        build verbosity config distPref buildFlags' extraArgs'
+  maybeWithSandboxDirOnSearchPath useSandbox $
+    build verbosity config distPref buildFlags' extraArgs'
 
-      maybeWithSandboxDirOnSearchPath useSandbox $
-        setupWrapper verbosity setupOptions Nothing
-          Cabal.benchmarkCommand (const benchmarkFlags') extraArgs'
+  maybeWithSandboxDirOnSearchPath useSandbox $
+    setupWrapper verbosity setupOptions Nothing
+    Cabal.benchmarkCommand (const benchmarkFlags') extraArgs'
 
-haddockAction :: HaddockFlags -> [String] -> GlobalFlags -> IO ()
+haddockAction :: HaddockFlags -> [String] -> Action
 haddockAction haddockFlags extraArgs globalFlags = do
   let verbosity = fromFlag (haddockVerbosity haddockFlags)
   (_useSandbox, config, distPref) <-
@@ -856,8 +891,15 @@
       setupScriptOptions = defaultSetupScriptOptions { useDistPref = distPref }
   setupWrapper verbosity setupScriptOptions Nothing
     haddockCommand (const haddockFlags') extraArgs
+  when (fromFlagOrDefault False $ haddockForHackage haddockFlags) $ do
+    pkg <- fmap LBI.localPkgDescr (getPersistBuildConfig distPref)
+    let dest = distPref </> name <.> "tar.gz"
+        name = display (packageId pkg) ++ "-docs"
+        docDir = distPref </> "doc" </> "html"
+    createTarGzFile dest docDir name
+    notice verbosity $ "Documentation tarball created: " ++ dest
 
-cleanAction :: CleanFlags -> [String] -> GlobalFlags -> IO ()
+cleanAction :: CleanFlags -> [String] -> Action
 cleanAction cleanFlags extraArgs globalFlags = do
   (_, config) <- loadConfigOrSandboxConfig verbosity globalFlags
   distPref <- findSavedDistPref config (cleanDistPref cleanFlags)
@@ -871,7 +913,7 @@
   where
     verbosity = fromFlagOrDefault normal (cleanVerbosity cleanFlags)
 
-listAction :: ListFlags -> [String] -> GlobalFlags -> IO ()
+listAction :: ListFlags -> [String] -> Action
 listAction listFlags extraArgs globalFlags = do
   let verbosity = fromFlag (listVerbosity listFlags)
   (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity
@@ -883,15 +925,16 @@
         }
       globalFlags' = savedGlobalFlags    config `mappend` globalFlags
   (comp, _, conf) <- configCompilerAux' configFlags
-  List.list verbosity
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+    List.list verbosity
        (configPackageDB' configFlags)
-       (globalRepos globalFlags')
+       repoContext
        comp
        conf
        listFlags
        extraArgs
 
-infoAction :: InfoFlags -> [String] -> GlobalFlags -> IO ()
+infoAction :: InfoFlags -> [String] -> Action
 infoAction infoFlags extraArgs globalFlags = do
   let verbosity = fromFlag (infoVerbosity infoFlags)
   targets <- readUserTargets verbosity extraArgs
@@ -904,16 +947,17 @@
         }
       globalFlags' = savedGlobalFlags    config `mappend` globalFlags
   (comp, _, conf) <- configCompilerAuxEx configFlags
-  List.info verbosity
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+    List.info verbosity
        (configPackageDB' configFlags)
-       (globalRepos globalFlags')
+       repoContext
        comp
        conf
        globalFlags'
        infoFlags
        targets
 
-updateAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()
+updateAction :: Flag Verbosity -> [String] -> Action
 updateAction verbosityFlag extraArgs globalFlags = do
   unless (null extraArgs) $
     die $ "'update' doesn't take any extra arguments: " ++ unwords extraArgs
@@ -921,11 +965,11 @@
   (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity
                            (globalFlags { globalRequireSandbox = Flag False })
   let globalFlags' = savedGlobalFlags config `mappend` globalFlags
-  transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags'))
-  update transport verbosity (globalRepos globalFlags')
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+    update verbosity repoContext
 
 upgradeAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)
-              -> [String] -> GlobalFlags -> IO ()
+              -> [String] -> Action
 upgradeAction _ _ _ = die $
     "Use the 'cabal install' command instead of 'cabal upgrade'.\n"
  ++ "You can install the latest version of a package using 'cabal install'. "
@@ -939,7 +983,7 @@
  ++ "--upgrade-dependencies, it is recommended that you do not upgrade core "
  ++ "packages (e.g. by using appropriate --constraint= flags)."
 
-fetchAction :: FetchFlags -> [String] -> GlobalFlags -> IO ()
+fetchAction :: FetchFlags -> [String] -> Action
 fetchAction fetchFlags extraArgs globalFlags = do
   let verbosity = fromFlag (fetchVerbosity fetchFlags)
   targets <- readUserTargets verbosity extraArgs
@@ -947,13 +991,14 @@
   let configFlags  = savedConfigureFlags config
       globalFlags' = savedGlobalFlags config `mappend` globalFlags
   (comp, platform, conf) <- configCompilerAux' configFlags
-  fetch verbosity
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+    fetch verbosity
         (configPackageDB' configFlags)
-        (globalRepos globalFlags')
+        repoContext
         comp platform conf globalFlags' fetchFlags
         targets
 
-freezeAction :: FreezeFlags -> [String] -> GlobalFlags -> IO ()
+freezeAction :: FreezeFlags -> [String] -> Action
 freezeAction freezeFlags _extraArgs globalFlags = do
   let verbosity = fromFlag (freezeVerbosity freezeFlags)
   (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags
@@ -964,20 +1009,44 @@
   maybeWithSandboxPackageInfo verbosity configFlags globalFlags'
                               comp platform conf useSandbox $ \mSandboxPkgInfo ->
                               maybeWithSandboxDirOnSearchPath useSandbox $
+    withRepoContext verbosity globalFlags' $ \repoContext ->
       freeze verbosity
             (configPackageDB' configFlags)
-            (globalRepos globalFlags')
+            repoContext
             comp platform conf
             mSandboxPkgInfo
             globalFlags' freezeFlags
 
-uploadAction :: UploadFlags -> [String] -> GlobalFlags -> IO ()
+genBoundsAction :: FreezeFlags -> [String] -> GlobalFlags -> IO ()
+genBoundsAction freezeFlags _extraArgs globalFlags = do
+  let verbosity = fromFlag (freezeVerbosity freezeFlags)
+  (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags
+  let configFlags  = savedConfigureFlags config
+      globalFlags' = savedGlobalFlags config `mappend` globalFlags
+  (comp, platform, conf) <- configCompilerAux' configFlags
+
+  maybeWithSandboxPackageInfo verbosity configFlags globalFlags'
+                              comp platform conf useSandbox $ \mSandboxPkgInfo ->
+                              maybeWithSandboxDirOnSearchPath useSandbox $
+    withRepoContext verbosity globalFlags' $ \repoContext ->
+      genBounds verbosity
+            (configPackageDB' configFlags)
+            repoContext
+            comp platform conf
+            mSandboxPkgInfo
+            globalFlags' freezeFlags
+
+uploadAction :: UploadFlags -> [String] -> Action
 uploadAction uploadFlags extraArgs globalFlags = do
-  let verbosity = fromFlag (uploadVerbosity uploadFlags)
   config <- loadConfig verbosity (globalConfigFile globalFlags)
   let uploadFlags' = savedUploadFlags config `mappend` uploadFlags
       globalFlags' = savedGlobalFlags config `mappend` globalFlags
       tarfiles     = extraArgs
+  when (null tarfiles && not (fromFlag (uploadDoc uploadFlags'))) $
+    die "the 'upload' command expects at least one .tar.gz archive."
+  when (fromFlag (uploadCheck uploadFlags')
+        && fromFlag (uploadDoc uploadFlags')) $
+    die "--check and --doc cannot be used together."
   checkTarFiles extraArgs
   maybe_password <-
     case uploadPasswordCmd uploadFlags'
@@ -985,21 +1054,32 @@
                         getProgramInvocationOutput verbosity
                         (simpleProgramInvocation xs xss)
        _             -> pure $ flagToMaybe $ uploadPassword uploadFlags'
-  transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags'))
-  if fromFlag (uploadCheck uploadFlags')
-    then Upload.check transport verbosity tarfiles
-    else upload transport
-                verbosity
-                (globalRepos globalFlags')
-                (flagToMaybe $ uploadUsername uploadFlags')
-                maybe_password
-                tarfiles
-  where
+  withRepoContext verbosity globalFlags' $ \repoContext -> do
+    if fromFlag (uploadCheck uploadFlags')
+    then do
+      Upload.check verbosity repoContext tarfiles
+    else if fromFlag (uploadDoc uploadFlags')
+    then do
+      when (length tarfiles > 1) $
+       die $ "the 'upload' command can only upload documentation "
+             ++ "for one package at a time."
+      tarfile <- maybe (generateDocTarball config) return $ listToMaybe tarfiles
+      Upload.uploadDoc verbosity
+                       repoContext
+                       (flagToMaybe $ uploadUsername uploadFlags')
+                       maybe_password
+                       tarfile
+    else do
+      Upload.upload verbosity
+                    repoContext
+                    (flagToMaybe $ uploadUsername uploadFlags')
+                    maybe_password
+                    tarfiles
+    where
+    verbosity = fromFlag (uploadVerbosity uploadFlags)
     checkTarFiles tarfiles
-      | null tarfiles
-      = die "the 'upload' command expects one or more .tar.gz packages."
       | not (null otherFiles)
-      = die $ "the 'upload' command expects only .tar.gz packages: "
+      = die $ "the 'upload' command expects only .tar.gz archives: "
            ++ intercalate ", " otherFiles
       | otherwise = sequence_
                       [ do exists <- doesFileExist tarfile
@@ -1010,15 +1090,23 @@
             isTarGzFile file = case splitExtension file of
               (file', ".gz") -> takeExtension file' == ".tar"
               _              -> False
+    generateDocTarball config = do
+      notice verbosity
+        "No documentation tarball specified. Building documentation tarball..."
+      haddockAction (defaultHaddockFlags { haddockForHackage = Flag True })
+                    [] globalFlags
+      distPref <- findSavedDistPref config NoFlag
+      pkg <- fmap LBI.localPkgDescr (getPersistBuildConfig distPref)
+      return $ distPref </> display (packageId pkg) ++ "-docs" <.> "tar.gz"
 
-checkAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()
+checkAction :: Flag Verbosity -> [String] -> Action
 checkAction verbosityFlag extraArgs _globalFlags = do
   unless (null extraArgs) $
     die $ "'check' doesn't take any extra arguments: " ++ unwords extraArgs
   allOk <- Check.check (fromFlag verbosityFlag)
   unless allOk exitFailure
 
-formatAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()
+formatAction :: Flag Verbosity -> [String] -> Action
 formatAction verbosityFlag extraArgs _globalFlags = do
   let verbosity = fromFlag verbosityFlag
   path <- case extraArgs of
@@ -1029,18 +1117,19 @@
   -- Uses 'writeFileAtomic' under the hood.
   writeGenericPackageDescription path pkgDesc
 
-uninstallAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()
+uninstallAction :: Flag Verbosity -> [String] -> Action
 uninstallAction _verbosityFlag extraArgs _globalFlags = do
   let package = case extraArgs of
         p:_ -> p
         _   -> "PACKAGE_NAME"
-  die $ "This version of 'cabal-install' does not support the 'uninstall' operation. "
-        ++ "It will likely be implemented at some point in the future; in the meantime "
-        ++ "you're advised to use either 'ghc-pkg unregister " ++ package ++ "' or "
-        ++ "'cabal sandbox hc-pkg -- unregister " ++ package ++ "'."
+  die $ "This version of 'cabal-install' does not support the 'uninstall' "
+    ++ "operation. "
+    ++ "It will likely be implemented at some point in the future; "
+    ++ "in the meantime you're advised to use either 'ghc-pkg unregister "
+    ++ package ++ "' or 'cabal sandbox hc-pkg -- unregister " ++ package ++ "'."
 
 
-sdistAction :: (SDistFlags, SDistExFlags) -> [String] -> GlobalFlags -> IO ()
+sdistAction :: (SDistFlags, SDistExFlags) -> [String] -> Action
 sdistAction (sdistFlags, sdistExFlags) extraArgs globalFlags = do
   unless (null extraArgs) $
     die $ "'sdist' doesn't take any extra arguments: " ++ unwords extraArgs
@@ -1050,7 +1139,7 @@
   let sdistFlags' = sdistFlags { sDistDistPref = toFlag distPref }
   sdist sdistFlags' sdistExFlags
 
-reportAction :: ReportFlags -> [String] -> GlobalFlags -> IO ()
+reportAction :: ReportFlags -> [String] -> Action
 reportAction reportFlags extraArgs globalFlags = do
   unless (null extraArgs) $
     die $ "'report' doesn't take any extra arguments: " ++ unwords extraArgs
@@ -1060,11 +1149,12 @@
   let globalFlags' = savedGlobalFlags config `mappend` globalFlags
       reportFlags' = savedReportFlags config `mappend` reportFlags
 
-  Upload.report verbosity (globalRepos globalFlags')
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+   Upload.report verbosity repoContext
     (flagToMaybe $ reportUsername reportFlags')
     (flagToMaybe $ reportPassword reportFlags')
 
-runAction :: (BuildFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()
+runAction :: (BuildFlags, BuildExFlags) -> [String] -> Action
 runAction (buildFlags, buildExFlags) extraArgs globalFlags = do
   let verbosity   = fromFlagOrDefault normal (buildVerbosity buildFlags)
   let noAddSource = fromFlagOrDefault DontSkipAddSourceDepsCheck
@@ -1086,39 +1176,43 @@
   maybeWithSandboxDirOnSearchPath useSandbox $
     run verbosity lbi exe exeArgs
 
-getAction :: GetFlags -> [String] -> GlobalFlags -> IO ()
+getAction :: GetFlags -> [String] -> Action
 getAction getFlags extraArgs globalFlags = do
   let verbosity = fromFlag (getVerbosity getFlags)
   targets <- readUserTargets verbosity extraArgs
   (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity
                            (globalFlags { globalRequireSandbox = Flag False })
   let globalFlags' = savedGlobalFlags config `mappend` globalFlags
-  get verbosity
-    (globalRepos (savedGlobalFlags config))
+  withRepoContext verbosity (savedGlobalFlags config) $ \repoContext ->
+   get verbosity
+    repoContext
     globalFlags'
     getFlags
     targets
 
-unpackAction :: GetFlags -> [String] -> GlobalFlags -> IO ()
+unpackAction :: GetFlags -> [String] -> Action
 unpackAction getFlags extraArgs globalFlags = do
   getAction getFlags extraArgs globalFlags
 
-initAction :: InitFlags -> [String] -> GlobalFlags -> IO ()
-initAction initFlags _extraArgs globalFlags = do
+initAction :: InitFlags -> [String] -> Action
+initAction initFlags extraArgs globalFlags = do
+  when (extraArgs /= []) $
+    die $ "'init' doesn't take any extra arguments: " ++ unwords extraArgs
   let verbosity = fromFlag (initVerbosity initFlags)
   (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity
                            (globalFlags { globalRequireSandbox = Flag False })
   let configFlags  = savedConfigureFlags config
   let globalFlags' = savedGlobalFlags    config `mappend` globalFlags
   (comp, _, conf) <- configCompilerAux' configFlags
-  initCabal verbosity
+  withRepoContext verbosity globalFlags' $ \repoContext ->
+    initCabal verbosity
             (configPackageDB' configFlags)
-            (globalRepos globalFlags')
+            repoContext
             comp
             conf
             initFlags
 
-sandboxAction :: SandboxFlags -> [String] -> GlobalFlags -> IO ()
+sandboxAction :: SandboxFlags -> [String] -> Action
 sandboxAction sandboxFlags extraArgs globalFlags = do
   let verbosity = fromFlag (sandboxVerbosity sandboxFlags)
   case extraArgs of
@@ -1153,29 +1247,35 @@
   where
     noExtraArgs = (<1) . length
 
-execAction :: ExecFlags -> [String] -> GlobalFlags -> IO ()
+execAction :: ExecFlags -> [String] -> Action
 execAction execFlags extraArgs globalFlags = do
   let verbosity = fromFlag (execVerbosity execFlags)
   (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags
   let configFlags = savedConfigureFlags config
-  (comp, platform, conf) <- configCompilerAux' configFlags
+  (comp, platform, conf) <- getPersistOrConfigCompiler configFlags
   exec verbosity useSandbox comp platform conf extraArgs
 
-userConfigAction :: UserConfigFlags -> [String] -> GlobalFlags -> IO ()
+userConfigAction :: UserConfigFlags -> [String] -> Action
 userConfigAction ucflags extraArgs globalFlags = do
   let verbosity = fromFlag (userConfigVerbosity ucflags)
+      force     = fromFlag (userConfigForce ucflags)
   case extraArgs of
+    ("init":_) -> do
+      path       <- configFile
+      fileExists <- doesFileExist path
+      if (not fileExists || (fileExists && force))
+      then createDefaultConfigFile verbosity path
+      else die $ path ++ " already exists."
     ("diff":_) -> mapM_ putStrLn =<< userConfigDiff globalFlags
     ("update":_) -> userConfigUpdate verbosity globalFlags
     -- Error handling.
     [] -> die $ "Please specify a subcommand (see 'help user-config')"
     _  -> die $ "Unknown 'user-config' subcommand: " ++ unwords extraArgs
-
+  where configFile = getConfigFilePath (globalConfigFile globalFlags)
 
 -- | See 'Distribution.Client.Install.withWin32SelfUpgrade' for details.
 --
-win32SelfUpgradeAction :: Win32SelfUpgradeFlags -> [String] -> GlobalFlags
-                          -> IO ()
+win32SelfUpgradeAction :: Win32SelfUpgradeFlags -> [String] -> Action
 win32SelfUpgradeAction selfUpgradeFlags (pid:path:_extraArgs) _globalFlags = do
   let verbosity = fromFlag (win32SelfUpgradeVerbosity selfUpgradeFlags)
   Win32SelfUpgrade.deleteOldExeFile verbosity (read pid) path
@@ -1184,7 +1284,7 @@
 -- | Used as an entry point when cabal-install needs to invoke itself
 -- as a setup script. This can happen e.g. when doing parallel builds.
 --
-actAsSetupAction :: ActAsSetupFlags -> [String] -> GlobalFlags -> IO ()
+actAsSetupAction :: ActAsSetupFlags -> [String] -> Action
 actAsSetupAction actAsSetupFlags args _globalFlags =
   let bt = fromFlag (actAsSetupBuildType actAsSetupFlags)
   in case bt of
@@ -1194,3 +1294,13 @@
     Make      -> Make.defaultMainArgs args
     Custom               -> error "actAsSetupAction Custom"
     (UnknownBuildType _) -> error "actAsSetupAction UnknownBuildType"
+
+manpageAction :: [CommandSpec action] -> Flag Verbosity -> [String] -> Action
+manpageAction commands _ extraArgs _ = do
+  unless (null extraArgs) $
+    die $ "'manpage' doesn't take any extra arguments: " ++ unwords extraArgs
+  pname <- getProgName
+  let cabalCmd = if takeExtension pname == ".exe"
+                 then dropExtension pname
+                 else pname
+  putStrLn $ manpage cabalCmd commands
diff --git a/cabal/cabal-install/Setup.hs b/cabal/cabal-install/Setup.hs
--- a/cabal/cabal-install/Setup.hs
+++ b/cabal/cabal-install/Setup.hs
@@ -1,2 +1,63 @@
-import Distribution.Simple
-main = defaultMain
+import Distribution.PackageDescription ( PackageDescription )
+import Distribution.Simple ( defaultMainWithHooks
+                           , simpleUserHooks
+                           , postBuild
+                           , postCopy
+                           , postInst
+                           )
+import Distribution.Simple.InstallDirs ( mandir
+                                       , CopyDest (NoCopyDest)
+                                       )
+import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..)
+                                          , absoluteInstallDirs
+                                          )
+import Distribution.Simple.Utils ( copyFiles
+                                 , notice )
+import Distribution.Simple.Setup ( buildVerbosity
+                                 , copyDest
+                                 , copyVerbosity
+                                 , fromFlag
+                                 , installVerbosity
+                                 )
+import Distribution.Verbosity ( Verbosity )
+
+import System.IO ( openFile
+                 , IOMode (WriteMode)
+                 )
+import System.Process ( runProcess )
+import System.FilePath ( (</>) )
+
+-- WARNING to editors of this file:
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- At this moment (Cabal 1.23), whatever you write here must be
+-- compatible with ALL Cabal libraries which we support bootstrapping
+-- with.  This is because pre-setup-depends versions of cabal-install will
+-- build Setup.hs against the version of Cabal which MATCHES the library
+-- that cabal-install was built against.  There is no way of overriding
+-- this behavior without bumping the required 'cabal-version' in our
+-- Cabal file.  Travis will let you know if we fail to install from
+-- tarball!
+
+main :: IO ()
+main = defaultMainWithHooks $ simpleUserHooks
+  { postBuild = \ _ flags _ lbi ->
+      buildManpage lbi (fromFlag $ buildVerbosity flags)
+  , postCopy = \ _ flags pkg lbi ->
+      installManpage pkg lbi (fromFlag $ copyVerbosity flags) (fromFlag $ copyDest flags)
+  , postInst = \ _ flags pkg lbi ->
+      installManpage pkg lbi (fromFlag $ installVerbosity flags) NoCopyDest
+  }
+
+buildManpage :: LocalBuildInfo -> Verbosity -> IO ()
+buildManpage lbi verbosity = do
+  let cabal = buildDir lbi </> "cabal/cabal"
+      manpage = buildDir lbi </> "cabal/cabal.1"
+  manpageHandle <- openFile manpage WriteMode
+  notice verbosity ("Generating manual page " ++ manpage ++ " ...")
+  _ <- runProcess cabal ["manpage"] Nothing Nothing Nothing (Just manpageHandle) Nothing
+  return ()
+
+installManpage :: PackageDescription -> LocalBuildInfo -> Verbosity -> CopyDest -> IO ()
+installManpage pkg lbi verbosity copy = do
+  let destDir = mandir (absoluteInstallDirs pkg lbi copy) </> "man1"
+  copyFiles verbosity destDir [(buildDir lbi </> "cabal", "cabal.1")]
diff --git a/cabal/cabal-install/bash-completion/cabal b/cabal/cabal-install/bash-completion/cabal
--- a/cabal/cabal-install/bash-completion/cabal
+++ b/cabal/cabal-install/bash-completion/cabal
@@ -53,8 +53,27 @@
     done
 }
 
+__cabal_has_doubledash ()
+{
+    local c=1
+    # Ignore the last word, because it is replaced anyways.
+    # This allows expansion for flags on "cabal foo --",
+    # but does not try to complete after "cabal foo -- ".
+    local n=$((${#COMP_WORDS[@]} - 1))
+    while [ $c -lt $n ]; do
+        if [ "--" = "${COMP_WORDS[c]}" ]; then
+            return 0
+        fi
+        ((c++))
+    done
+    return 1
+}
+
 _cabal()
 {
+    # no completion past cabal arguments.
+    __cabal_has_doubledash && return
+
     # get the word currently being completed
     local cur
     cur=${COMP_WORDS[$COMP_CWORD]}
diff --git a/cabal/cabal-install/bootstrap.sh b/cabal/cabal-install/bootstrap.sh
--- a/cabal/cabal-install/bootstrap.sh
+++ b/cabal/cabal-install/bootstrap.sh
@@ -179,36 +179,52 @@
 # The version regex says what existing installed versions are ok.
 PARSEC_VER="3.1.9";    PARSEC_VER_REGEXP="[3]\.[01]\."
                        # >= 3.0 && < 3.2
-DEEPSEQ_VER="1.4.1.1"; DEEPSEQ_VER_REGEXP="1\.[1-9]\."
+DEEPSEQ_VER="1.4.1.2"; DEEPSEQ_VER_REGEXP="1\.[1-9]\."
                        # >= 1.1 && < 2
-BINARY_VER="0.7.2.3";  BINARY_VER_REGEXP="[0]\.[7]\."
-                       # == 0.7.*
-TEXT_VER="1.2.1.1";    TEXT_VER_REGEXP="((1\.[012]\.)|(0\.([2-9]|(1[0-1]))\.))"
+BINARY_VER="0.8.2.1";  BINARY_VER_REGEXP="[0]\.[78]\."
+                       # >= 0.7 && < 0.9
+TEXT_VER="1.2.2.1";    TEXT_VER_REGEXP="((1\.[012]\.)|(0\.([2-9]|(1[0-1]))\.))"
                        # >= 0.2 && < 1.3
-NETWORK_VER="2.6.2.0"; NETWORK_VER_REGEXP="2\.[0-6]\."
+NETWORK_VER="2.6.2.1"; NETWORK_VER_REGEXP="2\.[0-6]\."
                        # >= 2.0 && < 2.7
-NETWORK_URI_VER="2.6.0.3"; NETWORK_URI_VER_REGEXP="2\.6\."
+NETWORK_URI_VER="2.6.1.0"; NETWORK_URI_VER_REGEXP="2\.6\."
                        # >= 2.6 && < 2.7
-CABAL_VER="1.23.0.0";  CABAL_VER_REGEXP="1\.23\."
-                       # >= 1.23 && < 1.24
-TRANS_VER="0.4.2.0";   TRANS_VER_REGEXP="0\.[4]\."
-                       # >= 0.2.* && < 0.5
+CABAL_VER="1.25.0.0";  CABAL_VER_REGEXP="1\.25\.[0-9]"
+                       # >= 1.25 && < 1.26
+TRANS_VER="0.5.2.0";   TRANS_VER_REGEXP="0\.[45]\."
+                       # >= 0.2.* && < 0.6
 MTL_VER="2.2.1";       MTL_VER_REGEXP="[2]\."
                        #  >= 2.0 && < 3
-HTTP_VER="4000.2.19";  HTTP_VER_REGEXP="4000\.2\.([5-9]|1[0-9]|2[0-9])"
-                       # >= 4000.2.5 < 4000.3
-ZLIB_VER="0.5.4.2";    ZLIB_VER_REGEXP="0\.[45]\."
-                       # == 0.4.* || == 0.5.*
-TIME_VER="1.5.0.1"     TIME_VER_REGEXP="1\.[12345]\.?"
-                       # >= 1.1 && < 1.6
+HTTP_VER="4000.3.3";   HTTP_VER_REGEXP="4000\.(2\.([5-9]|1[0-9]|2[0-9])|3\.?)"
+                       # >= 4000.2.5 < 4000.4
+ZLIB_VER="0.6.1.1";    ZLIB_VER_REGEXP="(0\.5\.([3-9]|1[0-9])|0\.6)"
+                       # >= 0.5.3 && <= 0.7
+TIME_VER="1.6"         TIME_VER_REGEXP="1\.[1-6]\.?"
+                       # >= 1.1 && < 1.7
 RANDOM_VER="1.1"       RANDOM_VER_REGEXP="1\.[01]\.?"
                        # >= 1 && < 1.2
-STM_VER="2.4.4";       STM_VER_REGEXP="2\."
+STM_VER="2.4.4.1";     STM_VER_REGEXP="2\."
                        # == 2.*
+ASYNC_VER="2.1.0";     ASYNC_VER_REGEXP="2\."
+                       # 2.*
 OLD_TIME_VER="1.1.0.3"; OLD_TIME_VER_REGEXP="1\.[01]\.?"
                        # >=1.0.0.0 && <1.2
 OLD_LOCALE_VER="1.0.0.7"; OLD_LOCALE_VER_REGEXP="1\.0\.?"
                        # >=1.0.0.0 && <1.1
+BYTEABLE_VER="0.1.1";  BYTEABLE_VER_REGEXP="0\.?"
+                       # 0.1.1
+CRYPTOHASH_VER="0.11.7"; CRYPTOHASH_VER_REGEXP="0\.11\.?"
+                       # 0.11.*
+ED25519_VER="0.0.5.0"; ED25519_VER_REGEXP="0\.0\.?"
+                       # 0.0.*
+HACKAGE_SECURITY_VER="0.5.0.2"; HACKAGE_SECURITY_VER_REGEXP="0\.5\.?"
+                       # >= 0.5 && < 0.6
+TAR_VER="0.5.0.1";     TAR_VER_REGEXP="0\.5\.([1-9]|1[0-9]|0\.1)\.?"
+                       # >= 0.5.0.1  && < 0.6
+BASE64_BYTESTRING_VER="1.0.0.1";    BASE64_BYTESTRING_REGEXP="1\."
+                                    # >=1.0
+HASHABLE_VER="1.2.4.0"; HASHABLE_VER_REGEXP="1\."
+                       # 1.*
 
 HACKAGE_URL="https://hackage.haskell.org/package"
 
@@ -255,24 +271,32 @@
   PKG=$1
   VER=$2
 
-  URL=${HACKAGE_URL}/${PKG}-${VER}/${PKG}-${VER}.tar.gz
+  URL_PKG=${HACKAGE_URL}/${PKG}-${VER}/${PKG}-${VER}.tar.gz
+  URL_PKGDESC=${HACKAGE_URL}/${PKG}-${VER}/${PKG}.cabal
   if which ${CURL} > /dev/null
   then
     # TODO: switch back to resuming curl command once
     #       https://github.com/haskell/hackage-server/issues/111 is resolved
-    #${CURL} -L --fail -C - -O ${URL} || die "Failed to download ${PKG}."
-    ${CURL} -L --fail -O ${URL} || die "Failed to download ${PKG}."
+    #${CURL} -L --fail -C - -O ${URL_PKG} || die "Failed to download ${PKG}."
+    ${CURL} -L --fail -O ${URL_PKG} || die "Failed to download ${PKG}."
+    ${CURL} -L --fail -O ${URL_PKGDESC} \
+        || die "Failed to download '${PKG}.cabal'."
   elif which ${WGET} > /dev/null
   then
-    ${WGET} -c ${URL} || die "Failed to download ${PKG}."
+    ${WGET} -c ${URL_PKG} || die "Failed to download ${PKG}."
+    ${WGET} -c ${URL_PKGDESC} || die "Failed to download '${PKG}.cabal'."
   elif which ${FETCH} > /dev/null
     then
-      ${FETCH} ${URL} || die "Failed to download ${PKG}."
+      ${FETCH} ${URL_PKG} || die "Failed to download ${PKG}."
+      ${FETCH} ${URL_PKGDESC} || die "Failed to download '${PKG}.cabal'."
   else
     die "Failed to find a downloader. 'curl', 'wget' or 'fetch' is required."
   fi
   [ -f "${PKG}-${VER}.tar.gz" ] ||
-     die "Downloading ${URL} did not create ${PKG}-${VER}.tar.gz"
+     die "Downloading ${URL_PKG} did not create ${PKG}-${VER}.tar.gz"
+  [ -f "${PKG}.cabal" ] ||
+     die "Downloading ${URL_PKGDESC} did not create ${PKG}.cabal"
+  mv "${PKG}.cabal" "${PKG}.cabal.hackage"
 }
 
 unpack_pkg () {
@@ -282,6 +306,7 @@
   rm -rf "${PKG}-${VER}.tar" "${PKG}-${VER}"
   ${GZIP_PROGRAM} -d < "${PKG}-${VER}.tar.gz" | ${TAR} -xf -
   [ -d "${PKG}-${VER}" ] || die "Failed to unpack ${PKG}-${VER}.tar.gz"
+  cp "${PKG}.cabal.hackage" "${PKG}-${VER}/${PKG}.cabal"
 }
 
 install_pkg () {
@@ -307,7 +332,8 @@
 
   if [ ! ${NO_DOCUMENTATION} ]
   then
-    if echo "${PKG}-${VER}" | egrep ${NO_DOCS_PACKAGES_VER_REGEXP} > /dev/null 2>&1
+    if echo "${PKG}-${VER}" | egrep ${NO_DOCS_PACKAGES_VER_REGEXP} \
+        > /dev/null 2>&1
     then
       echo "Skipping documentation for the ${PKG} package."
     else
@@ -356,8 +382,10 @@
     do_pkg   "network-uri" ${NETWORK_URI_VER} ${NETWORK_URI_VER_REGEXP}
   else
     # Use network < 2.6 && network-uri < 2.6
-    info_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} ${NETWORK_URI_DUMMY_VER_REGEXP}
-    do_pkg   "network-uri" ${NETWORK_URI_DUMMY_VER} ${NETWORK_URI_DUMMY_VER_REGEXP}
+    info_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} \
+        ${NETWORK_URI_DUMMY_VER_REGEXP}
+    do_pkg   "network-uri" ${NETWORK_URI_DUMMY_VER} \
+        ${NETWORK_URI_DUMMY_VER_REGEXP}
   fi
 }
 
@@ -373,11 +401,21 @@
 info_pkg "parsec"       ${PARSEC_VER}  ${PARSEC_VER_REGEXP}
 info_pkg "network"      ${NETWORK_VER} ${NETWORK_VER_REGEXP}
 info_pkg "old-locale"   ${OLD_LOCALE_VER} ${OLD_LOCALE_VER_REGEXP}
-info_pkg "old-time"     ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP}
+info_pkg "old-time"     ${OLD_TIME_VER}   ${OLD_TIME_VER_REGEXP}
 info_pkg "HTTP"         ${HTTP_VER}    ${HTTP_VER_REGEXP}
 info_pkg "zlib"         ${ZLIB_VER}    ${ZLIB_VER_REGEXP}
 info_pkg "random"       ${RANDOM_VER}  ${RANDOM_VER_REGEXP}
 info_pkg "stm"          ${STM_VER}     ${STM_VER_REGEXP}
+info_pkg "async"        ${ASYNC_VER}   ${ASYNC_VER_REGEXP}
+info_pkg "byteable"          ${BYTEABLE_VER}         ${BYTEABLE_VER_REGEXP}
+info_pkg "cryptohash"        ${CRYPTOHASH_VER}       ${CRYPTOHASH_VER_REGEXP}
+info_pkg "ed25519"           ${ED25519_VER}          ${ED25519_VER_REGEXP}
+info_pkg "tar"               ${TAR_VER}              ${TAR_VER_REGEXP}
+info_pkg "base64-bytestring" ${BASE64_BYTESTRING_VER} \
+    ${BASE64_BYTESTRING_VER_REGEXP}
+info_pkg "hashable"          ${HASHABLE_VER}          ${HASHABLE_VER_REGEXP}
+info_pkg "hackage-security"  ${HACKAGE_SECURITY_VER} \
+    ${HACKAGE_SECURITY_VER_REGEXP}
 
 do_pkg   "deepseq"      ${DEEPSEQ_VER} ${DEEPSEQ_VER_REGEXP}
 do_pkg   "binary"       ${BINARY_VER}  ${BINARY_VER_REGEXP}
@@ -393,11 +431,22 @@
 do_network_uri_pkg
 
 do_pkg   "old-locale"   ${OLD_LOCALE_VER} ${OLD_LOCALE_VER_REGEXP}
-do_pkg   "old-time"     ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP}
-do_pkg   "HTTP"         ${HTTP_VER}    ${HTTP_VER_REGEXP}
-do_pkg   "zlib"         ${ZLIB_VER}    ${ZLIB_VER_REGEXP}
-do_pkg   "random"       ${RANDOM_VER}  ${RANDOM_VER_REGEXP}
-do_pkg   "stm"          ${STM_VER}     ${STM_VER_REGEXP}
+do_pkg   "old-time"     ${OLD_TIME_VER}   ${OLD_TIME_VER_REGEXP}
+do_pkg   "HTTP"         ${HTTP_VER}       ${HTTP_VER_REGEXP}
+do_pkg   "zlib"         ${ZLIB_VER}       ${ZLIB_VER_REGEXP}
+do_pkg   "random"       ${RANDOM_VER}     ${RANDOM_VER_REGEXP}
+do_pkg   "stm"          ${STM_VER}        ${STM_VER_REGEXP}
+do_pkg   "async"        ${ASYNC_VER}      ${ASYNC_VER_REGEXP}
+do_pkg   "byteable"          ${BYTEABLE_VER}         ${BYTEABLE_VER_REGEXP}
+do_pkg   "cryptohash"        ${CRYPTOHASH_VER}       ${CRYPTOHASH_VER_REGEXP}
+do_pkg   "ed25519"           ${ED25519_VER}          ${ED25519_VER_REGEXP}
+do_pkg   "tar"               ${TAR_VER}              ${TAR_VER_REGEXP}
+do_pkg   "base64-bytestring" ${BASE64_BYTESTRING_VER} \
+    ${BASE64_BYTESTRING_VER_REGEXP}
+do_pkg   "hashable"          ${HASHABLE_VER}         ${HASHABLE_VER_REGEXP}
+do_pkg   "hackage-security"  ${HACKAGE_SECURITY_VER} \
+    ${HACKAGE_SECURITY_VER_REGEXP}
+
 
 install_pkg "cabal-install"
 
diff --git a/cabal/cabal-install/cabal-install.cabal b/cabal/cabal-install/cabal-install.cabal
--- a/cabal/cabal-install/cabal-install.cabal
+++ b/cabal/cabal-install/cabal-install.cabal
@@ -1,5 +1,5 @@
 Name:               cabal-install
-Version:            1.23.0.0
+Version:            1.25.0.0
 Synopsis:           The command-line interface for Cabal and Hackage.
 Description:
     The \'cabal\' command-line program simplifies the process of managing
@@ -21,13 +21,80 @@
                     2007 Isaac Potoczny-Jones <ijones@syntaxpolice.org>
                     2007-2012 Duncan Coutts <duncan@community.haskell.org>
 Category:           Distribution
-Build-type:         Simple
+Build-type:         Custom
 Cabal-Version:      >= 1.10
 Extra-Source-Files:
   README.md bash-completion/cabal bootstrap.sh changelog
+  tests/README.md
 
-  -- Generated with '../Cabal/misc/gen-extra-source-files.sh | sort'
-  tests/PackageTests/Freeze/my.cabal
+  -- Generated with '../Cabal/misc/gen-extra-source-files.sh'
+  -- Do NOT edit this section manually; instead, run the script.
+  -- BEGIN gen-extra-source-files
+  tests/IntegrationTests/common.sh
+  tests/IntegrationTests/custom/plain.err
+  tests/IntegrationTests/custom/plain.sh
+  tests/IntegrationTests/custom/plain/A.hs
+  tests/IntegrationTests/custom/plain/Setup.hs
+  tests/IntegrationTests/custom/plain/plain.cabal
+  tests/IntegrationTests/exec/Foo.hs
+  tests/IntegrationTests/exec/My.hs
+  tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out
+  tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh
+  tests/IntegrationTests/exec/auto_configures_on_exec.out
+  tests/IntegrationTests/exec/auto_configures_on_exec.sh
+  tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out
+  tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh
+  tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh
+  tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh
+  tests/IntegrationTests/exec/exit_with_failure_without_args.err
+  tests/IntegrationTests/exec/exit_with_failure_without_args.sh
+  tests/IntegrationTests/exec/my.cabal
+  tests/IntegrationTests/exec/runs_given_command.out
+  tests/IntegrationTests/exec/runs_given_command.sh
+  tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh
+  tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh
+  tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh
+  tests/IntegrationTests/freeze/does_not_freeze_self.sh
+  tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh
+  tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh
+  tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh
+  tests/IntegrationTests/freeze/freezes_direct_dependencies.sh
+  tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh
+  tests/IntegrationTests/freeze/my.cabal
+  tests/IntegrationTests/freeze/runs_without_error.sh
+  tests/IntegrationTests/internal-libs/internal_lib_basic.sh
+  tests/IntegrationTests/internal-libs/internal_lib_shadow.sh
+  tests/IntegrationTests/internal-libs/p/Foo.hs
+  tests/IntegrationTests/internal-libs/p/p.cabal
+  tests/IntegrationTests/internal-libs/p/p/P.hs
+  tests/IntegrationTests/internal-libs/p/q/Q.hs
+  tests/IntegrationTests/internal-libs/q/Q.hs
+  tests/IntegrationTests/internal-libs/q/q.cabal
+  tests/IntegrationTests/manpage/outputs_manpage.sh
+  tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh
+  tests/IntegrationTests/multiple-source/p/Setup.hs
+  tests/IntegrationTests/multiple-source/p/p.cabal
+  tests/IntegrationTests/multiple-source/q/Setup.hs
+  tests/IntegrationTests/multiple-source/q/q.cabal
+  tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err
+  tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh
+  tests/IntegrationTests/sandbox-sources/p/Setup.hs
+  tests/IntegrationTests/sandbox-sources/p/p.cabal
+  tests/IntegrationTests/sandbox-sources/q/Setup.hs
+  tests/IntegrationTests/sandbox-sources/q/q.cabal
+  tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh
+  tests/IntegrationTests/sandbox-sources/report_success_removing_source.out
+  tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh
+  tests/IntegrationTests/user-config/common.sh
+  tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err
+  tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh
+  tests/IntegrationTests/user-config/overwrites_with_f.out
+  tests/IntegrationTests/user-config/overwrites_with_f.sh
+  tests/IntegrationTests/user-config/runs_without_error.out
+  tests/IntegrationTests/user-config/runs_without_error.sh
+  tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out
+  tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh
+  -- END gen-extra-source-files
 
 source-repository head
   type:     git
@@ -45,6 +112,11 @@
 executable cabal
     main-is:        Main.hs
     ghc-options:    -Wall -fwarn-tabs
+    if impl(ghc >= 8.0)
+        ghc-options: -Wcompat
+                     -Wnoncanonical-monad-instances
+                     -Wnoncanonical-monadfail-instances
+
     other-modules:
         Distribution.Client.BuildReports.Anonymous
         Distribution.Client.BuildReports.Storage
@@ -64,6 +136,7 @@
         Distribution.Client.Dependency.Modular.Builder
         Distribution.Client.Dependency.Modular.Configured
         Distribution.Client.Dependency.Modular.ConfiguredConversion
+        Distribution.Client.Dependency.Modular.Cycles
         Distribution.Client.Dependency.Modular.Dependency
         Distribution.Client.Dependency.Modular.Explore
         Distribution.Client.Dependency.Modular.Flag
@@ -79,11 +152,16 @@
         Distribution.Client.Dependency.Modular.Tree
         Distribution.Client.Dependency.Modular.Validate
         Distribution.Client.Dependency.Modular.Version
+        Distribution.Client.DistDirLayout
         Distribution.Client.Exec
         Distribution.Client.Fetch
         Distribution.Client.FetchUtils
+        Distribution.Client.FileMonitor
         Distribution.Client.Freeze
+        Distribution.Client.GenBounds
         Distribution.Client.Get
+        Distribution.Client.Glob
+        Distribution.Client.GlobalFlags
         Distribution.Client.GZipUtils
         Distribution.Client.Haddock
         Distribution.Client.HttpUtils
@@ -97,16 +175,26 @@
         Distribution.Client.InstallSymlink
         Distribution.Client.JobControl
         Distribution.Client.List
+        Distribution.Client.Manpage
+        Distribution.Client.PackageHash
         Distribution.Client.PackageIndex
         Distribution.Client.PackageUtils
         Distribution.Client.ParseUtils
+        Distribution.Client.PkgConfigDb
         Distribution.Client.PlanIndex
+        Distribution.Client.ProjectBuilding
+        Distribution.Client.ProjectConfig
+        Distribution.Client.ProjectConfig.Types
+        Distribution.Client.ProjectConfig.Legacy
+        Distribution.Client.ProjectPlanning
         Distribution.Client.Run
+        Distribution.Client.RebuildMonad
         Distribution.Client.Sandbox
         Distribution.Client.Sandbox.Index
         Distribution.Client.Sandbox.PackageEnvironment
         Distribution.Client.Sandbox.Timestamp
         Distribution.Client.Sandbox.Types
+        Distribution.Client.Security.HTTP
         Distribution.Client.Setup
         Distribution.Client.SetupWrapper
         Distribution.Client.SrcDist
@@ -129,48 +217,54 @@
     -- NOTE: when updating build-depends, don't forget to update version regexps
     -- in bootstrap.sh.
     build-depends:
-        array      >= 0.1      && < 0.6,
-        base       >= 4.3      && < 5,
+        async      >= 2.0      && < 3,
+        array      >= 0.4      && < 0.6,
+        base       >= 4.5      && < 5,
+        binary     >= 0.5      && < 0.9,
+        byteable   >= 0.1      && < 0.2,
         bytestring >= 0.9      && < 1,
-        Cabal      >= 1.23     && < 1.24,
-        containers >= 0.1      && < 0.6,
-        filepath   >= 1.0      && < 1.5,
-        HTTP       >= 4000.1.5 && < 4000.3,
+        Cabal      >= 1.25     && < 1.26,
+        containers >= 0.4      && < 0.6,
+        cryptohash >= 0.11     && < 0.12,
+        filepath   >= 1.3      && < 1.5,
+        hashable   >= 1.0      && < 2,
+        HTTP       >= 4000.1.5 && < 4000.4,
         mtl        >= 2.0      && < 3,
-        pretty     >= 1        && < 1.2,
+        pretty     >= 1.1      && < 1.2,
         random     >= 1        && < 1.2,
         stm        >= 2.0      && < 3,
-        time       >= 1.1      && < 1.6,
-        zlib       >= 0.5.3    && < 0.7
+        tar        >= 0.5.0.1  && < 0.6,
+        time       >= 1.4      && < 1.7,
+        zlib       >= 0.5.3    && < 0.7,
+        hackage-security >= 0.5 && < 0.6
 
     if flag(old-directory)
-      build-depends: directory >= 1 && < 1.2, old-time >= 1 && < 1.2,
+      build-depends: directory >= 1.1 && < 1.2, old-time >= 1 && < 1.2,
                      process   >= 1.0.1.1  && < 1.1.0.2
     else
       build-depends: directory >= 1.2 && < 1.3,
-                     process   >= 1.1.0.2  && < 1.4
+                     process   >= 1.1.0.2  && < 1.5
 
     -- NOTE: you MUST include the network dependency even when network-uri
     -- is pulled in, otherwise the constraint solver doesn't have enough
     -- information
     if flag(network-uri)
-      build-depends: network-uri >= 2.6, network >= 2.6
+      build-depends: network-uri >= 2.6 && < 2.7, network >= 2.6 && < 2.7
     else
       build-depends: network     >= 2.4 && < 2.6
 
+    -- Needed for GHC.Generics before GHC 7.6
+    if impl(ghc < 7.6)
+      build-depends: ghc-prim >= 0.2 && < 0.3
+
     if os(windows)
       build-depends: Win32 >= 2 && < 3
-      cpp-options: -DWIN32
     else
-      build-depends: unix >= 2.0 && < 2.8
+      build-depends: unix >= 2.5 && < 2.8
 
-    if arch(arm) && impl(ghc < 7.6)
-       -- older ghc on arm does not support -threaded
-       cc-options:  -DCABAL_NO_THREADED
-    else
-       ghc-options: -threaded
+    if !(arch(arm) && impl(ghc < 7.6))
+      ghc-options: -threaded
 
-    c-sources: cbits/getnumcores.c
     default-language: Haskell2010
 
 -- Small, fast running tests.
@@ -180,13 +274,21 @@
   hs-source-dirs: tests, .
   ghc-options: -Wall -fwarn-tabs
   other-modules:
+    UnitTests.Distribution.Client.ArbitraryInstances
     UnitTests.Distribution.Client.Targets
+    UnitTests.Distribution.Client.Compat.Time
     UnitTests.Distribution.Client.Dependency.Modular.PSQ
     UnitTests.Distribution.Client.Dependency.Modular.Solver
     UnitTests.Distribution.Client.Dependency.Modular.DSL
+    UnitTests.Distribution.Client.FileMonitor
+    UnitTests.Distribution.Client.Glob
     UnitTests.Distribution.Client.GZipUtils
     UnitTests.Distribution.Client.Sandbox
+    UnitTests.Distribution.Client.Sandbox.Timestamp
+    UnitTests.Distribution.Client.Tar
     UnitTests.Distribution.Client.UserConfig
+    UnitTests.Distribution.Client.ProjectConfig
+    UnitTests.Options
   build-depends:
         base,
         array,
@@ -198,16 +300,20 @@
         process,
         directory,
         filepath,
+        hashable,
         stm,
+        tar,
         time,
         HTTP,
         zlib,
+        binary,
         random,
+        hackage-security,
         tasty,
         tasty-hunit,
         tasty-quickcheck,
         tagged,
-        QuickCheck >= 2.5
+        QuickCheck >= 2.8.2
 
   if flag(old-directory)
     build-depends: old-time
@@ -217,52 +323,101 @@
   else
     build-depends: network-uri < 2.6, network < 2.6
 
+  if impl(ghc < 7.6)
+    build-depends: ghc-prim >= 0.2 && < 0.3
+
   if os(windows)
     build-depends: Win32
-    cpp-options: -DWIN32
   else
     build-depends: unix
 
-  if arch(arm)
-    cc-options:  -DCABAL_NO_THREADED
+  if !(arch(arm) && impl(ghc < 7.6))
+    ghc-options: -threaded
+  default-language: Haskell2010
+
+-- Slow solver tests
+Test-Suite solver-quickcheck
+  type: exitcode-stdio-1.0
+  main-is: SolverQuickCheck.hs
+  hs-source-dirs: tests, .
+  ghc-options: -Wall -fwarn-tabs
+  other-modules:
+    UnitTests.Distribution.Client.Dependency.Modular.DSL
+    UnitTests.Distribution.Client.Dependency.Modular.QuickCheck
+  build-depends:
+        base,
+        array,
+        bytestring,
+        Cabal,
+        containers,
+        mtl,
+        pretty,
+        process,
+        directory,
+        filepath,
+        hashable,
+        stm,
+        tar,
+        time,
+        HTTP,
+        zlib,
+        binary,
+        random,
+        hackage-security,
+        tasty,
+        tasty-quickcheck,
+        QuickCheck >= 2.8.2,
+        pretty-show
+
+  if flag(old-directory)
+    build-depends: old-time
+
+  if flag(network-uri)
+    build-depends: network-uri >= 2.6, network >= 2.6
   else
+    build-depends: network-uri < 2.6, network < 2.6
+
+  if impl(ghc < 7.6)
+    build-depends: ghc-prim >= 0.2 && < 0.3
+
+  if os(windows)
+    build-depends: Win32
+  else
+    build-depends: unix
+
+  if !(arch(arm) && impl(ghc < 7.6))
     ghc-options: -threaded
   default-language: Haskell2010
 
--- Large, system tests that build packages.
-test-suite package-tests
+test-suite integration-tests
   type: exitcode-stdio-1.0
   hs-source-dirs: tests
-  main-is: PackageTests.hs
-  other-modules:
-    PackageTests.Exec.Check
-    PackageTests.Freeze.Check
-    PackageTests.MultipleSource.Check
-    PackageTests.PackageTester
+  main-is: IntegrationTests.hs
   build-depends:
     Cabal,
-    QuickCheck >= 2.1.0.1 && < 2.9,
+    async,
     base,
     bytestring,
     directory,
-    extensible-exceptions,
     filepath,
     process,
     regex-posix,
     tasty,
-    tasty-hunit,
-    tasty-quickcheck
+    tasty-hunit
 
   if os(windows)
     build-depends: Win32 >= 2 && < 3
-    cpp-options: -DWIN32
   else
-    build-depends: unix >= 2.0 && < 2.8
+    build-depends: unix >= 2.5 && < 2.8
 
-  if arch(arm)
-    cc-options:  -DCABAL_NO_THREADED
-  else
+  if !(arch(arm) && impl(ghc < 7.6))
     ghc-options: -threaded
 
   ghc-options: -Wall
   default-language: Haskell2010
+
+custom-setup
+  setup-depends: Cabal >= 1.25,
+                 base,
+                 process   >= 1.1.0.1  && < 1.5,
+                 filepath   >= 1.3      && < 1.5
diff --git a/cabal/cabal-install/cbits/getnumcores.c b/cabal/cabal-install/cbits/getnumcores.c
deleted file mode 100644
--- a/cabal/cabal-install/cbits/getnumcores.c
+++ /dev/null
@@ -1,46 +0,0 @@
-#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 612) && !defined(CABAL_NO_THREADED)
-/* Since version 6.12, GHC's threaded RTS includes a getNumberOfProcessors
-   function, so we try to use that if available. cabal-install is always built
-   with -threaded nowadays.  */
-#define HAS_GET_NUMBER_OF_PROCESSORS
-#endif
-
-
-#ifndef HAS_GET_NUMBER_OF_PROCESSORS
-
-#ifdef _WIN32
-#include <windows.h>
-#elif MACOS
-#include <sys/param.h>
-#include <sys/sysctl.h>
-#elif __linux__
-#include <unistd.h>
-#endif
-
-int getNumberOfProcessors() {
-#ifdef WIN32
-    SYSTEM_INFO sysinfo;
-    GetSystemInfo(&sysinfo);
-    return sysinfo.dwNumberOfProcessors;
-#elif MACOS
-    int nm[2];
-    size_t len = 4;
-    uint32_t count;
-
-    nm[0] = CTL_HW; nm[1] = HW_AVAILCPU;
-    sysctl(nm, 2, &count, &len, NULL, 0);
-
-    if(count < 1) {
-        nm[1] = HW_NCPU;
-        sysctl(nm, 2, &count, &len, NULL, 0);
-        if(count < 1) { count = 1; }
-    }
-    return count;
-#elif __linux__
-    return sysconf(_SC_NPROCESSORS_ONLN);
-#else
-    return 1;
-#endif
-}
-
-#endif /* HAS_GET_NUMBER_OF_PROCESSORS */
diff --git a/cabal/cabal-install/changelog b/cabal/cabal-install/changelog
--- a/cabal/cabal-install/changelog
+++ b/cabal/cabal-install/changelog
@@ -1,6 +1,9 @@
 -*-change-log-*-
 
-1.23.x.x (current development version)
+1.25.x.x (current development version)
+	* Made the solver aware of pkg-config constraints (#3023).
+
+1.24.0.0 Ryan Thomas <ryan@ryant.org> March 2016
 	* If there are multiple remote repos, 'cabal update' now updates
 	them in parallel (#2503).
 	* New 'cabal upload' option '-P'/'--password-command' for reading
@@ -18,13 +21,44 @@
 	* Automatically use https for cabal upload for the main
 	hackage.haskell.org (other repos will use whatever they are
 	configured to use).
-	* Support for dependencies of custom Setup.hs scripts.
+	* Support for dependencies of custom Setup.hs scripts
+	(see http://www.well-typed.com/blog/2015/07/cabal-setup-deps/).
 	* 'cabal' program itself now can be used as an external setup
 	method. This fixes an issue when Cabal version mismatch caused
 	unnecessary reconfigures (#2633).
 	* Improved error message for unsatisfiable package constraints
 	(#2727).
 	* Fixed a space leak in 'cabal update' (#2826).
+	* 'cabal exec' and 'sandbox hc-pkg' now use the configured
+	compiler (#2859).
+	* New 'cabal haddock' option: '--for-hackage' (#2852).
+	* Added a warning when the solver cannot find a dependency (#2853).
+	* New 'cabal upload' option: '--doc': upload documentation to
+	hackage (#2890).
+	* Improved error handling for 'sandbox delete-source' (#2943).
+	* Solver support for extension and language flavours (#2873).
+	* Support for secure repos using hackage-security (#2983).
+	* Added a log file message similar to one printed by 'make' when
+	building in another directory (#2642).
+	* Added new subcommand 'init' to 'cabal user-config'. This
+	subcommand creates a cabal configuration file in either the
+	default location or as specified by --config-file (#2553).
+	* The man page for 'cabal-install' is now automatically generated
+	(#2877).
+	* The '--allow-newer' option now works as expected when specified
+	multiple times (#2588).
+	* New config file field: 'extra-framework-dirs' (extra locations
+	to find OS X frameworks in). Can be also specified as an argument
+	for 'install' and 'configure' commands (#3158).
+	* It's now possible to limit the scope of '--allow-newer' to
+	single packages in the install plan (#2756).
+	* Full '--allow-newer' syntax is now supported in the config file
+	(that is, 'allow-newer: base, ghc-prim,  some-package:vector')
+	(#3171).
+	* Improved performance of '--reorder-goals' (#3208).
+	* Fixed space leaks in modular solver (#2916, #2914).
+	* Added a new command: 'gen-bounds' (#3223). See
+	http://softwaresimply.blogspot.se/2015/08/cabal-gen-bounds-easy-generation-of.html.
 
 1.22.0.0 Johan Tibell <johan.tibell@gmail.com> January 2015
 	* New command: user-config (#2159).
diff --git a/cabal/cabal-install/tests/IntegrationTests.hs b/cabal/cabal-install/tests/IntegrationTests.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests.hs
@@ -0,0 +1,319 @@
+{-# LANGUAGE CPP #-}
+-- | Groups black-box tests of cabal-install and configures them to test
+-- the correct binary.
+--
+-- This file should do nothing but import tests from other modules and run
+-- them with the path to the correct cabal-install binary.
+module Main
+       where
+
+-- Modules from Cabal.
+import Distribution.Compat.CreatePipe (createPipe)
+import Distribution.Compat.Environment (setEnv)
+import Distribution.Compat.Internal.TempFile (createTempDirectory)
+import Distribution.Simple.Configure (findDistPrefOrDefault)
+import Distribution.Simple.Program.Builtin (ghcPkgProgram)
+import Distribution.Simple.Program.Db
+        (defaultProgramDb, requireProgram, setProgramSearchPath)
+import Distribution.Simple.Program.Find
+        (ProgramSearchPathEntry(ProgramSearchPathDir), defaultProgramSearchPath)
+import Distribution.Simple.Program.Types
+        ( Program(..), simpleProgram, programPath)
+import Distribution.Simple.Setup ( Flag(..) )
+import Distribution.Simple.Utils ( findProgramVersion, copyDirectoryRecursive, installOrdinaryFile )
+import Distribution.Verbosity (normal)
+
+-- Third party modules.
+import Control.Concurrent.Async (withAsync, wait)
+import Control.Exception (bracket)
+import Data.Maybe (fromMaybe)
+import System.Directory
+        ( canonicalizePath
+        , findExecutable
+        , getDirectoryContents
+        , getTemporaryDirectory
+        , doesDirectoryExist
+        , removeDirectoryRecursive
+        , doesFileExist )
+import System.FilePath
+import Test.Tasty (TestTree, defaultMain, testGroup)
+import Test.Tasty.HUnit (testCase, Assertion, assertFailure)
+import Control.Monad ( filterM, forM, unless, when )
+import Data.List (isPrefixOf, isSuffixOf, sort)
+import Data.IORef (newIORef, writeIORef, readIORef)
+import System.Exit (ExitCode(..))
+import System.IO (withBinaryFile, IOMode(ReadMode))
+import System.Process (runProcess, waitForProcess)
+import Text.Regex.Posix ((=~))
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as C8
+import           Data.ByteString (ByteString)
+
+#if MIN_VERSION_base(4,6,0)
+import System.Environment ( getExecutablePath )
+#endif
+
+-- | Test case.
+data TestCase = TestCase
+    { tcName :: String -- ^ Name of the shell script
+    , tcBaseDirectory :: FilePath -- ^ The base directory where tests are found
+                                  --   e.g., cabal-install/tests/IntegrationTests
+    , tcCategory :: String -- ^ Category of test; e.g., custom, exec, freeze, ...
+    , tcStdOutPath :: Maybe FilePath -- ^ File path of expected standard output
+    , tcStdErrPath :: Maybe FilePath -- ^ File path of expected standard error
+    }
+
+-- | Test result.
+data TestResult = TestResult
+    { trExitCode :: ExitCode -- ^ Exit code of test script
+    , trStdOut :: ByteString -- ^ Actual standard output
+    , trStdErr :: ByteString -- ^ Actual standard error
+    , trWorkingDirectory :: FilePath -- ^ Temporary working directory test was
+                                     --   executed in.
+    }
+
+-- | Cabal executable
+cabalProgram :: Program
+cabalProgram = (simpleProgram "cabal") {
+    programFindVersion = findProgramVersion "--numeric-version" id
+  }
+
+-- | Convert test result to user-friendly string message.
+testResultToString :: TestResult -> String
+testResultToString testResult =
+    exitStatus ++ "\n" ++ workingDirectory ++ "\n\n" ++ stdOut ++ "\n\n" ++ stdErr
+  where
+    exitStatus = "Exit status: " ++ show (trExitCode testResult)
+    workingDirectory = "Working directory: " ++ (trWorkingDirectory testResult)
+    stdOut = "<stdout> was:\n" ++ C8.unpack (trStdOut testResult)
+    stdErr = "<stderr> was:\n" ++ C8.unpack (trStdErr testResult)
+
+-- | Returns the command that was issued, the return code, and the output text
+run :: FilePath -> String -> [String] -> IO TestResult
+run cwd path args = do
+  -- path is relative to the current directory; canonicalizePath makes it
+  -- absolute, so that runProcess will find it even when changing directory.
+  path' <- canonicalizePath path
+
+  (pid, hReadStdOut, hReadStdErr) <- do
+    -- Create pipes for StdOut and StdErr
+    (hReadStdOut, hWriteStdOut) <- createPipe
+    (hReadStdErr, hWriteStdErr) <- createPipe
+    -- Run the process
+    pid <- runProcess path' args (Just cwd) Nothing Nothing (Just hWriteStdOut) (Just hWriteStdErr)
+    -- Return the pid and read ends of the pipes
+    return (pid, hReadStdOut, hReadStdErr)
+  -- Read subprocess output using asynchronous threads; we need to
+  -- do this aynchronously to avoid deadlocks due to buffers filling
+  -- up.
+  withAsync (B.hGetContents hReadStdOut) $ \stdOutAsync -> do
+    withAsync (B.hGetContents hReadStdErr) $ \stdErrAsync -> do
+      -- Wait for the subprocess to terminate
+      exitcode <- waitForProcess pid
+      -- We can now be sure that no further output is going to arrive,
+      -- so we wait for the results of the asynchronous reads.
+      stdOut <- wait stdOutAsync
+      stdErr <- wait stdErrAsync
+      -- Done
+      return $ TestResult exitcode stdOut stdErr cwd
+
+-- | Get a list of all names in a directory, excluding all hidden or
+-- system files/directories such as '.', '..'  or any files/directories
+-- starting with a '.'.
+listDirectory :: FilePath -> IO [String]
+listDirectory directory = do
+  fmap (filter notHidden) $ getDirectoryContents directory
+  where
+    notHidden = not . isHidden
+    isHidden name = "." `isPrefixOf` name
+
+-- | List a directory as per 'listDirectory', but return an empty list
+-- in case the directory does not exist.
+listDirectoryLax :: FilePath -> IO [String]
+listDirectoryLax directory = do
+  d <- doesDirectoryExist directory
+  if d then
+    listDirectory directory
+  else
+    return [ ]
+
+-- | @pathIfExists p == return (Just p)@ if @p@ exists, and
+-- @return Nothing@ otherwise.
+pathIfExists :: FilePath -> IO (Maybe FilePath)
+pathIfExists p = do
+  e <- doesFileExist p
+  if e then
+    return $ Just p
+    else
+      return Nothing
+
+-- | Checks if file @p@ matches a 'ByteString' @s@, subject
+-- to line-break normalization.  Lines in the file which are
+-- prefixed with @RE:@ are treated specially as a regular expression.
+fileMatchesString :: FilePath -> ByteString -> IO Bool
+fileMatchesString p s = do
+  withBinaryFile p ReadMode $ \h -> do
+    expected <- (C8.lines . normalizeLinebreaks) `fmap` B.hGetContents h -- Strict
+    let actual = C8.lines $ normalizeLinebreaks s
+    return $ length expected == length actual &&
+             and (zipWith matches expected actual)
+  where
+    matches :: ByteString -> ByteString -> Bool
+    matches pattern line
+        | C8.pack "RE:" `B.isPrefixOf` pattern = line =~ C8.drop 3 pattern
+        | otherwise                            = line == pattern
+
+    -- This is a bit of a hack, but since we're comparing
+    -- *text* output, we should be OK.
+    normalizeLinebreaks = B.filter (not . ((==) 13)) -- carriage return
+
+-- | Check if the @actual@ 'ByteString' matches the contents of
+-- @expected@ (always succeeds if the expectation is 'Nothing').
+-- Also takes the 'TestResult' and a label @handleName@ describing
+-- what text the string values correspond to.
+mustMatch :: TestResult -> String -> ByteString -> Maybe FilePath -> Assertion
+mustMatch _          _          _      Nothing         =  return ()
+mustMatch testResult handleName actual (Just expected) = do
+  m <- fileMatchesString expected actual
+  unless m $ assertFailure $
+      "<" ++ handleName ++ "> did not match file '"
+      ++ expected ++ "'.\n" ++ testResultToString testResult
+
+-- | Given a @directory@, return its subdirectories.  This is
+-- run on @cabal-install/tests/IntegrationTests@ to get the
+-- list of test categories which can be run.
+discoverTestCategories :: FilePath -> IO [String]
+discoverTestCategories directory = do
+  names <- listDirectory directory
+  fmap sort $ filterM (\name -> doesDirectoryExist $ directory </> name) names
+
+-- | Find all shell scripts in @baseDirectory </> category@.
+discoverTestCases :: FilePath -> String -> IO [TestCase]
+discoverTestCases baseDirectory category = do
+  -- Find the names of the shell scripts
+  names <- fmap (filter isTestCase) $ listDirectoryLax directory
+  -- Fill in TestCase for each script
+  forM (sort names) $ \name -> do
+    stdOutPath <- pathIfExists $ directory </> name `replaceExtension` ".out"
+    stdErrPath <- pathIfExists $ directory </> name `replaceExtension` ".err"
+    return $ TestCase { tcName = name
+                      , tcBaseDirectory = baseDirectory
+                      , tcCategory = category
+                      , tcStdOutPath = stdOutPath
+                      , tcStdErrPath = stdErrPath
+                      }
+  where
+    directory = baseDirectory </> category
+    isTestCase name = ".sh" `isSuffixOf` name
+
+-- | Given a list of 'TestCase's (describing a shell script for a
+-- single test case), run @mk@ on each of them to form a runnable
+-- 'TestTree'.
+createTestCases :: [TestCase] -> (TestCase -> Assertion) -> IO [TestTree]
+createTestCases testCases mk =
+  return $ (flip map) testCases $ \tc -> testCase (tcName tc ++ suffix tc) $ mk tc
+  where
+    suffix tc = case (tcStdOutPath tc, tcStdErrPath tc) of
+      (Nothing, Nothing) -> " (ignoring stdout+stderr)"
+      (Just _ , Nothing) -> " (ignoring stderr)"
+      (Nothing, Just _ ) -> " (ignoring stdout)"
+      (Just _ , Just _ ) -> ""
+
+-- | Given a 'TestCase', run it.
+--
+-- A test case of the form @category/testname.sh@ is
+-- run in the following way
+--
+--      1. We make a full copy all of @category@ into a temporary
+--         directory.
+--      2. In the temporary directory, run @testname.sh@.
+--      3. Test that the exit code is zero, and that stdout/stderr
+--         match the expected results.
+--
+runTestCase :: TestCase -> IO ()
+runTestCase tc = do
+  doRemove <- newIORef False
+  bracket createWorkDirectory (removeWorkDirectory doRemove) $ \workDirectory -> do
+    -- Run
+    let scriptDirectory = workDirectory
+    sh <- fmap (fromMaybe $ error "Cannot find 'sh' executable") $ findExecutable "sh"
+    testResult <- run scriptDirectory sh [ "-e", tcName tc]
+    -- Assert that we got what we expected
+    case trExitCode testResult of
+      ExitSuccess ->
+        return () -- We're good
+      ExitFailure _ ->
+        assertFailure $ "Unexpected exit status.\n\n" ++ testResultToString testResult
+    mustMatch testResult "stdout" (trStdOut testResult) (tcStdOutPath tc)
+    mustMatch testResult "stderr" (trStdErr testResult) (tcStdErrPath tc)
+    -- Only remove working directory if test succeeded
+    writeIORef doRemove True
+  where
+    createWorkDirectory = do
+      -- Create the temporary directory
+      tempDirectory <- getTemporaryDirectory
+      workDirectory <- createTempDirectory tempDirectory "cabal-install-test"
+      -- Copy all the files from the category into the working directory.
+      copyDirectoryRecursive normal
+        (tcBaseDirectory tc </> tcCategory tc)
+        workDirectory
+      -- Copy in the common.sh stub
+      let commonDest = workDirectory </> "common.sh"
+      e <- doesFileExist commonDest
+      unless e $
+        installOrdinaryFile normal (tcBaseDirectory tc </> "common.sh") commonDest
+      -- Done
+      return workDirectory
+    removeWorkDirectory doRemove workDirectory = do
+        remove <- readIORef doRemove
+        when remove $ removeDirectoryRecursive workDirectory
+
+discoverCategoryTests :: FilePath -> String -> IO [TestTree]
+discoverCategoryTests baseDirectory category = do
+  testCases <- discoverTestCases baseDirectory category
+  createTestCases testCases runTestCase
+
+main :: IO ()
+main = do
+  -- Find executables and build directories, etc.
+  distPref <- guessDistDir
+  buildDir <- canonicalizePath (distPref </> "build/cabal")
+  let programSearchPath = ProgramSearchPathDir buildDir : defaultProgramSearchPath
+  (cabal, _) <- requireProgram normal cabalProgram (setProgramSearchPath programSearchPath defaultProgramDb)
+  (ghcPkg, _) <- requireProgram normal ghcPkgProgram defaultProgramDb
+  baseDirectory <- canonicalizePath $ "tests" </> "IntegrationTests"
+  -- Set up environment variables for test scripts
+  setEnv "GHC_PKG" $ programPath ghcPkg
+  setEnv "CABAL" $ programPath cabal
+  -- Define default arguments
+  setEnv "CABAL_ARGS" $ "--config-file=config-file"
+  setEnv "CABAL_ARGS_NO_CONFIG_FILE" " "
+  -- Discover all the test categories
+  categories <- discoverTestCategories baseDirectory
+  -- Discover tests in each category
+  tests <- forM categories $ \category -> do
+    categoryTests <- discoverCategoryTests baseDirectory category
+    return (category, categoryTests)
+  -- Map into a test tree
+  let testTree = map (\(category, categoryTests) -> testGroup category categoryTests) tests
+  -- Run the tests
+  defaultMain $ testGroup "Integration Tests" $ testTree
+
+-- See this function in Cabal's PackageTests. If you update this,
+-- update its copy in cabal-install.  (Why a copy here? I wanted
+-- to try moving this into the Cabal library, but to do this properly
+-- I'd have to BC'ify getExecutablePath, and then it got hairy, so
+-- I aborted and did something simple.)
+guessDistDir :: IO FilePath
+guessDistDir = do
+#if MIN_VERSION_base(4,6,0)
+    exe_path <- canonicalizePath =<< getExecutablePath
+    let dist0 = dropFileName exe_path </> ".." </> ".."
+    b <- doesFileExist (dist0 </> "setup-config")
+#else
+    let dist0 = error "no path"
+        b = False
+#endif
+    -- Method (2)
+    if b then canonicalizePath dist0
+         else findDistPrefOrDefault NoFlag >>= canonicalizePath
diff --git a/cabal/cabal-install/tests/IntegrationTests/common.sh b/cabal/cabal-install/tests/IntegrationTests/common.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/common.sh
@@ -0,0 +1,12 @@
+# A globally available set of useful utilities.  Test
+# scripts can include this by saying ". ./common.sh"
+
+# Helper to run Cabal
+cabal() {
+    "$CABAL" $CABAL_ARGS "$@"
+}
+
+die() {
+    echo "die: $@"
+    exit 1
+}
diff --git a/cabal/cabal-install/tests/IntegrationTests/custom/plain.err b/cabal/cabal-install/tests/IntegrationTests/custom/plain.err
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/custom/plain.err
@@ -0,0 +1,2 @@
+Custom
+Custom
diff --git a/cabal/cabal-install/tests/IntegrationTests/custom/plain.sh b/cabal/cabal-install/tests/IntegrationTests/custom/plain.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/custom/plain.sh
@@ -0,0 +1,4 @@
+. ./common.sh
+cd plain
+cabal configure
+cabal build
diff --git a/cabal/cabal-install/tests/IntegrationTests/custom/plain/A.hs b/cabal/cabal-install/tests/IntegrationTests/custom/plain/A.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/custom/plain/A.hs
@@ -0,0 +1,1 @@
+module A where
diff --git a/cabal/cabal-install/tests/IntegrationTests/custom/plain/Setup.hs b/cabal/cabal-install/tests/IntegrationTests/custom/plain/Setup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/custom/plain/Setup.hs
@@ -0,0 +1,3 @@
+import Distribution.Simple
+import System.IO
+main = hPutStrLn stderr "Custom" >> defaultMain
diff --git a/cabal/cabal-install/tests/IntegrationTests/custom/plain/plain.cabal b/cabal/cabal-install/tests/IntegrationTests/custom/plain/plain.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/custom/plain/plain.cabal
@@ -0,0 +1,12 @@
+name:                plain
+version:             0.1.0.0
+license:             BSD3
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Custom
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     A
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/Foo.hs b/cabal/cabal-install/tests/IntegrationTests/exec/Foo.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/Foo.hs
@@ -0,0 +1,4 @@
+module Foo where
+
+foo :: String
+foo = "foo"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/My.hs b/cabal/cabal-install/tests/IntegrationTests/exec/My.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/My.hs
@@ -0,0 +1,5 @@
+module Main where
+
+main :: IO ()
+main = do
+    putStrLn "This is my-executable"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out b/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out
@@ -0,0 +1,1 @@
+This is my-executable
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh b/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh
@@ -0,0 +1,10 @@
+. ./common.sh
+
+cabal sandbox delete > /dev/null
+cabal exec my-executable && die "Unexpectedly found executable"
+
+cabal sandbox init > /dev/null
+cabal install > /dev/null
+
+# Execute indirectly via bash to ensure that we go through $PATH
+cabal exec sh -- -c my-executable || die "Did not find executable"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.out b/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.out
@@ -0,0 +1,4 @@
+Config file path source is commandline option.
+Config file config-file not found.
+Writing default configuration to config-file
+find_me_in_output
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.sh b/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.sh
@@ -0,0 +1,2 @@
+. ./common.sh
+cabal exec echo find_me_in_output
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out b/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out
@@ -0,0 +1,1 @@
+This is my-executable
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh b/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh
@@ -0,0 +1,9 @@
+. ./common.sh
+
+cabal sandbox delete > /dev/null
+cabal exec my-executable && die "Unexpectedly found executable"
+
+cabal sandbox init > /dev/null
+cabal install > /dev/null
+
+cabal exec my-executable || die "Did not find executable"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh b/cabal/cabal-install/tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh
@@ -0,0 +1,14 @@
+. ./common.sh
+
+cabal sandbox delete > /dev/null
+cabal exec my-executable && die "Unexpectedly found executable"
+
+cabal sandbox init > /dev/null
+cabal install > /dev/null
+
+# The library should not be available outside the sandbox
+"$GHC_PKG" list | grep -v "my-0.1"
+
+# When run inside 'cabal-exec' the 'sandbox hc-pkg list' sub-command
+# should find the library.
+cabal exec sh -- -c 'cd subdir && "$CABAL" sandbox hc-pkg list' | grep "my-0.1"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh b/cabal/cabal-install/tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh
@@ -0,0 +1,13 @@
+. ./common.sh
+
+cabal sandbox delete > /dev/null
+cabal exec my-executable && die "Unexpectedly found executable"
+
+cabal sandbox init > /dev/null
+cabal install > /dev/null
+
+# The library should not be available outside the sandbox
+"$GHC_PKG" list | grep -v "my-0.1"
+
+# Execute ghc-pkg inside the sandbox; it should find my-0.1
+cabal exec ghc-pkg list | grep "my-0.1"
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.err b/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.err
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.err
@@ -0,0 +1,1 @@
+RE:^cabal(\.exe)?: Please specify an executable to run$
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.sh b/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+
+! cabal exec
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/my.cabal b/cabal/cabal-install/tests/IntegrationTests/exec/my.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/my.cabal
@@ -0,0 +1,14 @@
+name:           my
+version:        0.1
+license:        BSD3
+cabal-version:  >= 1.2
+build-type:     Simple
+
+library
+    exposed-modules:    Foo
+    build-depends:      base
+
+
+executable my-executable
+    main-is:            My.hs
+    build-depends:      base
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.out b/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.out
@@ -0,0 +1,1 @@
+this string
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.sh b/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal configure > /dev/null
+cabal exec echo this string
diff --git a/cabal/cabal-install/tests/IntegrationTests/exec/subdir/.gitkeep b/cabal/cabal-install/tests/IntegrationTests/exec/subdir/.gitkeep
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/exec/subdir/.gitkeep
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze --disable-benchmarks
+grep -v " criterion ==" cabal.config || die "should NOT have frozen criterion"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze --disable-tests
+grep -v " test-framework ==" cabal.config || die "should NOT have frozen test-framework"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh
@@ -0,0 +1,5 @@
+. ./common.sh
+# TODO: Test this against a package installed in the sandbox but not
+# depended upon.
+cabal freeze
+grep -v "exceptions ==" cabal.config || die "should not have frozen exceptions"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_self.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_self.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_self.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze
+grep -v " my ==" cabal.config || die "should not have frozen self"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze --dry-run
+[ ! -e cabal.config ] || die "cabal.config file should not have been created"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh
@@ -0,0 +1,4 @@
+. ./common.sh
+# TODO: solver should find solution without extra flags too
+cabal freeze --enable-benchmarks --reorder-goals --max-backjumps=-1
+grep " criterion ==" cabal.config || die "should have frozen criterion"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze --enable-tests
+grep " test-framework ==" cabal.config || die "should have frozen test-framework"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_direct_dependencies.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_direct_dependencies.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_direct_dependencies.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze
+grep " base ==" cabal.config || die "'base' should have been frozen"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh
@@ -0,0 +1,3 @@
+. ./common.sh
+cabal freeze
+grep " ghc-prim ==" cabal.config || die "'ghc-prim' should have been frozen"
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/my.cabal b/cabal/cabal-install/tests/IntegrationTests/freeze/my.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/my.cabal
@@ -0,0 +1,21 @@
+name:           my
+version:        0.1
+license:        BSD3
+cabal-version:  >= 1.20.0
+build-type:     Simple
+
+library
+    exposed-modules:    Foo
+    build-depends:      base
+
+test-suite test-Foo
+    type:   exitcode-stdio-1.0
+    hs-source-dirs: tests
+    main-is:    test-Foo.hs
+    build-depends: base, my, test-framework
+
+benchmark bench-Foo
+    type:   exitcode-stdio-1.0
+    hs-source-dirs: benchmarks
+    main-is:    benchmark-Foo.hs
+    build-depends: base, my, criterion
diff --git a/cabal/cabal-install/tests/IntegrationTests/freeze/runs_without_error.sh b/cabal/cabal-install/tests/IntegrationTests/freeze/runs_without_error.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/freeze/runs_without_error.sh
@@ -0,0 +1,2 @@
+. ./common.sh
+cabal freeze
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_basic.sh b/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_basic.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_basic.sh
@@ -0,0 +1,5 @@
+. ./common.sh
+
+cabal sandbox init
+cabal sandbox add-source p
+cabal install p
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_shadow.sh b/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_shadow.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_shadow.sh
@@ -0,0 +1,6 @@
+. ./common.sh
+
+cabal sandbox init
+cabal sandbox add-source p
+cabal sandbox add-source q
+cabal install p
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/Foo.hs b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/Foo.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/Foo.hs
@@ -0,0 +1,2 @@
+import Q
+main = putStrLn q
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p.cabal b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p.cabal
@@ -0,0 +1,23 @@
+name:                p
+version:             0.1.0.0
+license:             BSD3
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.23
+
+library q
+  build-depends:       base
+  exposed-modules:     Q
+  hs-source-dirs:      q
+  default-language:    Haskell2010
+
+library
+  build-depends:       base, q
+  exposed-modules:     P
+  hs-source-dirs:      p
+  default-language:    Haskell2010
+
+executable foo
+  build-depends:       base, q
+  main-is:             Foo.hs
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p/P.hs b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p/P.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p/P.hs
@@ -0,0 +1,2 @@
+module P where
+import Q
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/q/Q.hs b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/q/Q.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/p/q/Q.hs
@@ -0,0 +1,2 @@
+module Q where
+q = "I AM THE ONE"
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/Q.hs b/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/Q.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/Q.hs
@@ -0,0 +1,2 @@
+module Q where
+q = "DO NOT SEE ME"
diff --git a/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/q.cabal b/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/q.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/internal-libs/q/q.cabal
@@ -0,0 +1,12 @@
+name:                q
+version:             0.1.0.0
+license:             BSD3
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Q
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/manpage/outputs_manpage.sh b/cabal/cabal-install/tests/IntegrationTests/manpage/outputs_manpage.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/manpage/outputs_manpage.sh
@@ -0,0 +1,11 @@
+. ./common.sh
+
+OUTPUT=`cabal manpage`
+
+# contains visible command descriptions
+echo $OUTPUT | grep -q '\.B cabal install' || die "visible command description line not found in:\n----$OUTPUT\n----"
+
+# does not contain hidden command descriptions
+echo $OUTPUT | grep -q '\.B cabal manpage' && die "hidden command description line found in:\n----$OUTPUT\n----"
+
+exit 0
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh b/cabal/cabal-install/tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh
@@ -0,0 +1,11 @@
+. ./common.sh
+
+# Create the sandbox
+cabal sandbox init
+
+# Add the sources
+cabal sandbox add-source p
+cabal sandbox add-source q
+
+# Install the second package
+cabal install q
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/LICENSE b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/LICENSE
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/LICENSE
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/Setup.hs b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/Setup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/p.cabal b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/p.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/p/p.cabal
@@ -0,0 +1,11 @@
+name:                p
+version:             0.1.0.0
+license-file:        LICENSE
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/LICENSE b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/LICENSE
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/LICENSE
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/Setup.hs b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/Setup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/q.cabal b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/q.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/multiple-source/q/q.cabal
@@ -0,0 +1,11 @@
+name:                q
+version:             0.1.0.0
+license-file:        LICENSE
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err
@@ -0,0 +1,3 @@
+Warning: Sources not registered: "q"
+
+RE:^cabal(\.exe)?: The sources with the above errors were skipped\. \("q"\)$
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh
@@ -0,0 +1,10 @@
+. ./common.sh
+
+# Create the sandbox
+cabal sandbox init > /dev/null
+
+# Add one source
+cabal sandbox add-source p > /dev/null
+
+# Remove a source that exists on disk, but is not registered
+! cabal sandbox delete-source q
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/LICENSE b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/LICENSE
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/LICENSE
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/Setup.hs b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/Setup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/p.cabal b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/p.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/p.cabal
@@ -0,0 +1,11 @@
+name:                p
+version:             0.1.0.0
+license-file:        LICENSE
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/LICENSE b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/LICENSE
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/LICENSE
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/Setup.hs b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/Setup.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/q.cabal b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/q.cabal
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/q.cabal
@@ -0,0 +1,11 @@
+name:                q
+version:             0.1.0.0
+license-file:        LICENSE
+author:              Edward Z. Yang
+maintainer:          ezyang@cs.stanford.edu
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  build-depends:       base
+  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh
@@ -0,0 +1,22 @@
+. ./common.sh
+
+# Create the sandbox
+cabal sandbox init
+
+# Add the sources
+cabal sandbox add-source p
+cabal sandbox add-source q
+
+# delete the directory on disk
+rm -R p
+
+# Remove the registered source which is no longer on disk. cabal's handling of
+# non-existent sources depends on the behavior of the directory package.
+if OUTPUT=`cabal sandbox delete-source p 2>&1`; then
+    # 'canonicalizePath' should always succeed with directory >= 1.2.3.0
+    echo $OUTPUT | grep 'Success deleting sources: "p"' \
+	|| die "Incorrect success message: $OUTPUT"
+else
+    echo $OUTPUT | grep 'Warning: Source directory not found for paths: "p"' \
+	|| die "Incorrect failure message: $OUTPUT"
+fi
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.out b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.out
@@ -0,0 +1,6 @@
+Success deleting sources: "p" "q"
+
+Note: 'sandbox delete-source' only unregisters the source dependency, but does
+not remove the package from the sandbox package DB.
+
+Use 'sandbox hc-pkg -- unregister' to do that.
diff --git a/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh
@@ -0,0 +1,11 @@
+. ./common.sh
+
+# Create the sandbox
+cabal sandbox init > /dev/null
+
+# Add the sources
+cabal sandbox add-source p > /dev/null
+cabal sandbox add-source q > /dev/null
+
+# Remove one of the sources
+cabal sandbox delete-source p q
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/cabal-config b/cabal/cabal-install/tests/IntegrationTests/user-config/cabal-config
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/cabal-config
@@ -0,0 +1,182 @@
+-- This is the configuration file for the 'cabal' command line tool.
+
+-- The available configuration options are listed below.
+-- Some of them have default values listed.
+
+-- Lines (like this one) beginning with '--' are comments.
+-- Be careful with spaces and indentation because they are
+-- used to indicate layout for nested sections.
+
+
+repository hackage.haskell.org
+  url: http://hackage.haskell.org/
+
+-- default-user-config:
+-- require-sandbox: False
+-- ignore-sandbox: False
+-- ignore-expiry: False
+-- http-transport:
+remote-repo-cache: /home/gman/.cabal/packages
+-- local-repo:
+-- logs-dir:
+world-file: /home/gman/.cabal/world
+-- verbose: 1
+-- compiler: ghc
+-- with-compiler:
+-- with-hc-pkg:
+-- program-prefix:
+-- program-suffix:
+-- library-vanilla: True
+-- library-profiling:
+-- shared:
+-- executable-dynamic: False
+-- profiling:
+-- executable-profiling:
+-- profiling-detail:
+-- library-profiling-detail:
+-- optimization: True
+-- debug-info: False
+-- library-for-ghci:
+-- split-objs: False
+-- executable-stripping: True
+-- library-stripping: True
+-- configure-option:
+-- user-install: True
+-- package-db:
+-- flags:
+-- extra-include-dirs:
+-- extra-lib-dirs:
+extra-prog-path: /home/gman/.cabal/bin
+-- tests: False
+-- coverage: False
+-- library-coverage:
+-- exact-configuration: False
+-- benchmarks: False
+-- relocatable: False
+-- cabal-lib-version:
+-- constraint:
+-- preference:
+-- solver: choose
+-- allow-newer: False
+-- documentation: False
+-- doc-index-file: $datadir/doc/$arch-$os-$compiler/index.html
+-- max-backjumps: 2000
+-- reorder-goals: False
+-- shadow-installed-packages: False
+-- strong-flags: False
+-- reinstall: False
+-- avoid-reinstalls: False
+-- force-reinstalls: False
+-- upgrade-dependencies: False
+-- root-cmd:
+-- symlink-bindir:
+build-summary: /home/gman/.cabal/logs/build.log
+-- build-log:
+remote-build-reporting: anonymous
+-- report-planning-failure: False
+-- one-shot: False
+-- run-tests:
+jobs: $ncpus
+-- offline: False
+-- username:
+-- password:
+-- password-command:
+-- builddir:
+
+haddock
+  -- keep-temp-files: False
+  -- hoogle: False
+  -- html: False
+  -- html-location:
+  -- for-hackage: False
+  -- executables: False
+  -- tests: False
+  -- benchmarks: False
+  -- all:
+  -- internal: False
+  -- css:
+  -- hyperlink-source: False
+  -- hscolour-css:
+  -- contents-location:
+
+install-dirs user
+  -- prefix: /home/gman/.cabal
+  -- bindir: $prefix/bin
+  -- libdir: $prefix/lib
+  -- libsubdir: $abi/$libname
+  -- libexecdir: $prefix/libexec
+  -- datadir: $prefix/share
+  -- datasubdir: $abi/$pkgid
+  -- docdir: $datadir/doc/$abi/$pkgid
+  -- htmldir: $docdir/html
+  -- haddockdir: $htmldir
+  -- sysconfdir: $prefix/etc
+
+install-dirs global
+  -- prefix: /usr/local
+  -- bindir: $prefix/bin
+  -- libdir: $prefix/lib
+  -- libsubdir: $abi/$libname
+  -- libexecdir: $prefix/libexec
+  -- datadir: $prefix/share
+  -- datasubdir: $abi/$pkgid
+  -- docdir: $datadir/doc/$abi/$pkgid
+  -- htmldir: $docdir/html
+  -- haddockdir: $htmldir
+  -- sysconfdir: $prefix/etc
+
+program-locations
+  -- alex-location:
+  -- ar-location:
+  -- c2hs-location:
+  -- cpphs-location:
+  -- gcc-location:
+  -- ghc-location:
+  -- ghc-pkg-location:
+  -- ghcjs-location:
+  -- ghcjs-pkg-location:
+  -- greencard-location:
+  -- haddock-location:
+  -- happy-location:
+  -- haskell-suite-location:
+  -- haskell-suite-pkg-location:
+  -- hmake-location:
+  -- hpc-location:
+  -- hsc2hs-location:
+  -- hscolour-location:
+  -- jhc-location:
+  -- ld-location:
+  -- lhc-location:
+  -- lhc-pkg-location:
+  -- pkg-config-location:
+  -- strip-location:
+  -- tar-location:
+  -- uhc-location:
+
+program-default-options
+  -- alex-options:
+  -- ar-options:
+  -- c2hs-options:
+  -- cpphs-options:
+  -- gcc-options:
+  -- ghc-options:
+  -- ghc-pkg-options:
+  -- ghcjs-options:
+  -- ghcjs-pkg-options:
+  -- greencard-options:
+  -- haddock-options:
+  -- happy-options:
+  -- haskell-suite-options:
+  -- haskell-suite-pkg-options:
+  -- hmake-options:
+  -- hpc-options:
+  -- hsc2hs-options:
+  -- hscolour-options:
+  -- jhc-options:
+  -- ld-options:
+  -- lhc-options:
+  -- lhc-pkg-options:
+  -- pkg-config-options:
+  -- strip-options:
+  -- tar-options:
+  -- uhc-options:
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/common.sh b/cabal/cabal-install/tests/IntegrationTests/user-config/common.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/common.sh
@@ -0,0 +1,9 @@
+# Helper to run Cabal
+cabal() {
+    "$CABAL" $CABAL_ARGS_NO_CONFIG_FILE "$@"
+}
+
+die() {
+    echo "die: $@"
+    exit 1
+}
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err b/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err
@@ -0,0 +1,1 @@
+RE:^cabal(\.exe)?: \./cabal-config already exists\.$
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh b/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh
@@ -0,0 +1,5 @@
+. ./common.sh
+
+rm -f ./cabal-config
+cabal --config-file=./cabal-config user-config init > /dev/null
+! cabal --config-file=./cabal-config user-config init
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.out b/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.out
@@ -0,0 +1,2 @@
+Writing default configuration to ./cabal-config
+Writing default configuration to ./cabal-config
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.sh b/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.sh
@@ -0,0 +1,9 @@
+. ./common.sh
+
+rm -f ./cabal-config
+cabal --config-file=./cabal-config user-config init \
+    || die "Couldn't create config file"
+cabal --config-file=./cabal-config user-config -f init \
+    || die "Couldn't create config file"
+test -e ./cabal-config || die "Config file doesn't exist"
+rm -f ./cabal-config
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.out b/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.out
@@ -0,0 +1,1 @@
+Writing default configuration to ./cabal-config
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.sh b/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.sh
@@ -0,0 +1,7 @@
+. ./common.sh
+
+rm -f ./cabal-config
+cabal --config-file=./cabal-config user-config init \
+    || die "Couldn't create config file"
+test -e ./cabal-config || die "Config file doesn't exist"
+rm -f ./cabal-config
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out b/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out
@@ -0,0 +1,1 @@
+Writing default configuration to ./my-config
diff --git a/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh b/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh
@@ -0,0 +1,5 @@
+. ./common.sh
+
+export CABAL_CONFIG=./my-config
+cabal user-config init || die "Couldn't create config file"
+test -e ./my-config || die "Config file doesn't exist"
diff --git a/cabal/cabal-install/tests/PackageTests.hs b/cabal/cabal-install/tests/PackageTests.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests.hs
+++ /dev/null
@@ -1,95 +0,0 @@
--- | Groups black-box tests of cabal-install and configures them to test
--- the correct binary.
---
--- This file should do nothing but import tests from other modules and run
--- them with the path to the correct cabal-install binary.
-module Main
-       where
-
--- Modules from Cabal.
-import Distribution.Simple.Configure (findDistPrefOrDefault)
-import Distribution.Simple.Program.Builtin (ghcPkgProgram)
-import Distribution.Simple.Program.Db
-        (defaultProgramDb, requireProgram, setProgramSearchPath)
-import Distribution.Simple.Program.Find
-        (ProgramSearchPathEntry(ProgramSearchPathDir), defaultProgramSearchPath)
-import Distribution.Simple.Program.Types
-        ( Program(..), simpleProgram, programPath)
-import Distribution.Simple.Setup ( Flag(..) )
-import Distribution.Simple.Utils ( findProgramVersion )
-import Distribution.Verbosity (normal)
-
--- Third party modules.
-import qualified Control.Exception.Extensible as E
-import Distribution.Compat.Environment ( setEnv )
-import System.Directory
-        ( canonicalizePath, getCurrentDirectory, setCurrentDirectory
-        , removeFile, doesFileExist )
-import System.FilePath ((</>))
-import Test.Tasty (TestTree, defaultMain, testGroup)
-import Control.Monad ( when )
-
--- Module containing common test code.
-
-import PackageTests.PackageTester ( TestsPaths(..)
-                                  , packageTestsDirectory
-                                  , packageTestsConfigFile )
-
--- Modules containing the tests.
-import qualified PackageTests.Exec.Check
-import qualified PackageTests.Freeze.Check
-import qualified PackageTests.MultipleSource.Check
-
--- List of tests to run. Each test will be called with the path to the
--- cabal binary to use.
-tests :: PackageTests.PackageTester.TestsPaths -> TestTree
-tests paths = testGroup "Package Tests" $
-    [ testGroup "Freeze"         $ PackageTests.Freeze.Check.tests         paths
-    , testGroup "Exec"           $ PackageTests.Exec.Check.tests           paths
-    , testGroup "MultipleSource" $ PackageTests.MultipleSource.Check.tests paths
-    ]
-
-cabalProgram :: Program
-cabalProgram = (simpleProgram "cabal") {
-    programFindVersion = findProgramVersion "--numeric-version" id
-  }
-
-main :: IO ()
-main = do
-    -- Find the builddir used to build Cabal
-    distPref <- findDistPrefOrDefault NoFlag
-    -- Use the default builddir for all of the subsequent package tests
-    setEnv "CABAL_BUILDDIR" "dist"
-    buildDir <- canonicalizePath (distPref </> "build/cabal")
-    let programSearchPath = ProgramSearchPathDir buildDir : defaultProgramSearchPath
-    (cabal, _) <- requireProgram normal cabalProgram
-                      (setProgramSearchPath programSearchPath defaultProgramDb)
-    (ghcPkg, _) <- requireProgram normal ghcPkgProgram defaultProgramDb
-    canonicalConfigPath <- canonicalizePath $ "tests" </> packageTestsDirectory
-
-    let testsPaths = TestsPaths {
-          cabalPath = programPath cabal,
-          ghcPkgPath = programPath ghcPkg,
-          configPath = canonicalConfigPath </> packageTestsConfigFile
-        }
-
-    putStrLn $ "Using cabal: "   ++ cabalPath  testsPaths
-    putStrLn $ "Using ghc-pkg: " ++ ghcPkgPath testsPaths
-
-    cwd <- getCurrentDirectory
-    let confFile = packageTestsDirectory </> "cabal-config"
-        removeConf = do
-          b <- doesFileExist confFile
-          when b $ removeFile confFile
-    let runTests = do
-          setCurrentDirectory "tests"
-          removeConf -- assert that there is no existing config file
-                     -- (we want deterministic testing with the default
-                     --  config values)
-          defaultMain $ tests testsPaths
-    runTests `E.finally` do
-        -- remove the default config file that got created by the tests
-        removeConf
-        -- Change back to the old working directory so that the tests can be
-        -- repeatedly run in `cabal repl` via `:main`.
-        setCurrentDirectory cwd
diff --git a/cabal/cabal-install/tests/PackageTests/Exec/Check.hs b/cabal/cabal-install/tests/PackageTests/Exec/Check.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Exec/Check.hs
+++ /dev/null
@@ -1,145 +0,0 @@
-{-# LANGUAGE CPP #-}
-module PackageTests.Exec.Check
-       ( tests
-       ) where
-
-
-import PackageTests.PackageTester
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative ((<$>))
-#endif
-import Control.Monad (when)
-import Data.List (intercalate, isInfixOf)
-import System.FilePath ((</>))
-import System.Directory (getDirectoryContents)
-
-dir :: FilePath
-dir = packageTestsDirectory </> "Exec"
-
-tests :: TestsPaths -> [TestTree]
-tests paths =
-    [ testCase "exits with failure if given no argument" $ do
-          result <- cabal_exec paths dir []
-          assertExecFailed result
-
-    , testCase "prints error message if given no argument" $ do
-          result <- cabal_exec paths dir []
-          assertExecFailed result
-          let output = outputText result
-              expected = "specify an executable to run"
-              errMsg = "should have requested an executable be specified\n" ++
-                       output
-          assertBool errMsg $
-              expected `isInfixOf` (intercalate " " . lines $ output)
-
-    , testCase "runs the given command" $ do
-          result <- cabal_exec paths dir ["echo", "this", "string"]
-          assertExecSucceeded result
-          let output = outputText result
-              expected = "this string"
-              errMsg = "should have ran the given command\n" ++ output
-          assertBool errMsg $
-              expected `isInfixOf` (intercalate " " . lines $ output)
-
-    , testCase "can run executables installed in the sandbox" $ do
-          -- Test that an executable installed into the sandbox can be found.
-          -- We do this by removing any existing sandbox. Checking that the
-          -- executable cannot be found. Creating a new sandbox. Installing
-          -- the executable and checking it can be run.
-
-          cleanPreviousBuilds paths
-          assertMyExecutableNotFound paths
-          assertPackageInstall paths
-
-          result <- cabal_exec paths dir ["my-executable"]
-          assertExecSucceeded result
-          let output = outputText result
-              expected = "This is my-executable"
-              errMsg = "should have found a my-executable\n" ++ output
-          assertBool errMsg $
-              expected `isInfixOf` (intercalate " " . lines $ output)
-
-    , testCase "adds the sandbox bin directory to the PATH" $ do
-          cleanPreviousBuilds paths
-          assertMyExecutableNotFound paths
-          assertPackageInstall paths
-
-          result <- cabal_exec paths dir ["bash", "--", "-c", "my-executable"]
-          assertExecSucceeded result
-          let output = outputText result
-              expected = "This is my-executable"
-              errMsg = "should have found a my-executable\n" ++ output
-          assertBool errMsg $
-              expected `isInfixOf` (intercalate " " . lines $ output)
-
-    , testCase "configures GHC to use the sandbox" $ do
-          let libNameAndVersion = "my-0.1"
-
-          cleanPreviousBuilds paths
-          assertPackageInstall paths
-
-          assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion
-
-          result <- cabal_exec paths dir ["ghc-pkg", "list"]
-          assertExecSucceeded result
-          let output = outputText result
-              errMsg = "my library should have been found"
-          assertBool errMsg $
-              libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)
-          
-
-    -- , testCase "can find executables built from the package" $ do
-
-    , testCase "configures cabal to use the sandbox" $ do
-          let libNameAndVersion = "my-0.1"
-
-          cleanPreviousBuilds paths
-          assertPackageInstall paths
-
-          assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion
-
-          result <- cabal_exec paths dir ["bash", "--", "-c", "cd subdir ; cabal sandbox hc-pkg list"]
-          assertExecSucceeded result
-          let output = outputText result
-              errMsg = "my library should have been found"
-          assertBool errMsg $
-              libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)
-    ]
-
-cleanPreviousBuilds :: TestsPaths -> IO ()
-cleanPreviousBuilds paths = do
-    sandboxExists <- not . null . filter (== "cabal.sandbox.config") <$>
-                         getDirectoryContents dir
-    assertCleanSucceeded   =<< cabal_clean paths dir []
-    when sandboxExists $ do
-        assertSandboxSucceeded =<< cabal_sandbox paths dir ["delete"]
-
-
-assertPackageInstall :: TestsPaths -> IO ()
-assertPackageInstall paths = do
-    assertSandboxSucceeded =<< cabal_sandbox paths dir ["init"]
-    assertInstallSucceeded =<< cabal_install paths dir []
-
-
-assertMyExecutableNotFound :: TestsPaths -> IO ()
-assertMyExecutableNotFound paths = do
-    result <- cabal_exec paths dir ["my-executable"]
-    assertExecFailed result
-    let output = outputText result
-        expected = "The program 'my-executable' is required but it " ++
-                   "could not be found"
-        errMsg = "should not have found a my-executable\n" ++ output
-    assertBool errMsg $
-        expected `isInfixOf` (intercalate " " . lines $ output)
-
-
-
-assertMyLibIsNotAvailableOutsideofSandbox :: TestsPaths -> String -> IO ()
-assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion = do
-    (_, _, output) <- run (Just $ dir) (ghcPkgPath paths) ["list"]
-    assertBool "my library should not have been found" $ not $
-        libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)
diff --git a/cabal/cabal-install/tests/PackageTests/Exec/Foo.hs b/cabal/cabal-install/tests/PackageTests/Exec/Foo.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Exec/Foo.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-module Foo where
-
-foo :: String
-foo = "foo"
diff --git a/cabal/cabal-install/tests/PackageTests/Exec/My.hs b/cabal/cabal-install/tests/PackageTests/Exec/My.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Exec/My.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-module Main where
-
-main :: IO ()
-main = do
-    putStrLn "This is my-executable"
diff --git a/cabal/cabal-install/tests/PackageTests/Exec/my.cabal b/cabal/cabal-install/tests/PackageTests/Exec/my.cabal
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Exec/my.cabal
+++ /dev/null
@@ -1,14 +0,0 @@
-name:           my
-version:        0.1
-license:        BSD3
-cabal-version:  >= 1.2
-build-type:     Simple
-
-library
-    exposed-modules:    Foo
-    build-depends:      base
-
-
-executable my-executable
-    main-is:            My.hs
-    build-depends:      base
diff --git a/cabal/cabal-install/tests/PackageTests/Exec/subdir/.gitkeep b/cabal/cabal-install/tests/PackageTests/Exec/subdir/.gitkeep
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Exec/subdir/.gitkeep
+++ /dev/null
diff --git a/cabal/cabal-install/tests/PackageTests/Freeze/Check.hs b/cabal/cabal-install/tests/PackageTests/Freeze/Check.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Freeze/Check.hs
+++ /dev/null
@@ -1,116 +0,0 @@
-{-# LANGUAGE ScopedTypeVariables #-}
-
-module PackageTests.Freeze.Check
-       ( tests
-       ) where
-
-import PackageTests.PackageTester
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import qualified Control.Exception.Extensible as E
-import Data.List (intercalate, isInfixOf)
-import System.Directory (doesFileExist, removeFile)
-import System.FilePath ((</>))
-import System.IO.Error (isDoesNotExistError)
-
-dir :: FilePath
-dir = packageTestsDirectory </> "Freeze"
-
-tests :: TestsPaths -> [TestTree]
-tests paths =
-    [ testCase "runs without error" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir []
-          assertFreezeSucceeded result
-
-    , testCase "freezes direct dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir []
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should have frozen base\n" ++ c) $
-              " base ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "freezes transitory dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir []
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should have frozen ghc-prim\n" ++ c) $
-              " ghc-prim ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "does not freeze packages which are not dependend upon" $ do
-          -- TODO: Test this against a package installed in the sandbox but
-          -- not depended upon.
-          removeCabalConfig
-          result <- cabal_freeze paths dir []
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should not have frozen exceptions\n" ++ c) $ not $
-              " exceptions ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "does not include a constraint for the package being frozen" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir []
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should not have frozen self\n" ++ c) $ not $
-              " my ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "--dry-run does not modify the cabal.config file" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir ["--dry-run"]
-          assertFreezeSucceeded result
-          c <- doesFileExist $ dir </> "cabal.config"
-          assertBool "cabal.config file should not have been created" (not c)
-
-    , testCase "--enable-tests freezes test dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir ["--enable-tests"]
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should have frozen test-framework\n" ++ c) $
-              " test-framework ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "--disable-tests does not freeze test dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir ["--disable-tests"]
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should not have frozen test-framework\n" ++ c) $ not $
-              " test-framework ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "--enable-benchmarks freezes benchmark dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir ["--disable-benchmarks"]
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should not have frozen criterion\n" ++ c) $ not $
-              " criterion ==" `isInfixOf` (intercalate " " $ lines $ c)
-
-    , testCase "--disable-benchmarks does not freeze benchmark dependencies" $ do
-          removeCabalConfig
-          result <- cabal_freeze paths dir ["--disable-benchmarks"]
-          assertFreezeSucceeded result
-          c <- readCabalConfig
-          assertBool ("should not have frozen criterion\n" ++ c) $ not $
-              " criterion ==" `isInfixOf` (intercalate " " $ lines $ c)
-    ]
-
-removeCabalConfig :: IO ()
-removeCabalConfig = do
-    removeFile (dir </> "cabal.config")
-    `E.catch` \ (e :: IOError) ->
-        if isDoesNotExistError e
-        then return ()
-        else E.throw e
-
-
-readCabalConfig :: IO String
-readCabalConfig = do
-    config <- readFile $ dir </> "cabal.config"
-    -- Ensure that the file is closed so that it can be
-    -- deleted by the next test on Windows.
-    length config `seq` return config
diff --git a/cabal/cabal-install/tests/PackageTests/Freeze/my.cabal b/cabal/cabal-install/tests/PackageTests/Freeze/my.cabal
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/Freeze/my.cabal
+++ /dev/null
@@ -1,21 +0,0 @@
-name:           my
-version:        0.1
-license:        BSD3
-cabal-version:  >= 1.20.0
-build-type:     Simple
-
-library
-    exposed-modules:    Foo
-    build-depends:      base
-
-test-suite test-Foo
-    type:   exitcode-stdio-1.0
-    hs-source-dirs: tests
-    main-is:    test-Foo.hs
-    build-depends: base, my, test-framework
-
-benchmark bench-Foo
-    type:   exitcode-stdio-1.0
-    hs-source-dirs: benchmarks
-    main-is:    benchmark-Foo.hs
-    build-depends: base, my, criterion
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/Check.hs b/cabal/cabal-install/tests/PackageTests/MultipleSource/Check.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/Check.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module PackageTests.MultipleSource.Check
-       ( tests
-       ) where
-
-
-import PackageTests.PackageTester
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Control.Monad    (void, when)
-import System.Directory (doesDirectoryExist)
-import System.FilePath  ((</>))
-
-dir :: FilePath
-dir = packageTestsDirectory </> "MultipleSource"
-
-tests :: TestsPaths -> [TestTree]
-tests paths =
-    [ testCase "finds second source of multiple source" $ do
-          sandboxExists <- doesDirectoryExist $ dir </> ".cabal-sandbox"
-          when sandboxExists $
-            void $ cabal_sandbox paths dir ["delete"]
-          assertSandboxSucceeded =<< cabal_sandbox paths dir ["init"]
-          assertSandboxSucceeded =<< cabal_sandbox paths dir ["add-source", "p"]
-          assertSandboxSucceeded =<< cabal_sandbox paths dir ["add-source", "q"]
-          assertInstallSucceeded =<< cabal_install paths dir ["q"]
-    ]
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/LICENSE b/cabal/cabal-install/tests/PackageTests/MultipleSource/p/LICENSE
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/LICENSE
+++ /dev/null
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/Setup.hs b/cabal/cabal-install/tests/PackageTests/MultipleSource/p/Setup.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/p.cabal b/cabal/cabal-install/tests/PackageTests/MultipleSource/p/p.cabal
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/p/p.cabal
+++ /dev/null
@@ -1,11 +0,0 @@
-name:                p
-version:             0.1.0.0
-license-file:        LICENSE
-author:              Edward Z. Yang
-maintainer:          ezyang@cs.stanford.edu
-build-type:          Simple
-cabal-version:       >=1.10
-
-library
-  build-depends:       base
-  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/LICENSE b/cabal/cabal-install/tests/PackageTests/MultipleSource/q/LICENSE
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/LICENSE
+++ /dev/null
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/Setup.hs b/cabal/cabal-install/tests/PackageTests/MultipleSource/q/Setup.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/q.cabal b/cabal/cabal-install/tests/PackageTests/MultipleSource/q/q.cabal
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/MultipleSource/q/q.cabal
+++ /dev/null
@@ -1,11 +0,0 @@
-name:                q
-version:             0.1.0.0
-license-file:        LICENSE
-author:              Edward Z. Yang
-maintainer:          ezyang@cs.stanford.edu
-build-type:          Simple
-cabal-version:       >=1.10
-
-library
-  build-depends:       base
-  default-language:    Haskell2010
diff --git a/cabal/cabal-install/tests/PackageTests/PackageTester.hs b/cabal/cabal-install/tests/PackageTests/PackageTester.hs
deleted file mode 100644
--- a/cabal/cabal-install/tests/PackageTests/PackageTester.hs
+++ /dev/null
@@ -1,232 +0,0 @@
-{-# LANGUAGE ScopedTypeVariables #-}
-
--- TODO This module was originally based on the PackageTests.PackageTester
--- module in Cabal, however it has a few differences. I suspect that as
--- this module ages the two modules will diverge further. As such, I have
--- not attempted to merge them into a single module nor to extract a common
--- module from them.  Refactor this module and/or Cabal's
--- PackageTests.PackageTester to remove commonality.
---   2014-05-15 Ben Armston
-
--- | Routines for black-box testing cabal-install.
---
--- Instead of driving the tests by making library calls into
--- Distribution.Simple.* or Distribution.Client.* this module only every
--- executes the `cabal-install` binary.
---
--- You can set the following VERBOSE environment variable to control
--- the verbosity of the output generated by this module.
-module PackageTests.PackageTester
-    ( TestsPaths(..)
-    , Result(..)
-
-    , packageTestsDirectory
-    , packageTestsConfigFile
-
-    -- * Running cabal commands
-    , cabal_clean
-    , cabal_exec
-    , cabal_freeze
-    , cabal_install
-    , cabal_sandbox
-    , run
-
-    -- * Test helpers
-    , assertCleanSucceeded
-    , assertExecFailed
-    , assertExecSucceeded
-    , assertFreezeSucceeded
-    , assertInstallSucceeded
-    , assertSandboxSucceeded
-    ) where
-
-import qualified Control.Exception.Extensible as E
-import Control.Monad (when, unless)
-import Data.Maybe (fromMaybe)
-import System.Directory (canonicalizePath, doesFileExist)
-import System.Environment (getEnv)
-import System.Exit (ExitCode(ExitSuccess))
-import System.FilePath ( (<.>)  )
-import System.IO (hClose, hGetChar, hIsEOF)
-import System.IO.Error (isDoesNotExistError)
-import System.Process (runProcess, waitForProcess)
-import Test.Tasty.HUnit (Assertion, assertFailure)
-
-import Distribution.Simple.BuildPaths (exeExtension)
-import Distribution.Simple.Utils (printRawCommandAndArgs)
-import Distribution.Compat.CreatePipe (createPipe)
-import Distribution.ReadE (readEOrFail)
-import Distribution.Verbosity (Verbosity, flagToVerbosity, normal)
-
-data Success = Failure
-             -- | ConfigureSuccess
-             -- | BuildSuccess
-             -- | TestSuccess
-             -- | BenchSuccess
-             | CleanSuccess
-             | ExecSuccess
-             | FreezeSuccess
-             | InstallSuccess
-             | SandboxSuccess
-             deriving (Eq, Show)
-
-data TestsPaths = TestsPaths
-    { cabalPath  :: FilePath -- ^ absolute path to cabal executable.
-    , ghcPkgPath :: FilePath -- ^ absolute path to ghc-pkg executable.
-    , configPath :: FilePath -- ^ absolute path of the default config file
-                             --   to use for tests (tests are free to use
-                             --   a different one).
-    }
-
-data Result = Result
-    { successful :: Bool
-    , success    :: Success
-    , outputText :: String
-    } deriving Show
-
-nullResult :: Result
-nullResult = Result True Failure ""
-
-------------------------------------------------------------------------
--- * Config
-
-packageTestsDirectory :: FilePath
-packageTestsDirectory = "PackageTests"
-
-packageTestsConfigFile :: FilePath
-packageTestsConfigFile = "cabal-config"
-
-------------------------------------------------------------------------
--- * Running cabal commands
-
-recordRun :: (String, ExitCode, String) -> Success -> Result -> Result
-recordRun (cmd, exitCode, exeOutput) thisSucc res =
-    res { successful = successful res && exitCode == ExitSuccess
-        , success    = if exitCode == ExitSuccess then thisSucc
-                       else success res
-        , outputText =
-            (if null $ outputText res then "" else outputText res ++ "\n") ++
-            cmd ++ "\n" ++ exeOutput
-        }
-
--- | Run the clean command and return its result.
-cabal_clean :: TestsPaths -> FilePath -> [String] -> IO Result
-cabal_clean paths dir args = do
-    res <- cabal paths dir (["clean"] ++ args)
-    return $ recordRun res CleanSuccess nullResult
-
--- | Run the exec command and return its result.
-cabal_exec :: TestsPaths -> FilePath -> [String] -> IO Result
-cabal_exec paths dir args = do
-    res <- cabal paths dir (["exec"] ++ args)
-    return $ recordRun res ExecSuccess nullResult
-
--- | Run the freeze command and return its result.
-cabal_freeze :: TestsPaths -> FilePath -> [String] -> IO Result
-cabal_freeze paths dir args = do
-    res <- cabal paths dir (["freeze"] ++ args)
-    return $ recordRun res FreezeSuccess nullResult
-
--- | Run the install command and return its result.
-cabal_install :: TestsPaths -> FilePath -> [String] -> IO Result
-cabal_install paths dir args = do
-    res <- cabal paths dir (["install"] ++ args)
-    return $ recordRun res InstallSuccess nullResult
-
--- | Run the sandbox command and return its result.
-cabal_sandbox :: TestsPaths -> FilePath -> [String] -> IO Result
-cabal_sandbox paths dir args = do
-    res <- cabal paths dir (["sandbox"] ++ args)
-    return $ recordRun res SandboxSuccess nullResult
-
--- | Returns the command that was issued, the return code, and the output text.
-cabal :: TestsPaths -> FilePath -> [String] -> IO (String, ExitCode, String)
-cabal paths dir cabalArgs = do
-    run (Just dir) (cabalPath paths) args
-  where
-    args = configFileArg : cabalArgs
-    configFileArg = "--config-file=" ++ configPath paths
-
--- | Returns the command that was issued, the return code, and the output text
-run :: Maybe FilePath -> String -> [String] -> IO (String, ExitCode, String)
-run cwd path args = do
-    verbosity <- getVerbosity
-    -- path is relative to the current directory; canonicalizePath makes it
-    -- absolute, so that runProcess will find it even when changing directory.
-    path' <- do pathExists <- doesFileExist path
-                canonicalizePath (if pathExists then path else path <.> exeExtension)
-    printRawCommandAndArgs verbosity path' args
-    (readh, writeh) <- createPipe
-    pid <- runProcess path' args cwd Nothing Nothing (Just writeh) (Just writeh)
-
-    -- fork off a thread to start consuming the output
-    out <- suckH [] readh
-    hClose readh
-
-    -- wait for the program to terminate
-    exitcode <- waitForProcess pid
-    let fullCmd = unwords (path' : args)
-    return ("\"" ++ fullCmd ++ "\" in " ++ fromMaybe "" cwd, exitcode, out)
-  where
-    suckH output h = do
-        eof <- hIsEOF h
-        if eof
-            then return (reverse output)
-            else do
-                c <- hGetChar h
-                suckH (c:output) h
-
-------------------------------------------------------------------------
--- * Test helpers
-
-assertCleanSucceeded :: Result -> Assertion
-assertCleanSucceeded result = unless (successful result) $
-    assertFailure $
-    "expected: \'cabal clean\' should succeed\n" ++
-    "  output: " ++ outputText result
-
-assertExecSucceeded :: Result -> Assertion
-assertExecSucceeded result = unless (successful result) $
-    assertFailure $
-    "expected: \'cabal exec\' should succeed\n" ++
-    "  output: " ++ outputText result
-
-assertExecFailed :: Result -> Assertion
-assertExecFailed result = when (successful result) $
-    assertFailure $
-    "expected: \'cabal exec\' should fail\n" ++
-    "  output: " ++ outputText result
-
-assertFreezeSucceeded :: Result -> Assertion
-assertFreezeSucceeded result = unless (successful result) $
-    assertFailure $
-    "expected: \'cabal freeze\' should succeed\n" ++
-    "  output: " ++ outputText result
-
-assertInstallSucceeded :: Result -> Assertion
-assertInstallSucceeded result = unless (successful result) $
-    assertFailure $
-    "expected: \'cabal install\' should succeed\n" ++
-    "  output: " ++ outputText result
-
-assertSandboxSucceeded :: Result -> Assertion
-assertSandboxSucceeded result = unless (successful result) $
-    assertFailure $
-    "expected: \'cabal sandbox\' should succeed\n" ++
-    "  output: " ++ outputText result
-
-------------------------------------------------------------------------
--- Verbosity
-
-lookupEnv :: String -> IO (Maybe String)
-lookupEnv name =
-    (fmap Just $ getEnv name)
-    `E.catch` \ (e :: IOError) ->
-        if isDoesNotExistError e
-        then return Nothing
-        else E.throw e
-
--- TODO: Convert to a "-v" flag instead.
-getVerbosity :: IO Verbosity
-getVerbosity = do
-    maybe normal (readEOrFail flagToVerbosity) `fmap` lookupEnv "VERBOSE"
diff --git a/cabal/cabal-install/tests/README b/cabal/cabal-install/tests/README
deleted file mode 100644
--- a/cabal/cabal-install/tests/README
+++ /dev/null
@@ -1,1 +0,0 @@
-See Cabal/tests/README.
diff --git a/cabal/cabal-install/tests/README.md b/cabal/cabal-install/tests/README.md
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/README.md
@@ -0,0 +1,27 @@
+Integration Tests
+=================
+
+Each test is a shell script.  Tests that share files (e.g., `.cabal` files) are
+grouped under a common sub-directory of [IntegrationTests].  The framework
+copies the whole group's directory before running each test, which allows tests
+to reuse files, yet run independently.  A group's tests are further divided into
+`should_run` and `should_fail` directories, based on the expected exit status.
+For example, the test
+`IntegrationTests/exec/should_fail/exit_with_failure_without_args.sh` has access
+to all files under `exec` and is expected to fail.
+
+Tests can specify their expected output.  For a test named `x.sh`, `x.out`
+specifies `stdout` and `x.err` specifies `stderr`.  Both files are optional.
+The framework expects an exact match between lines in the file and output,
+except for lines beginning with "RE:", which are interpreted as regular
+expressions.
+
+[IntegrationTests.hs] defines several environment variables:
+
+* `CABAL` - The path to the executable being tested.
+* `GHC_PKG` - The path to ghc-pkg.
+* `CABAL_ARGS` - A common set of arguments for running cabal.
+* `CABAL_ARGS_NO_CONFIG_FILE` - `CABAL_ARGS` without `--config-file`.
+
+[IntegrationTests]: IntegrationTests
+[IntegrationTests.hs]: IntegrationTests.hs
diff --git a/cabal/cabal-install/tests/SolverQuickCheck.hs b/cabal/cabal-install/tests/SolverQuickCheck.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/SolverQuickCheck.hs
@@ -0,0 +1,16 @@
+module Main where
+
+import Test.Tasty
+
+import qualified UnitTests.Distribution.Client.Dependency.Modular.QuickCheck
+
+
+tests :: TestTree
+tests =
+  testGroup "Solver QuickCheck"
+  [ testGroup "UnitTests.Distribution.Client.Dependency.Modular.QuickCheck"
+        UnitTests.Distribution.Client.Dependency.Modular.QuickCheck.tests
+  ]
+
+main :: IO ()
+main = defaultMain tests
diff --git a/cabal/cabal-install/tests/UnitTests.hs b/cabal/cabal-install/tests/UnitTests.hs
--- a/cabal/cabal-install/tests/UnitTests.hs
+++ b/cabal/cabal-install/tests/UnitTests.hs
@@ -1,39 +1,106 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
 module Main
        where
 
 import Test.Tasty
-import Test.Tasty.Options
 
-import qualified UnitTests.Distribution.Client.Sandbox
-import qualified UnitTests.Distribution.Client.UserConfig
-import qualified UnitTests.Distribution.Client.Targets
-import qualified UnitTests.Distribution.Client.GZipUtils
+import Control.Monad
+import Data.Time.Clock
+import System.FilePath
+
+import Distribution.Simple.Utils
+import Distribution.Verbosity
+
+import Distribution.Client.Compat.Time
+
+import qualified UnitTests.Distribution.Client.Compat.Time
 import qualified UnitTests.Distribution.Client.Dependency.Modular.PSQ
 import qualified UnitTests.Distribution.Client.Dependency.Modular.Solver
+import qualified UnitTests.Distribution.Client.FileMonitor
+import qualified UnitTests.Distribution.Client.Glob
+import qualified UnitTests.Distribution.Client.GZipUtils
+import qualified UnitTests.Distribution.Client.Sandbox
+import qualified UnitTests.Distribution.Client.Sandbox.Timestamp
+import qualified UnitTests.Distribution.Client.Tar
+import qualified UnitTests.Distribution.Client.Targets
+import qualified UnitTests.Distribution.Client.UserConfig
+import qualified UnitTests.Distribution.Client.ProjectConfig
 
-tests :: TestTree
-tests = testGroup "Unit Tests" [
-   testGroup "UnitTests.Distribution.Client.UserConfig"
-       UnitTests.Distribution.Client.UserConfig.tests
-  ,testGroup "Distribution.Client.Sandbox"
-       UnitTests.Distribution.Client.Sandbox.tests
-  ,testGroup "Distribution.Client.Targets"
-       UnitTests.Distribution.Client.Targets.tests
-  ,testGroup "Distribution.Client.GZipUtils"
-       UnitTests.Distribution.Client.GZipUtils.tests
-  ,testGroup "UnitTests.Distribution.Client.Dependency.Modular.PSQ"
+import UnitTests.Options
+
+
+tests :: Int -> TestTree
+tests mtimeChangeCalibrated =
+  askOption $ \(OptionMtimeChangeDelay mtimeChangeProvided) ->
+  let mtimeChange = if mtimeChangeProvided /= 0
+                    then mtimeChangeProvided
+                    else mtimeChangeCalibrated
+  in
+  testGroup "Unit Tests"
+  [ testGroup "UnitTests.Distribution.Client.Compat.Time" $
+        UnitTests.Distribution.Client.Compat.Time.tests mtimeChange
+  , testGroup "UnitTests.Distribution.Client.Dependency.Modular.PSQ"
         UnitTests.Distribution.Client.Dependency.Modular.PSQ.tests
-  ,testGroup "UnitTests.Distribution.Client.Dependency.Modular.Solver"
+  , testGroup "UnitTests.Distribution.Client.Dependency.Modular.Solver"
         UnitTests.Distribution.Client.Dependency.Modular.Solver.tests
-  ]
-
--- Extra options for running the test suite
-extraOptions :: [OptionDescription]
-extraOptions = concat [
-    UnitTests.Distribution.Client.Dependency.Modular.Solver.options
+  , testGroup "UnitTests.Distribution.Client.FileMonitor" $
+        UnitTests.Distribution.Client.FileMonitor.tests mtimeChange
+  , testGroup "UnitTests.Distribution.Client.Glob"
+        UnitTests.Distribution.Client.Glob.tests
+  , testGroup "Distribution.Client.GZipUtils"
+       UnitTests.Distribution.Client.GZipUtils.tests
+  , testGroup "Distribution.Client.Sandbox"
+       UnitTests.Distribution.Client.Sandbox.tests
+  , testGroup "Distribution.Client.Sandbox.Timestamp"
+       UnitTests.Distribution.Client.Sandbox.Timestamp.tests
+  , testGroup "Distribution.Client.Tar"
+       UnitTests.Distribution.Client.Tar.tests
+  , testGroup "Distribution.Client.Targets"
+       UnitTests.Distribution.Client.Targets.tests
+  , testGroup "UnitTests.Distribution.Client.UserConfig"
+       UnitTests.Distribution.Client.UserConfig.tests
+  , testGroup "UnitTests.Distribution.Client.ProjectConfig"
+       UnitTests.Distribution.Client.ProjectConfig.tests
   ]
 
 main :: IO ()
-main = defaultMainWithIngredients
+main = do
+  mtimeChangeDelay <- calibrateMtimeChangeDelay
+  defaultMainWithIngredients
          (includingOptions extraOptions : defaultIngredients)
-         tests
+         (tests mtimeChangeDelay)
+
+-- Based on code written by Neill Mitchell for Shake. See
+-- 'sleepFileTimeCalibrate' in 'Test.Type'. The returned delay is never smaller
+-- than 10 ms, but never larger than 1 second.
+calibrateMtimeChangeDelay :: IO Int
+calibrateMtimeChangeDelay = do
+  withTempDirectory silent "." "calibration-" $ \dir -> do
+    let fileName = dir </> "probe"
+    mtimes <- forM [1..25] $ \(i::Int) -> time $ do
+      writeFile fileName $ show i
+      t0 <- getModTime fileName
+      let spin j = do
+            writeFile fileName $ show (i,j)
+            t1 <- getModTime fileName
+            unless (t0 < t1) (spin $ j + 1)
+      spin (0::Int)
+    let mtimeChange  = maximum mtimes
+        mtimeChange' = min 1000000 $ (max 10000 mtimeChange) * 2
+    notice normal $ "File modification time resolution calibration completed, "
+      ++ "maximum delay observed: "
+      ++ (show . toMillis $ mtimeChange ) ++ " ms. "
+      ++ "Will be using delay of " ++ (show . toMillis $ mtimeChange')
+      ++ " for test runs."
+    return mtimeChange'
+  where
+    toMillis :: Int -> Double
+    toMillis x = fromIntegral x / 1000.0
+
+    time :: IO () -> IO Int
+    time act = do
+      t0 <- getCurrentTime
+      act
+      t1 <- getCurrentTime
+      return . ceiling $! (t1 `diffUTCTime` t0) * 1e6 -- microseconds
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module UnitTests.Distribution.Client.ArbitraryInstances (
+    adjustSize,
+    shortListOf,
+    shortListOf1,
+    arbitraryFlag,
+    ShortToken(..),
+    arbitraryShortToken,
+    NonMEmpty(..),
+    NoShrink(..),
+  ) where
+
+import Data.Char
+import Data.List
+#if !MIN_VERSION_base(4,8,0)
+import Data.Monoid
+import Control.Applicative
+#endif
+import Control.Monad
+
+import Distribution.Version
+import Distribution.Package
+import Distribution.System
+import Distribution.Verbosity
+
+import Distribution.Simple.Setup
+import Distribution.Simple.InstallDirs
+
+import Distribution.Utils.NubList
+
+import Test.QuickCheck
+
+
+adjustSize :: (Int -> Int) -> Gen a -> Gen a
+adjustSize adjust gen = sized (\n -> resize (adjust n) gen)
+
+shortListOf :: Int -> Gen a -> Gen [a]
+shortListOf bound gen =
+    sized $ \n -> do
+      k <- choose (0, (n `div` 2) `min` bound)
+      vectorOf k gen
+
+shortListOf1 :: Int -> Gen a -> Gen [a]
+shortListOf1 bound gen =
+    sized $ \n -> do
+      k <- choose (1, 1 `max` ((n `div` 2) `min` bound))
+      vectorOf k gen
+
+newtype ShortToken = ShortToken { getShortToken :: String }
+  deriving Show
+
+instance Arbitrary ShortToken where
+  arbitrary =
+    ShortToken <$>
+      (shortListOf1 5 (choose ('#', '~'))
+       `suchThat` (not . ("[]" `isPrefixOf`)))
+    --TODO: [code cleanup] need to replace parseHaskellString impl to stop
+    -- accepting Haskell list syntax [], ['a'] etc, just allow String syntax.
+    -- Workaround, don't generate [] as this does not round trip.
+
+
+  shrink (ShortToken cs) =
+    [ ShortToken cs' | cs' <- shrink cs, not (null cs') ]
+
+arbitraryShortToken :: Gen String
+arbitraryShortToken = getShortToken <$> arbitrary
+
+instance Arbitrary Version where
+  arbitrary = do
+    branch <- shortListOf1 4 $
+                frequency [(3, return 0)
+                          ,(3, return 1)
+                          ,(2, return 2)
+                          ,(1, return 3)]
+    return (Version branch []) -- deliberate []
+    where
+
+  shrink (Version branch []) =
+    [ Version branch' [] | branch' <- shrink branch, not (null branch') ]
+  shrink (Version branch _tags) =
+    [ Version branch [] ]
+
+instance Arbitrary VersionRange where
+  arbitrary = canonicaliseVersionRange <$> sized verRangeExp
+    where
+      verRangeExp n = frequency $
+        [ (2, return anyVersion)
+        , (1, liftM thisVersion arbitrary)
+        , (1, liftM laterVersion arbitrary)
+        , (1, liftM orLaterVersion arbitrary)
+        , (1, liftM orLaterVersion' arbitrary)
+        , (1, liftM earlierVersion arbitrary)
+        , (1, liftM orEarlierVersion arbitrary)
+        , (1, liftM orEarlierVersion' arbitrary)
+        , (1, liftM withinVersion arbitrary)
+        , (2, liftM VersionRangeParens arbitrary)
+        ] ++ if n == 0 then [] else
+        [ (2, liftM2 unionVersionRanges     verRangeExp2 verRangeExp2)
+        , (2, liftM2 intersectVersionRanges verRangeExp2 verRangeExp2)
+        ]
+        where
+          verRangeExp2 = verRangeExp (n `div` 2)
+
+      orLaterVersion'   v =
+        unionVersionRanges (laterVersion v)   (thisVersion v)
+      orEarlierVersion' v =
+        unionVersionRanges (earlierVersion v) (thisVersion v)
+
+      canonicaliseVersionRange = fromVersionIntervals . toVersionIntervals
+
+instance Arbitrary PackageName where
+    arbitrary = PackageName . intercalate "-" <$> shortListOf1 2 nameComponent
+      where
+        nameComponent = shortListOf1 5 (elements packageChars)
+                        `suchThat` (not . all isDigit)
+        packageChars  = filter isAlphaNum ['\0'..'\127']
+
+instance Arbitrary Dependency where
+    arbitrary = Dependency <$> arbitrary <*> arbitrary
+
+instance Arbitrary OS where
+    arbitrary = elements knownOSs
+
+instance Arbitrary Arch where
+    arbitrary = elements knownArches
+
+instance Arbitrary Platform where
+    arbitrary = Platform <$> arbitrary <*> arbitrary
+
+instance Arbitrary a => Arbitrary (Flag a) where
+    arbitrary = arbitraryFlag arbitrary
+    shrink NoFlag   = []
+    shrink (Flag x) = NoFlag : [ Flag x' | x' <- shrink x ]
+
+arbitraryFlag :: Gen a -> Gen (Flag a)
+arbitraryFlag genA =
+    sized $ \sz ->
+      case sz of
+        0 -> pure NoFlag
+        _ -> frequency [ (1, pure NoFlag)
+                       , (3, Flag <$> genA) ]
+
+
+instance (Arbitrary a, Ord a) => Arbitrary (NubList a) where
+    arbitrary = toNubList <$> arbitrary
+    shrink xs = [ toNubList [] | (not . null) (fromNubList xs) ]
+    -- try empty, otherwise don't shrink as it can loop
+
+instance Arbitrary Verbosity where
+    arbitrary = elements [minBound..maxBound]
+
+instance Arbitrary PathTemplate where
+    arbitrary = toPathTemplate <$> arbitraryShortToken
+    shrink t  = [ toPathTemplate s | s <- shrink (show t), not (null s) ]
+
+
+newtype NonMEmpty a = NonMEmpty { getNonMEmpty :: a }
+  deriving (Eq, Ord, Show)
+
+instance (Arbitrary a, Monoid a, Eq a) => Arbitrary (NonMEmpty a) where
+  arbitrary = NonMEmpty <$> (arbitrary `suchThat` (/= mempty))
+  shrink (NonMEmpty x) = [ NonMEmpty x' | x' <- shrink x, x' /= mempty ]
+
+newtype NoShrink a = NoShrink { getNoShrink :: a }
+  deriving (Eq, Ord, Show)
+
+instance Arbitrary a => Arbitrary (NoShrink a) where
+    arbitrary = NoShrink <$> arbitrary
+    shrink _  = []
+
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Compat/Time.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Compat/Time.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Compat/Time.hs
@@ -0,0 +1,49 @@
+module UnitTests.Distribution.Client.Compat.Time (tests) where
+
+import Control.Concurrent (threadDelay)
+import System.FilePath
+
+import Distribution.Simple.Utils (withTempDirectory)
+import Distribution.Verbosity
+
+import Distribution.Client.Compat.Time
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+tests :: Int -> [TestTree]
+tests mtimeChange =
+  [ testCase "getModTime has sub-second resolution" $ getModTimeTest mtimeChange
+  , testCase "getCurTime works as expected"         $ getCurTimeTest mtimeChange
+  ]
+
+getModTimeTest :: Int -> Assertion
+getModTimeTest mtimeChange =
+  withTempDirectory silent "." "getmodtime-" $ \dir -> do
+    let fileName = dir </> "foo"
+    writeFile fileName "bar"
+    t0 <- getModTime fileName
+    threadDelay mtimeChange
+    writeFile fileName "baz"
+    t1 <- getModTime fileName
+    assertBool "expected different file mtimes" (t1 > t0)
+
+
+getCurTimeTest :: Int -> Assertion
+getCurTimeTest mtimeChange =
+  withTempDirectory silent "." "getmodtime-" $ \dir -> do
+    let fileName = dir </> "foo"
+    writeFile fileName "bar"
+    t0 <- getModTime fileName
+    threadDelay mtimeChange
+    t1 <- getCurTime
+    assertBool("expected file mtime (" ++ show t0
+               ++ ") to be earlier than current time (" ++ show t1 ++ ")")
+      (t0 < t1)
+
+    threadDelay mtimeChange
+    writeFile fileName "baz"
+    t2 <- getModTime fileName
+    assertBool ("expected current time (" ++ show t1
+                ++ ") to be earlier than file mtime (" ++ show t2 ++ ")")
+      (t1 < t2)
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs
--- a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs
@@ -2,17 +2,31 @@
 -- | DSL for testing the modular solver
 module UnitTests.Distribution.Client.Dependency.Modular.DSL (
     ExampleDependency(..)
+  , Dependencies(..)
+  , ExTest(..)
+  , ExPreference(..)
   , ExampleDb
+  , ExampleVersionRange
+  , ExamplePkgVersion
+  , ExamplePkgName
+  , ExampleAvailable(..)
+  , ExampleInstalled(..)
+  , IndepGoals(..)
+  , ReorderGoals(..)
   , exAv
   , exInst
+  , exFlag
   , exResolve
   , extractInstallPlan
   , withSetupDeps
+  , withTest
+  , withTests
   ) where
 
 -- base
 import Data.Either (partitionEithers)
 import Data.Maybe (catMaybes)
+import Data.List (nub)
 import Data.Monoid
 import Data.Version
 import qualified Data.Map as Map
@@ -21,11 +35,12 @@
 import qualified Distribution.Compiler             as C
 import qualified Distribution.InstalledPackageInfo as C
 import qualified Distribution.Package              as C
-  hiding (HasInstalledPackageId(..))
+  hiding (HasUnitId(..))
 import qualified Distribution.PackageDescription   as C
 import qualified Distribution.Simple.PackageIndex  as C.PackageIndex
 import qualified Distribution.System               as C
 import qualified Distribution.Version              as C
+import Language.Haskell.Extension (Extension(..), Language)
 
 -- cabal-install
 import Distribution.Client.ComponentDeps (ComponentDeps)
@@ -34,6 +49,7 @@
 import Distribution.Client.Types
 import qualified Distribution.Client.InstallPlan   as CI.InstallPlan
 import qualified Distribution.Client.PackageIndex  as CI.PackageIndex
+import qualified Distribution.Client.PkgConfigDb   as PC
 import qualified Distribution.Client.ComponentDeps as CD
 
 {-------------------------------------------------------------------------------
@@ -59,7 +75,7 @@
   * The difference between `GenericPackageDescription` and `PackageDescription`
     is that `PackageDescription` describes a particular _configuration_ of a
     package (for instance, see documentation for `checkPackage`). A
-    `GenericPackageDescription` can be returned into a `PackageDescription` in
+    `GenericPackageDescription` can be turned into a `PackageDescription` in
     two ways:
 
       a. `finalizePackageDescription` does the proper translation, by taking
@@ -83,7 +99,11 @@
 type ExamplePkgHash    = String  -- for example "installed" packages
 type ExampleFlagName   = String
 type ExampleTestName   = String
+type ExampleVersionRange = C.VersionRange
 
+data Dependencies = NotBuildable | Buildable [ExampleDependency]
+  deriving Show
+
 data ExampleDependency =
     -- | Simple dependency on any version
     ExAny ExamplePkgName
@@ -92,56 +112,110 @@
   | ExFix ExamplePkgName ExamplePkgVersion
 
     -- | Dependencies indexed by a flag
-  | ExFlag ExampleFlagName [ExampleDependency] [ExampleDependency]
+  | ExFlag ExampleFlagName Dependencies Dependencies
 
-    -- | Dependency if tests are enabled
-  | ExTest ExampleTestName [ExampleDependency]
+    -- | Dependency on a language extension
+  | ExExt Extension
 
+    -- | Dependency on a language version
+  | ExLang Language
+
+    -- | Dependency on a pkg-config package
+  | ExPkg (ExamplePkgName, ExamplePkgVersion)
+  deriving Show
+
+data ExTest = ExTest ExampleTestName [ExampleDependency]
+
+exFlag :: ExampleFlagName -> [ExampleDependency] -> [ExampleDependency]
+       -> ExampleDependency
+exFlag n t e = ExFlag n (Buildable t) (Buildable e)
+
+data ExPreference = ExPref String ExampleVersionRange
+
 data ExampleAvailable = ExAv {
     exAvName    :: ExamplePkgName
   , exAvVersion :: ExamplePkgVersion
   , exAvDeps    :: ComponentDeps [ExampleDependency]
-  }
+  } deriving Show
 
+-- | Constructs an 'ExampleAvailable' package for the 'ExampleDb',
+-- given:
+--
+--      1. The name 'ExamplePkgName' of the available package,
+--      2. The version 'ExamplePkgVersion' available
+--      3. The list of dependency constraints 'ExampleDependency'
+--         that this package has.  'ExampleDependency' provides
+--         a number of pre-canned dependency types to look at.
+--
 exAv :: ExamplePkgName -> ExamplePkgVersion -> [ExampleDependency]
      -> ExampleAvailable
 exAv n v ds = ExAv { exAvName = n, exAvVersion = v
-                   , exAvDeps = CD.fromLibraryDeps ds }
+                   , exAvDeps = CD.fromLibraryDeps n ds }
 
 withSetupDeps :: ExampleAvailable -> [ExampleDependency] -> ExampleAvailable
 withSetupDeps ex setupDeps = ex {
       exAvDeps = exAvDeps ex <> CD.fromSetupDeps setupDeps
     }
 
+withTest :: ExampleAvailable -> ExTest -> ExampleAvailable
+withTest ex test = withTests ex [test]
+
+withTests :: ExampleAvailable -> [ExTest] -> ExampleAvailable
+withTests ex tests =
+  let testCDs = CD.fromList [(CD.ComponentTest name, deps)
+                            | ExTest name deps <- tests]
+  in ex { exAvDeps = exAvDeps ex <> testCDs }
+
+-- | An installed package in 'ExampleDb'; construct me with 'exInst'.
 data ExampleInstalled = ExInst {
     exInstName         :: ExamplePkgName
   , exInstVersion      :: ExamplePkgVersion
   , exInstHash         :: ExamplePkgHash
-  , exInstBuildAgainst :: [ExampleInstalled]
-  }
+  , exInstBuildAgainst :: [ExamplePkgHash]
+  } deriving Show
 
+-- | Constructs an example installed package given:
+--
+--      1. The name of the package 'ExamplePkgName', i.e., 'String'
+--      2. The version of the package 'ExamplePkgVersion', i.e., 'Int'
+--      3. The IPID for the package 'ExamplePkgHash', i.e., 'String'
+--         (just some unique identifier for the package.)
+--      4. The 'ExampleInstalled' packages which this package was
+--         compiled against.)
+--
 exInst :: ExamplePkgName -> ExamplePkgVersion -> ExamplePkgHash
        -> [ExampleInstalled] -> ExampleInstalled
-exInst = ExInst
+exInst pn v hash deps = ExInst pn v hash (map exInstHash deps)
 
+-- | An example package database is a list of installed packages
+-- 'ExampleInstalled' and available packages 'ExampleAvailable'.
+-- Generally, you want to use 'exInst' and 'exAv' to construct
+-- these packages.
 type ExampleDb = [Either ExampleInstalled ExampleAvailable]
 
 type DependencyTree a = C.CondTree C.ConfVar [C.Dependency] a
 
+newtype IndepGoals = IndepGoals Bool
+  deriving Show
+
+newtype ReorderGoals = ReorderGoals Bool
+  deriving Show
+
 exDbPkgs :: ExampleDb -> [ExamplePkgName]
 exDbPkgs = map (either exInstName exAvName)
 
-exAvSrcPkg :: ExampleAvailable -> SourcePackage
+exAvSrcPkg :: ExampleAvailable -> UnresolvedSourcePackage
 exAvSrcPkg ex =
-    let (libraryDeps, testSuites) = splitTopLevel (CD.libraryDeps (exAvDeps ex))
+    let (libraryDeps, exts, mlang, pcpkgs) = splitTopLevel (CD.libraryDeps (exAvDeps ex))
+        testSuites = [(name, deps) | (CD.ComponentTest name, deps) <- CD.toList (exAvDeps ex)]
     in SourcePackage {
            packageInfoId        = exAvPkgId ex
          , packageSource        = LocalTarballPackage "<<path>>"
          , packageDescrOverride = Nothing
-         , packageDescription   = C.GenericPackageDescription{
+         , packageDescription   = C.GenericPackageDescription {
                C.packageDescription = C.emptyPackageDescription {
                    C.package        = exAvPkgId ex
-                 , C.library        = error "not yet configured: library"
+                 , C.libraries      = error "not yet configured: library"
                  , C.executables    = error "not yet configured: executables"
                  , C.testSuites     = error "not yet configured: testSuites"
                  , C.benchmarks     = error "not yet configured: benchmarks"
@@ -150,47 +224,75 @@
                        C.setupDepends = mkSetupDeps (CD.setupDeps (exAvDeps ex))
                      }
                  }
-             , C.genPackageFlags = concatMap extractFlags
-                                   (CD.libraryDeps (exAvDeps ex))
-             , C.condLibrary     = Just $ mkCondTree libraryDeps
+             , C.genPackageFlags = nub $ concatMap extractFlags $
+                                   CD.libraryDeps (exAvDeps ex) ++ concatMap snd testSuites
+             , C.condLibraries   = [(exAvName ex, mkCondTree (extsLib exts <> langLib mlang <> pcpkgLib pcpkgs)
+                                                     disableLib
+                                                     (Buildable libraryDeps))]
              , C.condExecutables = []
-             , C.condTestSuites  = map (\(t, deps) -> (t, mkCondTree deps))
-                                   testSuites
+             , C.condTestSuites  =
+                 let mkTree = mkCondTree mempty disableTest . Buildable
+                 in map (\(t, deps) -> (t, mkTree deps)) testSuites
              , C.condBenchmarks  = []
              }
          }
   where
+    -- Split the set of dependencies into the set of dependencies of the library,
+    -- the dependencies of the test suites and extensions.
     splitTopLevel :: [ExampleDependency]
                   -> ( [ExampleDependency]
-                     , [(ExampleTestName, [ExampleDependency])]
+                     , [Extension]
+                     , Maybe Language
+                     , [(ExamplePkgName, ExamplePkgVersion)] -- pkg-config
                      )
-    splitTopLevel []                = ([], [])
-    splitTopLevel (ExTest t a:deps) =
-      let (other, testSuites) = splitTopLevel deps
-      in (other, (t, a):testSuites)
-    splitTopLevel (dep:deps)        =
-      let (other, testSuites) = splitTopLevel deps
-      in (dep:other, testSuites)
+    splitTopLevel [] =
+        ([], [], Nothing, [])
+    splitTopLevel (ExExt ext:deps) =
+      let (other, exts, lang, pcpkgs) = splitTopLevel deps
+      in (other, ext:exts, lang, pcpkgs)
+    splitTopLevel (ExLang lang:deps) =
+        case splitTopLevel deps of
+            (other, exts, Nothing, pcpkgs) -> (other, exts, Just lang, pcpkgs)
+            _ -> error "Only 1 Language dependency is supported"
+    splitTopLevel (ExPkg pkg:deps) =
+      let (other, exts, lang, pcpkgs) = splitTopLevel deps
+      in (other, exts, lang, pkg:pcpkgs)
+    splitTopLevel (dep:deps) =
+      let (other, exts, lang, pcpkgs) = splitTopLevel deps
+      in (dep:other, exts, lang, pcpkgs)
 
+    -- Extract the total set of flags used
     extractFlags :: ExampleDependency -> [C.Flag]
     extractFlags (ExAny _)      = []
     extractFlags (ExFix _ _)    = []
     extractFlags (ExFlag f a b) = C.MkFlag {
                                       C.flagName        = C.FlagName f
                                     , C.flagDescription = ""
-                                    , C.flagDefault     = False
+                                    , C.flagDefault     = True
                                     , C.flagManual      = False
                                     }
-                                : concatMap extractFlags (a ++ b)
-    extractFlags (ExTest _ a)   = concatMap extractFlags a
+                                : concatMap extractFlags (deps a ++ deps b)
+      where
+        deps :: Dependencies -> [ExampleDependency]
+        deps NotBuildable = []
+        deps (Buildable ds) = ds
+    extractFlags (ExExt _)      = []
+    extractFlags (ExLang _)     = []
+    extractFlags (ExPkg _)      = []
 
-    mkCondTree :: Monoid a => [ExampleDependency] -> DependencyTree a
-    mkCondTree deps =
+    mkCondTree :: Monoid a => a -> (a -> a) -> Dependencies -> DependencyTree a
+    mkCondTree x dontBuild NotBuildable =
+      C.CondNode {
+             C.condTreeData        = dontBuild x
+           , C.condTreeConstraints = []
+           , C.condTreeComponents  = []
+           }
+    mkCondTree x dontBuild (Buildable deps) =
       let (directDeps, flaggedDeps) = splitDeps deps
       in C.CondNode {
-             C.condTreeData        = mempty -- irrelevant to the solver
-           , C.condTreeConstraints = map mkDirect  directDeps
-           , C.condTreeComponents  = map mkFlagged flaggedDeps
+             C.condTreeData        = x -- Necessary for language extensions
+           , C.condTreeConstraints = map mkDirect directDeps
+           , C.condTreeComponents  = map (mkFlagged dontBuild) flaggedDeps
            }
 
     mkDirect :: (ExamplePkgName, Maybe ExamplePkgVersion) -> C.Dependency
@@ -200,17 +302,25 @@
         v = Version [n, 0, 0] []
 
     mkFlagged :: Monoid a
-              => (ExampleFlagName, [ExampleDependency], [ExampleDependency])
+              => (a -> a)
+              -> (ExampleFlagName, Dependencies, Dependencies)
               -> (C.Condition C.ConfVar
                  , DependencyTree a, Maybe (DependencyTree a))
-    mkFlagged (f, a, b) = ( C.Var (C.Flag (C.FlagName f))
-                          , mkCondTree a
-                          , Just (mkCondTree b)
-                          )
+    mkFlagged dontBuild (f, a, b) = ( C.Var (C.Flag (C.FlagName f))
+                                    , mkCondTree mempty dontBuild a
+                                    , Just (mkCondTree mempty dontBuild b)
+                                    )
 
+    -- Split a set of dependencies into direct dependencies and flagged
+    -- dependencies. A direct dependency is a tuple of the name of package and
+    -- maybe its version (no version means any version) meant to be converted
+    -- to a 'C.Dependency' with 'mkDirect' for example. A flagged dependency is
+    -- the set of dependencies guarded by a flag.
+    --
+    -- TODO: Take care of flagged language extensions and language flavours.
     splitDeps :: [ExampleDependency]
               -> ( [(ExamplePkgName, Maybe Int)]
-                 , [(ExampleFlagName, [ExampleDependency], [ExampleDependency])]
+                 , [(ExampleFlagName, Dependencies, Dependencies)]
                  )
     splitDeps [] =
       ([], [])
@@ -223,14 +333,34 @@
     splitDeps (ExFlag f a b:deps) =
       let (directDeps, flaggedDeps) = splitDeps deps
       in (directDeps, (f, a, b):flaggedDeps)
-    splitDeps (ExTest _ _:_) =
-      error "Unexpected nested test"
+    splitDeps (_:deps) = splitDeps deps
 
     -- Currently we only support simple setup dependencies
     mkSetupDeps :: [ExampleDependency] -> [C.Dependency]
     mkSetupDeps deps =
       let (directDeps, []) = splitDeps deps in map mkDirect directDeps
 
+    -- A 'C.Library' with just the given extensions in its 'BuildInfo'
+    extsLib :: [Extension] -> C.Library
+    extsLib es = mempty { C.libBuildInfo = mempty { C.otherExtensions = es } }
+
+    -- A 'C.Library' with just the given extensions in its 'BuildInfo'
+    langLib :: Maybe Language -> C.Library
+    langLib (Just lang) = mempty { C.libBuildInfo = mempty { C.defaultLanguage = Just lang } }
+    langLib _ = mempty
+
+    disableLib :: C.Library -> C.Library
+    disableLib lib =
+        lib { C.libBuildInfo = (C.libBuildInfo lib) { C.buildable = False }}
+
+    disableTest :: C.TestSuite -> C.TestSuite
+    disableTest test =
+        test { C.testBuildInfo = (C.testBuildInfo test) { C.buildable = False }}
+
+    -- A 'C.Library' with just the given pkgconfig-depends in its 'BuildInfo'
+    pcpkgLib :: [(ExamplePkgName, ExamplePkgVersion)] -> C.Library
+    pcpkgLib ds = mempty { C.libBuildInfo = mempty { C.pkgconfigDepends = [mkDirect (n, (Just v)) | (n,v) <- ds] } }
+
 exAvPkgId :: ExampleAvailable -> C.PackageIdentifier
 exAvPkgId ex = C.PackageIdentifier {
       pkgName    = C.PackageName (exAvName ex)
@@ -239,11 +369,9 @@
 
 exInstInfo :: ExampleInstalled -> C.InstalledPackageInfo
 exInstInfo ex = C.emptyInstalledPackageInfo {
-      C.installedPackageId = C.InstalledPackageId (exInstHash ex)
+      C.installedUnitId    = C.mkUnitId (exInstHash ex)
     , C.sourcePackageId    = exInstPkgId ex
-    , C.packageKey         = exInstKey ex
-    , C.depends            = map (C.InstalledPackageId . exInstHash)
-                                 (exInstBuildAgainst ex)
+    , C.depends            = map C.mkUnitId (exInstBuildAgainst ex)
     }
 
 exInstPkgId :: ExampleInstalled -> C.PackageIdentifier
@@ -252,46 +380,51 @@
     , pkgVersion = Version [exInstVersion ex, 0, 0] []
     }
 
-exInstLibName :: ExampleInstalled -> C.LibraryName
-exInstLibName ex = C.packageKeyLibraryName (exInstPkgId ex) (exInstKey ex)
-
-exInstKey :: ExampleInstalled -> C.PackageKey
-exInstKey ex =
-    C.mkPackageKey True
-                   (exInstPkgId ex)
-                   (map exInstLibName (exInstBuildAgainst ex))
-
-exAvIdx :: [ExampleAvailable] -> CI.PackageIndex.PackageIndex SourcePackage
+exAvIdx :: [ExampleAvailable] -> CI.PackageIndex.PackageIndex UnresolvedSourcePackage
 exAvIdx = CI.PackageIndex.fromList . map exAvSrcPkg
 
 exInstIdx :: [ExampleInstalled] -> C.PackageIndex.InstalledPackageIndex
 exInstIdx = C.PackageIndex.fromList . map exInstInfo
 
 exResolve :: ExampleDb
+          -- List of extensions supported by the compiler, or Nothing if unknown.
+          -> Maybe [Extension]
+          -- List of languages supported by the compiler, or Nothing if unknown.
+          -> Maybe [Language]
+          -> PC.PkgConfigDb
           -> [ExamplePkgName]
-          -> Bool
+          -> Solver
+          -> IndepGoals
+          -> ReorderGoals
+          -> [ExPreference]
           -> ([String], Either String CI.InstallPlan.InstallPlan)
-exResolve db targets indepGoals = runProgress $
+exResolve db exts langs pkgConfigDb targets solver (IndepGoals indepGoals) (ReorderGoals reorder) prefs = runProgress $
     resolveDependencies C.buildPlatform
-                        (C.unknownCompilerInfo C.buildCompilerId C.NoAbiTag)
-                        Modular
+                        compiler pkgConfigDb
+                        solver
                         params
   where
+    defaultCompiler = C.unknownCompilerInfo C.buildCompilerId C.NoAbiTag
+    compiler = defaultCompiler { C.compilerInfoExtensions = exts
+                               , C.compilerInfoLanguages  = langs
+                               }
     (inst, avai) = partitionEithers db
     instIdx      = exInstIdx inst
     avaiIdx      = SourcePackageDb {
                        packageIndex       = exAvIdx avai
                      , packagePreferences = Map.empty
                      }
-    enableTests  = map (\p -> PackageConstraintStanzas
+    enableTests  = fmap (\p -> PackageConstraintStanzas
                               (C.PackageName p) [TestStanzas])
                        (exDbPkgs db)
-    targets'     = map (\p -> NamedPackage (C.PackageName p) []) targets
-    params       = addConstraints (map toLpc enableTests)
-                 $ (standardInstallPolicy instIdx avaiIdx targets') {
-                       depResolverIndependentGoals = indepGoals
-                     }
+    targets'     = fmap (\p -> NamedPackage (C.PackageName p) []) targets
+    params       =   addPreferences (fmap toPref prefs)
+                   $ addConstraints (fmap toLpc enableTests)
+                   $ setIndependentGoals indepGoals
+                   $ setReorderGoals reorder
+                   $ standardInstallPolicy instIdx avaiIdx targets'
     toLpc     pc = LabeledPackageConstraint pc ConstraintSourceUnknown
+    toPref (ExPref n v) = PackageVersionPreference (C.PackageName n) v
 
 extractInstallPlan :: CI.InstallPlan.InstallPlan
                    -> [(ExamplePkgName, ExamplePkgVersion)]
@@ -301,10 +434,10 @@
     confPkg (CI.InstallPlan.Configured pkg) = Just $ srcPkg pkg
     confPkg _                               = Nothing
 
-    srcPkg :: ConfiguredPackage -> (String, Int)
-    srcPkg (ConfiguredPackage pkg _flags _stanzas _deps) =
+    srcPkg :: ConfiguredPackage UnresolvedPkgLoc -> (String, Int)
+    srcPkg cpkg =
       let C.PackageIdentifier (C.PackageName p) (Version (n:_) _) =
-            packageInfoId pkg
+            packageInfoId (confPkgSource cpkg)
       in (p, n)
 
 {-------------------------------------------------------------------------------
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/QuickCheck.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/QuickCheck.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/QuickCheck.hs
@@ -0,0 +1,291 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module UnitTests.Distribution.Client.Dependency.Modular.QuickCheck (tests) where
+
+import Control.Monad (foldM)
+import Data.Either (lefts)
+import Data.Function (on)
+import Data.List (groupBy, nub, sort)
+import Data.Maybe (isJust)
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative ((<$>), (<*>))
+import Data.Monoid (Monoid)
+#endif
+
+import Text.Show.Pretty (parseValue, valToStr)
+
+import Test.Tasty (TestTree)
+import Test.Tasty.QuickCheck
+
+import qualified Distribution.Client.ComponentDeps as CD
+import Distribution.Client.ComponentDeps ( Component(..)
+                                         , ComponentDep, ComponentDeps)
+import Distribution.Client.Dependency.Types (Solver(..))
+import Distribution.Client.PkgConfigDb (pkgConfigDbFromList)
+
+import UnitTests.Distribution.Client.Dependency.Modular.DSL
+
+tests :: [TestTree]
+tests = [
+      -- This test checks that certain solver parameters do not affect the
+      -- existence of a solution. It runs the solver twice, and only sets those
+      -- parameters on the second run. The test also applies parameters that
+      -- can affect the existence of a solution to both runs.
+      testProperty "target order and --reorder-goals do not affect solvability" $
+          \(SolverTest db targets) targetOrder reorderGoals indepGoals solver ->
+            let r1 = solve (ReorderGoals False) indepGoals solver targets  db
+                r2 = solve reorderGoals         indepGoals solver targets2 db
+                targets2 = case targetOrder of
+                             SameOrder -> targets
+                             ReverseOrder -> reverse targets
+            in counterexample (showResults r1 r2) $
+               isJust (resultPlan r1) === isJust (resultPlan r2)
+    ]
+  where
+    showResults :: Result -> Result -> String
+    showResults r1 r2 = showResult 1 r1 ++ showResult 2 r2
+
+    showResult :: Int -> Result -> String
+    showResult n result =
+        unlines $ ["", "Run " ++ show n ++ ":"]
+               ++ resultLog result
+               ++ ["result: " ++ show (resultPlan result)]
+
+solve :: ReorderGoals -> IndepGoals -> Solver -> [PN] -> TestDb -> Result
+solve reorder indep solver targets (TestDb db) =
+  let (lg, result) =
+        exResolve db Nothing Nothing
+                  (pkgConfigDbFromList [])
+                  (map unPN targets)
+                  solver indep reorder []
+  in Result {
+       resultLog = lg
+     , resultPlan =
+          case result of
+            Left _ -> Nothing
+            Right plan -> Just (extractInstallPlan plan)
+     }
+
+-- | How to modify the order of the input targets.
+data TargetOrder = SameOrder | ReverseOrder
+  deriving Show
+
+instance Arbitrary TargetOrder where
+  arbitrary = elements [SameOrder, ReverseOrder]
+
+  shrink SameOrder = []
+  shrink ReverseOrder = [SameOrder]
+
+data Result = Result {
+    resultLog :: [String]
+  , resultPlan :: Maybe [(ExamplePkgName, ExamplePkgVersion)]
+  }
+
+-- | Package name.
+newtype PN = PN { unPN :: String }
+  deriving (Eq, Ord, Show)
+
+instance Arbitrary PN where
+  arbitrary = PN <$> elements ("base" : [[pn] | pn <- ['A'..'G']])
+
+-- | Package version.
+newtype PV = PV { unPV :: Int }
+  deriving (Eq, Ord, Show)
+
+instance Arbitrary PV where
+  arbitrary = PV <$> elements [1..10]
+
+type TestPackage = Either ExampleInstalled ExampleAvailable
+
+getName :: TestPackage -> PN
+getName = PN . either exInstName exAvName
+
+getVersion :: TestPackage -> PV
+getVersion = PV . either exInstVersion exAvVersion
+
+data SolverTest = SolverTest {
+    testDb :: TestDb
+  , testTargets :: [PN]
+  }
+
+-- | Pretty-print the test when quickcheck calls 'show'.
+instance Show SolverTest where
+  show test =
+    let str = "SolverTest {testDb = " ++ show (testDb test)
+                     ++ ", testTargets = " ++ show (testTargets test) ++ "}"
+    in maybe str valToStr $ parseValue str
+
+instance Arbitrary SolverTest where
+  arbitrary = do
+    db <- arbitrary
+    let pkgs = nub $ map getName (unTestDb db)
+    Positive n <- arbitrary
+    targets <- randomSubset n pkgs
+    return (SolverTest db targets)
+
+  shrink test =
+         [test { testDb = db } | db <- shrink (testDb test)]
+      ++ [test { testTargets = targets } | targets <- shrink (testTargets test)]
+
+-- | Collection of source and installed packages.
+newtype TestDb = TestDb { unTestDb :: ExampleDb }
+  deriving Show
+
+instance Arbitrary TestDb where
+  arbitrary = do
+      -- Avoid cyclic dependencies by grouping packages by name and only
+      -- allowing each package to depend on packages in the groups before it.
+      groupedPkgs <- shuffle . groupBy ((==) `on` fst) . nub . sort =<<
+                     boundedListOf 10 arbitrary
+      db <- foldM nextPkgs (TestDb []) groupedPkgs
+      TestDb <$> shuffle (unTestDb db)
+    where
+      nextPkgs :: TestDb -> [(PN, PV)] -> Gen TestDb
+      nextPkgs db pkgs = TestDb . (++ unTestDb db) <$> mapM (nextPkg db) pkgs
+
+      nextPkg :: TestDb -> (PN, PV) -> Gen TestPackage
+      nextPkg db (pn, v) = do
+        installed <- arbitrary
+        if installed
+        then Left <$> arbitraryExInst pn v (lefts $ unTestDb db)
+        else Right <$> arbitraryExAv pn v db
+
+  shrink (TestDb pkgs) = map TestDb $ shrink pkgs
+
+arbitraryExAv :: PN -> PV -> TestDb -> Gen ExampleAvailable
+arbitraryExAv pn v db =
+    ExAv (unPN pn) (unPV v) <$> arbitraryComponentDeps db
+
+arbitraryExInst :: PN -> PV -> [ExampleInstalled] -> Gen ExampleInstalled
+arbitraryExInst pn v pkgs = do
+  hash <- vectorOf 10 $ elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']
+  numDeps <- min 3 <$> arbitrary
+  deps <- randomSubset numDeps pkgs
+  return $ ExInst (unPN pn) (unPV v) hash (map exInstHash deps)
+
+arbitraryComponentDeps :: TestDb -> Gen (ComponentDeps [ExampleDependency])
+arbitraryComponentDeps (TestDb []) = return $ CD.fromList []
+arbitraryComponentDeps db =
+    -- CD.fromList combines duplicate components.
+    CD.fromList <$> boundedListOf 3 (arbitraryComponentDep db)
+
+arbitraryComponentDep :: TestDb -> Gen (ComponentDep [ExampleDependency])
+arbitraryComponentDep db = do
+  comp <- arbitrary
+  deps <- case comp of
+            ComponentSetup -> smallListOf (arbitraryExDep db Setup)
+            _              -> boundedListOf 5 (arbitraryExDep db NonSetup)
+  return (comp, deps)
+
+-- | Location of an 'ExampleDependency'. It determines which values are valid.
+data ExDepLocation = Setup | NonSetup
+
+arbitraryExDep :: TestDb -> ExDepLocation -> Gen ExampleDependency
+arbitraryExDep db@(TestDb pkgs) level =
+  let flag = ExFlag <$> arbitraryFlagName
+                    <*> arbitraryDeps db
+                    <*> arbitraryDeps db
+      other = [
+            ExAny . unPN <$> elements (map getName pkgs)
+
+          -- existing version
+          , let fixed pkg = ExFix (unPN $ getName pkg) (unPV $ getVersion pkg)
+            in fixed <$> elements pkgs
+
+          -- random version of an existing package
+          , ExFix . unPN . getName <$> elements pkgs <*> (unPV <$> arbitrary)
+          ]
+  in oneof $
+      case level of
+        NonSetup -> flag : other
+        Setup -> other
+
+arbitraryDeps :: TestDb -> Gen Dependencies
+arbitraryDeps db = frequency
+    [ (1, return NotBuildable)
+    , (20, Buildable <$> smallListOf (arbitraryExDep db NonSetup))
+    ]
+
+arbitraryFlagName :: Gen String
+arbitraryFlagName = (:[]) <$> elements ['A'..'E']
+
+arbitraryComponentName :: Gen String
+arbitraryComponentName = (:[]) <$> elements "ABC"
+
+instance Arbitrary ReorderGoals where
+  arbitrary = ReorderGoals <$> arbitrary
+
+  shrink (ReorderGoals reorder) = [ReorderGoals False | reorder]
+
+instance Arbitrary IndepGoals where
+  arbitrary = IndepGoals <$> arbitrary
+
+  shrink (IndepGoals indep) = [IndepGoals False | indep]
+
+instance Arbitrary Solver where
+  arbitrary = frequency [ (1, return TopDown)
+                        , (5, return Modular) ]
+
+  shrink Modular = []
+  shrink TopDown = [Modular]
+
+instance Arbitrary Component where
+  arbitrary = oneof [ ComponentLib <$> arbitraryComponentName
+                    , ComponentExe <$> arbitraryComponentName
+                    , ComponentTest <$> arbitraryComponentName
+                    , ComponentBench <$> arbitraryComponentName
+                    , return ComponentSetup
+                    ]
+
+  shrink (ComponentLib "") = []
+  shrink _ = [ComponentLib ""]
+
+instance Arbitrary ExampleInstalled where
+  arbitrary = error "arbitrary not implemented: ExampleInstalled"
+
+  shrink ei = [ ei { exInstBuildAgainst = deps }
+              | deps <- shrinkList shrinkNothing (exInstBuildAgainst ei)]
+
+instance Arbitrary ExampleAvailable where
+  arbitrary = error "arbitrary not implemented: ExampleAvailable"
+
+  shrink ea = [ea { exAvDeps = deps } | deps <- shrink (exAvDeps ea)]
+
+instance (Arbitrary a, Monoid a) => Arbitrary (ComponentDeps a) where
+  arbitrary = error "arbitrary not implemented: ComponentDeps"
+
+  shrink = map CD.fromList . shrink . CD.toList
+
+instance Arbitrary ExampleDependency where
+  arbitrary = error "arbitrary not implemented: ExampleDependency"
+
+  shrink (ExAny _) = []
+  shrink (ExFix pn _) = [ExAny pn]
+  shrink (ExFlag flag th el) =
+         deps th ++ deps el
+      ++ [ExFlag flag th' el | th' <- shrink th]
+      ++ [ExFlag flag th el' | el' <- shrink el]
+    where
+      deps NotBuildable = []
+      deps (Buildable ds) = ds
+  shrink dep = error $ "Dependency not handled: " ++ show dep
+
+instance Arbitrary Dependencies where
+  arbitrary = error "arbitrary not implemented: Dependencies"
+
+  shrink NotBuildable = [Buildable []]
+  shrink (Buildable deps) = map Buildable (shrink deps)
+
+randomSubset :: Int -> [a] -> Gen [a]
+randomSubset n xs = take n <$> shuffle xs
+
+boundedListOf :: Int -> Gen a -> Gen [a]
+boundedListOf n gen = take n <$> listOf gen
+
+-- | Generates lists with average length less than 1.
+smallListOf :: Gen a -> Gen [a]
+smallListOf gen =
+    frequency [ (fr, vectorOf n gen)
+              | (fr, n) <- [(3, 0), (5, 1), (2, 2)]]
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs
--- a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs
@@ -1,20 +1,27 @@
 {-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-module UnitTests.Distribution.Client.Dependency.Modular.Solver (tests, options) where
+module UnitTests.Distribution.Client.Dependency.Modular.Solver (tests)
+       where
 
 -- base
 import Control.Monad
 import Data.Maybe (isNothing)
-import Data.Proxy
-import Data.Typeable
 
+import qualified Data.Version         as V
+import qualified Distribution.Version as V
+
 -- test-framework
 import Test.Tasty as TF
 import Test.Tasty.HUnit (testCase, assertEqual, assertBool)
-import Test.Tasty.Options
 
+-- Cabal
+import Language.Haskell.Extension ( Extension(..)
+                                  , KnownExtension(..), Language(..))
+
 -- cabal-install
+import Distribution.Client.PkgConfigDb (PkgConfigDb, pkgConfigDbFromList)
+import Distribution.Client.Dependency.Types (Solver(Modular))
 import UnitTests.Distribution.Client.Dependency.Modular.DSL
+import UnitTests.Options
 
 tests :: [TF.TestTree]
 tests = [
@@ -67,39 +74,155 @@
         , runTest $ mkTest db12 "baseShim5" ["D"] Nothing
         , runTest $ mkTest db12 "baseShim6" ["E"] (Just [("E", 1), ("syb", 2)])
         ]
+    , testGroup "Cycles" [
+          runTest $ mkTest db14 "simpleCycle1"          ["A"]      Nothing
+        , runTest $ mkTest db14 "simpleCycle2"          ["A", "B"] Nothing
+        , runTest $ mkTest db14 "cycleWithFlagChoice1"  ["C"]      (Just [("C", 1), ("E", 1)])
+        , runTest $ mkTest db15 "cycleThroughSetupDep1" ["A"]      Nothing
+        , runTest $ mkTest db15 "cycleThroughSetupDep2" ["B"]      Nothing
+        , runTest $ mkTest db15 "cycleThroughSetupDep3" ["C"]      (Just [("C", 2), ("D", 1)])
+        , runTest $ mkTest db15 "cycleThroughSetupDep4" ["D"]      (Just [("D", 1)])
+        , runTest $ mkTest db15 "cycleThroughSetupDep5" ["E"]      (Just [("C", 2), ("D", 1), ("E", 1)])
+        ]
+    , testGroup "Extensions" [
+          runTest $ mkTestExts [EnableExtension CPP] dbExts1 "unsupported" ["A"] Nothing
+        , runTest $ mkTestExts [EnableExtension CPP] dbExts1 "unsupportedIndirect" ["B"] Nothing
+        , runTest $ mkTestExts [EnableExtension RankNTypes] dbExts1 "supported" ["A"] (Just [("A",1)])
+        , runTest $ mkTestExts (map EnableExtension [CPP,RankNTypes]) dbExts1 "supportedIndirect" ["C"] (Just [("A",1),("B",1), ("C",1)])
+        , runTest $ mkTestExts [EnableExtension CPP] dbExts1 "disabledExtension" ["D"] Nothing
+        , runTest $ mkTestExts (map EnableExtension [CPP,RankNTypes]) dbExts1 "disabledExtension" ["D"] Nothing
+        , runTest $ mkTestExts (UnknownExtension "custom" : map EnableExtension [CPP,RankNTypes]) dbExts1 "supportedUnknown" ["E"] (Just [("A",1),("B",1),("C",1),("E",1)])
+        ]
+    , testGroup "Languages" [
+          runTest $ mkTestLangs [Haskell98] dbLangs1 "unsupported" ["A"] Nothing
+        , runTest $ mkTestLangs [Haskell98,Haskell2010] dbLangs1 "supported" ["A"] (Just [("A",1)])
+        , runTest $ mkTestLangs [Haskell98] dbLangs1 "unsupportedIndirect" ["B"] Nothing
+        , runTest $ mkTestLangs [Haskell98, Haskell2010, UnknownLanguage "Haskell3000"] dbLangs1 "supportedUnknown" ["C"] (Just [("A",1),("B",1),("C",1)])
+        ]
+
+     , testGroup "Soft Constraints" [
+          runTest $ soft [ ExPref "A" $ mkvrThis 1]      $ mkTest db13 "selectPreferredVersionSimple" ["A"] (Just [("A", 1)])
+        , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionSimple2" ["A"] (Just [("A", 2)])
+        , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 2
+                         , ExPref "A" $ mkvrOrEarlier 1] $ mkTest db13 "selectPreferredVersionMultiple" ["A"] (Just [("A", 1)])
+        , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 1
+                         , ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionMultiple2" ["A"] (Just [("A", 1)])
+        , runTest $ soft [ ExPref "A" $ mkvrThis 1
+                         , ExPref "A" $ mkvrThis 2] $ mkTest db13 "selectPreferredVersionMultiple3" ["A"] (Just [("A", 2)])
+        , runTest $ soft [ ExPref "A" $ mkvrThis 1
+                         , ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionMultiple4" ["A"] (Just [("A", 1)])
+        ]
+     , testGroup "Buildable Field" [
+          testBuildable "avoid building component with unknown dependency" (ExAny "unknown")
+        , testBuildable "avoid building component with unknown extension" (ExExt (UnknownExtension "unknown"))
+        , testBuildable "avoid building component with unknown language" (ExLang (UnknownLanguage "unknown"))
+        , runTest $ mkTest dbBuildable1 "choose flags that set buildable to false" ["pkg"] (Just [("flag1-false", 1), ("flag2-true", 1), ("pkg", 1)])
+        , runTest $ mkTest dbBuildable2 "choose version that sets buildable to false" ["A"] (Just [("A", 1), ("B", 2)])
+         ]
+    , testGroup "Pkg-config dependencies" [
+          runTest $ mkTestPCDepends [] dbPC1 "noPkgs" ["A"] Nothing
+        , runTest $ mkTestPCDepends [("pkgA", "0")] dbPC1 "tooOld" ["A"] Nothing
+        , runTest $ mkTestPCDepends [("pkgA", "1.0.0"), ("pkgB", "1.0.0")] dbPC1 "pruneNotFound" ["C"] (Just [("A", 1), ("B", 1), ("C", 1)])
+        , runTest $ mkTestPCDepends [("pkgA", "1.0.0"), ("pkgB", "2.0.0")] dbPC1 "chooseNewest" ["C"] (Just [("A", 1), ("B", 2), ("C", 1)])
+        ]
     ]
   where
-    indep test = test { testIndepGoals = True }
+    -- | Combinator to turn on --independent-goals behavior, i.e. solve
+    -- for the goals as if we were solving for each goal independently.
+    -- (This doesn't really work well at the moment, see #2842)
+    indep test      = test { testIndepGoals = IndepGoals True }
+    soft prefs test = test { testSoftConstraints = prefs }
+    mkvrThis        = V.thisVersion . makeV
+    mkvrOrEarlier   = V.orEarlierVersion . makeV
+    makeV v         = V.Version [v,0,0] []
 
 {-------------------------------------------------------------------------------
   Solver tests
 -------------------------------------------------------------------------------}
 
 data SolverTest = SolverTest {
-    testLabel      :: String
-  , testTargets    :: [String]
-  , testResult     :: Maybe [(String, Int)]
-  , testIndepGoals :: Bool
-  , testDb         :: ExampleDb
+    testLabel          :: String
+  , testTargets        :: [String]
+  , testResult         :: Maybe [(String, Int)]
+  , testIndepGoals     :: IndepGoals
+  , testSoftConstraints :: [ExPreference]
+  , testDb             :: ExampleDb
+  , testSupportedExts  :: Maybe [Extension]
+  , testSupportedLangs :: Maybe [Language]
+  , testPkgConfigDb    :: PkgConfigDb
   }
 
+-- | Makes a solver test case, consisting of the following components:
+--
+--      1. An 'ExampleDb', representing the package database (both
+--         installed and remote) we are doing dependency solving over,
+--      2. A 'String' name for the test,
+--      3. A list '[String]' of package names to solve for
+--      4. The expected result, either 'Nothing' if there is no
+--         satisfying solution, or a list '[(String, Int)]' of
+--         packages to install, at which versions.
+--
+-- See 'UnitTests.Distribution.Client.Dependency.Modular.DSL' for how
+-- to construct an 'ExampleDb', as well as definitions of 'db1' etc.
+-- in this file.
 mkTest :: ExampleDb
        -> String
        -> [String]
        -> Maybe [(String, Int)]
        -> SolverTest
-mkTest db label targets result = SolverTest {
-    testLabel      = label
-  , testTargets    = targets
-  , testResult     = result
-  , testIndepGoals = False
-  , testDb         = db
+mkTest = mkTestExtLangPC Nothing Nothing []
+
+mkTestExts :: [Extension]
+           -> ExampleDb
+           -> String
+           -> [String]
+           -> Maybe [(String, Int)]
+           -> SolverTest
+mkTestExts exts = mkTestExtLangPC (Just exts) Nothing []
+
+mkTestLangs :: [Language]
+            -> ExampleDb
+            -> String
+            -> [String]
+            -> Maybe [(String, Int)]
+            -> SolverTest
+mkTestLangs langs = mkTestExtLangPC Nothing (Just langs) []
+
+mkTestPCDepends :: [(String, String)]
+                -> ExampleDb
+                -> String
+                -> [String]
+                -> Maybe [(String, Int)]
+                -> SolverTest
+mkTestPCDepends pkgConfigDb = mkTestExtLangPC Nothing Nothing pkgConfigDb
+
+mkTestExtLangPC :: Maybe [Extension]
+                -> Maybe [Language]
+                -> [(String, String)]
+                -> ExampleDb
+                -> String
+                -> [String]
+                -> Maybe [(String, Int)]
+                -> SolverTest
+mkTestExtLangPC exts langs pkgConfigDb db label targets result = SolverTest {
+    testLabel          = label
+  , testTargets        = targets
+  , testResult         = result
+  , testIndepGoals     = IndepGoals False
+  , testSoftConstraints = []
+  , testDb             = db
+  , testSupportedExts  = exts
+  , testSupportedLangs = langs
+  , testPkgConfigDb    = pkgConfigDbFromList pkgConfigDb
   }
 
 runTest :: SolverTest -> TF.TestTree
 runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) ->
     testCase testLabel $ do
-      let (_msgs, result) = exResolve testDb testTargets testIndepGoals
+      let (_msgs, result) = exResolve testDb testSupportedExts
+                            testSupportedLangs testPkgConfigDb testTargets
+                            Modular testIndepGoals (ReorderGoals False)
+                            testSoftConstraints
       when showSolverLog $ mapM_ putStrLn _msgs
       case result of
         Left  err  -> assertBool ("Unexpected error:\n" ++ err) (isNothing testResult)
@@ -139,27 +262,27 @@
 db3 = [
      Right $ exAv "A" 1 []
    , Right $ exAv "A" 2 []
-   , Right $ exAv "B" 1 [ExFlag "flagB" [ExFix "A" 1] [ExFix "A" 2]]
+   , Right $ exAv "B" 1 [exFlag "flagB" [ExFix "A" 1] [ExFix "A" 2]]
    , Right $ exAv "C" 1 [ExFix "A" 1, ExAny "B"]
    , Right $ exAv "D" 1 [ExFix "A" 2, ExAny "B"]
    ]
 
--- | Like exampleDb2, but the flag picks a different package rather than a
+-- | Like db3, but the flag picks a different package rather than a
 -- different package version
 --
--- In exampleDb2 we cannot install C and D as independent goals because:
+-- In db3 we cannot install C and D as independent goals because:
 --
 -- * The multiple instance restriction says C and D _must_ share B
--- * Since C relies on A.1, C needs B to be compiled with flagB on
--- * Since D relies on A.2, D needs B to be compiled with flagsB off
+-- * Since C relies on A-1, C needs B to be compiled with flagB on
+-- * Since D relies on A-2, D needs B to be compiled with flagB off
 -- * Hence C and D have incompatible requirements on B's flags.
 --
 -- However, _even_ if we don't check explicitly that we pick the same flag
 -- assignment for 0.B and 1.B, we will still detect the problem because
 -- 0.B depends on 0.A-1, 1.B depends on 1.A-2, hence we cannot link 0.A to
--- 1.B and therefore we cannot link 0.B to 1.B.
+-- 1.A and therefore we cannot link 0.B to 1.B.
 --
--- In exampleDb3 the situation however is trickier. We again cannot install
+-- In db4 the situation however is trickier. We again cannot install
 -- packages C and D as independent goals because:
 --
 -- * As above, the multiple instance restriction says that C and D _must_ share B
@@ -171,10 +294,10 @@
 -- we don't see the problem:
 --
 -- * We link 0.B to 1.B
--- * 0.B relies on Ay.1
--- * 1.B relies on Ax.1
+-- * 0.B relies on Ay-1
+-- * 1.B relies on Ax-1
 --
--- We will insist that 0.Ay will be linked to 1.Ay, and 0.Ax to 1.A, but since
+-- We will insist that 0.Ay will be linked to 1.Ay, and 0.Ax to 1.Ax, but since
 -- we only ever assign to one of these, these constraints are never broken.
 db4 :: ExampleDb
 db4 = [
@@ -182,7 +305,7 @@
    , Right $ exAv "Ax" 2 []
    , Right $ exAv "Ay" 1 []
    , Right $ exAv "Ay" 2 []
-   , Right $ exAv "B"  1 [ExFlag "flagB" [ExFix "Ax" 1] [ExFix "Ay" 1]]
+   , Right $ exAv "B"  1 [exFlag "flagB" [ExFix "Ax" 1] [ExFix "Ay" 1]]
    , Right $ exAv "C"  1 [ExFix "Ax" 2, ExAny "B"]
    , Right $ exAv "D"  1 [ExFix "Ay" 2, ExAny "B"]
    ]
@@ -207,11 +330,11 @@
     Right $ exAv "A" 1 []
   , Right $ exAv "A" 2 []
   , Right $ exAv "B" 1 []
-  , Right $ exAv "C" 1 [ExTest "testC" [ExAny "A"]]
-  , Right $ exAv "D" 1 [ExTest "testD" [ExFix "B" 2]]
-  , Right $ exAv "E" 1 [ExFix "A" 1, ExTest "testE" [ExAny "A"]]
-  , Right $ exAv "F" 1 [ExFix "A" 1, ExTest "testF" [ExFix "A" 2]]
-  , Right $ exAv "G" 1 [ExFix "A" 2, ExTest "testG" [ExAny "A"]]
+  , Right $ exAv "C" 1 [] `withTest` ExTest "testC" [ExAny "A"]
+  , Right $ exAv "D" 1 [] `withTest` ExTest "testD" [ExFix "B" 2]
+  , Right $ exAv "E" 1 [ExFix "A" 1] `withTest` ExTest "testE" [ExAny "A"]
+  , Right $ exAv "F" 1 [ExFix "A" 1] `withTest` ExTest "testF" [ExFix "A" 2]
+  , Right $ exAv "G" 1 [ExFix "A" 2] `withTest` ExTest "testG" [ExAny "A"]
   ]
 
 -- Now the _dependencies_ have test suites
@@ -226,7 +349,7 @@
 db6 = [
     Right $ exAv "A" 1 []
   , Right $ exAv "A" 2 []
-  , Right $ exAv "B" 1 [ExTest "testA" [ExAny "A"]]
+  , Right $ exAv "B" 1 [] `withTest` ExTest "testA" [ExAny "A"]
   , Right $ exAv "C" 1 [ExFix "A" 1, ExAny "B"]
   , Right $ exAv "D" 1 [ExAny "B"]
   ]
@@ -340,21 +463,126 @@
     , Right $ exAv "E" 1 [ExFix "base" 4, ExFix "syb" 2]
     ]
 
-{-------------------------------------------------------------------------------
-  Test options
--------------------------------------------------------------------------------}
+db13 :: ExampleDb
+db13 = [
+    Right $ exAv "A" 1 []
+  , Right $ exAv "A" 2 []
+  , Right $ exAv "A" 3 []
+  ]
 
-options :: [OptionDescription]
-options = [
-    Option (Proxy :: Proxy OptionShowSolverLog)
+-- | Database with some cycles
+--
+-- * Simplest non-trivial cycle: A -> B and B -> A
+-- * There is a cycle C -> D -> C, but it can be broken by picking the
+--   right flag assignment.
+db14 :: ExampleDb
+db14 = [
+    Right $ exAv "A" 1 [ExAny "B"]
+  , Right $ exAv "B" 1 [ExAny "A"]
+  , Right $ exAv "C" 1 [exFlag "flagC" [ExAny "D"] [ExAny "E"]]
+  , Right $ exAv "D" 1 [ExAny "C"]
+  , Right $ exAv "E" 1 []
   ]
 
-newtype OptionShowSolverLog = OptionShowSolverLog Bool
-  deriving Typeable
+-- | Cycles through setup dependencies
+--
+-- The first cycle is unsolvable: package A has a setup dependency on B,
+-- B has a regular dependency on A, and we only have a single version available
+-- for both.
+--
+-- The second cycle can be broken by picking different versions: package C-2.0
+-- has a setup dependency on D, and D has a regular dependency on C-*. However,
+-- version C-1.0 is already available (perhaps it didn't have this setup dep).
+-- Thus, we should be able to break this cycle even if we are installing package
+-- E, which explictly depends on C-2.0.
+db15 :: ExampleDb
+db15 = [
+    -- First example (real cycle, no solution)
+    Right $ exAv   "A" 1            []            `withSetupDeps` [ExAny "B"]
+  , Right $ exAv   "B" 1            [ExAny "A"]
+    -- Second example (cycle can be broken by picking versions carefully)
+  , Left  $ exInst "C" 1 "C-1-inst" []
+  , Right $ exAv   "C" 2            []            `withSetupDeps` [ExAny "D"]
+  , Right $ exAv   "D" 1            [ExAny "C"  ]
+  , Right $ exAv   "E" 1            [ExFix "C" 2]
+  ]
 
-instance IsOption OptionShowSolverLog where
-  defaultValue   = OptionShowSolverLog False
-  parseValue     = fmap OptionShowSolverLog . safeRead
-  optionName     = return "show-solver-log"
-  optionHelp     = return "Show full log from the solver"
-  optionCLParser = flagCLParser Nothing (OptionShowSolverLog True)
+dbExts1 :: ExampleDb
+dbExts1 = [
+    Right $ exAv "A" 1 [ExExt (EnableExtension RankNTypes)]
+  , Right $ exAv "B" 1 [ExExt (EnableExtension CPP), ExAny "A"]
+  , Right $ exAv "C" 1 [ExAny "B"]
+  , Right $ exAv "D" 1 [ExExt (DisableExtension CPP), ExAny "B"]
+  , Right $ exAv "E" 1 [ExExt (UnknownExtension "custom"), ExAny "C"]
+  ]
+
+dbLangs1 :: ExampleDb
+dbLangs1 = [
+    Right $ exAv "A" 1 [ExLang Haskell2010]
+  , Right $ exAv "B" 1 [ExLang Haskell98, ExAny "A"]
+  , Right $ exAv "C" 1 [ExLang (UnknownLanguage "Haskell3000"), ExAny "B"]
+  ]
+
+-- | cabal must set enable-lib to false in order to avoid the unavailable
+-- dependency. Flags are true by default. The flag choice causes "pkg" to
+-- depend on "false-dep".
+testBuildable :: String -> ExampleDependency -> TestTree
+testBuildable testName unavailableDep =
+    runTest $ mkTestExtLangPC (Just []) (Just []) [] db testName ["pkg"] expected
+  where
+    expected = Just [("false-dep", 1), ("pkg", 1)]
+    db = [
+        Right $ exAv "pkg" 1
+            [ unavailableDep
+            , ExFlag "enable-lib" (Buildable []) NotBuildable ]
+         `withTest`
+            ExTest "test" [exFlag "enable-lib"
+                              [ExAny "true-dep"]
+                              [ExAny "false-dep"]]
+      , Right $ exAv "true-dep" 1 []
+      , Right $ exAv "false-dep" 1 []
+      ]
+
+-- | cabal must choose -flag1 +flag2 for "pkg", which requires packages
+-- "flag1-false" and "flag2-true".
+dbBuildable1 :: ExampleDb
+dbBuildable1 = [
+    Right $ exAv "pkg" 1
+        [ ExAny "unknown"
+        , ExFlag "flag1" (Buildable []) NotBuildable
+        , ExFlag "flag2" (Buildable []) NotBuildable]
+     `withTests`
+        [ ExTest "optional-test"
+              [ ExAny "unknown"
+              , ExFlag "flag1"
+                    (Buildable [])
+                    (Buildable [ExFlag "flag2" NotBuildable (Buildable [])])]
+        , ExTest "test" [ exFlag "flag1" [ExAny "flag1-true"] [ExAny "flag1-false"]
+                        , exFlag "flag2" [ExAny "flag2-true"] [ExAny "flag2-false"]]
+        ]
+  , Right $ exAv "flag1-true" 1 []
+  , Right $ exAv "flag1-false" 1 []
+  , Right $ exAv "flag2-true" 1 []
+  , Right $ exAv "flag2-false" 1 []
+  ]
+
+-- | Package databases for testing @pkg-config@ dependencies.
+dbPC1 :: ExampleDb
+dbPC1 = [
+    Right $ exAv "A" 1 [ExPkg ("pkgA", 1)]
+  , Right $ exAv "B" 1 [ExPkg ("pkgB", 1), ExAny "A"]
+  , Right $ exAv "B" 2 [ExPkg ("pkgB", 2), ExAny "A"]
+  , Right $ exAv "C" 1 [ExAny "B"]
+  ]
+
+-- | cabal must pick B-2 to avoid the unknown dependency.
+dbBuildable2 :: ExampleDb
+dbBuildable2 = [
+    Right $ exAv "A" 1 [ExAny "B"]
+  , Right $ exAv "B" 1 [ExAny "unknown"]
+  , Right $ exAv "B" 2
+        [ ExAny "unknown"
+        , ExFlag "disable-lib" NotBuildable (Buildable [])
+        ]
+  , Right $ exAv "B" 3 [ExAny "unknown"]
+  ]
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs
@@ -0,0 +1,769 @@
+module UnitTests.Distribution.Client.FileMonitor (tests) where
+
+import Control.Monad
+import Control.Exception
+import Control.Concurrent (threadDelay)
+import qualified Data.Set as Set
+import System.FilePath
+import qualified System.Directory as IO
+import Prelude hiding (writeFile)
+import qualified Prelude as IO (writeFile)
+
+import Distribution.Text (simpleParse)
+import Distribution.Compat.Binary
+import Distribution.Simple.Utils (withTempDirectory)
+import Distribution.Verbosity (silent)
+
+import Distribution.Client.FileMonitor
+import Distribution.Client.Compat.Time
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+tests :: Int -> [TestTree]
+tests mtimeChange =
+  [ testCase "sanity check mtimes"   $ testFileMTimeSanity mtimeChange
+  , testCase "no monitor cache"      testNoMonitorCache
+  , testCase "corrupt monitor cache" testCorruptMonitorCache
+  , testCase "empty monitor"         testEmptyMonitor
+  , testCase "missing file"          testMissingFile
+  , testCase "change file"           $ testChangedFile mtimeChange
+  , testCase "file mtime vs content" $ testChangedFileMtimeVsContent mtimeChange
+  , testCase "update during action"  $ testUpdateDuringAction mtimeChange
+  , testCase "remove file"           testRemoveFile
+  , testCase "non-existent file"     testNonExistentFile
+  , testCase "changed file type"     $ testChangedFileType mtimeChange
+
+  , testGroup "glob matches"
+    [ testCase "no change"           testGlobNoChange
+    , testCase "add match"           $ testGlobAddMatch mtimeChange
+    , testCase "remove match"        $ testGlobRemoveMatch mtimeChange
+    , testCase "change match"        $ testGlobChangeMatch mtimeChange
+
+    , testCase "add match subdir"    $ testGlobAddMatchSubdir mtimeChange
+    , testCase "remove match subdir" $ testGlobRemoveMatchSubdir mtimeChange
+    , testCase "change match subdir" $ testGlobChangeMatchSubdir mtimeChange
+
+    , testCase "match toplevel dir"  $ testGlobMatchTopDir mtimeChange
+    , testCase "add non-match"       $ testGlobAddNonMatch mtimeChange
+    , testCase "remove non-match"    $ testGlobRemoveNonMatch mtimeChange
+
+    , testCase "add non-match"       $ testGlobAddNonMatchSubdir mtimeChange
+    , testCase "remove non-match"    $ testGlobRemoveNonMatchSubdir mtimeChange
+
+    , testCase "invariant sorted 1"  $ testInvariantMonitorStateGlobFiles
+                                         mtimeChange
+    , testCase "invariant sorted 2"  $ testInvariantMonitorStateGlobDirs
+                                         mtimeChange
+
+    , testCase "match dirs"          $ testGlobMatchDir mtimeChange
+    , testCase "match dirs only"     $ testGlobMatchDirOnly mtimeChange
+    , testCase "change file type"    $ testGlobChangeFileType mtimeChange
+    , testCase "absolute paths"      $ testGlobAbsolutePath mtimeChange
+    ]
+
+  , testCase "value unchanged"       testValueUnchanged
+  , testCase "value changed"         testValueChanged
+  , testCase "value & file changed"  $ testValueAndFileChanged mtimeChange
+  , testCase "value updated"         testValueUpdated
+  ]
+
+-- we rely on file mtimes having a reasonable resolution
+testFileMTimeSanity :: Int -> Assertion
+testFileMTimeSanity mtimeChange =
+  withTempDirectory silent "." "file-status-" $ \dir -> do
+    replicateM_ 10 $ do
+      IO.writeFile (dir </> "a") "content"
+      t1 <- getModTime (dir </> "a")
+      threadDelay mtimeChange
+      IO.writeFile (dir </> "a") "content"
+      t2 <- getModTime (dir </> "a")
+      assertBool "expected different file mtimes" (t2 > t1)
+
+-- first run, where we don't even call updateMonitor
+testNoMonitorCache :: Assertion
+testNoMonitorCache =
+  withFileMonitor $ \root monitor -> do
+    reason <- expectMonitorChanged root (monitor :: FileMonitor () ()) ()
+    reason @?= MonitorFirstRun
+
+-- write garbage into the binary cache file
+testCorruptMonitorCache :: Assertion
+testCorruptMonitorCache =
+  withFileMonitor $ \root monitor -> do
+    IO.writeFile (fileMonitorCacheFile monitor) "broken"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitorCorruptCache
+
+    updateMonitor root monitor [] () ()
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= []
+
+    IO.writeFile (fileMonitorCacheFile monitor) "broken"
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitorCorruptCache
+
+-- no files to monitor
+testEmptyMonitor :: Assertion
+testEmptyMonitor =
+  withFileMonitor $ \root monitor -> do
+    touchFile root "a"
+    updateMonitor root monitor [] () ()
+    touchFile root "b"
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= []
+
+-- monitor a file that is expected to exist
+testMissingFile :: Assertion
+testMissingFile = do
+    test monitorFile       touchFile  "a"
+    test monitorFileHashed touchFile  "a"
+    test monitorFile       touchFile ("dir" </> "a")
+    test monitorFileHashed touchFile ("dir" </> "a")
+    test monitorDirectory  touchDir   "a"
+    test monitorDirectory  touchDir  ("dir" </> "a")
+  where
+    test :: (FilePath -> MonitorFilePath)
+         -> (RootPath -> FilePath -> IO ())
+         -> FilePath
+         -> IO ()
+    test monitorKind touch file =
+      withFileMonitor $ \root monitor -> do
+        -- a file that doesn't exist at snapshot time is considered to have
+        -- changed
+        updateMonitor root monitor [monitorKind file] () ()
+        reason <- expectMonitorChanged root monitor ()
+        reason @?= MonitoredFileChanged file
+
+        -- a file doesn't exist at snapshot time, but gets added afterwards is
+        -- also considered to have changed
+        updateMonitor root monitor [monitorKind file] () ()
+        touch root file
+        reason2 <- expectMonitorChanged root monitor ()
+        reason2 @?= MonitoredFileChanged file
+
+
+testChangedFile :: Int -> Assertion
+testChangedFile mtimeChange = do
+    test monitorFile       touchFile touchFile         "a"
+    test monitorFileHashed touchFile touchFileContent  "a"
+    test monitorFile       touchFile touchFile        ("dir" </> "a")
+    test monitorFileHashed touchFile touchFileContent ("dir" </> "a")
+    test monitorDirectory  touchDir  touchDir          "a"
+    test monitorDirectory  touchDir  touchDir         ("dir" </> "a")
+  where
+    test :: (FilePath -> MonitorFilePath)
+         -> (RootPath -> FilePath -> IO ())
+         -> (RootPath -> FilePath -> IO ())
+         -> FilePath
+         -> IO ()
+    test monitorKind touch touch' file =
+      withFileMonitor $ \root monitor -> do
+        touch root file
+        updateMonitor root monitor [monitorKind file] () ()
+        threadDelay mtimeChange
+        touch' root file
+        reason <- expectMonitorChanged root monitor ()
+        reason @?= MonitoredFileChanged file
+
+
+testChangedFileMtimeVsContent :: Int -> Assertion
+testChangedFileMtimeVsContent mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    -- if we don't touch the file, it's unchanged
+    touchFile root "a"
+    updateMonitor root monitor [monitorFile "a"] () ()
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFile "a"]
+
+    -- if we do touch the file, it's changed if we only consider mtime
+    updateMonitor root monitor [monitorFile "a"] () ()
+    threadDelay mtimeChange
+    touchFile root "a"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged "a"
+
+    -- but if we touch the file, it's unchanged if we consider content hash
+    updateMonitor root monitor [monitorFileHashed "a"] () ()
+    threadDelay mtimeChange
+    touchFile root "a"
+    (res2, files2) <- expectMonitorUnchanged root monitor ()
+    res2   @?= ()
+    files2 @?= [monitorFileHashed "a"]
+
+    -- finally if we change the content it's changed
+    updateMonitor root monitor [monitorFileHashed "a"] () ()
+    threadDelay mtimeChange
+    touchFileContent root "a"
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitoredFileChanged "a"
+
+
+testUpdateDuringAction :: Int -> Assertion
+testUpdateDuringAction mtimeChange = do
+    test (monitorFile        "a") touchFile "a"
+    test (monitorFileHashed  "a") touchFile "a"
+    test (monitorDirectory   "a") touchDir  "a"
+    test (monitorFileGlobStr "*") touchFile "a"
+    test (monitorFileGlobStr "*") { monitorKindDir = DirModTime }
+                                  touchDir  "a"
+  where
+    test :: MonitorFilePath
+         -> (RootPath -> FilePath -> IO ())
+         -> FilePath
+         -> IO ()
+    test monitorSpec touch file =
+      withFileMonitor $ \root monitor -> do
+        touch root file
+        updateMonitor root monitor [monitorSpec] () ()
+
+        -- start doing an update action...
+        threadDelay mtimeChange -- some time passes
+        touch root file         -- a file gets updates during the action
+        threadDelay mtimeChange -- some time passes then we finish
+        updateMonitor root monitor [monitorSpec] () ()
+        -- we don't notice this change since we took the timestamp after the
+        -- action finished
+        (res, files) <- expectMonitorUnchanged root monitor ()
+        res   @?= ()
+        files @?= [monitorSpec]
+
+        -- Let's try again, this time taking the timestamp before the action
+        timestamp' <- beginUpdateFileMonitor
+        threadDelay mtimeChange -- some time passes
+        touch root file         -- a file gets updates during the action
+        threadDelay mtimeChange -- some time passes then we finish
+        updateMonitorWithTimestamp root monitor timestamp' [monitorSpec] () ()
+        -- now we do notice the change since we took the snapshot before the
+        -- action finished
+        reason <- expectMonitorChanged root monitor ()
+        reason @?= MonitoredFileChanged file
+
+
+testRemoveFile :: Assertion
+testRemoveFile = do
+    test monitorFile       touchFile removeFile  "a"
+    test monitorFileHashed touchFile removeFile  "a"
+    test monitorFile       touchFile removeFile ("dir" </> "a")
+    test monitorFileHashed touchFile removeFile ("dir" </> "a")
+    test monitorDirectory  touchDir  removeDir   "a"
+    test monitorDirectory  touchDir  removeDir  ("dir" </> "a")
+  where
+    test :: (FilePath -> MonitorFilePath)
+         -> (RootPath -> FilePath -> IO ())
+         -> (RootPath -> FilePath -> IO ())
+         -> FilePath
+         -> IO ()
+    test monitorKind touch remove file =
+      withFileMonitor $ \root monitor -> do
+        touch root file
+        updateMonitor root monitor [monitorKind file] () ()
+        remove root file
+        reason <- expectMonitorChanged root monitor ()
+        reason @?= MonitoredFileChanged file
+
+
+-- monitor a file that we expect not to exist
+testNonExistentFile :: Assertion
+testNonExistentFile =
+  withFileMonitor $ \root monitor -> do
+    -- a file that doesn't exist at snapshot time or check time is unchanged
+    updateMonitor root monitor [monitorNonExistentFile "a"] () ()
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorNonExistentFile "a"]
+
+    -- if the file then exists it has changed
+    touchFile root "a"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged "a"
+
+    -- if the file then exists at snapshot and check time it has changed
+    updateMonitor root monitor [monitorNonExistentFile "a"] () ()
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitoredFileChanged "a"
+
+    -- but if the file existed at snapshot time and doesn't exist at check time
+    -- it is consider unchanged. This is unlike files we expect to exist, but
+    -- that's because files that exist can have different content and actions
+    -- can depend on that content, whereas if the action expected a file not to
+    -- exist and it now does not, it'll give the same result, irrespective of
+    -- the fact that the file might have existed in the meantime.
+    updateMonitor root monitor [monitorNonExistentFile "a"] () ()
+    removeFile root "a"
+    (res2, files2) <- expectMonitorUnchanged root monitor ()
+    res2   @?= ()
+    files2 @?= [monitorNonExistentFile "a"]
+
+
+testChangedFileType :: Int-> Assertion
+testChangedFileType mtimeChange = do
+    test (monitorFile            "a") touchFile removeFile createDir
+    test (monitorFileHashed      "a") touchFile removeFile createDir
+
+    test (monitorDirectory       "a") createDir removeDir touchFile
+    test (monitorFileOrDirectory "a") createDir removeDir touchFile
+
+    test (monitorFileGlobStr     "*") { monitorKindDir = DirModTime }
+                                      touchFile removeFile createDir
+    test (monitorFileGlobStr     "*") { monitorKindDir = DirModTime }
+                                      createDir removeDir touchFile
+  where
+    test :: MonitorFilePath
+         -> (RootPath -> String -> IO ())
+         -> (RootPath -> String -> IO ())
+         -> (RootPath -> String -> IO ())
+         -> IO ()
+    test monitorKind touch remove touch' =
+      withFileMonitor $ \root monitor -> do
+        touch  root "a"
+        updateMonitor root monitor [monitorKind] () ()
+        threadDelay mtimeChange
+        remove root "a"
+        touch' root "a"
+        reason <- expectMonitorChanged root monitor ()
+        reason @?= MonitoredFileChanged "a"
+
+
+------------------
+-- globs
+--
+
+testGlobNoChange :: Assertion
+testGlobNoChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    touchFile root ("dir" </> "good-b")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/good-*"]
+
+testGlobAddMatch :: Int -> Assertion
+testGlobAddMatch mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/good-*"]
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "good-b")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "good-b")
+
+testGlobRemoveMatch :: Int -> Assertion
+testGlobRemoveMatch mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    touchFile root ("dir" </> "good-b")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    threadDelay mtimeChange
+    removeFile root "dir/good-a"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "good-a")
+
+testGlobChangeMatch :: Int -> Assertion
+testGlobChangeMatch mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    touchFile root ("dir" </> "good-b")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "good-b")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/good-*"]
+
+    touchFileContent root ("dir" </> "good-b")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "good-b")
+
+testGlobAddMatchSubdir :: Int -> Assertion
+testGlobAddMatchSubdir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "good-a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "b" </> "good-b")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "b" </> "good-b")
+
+testGlobRemoveMatchSubdir :: Int -> Assertion
+testGlobRemoveMatchSubdir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "good-a")
+    touchFile root ("dir" </> "b" </> "good-b")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()
+    threadDelay mtimeChange
+    removeDir root ("dir" </> "a")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "a" </> "good-a")
+
+testGlobChangeMatchSubdir :: Int -> Assertion
+testGlobChangeMatchSubdir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "good-a")
+    touchFile root ("dir" </> "b" </> "good-b")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "b" </> "good-b")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*/good-*"]
+
+    touchFileContent root "dir/b/good-b"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "b" </> "good-b")
+
+-- check nothing goes squiffy with matching in the top dir
+testGlobMatchTopDir :: Int -> Assertion
+testGlobMatchTopDir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    updateMonitor root monitor [monitorFileGlobStr "*"] () ()
+    threadDelay mtimeChange
+    touchFile root "a"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged "a"
+
+testGlobAddNonMatch :: Int -> Assertion
+testGlobAddNonMatch mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "bad")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/good-*"]
+
+testGlobRemoveNonMatch :: Int -> Assertion
+testGlobRemoveNonMatch mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "good-a")
+    touchFile root ("dir" </> "bad")
+    updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()
+    threadDelay mtimeChange
+    removeFile root "dir/bad"
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/good-*"]
+
+testGlobAddNonMatchSubdir :: Int -> Assertion
+testGlobAddNonMatchSubdir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "good-a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir" </> "b" </> "bad")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*/good-*"]
+
+testGlobRemoveNonMatchSubdir :: Int -> Assertion
+testGlobRemoveNonMatchSubdir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "good-a")
+    touchFile root ("dir" </> "b" </> "bad")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()
+    threadDelay mtimeChange
+    removeDir root ("dir" </> "b")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*/good-*"]
+
+
+-- try and tickle a bug that happens if we don't maintain the invariant that
+-- MonitorStateGlobFiles entries are sorted
+testInvariantMonitorStateGlobFiles :: Int -> Assertion
+testInvariantMonitorStateGlobFiles mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a")
+    touchFile root ("dir" </> "b")
+    touchFile root ("dir" </> "c")
+    touchFile root ("dir" </> "d")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()
+    threadDelay mtimeChange
+    -- so there should be no change (since we're doing content checks)
+    -- but if we can get the dir entries to appear in the wrong order
+    -- then if the sorted invariant is not maintained then we can fool
+    -- the 'probeGlobStatus' into thinking there's changes
+    removeFile root ("dir" </> "a")
+    removeFile root ("dir" </> "b")
+    removeFile root ("dir" </> "c")
+    removeFile root ("dir" </> "d")
+    touchFile root ("dir" </> "d")
+    touchFile root ("dir" </> "c")
+    touchFile root ("dir" </> "b")
+    touchFile root ("dir" </> "a")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*"]
+
+-- same thing for the subdirs case
+testInvariantMonitorStateGlobDirs :: Int -> Assertion
+testInvariantMonitorStateGlobDirs mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root ("dir" </> "a" </> "file")
+    touchFile root ("dir" </> "b" </> "file")
+    touchFile root ("dir" </> "c" </> "file")
+    touchFile root ("dir" </> "d" </> "file")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/file"] () ()
+    threadDelay mtimeChange
+    removeDir root ("dir" </> "a")
+    removeDir root ("dir" </> "b")
+    removeDir root ("dir" </> "c")
+    removeDir root ("dir" </> "d")
+    touchFile root ("dir" </> "d" </> "file")
+    touchFile root ("dir" </> "c" </> "file")
+    touchFile root ("dir" </> "b" </> "file")
+    touchFile root ("dir" </> "a" </> "file")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*/file"]
+
+-- ensure that a glob can match a directory as well as a file
+testGlobMatchDir :: Int -> Assertion
+testGlobMatchDir mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    createDir root ("dir" </> "a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()
+    threadDelay mtimeChange
+    -- nothing changed yet
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*"]
+    -- expect dir/b to match and be detected as changed
+    createDir root ("dir" </> "b")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "b")
+    -- now remove dir/a and expect it to be detected as changed
+    updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()
+    threadDelay mtimeChange
+    removeDir root ("dir" </> "a")
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitoredFileChanged ("dir" </> "a")
+
+testGlobMatchDirOnly :: Int -> Assertion
+testGlobMatchDirOnly mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    updateMonitor root monitor [monitorFileGlobStr "dir/*/"] () ()
+    threadDelay mtimeChange
+    -- expect file dir/a to not match, so not detected as changed
+    touchFile root ("dir" </> "a")
+    (res, files) <- expectMonitorUnchanged root monitor ()
+    res   @?= ()
+    files @?= [monitorFileGlobStr "dir/*/"]
+    -- note that checking the file monitor for changes can updates the
+    -- cached dir mtimes (when it has to record that there's new matches)
+    -- so we need an extra mtime delay
+    threadDelay mtimeChange
+    -- but expect dir/b to match
+    createDir root ("dir" </> "b")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "b")
+
+testGlobChangeFileType :: Int -> Assertion
+testGlobChangeFileType mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    -- change file to dir
+    touchFile root ("dir" </> "a")
+    updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()
+    threadDelay mtimeChange
+    removeFile root ("dir" </> "a")
+    createDir  root ("dir" </> "a")
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged ("dir" </> "a")
+    -- change dir to file
+    updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()
+    threadDelay mtimeChange
+    removeDir root ("dir" </> "a")
+    touchFile root ("dir" </> "a")
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitoredFileChanged ("dir" </> "a")
+
+testGlobAbsolutePath :: Int -> Assertion
+testGlobAbsolutePath mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    root' <- absoluteRoot root
+    -- absolute glob, removing a file
+    touchFile root ("dir/good-a")
+    touchFile root ("dir/good-b")
+    updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()
+    threadDelay mtimeChange
+    removeFile root "dir/good-a"
+    reason <- expectMonitorChanged root monitor ()
+    reason @?= MonitoredFileChanged (root' </> "dir/good-a")
+    -- absolute glob, adding a file
+    updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()
+    threadDelay mtimeChange
+    touchFile root ("dir/good-a")
+    reason2 <- expectMonitorChanged root monitor ()
+    reason2 @?= MonitoredFileChanged (root' </> "dir/good-a")
+    -- absolute glob, changing a file
+    updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()
+    threadDelay mtimeChange
+    touchFileContent root "dir/good-b"
+    reason3 <- expectMonitorChanged root monitor ()
+    reason3 @?= MonitoredFileChanged (root' </> "dir/good-b")
+
+
+------------------
+-- value changes
+--
+
+testValueUnchanged :: Assertion
+testValueUnchanged =
+  withFileMonitor $ \root monitor -> do
+    touchFile root "a"
+    updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"
+    (res, files) <- expectMonitorUnchanged root monitor 42
+    res   @?= "ok"
+    files @?= [monitorFile "a"]
+
+testValueChanged :: Assertion
+testValueChanged =
+  withFileMonitor $ \root monitor -> do
+    touchFile root "a"
+    updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"
+    reason <- expectMonitorChanged root monitor 43
+    reason @?= MonitoredValueChanged 42
+
+testValueAndFileChanged :: Int -> Assertion
+testValueAndFileChanged mtimeChange =
+  withFileMonitor $ \root monitor -> do
+    touchFile root "a"
+
+    -- we change the value and the file, and the value change is reported
+    updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"
+    threadDelay mtimeChange
+    touchFile root "a"
+    reason <- expectMonitorChanged root monitor 43
+    reason @?= MonitoredValueChanged 42
+
+    -- if fileMonitorCheckIfOnlyValueChanged then if only the value changed
+    -- then it's reported as MonitoredValueChanged
+    let monitor' :: FileMonitor Int String
+        monitor' = monitor { fileMonitorCheckIfOnlyValueChanged = True }
+    updateMonitor root monitor' [monitorFile "a"] 42 "ok"
+    reason2 <- expectMonitorChanged root monitor' 43
+    reason2 @?= MonitoredValueChanged 42
+
+    -- but if a file changed too then we don't report MonitoredValueChanged
+    updateMonitor root monitor' [monitorFile "a"] 42 "ok"
+    threadDelay mtimeChange
+    touchFile root "a"
+    reason3 <- expectMonitorChanged root monitor' 43
+    reason3 @?= MonitoredFileChanged "a"
+
+testValueUpdated :: Assertion
+testValueUpdated =
+  withFileMonitor $ \root monitor -> do
+    touchFile root "a"
+
+    let monitor' :: FileMonitor (Set.Set Int) String
+        monitor' = (monitor :: FileMonitor (Set.Set Int) String) {
+                     fileMonitorCheckIfOnlyValueChanged = True,
+                     fileMonitorKeyValid = Set.isSubsetOf
+                   }
+
+    updateMonitor root monitor' [monitorFile "a"] (Set.fromList [42,43]) "ok"
+    (res,_files) <- expectMonitorUnchanged root monitor' (Set.fromList [42])
+    res @?= "ok"
+
+    reason <- expectMonitorChanged root monitor' (Set.fromList [42,44])
+    reason @?= MonitoredValueChanged (Set.fromList [42,43])
+
+
+-------------
+-- Utils
+
+newtype RootPath = RootPath FilePath
+
+touchFile :: RootPath -> FilePath -> IO ()
+touchFile (RootPath root) fname = do
+  let path = root </> fname
+  IO.createDirectoryIfMissing True (takeDirectory path)
+  IO.writeFile path "touched"
+
+touchFileContent :: RootPath -> FilePath -> IO ()
+touchFileContent (RootPath root) fname = do
+  let path = root </> fname
+  IO.createDirectoryIfMissing True (takeDirectory path)
+  IO.writeFile path "different"
+
+removeFile :: RootPath -> FilePath -> IO ()
+removeFile (RootPath root) fname = IO.removeFile (root </> fname)
+
+touchDir :: RootPath -> FilePath -> IO ()
+touchDir root@(RootPath rootdir) dname = do
+  IO.createDirectoryIfMissing True (rootdir </> dname)
+  touchFile  root (dname </> "touch")
+  removeFile root (dname </> "touch")
+
+createDir :: RootPath -> FilePath -> IO ()
+createDir (RootPath root) dname = do
+  let path = root </> dname
+  IO.createDirectoryIfMissing True (takeDirectory path)
+  IO.createDirectory path
+
+removeDir :: RootPath -> FilePath -> IO ()
+removeDir (RootPath root) dname = IO.removeDirectoryRecursive (root </> dname)
+
+absoluteRoot :: RootPath -> IO FilePath
+absoluteRoot (RootPath root) = IO.canonicalizePath root
+
+monitorFileGlobStr :: String -> MonitorFilePath
+monitorFileGlobStr globstr
+  | Just glob <- simpleParse globstr = monitorFileGlob glob
+  | otherwise                        = error $ "Failed to parse " ++ globstr
+
+
+expectMonitorChanged :: (Binary a, Binary b)
+                     => RootPath -> FileMonitor a b -> a
+                     -> IO (MonitorChangedReason a)
+expectMonitorChanged root monitor key = do
+  res <- checkChanged root monitor key
+  case res of
+    MonitorChanged reason -> return reason
+    MonitorUnchanged _ _  -> throwIO $ HUnitFailure "expected change"
+
+expectMonitorUnchanged :: (Binary a, Binary b)
+                        => RootPath -> FileMonitor a b -> a
+                        -> IO (b, [MonitorFilePath])
+expectMonitorUnchanged root monitor key = do
+  res <- checkChanged root monitor key
+  case res of
+    MonitorChanged _reason   -> throwIO $ HUnitFailure "expected no change"
+    MonitorUnchanged b files -> return (b, files)
+
+checkChanged :: (Binary a, Binary b)
+             => RootPath -> FileMonitor a b
+             -> a -> IO (MonitorChanged a b)
+checkChanged (RootPath root) monitor key =
+  checkFileMonitorChanged monitor root key
+
+updateMonitor :: (Binary a, Binary b)
+              => RootPath -> FileMonitor a b
+              -> [MonitorFilePath] -> a -> b -> IO ()
+updateMonitor (RootPath root) monitor files key result =
+  updateFileMonitor monitor root Nothing files key result
+
+updateMonitorWithTimestamp :: (Binary a, Binary b)
+              => RootPath -> FileMonitor a b -> MonitorTimestamp
+              -> [MonitorFilePath] -> a -> b -> IO ()
+updateMonitorWithTimestamp (RootPath root) monitor timestamp files key result =
+  updateFileMonitor monitor root (Just timestamp) files key result
+
+withFileMonitor :: Eq a => (RootPath -> FileMonitor a b -> IO c) -> IO c
+withFileMonitor action = do
+  withTempDirectory silent "." "file-status-" $ \root -> do
+    let file    = root <.> "monitor"
+        monitor = newFileMonitor file
+    finally (action (RootPath root) monitor) $ do
+      exists <- IO.doesFileExist file
+      when exists $ IO.removeFile file
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs
@@ -0,0 +1,202 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module UnitTests.Distribution.Client.Glob (tests) where
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
+import Data.Char
+import Data.List
+import Distribution.Text (display, parse, simpleParse)
+import Distribution.Compat.ReadP
+
+import Distribution.Client.Glob
+import UnitTests.Distribution.Client.ArbitraryInstances
+
+import Test.Tasty
+import Test.Tasty.QuickCheck
+import Test.Tasty.HUnit
+import Control.Exception
+
+
+tests :: [TestTree]
+tests =
+  [ testProperty "print/parse roundtrip" prop_roundtrip_printparse
+  , testCase     "parse examples"        testParseCases
+  ]
+
+--TODO: [nice to have] tests for trivial globs, tests for matching,
+-- tests for windows style file paths
+
+prop_roundtrip_printparse :: FilePathGlob -> Bool
+prop_roundtrip_printparse pathglob =
+  -- can't use simpleParse because it mis-handles trailing spaces
+  case [ x | (x, []) <- readP_to_S parse (display pathglob) ] of
+    xs@(_:_) -> last xs == pathglob
+    _        -> False
+
+-- first run, where we don't even call updateMonitor
+testParseCases :: Assertion
+testParseCases = do
+
+  FilePathGlob FilePathUnixRoot GlobDirTrailing <- testparse "/"
+  FilePathGlob FilePathHomeDir  GlobDirTrailing <- testparse "~/"
+
+  FilePathGlob (FilePathWinDrive 'A') GlobDirTrailing <- testparse "A:/"
+  FilePathGlob (FilePathWinDrive 'Z') GlobDirTrailing <- testparse "z:/"
+  FilePathGlob (FilePathWinDrive 'C') GlobDirTrailing <- testparse "C:\\"
+  FilePathGlob FilePathRelative (GlobFile [Literal "_:"]) <- testparse "_:"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [Literal "."]) <- testparse "."
+
+  FilePathGlob FilePathRelative
+    (GlobFile [Literal "~"]) <- testparse "~"
+
+  FilePathGlob FilePathRelative
+    (GlobDir  [Literal "."] GlobDirTrailing) <- testparse "./"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [Literal "foo"]) <- testparse "foo"
+
+  FilePathGlob FilePathRelative
+    (GlobDir [Literal "foo"]
+      (GlobFile [Literal "bar"])) <- testparse "foo/bar"
+
+  FilePathGlob FilePathRelative
+    (GlobDir [Literal "foo"]
+      (GlobDir [Literal "bar"] GlobDirTrailing)) <- testparse "foo/bar/"
+
+  FilePathGlob FilePathUnixRoot
+    (GlobDir [Literal "foo"]
+      (GlobDir [Literal "bar"] GlobDirTrailing)) <- testparse "/foo/bar/"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [WildCard]) <- testparse "*"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [WildCard,WildCard]) <- testparse "**" -- not helpful but valid
+
+  FilePathGlob FilePathRelative
+    (GlobFile [WildCard, Literal "foo", WildCard]) <- testparse "*foo*"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [Literal "foo", WildCard, Literal "bar"]) <- testparse "foo*bar"
+
+  FilePathGlob FilePathRelative
+    (GlobFile [Union [[WildCard], [Literal "foo"]]]) <- testparse "{*,foo}"
+
+  parseFail "{"
+  parseFail "}"
+  parseFail ","
+  parseFail "{"
+  parseFail "{{}"
+  parseFail "{}"
+  parseFail "{,}"
+  parseFail "{foo,}"
+  parseFail "{,foo}"
+
+  return ()
+
+testparse :: String -> IO FilePathGlob
+testparse s =
+    case simpleParse s of
+      Just p  -> return p
+      Nothing -> throwIO $ HUnitFailure ("expected parse of: " ++ s)
+
+parseFail :: String -> Assertion
+parseFail s =
+    case simpleParse s :: Maybe FilePathGlob of
+      Just _  -> throwIO $ HUnitFailure ("expected no parse of: " ++ s)
+      Nothing -> return ()
+
+instance Arbitrary FilePathGlob where
+  arbitrary = (FilePathGlob <$> arbitrary <*> arbitrary)
+                `suchThat` validFilePathGlob
+
+  shrink (FilePathGlob root pathglob) =
+    [ FilePathGlob root' pathglob'
+    | (root', pathglob') <- shrink (root, pathglob)
+    , validFilePathGlob (FilePathGlob root' pathglob') ]
+
+validFilePathGlob :: FilePathGlob -> Bool
+validFilePathGlob (FilePathGlob FilePathRelative pathglob) =
+  case pathglob of
+    GlobDirTrailing             -> False
+    GlobDir [Literal "~"] _     -> False
+    GlobDir [Literal (d:":")] _ 
+      | isLetter d              -> False
+    _                           -> True
+validFilePathGlob _ = True
+
+instance Arbitrary FilePathRoot where
+  arbitrary =
+    frequency
+      [ (3, pure FilePathRelative)
+      , (1, pure FilePathUnixRoot)
+      , (1, pure FilePathHomeDir)
+      , (1, FilePathWinDrive <$> choose ('A', 'Z'))
+      ]
+
+  shrink FilePathRelative     = []
+  shrink FilePathUnixRoot     = [FilePathRelative]
+  shrink FilePathHomeDir      = [FilePathRelative]
+  shrink (FilePathWinDrive d) = FilePathRelative
+                              : [ FilePathWinDrive d' | d' <- shrink d ]
+
+
+instance Arbitrary FilePathGlobRel where
+  arbitrary = sized $ \sz ->
+    oneof $ take (max 1 sz)
+      [ pure GlobDirTrailing
+      , GlobFile  <$> (getGlobPieces <$> arbitrary)
+      , GlobDir   <$> (getGlobPieces <$> arbitrary)
+                  <*> resize (sz `div` 2) arbitrary
+      ]
+
+  shrink GlobDirTrailing = []
+  shrink (GlobFile glob) =
+      GlobDirTrailing
+    : [ GlobFile (getGlobPieces glob') | glob' <- shrink (GlobPieces glob) ]
+  shrink (GlobDir glob pathglob) =
+      pathglob
+    : GlobFile glob
+    : [ GlobDir (getGlobPieces glob') pathglob'
+      | (glob', pathglob') <- shrink (GlobPieces glob, pathglob) ]
+
+newtype GlobPieces = GlobPieces { getGlobPieces :: [GlobPiece] }
+  deriving Eq
+
+instance Arbitrary GlobPieces where
+  arbitrary = GlobPieces . mergeLiterals <$> shortListOf1 5 arbitrary
+
+  shrink (GlobPieces glob) =
+    [ GlobPieces (mergeLiterals (getNonEmpty glob'))
+    | glob' <- shrink (NonEmpty glob) ]
+
+mergeLiterals :: [GlobPiece] -> [GlobPiece]
+mergeLiterals (Literal a : Literal b : ps) = mergeLiterals (Literal (a++b) : ps)
+mergeLiterals (Union as : ps) = Union (map mergeLiterals as) : mergeLiterals ps
+mergeLiterals (p:ps) = p : mergeLiterals ps
+mergeLiterals []     = []
+
+instance Arbitrary GlobPiece where
+  arbitrary = sized $ \sz ->
+    frequency
+      [ (3, Literal <$> shortListOf1 10 (elements globLiteralChars))
+      , (1, pure WildCard)
+      , (1, Union <$> resize (sz `div` 2) (shortListOf1 5 (shortListOf1 5 arbitrary)))
+      ]
+
+  shrink (Literal str) = [ Literal str'
+                         | str' <- shrink str
+                         , not (null str')
+                         , all (`elem` globLiteralChars) str' ]
+  shrink WildCard       = []
+  shrink (Union as)     = [ Union (map getGlobPieces (getNonEmpty as'))
+                          | as' <- shrink (NonEmpty (map GlobPieces as)) ]
+
+globLiteralChars :: [Char]
+globLiteralChars = ['\0'..'\128'] \\ "*{},/\\"
+
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
@@ -0,0 +1,610 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module UnitTests.Distribution.Client.ProjectConfig (tests) where
+
+#if !MIN_VERSION_base(4,8,0)
+import Data.Monoid
+import Control.Applicative
+#endif
+import Data.Map (Map)
+import qualified Data.Map as Map
+import Data.List
+
+import Distribution.Package
+import Distribution.PackageDescription hiding (Flag)
+import Distribution.Compiler
+import Distribution.Version
+import Distribution.ParseUtils
+import Distribution.Simple.Compiler
+import Distribution.Simple.Setup
+import Distribution.Simple.InstallDirs
+import qualified Distribution.Compat.ReadP as Parse
+import Distribution.Simple.Utils
+import Distribution.Simple.Program.Types
+import Distribution.Simple.Program.Db
+
+import Distribution.Client.Types
+import Distribution.Client.Dependency.Types
+import Distribution.Client.BuildReports.Types
+import Distribution.Client.Targets
+import Distribution.Utils.NubList
+import Network.URI
+
+import Distribution.Client.ProjectConfig
+import Distribution.Client.ProjectConfig.Legacy
+
+import UnitTests.Distribution.Client.ArbitraryInstances
+
+import Test.Tasty
+import Test.Tasty.QuickCheck
+
+tests :: [TestTree]
+tests =
+  [ testGroup "ProjectConfig <-> LegacyProjectConfig round trip" $
+    [ testProperty "packages"  prop_roundtrip_legacytypes_packages
+    , testProperty "buildonly" prop_roundtrip_legacytypes_buildonly
+    , testProperty "specific"  prop_roundtrip_legacytypes_specific
+    ] ++
+    -- a couple tests seem to trigger a RTS fault in ghc-7.6 and older
+    -- unclear why as of yet
+    concat
+    [ [ testProperty "shared"    prop_roundtrip_legacytypes_shared
+      , testProperty "local"     prop_roundtrip_legacytypes_local
+      , testProperty "all"       prop_roundtrip_legacytypes_all
+      ]
+    | not usingGhc76orOlder
+    ]
+
+  , testGroup "individual parser tests"
+    [ testProperty "package location"  prop_parsePackageLocationTokenQ
+    ]
+
+  , testGroup "ProjectConfig printing/parsing round trip"
+    [ testProperty "packages"  prop_roundtrip_printparse_packages
+    , testProperty "buildonly" prop_roundtrip_printparse_buildonly
+    , testProperty "shared"    prop_roundtrip_printparse_shared
+    , testProperty "local"     prop_roundtrip_printparse_local
+    , testProperty "specific"  prop_roundtrip_printparse_specific
+    , testProperty "all"       prop_roundtrip_printparse_all
+    ]
+  ]
+  where
+    usingGhc76orOlder =
+      case buildCompilerId of
+        CompilerId GHC v -> v < Version [7,7] []
+        _                -> False
+
+
+------------------------------------------------
+-- Round trip: conversion to/from legacy types
+--
+
+roundtrip :: Eq a => (a -> b) -> (b -> a) -> a -> Bool
+roundtrip f f_inv x =
+    (f_inv . f) x == x
+
+roundtrip_legacytypes :: ProjectConfig -> Bool
+roundtrip_legacytypes =
+    roundtrip convertToLegacyProjectConfig
+              convertLegacyProjectConfig
+
+
+prop_roundtrip_legacytypes_all :: ProjectConfig -> Bool
+prop_roundtrip_legacytypes_all =
+    roundtrip_legacytypes
+
+prop_roundtrip_legacytypes_packages :: ProjectConfig -> Bool
+prop_roundtrip_legacytypes_packages config =
+    roundtrip_legacytypes
+      config {
+        projectConfigBuildOnly       = mempty,
+        projectConfigShared          = mempty,
+        projectConfigLocalPackages   = mempty,
+        projectConfigSpecificPackage = mempty
+      }
+
+prop_roundtrip_legacytypes_buildonly :: ProjectConfigBuildOnly -> Bool
+prop_roundtrip_legacytypes_buildonly config =
+    roundtrip_legacytypes
+      mempty { projectConfigBuildOnly = config }
+
+prop_roundtrip_legacytypes_shared :: ProjectConfigShared -> Bool
+prop_roundtrip_legacytypes_shared config =
+    roundtrip_legacytypes
+      mempty { projectConfigShared = config }
+
+prop_roundtrip_legacytypes_local :: PackageConfig -> Bool
+prop_roundtrip_legacytypes_local config =
+    roundtrip_legacytypes
+      mempty { projectConfigLocalPackages = config }
+
+prop_roundtrip_legacytypes_specific :: Map PackageName PackageConfig -> Bool
+prop_roundtrip_legacytypes_specific config =
+    roundtrip_legacytypes
+      mempty { projectConfigSpecificPackage = config }
+
+
+--------------------------------------------
+-- Round trip: printing and parsing config
+--
+
+roundtrip_printparse :: ProjectConfig -> Bool
+roundtrip_printparse config =
+    case (fmap convertLegacyProjectConfig
+        . parseLegacyProjectConfig
+        . showLegacyProjectConfig
+        . convertToLegacyProjectConfig)
+          config of
+      ParseOk _ x -> x == config
+      _           -> False
+
+
+prop_roundtrip_printparse_all :: ProjectConfig -> Bool
+prop_roundtrip_printparse_all config =
+    roundtrip_printparse config {
+      projectConfigBuildOnly =
+        hackProjectConfigBuildOnly (projectConfigBuildOnly config),
+
+      projectConfigShared =
+        hackProjectConfigShared (projectConfigShared config)
+    }
+
+prop_roundtrip_printparse_packages :: [PackageLocationString]
+                                   -> [PackageLocationString]
+                                   -> [SourceRepo]
+                                   -> [Dependency]
+                                   -> Bool
+prop_roundtrip_printparse_packages pkglocstrs1 pkglocstrs2 repos named =
+    roundtrip_printparse
+      mempty {
+        projectPackages         = map getPackageLocationString pkglocstrs1,
+        projectPackagesOptional = map getPackageLocationString pkglocstrs2,
+        projectPackagesRepo     = repos,
+        projectPackagesNamed    = named
+      }
+
+prop_roundtrip_printparse_buildonly :: ProjectConfigBuildOnly -> Bool
+prop_roundtrip_printparse_buildonly config =
+    roundtrip_printparse
+      mempty {
+        projectConfigBuildOnly = hackProjectConfigBuildOnly config
+      }
+
+hackProjectConfigBuildOnly :: ProjectConfigBuildOnly -> ProjectConfigBuildOnly
+hackProjectConfigBuildOnly config =
+    config {
+      -- These two fields are only command line transitory things, not
+      -- something to be recorded persistently in a config file
+      projectConfigOnlyDeps = mempty,
+      projectConfigDryRun   = mempty
+    }
+
+prop_roundtrip_printparse_shared :: ProjectConfigShared -> Bool
+prop_roundtrip_printparse_shared config =
+    roundtrip_printparse
+      mempty {
+        projectConfigShared = hackProjectConfigShared config
+      }
+
+hackProjectConfigShared :: ProjectConfigShared -> ProjectConfigShared
+hackProjectConfigShared config =
+    config {
+      projectConfigConstraints =
+      --TODO: [required eventually] parse ambiguity in constraint
+      -- "pkgname -any" as either any version or disabled flag "any".
+        let ambiguous ((UserConstraintFlags _pkg flags), _) =
+              (not . null) [ () | (FlagName name, False) <- flags
+                                , "any" `isPrefixOf` name ]
+            ambiguous _ = False
+         in filter (not . ambiguous) (projectConfigConstraints config)
+    }
+
+
+prop_roundtrip_printparse_local :: PackageConfig -> Bool
+prop_roundtrip_printparse_local config =
+    roundtrip_printparse
+      mempty {
+        projectConfigLocalPackages = config
+      }
+
+prop_roundtrip_printparse_specific :: Map PackageName (NonMEmpty PackageConfig)
+                                   -> Bool
+prop_roundtrip_printparse_specific config =
+    roundtrip_printparse
+      mempty {
+        projectConfigSpecificPackage = fmap getNonMEmpty config
+      }
+
+
+----------------------------
+-- Individual Parser tests 
+--
+
+prop_parsePackageLocationTokenQ :: PackageLocationString -> Bool
+prop_parsePackageLocationTokenQ (PackageLocationString str) =
+    case [ x | (x,"") <- Parse.readP_to_S parsePackageLocationTokenQ
+                                         (renderPackageLocationToken str) ] of
+      [str'] -> str' == str
+      _      -> False
+
+
+------------------------
+-- Arbitrary instances
+--
+
+instance Arbitrary ProjectConfig where
+    arbitrary =
+      ProjectConfig
+        <$> (map getPackageLocationString <$> arbitrary)
+        <*> (map getPackageLocationString <$> arbitrary)
+        <*> shortListOf 3 arbitrary
+        <*> arbitrary
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary
+        <*> (fmap getNonMEmpty . Map.fromList <$> shortListOf 3 arbitrary)
+        -- package entries with no content are equivalent to
+        -- the entry not existing at all, so exclude empty
+
+    shrink (ProjectConfig x0 x1 x2 x3 x4 x5 x6 x7) =
+      [ ProjectConfig x0' x1' x2' x3' x4' x5' x6' (fmap getNonMEmpty x7')
+      | ((x0', x1', x2', x3'), (x4', x5', x6', x7'))
+          <- shrink ((x0, x1, x2, x3), (x4, x5, x6, fmap NonMEmpty x7))
+      ]
+
+newtype PackageLocationString
+      = PackageLocationString { getPackageLocationString :: String }
+  deriving Show
+
+instance Arbitrary PackageLocationString where
+  arbitrary =
+    PackageLocationString <$>
+    oneof
+      [ show . getNonEmpty <$> (arbitrary :: Gen (NonEmptyList String))
+      , arbitraryGlobLikeStr
+      , show <$> (arbitrary :: Gen URI)
+      ]
+
+arbitraryGlobLikeStr :: Gen String
+arbitraryGlobLikeStr = outerTerm
+  where
+    outerTerm  = concat <$> shortListOf1 4
+                  (frequency [ (2, token)
+                             , (1, braces <$> innerTerm) ])
+    innerTerm  = intercalate "," <$> shortListOf1 3
+                  (frequency [ (3, token)
+                             , (1, braces <$> innerTerm) ])
+    token      = shortListOf1 4 (elements (['#'..'~'] \\ "{,}"))
+    braces s   = "{" ++ s ++ "}"
+
+
+instance Arbitrary ProjectConfigBuildOnly where
+    arbitrary =
+      ProjectConfigBuildOnly
+        <$> arbitrary
+        <*> arbitrary
+        <*> arbitrary
+        <*> (toNubList <$> shortListOf 2 arbitrary)             --  4
+        <*> arbitrary
+        <*> arbitrary
+        <*> arbitrary
+        <*> (fmap getShortToken <$> arbitrary)                  --  8
+        <*> arbitrary
+        <*> arbitraryNumJobs
+        <*> arbitrary
+        <*> arbitrary                                           -- 12
+        <*> (fmap getShortToken <$> arbitrary)
+        <*> arbitrary
+        <*> (fmap getShortToken <$> arbitrary)
+        <*> (fmap getShortToken <$> arbitrary)                  -- 16
+        <*> (fmap getShortToken <$> arbitrary)
+        <*> (fmap getShortToken <$> arbitrary)
+      where
+        arbitraryNumJobs = fmap (fmap getPositive) <$> arbitrary
+
+    shrink (ProjectConfigBuildOnly
+              x00 x01 x02 x03 x04 x05 x06 x07
+              x08 x09 x10 x11 x12 x13 x14 x15
+              x16 x17) =
+      [ ProjectConfigBuildOnly
+          x00' x01' x02' x03' x04'
+          x05' x06' x07' x08' (postShrink_NumJobs x09')
+          x10' x11' x12  x13' x14
+          x15 x16 x17
+      | ((x00', x01', x02', x03', x04'),
+         (x05', x06', x07', x08', x09'),
+         (x10', x11',       x13'))
+          <- shrink
+               ((x00, x01, x02, x03, x04),
+                (x05, x06, x07, x08, preShrink_NumJobs x09),
+                (x10, x11,      x13))
+      ]
+      where
+        preShrink_NumJobs  = fmap (fmap Positive)
+        postShrink_NumJobs = fmap (fmap getPositive)
+
+instance Arbitrary ProjectConfigShared where
+    arbitrary =
+      ProjectConfigShared
+        <$> (Map.fromList <$> shortListOf 10 
+              ((,) <$> arbitraryProgramName
+                   <*> arbitraryShortToken))
+        <*> (Map.fromList <$> shortListOf 10 
+              ((,) <$> arbitraryProgramName
+                   <*> listOf arbitraryShortToken))
+        <*> (toNubList <$> listOf arbitraryShortToken)
+        <*> arbitrary                                           --  4
+        <*> arbitraryFlag arbitraryShortToken
+        <*> arbitraryFlag arbitraryShortToken
+        <*> arbitrary
+        <*> arbitrary                                           --  8
+        <*> (toNubList <$> listOf arbitraryShortToken)
+        <*> arbitraryConstraints
+        <*> arbitrary <*> shortListOf 2 arbitrary               -- 12
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 16
+        <*> arbitrary <*> arbitrary
+      where
+        arbitraryProgramName :: Gen String
+        arbitraryProgramName =
+          elements [ programName prog
+                   | (prog, _) <- knownPrograms (defaultProgramDb) ]
+
+        arbitraryConstraints :: Gen [(UserConstraint, ConstraintSource)]
+        arbitraryConstraints =
+            map (\uc -> (uc, projectConfigConstraintSource)) <$> arbitrary
+
+    shrink (ProjectConfigShared
+              x00 x01 x02 x03 x04
+              x05 x06 x07 x08 x09
+              x10 x11 x12 x13 x14
+              x15 x16 x17) =
+      [ ProjectConfigShared
+          (postShrink_Paths x00')
+          (postShrink_Args  x01')
+          x02' x03'
+          (fmap getNonEmpty x04')
+          (fmap getNonEmpty x05')
+               x06' x07' x08'
+          (postShrink_Constraints x09')
+          x10' x11' x12' x13' x14'
+          x15' x16' x17'
+      | ((x00', x01', x02', x03', x04'),
+         (x05', x06', x07', x08', x09'),
+         (x10', x11', x12', x13', x14'),
+         (x15', x16', x17'))
+          <- shrink
+               ((preShrink_Paths x00,
+                 preShrink_Args  x01,
+                 x02, x03, fmap NonEmpty x04),
+                (fmap NonEmpty x05, x06, x07, x08, preShrink_Constraints x09),
+                (x10, x11, x12, x13, x14),
+                (x15, x16, x17))
+      ]
+      where
+        preShrink_Paths  = Map.map NonEmpty . Map.mapKeys NoShrink
+        postShrink_Paths = Map.map getNonEmpty . Map.mapKeys getNoShrink
+        preShrink_Args   = Map.map (NonEmpty . map NonEmpty)
+                         . Map.mapKeys NoShrink
+        postShrink_Args  = Map.map (map getNonEmpty . getNonEmpty)
+                         . Map.mapKeys getNoShrink
+        preShrink_Constraints  = map fst
+        postShrink_Constraints = map (\uc -> (uc, projectConfigConstraintSource))
+
+projectConfigConstraintSource :: ConstraintSource
+projectConfigConstraintSource = 
+    ConstraintSourceProjectConfig "TODO"
+
+instance Arbitrary PackageConfig where
+    arbitrary =
+      PackageConfig
+        <$> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             --  4
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             --  8
+        <*> shortListOf 5 arbitraryShortToken
+        <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 12
+        <*> shortListOf 5 arbitraryShortToken
+        <*> shortListOf 5 arbitraryShortToken
+        <*> shortListOf 5 arbitraryShortToken
+        <*> arbitrary                                           -- 16
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 20
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 24
+        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 28
+        <*> arbitraryFlag arbitraryShortToken
+        <*> arbitrary
+        <*> arbitrary <*> arbitrary                             -- 32
+        <*> arbitrary
+        <*> arbitraryFlag arbitraryShortToken
+        <*> arbitrary
+        <*> arbitraryFlag arbitraryShortToken                   -- 36
+        <*> arbitrary
+
+    shrink (PackageConfig
+              x00 x01 x02 x03 x04
+              x05 x06 x07 x08 x09
+              x10 x11 x12 x13 x14
+              x15 x16 x17 x18 x19
+              x20 x21 x22 x23 x24
+              x25 x26 x27 x28 x29
+              x30 x31 x32 x33 x34
+              x35 x36) =
+      [ PackageConfig
+          x00' x01' x02' x03' x04'
+          x05' x06' x07' (map getNonEmpty x08') x09'
+          x10' x11'
+          (map getNonEmpty x12')
+          (map getNonEmpty x13')
+          (map getNonEmpty x14')
+          x15' x16' x17' x18' x19'
+          x20' x21' x22' x23' x24'
+          x25' x26' x27' x28' x29'
+          x30' x31' x32' (fmap getNonEmpty x33') x34'
+          (fmap getNonEmpty x35') x36'
+      | (((x00', x01', x02', x03', x04'),
+          (x05', x06', x07', x08', x09'),
+          (x10', x11', x12', x13', x14'),
+          (x15', x16', x17', x18', x19')),
+         ((x20', x21', x22', x23', x24'),
+          (x25', x26', x27', x28', x29'),
+          (x30', x31', x32', x33', x34'),
+          (x35', x36')))
+          <- shrink
+               (((x00, x01, x02, x03, x04),
+                 (x05, x06, x07, map NonEmpty x08, x09),
+                 (x10, x11,
+                  map NonEmpty x12,
+                  map NonEmpty x13,
+                  map NonEmpty x14),
+                 (x15, x16, x17, x18, x19)),
+                ((x20, x21, x22, x23, x24),
+                 (x25, x26, x27, x28, x29),
+                 (x30, x31, x32, fmap NonEmpty x33, x34),
+                 (fmap NonEmpty x35, x36)))
+      ]
+
+
+instance Arbitrary SourceRepo where
+    arbitrary = (SourceRepo RepoThis
+                           <$> arbitrary
+                           <*> (fmap getShortToken <$> arbitrary)
+                           <*> (fmap getShortToken <$> arbitrary)
+                           <*> (fmap getShortToken <$> arbitrary)
+                           <*> (fmap getShortToken <$> arbitrary)
+                           <*> (fmap getShortToken <$> arbitrary))
+                `suchThat` (/= emptySourceRepo)
+
+    shrink (SourceRepo _ x1 x2 x3 x4 x5 x6) =
+      [ repo
+      | ((x1', x2', x3'), (x4', x5', x6'))
+          <- shrink ((x1,
+                      fmap ShortToken x2,
+                      fmap ShortToken x3),
+                     (fmap ShortToken x4,
+                      fmap ShortToken x5,
+                      fmap ShortToken x6))
+      , let repo = SourceRepo RepoThis x1'
+                              (fmap getShortToken x2')
+                              (fmap getShortToken x3')
+                              (fmap getShortToken x4')
+                              (fmap getShortToken x5')
+                              (fmap getShortToken x6')
+      , repo /= emptySourceRepo
+      ]
+
+emptySourceRepo :: SourceRepo
+emptySourceRepo = SourceRepo RepoThis Nothing Nothing Nothing
+                                      Nothing Nothing Nothing
+
+
+instance Arbitrary RepoType where
+    arbitrary = elements knownRepoTypes
+
+instance Arbitrary ReportLevel where
+    arbitrary = elements [NoReports .. DetailedReports]
+
+instance Arbitrary CompilerFlavor where
+    arbitrary = elements knownCompilerFlavors
+      where
+        --TODO: [code cleanup] export knownCompilerFlavors from D.Compiler
+        -- it's already defined there, just need it exported.
+        knownCompilerFlavors =
+          [GHC, GHCJS, NHC, YHC, Hugs, HBC, Helium, JHC, LHC, UHC]
+
+instance Arbitrary a => Arbitrary (InstallDirs a) where
+    arbitrary =
+      InstallDirs
+        <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary --  4
+        <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary --  8
+        <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary -- 12
+        <*> arbitrary <*> arbitrary                             -- 14
+
+instance Arbitrary PackageDB where
+    arbitrary = oneof [ pure GlobalPackageDB
+                      , pure UserPackageDB
+                      , SpecificPackageDB . getShortToken <$> arbitrary
+                      ]
+
+instance Arbitrary RemoteRepo where
+    arbitrary =
+      RemoteRepo
+        <$> arbitraryShortToken `suchThat` (not . (":" `isPrefixOf`))
+        <*> arbitrary  -- URI
+        <*> arbitrary
+        <*> listOf arbitraryRootKey
+        <*> (fmap getNonNegative arbitrary)
+        <*> pure False
+      where
+        arbitraryRootKey =
+          shortListOf1 5 (oneof [ choose ('0', '9')
+                                , choose ('a', 'f') ])
+
+instance Arbitrary UserConstraint where
+    arbitrary =
+      oneof
+        [ UserConstraintVersion   <$> arbitrary <*> arbitrary
+        , UserConstraintInstalled <$> arbitrary
+        , UserConstraintSource    <$> arbitrary
+        , UserConstraintFlags     <$> arbitrary <*> shortListOf1 3 arbitrary
+        , UserConstraintStanzas   <$> arbitrary <*> ((\x->[x]) <$> arbitrary)
+        ]
+
+instance Arbitrary OptionalStanza where
+    arbitrary = elements [minBound..maxBound]
+
+instance Arbitrary FlagName where
+    arbitrary = FlagName <$> flagident
+      where
+        flagident   = lowercase <$> shortListOf1 5 (elements flagChars)
+                      `suchThat` (("-" /=) . take 1)
+        flagChars   = "-_" ++ ['a'..'z']
+
+instance Arbitrary PreSolver where
+    arbitrary = elements [minBound..maxBound]
+
+instance Arbitrary AllowNewer where
+    arbitrary = oneof [ pure AllowNewerNone
+                      , AllowNewerSome <$> shortListOf1 3 arbitrary
+                      , pure AllowNewerAll
+                      ]
+
+instance Arbitrary AllowNewerDep where
+    arbitrary = oneof [ AllowNewerDep       <$> arbitrary
+                      , AllowNewerDepScoped <$> arbitrary <*> arbitrary
+                      ]
+
+instance Arbitrary ProfDetailLevel where
+    arbitrary = elements [ d | (_,_,d) <- knownProfDetailLevels ]
+
+instance Arbitrary OptimisationLevel where
+    arbitrary = elements [minBound..maxBound]
+
+instance Arbitrary DebugInfoLevel where
+    arbitrary = elements [minBound..maxBound]
+
+instance Arbitrary URI where
+    arbitrary =
+      URI <$> elements ["file:", "http:", "https:"]
+          <*> (Just <$> arbitrary)
+          <*> (('/':) <$> arbitraryURIToken)
+          <*> (('?':) <$> arbitraryURIToken)
+          <*> pure ""
+
+instance Arbitrary URIAuth where
+    arbitrary =
+      URIAuth <$> pure ""   -- no password as this does not roundtrip
+              <*> arbitraryURIToken
+              <*> arbitraryURIPort
+
+arbitraryURIToken :: Gen String
+arbitraryURIToken =
+    shortListOf1 6 (elements (filter isUnreserved ['\0'..'\255']))
+
+arbitraryURIPort :: Gen String
+arbitraryURIPort =
+    oneof [ pure "", (':':) <$> shortListOf1 4 (choose ('0','9')) ]
+
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Sandbox/Timestamp.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Sandbox/Timestamp.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Sandbox/Timestamp.hs
@@ -0,0 +1,63 @@
+module UnitTests.Distribution.Client.Sandbox.Timestamp (tests) where
+
+import System.FilePath
+
+import Distribution.Simple.Utils (withTempDirectory)
+import Distribution.Verbosity
+
+import Distribution.Client.Compat.Time
+import Distribution.Client.Sandbox.Timestamp
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+tests :: [TestTree]
+tests =
+  [ testCase "timestamp record version 1 can be read" timestampReadTest_v1
+  , testCase "timestamp record version 2 can be read" timestampReadTest_v2
+  , testCase "written timestamp record can be read"   timestampReadWriteTest ]
+
+timestampRecord_v1 :: String
+timestampRecord_v1 =
+  "[(\"i386-linux-ghc-8.0.0.20160204\",[(\"/foo/bar/Baz\",1455350946)])" ++
+  ",(\"i386-linux-ghc-7.10.3\",[(\"/foo/bar/Baz\",1455484719)])]\n"
+
+timestampRecord_v2 :: String
+timestampRecord_v2 =
+  "2\n" ++
+  "[(\"i386-linux-ghc-8.0.0.20160204\",[(\"/foo/bar/Baz\",1455350946)])" ++
+  ",(\"i386-linux-ghc-7.10.3\",[(\"/foo/bar/Baz\",1455484719)])]"
+
+timestampReadTest_v1 :: Assertion
+timestampReadTest_v1 =
+  timestampReadTest timestampRecord_v1 $
+  map (\(i, ts) ->
+        (i, map (\(p, ModTime t) ->
+                  (p, posixSecondsToModTime . fromIntegral $ t)) ts))
+  timestampRecord
+
+timestampReadTest_v2 :: Assertion
+timestampReadTest_v2 = timestampReadTest timestampRecord_v2 timestampRecord
+
+timestampReadTest :: FilePath -> [TimestampFileRecord] -> Assertion
+timestampReadTest fileContent expected =
+  withTempDirectory silent "." "cabal-timestamp-" $ \dir -> do
+    let fileName = dir </> "timestamp-record"
+    writeFile fileName fileContent
+    tRec <- readTimestampFile fileName
+    assertEqual "expected timestamp records to be equal"
+      expected tRec
+
+timestampRecord :: [TimestampFileRecord]
+timestampRecord =
+  [("i386-linux-ghc-8.0.0.20160204",[("/foo/bar/Baz",ModTime 1455350946)])
+  ,("i386-linux-ghc-7.10.3",[("/foo/bar/Baz",ModTime 1455484719)])]
+
+timestampReadWriteTest :: Assertion
+timestampReadWriteTest =
+  withTempDirectory silent "." "cabal-timestamp-" $ \dir -> do
+    let fileName = dir </> "timestamp-record"
+    writeTimestampFile fileName timestampRecord
+    tRec <- readTimestampFile fileName
+    assertEqual "expected timestamp records to be equal"
+      timestampRecord tRec
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/Tar.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Tar.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/Tar.hs
@@ -0,0 +1,75 @@
+module UnitTests.Distribution.Client.Tar (
+  tests
+  ) where
+
+import Distribution.Client.Tar ( filterEntries
+                               , filterEntriesM
+                               )
+import Codec.Archive.Tar       ( Entries(..)
+                               , foldEntries
+                               )
+import Codec.Archive.Tar.Entry ( EntryContent(..)
+                               , simpleEntry
+                               , Entry(..)
+                               , toTarPath
+                               )
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import qualified Data.ByteString.Lazy as BS
+import qualified Data.ByteString.Lazy.Char8 as BS.Char8
+import Control.Monad.Writer.Lazy (runWriterT, tell)
+
+tests :: [TestTree]
+tests = [ testCase "filterEntries" filterTest
+        , testCase "filterEntriesM" filterMTest
+        ]
+
+filterTest :: Assertion
+filterTest = do
+  let e1 = getFileEntry "file1" "x"
+      e2 = getFileEntry "file2" "y"
+      p = (\e -> let (NormalFile dta _) = entryContent e
+                     str = BS.Char8.unpack dta
+                 in not . (=="y") $ str)
+  assertEqual "Unexpected result for filter" "xz" $
+    entriesToString $ filterEntries p $ Next e1 $ Next e2 Done
+  assertEqual "Unexpected result for filter" "z" $
+    entriesToString $ filterEntries p $ Done
+  assertEqual "Unexpected result for filter" "xf" $
+    entriesToString $ filterEntries p $ Next e1 $ Next e2 $ Fail "f"
+
+filterMTest :: Assertion
+filterMTest = do
+  let e1 = getFileEntry "file1" "x"
+      e2 = getFileEntry "file2" "y"
+      p = (\e -> let (NormalFile dta _) = entryContent e
+                     str = BS.Char8.unpack dta
+                 in tell "t" >> return (not . (=="y") $ str))
+
+  (r, w) <- runWriterT $ filterEntriesM p $ Next e1 $ Next e2 Done
+  assertEqual "Unexpected result for filterM" "xz" $ entriesToString r
+  assertEqual "Unexpected result for filterM w" "tt" w
+
+  (r1, w1) <- runWriterT $ filterEntriesM p $ Done
+  assertEqual "Unexpected result for filterM" "z" $ entriesToString r1
+  assertEqual "Unexpected result for filterM w" "" w1
+
+  (r2, w2) <- runWriterT $ filterEntriesM p $ Next e1 $ Next e2 $ Fail "f"
+  assertEqual "Unexpected result for filterM" "xf" $ entriesToString r2
+  assertEqual "Unexpected result for filterM w" "tt" w2
+
+getFileEntry :: FilePath -> [Char] -> Entry
+getFileEntry pth dta =
+  simpleEntry tp $ NormalFile dta' $ BS.length dta'
+  where  tp = case toTarPath False pth of
+           Right tp' -> tp'
+           Left e -> error e
+         dta' = BS.Char8.pack dta
+
+entriesToString :: Entries String -> String
+entriesToString =
+  foldEntries (\e acc -> let (NormalFile dta _) = entryContent e
+                             str = BS.Char8.unpack dta
+                          in str ++ acc) "z" id
diff --git a/cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs b/cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs
--- a/cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs
+++ b/cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs
@@ -4,21 +4,24 @@
     ) where
 
 import Control.Exception (bracket)
+import Control.Monad (replicateM_)
 import Data.List (sort, nub)
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid
 #endif
-import System.Directory (getCurrentDirectory, removeDirectoryRecursive, createDirectoryIfMissing)
-import System.FilePath (takeDirectory)
+import System.Directory (doesFileExist,
+                         getCurrentDirectory, getTemporaryDirectory)
+import System.FilePath ((</>))
 
 import Test.Tasty
 import Test.Tasty.HUnit
 
-import Distribution.Compat.Environment (lookupEnv, setEnv)
 import Distribution.Client.Config
 import Distribution.Utils.NubList (fromNubList)
 import Distribution.Client.Setup (GlobalFlags (..), InstallFlags (..))
-import Distribution.Simple.Setup (ConfigFlags (..), fromFlag)
+import Distribution.Client.Utils (removeExistingFile)
+import Distribution.Simple.Setup (Flag (..), ConfigFlags (..), fromFlag)
+import Distribution.Simple.Utils (withTempDirectory)
 import Distribution.Verbosity (silent)
 
 tests :: [TestTree]
@@ -26,51 +29,48 @@
         , testCase "canDetectDifference" canDetectDifference
         , testCase "canUpdateConfig" canUpdateConfig
         , testCase "doubleUpdateConfig" doubleUpdateConfig
+        , testCase "newDefaultConfig" newDefaultConfig
         ]
 
 nullDiffOnCreateTest :: Assertion
-nullDiffOnCreateTest = bracketTest . const $ do
+nullDiffOnCreateTest = bracketTest $ \configFile -> do
     -- Create a new default config file in our test directory.
-    _ <- loadConfig silent mempty
+    _ <- loadConfig silent (Flag configFile)
     -- Now we read it in and compare it against the default.
-    diff <- userConfigDiff mempty
+    diff <- userConfigDiff $ globalFlags configFile
     assertBool (unlines $ "Following diff should be empty:" : diff) $ null diff
 
 
 canDetectDifference :: Assertion
-canDetectDifference = bracketTest . const $ do
+canDetectDifference = bracketTest $ \configFile -> do
     -- Create a new default config file in our test directory.
-    _ <- loadConfig silent mempty
-    cabalFile <- defaultConfigFile
-    appendFile cabalFile "verbose: 0\n"
-    diff <- userConfigDiff mempty
+    _ <- loadConfig silent (Flag configFile)
+    appendFile configFile "verbose: 0\n"
+    diff <- userConfigDiff $ globalFlags configFile
     assertBool (unlines $ "Should detect a difference:" : diff) $
         diff == [ "- verbose: 1", "+ verbose: 0" ]
 
 
 canUpdateConfig :: Assertion
-canUpdateConfig = bracketTest . const $ do
-    cabalFile <- defaultConfigFile
-    createDirectoryIfMissing True $ takeDirectory cabalFile
+canUpdateConfig = bracketTest $ \configFile -> do
     -- Write a trivial cabal file.
-    writeFile cabalFile "tests: True\n"
+    writeFile configFile "tests: True\n"
     -- Update the config file.
-    userConfigUpdate silent mempty
+    userConfigUpdate silent $ globalFlags configFile
     -- Load it again.
-    updated <- loadConfig silent mempty
+    updated <- loadConfig silent (Flag configFile)
     assertBool ("Field 'tests' should be True") $
         fromFlag (configTests $ savedConfigureFlags updated)
 
 
 doubleUpdateConfig :: Assertion
-doubleUpdateConfig = bracketTest . const $ do
+doubleUpdateConfig = bracketTest $ \configFile -> do
     -- Create a new default config file in our test directory.
-    _ <- loadConfig silent mempty
-    -- Update it.
-    userConfigUpdate silent mempty
-    userConfigUpdate silent mempty
+    _ <- loadConfig silent (Flag configFile)
+    -- Update it twice.
+    replicateM_ 2 . userConfigUpdate silent $ globalFlags configFile
     -- Load it again.
-    updated <- loadConfig silent mempty
+    updated <- loadConfig silent (Flag configFile)
 
     assertBool ("Field 'remote-repo' doesn't contain duplicates") $
         listUnique (map show . fromNubList . globalRemoteRepos $ savedGlobalFlags updated)
@@ -80,24 +80,33 @@
         listUnique (map show . fromNubList . installSummaryFile $ savedInstallFlags updated)
 
 
+newDefaultConfig :: Assertion
+newDefaultConfig = do
+    sysTmpDir <- getTemporaryDirectory
+    withTempDirectory silent sysTmpDir "cabal-test" $ \tmpDir -> do
+        let configFile  = tmpDir </> "tmp.config"
+        createDefaultConfigFile silent configFile
+        exists <- doesFileExist configFile
+        assertBool ("Config file should be written to " ++ configFile) exists
+
+
+globalFlags :: FilePath -> GlobalFlags
+globalFlags configFile = mempty { globalConfigFile = Flag configFile }
+
+
 listUnique :: Ord a => [a] -> Bool
 listUnique xs =
     let sorted = sort xs
     in nub sorted == xs
 
 
-bracketTest :: ((FilePath, FilePath) -> IO ()) -> Assertion
+bracketTest :: (FilePath -> IO ()) -> Assertion
 bracketTest =
     bracket testSetup testTearDown
   where
-    testSetup :: IO (FilePath, FilePath)
-    testSetup = do
-        Just oldHome <- lookupEnv "HOME"
-        testdir <- fmap (++ "/test-user-config") getCurrentDirectory
-        setEnv "HOME" testdir
-        return (oldHome, testdir)
+    testSetup :: IO FilePath
+    testSetup = fmap (</> "test-user-config") getCurrentDirectory
 
-    testTearDown :: (FilePath, FilePath) -> IO ()
-    testTearDown (oldHome, testdir) = do
-        setEnv "HOME" oldHome
-        removeDirectoryRecursive testdir
+    testTearDown :: FilePath -> IO ()
+    testTearDown configFile =
+        mapM_ removeExistingFile [configFile, configFile ++ ".backup"]
diff --git a/cabal/cabal-install/tests/UnitTests/Options.hs b/cabal/cabal-install/tests/UnitTests/Options.hs
new file mode 100644
--- /dev/null
+++ b/cabal/cabal-install/tests/UnitTests/Options.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+module UnitTests.Options ( OptionShowSolverLog(..)
+                         , OptionMtimeChangeDelay(..)
+                         , extraOptions )
+       where
+
+import Data.Proxy
+import Data.Typeable
+
+import Test.Tasty.Options
+
+{-------------------------------------------------------------------------------
+  Test options
+-------------------------------------------------------------------------------}
+
+extraOptions :: [OptionDescription]
+extraOptions =
+  [ Option (Proxy :: Proxy OptionShowSolverLog)
+  , Option (Proxy :: Proxy OptionMtimeChangeDelay)
+  ]
+
+newtype OptionShowSolverLog = OptionShowSolverLog Bool
+  deriving Typeable
+
+instance IsOption OptionShowSolverLog where
+  defaultValue   = OptionShowSolverLog False
+  parseValue     = fmap OptionShowSolverLog . safeRead
+  optionName     = return "show-solver-log"
+  optionHelp     = return "Show full log from the solver"
+  optionCLParser = flagCLParser Nothing (OptionShowSolverLog True)
+
+newtype OptionMtimeChangeDelay = OptionMtimeChangeDelay Int
+  deriving Typeable
+
+instance IsOption OptionMtimeChangeDelay where
+  defaultValue   = OptionMtimeChangeDelay 0
+  parseValue     = fmap OptionMtimeChangeDelay . safeRead
+  optionName     = return "mtime-change-delay"
+  optionHelp     = return $ "How long to wait before attempting to detect"
+                   ++ "file modification, in microseconds"
diff --git a/cabal/setup-dev.sh b/cabal/setup-dev.sh
--- a/cabal/setup-dev.sh
+++ b/cabal/setup-dev.sh
@@ -6,45 +6,31 @@
     exit 1
 }
 
-build_and_test() {
-   # Find sandbox location
-   local PACKAGEDB=`cabal exec -- sh -c 'echo $GHC_PACKAGE_PATH' | sed 's/:.*//'`
-   echo "Cabal package DB location: $PACKAGEDB"
-   # Do the building
-  ./Setup configure --enable-tests --package-db="$PACKAGEDB" || die "$1 'configure' failed"
-  ./Setup build || die "$1 'build' failed"
-# Disabled for now: Tests fail on my system for some reason
-#  ./Setup test || die "$1 'test' failed"
-}
-
-install_deps() {
-   cabal install --only-dependencies --enable-tests || die "$1: Could not install needed dependencies"
-}
-
-create_setup() {
-   ghc --make -threaded Setup.hs || die "$1: Could not create 'Setup' executable"
-}
-
-init_sandbox() {
-   cabal sandbox delete # Ignore error status; probably just means sandbox doesn't exist
-   cabal sandbox init || die "$1: Could not initialize sandbox"
-}
-
-setup_cabal() {
-   init_sandbox Cabal
-   install_deps Cabal
-   create_setup Cabal
-   build_and_test Cabal
-}
-
-setup_cabalinstall() {
-   init_sandbox cabal-install
-   cabal sandbox add-source ../Cabal/ || die "cabal-install: Failed to add ../Cabal source"
-   install_deps cabal-install
-   create_setup cabal-install
-   build_and_test cabal-install
+setup() {
+    # Extract parameters
+    local NAME="$1"
+    shift
+    local DEPS="$@"
+    # (Re-)create sandbox
+    cabal sandbox delete # Ignore error status; probably just means sandbox doesn't exist
+    cabal sandbox init || die "$NAME: Could not initialize sandbox"
+    # Add dependencies
+    for DEP in $DEPS; do
+        cabal sandbox add-source "$DEP"
+    done
+    # Install dependencies
+    cabal install --only-dependencies --enable-tests || die "$NAME: Could not install needed dependencies"
+    # Build the 'Setup' executable
+    ghc --make -threaded -i -i. Setup.hs || die "$NAME: Could not create 'Setup' executable"
+    # Build the package
+    local PACKAGEDB=`cabal exec -- sh -c 'echo $GHC_PACKAGE_PATH' | sed 's/:.*//'`
+    echo "Cabal package DB location: $PACKAGEDB"
+    ./Setup configure --enable-tests --package-db="$PACKAGEDB" || die "$NAME: 'configure' failed"
+    ./Setup build || die "$NAME: 'build' failed"
+    # Run tests
+    ./Setup test || die "$1 'test' failed"
 }
 
 # Build
-(cd ${SCRIPT_DIR}/Cabal         && setup_cabal       ) || die "Failed to build Cabal"
-(cd ${SCRIPT_DIR}/cabal-install && setup_cabalinstall) || die "Failed to build cabal-install"
+(cd ${SCRIPT_DIR}/Cabal         && setup "Cabal"                 ) || die "Failed to build Cabal"
+(cd ${SCRIPT_DIR}/cabal-install && setup "cabal-install" ../Cabal) || die "Failed to build cabal-install"
diff --git a/cabal/travis-script.sh b/cabal/travis-script.sh
new file mode 100644
--- /dev/null
+++ b/cabal/travis-script.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+set -ev
+
+# Initial working directory: base directory of Git repository
+
+# We depend on parsec nowadays, which isn't distributed with GHC <8.0
+if [ "$PARSEC_BUNDLED" != "YES" ]; then
+    cabal install parsec
+fi
+
+# ---------------------------------------------------------------------
+# Cabal
+# ---------------------------------------------------------------------
+
+cd Cabal
+
+# Test if gen-extra-source-files.sh was run recently enough
+./misc/gen-extra-source-files.sh Cabal.cabal
+./misc/travis-diff-files.sh
+
+cd ../cabal-install
+../Cabal/misc/gen-extra-source-files.sh cabal-install.cabal
+../Cabal/misc/travis-diff-files.sh
+
+cd ../Cabal
+
+# Build the setup script in the same way that cabal-install would:
+mkdir -p ./dist/setup
+cp Setup.hs ./dist/setup/setup.hs
+ghc --make \
+    -odir ./dist/setup -hidir ./dist/setup -i -i. \
+    ./dist/setup/setup.hs -o ./dist/setup/setup \
+    -Wall -Werror -threaded
+
+# Install test dependencies only after setup is built
+cabal install --only-dependencies --enable-tests --enable-benchmarks
+./dist/setup/setup configure \
+    --user --ghc-option=-Werror --enable-tests --enable-benchmarks \
+    -v2 # -v2 provides useful information for debugging
+
+# Build all libraries and executables (including tests/benchmarks)
+./dist/setup/setup build
+./dist/setup/setup haddock # see https://github.com/haskell/cabal/issues/2198
+./dist/setup/setup test --show-details=streaming --test-option=--hide-successes
+
+# Redo the package tests with different versions of GHC
+if [ "$TEST_OLDER" == "YES" ]; then
+    CABAL_PACKAGETESTS_WITH_GHC=/opt/ghc/7.0.4/bin/ghc \
+        ./dist/setup/setup test package-tests --show-details=streaming
+    CABAL_PACKAGETESTS_WITH_GHC=/opt/ghc/7.2.2/bin/ghc \
+        ./dist/setup/setup test package-tests --show-details=streaming
+fi
+
+cabal check
+cabal sdist   # tests that a source-distribution can be generated
+
+# The following scriptlet checks that the resulting source distribution can be
+# built & installed.
+function install_from_tarball {
+   export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;
+   if [ -f "dist/$SRC_TGZ" ]; then
+      cabal install -j1 "dist/$SRC_TGZ" -v2;
+   else
+      echo "expected 'dist/$SRC_TGZ' not found";
+      exit 1;
+   fi
+}
+
+install_from_tarball
+
+# ---------------------------------------------------------------------
+# cabal-install
+# ---------------------------------------------------------------------
+
+cd ../cabal-install
+
+cabal install happy
+cabal install --only-dependencies --enable-tests --enable-benchmarks
+cabal configure \
+    --user --ghc-option=-Werror --enable-tests --enable-benchmarks \
+    -v2 # -v2 provides useful information for debugging
+cabal build
+cabal haddock # see https://github.com/haskell/cabal/issues/2198
+cabal test unit-tests --show-details=streaming --test-option=--hide-successes
+cabal test integration-tests --show-details=streaming --test-option=--hide-successes
+cabal check
+./dist/setup/setup sdist
+install_from_tarball
+
+# Check what we got
+$HOME/.cabal/bin/cabal --version
diff --git a/hackage-security/.git b/hackage-security/.git
new file mode 100644
--- /dev/null
+++ b/hackage-security/.git
@@ -0,0 +1,1 @@
+gitdir: ../.git/modules/hackage-security
diff --git a/hackage-security/.gitignore b/hackage-security/.gitignore
new file mode 100644
--- /dev/null
+++ b/hackage-security/.gitignore
@@ -0,0 +1,5 @@
+tags
+prototype-state/client/
+prototype-state/server/
+prototype-state/offline/
+.stack-work/
diff --git a/hackage-security/.travis.yml b/hackage-security/.travis.yml
new file mode 100644
--- /dev/null
+++ b/hackage-security/.travis.yml
@@ -0,0 +1,125 @@
+# see also https://github.com/hvr/multi-ghc-travis
+language: c
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.cabsnap
+    - $HOME/.cabal/packages
+
+before_cache:
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar
+
+matrix:
+  include:
+# cabal 1.16's solver is too weak for GHC 7.4
+    - env: CABALVER=1.22 GHCVER=7.4.2
+      compiler: ": #GHC 7.4.2"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.4.2], sources: [hvr-ghc]}}
+    - env: CABALVER=1.16 GHCVER=7.6.3
+      compiler: ": #GHC 7.6.3"
+      addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3], sources: [hvr-ghc]}}
+    - env: CABALVER=1.18 GHCVER=7.8.4
+      compiler: ": #GHC 7.8.4"
+      addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
+    - env: CABALVER=1.22 GHCVER=7.10.2
+      compiler: ": #GHC 7.10.2"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2], sources: [hvr-ghc]}}
+
+before_install:
+ - unset CC
+ - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
+ - TARGETS="hackage-security/ hackage-security-curl/ hackage-security-HTTP/ hackage-root-tool/ hackage-repo-tool/ example-client/ hackage-security-http-client/";
+
+install:
+# Compute install plan (which we use as a travis cache key)
+ - cabal --version
+ - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
+ - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];
+   then
+     zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >
+          $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
+   fi
+ - travis_retry cabal update -v
+ - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
+ - cabal install --constraint="directory installed" --constraint="bytestring installed" --only-dependencies --enable-tests --dry -v $TARGETS > installplan.txt
+ - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt
+
+# check whether current requested install-plan matches cached package-db snapshot
+ - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;
+   then
+     echo "cabal build-cache HIT";
+     rm -rfv .ghc;
+     cp -a $HOME/.cabsnap/ghc $HOME/.ghc;
+     cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;
+   else
+     echo "cabal build-cache MISS";
+     rm -rf $HOME/.cabsnap;
+     mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
+     cabal install --constraint="directory installed" --constraint="bytestring installed" --only-dependencies --enable-tests $TARGETS;
+   fi
+
+# snapshot package-db on cache miss
+ - if [ ! -d $HOME/.cabsnap ];
+   then
+      echo "snapshotting package-db to build-cache";
+      mkdir $HOME/.cabsnap;
+      cp -a $HOME/.ghc $HOME/.cabsnap/ghc;
+      cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
+   fi
+
+# Here starts the actual work to be performed for the package under test;
+# any command which exits with a non-zero exit code causes the build to fail.
+script:
+ - cd hackage-security/
+ - cabal configure --enable-tests -v2
+ - cabal build
+ - cabal sdist
+ - cabal test
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd hackage-security-curl/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd hackage-security-HTTP/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd hackage-root-tool/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd hackage-repo-tool/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd hackage-security-http-client/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+ - cd example-client/
+ - cabal configure -v2
+ - cabal build
+ - cabal sdist
+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
+ - cd ..
+
+# EOF
diff --git a/hackage-security/README.md b/hackage-security/README.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/README.md
@@ -0,0 +1,694 @@
+# Hackage Security
+
+This is a library for Hackage security based on [TUF, The Update
+Framework][TUF].
+
+## Project phases and shortcuts
+
+Phase 1 of the project will implement the basic TUF framework, but leave out
+author signing; support for author signed packages (and other targets) will
+added in phase 2. The main goal of phase 1 is to be able to have untrusted
+mirrors of the Hackage server.
+
+## Brief overview of Hackage and cabal-install
+
+Hackage makes all packages available from a single directory `/package`; for
+example, version 1.0 of package Foo is available at `/package/Foo-1.0.tar.gz`
+(see [Footnote: Paths](#paths)).
+
+Additionally, Hackage offers a tarball, variously known as &ldquo;the
+index&rdquo; or &ldquo;the index tarball&rdquo;, located at `/00-index.tar.gz`.
+The index tarball contains the `.cabal` for all packages available on the
+server; `cabal-install` downloads this file whenever you call `cabal update` to
+figure out which packages are available, and it uses this file when you `cabal
+install` a package to figure out which dependencies to install. The `.cabal`
+file for Foo-1.0 is located at `Foo/1.0/Foo.cabal` in the index. (One side goal
+of the Hackage Security project is to make downloading the index incremental.)
+
+Note that although Hackage additionally offers the (latest version of) the
+`.cabal` file at `/package/Foo-1.0/Foo.cabal`, this is never used by
+`cabal-install`.
+
+In order to distinguish between paths on the server and paths in the index we
+will qualify them as `<repo>/package/Foo-1.0.tar.gz` and
+`<index>/Foo/1.0/Foo.cabal` respectively, both informally in this text and in
+formal delegation rules.
+
+## Comparison with TUF
+
+In this section we highlight some of the differences in the specifics of the
+implementation; generally we try to follow the TUF model as closely as
+possible.
+
+This section is not (currently) intended to be complete.
+
+### Targets
+
+In the current proposal we do not yet implement author signing, but when we do
+implement author signing we want to have a smooth transition, and moreover we
+want to be able to have a mixture of packages, some of which are author signed
+and some of which are not. That is, package authors must be able to opt-in to
+author signing (or not).
+
+#### Unsigned packages
+
+##### Package specific metadata
+
+The package metadata files (&ldquo;target files&rdquo;) will be stored _in the
+index_. The metadata for Foo 1.0 will be stored in
+`<index>/Foo/1.0/package.json`, and will contain the hash and size of the
+package tarball:
+
+``` javascript
+{ "signed" : {
+     "_type"   : "Targets"
+   , "version" : VERSION
+   , "expires" : never
+   , "targets" : { "<repo>/package/Foo-1.0.tar.gz" : FILEINFO }
+   }
+, "signatures" : []
+}
+```
+
+Note that expiry dates are relevant only for information that we expect to
+change over time (such as the snapshot). Since packages are immutable, they
+cannot expire. (Additionally, there is no point adding an expiry date to files
+that are protected only the snapshot key, as the snapshot _itself_ will expire).
+
+It is not necessary to list the file info of the `.cabal` files here: `.cabal`
+files are listed by value in the index tarball, and are therefore already
+protected by the snapshot key (but see [author signing](#author-signing)).
+
+##### Delegation
+
+[Conceptually speaking](#phase1-shortcuts) we then need a top-level target file
+`<index>/targets.json` that contains the required delegation information:
+
+``` javascript
+{ "signed" : {
+      "_type"       : Targets
+    , "version"     : VERSION
+    , "expires"     : never
+    , "targets"     : []
+    , "delegations" : {
+          "keys"  : []
+        , "roles" : [
+               { "name"      : "<index>/$NAME/$VERSION/package.json"
+               , "keyids"    : []
+               , "threshold" : 0
+               , "path"      : "<repo>/package/$NAME-$VERSION.tar.gz"
+               }
+             ]
+       }
+    }
+, "signatures" : /* target keys */
+}
+```
+
+This file itself is signed by the target keys (kept offline by the Hackage
+admins).  
+
+Note that this file uses various extension to TUF spec:
+
+* We can use wildcards in names as well as in paths. This means that we list a
+  <b>single</b> path with a <b>single</b> replacement name. (Alternatively, we
+  could have a list of pairs of paths and names.)
+* Paths contain namespaces (`<repo>` versus `<index>`)
+* Wildcards have more structure than TUF provides for.
+
+The first one of these is the most important, as it has some security
+implications; see comments below.
+
+New unsigned packages, as well as new versions of existing unsigned packages,
+can be uploaded to Hackage without any intervention from the Hackage admins (the
+offline target keys are not required).
+
+##### Security
+
+This setup is sufficient to allow for untrusted mirrors: since they do not have
+access to the snapshot key, they (or a man-in-the-middle) cannot change which
+packages are visible or change the packages themselves.
+
+However, since the snapshot key is stored on the server, if the server itself is
+compromised almost all security guarantees are void.
+
+#### <a name="author-signing">Signed packages</a>
+
+We sketch the design here only, we do not actually intend to implement this yet
+in phase 1 of the project.
+
+##### Package specific metadata
+
+As for unsigned packages, we keep metadata specific for each package version.
+Unlike for unsigned packages, however, we store two files: one that can be
+signed by the package author, and one that can be signed by the Hackage
+trustees, who can upload new `.cabal` file revisions but not change the
+package contents.
+
+As before we still have `<index>/Foo/1.0/package.json` containing
+
+``` javascript
+{ "signed" : {
+     "_type"   : "Targets"
+   , "version" : VERSION
+   , "expires" : never
+   , "targets" : { "<repo>/package/Foo-1.0.tar.gz" : FILEINFO }
+   }
+, "signatures" : /* signatures from package authors */
+}
+```
+
+It is not necessary to separately sign the `.cabal` file that is listed _inside_
+the package `.tar.gz` file. However, this `.cabal` file may not match the one in
+the index, either because a Hackage trustee uploaded a revision, or because of
+an malicious attempt to fool the solver in installing different dependencies
+than intended.
+
+Therefore, unlike for unsigned packages, listing the file info for the `.cabal`
+file in the index is useful for signed packages: although the `.cabal` files are
+listed by value in the index tarball, the index is only signed by the snapshot
+key. We may want to additionally check that the `.cabal` are properly author
+signed too. We record this in a different file `<index>/Foo/1.0/revisions.json`,
+which can be signed by either the package authors or the Hackage trustees.
+
+``` javascript
+{ "signed" : {
+     "_type"   : "Targets"
+   , "version" : VERSION
+   , "expires" : never
+   , "targets" : { "<index>/Foo/1.0/Foo.cabal" : FILEINFO }
+   }
+, "signatures" : /* signatures from package authors or Hackage trustees */
+}
+```
+
+##### Delegation
+
+Delegation for signed packages is a bit more complicated. We extend the
+top-level targets file to
+
+``` javascript
+{ "signed" : {
+      "_type"       : Targets
+    , "version"     : VERSION
+    , "expires"     : EXPIRES
+    , "targets"     : []
+    , "delegations" : {
+          "keys"  : /* Hackage trustee keys */
+        , "roles" : [
+               // Delegation for unsigned packages
+               { "name"      : "<index>/$NAME/$VERSION/package.json"
+               , "keyids"    : []
+               , "threshold" : 0
+               , "path"      : "<repo>/package/$NAME-$VERSION.tar.gz"
+               }
+             // Delegation for package Bar
+             , { "name"      : "<index>/Bar/authors.json"
+               , "keyids"    : /* top-level target keys */
+               , "threshold" : THRESHOLD
+               , "path"      : "<repo>/package/Bar-$VERSION.tar.gz"
+               }
+             , { "name"      : "<index>/Bar/authors.json"
+               , "keyids"    : /* top-level target keys */
+               , "threshold" : THRESHOLD
+               , "path"      : "<index>/Bar/$VERSION/Bar.cabal"
+               }
+             , { "name"      : "<index>/Bar/$VERSION/revisions.json"
+               , "keyids"    : /* Hackage trustee key IDs  */
+               , "threshold" : THRESHOLD
+               , "path"      : "<index>/Bar/$VERSION/Bar.cabal"
+               }
+             // .. delegation for other signed packages ..
+             ]
+        }
+    }
+, "signatures" : /* target keys */
+}
+```
+
+Since this lists all signed packages, we must list an expiry date here so that
+attackers cannot mount freeze attacks (although this is somewhat less of an
+issue here as freezing this list would make an entire new package, rather than
+a new package version, invisible).
+
+This &ldquo;middle level&rdquo; targets file `<index>/Bar/authors.json`
+introduces the package author/maintainer keys and contains further delegation
+information:
+
+``` javascript
+{ "signed" : {
+      "_type"       : "Targets"
+    , "version"     : VERSION
+    , "expires"     : never
+    , "targets"     : {}
+    , "delegations" : {
+          "keys"  : /* package maintainer keys */
+        , "roles" : [
+              { "name"      : "<index>/Bar/$VERSION/package.json"
+              , "keyids"    : /* package maintainer key IDs */
+              , "threshold" : THRESHOLD
+              , "path"      : "<repo>/Bar/$VERSION/Bar-$VERSION.tar.gz"
+              }
+            , { "name"      : "<index>/Bar/$VERSION/revisions.json"
+              , "keyids"    : /* package maintainer key IDs */
+              , "threshold" : THRESHOLD
+              , "path"      : "<index>/Bar/$VERSION/Bar.cabal"
+              }
+            ]
+    }
+, "signatures" : /* signatures from top-level target keys */
+}
+```
+
+Some notes:
+
+1. When a new signed package is introduced, the Hackage admins need to create
+   and sign a new `targets.json` that lists the package author keys and
+   appropriate delegation information, as well as add corresponding entries to
+   the top-level delegation file. However, once this is done, the Hackage admins
+   do not need to be involved when package authors wish to upload new versions.
+
+2. When package authors upload a new version, they need to sign only a single
+   file that contains the information about that particular version.
+
+3. Both package authors (through the package-specific &ldquo;middle level&rdquo;
+   delegation information) and Hackage trustees (through the top-level
+   delegation information) can sign `.cabal` file revisions, but only authors
+   can sign the packages themselves.
+
+4. Hackage trustees are listed only in the top-level delegation information, so
+   when the set of trustees changes we only need to modify one file (as opposed
+   to each middle-level package delegation information).
+
+5. For signed packages that do not want to allow Hackage trustees to sign
+   `.cabal` file revisions we can just omit the corresponding entry from the
+   top-level delegations file.
+
+6. There are two kinds of rule overlaps in these delegation rules:
+   `<repo>/package/Bar-1.0.tar.gz` will match against the rule for unsigned
+   packages (`<repo>/package/$NAME-$VERSION.tar.gz`) and against the rule for
+   signed packages (`<repo>/package/Bar-$VERSION.tar.gz`). It is important
+   here that the signed rule take precedence, because author signed packages
+   _must_ be author signed. The priority scheme can be simple: more specific
+   rules should take precedence (the TUF specification leaves the priority
+   scheme used open).
+
+   The second kind of overlap occurs between the rule
+   for the `.cabal` file in the the top-level `targets.json` and the
+   corresponding rule in `authors.json`; in this case both rules match against
+   precisely the same path (`<index>/Bar/$VERSION/Bar.cabal`), and indeed in
+   this case there is no priority: as long as either rule matches
+   (that is, the file is either signed by a package author or by a Hackage
+   trustee) we're okay.
+
+##### Transition packages from `unsigned` to `signed`
+
+When a package that previously did not opt-in to author signing now wants
+author-signing, we just need to add the appropriate entries to the top-level
+delegation file and set up the appropriate middle-level delegation information.
+
+##### Security
+
+When the snapshot key is compromised, attackers still do not have access to
+package author keys, which are strictly kept offline. However, they can still
+mount freeze attacks on packages versions, because there is no file (which is
+signed with offline key) listing which versions are available.
+
+We could increase security here by changing the middle-level `authors.json` to
+remove the wildcard rule, list all versions explicitly, and change the top-level
+delegation information to say that the middle-level file should be signed by
+the package authors instead.
+
+Note that we do not use a wildcard for signed packages in the top-level
+`targets.json` for a similar reason: by listing all packages that we expect to
+be signed explicitly, we have a list of signed packages which is signed by
+offline keys (in this case, the target keys).
+
+### Snapshot
+
+#### Interaction with the index tarball
+
+According to the official specification we should have a file `snapshot.json`,
+signed with the snapshot key, which lists the hashes of _all_ metadata files in
+the repo. In Hackage however we have the index tarball, which _contains_ most of
+the metadata files in the repo (that is, it contains all the `.cabal` files, but
+it also contains all the various `.json` files). The only thing that is missing
+from the index tarball, compared to the `snapshot.json` file from the TUF spec,
+is the version, expiry time, and signatures. Therefore our `snapshot.json` looks
+like
+
+``` javascript
+{ "signed" : {
+      "_type"   : "Snapshot"
+    , "version" : VERSION
+    , "expires" : EXPIRES
+    , "meta"    : {
+           "root.json"    : FILEINFO
+         , "mirrors.json" : FILEINFO
+         , "index.tar"    : FILEINFO
+         , "index.tar.gz" : FILEINFO
+        }
+    }
+, "signatures" : /* signatures from snapshot key */
+}
+```
+
+Then the combination of `snapshot.json` together with the index tarball is
+a strict superset of the information in TUF's `snapshot.json` (instead of
+containing the hashes of the metadata in the repo, it contains the actual
+metadata themselves).
+
+We list the file info of the root and mirrors metadata explicitly, rather than
+recording it in the index tarball, so that we can check them for updates during
+the update process  (section 5.1, &ldquo;The Client Application&rdquo;, of the
+TUF spec) without downloading the entire index tarball.
+
+### Out-of-tarball targets
+
+All versions of all packages are listed, along with their full `.cabal` file, in
+the Hackage index. This is useful because the constraint solver needs most of
+this information anyway. However, when we add additional kinds of targets we may
+not wish to add these to the index: people who are not interested in these new
+targets should not, or only minimally, be affected. In particular, new releases
+of these new kinds of targets should not result in a linear increase in what
+clients who are not interested in these targets need to download whenever they
+call `cabal install`.
+
+To support these out-of-tarball targets we can use the regular TUF setup. Since
+the index does not serve as an exhaustive list of which targets (and which
+target versions) are available, it becomes important to have target metadata
+that list all targets exhaustively, to avoid freeze attacks. The file
+information (hash and filesize) of all these target metadata files must, by the
+TUF spec, be listed in the top-level snapshot; we should thus avoid introducing
+too many of them (in particular, we should avoid requiring a new metadata file
+for each new version of a particular kind of target).
+
+It is important to store OOT targets under a different prefix than `/package` to
+avoid name clashes.
+
+#### Collections
+
+[Package collections][CabalHell1] are a new Hackage feature that's [currently in
+development][ZuriHac]. We want package collections to be signed, just like
+anything else.
+
+Like packages, collections are versioned and immutable, so we have
+
+```
+collection/StackageNightly-2015.06.02.collection
+collection/StackageNightly-2015.06.03.collection
+collection/StackageNightly-...
+collection/DebianJessie-...
+collection/...
+```
+
+As for packages, collections should be able to opt-in for author signing (once
+we support author signing), but we should also support not-author-signed
+(&ldquo;unsigned&rdquo;) collections. Moreover, it should be possible for people
+to create new unsigned collections without the involvement of the Hackage
+admins. This rules out listing all collections explicitly in the top-level
+`targets.json` (which is signed with offline target keys).
+
+Below we sketch a possible design (we may want to tweak this further).
+
+##### Unsigned collections
+
+For unsigned collections we add a single delegation rule to the top-level
+`targets.json`:
+
+``` javascript
+{ "name"      : "<repo>/collection/collections.json"
+, "keyids"    : /* snapshot key */
+, "threshold" : 1
+, "path"      : "<repo>/collection/$NAME-$VERSION.collection"
+}
+```
+
+The middle-level `collections.json`, signed with the snapshot role, lists
+delegation rules for all available collections:
+
+``` javascript
+[ { "name"      : "<repo>/collection/StackageNightly.json"
+  , "keyids"    : /* snapshot key */
+  , "threshold" : 1
+  , "path"      : "<repo>/collection/StackageNightly-$VERSION.collection"
+  }
+, { "name"      : "<repo>/collection/DebianJessie.json"
+  , "keyids"    : /* snapshot key */
+  , "threshold" : 1
+  , "path"      : "<repo>/collection/DebianJessie-$VERSION.collection"
+  }
+, ...
+]
+```
+
+where `StackageNightly` and `DebianJessie` are two package collection (the
+[Stackage Nightly][StackageNightly] collection and the set of Haskell package
+distributed with the [Debian Jessie][DebianJessie] Linux distribution).
+
+The final per-collection targets metadata finally lists all versions:
+
+``` javascript
+{ "signed" : {
+     "_type"   : "Targets"
+   , "version" : VERSION
+   , "expires" : /* expiry */
+   , "targets" : {
+         "<repo>/collection/StackageNightly-2015.06.02.collection" : FILEINFO
+       , "<repo>/collection/StackageNightly-2015.06.03.collection" : FILEINFO
+       , ...
+       }
+   }
+, "signatures" : /* signed with snapshot role */
+}
+```
+
+Since we cannot rely on the index to have the list of all version we must list
+all versions explicitly here rather than using wildcards. Note that this means
+that the snapshot will get a new entry for each new collection introduced, but
+not for each new _version_ of each collection.
+
+##### Author-signed collections
+
+For author-signed collections we only need to make a single change. Suppose that
+the `DebianJessie` collection is signed. Then we move the rule for
+`DebianJessie` from `collection/collections.json` and instead list it in the
+top-level `targets.json` (as for packages, introducing a signed collection
+necessarily requires the involvement of the Hackage admins):
+
+``` javascript
+{ "name"      : "<repo>/collection/DebianJessie.json"
+, "keyids"    : /* DebianJessie maintainer keys */
+, "threshold" : /* threshold */
+, "path"      : "<repo>/collection/DebianJessie-$VERSION.collection"
+}
+```
+
+No other changes are required (apart from of course that
+`<repo>/collection/DebianJessie.json` will now be signed with the
+`DebianJessie` maintainer keys rather than the snapshot key). As for packages,
+this requires a priority scheme for delegation rules.
+
+(One difference between this scheme and the scheme we use for packages is that
+this means that the top-level `targets.json` file lists all keys for all
+collection authors. If we want to avoid that we need a further level of
+indirection.)
+
+Note that in a sense author-signed collections are snapshots of the server. As
+such, it would be good if these collections listed the file info (hashes and
+filesizes) of the packages the collection.
+
+## Project phases and shortcuts
+
+Phase 1 of the project will implement the basic TUF framework, but leave out
+author signing; support for author signed packages (and other targets) will
+added in phase 2.
+
+### <a name="phase1-shortcuts">Shortcuts taken in phase 1 (aka TODOs for phase 2)</a>
+
+This list is currenty not exhaustive.
+
+#### Core library
+
+* Although the infrastructure is in place for [target metadata][Targetshs],
+  including typed data types representing [pattern matches and
+  replacements][Patternshs], we have not yet actually implementing target
+  delegation proper. We don't need to: we only support one kind of target
+  (not-author-signed packages), and we know statically where the target
+  information for packages can be found (in `/package/version/targets.json`).
+  This is currently hardcoded.  
+
+  Once we have author signing we need a proper implementation of delegation,
+  including a priority scheme between rules. This will also involve lazily
+  downloading additional target metadata.
+
+* Out-of-tarballs targets are not yet implemented. The main difficulty here is
+  that they require a proper implementation of delegation; once that is done
+  (required anyway for author signing) support for OOT targets should be
+  straightforward.
+
+#### Integration in `cabal-install`
+
+* The cabal integration uses the `hackage-security` library to check for updates
+  (that is, update the local copy of the index) and to download packages
+  (verifying the downloaded file against the file info that was recorded in
+  the package metadata, which itself is stored in the index). However, it does
+  not use the library to get a list of available packages, nor to access
+  `.cabal` files.
+
+  Once we have author signing however we may want to do additional checks:
+
+  * We should look at the top-level `targets.json` file (in addition to the
+    index) to figure out which packages are available. (The top-level targets
+    file, signed by offline keys, will enumerate all author-signed packages.)
+
+  * If we do allow package authors to sign list of package versions (as detailed
+    above) we should use these &ldquo;middle level&rdquo; target files to figure
+    out which versions are available for these packages.
+
+  * We might want to verify the `.cabal` files to make sure that they match the
+    file info listed in the now author-signed metadata.
+
+  Therefore `cabal-install` should be modified to go through the
+  `hackage-security`  library get the list of available packages, package
+  versions, and to access the actual `.cabal` files.
+
+## Open questions / TODOs
+
+* The set of maintainers of a package can change over time, and can even change
+  so much that the old maintainers of a package are no longer maintainters.
+  But we would still like to be able to install and verify old packages. How
+  do we deal with this?
+
+## <a name="paths">Footnotes</a>
+
+### Footnote: Paths
+
+The situation with paths in cabal/hackage is a bit of a mess. In this footnote
+we describe the situation before the work on the Hackage Security library.
+
+#### The index tarball
+
+The index tarball contains paths of the form
+
+```
+<package-name>/<package-version>/<package-name>.cabal
+```
+
+For example:
+
+```
+mtl/1.0/mtl.cabal
+```
+
+as well as a single top-level `preferred-versions` file.
+
+#### Package resources offered by Hackage
+
+Hackage offers a number of resources for package tarballs: one
+&ldquo;official&rdquo; one and a few redirects.
+
+1.  The official location of package tarballs on a Hackage server is
+
+    ```
+    /package/<package-id>/<package-id>.tar.gz
+    ```
+
+    for example
+
+    ```
+    /package/mtl-2.2.1/mtl-2.2.1.tar.gz
+    ```
+
+    (This was the official location from [very early on][63a8c728]).
+
+2.  It [provides a redirect][3cfe4de] for
+
+    ```
+    /package/<package-id>.tar.gz
+    ```
+
+    for example
+
+    ```
+    /package/mtl-2.2.1.tar.gz
+    ```
+
+    (that is, a request for `/package/mtl-2.2.1.tar.gz` will get a 301 Moved
+    Permanently response, and is redirected to
+    `/package/mtl-2.2.1/mtl-2.2.1.tar.gz`).
+
+3.  It provides a redirect for Hackage-1 style URLs of the form
+
+    ```
+    /packages/archive/<package-name>/<package-version>/<package-id>.tar.gz
+    ```
+
+    for example
+
+    ```
+    /packages/archive/mtl/2.2.1/mtl-2.2.1.tar.gz
+    ```
+
+#### Locations used by cabal-install to find packages
+
+There are two kinds of repositories supported by `cabal-install`: local and
+remote.
+
+1.  For a local repository `cabal-install` looks for packages at
+
+    ```
+    <local-dir>/<package-name>/<package-version>/<package-id>.tar.gz
+    ```
+
+2.  For remote repositories however `cabal-install` looks for packages in one of
+    two locations.
+
+    a.  If the remote repository (`<repo>`) is
+        `http://hackage.haskell.org/packages/archive` (this value is hardcoded)
+        then it looks for the package at
+
+        <repo>/<package-name>/<package-version>/<package-id>.tar.gz
+
+    b.  For any other repository it looks for the package at
+
+        <repo>/package/<package-id>.tar.gz
+
+Some notes:
+
+1.  Files downloaded from a remote repository are cached locally as
+
+    ```
+    <cache>/<package-name>/<package-version>/<package-id>.tar.gz
+    ```
+
+    I.e., the layout of the local cache matches the layout of a local
+    repository (and matches the structure of the index tarball too).
+
+2.  Somewhat bizarrely, when `cabal-install` creates a new initial `config`
+    file it uses `http://hackage.haskell.org/packages/archive` as the repo base
+    URI (even in newer versions of `cabal-install`; this was [changed only very
+    recently][bfeb01f]).
+
+3.  However, notice that _even when we give `cabal` a &ldquo;new-style&rdquo;
+    URI_ the address used by `cabal` _still_ causes a redirect (from
+    `/package/<package-id>.tar.gz` to
+    `/package/<package-id>/<package-id>.tar.gz`).
+
+The most important observation however is the following: **It is not possible to
+serve a local repository as a remote repository** (by poining a webserver at a
+local repository) because the layouts are completely different. (Note that the
+location of packages on Hackage-1 _did_ match the layout of local repositories,
+but that doesn't help because the _only_ repository that `cabal-install` will
+regard as a Hackage-1 repository is one hosted on `hackage.haskell.org`).
+
+[TUF]: http://theupdateframework.com/
+[CabalHell1]: http://www.well-typed.com/blog/2014/09/how-we-might-abolish-cabal-hell-part-1/
+[ZuriHac]: http://www.well-typed.com/blog/2015/06/cabal-hackage-hacking-at-zurihac/
+[StackageNightly]: http://www.stackage.org/
+[DebianJessie]: https://wiki.debian.org/DebianJessie
+[Targetshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Targets.hs
+[Patternshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Patterns.hs
+[bfeb01f]: https://github.com/haskell/cabal/commit/bfeb01f
+[63a8c728]: https://github.com/haskell/hackage-server/commit/63a8c728
+[3cfe4de]: https://github.com/haskell/hackage-server/commit/3cfe4de
diff --git a/hackage-security/example-client/LICENSE b/hackage-security/example-client/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/example-client/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/example-client/example-client.cabal b/hackage-security/example-client/example-client.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/example-client/example-client.cabal
@@ -0,0 +1,48 @@
+name:                example-client
+version:             0.1.0.0
+synopsis:            Example client using the Hackage security library
+-- description:
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+executable example-client
+  main-is:             Main.hs
+  other-modules: Prelude ExampleClient.Options
+
+  build-depends:       base                 >= 4.4,
+                       bytestring           >= 0.9,
+                       Cabal                >= 1.12,
+                       directory            >= 1.1,
+                       filepath             >= 1.2,
+                       optparse-applicative >= 0.11,
+                       time                 >= 1.2,
+                       hackage-security     >= 0.5,
+                       hackage-security-HTTP,
+                       hackage-security-curl,
+                       hackage-security-http-client
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  DeriveDataTypeable
+                       FlexibleContexts
+                       RankNTypes
+                       RecordWildCards
+                       ScopedTypeVariables
+  other-extensions:    CPP
+  ghc-options:         -Wall
+
+  -- see comments in hackage-security.cabal
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
diff --git a/hackage-security/example-client/src/ExampleClient/Options.hs b/hackage-security/example-client/src/ExampleClient/Options.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/example-client/src/ExampleClient/Options.hs
@@ -0,0 +1,171 @@
+module ExampleClient.Options (
+    GlobalOpts(..)
+  , Command(..)
+  , NewOnly
+  , getOptions
+    -- * Re-exports
+  , URI
+  ) where
+
+import Data.List (isPrefixOf)
+import Network.URI (URI, parseURI)
+import Options.Applicative
+import System.IO.Unsafe (unsafePerformIO)
+
+import Distribution.Package
+import Distribution.Text
+
+import Hackage.Security.Client
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+data GlobalOpts = GlobalOpts {
+    -- | Path to the repository (local or remote)
+    globalRepo :: Either (Path Absolute) URI
+
+    -- | Directory to store the client cache
+  , globalCache :: Path Absolute
+
+    -- | HTTP client to use
+  , globalHttpClient :: String
+
+    -- | Trusted root key (used for bootstrapping)
+  , globalRootKeys :: [KeyId]
+
+    -- | Should we check expiry times?
+  , globalCheckExpiry :: Bool
+
+    -- | Command to execute
+  , globalCommand :: Command
+  }
+
+data Command =
+    -- | Get initial root info
+    Bootstrap KeyThreshold
+
+    -- | Check for updates on the server
+  | Check
+
+    -- | Download a specific package
+  | Get PackageIdentifier
+
+    -- | Enumerate the entries in the index
+  | EnumIndex NewOnly
+
+    -- | Extract a cabal file from the index
+  | GetCabal PackageIdentifier
+
+    -- | Read a package hash from the index
+  | GetHash PackageIdentifier
+  deriving Show
+
+type NewOnly = Bool
+
+{-------------------------------------------------------------------------------
+  Parsers
+-------------------------------------------------------------------------------}
+
+getOptions :: IO GlobalOpts
+getOptions = execParser opts
+  where
+    opts = info (helper <*> parseGlobalOptions) $ mconcat [
+        fullDesc
+      , header "Example Hackage client"
+      ]
+
+parseBootstrap :: Parser Command
+parseBootstrap = Bootstrap <$> argument readKeyThreshold (metavar "THRESHOLD")
+
+parseCheck :: Parser Command
+parseCheck = pure Check
+
+parseGet :: Parser Command
+parseGet = Get <$> argument readPackageIdentifier (metavar "PKG")
+
+parseEnumIndex :: Parser Command
+parseEnumIndex = EnumIndex
+  <$> (switch $ mconcat [
+      long "new-only"
+    , help "Only enumerate entries since last call to enum-index"
+    ])
+
+parseGetCabal :: Parser Command
+parseGetCabal = GetCabal <$> argument readPackageIdentifier (metavar "PKG")
+
+parseGetHash :: Parser Command
+parseGetHash = GetHash <$> argument readPackageIdentifier (metavar "PKG")
+
+parseGlobalOptions :: Parser GlobalOpts
+parseGlobalOptions = GlobalOpts
+  <$> (option (str >>= readRepo) $ mconcat [
+          long "repo"
+        , metavar "URL"
+        , help "Location of the repository"
+        ])
+  <*> (option (str >>= readAbsolutePath) $ mconcat [
+          long "cache"
+        , metavar "PATH"
+        , help "Path to client cache"
+        ])
+  <*> (strOption $ mconcat [
+         long "http-client"
+       , metavar "CLIENT"
+       , value "HTTP"
+       , showDefault
+       , help "HTTP client to use (currently supported: HTTP, http-conduit, curl)"
+       ])
+  <*> (many . option readKeyId $ mconcat [
+         long "root-key"
+       , metavar "KEYID"
+       , help "Root key (used for bootstrapping; can be used multiple times)"
+       ])
+  <*> (switch $ mconcat [
+         long "ignore-expiry"
+       , help "Don't check expiry dates (should only be used in exceptional circumstances)"
+       ])
+  <*> (subparser $ mconcat [
+          command "bootstrap" $ info (helper <*> parseBootstrap) $
+            progDesc "Get the initial root information. If using a key threshold larger than 0, you will need to use the --root-key option to specify one or more trusted root keys."
+        , command "check" $ info (helper <*> parseCheck) $
+            progDesc "Check for updates"
+        , command "get" $ info (helper <*> parseGet) $
+            progDesc "Download a package"
+        , command "enum-index" $ info (helper <*> parseEnumIndex) $
+            progDesc "Enumerate the index"
+        , command "cabal" $ info (helper <*> parseGetCabal) $
+            progDesc "Extract a cabal file from the index"
+        , command "hash" $ info (helper <*> parseGetHash) $
+            progDesc "Read a package hash from the index"
+        ])
+
+readKeyId :: ReadM KeyId
+readKeyId = KeyId <$> str
+
+readKeyThreshold :: ReadM KeyThreshold
+readKeyThreshold = KeyThreshold <$> auto
+
+readPackageIdentifier :: ReadM PackageIdentifier
+readPackageIdentifier = do
+    raw <- str
+    case simpleParse raw of
+      Just pkgId -> return pkgId
+      Nothing    -> fail $ "Invalid package ID " ++ show raw
+
+-- Sadly, cannot do I/O actions inside ReadM
+readAbsolutePath :: String -> ReadM (Path Absolute)
+readAbsolutePath = return . unsafePerformIO . makeAbsolute . fromFilePath
+
+readRepo :: String -> ReadM (Either (Path Absolute) URI)
+readRepo filePath =
+    if "http://" `isPrefixOf` filePath
+      then Right <$> readURI filePath
+      else Left <$> readAbsolutePath filePath
+
+readURI :: String -> ReadM URI
+readURI uriStr =
+   case parseURI uriStr of
+     Nothing  -> fail $ "Invalid URI " ++ show uriStr
+     Just uri -> return uri
diff --git a/hackage-security/example-client/src/Main.hs b/hackage-security/example-client/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/example-client/src/Main.hs
@@ -0,0 +1,182 @@
+module Main where
+
+-- stdlib
+import Control.Exception
+import Control.Monad
+import Data.Time
+import qualified Data.ByteString.Lazy as BS.L
+
+-- Cabal
+import Distribution.Package
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+import Hackage.Security.Client.Repository.HttpLib
+import qualified Hackage.Security.Client.Repository.Cache       as Cache
+import qualified Hackage.Security.Client.Repository.Local       as Local
+import qualified Hackage.Security.Client.Repository.Remote      as Remote
+import qualified Hackage.Security.Client.Repository.HttpLib.HTTP as HttpLib.HTTP
+import qualified Hackage.Security.Client.Repository.HttpLib.Curl as HttpLib.Curl
+import qualified Hackage.Security.Client.Repository.HttpLib.HttpClient as HttpLib.HttpClient
+
+-- example-client
+import ExampleClient.Options
+
+main :: IO ()
+main = do
+    opts@GlobalOpts{..} <- getOptions
+    case globalCommand of
+      Bootstrap threshold -> cmdBootstrap opts threshold
+      Check               -> cmdCheck     opts
+      Get       pkgId     -> cmdGet       opts pkgId
+      EnumIndex newOnly   -> cmdEnumIndex opts newOnly
+      GetCabal  pkgId     -> cmdGetCabal  opts pkgId
+      GetHash   pkgId     -> cmdGetHash   opts pkgId
+
+{-------------------------------------------------------------------------------
+  The commands are just thin wrappers around the hackage-security Client API
+-------------------------------------------------------------------------------}
+
+cmdBootstrap :: GlobalOpts -> KeyThreshold -> IO ()
+cmdBootstrap opts threshold =
+    withRepo opts $ \rep -> uncheckClientErrors $ do
+      bootstrap rep (globalRootKeys opts) threshold
+      putStrLn "OK"
+
+cmdCheck :: GlobalOpts -> IO ()
+cmdCheck opts =
+    withRepo opts $ \rep -> uncheckClientErrors $ do
+      mNow <- if globalCheckExpiry opts
+                then Just `fmap` getCurrentTime
+                else return Nothing
+      print =<< checkForUpdates rep mNow
+
+cmdGet :: GlobalOpts -> PackageIdentifier -> IO ()
+cmdGet opts pkgId = do
+    cwd <- getCurrentDirectory
+    let localFile = cwd </> fragment tarGzName
+    withRepo opts $ \rep -> uncheckClientErrors $
+      downloadPackage rep pkgId localFile
+  where
+    tarGzName :: String
+    tarGzName = takeFileName $ repoLayoutPkgTarGz hackageRepoLayout pkgId
+
+cmdEnumIndex :: GlobalOpts -> NewOnly -> IO ()
+cmdEnumIndex opts False =
+    withRepo opts $ \rep -> uncheckClientErrors $ do
+      dir <- getDirectory rep
+      forM_ (directoryEntries dir) $ putStrLn . aux
+  where
+    aux :: (Pretty fp, Pretty file) => (DirectoryEntry, fp, Maybe file) -> String
+    aux (_, _,  Just file) = pretty file
+    aux (_, fp, Nothing  ) = "unrecognized: " ++ pretty fp
+cmdEnumIndex opts True = do
+    withRepo opts $ \rep -> uncheckClientErrors $ do
+      withIndex rep $ \IndexCallbacks{..} -> do
+        let go n = do (Some IndexEntry{..}, mNext) <- indexLookupEntry n
+                      putStrLn $ pretty indexEntryPath
+                      case mNext of
+                        Nothing   -> return ()
+                        Just next -> go next
+        startingPoint <- getStartingPoint (directoryFirst indexDirectory)
+        if (startingPoint == directoryNext indexDirectory)
+          then putStrLn "No new entries"
+          else do
+            go startingPoint
+            saveStartingPoint $ directoryNext indexDirectory
+  where
+    getStartingPoint :: DirectoryEntry -> IO DirectoryEntry
+    getStartingPoint def =
+      catch (read <$> readFile marker)
+            (\(SomeException _) -> return def)
+
+    saveStartingPoint :: DirectoryEntry -> IO ()
+    saveStartingPoint = writeFile marker . show
+
+    marker :: FilePath
+    marker = toFilePath (globalCache opts </> fragment "enum.marker")
+
+cmdGetCabal :: GlobalOpts -> PackageIdentifier -> IO ()
+cmdGetCabal opts pkgId =
+    withRepo opts $ \rep -> uncheckClientErrors $
+      withIndex rep $ \IndexCallbacks{..} ->
+        BS.L.putStr . trusted =<< indexLookupCabal pkgId
+
+cmdGetHash :: GlobalOpts -> PackageIdentifier -> IO ()
+cmdGetHash opts pkgId =
+    withRepo opts $ \rep -> uncheckClientErrors $
+      withIndex rep $ \IndexCallbacks{..} ->
+        print =<< indexLookupHash pkgId
+
+{-------------------------------------------------------------------------------
+  Common functionality
+-------------------------------------------------------------------------------}
+
+withRepo :: GlobalOpts
+         -> (forall down. DownloadedFile down => Repository down -> IO a)
+         -> IO a
+withRepo GlobalOpts{..} = \callback ->
+    case globalRepo of
+      Left  local  -> withLocalRepo  local  callback
+      Right remote -> withRemoteRepo remote callback
+  where
+    withLocalRepo :: Path Absolute -> (Repository Local.LocalFile -> IO a) -> IO a
+    withLocalRepo repo =
+        Local.withRepository repo
+                             cache
+                             hackageRepoLayout
+                             hackageIndexLayout
+                             logTUF
+
+    withRemoteRepo :: URI -> (Repository Remote.RemoteTemp -> IO a) -> IO a
+    withRemoteRepo baseURI callback = withClient $ \httpClient ->
+        Remote.withRepository httpClient
+                              [baseURI]
+                              repoOpts
+                              cache
+                              hackageRepoLayout
+                              hackageIndexLayout
+                              logTUF
+                              callback
+
+    repoOpts :: Remote.RepoOpts
+    repoOpts = Remote.defaultRepoOpts
+
+    withClient :: (HttpLib -> IO a) -> IO a
+    withClient act =
+        case globalHttpClient of
+          "HTTP" ->
+            HttpLib.HTTP.withClient $ \browser httpLib -> do
+              HttpLib.HTTP.setProxy      browser proxyConfig
+              HttpLib.HTTP.setOutHandler browser logHTTP
+              HttpLib.HTTP.setErrHandler browser logHTTP
+              act httpLib
+          "curl" ->
+            HttpLib.Curl.withClient $ \httpLib ->
+              act httpLib
+          "http-client" ->
+            HttpLib.HttpClient.withClient proxyConfig $ \_manager httpLib ->
+              act httpLib
+          otherClient ->
+            error $ "unsupported HTTP client " ++ show otherClient
+
+    -- use automatic proxy configuration
+    proxyConfig :: forall a. ProxyConfig a
+    proxyConfig = ProxyConfigAuto
+
+    -- used for log messages from the Hackage.Security code
+    logTUF :: LogMessage -> IO ()
+    logTUF msg = putStrLn $ "# " ++ pretty msg
+
+    -- used for log messages from the HTTP clients
+    logHTTP :: String -> IO ()
+    logHTTP = putStrLn
+
+    cache :: Cache.Cache
+    cache = Cache.Cache {
+        cacheRoot   = globalCache
+      , cacheLayout = cabalCacheLayout
+      }
diff --git a/hackage-security/example-client/src/Prelude.hs b/hackage-security/example-client/src/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/example-client/src/Prelude.hs
@@ -0,0 +1,27 @@
+-- | Smooth over differences between various ghc versions by making older
+-- preludes look like 4.8.0
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE CPP #-}
+module Prelude (
+    module P
+#if !MIN_VERSION_base(4,8,0)
+  , Applicative(..)
+  , Monoid(..)
+  , (<$>)
+  , (<$)
+  , traverse
+#endif
+  ) where
+
+#if MIN_VERSION_base(4,8,0)
+import "base" Prelude as P
+#else
+#if MIN_VERSION_base(4,6,0)
+import "base" Prelude as P
+#else
+import "base" Prelude as P hiding (catch)
+#endif
+import Control.Applicative
+import Data.Monoid
+import Data.Traversable (traverse)
+#endif
diff --git a/hackage-security/hackage-repo-tool/ChangeLog.md b/hackage-security/hackage-repo-tool/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/ChangeLog.md
@@ -0,0 +1,13 @@
+0.1.1
+-----
+* Update for hackage-security-0.5
+
+0.1.0.1
+-------
+* Add missing module to other-modules (#100)
+* Allow for hackage-security-0.3
+* Include ChangeLog.md in sdist (#98)
+
+0.1.0.0
+-------
+* Initial (beta) release
diff --git a/hackage-security/hackage-repo-tool/LICENSE b/hackage-security/hackage-repo-tool/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-repo-tool/Setup.hs b/hackage-security/hackage-repo-tool/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-repo-tool/hackage-repo-tool.cabal b/hackage-security/hackage-repo-tool/hackage-repo-tool.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/hackage-repo-tool.cabal
@@ -0,0 +1,69 @@
+name:                hackage-repo-tool
+version:             0.1.1
+synopsis:            Utility to manage secure file-based package repositories
+description:         This utility can be used to manage secure file-based
+                     repositories (creating TUF metadata as well as a Hackage
+                     index tarball). Currently it also provides various
+                     lower level utilities for creating and signing TUF files.
+                     .
+                     This is part of the Hackage Security infrastructure.
+homepage:            http://github.com/well-typed/hackage-security/
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+homepage:            https://github.com/well-typed/hackage-security
+bug-reports:         https://github.com/well-typed/hackage-security/issues
+build-type:          Simple
+cabal-version:       >=1.10
+
+extra-source-files:
+  ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/well-typed/hackage-security.git
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+executable hackage-repo-tool
+  main-is:             Main.hs
+  other-modules:       Hackage.Security.RepoTool.Options
+                       Hackage.Security.RepoTool.Layout
+                       Hackage.Security.RepoTool.Layout.Keys
+                       Hackage.Security.RepoTool.Paths
+                       Hackage.Security.RepoTool.Util.IO
+                       Prelude
+  build-depends:       base                 >= 4.4  && < 5,
+                       Cabal                >= 1.12 && < 1.25,
+                       bytestring           >= 0.9  && < 0.11,
+                       directory            >= 1.1  && < 1.3,
+                       filepath             >= 1.2  && < 1.5,
+                       optparse-applicative >= 0.11 && < 0.13,
+                       tar                  >= 0.4  && < 0.6,
+                       time                 >= 1.2  && < 1.6,
+                       unix                 >= 2.5  && < 2.8,
+                       zlib                 >= 0.5  && < 0.7,
+                       hackage-security     >= 0.5  && < 0.6
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  DeriveDataTypeable
+                       FlexibleContexts
+                       FlexibleInstances
+                       NoMonomorphismRestriction
+                       ScopedTypeVariables
+                       StandaloneDeriving
+                       RecordWildCards
+  other-extensions:    TemplateHaskell
+  ghc-options:         -Wall
+
+  -- see comments in hackage-security.cabal
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
diff --git a/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout.hs b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout.hs
@@ -0,0 +1,89 @@
+-- | Layout of the local repository as managed by this tool
+--
+-- The local repository follows a RepoLayout exactly, but adds some additional
+-- files. In addition, we also manage a directory of keys (although this will
+-- eventually need to be replaced with a proper key management system).
+module Hackage.Security.RepoTool.Layout (
+    -- * Additional paths in the repository
+    repoLayoutIndexDir
+    -- * Layout-parametrized version of TargetPath
+  , TargetPath'(..)
+  , prettyTargetPath'
+  , applyTargetPath'
+    -- * Utility
+  , anchorIndexPath
+  , anchorRepoPath
+  , anchorKeyPath
+  , anchorTargetPath'
+  ) where
+
+import Distribution.Package
+
+import Hackage.Security.Client
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+
+import Hackage.Security.RepoTool.Layout.Keys
+import Hackage.Security.RepoTool.Options
+import Hackage.Security.RepoTool.Paths
+
+{-------------------------------------------------------------------------------
+  Additional paths specifically to the kind of repository this tool manages
+-------------------------------------------------------------------------------}
+
+-- | Directory containing the unpacked index
+--
+-- Since the layout of the tarball may not match the layout of the index,
+-- we create a local directory with the unpacked contents of the index.
+repoLayoutIndexDir :: RepoLayout -> RepoPath
+repoLayoutIndexDir _ = rootPath $ fragment "index"
+
+{-------------------------------------------------------------------------------
+  TargetPath'
+-------------------------------------------------------------------------------}
+
+-- | This is a variation on 'TargetPath' parameterized by layout
+data TargetPath' =
+    InRep    (RepoLayout  -> RepoPath)
+  | InIdx    (IndexLayout -> IndexPath)
+  | InRepPkg (RepoLayout  -> PackageIdentifier -> RepoPath)  PackageIdentifier
+  | InIdxPkg (IndexLayout -> PackageIdentifier -> IndexPath) PackageIdentifier
+
+prettyTargetPath' :: GlobalOpts -> TargetPath' -> String
+prettyTargetPath' opts = pretty . applyTargetPath' opts
+
+-- | Apply the layout
+applyTargetPath' :: GlobalOpts -> TargetPath' -> TargetPath
+applyTargetPath' GlobalOpts{..} targetPath =
+    case targetPath of
+      InRep    file       -> TargetPathRepo  $ file globalRepoLayout
+      InIdx    file       -> TargetPathIndex $ file globalIndexLayout
+      InRepPkg file pkgId -> TargetPathRepo  $ file globalRepoLayout  pkgId
+      InIdxPkg file pkgId -> TargetPathIndex $ file globalIndexLayout pkgId
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+-- | Anchor a tarball path to the repo (see 'repoLayoutIndex')
+anchorIndexPath :: GlobalOpts -> RepoLoc -> (IndexLayout -> IndexPath) -> Path Absolute
+anchorIndexPath opts@GlobalOpts{..} repoLoc file =
+        anchorRepoPath opts repoLoc repoLayoutIndexDir
+    </> unrootPath (file globalIndexLayout)
+
+anchorRepoPath :: GlobalOpts -> RepoLoc -> (RepoLayout -> RepoPath) -> Path Absolute
+anchorRepoPath GlobalOpts{..} (RepoLoc repoLoc) file =
+    anchorRepoPathLocally repoLoc $ file globalRepoLayout
+
+anchorKeyPath :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> Path Absolute
+anchorKeyPath GlobalOpts{..} (KeysLoc keysLoc) dir =
+    keysLoc </> unrootPath (dir globalKeysLayout)
+
+anchorTargetPath' :: GlobalOpts -> RepoLoc -> TargetPath' -> Path Absolute
+anchorTargetPath' opts repoLoc = go
+  where
+    go :: TargetPath' -> Path Absolute
+    go (InRep    file)       = anchorRepoPath  opts repoLoc file
+    go (InIdx    file)       = anchorIndexPath opts repoLoc file
+    go (InRepPkg file pkgId) = anchorRepoPath  opts repoLoc (`file` pkgId)
+    go (InIdxPkg file pkgId) = anchorIndexPath opts repoLoc (`file` pkgId)
diff --git a/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout/Keys.hs b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout/Keys.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout/Keys.hs
@@ -0,0 +1,44 @@
+-- | Layout of the directory containing the (private) keys
+module Hackage.Security.RepoTool.Layout.Keys (
+    -- * Layout of the keys directory
+    KeysLayout(..)
+  , defaultKeysLayout
+  , keysLayoutKey
+  ) where
+
+import Hackage.Security.Client
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Some
+
+import Hackage.Security.RepoTool.Paths
+
+-- | Layout of the keys directory
+--
+-- Specifies the directories containing the keys (relative to the keys loc),
+-- as well as the filename for individual keys.
+data KeysLayout = KeysLayout {
+      keysLayoutRoot      :: KeyPath
+    , keysLayoutTarget    :: KeyPath
+    , keysLayoutTimestamp :: KeyPath
+    , keysLayoutSnapshot  :: KeyPath
+    , keysLayoutMirrors   :: KeyPath
+    , keysLayoutKeyFile   :: Some Key -> Path Unrooted
+    }
+
+defaultKeysLayout :: KeysLayout
+defaultKeysLayout = KeysLayout {
+      keysLayoutRoot      = rp $ fragment "root"
+    , keysLayoutTarget    = rp $ fragment "target"
+    , keysLayoutTimestamp = rp $ fragment "timestamp"
+    , keysLayoutSnapshot  = rp $ fragment "snapshot"
+    , keysLayoutMirrors   = rp $ fragment "mirrors"
+    , keysLayoutKeyFile   = \key -> let kId = keyIdString (someKeyId key)
+                                    in fragment kId <.> "private"
+    }
+  where
+    rp :: Path Unrooted -> KeyPath
+    rp = rootPath
+
+keysLayoutKey :: (KeysLayout -> KeyPath) -> Some Key -> KeysLayout -> KeyPath
+keysLayoutKey dir key keysLayout@KeysLayout{..} =
+   dir keysLayout </> keysLayoutKeyFile key
diff --git a/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Options.hs b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Options.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Options.hs
@@ -0,0 +1,198 @@
+module Hackage.Security.RepoTool.Options (
+    GlobalOpts(..)
+  , Command(..)
+  , KeyLoc
+  , DeleteExistingSignatures
+  , getOptions
+  ) where
+
+import Network.URI (URI, parseURI)
+import Options.Applicative
+import System.IO.Unsafe (unsafePerformIO)
+
+import Hackage.Security.Client
+import Hackage.Security.Util.Path
+
+import Hackage.Security.RepoTool.Layout.Keys
+import Hackage.Security.RepoTool.Paths
+
+{-------------------------------------------------------------------------------
+  Types
+-------------------------------------------------------------------------------}
+
+-- | Command line options
+data GlobalOpts = GlobalOpts {
+    -- | Key directory layout
+    globalKeysLayout :: KeysLayout
+
+    -- | Local repository layout
+  , globalRepoLayout :: RepoLayout
+
+    -- | Local index layout
+  , globalIndexLayout :: IndexLayout
+
+    -- | Should we be verbose?
+  , globalVerbose :: Bool
+
+    -- | Expiry time when creating root (in years)
+  , globalExpireRoot :: Integer
+
+    -- | Expiry time when creating mirrors (in years)
+  , globalExpireMirrors :: Integer
+
+    -- | Command to execute
+  , globalCommand :: Command
+  }
+
+data Command =
+    -- | Create keys
+    CreateKeys KeysLoc
+
+    -- | Bootstrap a secure local repository
+  | Bootstrap KeysLoc RepoLoc
+
+    -- | Update a previously bootstrapped local repository
+  | Update KeysLoc RepoLoc
+
+    -- | Create root metadta
+  | CreateRoot KeysLoc (Path Absolute)
+
+    -- | Create mirrors metadata
+  | CreateMirrors KeysLoc (Path Absolute) [URI]
+
+    -- | Create a directory with symlinks in cabal-local-rep layout
+  | SymlinkCabalLocalRepo RepoLoc RepoLoc
+
+    -- | Sign an individual file
+  | Sign [KeyLoc] DeleteExistingSignatures (Path Absolute)
+
+type KeyLoc                   = Path Absolute
+type DeleteExistingSignatures = Bool
+
+{-------------------------------------------------------------------------------
+  Parsers
+-------------------------------------------------------------------------------}
+
+getOptions :: IO GlobalOpts
+getOptions = execParser opts
+  where
+    opts = info (helper <*> parseGlobalOptions) $ mconcat [
+        fullDesc
+      , header "Manage local Hackage repositories"
+      ]
+
+parseRepoLoc :: Parser RepoLoc
+parseRepoLoc = RepoLoc <$> (option (str >>= readAbsolutePath) $ mconcat [
+      long "repo"
+    , metavar "PATH"
+    , help "Path to local repository"
+    ])
+
+parseKeysLoc :: Parser KeysLoc
+parseKeysLoc = KeysLoc <$> (option (str >>= readAbsolutePath) $ mconcat [
+      long "keys"
+    , metavar "PATH"
+    , help "Path to key store"
+    ])
+
+parseCreateKeys :: Parser Command
+parseCreateKeys = CreateKeys <$> parseKeysLoc
+
+parseBootstrap :: Parser Command
+parseBootstrap = Bootstrap <$> parseKeysLoc <*> parseRepoLoc
+
+parseUpdate :: Parser Command
+parseUpdate = Update <$> parseKeysLoc <*> parseRepoLoc
+
+parseCreateRoot :: Parser Command
+parseCreateRoot = CreateRoot
+  <$> parseKeysLoc
+  <*> (option (str >>= readAbsolutePath) $ mconcat [
+          short 'o'
+        , metavar "FILE"
+        , help "Location of the root file"
+        ])
+
+parseCreateMirrors :: Parser Command
+parseCreateMirrors = CreateMirrors
+  <$> parseKeysLoc
+  <*> (option (str >>= readAbsolutePath) $ mconcat [
+          short 'o'
+        , metavar "FILE"
+        , help "Location of the mirrors file"
+        ])
+  <*> (many $ argument (str >>= readURI) (metavar "MIRROR"))
+
+parseSymlinkCabalLocalRepo :: Parser Command
+parseSymlinkCabalLocalRepo = SymlinkCabalLocalRepo
+  <$> parseRepoLoc
+  <*> (option (str >>= liftA RepoLoc . readAbsolutePath) $ mconcat [
+          long "cabal-repo"
+        , help "Location of the cabal repo"
+        ])
+
+parseSign :: Parser Command
+parseSign = Sign
+  <$> (many . option (str >>= readAbsolutePath) $ mconcat [
+         long "key"
+       , help "Path to private key (can be specified multiple times)"
+       ])
+  <*> (switch $ mconcat [
+         long "delete-existing"
+       , help "Delete any existing signatures"
+       ])
+  <*> argument (str >>= readAbsolutePath) (metavar "FILE")
+
+-- | Global options
+--
+-- TODO: Make repo and keys layout configurable
+parseGlobalOptions :: Parser GlobalOpts
+parseGlobalOptions = GlobalOpts
+  <$> (pure defaultKeysLayout)
+  <*> (pure hackageRepoLayout)
+  <*> (pure hackageIndexLayout)
+  <*> (switch $ mconcat [
+          long "verbose"
+        , short 'v'
+        , help "Verbose logging"
+        ])
+  <*> (option auto $ mconcat [
+          long "expire-root"
+        , metavar "YEARS"
+        , help "Expiry time for the root info"
+        , value 1
+        , showDefault
+        ])
+  <*> (option auto $ mconcat [
+          long "expire-mirrors"
+        , metavar "YEARS"
+        , help "Expiry time for the mirrors"
+        , value 10
+        , showDefault
+        ])
+  <*> (subparser $ mconcat [
+          command "create-keys" $ info (helper <*> parseCreateKeys) $
+            progDesc "Create keys"
+        , command "bootstrap" $ info (helper <*> parseBootstrap) $
+            progDesc "Bootstrap a local repository"
+        , command "update" $ info (helper <*> parseUpdate) $
+            progDesc "Update a (previously bootstrapped) local repository"
+        , command "create-root" $ info (helper <*> parseCreateRoot) $
+            progDesc "Create root metadata"
+        , command "create-mirrors" $ info (helper <*> parseCreateMirrors) $
+            progDesc "Create mirrors metadata. All MIRRORs specified on the command line will be written to the file."
+        , command "symlink-cabal-local-repo" $ info (helper <*> parseSymlinkCabalLocalRepo) $
+            progDesc "Create a directory in cabal-local-repo layout with symlinks to the specified repository."
+        , command "sign" $ info (helper <*> parseSign) $
+            progDesc "Sign a file"
+        ])
+
+readURI :: String -> ReadM URI
+readURI uriStr =
+   case parseURI uriStr of
+     Nothing  -> fail $ "Invalid URI " ++ show uriStr
+     Just uri -> return uri
+
+-- Sadly, cannot do I/O actions inside ReadM
+readAbsolutePath :: String -> ReadM (Path Absolute)
+readAbsolutePath = return . unsafePerformIO . makeAbsolute . fromFilePath
diff --git a/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Paths.hs b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Paths.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Paths.hs
@@ -0,0 +1,33 @@
+-- | Additional paths
+module Hackage.Security.RepoTool.Paths (
+    -- * Repo
+    RepoLoc(..)
+    -- * Keys
+  , KeyRoot
+  , KeyPath
+  , KeysLoc(..)
+  ) where
+
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+
+{-------------------------------------------------------------------------------
+  Repo
+-------------------------------------------------------------------------------}
+
+newtype RepoLoc = RepoLoc { repoLocPath :: Path Absolute }
+  deriving Eq
+
+{-------------------------------------------------------------------------------
+  Keys
+-------------------------------------------------------------------------------}
+
+-- | The key directory
+data KeyRoot
+type KeyPath = Path KeyRoot
+
+instance Pretty (Path KeyRoot) where
+    pretty (Path fp) = "<keys>/" ++ fp
+
+newtype KeysLoc = KeysLoc { keysLocPath :: Path Absolute }
+  deriving Eq
diff --git a/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Util/IO.hs b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Util/IO.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Util/IO.hs
@@ -0,0 +1,112 @@
+-- | IO utilities
+{-# LANGUAGE CPP #-}
+module Hackage.Security.RepoTool.Util.IO (
+    -- * Miscellaneous
+    compress
+  , getFileModTime
+  , createSymbolicLink
+    -- * Tar archives
+  , TarGzError
+  , tarExtractFile
+  ) where
+
+import Control.Exception
+import Data.Typeable
+import System.IO.Error
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
+import qualified Codec.Compression.GZip  as GZip
+import qualified Data.ByteString.Lazy    as BS.L
+
+-- Unlike the hackage-security library properly,
+-- this currently works on unix systems only
+import System.Posix.Types (EpochTime)
+import qualified System.Posix.Files as Posix
+
+-- hackage-security
+import Hackage.Security.Util.Path
+
+-- hackage-repo-tool
+import Hackage.Security.RepoTool.Options
+import Hackage.Security.RepoTool.Layout
+import Hackage.Security.RepoTool.Paths
+
+-- | Get the modification time of the specified file
+--
+-- Returns 0 if the file does not exist .
+getFileModTime :: GlobalOpts -> RepoLoc -> TargetPath' -> IO EpochTime
+getFileModTime opts repoLoc targetPath =
+    handle handler $
+      Posix.modificationTime <$> Posix.getFileStatus (toFilePath fp)
+  where
+    fp :: Path Absolute
+    fp = anchorTargetPath' opts repoLoc targetPath
+
+    handler :: IOException -> IO EpochTime
+    handler ex = if isDoesNotExistError ex then return 0
+                                           else throwIO ex
+
+compress :: Path Absolute -> Path Absolute -> IO ()
+compress src dst =
+    withFile dst WriteMode $ \h ->
+      BS.L.hPut h =<< GZip.compress <$> readLazyByteString src
+
+-- | Create a symbolic link (unix only)
+--
+-- Create the directory for the target if it does not exist.
+--
+-- TODO: Currently this always creates links to absolute locations, whether the
+-- user specified an absolute or a relative target.
+createSymbolicLink :: (FsRoot root, FsRoot root')
+                   => Path root  -- ^ Link target
+                   -> Path root' -- ^ Link location
+                   -> IO ()
+createSymbolicLink linkTarget linkLoc = do
+    createDirectoryIfMissing True (takeDirectory linkLoc)
+    linkTarget' <- toAbsoluteFilePath linkTarget
+    linkLoc'    <- toAbsoluteFilePath linkLoc
+    Posix.createSymbolicLink linkTarget' linkLoc'
+
+{-------------------------------------------------------------------------------
+  Working with tar archives
+-------------------------------------------------------------------------------}
+
+-- | Extract a file from a tar archive
+--
+-- Throws an exception if there is an error in the archive or when the entry
+-- is not a file. Returns nothing if the entry cannot be found.
+tarExtractFile :: GlobalOpts
+               -> RepoLoc
+               -> TargetPath'
+               -> FilePath
+               -> IO (Maybe (BS.L.ByteString, Tar.FileSize))
+tarExtractFile opts repoLoc pathTarGz pathToExtract =
+     handle (throwIO . TarGzError (prettyTargetPath' opts pathTarGz)) $ do
+       let pathTarGz' = anchorTargetPath' opts repoLoc pathTarGz
+       go =<< Tar.read . GZip.decompress <$> readLazyByteString pathTarGz'
+  where
+    go :: Exception e => Tar.Entries e -> IO (Maybe (BS.L.ByteString, Tar.FileSize))
+    go Tar.Done        = return Nothing
+    go (Tar.Fail err)  = throwIO err
+    go (Tar.Next e es) =
+      if Tar.entryPath e == pathToExtract
+        then case Tar.entryContent e of
+               Tar.NormalFile bs sz -> return $ Just (bs, sz)
+               _ -> throwIO $ userError
+                            $ "tarExtractFile: "
+                           ++ pathToExtract ++ " not a normal file"
+        else do -- putStrLn $ show (Tar.entryPath e) ++ " /= " ++ show path
+                go es
+
+data TarGzError = TarGzError FilePath SomeException
+  deriving (Typeable)
+
+instance Exception TarGzError where
+#if MIN_VERSION_base(4,8,0)
+  displayException (TarGzError path e) = path ++ ": " ++ displayException e
+
+deriving instance Show TarGzError
+#else
+instance Show TarGzError where
+  show (TarGzError path e) = path ++ ": " ++ show e
+#endif
diff --git a/hackage-security/hackage-repo-tool/src/Main.hs b/hackage-security/hackage-repo-tool/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Main.hs
@@ -0,0 +1,673 @@
+{-# LANGUAGE CPP #-}
+module Main (main) where
+
+import Control.Exception
+import Control.Monad
+import Data.List (nub)
+import Data.Maybe (catMaybes, mapMaybe)
+import Data.Time
+import GHC.Conc.Sync (setUncaughtExceptionHandler)
+import Network.URI (URI)
+import System.Exit
+import System.IO.Error (isAlreadyExistsError)
+import qualified Data.ByteString.Lazy as BS.L
+import qualified System.FilePath      as FilePath
+
+-- Cabal
+import Distribution.Package
+import Distribution.Text
+
+-- hackage-security
+import Hackage.Security.Server
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.IO
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import qualified Hackage.Security.Key.Env     as KeyEnv
+import qualified Hackage.Security.TUF.FileMap as FileMap
+import qualified Hackage.Security.Util.Lens   as Lens
+import Text.JSON.Canonical (JSValue)
+
+-- hackage-repo-tool
+import Hackage.Security.RepoTool.Options
+import Hackage.Security.RepoTool.Layout
+import Hackage.Security.RepoTool.Layout.Keys
+import Hackage.Security.RepoTool.Paths
+import Hackage.Security.RepoTool.Util.IO
+
+{-------------------------------------------------------------------------------
+  Main application driver
+-------------------------------------------------------------------------------}
+
+main :: IO ()
+main = do
+    setUncaughtExceptionHandler topLevelExceptionHandler
+    opts@GlobalOpts{..} <- getOptions
+    case globalCommand of
+      CreateKeys keysLoc ->
+        createKeys opts keysLoc
+      Bootstrap keysLoc repoLoc ->
+        bootstrapOrUpdate opts keysLoc repoLoc True
+      Update keysLoc repoLoc ->
+        bootstrapOrUpdate opts keysLoc repoLoc False
+      CreateRoot keysLoc rootLoc ->
+        createRoot opts keysLoc rootLoc
+      CreateMirrors keysLoc mirrorsLoc mirrors ->
+        createMirrors opts keysLoc mirrorsLoc mirrors
+      SymlinkCabalLocalRepo repoLoc cabalRepoLoc ->
+        symlinkCabalLocalRepo opts repoLoc cabalRepoLoc
+      Sign keys deleteExisting file ->
+        signFile keys deleteExisting file
+
+-- | Top-level exception handler that uses 'displayException'
+--
+-- Although base 4.8 introduces 'displayException', the top-level exception
+-- handler still uses 'show', sadly. See "PROPOSAL: Add displayException to
+-- Exception typeclass" thread on the libraries mailing list.
+--
+-- NOTE: This is a terrible hack. See the above thread for some insights into
+-- how we should do this better. For now it will do however.
+topLevelExceptionHandler :: SomeException -> IO ()
+topLevelExceptionHandler e = do
+    putStrLn $ displayException e
+    exitFailure
+
+#if !MIN_VERSION_base(4,8,0)
+displayException :: Exception e => e -> String
+displayException = show
+#endif
+
+{-------------------------------------------------------------------------------
+  Creating keys
+-------------------------------------------------------------------------------}
+
+createKeys :: GlobalOpts -> KeysLoc -> IO ()
+createKeys opts keysLoc = do
+    privateRoot      <- replicateM 3 $ createKey' KeyTypeEd25519
+    privateTarget    <- replicateM 3 $ createKey' KeyTypeEd25519
+    privateTimestamp <- replicateM 1 $ createKey' KeyTypeEd25519
+    privateSnapshot  <- replicateM 1 $ createKey' KeyTypeEd25519
+    privateMirrors   <- replicateM 3 $ createKey' KeyTypeEd25519
+    writeKeys opts keysLoc PrivateKeys{..}
+
+{-------------------------------------------------------------------------------
+  Dealing with (private) keys
+-------------------------------------------------------------------------------}
+
+data PrivateKeys = PrivateKeys {
+    privateRoot      :: [Some Key]
+  , privateTarget    :: [Some Key]
+  , privateTimestamp :: [Some Key]
+  , privateSnapshot  :: [Some Key]
+  , privateMirrors   :: [Some Key]
+  }
+
+readKeys :: GlobalOpts -> KeysLoc -> IO PrivateKeys
+readKeys opts keysLoc =
+    PrivateKeys <$> readKeysAt opts keysLoc keysLayoutRoot
+                <*> readKeysAt opts keysLoc keysLayoutTarget
+                <*> readKeysAt opts keysLoc keysLayoutTimestamp
+                <*> readKeysAt opts keysLoc keysLayoutSnapshot
+                <*> readKeysAt opts keysLoc keysLayoutMirrors
+
+writeKeys :: GlobalOpts -> KeysLoc -> PrivateKeys -> IO ()
+writeKeys opts keysLoc PrivateKeys{..} = do
+    forM_ privateRoot      $ writeKey opts keysLoc keysLayoutRoot
+    forM_ privateTarget    $ writeKey opts keysLoc keysLayoutTarget
+    forM_ privateTimestamp $ writeKey opts keysLoc keysLayoutTimestamp
+    forM_ privateSnapshot  $ writeKey opts keysLoc keysLayoutSnapshot
+    forM_ privateMirrors   $ writeKey opts keysLoc keysLayoutMirrors
+
+readKeysAt :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> IO [Some Key]
+readKeysAt opts keysLoc subDir = catMaybes <$> do
+    entries <- getDirectoryContents absPath
+    forM entries $ \entry -> do
+      let path = absPath </> entry
+      mKey <- readJSON_NoKeys_NoLayout path
+      case mKey of
+        Left _err -> do logWarn opts $ "Skipping unrecognized " ++ pretty path
+                        return Nothing
+        Right key -> return $ Just key
+  where
+    absPath = anchorKeyPath opts keysLoc subDir
+
+writeKey :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> Some Key -> IO ()
+writeKey opts@GlobalOpts{..} keysLoc subDir key = do
+    logInfo opts $ "Writing " ++ pretty (relPath globalKeysLayout)
+    createDirectoryIfMissing True (takeDirectory absPath)
+    writeJSON_NoLayout absPath key
+  where
+    relPath = keysLayoutKey subDir key
+    absPath = anchorKeyPath opts keysLoc relPath
+
+{-------------------------------------------------------------------------------
+  Creating individual files
+
+  We translate absolute paths to repo layout to fit with rest of infrastructure.
+-------------------------------------------------------------------------------}
+
+createRoot :: GlobalOpts -> KeysLoc -> Path Absolute -> IO ()
+createRoot opts@GlobalOpts{..} keysLoc rootLoc = do
+    keys <- readKeys opts keysLoc
+    now  <- getCurrentTime
+    updateRoot opts { globalRepoLayout = layout }
+               repoLoc
+               WriteUpdate
+               keys
+               now
+  where
+    repoLoc = RepoLoc $ takeDirectory rootLoc
+    layout  = globalRepoLayout {
+                  repoLayoutRoot = rootFragment $ takeFileName rootLoc
+                }
+
+createMirrors :: GlobalOpts -> KeysLoc -> Path Absolute -> [URI] -> IO ()
+createMirrors opts@GlobalOpts{..} keysLoc mirrorsLoc mirrors = do
+    keys <- readKeys opts keysLoc
+    now  <- getCurrentTime
+    updateMirrors opts { globalRepoLayout = layout }
+                  repoLoc
+                  WriteUpdate
+                  keys
+                  now
+                  mirrors
+  where
+    repoLoc = RepoLoc $ takeDirectory mirrorsLoc
+    layout  = globalRepoLayout {
+                  repoLayoutMirrors = rootFragment $ takeFileName mirrorsLoc
+                }
+
+rootFragment :: String -> RepoPath
+rootFragment = rootPath . fragment
+
+{-------------------------------------------------------------------------------
+  Bootstrapping / updating
+
+  TODO: Some of this functionality should be moved to
+  @Hackage.Security.Server.*@ (to be shared by both, say, Hackage, and
+  secure-local),  but I'm not sure precisely in what form yet.
+-------------------------------------------------------------------------------}
+
+bootstrapOrUpdate :: GlobalOpts -> KeysLoc -> RepoLoc -> Bool -> IO ()
+bootstrapOrUpdate opts@GlobalOpts{..} keysLoc repoLoc isBootstrap = do
+    -- Collect info
+    keys <- readKeys opts keysLoc
+    now  <- getCurrentTime
+    pkgs <- findPackages opts repoLoc
+
+    -- Sanity check
+    repoLayoutOk <- checkRepoLayout opts repoLoc pkgs
+    unless repoLayoutOk $
+      throwIO $ userError "Unexpected repository layout"
+
+    -- We overwrite files during bootstrap process, but update them only
+    -- if necessary during an update. Note that we _only_ write the updated
+    -- files to the tarball, so the user deletes the tarball and then calls
+    -- update (rather than bootstrap) the tarball will be missing files.
+    let whenWrite = if isBootstrap
+                      then WriteInitial
+                      else WriteUpdate
+
+    -- If doing bootstrap: create root and mirrors
+    when isBootstrap $ do
+      updateRoot    opts repoLoc whenWrite keys now
+      updateMirrors opts repoLoc whenWrite keys now []
+
+    -- Create targets.json for each package version
+    forM_ pkgs $ \pkgId -> do
+      createPackageMetadata opts repoLoc whenWrite pkgId
+      extractCabalFile      opts repoLoc whenWrite pkgId
+
+    -- Recreate index tarball
+    newFiles <- findNewIndexFiles opts repoLoc whenWrite
+    case (whenWrite, null newFiles) of
+      (WriteInitial, _) -> do
+        -- If we are recreating all files, also recreate the index
+        _didExist <- handleDoesNotExist $ removeFile pathIndexTar
+        logInfo opts $ "Writing " ++ prettyRepo repoLayoutIndexTar
+      (WriteUpdate, True) -> do
+        logInfo opts $ "Skipping " ++ prettyRepo repoLayoutIndexTar
+      (WriteUpdate, False) ->
+        logInfo opts $ "Appending " ++ show (length newFiles)
+                    ++ " file(s) to " ++ prettyRepo repoLayoutIndexTar
+    unless (null newFiles) $ do
+      tarAppend
+        (anchorRepoPath opts repoLoc repoLayoutIndexTar)
+        (anchorRepoPath opts repoLoc repoLayoutIndexDir)
+        (map castRoot newFiles)
+
+      logInfo opts $ "Writing " ++ prettyRepo repoLayoutIndexTarGz
+      compress (anchorRepoPath opts repoLoc repoLayoutIndexTar)
+               (anchorRepoPath opts repoLoc repoLayoutIndexTarGz)
+
+    -- Create snapshot
+    -- TODO: If we are updating we should be incrementing the version, not
+    -- keeping it the same
+    rootInfo    <- computeFileInfo' repoLayoutRoot
+    mirrorsInfo <- computeFileInfo' repoLayoutMirrors
+    tarInfo     <- computeFileInfo' repoLayoutIndexTar
+    tarGzInfo   <- computeFileInfo' repoLayoutIndexTarGz
+    let snapshot = Snapshot {
+            snapshotVersion     = versionInitial
+          , snapshotExpires     = expiresInDays now 3
+          , snapshotInfoRoot    = rootInfo
+          , snapshotInfoMirrors = mirrorsInfo
+          , snapshotInfoTar     = Just tarInfo
+          , snapshotInfoTarGz   = tarGzInfo
+          }
+    updateFile opts
+               repoLoc
+               whenWrite
+               (InRep repoLayoutSnapshot)
+               (withSignatures globalRepoLayout (privateSnapshot keys))
+               snapshot
+
+    -- Finally, create the timestamp
+    snapshotInfo <- computeFileInfo' repoLayoutSnapshot
+    let timestamp = Timestamp {
+            timestampVersion      = versionInitial
+          , timestampExpires      = expiresInDays now 3
+          , timestampInfoSnapshot = snapshotInfo
+          }
+    updateFile opts
+               repoLoc
+               whenWrite
+               (InRep repoLayoutTimestamp)
+               (withSignatures globalRepoLayout (privateTimestamp keys))
+               timestamp
+  where
+    pathIndexTar :: Path Absolute
+    pathIndexTar = anchorRepoPath opts repoLoc repoLayoutIndexTar
+
+    -- | Compute file information for a file in the repo
+    computeFileInfo' :: (RepoLayout -> RepoPath) -> IO FileInfo
+    computeFileInfo' = computeFileInfo . anchorRepoPath opts repoLoc
+
+    prettyRepo :: (RepoLayout -> RepoPath) -> String
+    prettyRepo = prettyTargetPath' opts . InRep
+
+-- | Create root metadata
+updateRoot :: GlobalOpts
+           -> RepoLoc
+           -> WhenWrite
+           -> PrivateKeys
+           -> UTCTime
+           -> IO ()
+updateRoot opts repoLoc whenWrite keys now =
+    updateFile opts
+               repoLoc
+               whenWrite
+               (InRep repoLayoutRoot)
+               (withSignatures' (privateRoot keys))
+               root
+  where
+    root :: Root
+    root = Root {
+        rootVersion = versionInitial
+      , rootExpires = expiresInDays now (globalExpireRoot opts * 365)
+      , rootKeys    = KeyEnv.fromKeys $ concat [
+                          privateRoot      keys
+                        , privateTarget    keys
+                        , privateSnapshot  keys
+                        , privateTimestamp keys
+                        , privateMirrors   keys
+                        ]
+      , rootRoles   = RootRoles {
+            rootRolesRoot = RoleSpec {
+                roleSpecKeys      = map somePublicKey (privateRoot keys)
+              , roleSpecThreshold = KeyThreshold 2
+              }
+          , rootRolesTargets = RoleSpec {
+                roleSpecKeys      = map somePublicKey (privateTarget keys)
+              , roleSpecThreshold = KeyThreshold 1
+              }
+          , rootRolesSnapshot = RoleSpec {
+                roleSpecKeys      = map somePublicKey (privateSnapshot keys)
+              , roleSpecThreshold = KeyThreshold 1
+              }
+          , rootRolesTimestamp = RoleSpec {
+                roleSpecKeys      = map somePublicKey (privateTimestamp keys)
+              , roleSpecThreshold = KeyThreshold 1
+              }
+          , rootRolesMirrors = RoleSpec {
+                roleSpecKeys      = map somePublicKey (privateMirrors keys)
+              , roleSpecThreshold = KeyThreshold 1
+              }
+          }
+      }
+
+-- | Create root metadata
+updateMirrors :: GlobalOpts
+              -> RepoLoc
+              -> WhenWrite
+              -> PrivateKeys
+              -> UTCTime
+              -> [URI]
+              -> IO ()
+updateMirrors opts repoLoc whenWrite keys now uris =
+    updateFile opts
+               repoLoc
+               whenWrite
+               (InRep repoLayoutMirrors)
+               (withSignatures' (privateMirrors keys))
+               mirrors
+  where
+    mirrors :: Mirrors
+    mirrors = Mirrors {
+        mirrorsVersion = versionInitial
+      , mirrorsExpires = expiresInDays now (globalExpireMirrors opts * 365)
+      , mirrorsMirrors = map mkMirror uris
+      }
+
+    mkMirror :: URI -> Mirror
+    mkMirror uri = Mirror uri MirrorFull
+
+-- | Create package metadata
+createPackageMetadata :: GlobalOpts -> RepoLoc -> WhenWrite -> PackageIdentifier -> IO ()
+createPackageMetadata opts repoLoc whenWrite pkgId = do
+    srcTS <- getFileModTime opts repoLoc src
+    dstTS <- getFileModTime opts repoLoc dst
+    let skip = case whenWrite of
+                 WriteInitial -> False
+                 WriteUpdate  -> dstTS >= srcTS
+
+    if skip
+      then logInfo opts $ "Skipping " ++ prettyTargetPath' opts dst
+      else do
+        fileMapEntries <- mapM computeFileMapEntry fileMapFiles
+        let targets = Targets {
+                targetsVersion     = versionInitial
+              , targetsExpires     = expiresNever
+              , targetsTargets     = FileMap.fromList fileMapEntries
+              , targetsDelegations = Nothing
+              }
+
+        -- Currently we "sign" with no keys
+        updateFile opts
+                   repoLoc
+                   whenWrite
+                   dst
+                   (withSignatures' [])
+                   targets
+  where
+    computeFileMapEntry :: TargetPath' -> IO (TargetPath, FileInfo)
+    computeFileMapEntry file = do
+      info <- computeFileInfo $ anchorTargetPath' opts repoLoc file
+      return (applyTargetPath' opts file, info)
+
+    -- The files we need to add to the package targets file
+    -- Currently this is just the .tar.gz file
+    fileMapFiles :: [TargetPath']
+    fileMapFiles = [src]
+
+    src, dst :: TargetPath'
+    src = InRepPkg repoLayoutPkgTarGz     pkgId
+    dst = InIdxPkg indexLayoutPkgMetadata pkgId
+
+{-------------------------------------------------------------------------------
+  Working with the index
+-------------------------------------------------------------------------------}
+
+-- | Find the files we need to add to the index
+findNewIndexFiles :: GlobalOpts -> RepoLoc -> WhenWrite -> IO [IndexPath]
+findNewIndexFiles opts@GlobalOpts{..} repoLoc whenWrite = do
+    indexTS    <- getFileModTime opts repoLoc (InRep repoLayoutIndexTar)
+    indexFiles <- getRecursiveContents absIndexDir
+
+    let indexFiles' :: [IndexPath]
+        indexFiles' = map rootPath indexFiles
+
+    case whenWrite of
+      WriteInitial -> return indexFiles'
+      WriteUpdate  -> liftM catMaybes $
+        forM indexFiles' $ \indexFile -> do
+          fileTS <- getFileModTime opts repoLoc $ InIdx (const indexFile)
+          if fileTS > indexTS then return $ Just indexFile
+                              else return Nothing
+  where
+    absIndexDir :: Path Absolute
+    absIndexDir = anchorRepoPath opts repoLoc repoLayoutIndexDir
+
+-- | Extract the cabal file from the package tarball and copy it to the index
+extractCabalFile :: GlobalOpts -> RepoLoc -> WhenWrite -> PackageIdentifier -> IO ()
+extractCabalFile opts@GlobalOpts{..} repoLoc whenWrite pkgId = do
+    srcTS <- getFileModTime opts repoLoc src
+    dstTS <- getFileModTime opts repoLoc dst
+    let skip = case whenWrite of
+                 WriteInitial -> False
+                 WriteUpdate  -> dstTS >= srcTS
+    if skip
+      then logInfo opts $ "Skipping " ++ prettyTargetPath' opts dst
+      else do
+        mCabalFile <- try $ tarExtractFile opts repoLoc src pathCabalInTar
+        case mCabalFile of
+          Left (ex :: SomeException) ->
+            logWarn opts $ "Failed to extract .cabal from package " ++ display pkgId
+                        ++ ": " ++ displayException ex
+          Right Nothing ->
+            logWarn opts $ ".cabal file missing for package " ++ display pkgId
+          Right (Just (cabalFile, _cabalSize)) -> do
+            logInfo opts $ "Writing "
+                        ++ prettyTargetPath' opts dst
+                        ++ " (extracted from "
+                        ++ prettyTargetPath' opts src
+                        ++ ")"
+            withFile pathCabalInIdx WriteMode $ \h -> BS.L.hPut h cabalFile
+  where
+    pathCabalInTar :: FilePath
+    pathCabalInTar = FilePath.joinPath [
+                         display pkgId
+                       , display (packageName pkgId)
+                       ] FilePath.<.> "cabal"
+
+    pathCabalInIdx :: Path Absolute
+    pathCabalInIdx = anchorTargetPath' opts repoLoc dst
+
+    src, dst :: TargetPath'
+    dst = InIdxPkg indexLayoutPkgCabal pkgId
+    src = InRepPkg repoLayoutPkgTarGz  pkgId
+
+{-------------------------------------------------------------------------------
+  Updating files in the repo or in the index
+-------------------------------------------------------------------------------}
+
+data WhenWrite =
+    -- | Write the initial version of a file
+    --
+    -- If applicable, set file version to 1.
+    WriteInitial
+
+    -- | Update an existing
+    --
+    -- If applicable, increment file version number.
+  | WriteUpdate
+
+-- | Write canonical JSON
+--
+-- We write the file to a temporary location and compare file info with the file
+-- that was already in the target location (if any). If it's the same (modulo
+-- version number) we don't overwrite it and return Nothing; otherwise we
+-- increment the version number, write the file, and (if it's in the index)
+-- copy it to the unpacked index directory.
+updateFile :: forall a. (ToJSON WriteJSON (Signed a), HasHeader a)
+           => GlobalOpts
+           -> RepoLoc
+           -> WhenWrite
+           -> TargetPath'
+           -> (a -> Signed a)          -- ^ Signing function
+           -> a                        -- ^ Unsigned file contents
+           -> IO ()
+updateFile opts@GlobalOpts{..} repoLoc whenWrite fileLoc signPayload a = do
+    mOldHeader :: Maybe (Either DeserializationError (UninterpretedSignatures Header)) <-
+      handleDoesNotExist $ readJSON_NoKeys_NoLayout fp
+
+    case (whenWrite, mOldHeader) of
+      (WriteInitial, _) ->
+        writeDoc writing a
+      (WriteUpdate, Nothing) -> -- no previous version
+        writeDoc creating a
+      (WriteUpdate, Just (Left _err)) -> -- old file corrupted
+        writeDoc overwriting a
+      (WriteUpdate, Just (Right (UninterpretedSignatures oldHeader _oldSigs))) -> do
+        -- We cannot quite read the entire old file, because we don't know what
+        -- key environment to use. Instead, we render the _new_ file, but
+        -- setting the version number to be equal to the version number of the
+        -- old file. If the result turns out to be equal to the old file (same
+        -- FileInfo), we skip writing this file. However, if this is NOT equal,
+        -- we set the version number of the new file to be equal to the version
+        -- number of the old plus one, and write it.
+        oldFileInfo <- computeFileInfo fp
+
+        let oldVersion :: FileVersion
+            oldVersion = headerVersion oldHeader
+
+            wOldVersion, wIncVersion :: a
+            wOldVersion = Lens.set fileVersion oldVersion a
+            wIncVersion = Lens.set fileVersion (versionIncrement oldVersion) a
+
+            wOldSigned :: Signed a
+            wOldSigned = signPayload wOldVersion
+
+            wOldRendered :: BS.L.ByteString
+            wOldRendered = renderJSON globalRepoLayout wOldSigned
+
+            -- TODO: We could be be more efficient here and verify file size
+            -- first; however, these files are tiny so it doesn't really matter.
+            wOldFileInfo :: FileInfo
+            wOldFileInfo = fileInfo wOldRendered
+
+        if knownFileInfoEqual oldFileInfo wOldFileInfo
+          then logInfo opts $ "Unchanged " ++ prettyTargetPath' opts fileLoc
+          else writeDoc updating wIncVersion
+  where
+    -- | Actually write the file
+    writeDoc :: String -> a -> IO ()
+    writeDoc reason doc = do
+      logInfo opts reason
+      createDirectoryIfMissing True (takeDirectory fp)
+      writeJSON globalRepoLayout fp (signPayload doc)
+
+    fp :: Path Absolute
+    fp = anchorTargetPath' opts repoLoc fileLoc
+
+    writing, creating, overwriting, updating :: String
+    writing     = "Writing "     ++ prettyTargetPath' opts fileLoc
+    creating    = "Creating "    ++ prettyTargetPath' opts fileLoc
+    overwriting = "Overwriting " ++ prettyTargetPath' opts fileLoc ++ " (old file corrupted)"
+    updating    = "Updating "    ++ prettyTargetPath' opts fileLoc
+
+{-------------------------------------------------------------------------------
+  Inspect the repo layout
+-------------------------------------------------------------------------------}
+
+-- | Find packages
+--
+-- Repository layouts are configurable, but we don't know if the layout of the
+-- current directory matches the specified layout. We therefore here just search
+-- through the directory looking for anything that looks like a package.
+-- We can then verify that this list of packages actually matches the layout as
+-- a separate step.
+findPackages :: GlobalOpts -> RepoLoc -> IO [PackageIdentifier]
+findPackages GlobalOpts{..} (RepoLoc repoLoc) =
+    nub . mapMaybe isPackage <$> getRecursiveContents repoLoc
+  where
+    isPackage :: Path Unrooted -> Maybe PackageIdentifier
+    isPackage path = do
+      guard $ not (isIndex path)
+      pkg <- hasExtensions path [".tar", ".gz"]
+      simpleParse pkg
+
+    isIndex :: Path Unrooted -> Bool
+    isIndex = (==) (unrootPath (repoLayoutIndexTarGz globalRepoLayout))
+
+-- | Check that packages are in their expected location
+checkRepoLayout :: GlobalOpts -> RepoLoc -> [PackageIdentifier] -> IO Bool
+checkRepoLayout opts repoLoc = liftM and . mapM checkPackage
+  where
+    checkPackage :: PackageIdentifier -> IO Bool
+    checkPackage pkgId = do
+        existsTarGz <- doesFileExist $ anchorTargetPath' opts repoLoc expectedTarGz
+        unless existsTarGz $
+          logWarn opts $ "Package tarball " ++ display pkgId
+                      ++ " expected in location "
+                      ++ prettyTargetPath' opts expectedTarGz
+
+        return existsTarGz
+      where
+        expectedTarGz :: TargetPath'
+        expectedTarGz = InRepPkg repoLayoutPkgTarGz pkgId
+
+{-------------------------------------------------------------------------------
+  Creating Cabal-local-repo
+-------------------------------------------------------------------------------}
+
+symlinkCabalLocalRepo :: GlobalOpts -> RepoLoc -> RepoLoc -> IO ()
+symlinkCabalLocalRepo opts@GlobalOpts{..} repoLoc cabalRepoLoc = do
+    symlink repoLayoutIndexTar
+    pkgs <- findPackages opts repoLoc
+    forM_ pkgs $ \pkgId -> symlink (`repoLayoutPkgTarGz` pkgId)
+  where
+    -- TODO: This gives a warning for files that we previously linked, as well
+    -- as for files that we _never_ need to link (because the location of both
+    -- repos is the same). This is potentially confusing.
+    symlink :: (RepoLayout -> RepoPath) -> IO ()
+    symlink file =
+        catch (createSymbolicLink target loc) $ \ex ->
+          if isAlreadyExistsError ex
+            then logWarn opts $ "Skipping " ++ pretty (file globalRepoLayout)
+                             ++ " (already exists)"
+            else throwIO ex
+      where
+        target = anchorRepoPath opts  repoLoc      file
+        loc    = anchorRepoPath opts' cabalRepoLoc file
+        opts'  = opts { globalRepoLayout = cabalLocalRepoLayout }
+
+{-------------------------------------------------------------------------------
+  Signing individual files
+-------------------------------------------------------------------------------}
+
+signFile :: [KeyLoc] -> DeleteExistingSignatures -> Path Absolute -> IO ()
+signFile keyLocs deleteExisting fp = do
+    UninterpretedSignatures (payload :: JSValue) oldSigs <-
+      throwErrors =<< readJSON_NoKeys_NoLayout fp
+    keys :: [Some Key] <- forM keyLocs $ \keyLoc ->
+      throwErrors =<< readJSON_NoKeys_NoLayout keyLoc
+    let newSigs = concat [
+            if deleteExisting then [] else oldSigs
+          , toPreSignatures (signRendered keys $ renderJSON_NoLayout payload)
+          ]
+    writeJSON_NoLayout fp $ UninterpretedSignatures payload newSigs
+
+{-------------------------------------------------------------------------------
+  Logging
+-------------------------------------------------------------------------------}
+
+logInfo :: GlobalOpts -> String -> IO ()
+logInfo GlobalOpts{..} str = when globalVerbose $
+    putStrLn $ "Info: " ++ str
+
+logWarn :: GlobalOpts -> String -> IO ()
+logWarn _opts str =
+    putStrLn $ "Warning: " ++ str
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Check that a file has the given extensions
+--
+-- Returns the filename without the verified extensions. For example:
+--
+-- > hasExtensions "foo.tar.gz" [".tar", ".gz"] == Just "foo"
+hasExtensions :: Path a -> [String] -> Maybe String
+hasExtensions = \fp exts -> go (takeFileName fp) (reverse exts)
+  where
+    go :: FilePath -> [String] -> Maybe String
+    go fp []     = return fp
+    go fp (e:es) = do let (fp', e') = FilePath.splitExtension fp
+                      guard $ e == e'
+                      go fp' es
+
+throwErrors :: Exception e => Either e a -> IO a
+throwErrors (Left err) = throwIO err
+throwErrors (Right a)  = return a
diff --git a/hackage-security/hackage-repo-tool/src/Prelude.hs b/hackage-security/hackage-repo-tool/src/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-repo-tool/src/Prelude.hs
@@ -0,0 +1,27 @@
+-- | Smooth over differences between various ghc versions by making older
+-- preludes look like 4.8.0
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE CPP #-}
+module Prelude (
+    module P
+#if !MIN_VERSION_base(4,8,0)
+  , Applicative(..)
+  , Monoid(..)
+  , (<$>)
+  , (<$)
+  , traverse
+#endif
+  ) where
+
+#if MIN_VERSION_base(4,8,0)
+import "base" Prelude as P
+#else
+#if MIN_VERSION_base(4,6,0)
+import "base" Prelude as P
+#else
+import "base" Prelude as P hiding (catch)
+#endif
+import Control.Applicative
+import Data.Monoid
+import Data.Traversable (traverse)
+#endif
diff --git a/hackage-security/hackage-root-tool/ChangeLog.md b/hackage-security/hackage-root-tool/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-root-tool/ChangeLog.md
@@ -0,0 +1,4 @@
+0.1.0.0
+-------
+* Initial hack, just to make it possible to create root keys
+  and re-sign root info.
diff --git a/hackage-security/hackage-root-tool/LICENSE b/hackage-security/hackage-root-tool/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-root-tool/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-root-tool/Main.hs b/hackage-security/hackage-root-tool/Main.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-root-tool/Main.hs
@@ -0,0 +1,159 @@
+{-# LANGUAGE CPP, ScopedTypeVariables, RecordWildCards #-}
+module Main (main) where
+
+import Data.Monoid
+import Control.Exception
+import GHC.Conc.Sync (setUncaughtExceptionHandler)
+import System.Exit
+
+-- hackage-security
+import Hackage.Security.Server
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.Path
+import Text.JSON.Canonical (JSValue)
+
+import Options.Applicative
+
+
+{-------------------------------------------------------------------------------
+  Main application driver
+-------------------------------------------------------------------------------}
+
+main :: IO ()
+main = do
+    setUncaughtExceptionHandler topLevelExceptionHandler
+    GlobalOpts{..} <- getOptions
+    case globalCommand of
+      CreateKeys ->
+        createKeys
+
+      Sign key file -> do
+        key'  <- makeAbsolute (fromFilePath key)
+        file' <- makeAbsolute (fromFilePath file)
+        signFile key' file'
+
+-- | Top-level exception handler that uses 'displayException'
+--
+-- Although base 4.8 introduces 'displayException', the top-level exception
+-- handler still uses 'show', sadly. See "PROPOSAL: Add displayException to
+-- Exception typeclass" thread on the libraries mailing list.
+--
+-- NOTE: This is a terrible hack. See the above thread for some insights into
+-- how we should do this better. For now it will do however.
+topLevelExceptionHandler :: SomeException -> IO ()
+topLevelExceptionHandler e = do
+    putStrLn $ displayException e
+    exitFailure
+
+#if !MIN_VERSION_base(4,8,0)
+displayException :: Exception e => e -> String
+displayException = show
+#endif
+
+{-------------------------------------------------------------------------------
+  Creating keys
+-------------------------------------------------------------------------------}
+
+createKeys :: IO ()
+createKeys = do
+    privateRoot <- createKey' KeyTypeEd25519
+    writeKey privateRoot
+
+{-------------------------------------------------------------------------------
+  Dealing with (private) keys
+-------------------------------------------------------------------------------}
+
+writeKey :: Some Key -> IO ()
+writeKey keypair = do
+    let keypath = keyIdString (someKeyId keypair)
+    keypathabs <- makeAbsolute (fromFilePath keypath)
+
+    logInfo $ "Writing new key:\n  " ++ keypath ++ ".{private,public}"
+
+    writeJSON_NoLayout (keypathabs <.> "private") keypair
+    writeJSON_NoLayout (keypathabs <.> "public")  keypublic
+  where
+    keypublic :: Some PublicKey
+    keypublic = somePublicKey keypair
+
+{-------------------------------------------------------------------------------
+  Signing individual files
+-------------------------------------------------------------------------------}
+
+signFile :: KeyLoc -> Path Absolute -> IO ()
+signFile keyLoc fp = do
+    UninterpretedSignatures (payload :: JSValue) _oldSigs <-
+      throwErrors =<< readJSON_NoKeys_NoLayout fp
+    key :: Some Key <-
+      throwErrors =<< readJSON_NoKeys_NoLayout keyLoc
+    let newSig = toPreSignatures (signRendered [key]
+                                               (renderJSON_NoLayout payload))
+    writeJSON_NoLayout (fp <.> "sig") newSig
+
+{-------------------------------------------------------------------------------
+  Logging
+-------------------------------------------------------------------------------}
+
+logInfo :: String -> IO ()
+logInfo msg = putStrLn msg
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+throwErrors :: Exception e => Either e a -> IO a
+throwErrors (Left err) = throwIO err
+throwErrors (Right a)  = return a
+
+{-------------------------------------------------------------------------------
+  Types
+-------------------------------------------------------------------------------}
+
+-- | Command line options
+data GlobalOpts = GlobalOpts {
+
+    -- | Command to execute
+    globalCommand :: Command
+  }
+
+data Command =
+    -- | Create keys
+    CreateKeys
+
+    -- | Sign an individual file
+  | Sign FilePath FilePath
+
+type KeyLoc = Path Absolute
+
+{-------------------------------------------------------------------------------
+  Parsers
+-------------------------------------------------------------------------------}
+
+getOptions :: IO GlobalOpts
+getOptions = execParser opts
+  where
+    opts = info (helper <*> parseGlobalOptions) $ mconcat [
+        fullDesc
+      , header "Manage local Hackage repositories"
+      ]
+
+parseCreateKeys :: Parser Command
+parseCreateKeys = pure CreateKeys
+
+parseSign :: Parser Command
+parseSign = Sign
+  <$> argument str (metavar "KEY")
+  <*> argument str (metavar "FILE")
+
+-- | Global options
+--
+-- TODO: Make repo and keys layout configurable
+parseGlobalOptions :: Parser GlobalOpts
+parseGlobalOptions =
+      GlobalOpts
+  <$> (subparser $ mconcat [
+          command "create-key" $ info (helper <*> parseCreateKeys) $
+            progDesc "Create keys"
+        , command "sign" $ info (helper <*> parseSign) $
+            progDesc "Sign a file"
+        ])
diff --git a/hackage-security/hackage-root-tool/Setup.hs b/hackage-security/hackage-root-tool/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-root-tool/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-root-tool/hackage-root-tool.cabal b/hackage-security/hackage-root-tool/hackage-root-tool.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-root-tool/hackage-root-tool.cabal
@@ -0,0 +1,34 @@
+name:                hackage-root-tool
+version:             0.1.0.0
+synopsis:            Utility for Hackage key holders to generate keys and sign root info.
+description:         A command line tool for people who hold Hackage root keys.
+                     It can generate new keys, and can sign root information.
+                     .
+                     This is part of the Hackage Security infrastructure.
+homepage:            http://github.com/well-typed/hackage-security/
+license:             BSD3
+license-file:        LICENSE
+author:              Duncan Coutts, Edsko de Vries
+maintainer:          duncan@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+
+extra-source-files:
+  ChangeLog.md
+
+source-repository head
+  type:                git
+  location:            http://github.com/well-typed/hackage-security
+  subdir:              hackage-root-tool
+
+executable hackage-root-tool
+  main-is:             Main.hs
+  build-depends:       base                 >= 4.4  && < 5,
+                       filepath             >= 1.2  && < 1.5,
+                       optparse-applicative >= 0.11 && < 0.13,
+                       hackage-security     >= 0.2  && < 0.6
+  default-language:    Haskell2010
+  other-extensions:    CPP, ScopedTypeVariables, RecordWildCards
+  ghc-options:         -Wall
diff --git a/hackage-security/hackage-security-HTTP/ChangeLog.md b/hackage-security/hackage-security-HTTP/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-HTTP/ChangeLog.md
@@ -0,0 +1,17 @@
+0.1.1
+-----
+* Implement updated HttpLib API from hackage-security 0.5
+
+0.1.0.2
+-------
+* Allow for hackage-security 0.3.*
+* Include ChangeLog.md in sdist (#98)
+
+0.1.0.1
+-------
+* Allow for hackage-security 0.2.*
+* Allow for network-2.5 (rather than network-uri-2.6.*)
+
+0.1.0.0
+-------
+* Initial beta release
diff --git a/hackage-security/hackage-security-HTTP/LICENSE b/hackage-security/hackage-security-HTTP/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-HTTP/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-security-HTTP/Setup.hs b/hackage-security/hackage-security-HTTP/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-HTTP/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-security-HTTP/hackage-security-HTTP.cabal b/hackage-security/hackage-security-HTTP/hackage-security-HTTP.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-HTTP/hackage-security-HTTP.cabal
@@ -0,0 +1,55 @@
+name:                hackage-security-HTTP
+version:             0.1.1
+synopsis:            Hackage security bindings against the HTTP library
+description:         The hackage security library provides a 'HttpLib'
+                     abstraction to allow to bind against different HTTP
+                     libraries. This library implements this abstraction using
+                     the @HTTP@ library.
+homepage:            http://github.com/well-typed/hackage-security/
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+homepage:            https://github.com/well-typed/hackage-security
+bug-reports:         https://github.com/well-typed/hackage-security/issues
+build-type:          Simple
+cabal-version:       >=1.10
+
+extra-source-files:
+  ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/well-typed/hackage-security.git
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+library
+  exposed-modules:     Hackage.Security.Client.Repository.HttpLib.HTTP
+  build-depends:       base             >= 4.4       && < 5,
+                       bytestring       >= 0.9       && < 0.11,
+                       HTTP             >= 4000.2.19 && < 4000.4,
+                       mtl              >= 2.2       && < 2.3,
+                       zlib             >= 0.5       && < 0.7,
+                       hackage-security >= 0.5       && < 0.6
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  DeriveDataTypeable
+                       FlexibleContexts
+                       RankNTypes
+                       RecordWildCards
+                       ScopedTypeVariables
+                       StandaloneDeriving
+  other-extensions:    CPP
+  ghc-options:         -Wall
+
+  -- See comments in hackage-security.cabal
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
diff --git a/hackage-security/hackage-security-HTTP/src/Hackage/Security/Client/Repository/HttpLib/HTTP.hs b/hackage-security/hackage-security-HTTP/src/Hackage/Security/Client/Repository/HttpLib/HTTP.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-HTTP/src/Hackage/Security/Client/Repository/HttpLib/HTTP.hs
@@ -0,0 +1,274 @@
+{-# LANGUAGE CPP #-}
+-- | Implementation of 'HttpClient' using the HTTP package
+module Hackage.Security.Client.Repository.HttpLib.HTTP (
+    withClient
+    -- ** Additional operations
+  , setOutHandler
+  , setErrHandler
+  , setProxy
+  , request
+    -- ** Low-level API
+  , Browser -- opaque
+  , withBrowser
+    -- * Exception types
+  , UnexpectedResponse(..)
+  , InvalidProxy(..)
+  ) where
+
+import Control.Concurrent
+import Control.Exception
+import Control.Monad
+import Data.List (intercalate)
+import Data.Typeable (Typeable)
+import Network.URI
+import qualified Data.ByteString.Lazy as BS.L
+import qualified Control.Monad.State  as State
+import qualified Network.Browser      as HTTP
+import qualified Network.HTTP         as HTTP
+import qualified Network.HTTP.Proxy   as HTTP
+
+import Hackage.Security.Client
+import Hackage.Security.Client.Repository.HttpLib
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Pretty
+import qualified Hackage.Security.Util.Lens as Lens
+
+{-------------------------------------------------------------------------------
+  Top-level API
+-------------------------------------------------------------------------------}
+
+-- | Initialize the client
+--
+-- TODO: This currently uses the lazy bytestring API offered by the HTTP
+-- library. Unfortunately this provides no way of closing the connection when
+-- the callback decides it doens't require any further input. It seems
+-- impossible however to implement a proper streaming API.
+-- See <https://github.com/haskell/HTTP/issues/86>.
+withClient :: (Browser -> HttpLib -> IO a) -> IO a
+withClient callback =
+    bracket browserInit browserCleanup $ \browser ->
+      callback browser HttpLib {
+          httpGet      = get      browser
+        , httpGetRange = getRange browser
+        }
+
+{-------------------------------------------------------------------------------
+  Individual methods
+-------------------------------------------------------------------------------}
+
+get :: Throws SomeRemoteError
+    => Browser
+    -> [HttpRequestHeader] -> URI
+    -> ([HttpResponseHeader] -> BodyReader -> IO a)
+    -> IO a
+get browser reqHeaders uri callback = wrapCustomEx $ do
+    response <- request browser
+      $ setRequestHeaders reqHeaders
+      -- avoid silly `Content-Length: 0` header inserted by `mkRequest`
+      $ removeHeader HTTP.HdrContentLength
+      $ HTTP.mkRequest HTTP.GET uri
+    case HTTP.rspCode response of
+      (2, 0, 0) -> withResponse response callback
+      otherCode -> throwChecked $ UnexpectedResponse uri otherCode
+
+getRange :: Throws SomeRemoteError
+         => Browser
+         -> [HttpRequestHeader] -> URI -> (Int, Int)
+         -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)
+         -> IO a
+getRange browser reqHeaders uri (from, to) callback = wrapCustomEx $ do
+    response <- request browser
+      $ setRange from to
+      $ setRequestHeaders reqHeaders
+      -- avoid silly `Content-Length: 0` header inserted by `mkRequest`
+      $ removeHeader HTTP.HdrContentLength
+      $ HTTP.mkRequest HTTP.GET uri
+    case HTTP.rspCode response of
+      (2, 0, 0) -> withResponse response $ callback HttpStatus200OK
+      (2, 0, 6) -> withResponse response $ callback HttpStatus206PartialContent
+      otherCode -> throwChecked $ UnexpectedResponse uri otherCode
+
+removeHeader :: HTTP.HasHeaders a => HTTP.HeaderName -> a -> a
+removeHeader name h = HTTP.setHeaders h newHeaders
+  where
+    newHeaders = [ x | x@(HTTP.Header n _) <- HTTP.getHeaders h, name /= n ]
+
+{-------------------------------------------------------------------------------
+  Auxiliary methods used to implement the HttpClient interface
+-------------------------------------------------------------------------------}
+
+withResponse :: Throws SomeRemoteError
+             => HTTP.Response BS.L.ByteString
+             -> ([HttpResponseHeader] -> BodyReader -> IO a)
+             -> IO a
+withResponse response callback = wrapCustomEx $ do
+    br <- bodyReaderFromBS $ HTTP.rspBody response
+    callback responseHeaders $ wrapCustomEx br
+  where
+    responseHeaders = getResponseHeaders response
+
+{-------------------------------------------------------------------------------
+  Custom exception types
+-------------------------------------------------------------------------------}
+
+wrapCustomEx :: ( ( Throws UnexpectedResponse
+                  , Throws IOException
+                  ) => IO a)
+             -> (Throws SomeRemoteError => IO a)
+wrapCustomEx act = handleChecked (\(ex :: UnexpectedResponse) -> go ex)
+                 $ handleChecked (\(ex :: IOException)        -> go ex)
+                 $ act
+  where
+    go ex = throwChecked (SomeRemoteError ex)
+
+data UnexpectedResponse = UnexpectedResponse URI (Int, Int, Int)
+  deriving (Typeable)
+
+data InvalidProxy = InvalidProxy String
+  deriving (Typeable)
+
+instance Pretty UnexpectedResponse where
+  pretty (UnexpectedResponse uri code) = "Unexpected response " ++ show code
+                                      ++ "for " ++ show uri
+
+instance Pretty InvalidProxy where
+  pretty (InvalidProxy p) = "Invalid proxy " ++ show p
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show UnexpectedResponse
+deriving instance Show InvalidProxy
+instance Exception UnexpectedResponse where displayException = pretty
+instance Exception InvalidProxy where displayException = pretty
+#else
+instance Show UnexpectedResponse where show = pretty
+instance Show InvalidProxy where show = pretty
+instance Exception UnexpectedResponse
+instance Exception InvalidProxy
+#endif
+
+{-------------------------------------------------------------------------------
+  Additional operations
+-------------------------------------------------------------------------------}
+
+setProxy :: Browser -> ProxyConfig String -> IO ()
+setProxy browser proxyConfig = do
+    proxy <- case proxyConfig of
+      ProxyConfigNone  -> return HTTP.NoProxy
+      ProxyConfigAuto  -> HTTP.fetchProxy True
+      ProxyConfigUse p -> case HTTP.parseProxy p of
+                             Nothing -> throwUnchecked $ InvalidProxy p
+                             Just p' -> return p'
+    withBrowser browser $ HTTP.setProxy (emptyAsNone proxy)
+  where
+    emptyAsNone :: HTTP.Proxy -> HTTP.Proxy
+    emptyAsNone (HTTP.Proxy uri _) | null uri = HTTP.NoProxy
+    emptyAsNone p = p
+
+setOutHandler :: Browser -> (String -> IO ()) -> IO ()
+setOutHandler browser = withBrowser browser . HTTP.setOutHandler
+
+setErrHandler :: Browser -> (String -> IO ()) -> IO ()
+setErrHandler browser = withBrowser browser . HTTP.setErrHandler
+
+-- | Execute a single request
+request :: Throws IOException
+        => Browser
+        -> HTTP.Request BS.L.ByteString
+        -> IO (HTTP.Response BS.L.ByteString)
+request browser = checkIO . liftM snd . withBrowser browser . HTTP.request
+
+{-------------------------------------------------------------------------------
+  Browser state
+-------------------------------------------------------------------------------}
+
+type LazyStream = HTTP.HandleStream BS.L.ByteString
+
+data Browser = Browser {
+    browserState :: MVar (HTTP.BrowserState LazyStream)
+  }
+
+-- | Run a browser action
+--
+-- IMPLEMENTATION NOTE: the 'browse' action doesn't itself create any
+-- connections, they are created on demand; we just need to make sure to carry
+-- this state from one invocation of 'browse' to another.
+withBrowser :: forall a. Browser -> HTTP.BrowserAction LazyStream a -> IO a
+withBrowser Browser{..} act = modifyMVar browserState $ \bst -> HTTP.browse $ do
+    State.put bst
+    result <- act
+    bst'   <- State.get
+    return (bst', result)
+
+-- | Initial browser state
+--
+-- Throws an 'InvalidProxy' exception if the proxy definition is invalid.
+--
+-- TODO: If the proxy configuration is automatic, the _only_ way that we can
+-- find out from the @HTTP@ library is to pass @True@ as the argument to
+-- 'fetchProxy'; but this prints to standard error when the proxy is invalid,
+-- rather than throwing an exception :-O
+browserInit :: IO Browser
+browserInit = do
+    browserState <- newMVar =<< HTTP.browse State.get
+    return Browser{..}
+
+-- | Cleanup browser state
+--
+-- NOTE: Calling 'withBrowser' after 'browserCleanup' will result in deadlock.
+--
+-- IMPLEMENTATION NOTE: "HTTP" does not provide any explicit API for resource
+-- cleanup, so we can only rely on the garbage collector to do for us.
+browserCleanup :: Browser -> IO ()
+browserCleanup Browser{..} = void $ takeMVar browserState
+
+{-------------------------------------------------------------------------------
+  HTTP auxiliary
+-------------------------------------------------------------------------------}
+
+hAcceptRanges :: HTTP.HeaderName
+hAcceptRanges = HTTP.HdrCustom "Accept-Ranges"
+
+setRange :: HTTP.HasHeaders a => Int -> Int -> a -> a
+setRange from to = HTTP.insertHeader HTTP.HdrRange rangeHeader
+  where
+    -- Content-Range header uses inclusive rather than exclusive bounds
+    -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>
+    rangeHeader = "bytes=" ++ show from ++ "-" ++ show (to - 1)
+
+setRequestHeaders :: HTTP.HasHeaders a => [HttpRequestHeader] -> a -> a
+setRequestHeaders =
+    foldr (.) id . map (uncurry HTTP.insertHeader) . trOpt []
+  where
+    trOpt :: [(HTTP.HeaderName, [String])]
+          -> [HttpRequestHeader]
+          -> [(HTTP.HeaderName, String)]
+    trOpt acc [] =
+      concatMap finalizeHeader acc
+    trOpt acc (HttpRequestMaxAge0:os) =
+      trOpt (insert HTTP.HdrCacheControl ["max-age=0"] acc) os
+    trOpt acc (HttpRequestNoTransform:os) =
+      trOpt (insert HTTP.HdrCacheControl ["no-transform"] acc) os
+
+    -- Some headers are comma-separated, others need multiple headers for
+    -- multiple options.
+    --
+    -- TODO: Right we we just comma-separate all of them.
+    finalizeHeader :: (HTTP.HeaderName, [String]) -> [(HTTP.HeaderName, String)]
+    finalizeHeader (name, strs) = [(name, intercalate ", " (reverse strs))]
+
+    insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]
+    insert x y = Lens.modify (Lens.lookupM x) (++ y)
+
+getResponseHeaders :: HTTP.Response a -> [HttpResponseHeader]
+getResponseHeaders response = concat [
+    -- Check the @Accept-Ranges@ header.
+    --
+    -- @Accept-Ranges@ takes a _single_ argument, but there might potentially
+    -- be more than one of them (although the spec does not explicitly say so).
+
+    -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.5>
+    -- and <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12>
+    [ HttpResponseAcceptRangesBytes
+    | "bytes" `elem` map HTTP.hdrValue (HTTP.retrieveHeaders hAcceptRanges response)
+    ]
+  ]
diff --git a/hackage-security/hackage-security-curl/LICENSE b/hackage-security/hackage-security-curl/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-curl/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-security-curl/Setup.hs b/hackage-security/hackage-security-curl/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-curl/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-security-curl/hackage-security-curl.cabal b/hackage-security/hackage-security-curl/hackage-security-curl.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-curl/hackage-security-curl.cabal
@@ -0,0 +1,33 @@
+name:                hackage-security-curl
+version:             0.1.0.0
+synopsis:            Hackage security bindings against curl (and other external downloaders)
+homepage:            http://github.com/well-typed/hackage-security/
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+library
+  exposed-modules:     Hackage.Security.Client.Repository.HttpLib.Curl
+  build-depends:       base        >= 4.4,
+                       bytestring  >= 0.9,
+                       process     >= 1.1,
+                       hackage-security
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+  -- See comments in hackage-security.cabal
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
diff --git a/hackage-security/hackage-security-curl/src/Hackage/Security/Client/Repository/HttpLib/Curl.hs b/hackage-security/hackage-security-curl/src/Hackage/Security/Client/Repository/HttpLib/Curl.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-curl/src/Hackage/Security/Client/Repository/HttpLib/Curl.hs
@@ -0,0 +1,70 @@
+-- | Implementation of 'HttpClient' using external downloader
+module Hackage.Security.Client.Repository.HttpLib.Curl (
+    withClient
+  ) where
+
+import Control.Monad
+import Network.URI
+import System.Process
+import System.IO
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy.Internal as BS.L
+
+import Hackage.Security.Client.Repository.HttpLib
+
+{-------------------------------------------------------------------------------
+  Top-level API
+-------------------------------------------------------------------------------}
+
+-- | HttpClient using external 'curl' executable
+--
+-- This is currently just a proof of concept. With a bit of luck we'll be
+-- able to reuse most of https://github.com/haskell/cabal/pull/2613 for a
+-- more serious implementation. Current TODOs:
+--
+-- * Set HTTP request headers in 'get'
+-- * Get HTTP response headers in 'get'
+-- * Support range requests
+-- * Deal with proxy settings
+-- * Configure logging
+--
+-- Probably we will want to add a @Browser@ or @Manager@ like abstraction (see
+-- @hackage-security-HTTP@ and @hackage-security-http-client@, respectively)
+-- in order to allow to change settings as we go.
+withClient :: (HttpLib -> IO a) -> IO a
+withClient callback = do
+    callback HttpLib {
+      httpGet      = get
+    , httpGetRange = undefined -- TODO: support range requests
+    }
+
+{-------------------------------------------------------------------------------
+  Implementation of the individual methods
+-------------------------------------------------------------------------------}
+
+get :: [HttpRequestHeader] -> URI
+    -> ([HttpResponseHeader] -> BodyReader -> IO a)
+    -> IO a
+get _httpOpts uri callback = do
+    (Nothing, Just hOut, Nothing, hProc) <- createProcess curl
+    callback [] (bodyReader hProc hOut)
+  where
+    curl :: CreateProcess
+    curl = (proc "curl" [show uri]) { std_out = CreatePipe }
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Construct a body reader for a process
+--
+-- TODO: We should also deal with stderr
+-- TODO: Perhaps this should be in the main library, alongside bodyReaderFromBS.
+bodyReader :: ProcessHandle -> Handle -> BodyReader
+bodyReader hProc hOut = do
+    chunk <- BS.hGetSome hOut BS.L.smallChunkSize
+    when (BS.null chunk) $ do
+      _exitCode <- waitForProcess hProc
+      -- TODO: Check for successful termination
+      return ()
+    return chunk
diff --git a/hackage-security/hackage-security-http-client/ChangeLog.md b/hackage-security/hackage-security-http-client/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-http-client/ChangeLog.md
@@ -0,0 +1,7 @@
+0.1.1
+-----
+* Implement updated HttpLib API from hackage-security 0.5
+
+0.1.0.0
+-------
+* Initial beta release
diff --git a/hackage-security/hackage-security-http-client/LICENSE b/hackage-security/hackage-security-http-client/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-http-client/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-security-http-client/Setup.hs b/hackage-security/hackage-security-http-client/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-http-client/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-security-http-client/hackage-security-http-client.cabal b/hackage-security/hackage-security-http-client/hackage-security-http-client.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-http-client/hackage-security-http-client.cabal
@@ -0,0 +1,39 @@
+name:                hackage-security-http-client
+version:             0.1.1
+synopsis:            Hackage security bindings for the http-client library
+homepage:            http://github.com/well-typed/hackage-security/
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+library
+  exposed-modules:     Hackage.Security.Client.Repository.HttpLib.HttpClient
+  build-depends:       base               >= 4.4,
+                       bytestring         >= 0.9,
+                       data-default-class >= 0.0,
+                       http-client        >= 0.4,
+                       http-types         >= 0.8,
+                       hackage-security   >= 0.5 && < 0.6
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  FlexibleContexts
+                       RankNTypes
+                       ScopedTypeVariables
+  other-extensions:    OverloadedStrings
+  ghc-options:         -Wall
+
+  -- see comments in hackage-security.cabal
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
diff --git a/hackage-security/hackage-security-http-client/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs b/hackage-security/hackage-security-http-client/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security-http-client/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs
@@ -0,0 +1,164 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Hackage.Security.Client.Repository.HttpLib.HttpClient (
+    withClient
+    -- ** Re-exports
+  , Manager -- opaque
+  ) where
+
+import Control.Exception
+import Data.ByteString (ByteString)
+import Data.Default.Class (def)
+import Network.URI
+import Network.HTTP.Client (Manager)
+import qualified Data.ByteString              as BS
+import qualified Data.ByteString.Char8        as BS.C8
+import qualified Network.HTTP.Client          as HttpClient
+import qualified Network.HTTP.Client.Internal as HttpClient
+import qualified Network.HTTP.Types           as HttpClient
+
+import Hackage.Security.Client hiding (Header)
+import Hackage.Security.Client.Repository.HttpLib
+import Hackage.Security.Util.Checked
+import qualified Hackage.Security.Util.Lens as Lens
+
+{-------------------------------------------------------------------------------
+  Top-level API
+-------------------------------------------------------------------------------}
+
+-- | Initialization
+--
+-- The proxy must be specified at initialization because @http-client@ does not
+-- allow to change the proxy once the 'Manager' is created.
+withClient :: ProxyConfig HttpClient.Proxy -> (Manager -> HttpLib -> IO a) -> IO a
+withClient proxyConfig callback = do
+    manager <- HttpClient.newManager (setProxy HttpClient.defaultManagerSettings)
+    callback manager HttpLib {
+        httpGet      = get      manager
+      , httpGetRange = getRange manager
+      }
+  where
+    setProxy = HttpClient.managerSetProxy $
+      case proxyConfig of
+        ProxyConfigNone  -> HttpClient.noProxy
+        ProxyConfigUse p -> HttpClient.useProxy p
+        ProxyConfigAuto  -> HttpClient.proxyEnvironment Nothing
+
+{-------------------------------------------------------------------------------
+  Individual methods
+-------------------------------------------------------------------------------}
+
+get :: Throws SomeRemoteError
+    => Manager
+    -> [HttpRequestHeader] -> URI
+    -> ([HttpResponseHeader] -> BodyReader -> IO a)
+    -> IO a
+get manager reqHeaders uri callback = wrapCustomEx $ do
+    -- TODO: setUri fails under certain circumstances; in particular, when
+    -- the URI contains URL auth. Not sure if this is a concern.
+    request' <- HttpClient.setUri def uri
+    let request = setRequestHeaders reqHeaders
+                $ request'
+    checkHttpException $ HttpClient.withResponse request manager $ \response -> do
+      let br = wrapCustomEx $ HttpClient.responseBody response
+      callback (getResponseHeaders response) br
+
+getRange :: Throws SomeRemoteError
+         => Manager
+         -> [HttpRequestHeader] -> URI -> (Int, Int)
+         -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)
+         -> IO a
+getRange manager reqHeaders uri (from, to) callback = wrapCustomEx $ do
+    request' <- HttpClient.setUri def uri
+    let request = setRange from to
+                $ setRequestHeaders reqHeaders
+                $ request'
+    checkHttpException $ HttpClient.withResponse request manager $ \response -> do
+      let br = wrapCustomEx $ HttpClient.responseBody response
+      case () of
+         () | HttpClient.responseStatus response == HttpClient.partialContent206 ->
+           callback HttpStatus206PartialContent (getResponseHeaders response) br
+         () | HttpClient.responseStatus response == HttpClient.ok200 ->
+           callback HttpStatus200OK (getResponseHeaders response) br
+         _otherwise ->
+           throwChecked $ HttpClient.StatusCodeException
+                            (HttpClient.responseStatus    response)
+                            (HttpClient.responseHeaders   response)
+                            (HttpClient.responseCookieJar response)
+
+-- | Wrap custom exceptions
+--
+-- NOTE: The only other exception defined in @http-client@ is @TimeoutTriggered@
+-- but it is currently disabled <https://github.com/snoyberg/http-client/issues/116>
+wrapCustomEx :: (Throws HttpClient.HttpException => IO a)
+             -> (Throws SomeRemoteError => IO a)
+wrapCustomEx act = handleChecked (\(ex :: HttpClient.HttpException) -> go ex)
+                 $ act
+  where
+    go ex = throwChecked (SomeRemoteError ex)
+
+checkHttpException :: Throws HttpClient.HttpException => IO a -> IO a
+checkHttpException = handle $ \(ex :: HttpClient.HttpException) ->
+                       throwChecked ex
+
+{-------------------------------------------------------------------------------
+  http-client auxiliary
+-------------------------------------------------------------------------------}
+
+hAcceptRanges :: HttpClient.HeaderName
+hAcceptRanges = "Accept-Ranges"
+
+hAcceptEncoding :: HttpClient.HeaderName
+hAcceptEncoding = "Accept-Encoding"
+
+setRange :: Int -> Int
+         -> HttpClient.Request -> HttpClient.Request
+setRange from to req = req {
+      HttpClient.requestHeaders = (HttpClient.hRange, rangeHeader)
+                                : HttpClient.requestHeaders req
+    }
+  where
+    -- Content-Range header uses inclusive rather than exclusive bounds
+    -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>
+    rangeHeader = BS.C8.pack $ "bytes=" ++ show from ++ "-" ++ show (to - 1)
+
+-- | Set request headers
+setRequestHeaders :: [HttpRequestHeader]
+                  -> HttpClient.Request -> HttpClient.Request
+setRequestHeaders opts req = req {
+      HttpClient.requestHeaders = trOpt disallowCompressionByDefault opts
+    }
+  where
+    trOpt :: [(HttpClient.HeaderName, [ByteString])]
+          -> [HttpRequestHeader]
+          -> [HttpClient.Header]
+    trOpt acc [] =
+      concatMap finalizeHeader acc
+    trOpt acc (HttpRequestMaxAge0:os) =
+      trOpt (insert HttpClient.hCacheControl ["max-age=0"] acc) os
+    trOpt acc (HttpRequestNoTransform:os) =
+      trOpt (insert HttpClient.hCacheControl ["no-transform"] acc) os
+
+    -- disable content compression (potential security issue)
+    disallowCompressionByDefault :: [(HttpClient.HeaderName, [ByteString])]
+    disallowCompressionByDefault = [(hAcceptEncoding, [])]
+
+    -- Some headers are comma-separated, others need multiple headers for
+    -- multiple options.
+    --
+    -- TODO: Right we we just comma-separate all of them.
+    finalizeHeader :: (HttpClient.HeaderName, [ByteString])
+                   -> [HttpClient.Header]
+    finalizeHeader (name, strs) = [(name, BS.intercalate ", " (reverse strs))]
+
+    insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]
+    insert x y = Lens.modify (Lens.lookupM x) (++ y)
+
+-- | Extract the response headers
+getResponseHeaders :: HttpClient.Response a -> [HttpResponseHeader]
+getResponseHeaders response = concat [
+      [ HttpResponseAcceptRangesBytes
+      | (hAcceptRanges, "bytes") `elem` headers
+      ]
+    ]
+  where
+    headers = HttpClient.responseHeaders response
diff --git a/hackage-security/hackage-security/ChangeLog.md b/hackage-security/hackage-security/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/ChangeLog.md
@@ -0,0 +1,58 @@
+0.5.0.2
+-------
+* Use tar 0.5.0
+* Relax lower bound on directory
+
+0.5.0.1
+-------
+* Relaxed dependency bounds
+
+0.5.0.0
+-------
+* Treat deserialization errors as verification errors (#108, #75)
+* Avoid `Content-Length: 0` in GET requests (#103)
+* Fix bug in Trusted
+* Build tar-index incrementally (#22)
+* Generalize 'Repository' over the representation of downloaded remote files.
+* Update index incrementally by downloading delta of `.tar.gz` and writing only
+  tail of local `.tar` file (#101). Content compression no longer used.
+* Take a lock on the cache directory before updating it, and no longer use
+  atomic file ops (pointless since we now update some files incrementally)
+* Code refactoring/simplification.
+* Support for ed25519 >= 0.0.4
+* `downloadPackage` no longer takes a callback.
+* API for accessing the Hackage index contents changed; it should now be
+  easier for clients to do their own incremental updates should they wish
+  to do so.
+* Relies on tar >= 0.4.4
+* Removed obsolete option for downloading the compressed index (we now _always_
+  download the compressed index)
+* Path module now works on Windows (#118)
+* Dropped support for ghc 7.2
+* Replaced uses of Int with Int54, to make sure canonical JSON really is
+  canonical (#141).
+
+0.4.0.0
+-------
+* Allow clients to pass in their own time for expiry verification
+  (this is an API change hence the major version bump)
+* Export .Client.Formats (necessary to define new Repositories)
+* Start work on basic test framework
+
+0.3.0.0
+-------
+* Don't use compression for range requests (#101)
+* Download index.tar.gz, not index.tar, if range request fails (#99)
+* Minor change in the LogMessage type (hence the API version bumb)
+* Include ChangeLog.md in the tarball (#98)
+
+0.2.0.0
+-------
+* Allow for network-2.5 (rather than network-uri-2.6)
+* Use cryptohash rather than SHA
+* Various bugfixes
+* API change: introduce RepoOpts in the Remote repository
+
+0.1.0.0
+-------
+* Initial beta release
diff --git a/hackage-security/hackage-security/LICENSE b/hackage-security/hackage-security/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/hackage-security/Setup.hs b/hackage-security/hackage-security/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/hackage-security/hackage-security.cabal b/hackage-security/hackage-security/hackage-security.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/hackage-security.cabal
@@ -0,0 +1,232 @@
+name:                hackage-security
+version:             0.5.0.2
+synopsis:            Hackage security library
+description:         The hackage security library provides both server and
+                     client utilities for securing the Hackage package server
+                     (<http://hackage.haskell.org/>).  It is based on The Update
+                     Framework (<http://theupdateframework.com/>), a set of
+                     recommendations developed by security researchers at
+                     various universities in the US as well as developers on the
+                     Tor project (<https://www.torproject.org/>).
+                     .
+                     The current implementation supports only index signing,
+                     thereby enabling untrusted mirrors. It does not yet provide
+                     facilities for author package signing.
+                     .
+                     The library has two main entry points:
+                     "Hackage.Security.Client" is the main entry point for
+                     clients (the typical example being @cabal@), and
+                     "Hackage.Security.Server" is the main entry point for
+                     servers (the typical example being @hackage-server@).
+                     .
+                     This is a beta release.
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+homepage:            https://github.com/well-typed/hackage-security
+bug-reports:         https://github.com/well-typed/hackage-security/issues
+build-type:          Simple
+cabal-version:       >=1.10
+
+extra-source-files:
+  ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/well-typed/hackage-security.git
+
+flag base48
+  description: Are we using base 4.8 or later?
+  manual: False
+
+flag use-network-uri
+  description: Are we using network-uri?
+  manual: False
+
+library
+  -- Most functionality is exported through the top-level entry points .Client
+  -- and .Server; the other exported modules are intended for qualified imports.
+  exposed-modules:     Hackage.Security.Client
+                       Hackage.Security.Client.Formats
+                       Hackage.Security.Client.Repository
+                       Hackage.Security.Client.Repository.Cache
+                       Hackage.Security.Client.Repository.Local
+                       Hackage.Security.Client.Repository.Remote
+                       Hackage.Security.Client.Repository.HttpLib
+                       Hackage.Security.Client.Verify
+                       Hackage.Security.JSON
+                       Hackage.Security.Key.Env
+                       Hackage.Security.Server
+                       Hackage.Security.Trusted
+                       Hackage.Security.TUF.FileMap
+                       Hackage.Security.Util.Checked
+                       Hackage.Security.Util.IO
+                       Hackage.Security.Util.Lens
+                       Hackage.Security.Util.Path
+                       Hackage.Security.Util.Pretty
+                       Hackage.Security.Util.Some
+                       Text.JSON.Canonical
+  other-modules:       Hackage.Security.Key
+                       Hackage.Security.Trusted.TCB
+                       Hackage.Security.TUF
+                       Hackage.Security.TUF.Common
+                       Hackage.Security.TUF.FileInfo
+                       Hackage.Security.TUF.Header
+                       Hackage.Security.TUF.Layout.Cache
+                       Hackage.Security.TUF.Layout.Index
+                       Hackage.Security.TUF.Layout.Repo
+                       Hackage.Security.TUF.Mirrors
+                       Hackage.Security.TUF.Paths
+                       Hackage.Security.TUF.Patterns
+                       Hackage.Security.TUF.Root
+                       Hackage.Security.TUF.Signed
+                       Hackage.Security.TUF.Snapshot
+                       Hackage.Security.TUF.Targets
+                       Hackage.Security.TUF.Timestamp
+                       Hackage.Security.Util.Base64
+                       Hackage.Security.Util.JSON
+                       Hackage.Security.Util.Stack
+                       Hackage.Security.Util.TypedEmbedded
+                       Prelude
+  -- We support ghc 7.4 (bundled with Cabal 1.14) and up
+  build-depends:       base              >= 4.5     && < 5,
+                       base64-bytestring >= 1.0     && < 1.1,
+                       bytestring        >= 0.9     && < 0.11,
+                       Cabal             >= 1.14    && < 1.26,
+                       containers        >= 0.4     && < 0.6,
+                       directory         >= 1.1.0.2 && < 1.3,
+                       ed25519           >= 0.0     && < 0.1,
+                       filepath          >= 1.2     && < 1.5,
+                       mtl               >= 2.2     && < 2.3,
+                       parsec            >= 3.1     && < 3.2,
+                       cryptohash        >= 0.11    && < 0.12,
+                       -- 0.4.2 introduces TarIndex, 0.4.4 introduces more
+                       -- functionality, 0.5.0 changes type of serialise
+                       tar               >= 0.5     && < 0.6,
+                       time              >= 1.2     && < 1.7,
+                       transformers      >= 0.4     && < 0.6,
+                       zlib              >= 0.5     && < 0.7,
+                       -- whatever versions are bundled with ghc:
+                       template-haskell,
+                       ghc-prim
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  DefaultSignatures
+                       DeriveDataTypeable
+                       DeriveFunctor
+                       FlexibleContexts
+                       FlexibleInstances
+                       GADTs
+                       GeneralizedNewtypeDeriving
+                       KindSignatures
+                       MultiParamTypeClasses
+                       NamedFieldPuns
+                       NoMonomorphismRestriction
+                       RankNTypes
+                       RecordWildCards
+                       ScopedTypeVariables
+                       StandaloneDeriving
+                       TupleSections
+                       TypeFamilies
+                       TypeOperators
+                       ViewPatterns
+  other-extensions:    BangPatterns
+                       CPP
+                       OverlappingInstances
+                       PackageImports
+                       UndecidableInstances
+  -- use the new stage1/cross-compile-friendly Quotes subset of TH for new GHCs
+  if impl(ghc >= 8.0)
+    -- place holder until Hackage allows to edit in the new extension token
+    -- other-extensions: TemplateHaskellQuotes
+    other-extensions:
+  else
+    other-extensions: TemplateHaskell
+
+  ghc-options:         -Wall
+
+  if flag(base48)
+    build-depends: base >= 4.8
+  else
+    build-depends: old-locale >= 1.0
+
+  -- The URI type got split out off the network package after version 2.5, and
+  -- moved to a separate network-uri package. Since we don't need the rest of
+  -- network here, it would suffice to rely only on network-uri:
+  --
+  -- > if flag(use-network-uri)
+  -- >   build-depends: network-uri >= 2.6 && < 2.7
+  -- > else
+  -- >   build-depends: network     >= 2.5 && < 2.6
+  --
+  -- However, if we did the same in hackage-security-HTTP, Cabal would consider
+  -- those two flag choices (hackage-security:use-network-uri and
+  -- hackage-security-HTTP:use-network-uri) to be completely independent; but
+  -- they aren't: if it links hackage-security against network-uri and
+  -- hackage-security-HTTP against network, we will get type errors when
+  -- hackage-security-HTTP tries to pass a URI to hackage-security.
+  --
+  -- It might seem we can solve this problem by re-exporting the URI type in
+  -- hackage-security and avoid the dependency in hackage-security-HTTP
+  -- altogether. However, this merely shifts the problem: hackage-security-HTTP
+  -- relies on the HTTP library which--surprise!--makes the same choice between
+  -- depending on network or network-uri. Cabal will not notice that we cannot
+  -- build hackage-security and hackage-security-HTTP against network-uri but
+  -- HTTP against network.
+  --
+  -- We solve the problem by explicitly relying on network-2.6 when choosing
+  -- network-uri. This dependency is redundant, strictly speaking. However, it
+  -- serves as a proxy for forcing flag choices: since all packages in a
+  -- solution must be linked against the same version of network, having one
+  -- version of network in one branch of the conditional and another version of
+  -- network in the other branch forces the choice to be consistent throughout.
+  -- (Note that the HTTP library does the same thing, though in this case the
+  -- dependency in network is not redundant.)
+  if flag(use-network-uri)
+    build-depends: network-uri >= 2.6 && < 2.7,
+                   network     >= 2.6 && < 2.7
+  else
+    build-depends: network     >= 2.5 && < 2.6
+
+  if impl(ghc >= 7.8)
+     other-extensions: RoleAnnotations
+
+  if impl(ghc >= 7.10)
+     other-extensions: AllowAmbiguousTypes
+--                       StaticPointers
+-- ^^^ Temporarily disabled because Hackage doesn't know yet about this
+-- extension and will therefore reject this package.
+
+test-suite TestSuite
+  type:                exitcode-stdio-1.0
+  main-is:             TestSuite.hs
+  other-modules:       TestSuite.InMemCache
+                       TestSuite.InMemRepo
+                       TestSuite.InMemRepository
+                       TestSuite.PrivateKeys
+                       TestSuite.Util.StrictMVar
+  build-depends:       base,
+                       Cabal,
+                       containers,
+                       HUnit,
+                       bytestring,
+                       hackage-security,
+                       network-uri,
+                       tar,
+                       tasty,
+                       tasty-hunit,
+                       temporary,
+                       time,
+                       zlib
+  hs-source-dirs:      tests
+  default-language:    Haskell2010
+  default-extensions:  FlexibleContexts
+                       GADTs
+                       KindSignatures
+                       RankNTypes
+                       RecordWildCards
+                       ScopedTypeVariables
+  ghc-options:         -Wall
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client.hs b/hackage-security/hackage-security/src/Hackage/Security/Client.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client.hs
@@ -0,0 +1,1031 @@
+{-# LANGUAGE CPP #-}
+#if __GLASGOW_HASKELL__ >= 710
+{-# LANGUAGE StaticPointers #-}
+#endif
+-- | Main entry point into the Hackage Security framework for clients
+module Hackage.Security.Client (
+    -- * Checking for updates
+    checkForUpdates
+  , HasUpdates(..)
+    -- * Downloading targets
+  , downloadPackage
+  , downloadPackage'
+    -- * Access to the Hackage index
+  , Directory(..)
+  , DirectoryEntry(..)
+  , getDirectory
+  , IndexFile(..)
+  , IndexEntry(..)
+  , IndexCallbacks(..)
+  , withIndex
+    -- * Bootstrapping
+  , requiresBootstrap
+  , bootstrap
+    -- * Re-exports
+  , module Hackage.Security.TUF
+  , module Hackage.Security.Key
+  , trusted
+    -- ** We only a few bits from .Repository
+    -- TODO: Maybe this is a sign that these should be in a different module?
+  , Repository -- opaque
+  , DownloadedFile(..)
+  , SomeRemoteError(..)
+  , LogMessage(..)
+    -- * Exceptions
+  , uncheckClientErrors
+  , VerificationError(..)
+  , VerificationHistory
+  , RootUpdated(..)
+  , InvalidPackageException(..)
+  , InvalidFileInIndex(..)
+  , LocalFileCorrupted(..)
+  ) where
+
+import Prelude hiding (log)
+import Control.Arrow (first)
+import Control.Exception
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.List (sortBy)
+import Data.Maybe (isNothing)
+import Data.Ord (comparing)
+import Data.Time
+import Data.Traversable (for)
+import Data.Typeable (Typeable)
+import qualified Codec.Archive.Tar          as Tar
+import qualified Codec.Archive.Tar.Entry    as Tar
+import qualified Codec.Archive.Tar.Index    as Tar
+import qualified Data.ByteString.Lazy       as BS.L
+import qualified Data.ByteString.Lazy.Char8 as BS.L.C8
+
+import Distribution.Package (PackageIdentifier)
+import Distribution.Text (display)
+
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Verify
+import Hackage.Security.JSON
+import Hackage.Security.Key
+import Hackage.Security.Key.Env (KeyEnv)
+import Hackage.Security.Trusted
+import Hackage.Security.Trusted.TCB
+import Hackage.Security.TUF
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.Stack
+import qualified Hackage.Security.Key.Env as KeyEnv
+
+{-------------------------------------------------------------------------------
+  Checking for updates
+-------------------------------------------------------------------------------}
+
+data HasUpdates = HasUpdates | NoUpdates
+  deriving (Show, Eq, Ord)
+
+-- | Generic logic for checking if there are updates
+--
+-- This implements the logic described in Section 5.1, "The client application",
+-- of the TUF spec. It checks which of the server metadata has changed, and
+-- downloads all changed metadata to the local cache. (Metadata here refers
+-- both to the TUF security metadata as well as the Hackage packge index.)
+--
+-- You should pass @Nothing@ for the UTCTime _only_ under exceptional
+-- circumstances (such as when the main server is down for longer than the
+-- expiry dates used in the timestamp files on mirrors).
+checkForUpdates :: (Throws VerificationError, Throws SomeRemoteError)
+                => Repository down
+                -> Maybe UTCTime -- ^ To check expiry times against (if using)
+                -> IO HasUpdates
+checkForUpdates rep@Repository{..} mNow =
+    withMirror rep $ limitIterations []
+  where
+    -- More or less randomly chosen maximum iterations
+    -- See <https://github.com/theupdateframework/tuf/issues/287>.
+    maxNumIterations :: Int
+    maxNumIterations = 5
+
+    -- The spec stipulates that on a verification error we must download new
+    -- root information and start over. However, in order to prevent DoS attacks
+    -- we limit how often we go round this loop.
+    -- See als <https://github.com/theupdateframework/tuf/issues/287>.
+    limitIterations :: (Throws VerificationError, Throws SomeRemoteError)
+                    => VerificationHistory -> IO HasUpdates
+    limitIterations history | length history >= maxNumIterations =
+        throwChecked $ VerificationErrorLoop (reverse history)
+    limitIterations history = do
+        -- Get all cached info
+        --
+        -- NOTE: Although we don't normally update any cached files until the
+        -- whole verification process successfully completes, in case of a
+        -- verification error, or in case of a regular update of the root info,
+        -- we DO update the local files. Hence, we must re-read all local files
+        -- on each iteration.
+        cachedInfo <- getCachedInfo rep
+
+        mHasUpdates <- tryChecked -- catch RootUpdated
+                     $ tryChecked -- catch VerificationError
+                     $ runVerify repLockCache
+                     $ go attemptNr cachedInfo
+        case mHasUpdates of
+          Left ex -> do
+            -- NOTE: This call to updateRoot is not itself protected by an
+            -- exception handler, and may therefore throw a VerificationError.
+            -- This is intentional: if we get verification errors during the
+            -- update process, _and_ we cannot update the main root info, then
+            -- we cannot do anything.
+            log rep $ LogVerificationError ex
+            let history'   = Right ex : history
+                attemptNr' = attemptNr + 1
+            updateRoot rep mNow attemptNr' cachedInfo (Left ex)
+            limitIterations history'
+          Right (Left RootUpdated) -> do
+            log rep $ LogRootUpdated
+            let history' = Left RootUpdated : history
+            limitIterations history'
+          Right (Right hasUpdates) ->
+            return hasUpdates
+      where
+        attemptNr :: AttemptNr
+        attemptNr = fromIntegral $ length history
+
+    -- The 'Verify' monad only caches the downloaded files after verification.
+    -- See also <https://github.com/theupdateframework/tuf/issues/283>.
+    go :: Throws RootUpdated => AttemptNr -> CachedInfo -> Verify HasUpdates
+    go attemptNr cachedInfo@CachedInfo{..} = do
+      -- Get the new timestamp
+      newTS <- getRemoteFile' RemoteTimestamp
+      let newInfoSS = static timestampInfoSnapshot <$$> newTS
+
+      -- Check if the snapshot has changed
+      if not (fileChanged cachedInfoSnapshot newInfoSS)
+        then return NoUpdates
+        else do
+          -- Get the new snapshot
+          newSS <- getRemoteFile' (RemoteSnapshot newInfoSS)
+          let newInfoRoot    = static snapshotInfoRoot    <$$> newSS
+              newInfoMirrors = static snapshotInfoMirrors <$$> newSS
+              newInfoTarGz   = static snapshotInfoTarGz   <$$> newSS
+              mNewInfoTar    = trustElems (static snapshotInfoTar <$$> newSS)
+
+          -- If root metadata changed, download and restart
+          when (rootChanged cachedInfoRoot newInfoRoot) $ liftIO $ do
+            updateRoot rep mNow attemptNr cachedInfo (Right newInfoRoot)
+            -- By throwing 'RootUpdated' as an exception we make sure that
+            -- any files previously downloaded (to temporary locations)
+            -- will not be cached.
+            throwChecked RootUpdated
+
+          -- If mirrors changed, download and verify
+          when (fileChanged cachedInfoMirrors newInfoMirrors) $
+            newMirrors =<< getRemoteFile' (RemoteMirrors newInfoMirrors)
+
+          -- If index changed, download and verify
+          when (fileChanged cachedInfoTarGz newInfoTarGz) $
+            updateIndex newInfoTarGz mNewInfoTar
+
+          return HasUpdates
+      where
+        getRemoteFile' :: ( VerifyRole a
+                          , FromJSON ReadJSON_Keys_Layout (Signed a)
+                          )
+                       => RemoteFile (f :- ()) Metadata -> Verify (Trusted a)
+        getRemoteFile' = liftM fst . getRemoteFile rep cachedInfo attemptNr mNow
+
+        -- Update the index and check against the appropriate hash
+        updateIndex :: Trusted FileInfo         -- info about @.tar.gz@
+                    -> Maybe (Trusted FileInfo) -- info about @.tar@
+                    -> Verify ()
+        updateIndex newInfoTarGz Nothing = do
+          (targetPath, tempPath) <- getRemote' rep attemptNr $
+            RemoteIndex (HFZ FGz) (FsGz newInfoTarGz)
+          verifyFileInfo' (Just newInfoTarGz) targetPath tempPath
+        updateIndex newInfoTarGz (Just newInfoTar) = do
+          (format, targetPath, tempPath) <- getRemote rep attemptNr $
+            RemoteIndex (HFS (HFZ FGz)) (FsUnGz newInfoTar newInfoTarGz)
+          case format of
+            Some FGz -> verifyFileInfo' (Just newInfoTarGz) targetPath tempPath
+            Some FUn -> verifyFileInfo' (Just newInfoTar)   targetPath tempPath
+
+    -- Unlike for other files, if we didn't have an old snapshot, consider the
+    -- root info unchanged (otherwise we would loop indefinitely).
+    -- See also <https://github.com/theupdateframework/tuf/issues/286>
+    rootChanged :: Maybe (Trusted FileInfo) -> Trusted FileInfo -> Bool
+    rootChanged Nothing    _   = False
+    rootChanged (Just old) new = not (trustedFileInfoEqual old new)
+
+    -- For any file other than the root we consider the file to have changed
+    -- if we do not yet have a local snapshot to tell us the old info.
+    fileChanged :: Maybe (Trusted FileInfo) -> Trusted FileInfo -> Bool
+    fileChanged Nothing    _   = True
+    fileChanged (Just old) new = not (trustedFileInfoEqual old new)
+
+    -- We don't actually _do_ anything with the mirrors file until the next call
+    -- to 'checkUpdates', because we want to use a single server for a single
+    -- check-for-updates request. If validation was successful the repository
+    -- will have cached the mirrors file and it will be available on the next
+    -- request.
+    newMirrors :: Trusted Mirrors -> Verify ()
+    newMirrors _ = return ()
+
+-- | Update the root metadata
+--
+-- Note that the new root metadata is verified using the old root metadata,
+-- and only then trusted.
+--
+-- We don't always have root file information available. If we notice during
+-- the normal update process that the root information has changed then the
+-- snapshot will give us the new file information; but if we need to update
+-- the root information due to a verification error we do not.
+--
+-- We additionally delete the cached cached snapshot and timestamp. This is
+-- necessary for two reasons:
+--
+-- 1. If during the normal update process we notice that the root info was
+--    updated (because the hash of @root.json@ in the new snapshot is different
+--    from the old snapshot) we download new root info and start over, without
+--    (yet) downloading a (potential) new index. This means it is important that
+--    we not overwrite our local cached snapshot, because if we did we would
+--    then on the next iteration conclude there were no updates and we would
+--    fail to notice that we should have updated the index. However, unless we
+--    do something, this means that we would conclude on the next iteration once
+--    again that the root info has changed (because the hash in the new shapshot
+--    still doesn't match the hash in the cached snapshot), and we would loop
+--    until we throw a 'VerificationErrorLoop' exception. By deleting the local
+--    snapshot we basically reset the client to its initial state, and we will
+--    not try to download the root info once again. The only downside of this is
+--    that we will also re-download the index after every root info change.
+--    However, this should be infrequent enough that this isn't an issue.
+--    See also <https://github.com/theupdateframework/tuf/issues/285>.
+--
+-- 2. Additionally, deleting the local timestamp and snapshot protects against
+--    an attack where an attacker has set the file version of the snapshot or
+--    timestamp to MAX_INT, thereby making further updates impossible.
+--    (Such an attack would require a timestamp/snapshot key compromise.)
+--
+-- However, we _ONLY_ do this when the root information has actually changed.
+-- If we did this unconditionally it would mean that we delete the locally
+-- cached timestamp whenever the version on the remote timestamp is invalid,
+-- thereby rendering the file version on the timestamp and the snapshot useless.
+-- See <https://github.com/theupdateframework/tuf/issues/283#issuecomment-115739521>
+updateRoot :: (Throws VerificationError, Throws SomeRemoteError)
+           => Repository down
+           -> Maybe UTCTime
+           -> AttemptNr
+           -> CachedInfo
+           -> Either VerificationError (Trusted FileInfo)
+           -> IO ()
+updateRoot rep@Repository{..} mNow isRetry cachedInfo eFileInfo = do
+    rootReallyChanged <- runVerify repLockCache $ do
+      (_newRoot :: Trusted Root, rootTempFile) <- getRemoteFile
+        rep
+        cachedInfo
+        isRetry
+        mNow
+        (RemoteRoot (eitherToMaybe eFileInfo))
+
+      -- NOTE: It is important that we do this check within the evalContT,
+      -- because the temporary file will be deleted once we leave its scope.
+      case eFileInfo of
+        Right _ ->
+          -- We are downloading the root info because the hash in the snapshot
+          -- changed. In this case the root definitely changed.
+          return True
+        Left _e -> liftIO $ do
+          -- We are downloading the root because of a verification error. In
+          -- this case the root info may or may not have changed. In most cases
+          -- it would suffice to compare the file version now; however, in the
+          -- (exceptional) circumstance where the root info has changed but
+          -- the file version has not, this would result in the same infinite
+          -- loop described above. Hence, we must compare file hashes, and they
+          -- must be computed on the raw file, not the parsed file.
+          oldRootFile <- repGetCachedRoot
+          oldRootInfo <- DeclareTrusted <$> computeFileInfo oldRootFile
+          not <$> downloadedVerify rootTempFile oldRootInfo
+
+    when rootReallyChanged $ clearCache rep
+
+{-------------------------------------------------------------------------------
+  Convenience functions for downloading and parsing various files
+-------------------------------------------------------------------------------}
+
+data CachedInfo = CachedInfo {
+    cachedRoot         :: Trusted Root
+  , cachedKeyEnv       :: KeyEnv
+  , cachedTimestamp    :: Maybe (Trusted Timestamp)
+  , cachedSnapshot     :: Maybe (Trusted Snapshot)
+  , cachedMirrors      :: Maybe (Trusted Mirrors)
+  , cachedInfoSnapshot :: Maybe (Trusted FileInfo)
+  , cachedInfoRoot     :: Maybe (Trusted FileInfo)
+  , cachedInfoMirrors  :: Maybe (Trusted FileInfo)
+  , cachedInfoTarGz    :: Maybe (Trusted FileInfo)
+  }
+
+cachedVersion :: CachedInfo -> RemoteFile fs typ -> Maybe FileVersion
+cachedVersion CachedInfo{..} remoteFile =
+    case mustCache remoteFile of
+      CacheAs CachedTimestamp -> timestampVersion . trusted <$> cachedTimestamp
+      CacheAs CachedSnapshot  -> snapshotVersion  . trusted <$> cachedSnapshot
+      CacheAs CachedMirrors   -> mirrorsVersion   . trusted <$> cachedMirrors
+      CacheAs CachedRoot      -> Just . rootVersion . trusted $ cachedRoot
+      CacheIndex -> Nothing
+      DontCache  -> Nothing
+
+-- | Get all cached info (if any)
+getCachedInfo :: (Applicative m, MonadIO m) => Repository down -> m CachedInfo
+getCachedInfo rep = do
+    (cachedRoot, cachedKeyEnv) <- readLocalRoot rep
+    cachedTimestamp <- readLocalFile rep cachedKeyEnv CachedTimestamp
+    cachedSnapshot  <- readLocalFile rep cachedKeyEnv CachedSnapshot
+    cachedMirrors   <- readLocalFile rep cachedKeyEnv CachedMirrors
+
+    let cachedInfoSnapshot = fmap (static timestampInfoSnapshot <$$>) cachedTimestamp
+        cachedInfoRoot     = fmap (static snapshotInfoRoot      <$$>) cachedSnapshot
+        cachedInfoMirrors  = fmap (static snapshotInfoMirrors   <$$>) cachedSnapshot
+        cachedInfoTarGz    = fmap (static snapshotInfoTarGz     <$$>) cachedSnapshot
+
+    return CachedInfo{..}
+
+readLocalRoot :: MonadIO m => Repository down -> m (Trusted Root, KeyEnv)
+readLocalRoot rep = do
+    cachedPath <- liftIO $ repGetCachedRoot rep
+    signedRoot <- throwErrorsUnchecked LocalFileCorrupted =<<
+                    readCachedJSON rep KeyEnv.empty cachedPath
+    return (trustLocalFile signedRoot, rootKeys (signed signedRoot))
+
+readLocalFile :: ( FromJSON ReadJSON_Keys_Layout (Signed a)
+                 , MonadIO m, Applicative m
+                 )
+              => Repository down -> KeyEnv -> CachedFile -> m (Maybe (Trusted a))
+readLocalFile rep cachedKeyEnv file = do
+    mCachedPath <- liftIO $ repGetCached rep file
+    for mCachedPath $ \cachedPath -> do
+      signed <- throwErrorsUnchecked LocalFileCorrupted =<<
+                  readCachedJSON rep cachedKeyEnv cachedPath
+      return $ trustLocalFile signed
+
+getRemoteFile :: ( Throws VerificationError
+                 , Throws SomeRemoteError
+                 , VerifyRole a
+                 , FromJSON ReadJSON_Keys_Layout (Signed a)
+                 )
+              => Repository down
+              -> CachedInfo
+              -> AttemptNr
+              -> Maybe UTCTime
+              -> RemoteFile (f :- ()) Metadata
+              -> Verify (Trusted a, down Metadata)
+getRemoteFile rep@Repository{..} cachedInfo@CachedInfo{..} isRetry mNow file = do
+    (targetPath, tempPath) <- getRemote' rep isRetry file
+    verifyFileInfo' (remoteFileDefaultInfo file) targetPath tempPath
+    signed   <- throwErrorsChecked (VerificationErrorDeserialization targetPath) =<<
+                  readDownloadedJSON rep cachedKeyEnv tempPath
+    verified <- throwErrorsChecked id $ verifyRole
+                  cachedRoot
+                  targetPath
+                  (cachedVersion cachedInfo file)
+                  mNow
+                  signed
+    return (trustVerified verified, tempPath)
+
+{-------------------------------------------------------------------------------
+  Downloading target files
+-------------------------------------------------------------------------------}
+
+-- | Download a package
+downloadPackage :: ( Throws SomeRemoteError
+                   , Throws VerificationError
+                   , Throws InvalidPackageException
+                   )
+                => Repository down    -- ^ Repository
+                -> PackageIdentifier  -- ^ Package to download
+                -> Path Absolute      -- ^ Destination (see also 'downloadPackage'')
+                -> IO ()
+downloadPackage rep@Repository{..} pkgId dest =
+    withMirror rep $
+      withIndex rep $ \IndexCallbacks{..} -> runVerify repLockCache $ do
+        -- Get the metadata (from the previously updated index)
+        targetFileInfo <- liftIO $ indexLookupFileInfo pkgId
+
+        -- TODO: should we check if cached package available? (spec says no)
+        tarGz <- do
+          (targetPath, downloaded) <- getRemote' rep (AttemptNr 0) $
+            RemotePkgTarGz pkgId targetFileInfo
+          verifyFileInfo' (Just targetFileInfo) targetPath downloaded
+          return downloaded
+
+        -- If all checks succeed, copy file to its target location.
+        liftIO $ downloadedCopyTo tarGz dest
+
+-- | Variation on 'downloadPackage' that takes a FilePath instead.
+downloadPackage' :: ( Throws SomeRemoteError
+                    , Throws VerificationError
+                    , Throws InvalidPackageException
+                    )
+                 => Repository down    -- ^ Repository
+                 -> PackageIdentifier  -- ^ Package to download
+                 -> FilePath           -- ^ Destination
+                 -> IO ()
+downloadPackage' rep pkgId dest =
+    downloadPackage rep pkgId =<< makeAbsolute (fromFilePath dest)
+
+{-------------------------------------------------------------------------------
+  Access to the tar index (the API is exported and used internally)
+
+  NOTE: The files inside the index as evaluated lazily.
+
+  1. The index tarball contains delegated target.json files for both unsigned
+     and signed packages. We need to verify the signatures of all signed
+     metadata (that is: the metadata for signed packages).
+
+  2. Since the tarball also contains the .cabal files, we should also verify the
+     hashes of those .cabal files against the hashes recorded in signed metadata
+     (there is no point comparing against hashes recorded in unsigned metadata
+     because attackers could just change those).
+
+  Since we don't have author signing yet, we don't have any additional signed
+  metadata and therefore we currently don't have to do anything here.
+
+  TODO: If we have explicit, author-signed, lists of versions for a package (as
+  described in @README.md@), then evaluating these "middle-level" delegation
+  files lazily opens us up to a rollback attack: if we've never downloaded the
+  delegations for a package before, then we have nothing to compare the version
+  number in the file that we downloaded against. One option is to always
+  download and verify all these middle level files (strictly); other is to
+  include the version number of all of these files in the snapshot. This is
+  described in more detail in
+  <https://github.com/theupdateframework/tuf/issues/282#issuecomment-102468421>.
+
+  TODO: Currently we hardcode the location of the package specific metadata. By
+  rights we should read the global targets file and apply the delegation rules.
+  Until we have author signing however this is unnecessary.
+-------------------------------------------------------------------------------}
+
+-- | Index directory
+data Directory = Directory {
+    -- | The first entry in the dictionary
+    directoryFirst :: DirectoryEntry
+
+    -- | The next available (i.e., one after last) directory entry
+  , directoryNext :: DirectoryEntry
+
+    -- | Lookup an entry in the dictionary
+    --
+    -- This is an efficient operation.
+  , directoryLookup :: forall dec. IndexFile dec -> Maybe DirectoryEntry
+
+    -- | An enumeration of all entries
+    --
+    -- This field is lazily constructed, so if you don't need it, it does not
+    -- incur a performance overhead. Moreover, the 'IndexFile' is also created
+    -- lazily so if you only need the raw 'IndexPath' there is no parsing
+    -- overhead.
+    --
+    -- The entries are ordered by 'DirectoryEntry' so that the entries can
+    -- efficiently be read in sequence.
+    --
+    -- NOTE: This means that there are two ways to enumerate all entries in the
+    -- tar file, since when lookup an entry using 'indexLookupEntry' the
+    -- 'DirectoryEntry' of the next entry is also returned. However, this
+    -- involves reading through the entire @tar@ file. If you only need to read
+    -- /some/ files, it is significantly more efficient to enumerate the tar
+    -- entries using 'directoryEntries' instead and only call 'indexLookupEntry'
+    -- when required.
+  , directoryEntries :: [(DirectoryEntry, IndexPath, Maybe (Some IndexFile))]
+  }
+
+-- | Entry into the Hackage index.
+newtype DirectoryEntry = DirectoryEntry {
+    -- | (Low-level) block number of the tar index entry
+    --
+    -- Exposed for the benefit of clients who read the @.tar@ file directly.
+    -- For this reason also the 'Show' and 'Read' instances for 'DirectoryEntry'
+    -- just print and parse the underlying 'TarEntryOffset'.
+    directoryEntryBlockNo :: Tar.TarEntryOffset
+  }
+  deriving (Eq, Ord)
+
+instance Show DirectoryEntry where
+  show = show . directoryEntryBlockNo
+
+instance Read DirectoryEntry where
+  readsPrec p = map (first DirectoryEntry) . readsPrec p
+
+-- | Read the Hackage index directory
+--
+-- Should only be called after 'checkForUpdates'.
+getDirectory :: Repository down -> IO Directory
+getDirectory Repository{..} = mkDirectory <$> repGetIndexIdx
+  where
+    mkDirectory :: Tar.TarIndex -> Directory
+    mkDirectory idx = Directory {
+        directoryFirst   = DirectoryEntry 0
+      , directoryNext    = DirectoryEntry $ Tar.indexEndEntryOffset idx
+      , directoryLookup  = liftM dirEntry . Tar.lookup idx . filePath
+      , directoryEntries = map mkEntry $ sortBy (comparing snd) (Tar.toList idx)
+      }
+
+    mkEntry :: (FilePath, Tar.TarEntryOffset)
+            -> (DirectoryEntry, IndexPath, Maybe (Some IndexFile))
+    mkEntry (fp, off) = (DirectoryEntry off, path, indexFile path)
+      where
+        path = indexPath fp
+
+    dirEntry :: Tar.TarIndexEntry -> DirectoryEntry
+    dirEntry (Tar.TarFileEntry offset) = DirectoryEntry offset
+    dirEntry (Tar.TarDir _) = error "directoryLookup: unexpected directory"
+
+    indexFile :: IndexPath -> Maybe (Some IndexFile)
+    indexFile = indexFileFromPath repIndexLayout
+
+    indexPath :: FilePath -> IndexPath
+    indexPath = rootPath . fromUnrootedFilePath
+
+    filePath :: IndexFile dec -> FilePath
+    filePath = toUnrootedFilePath . unrootPath . indexFileToPath repIndexLayout
+
+-- | Entry from the Hackage index; see 'withIndex'.
+data IndexEntry dec = IndexEntry {
+    -- | The raw path in the tarfile
+    indexEntryPath :: IndexPath
+
+    -- | The parsed file (if recognised)
+  , indexEntryPathParsed :: Maybe (IndexFile dec)
+
+    -- | The raw contents
+    --
+    -- Although this is a lazy bytestring, this is actually read into memory
+    -- strictly (i.e., it can safely be used outside the scope of withIndex and
+    -- friends).
+  , indexEntryContent :: BS.L.ByteString
+
+    -- | The parsed contents
+    --
+    -- This field is lazily constructed; the parser is not unless you do a
+    -- pattern match on this value.
+  , indexEntryContentParsed :: Either SomeException dec
+
+    -- | The time of the entry in the tarfile.
+  , indexEntryTime :: Tar.EpochTime
+  }
+
+-- | Various operations that we can perform on the index once its open
+--
+-- Note that 'IndexEntry' contains a fields both for the raw file contents and
+-- the parsed file contents; clients can choose which to use.
+--
+-- In principle these callbacks will do verification (once we have implemented
+-- author signing). Right now they don't need to do that, because the index as a
+-- whole will have been verified.
+data IndexCallbacks = IndexCallbacks {
+    -- | Look up an entry by 'DirectoryEntry'
+    --
+    -- Since these 'DirectoryEntry's must come from somewhere (probably from the
+    -- 'Directory'), it is assumed that they are valid; if they are not, an
+    -- (unchecked) exception will be thrown.
+    --
+    -- This function also returns the 'DirectoryEntry' of the /next/ file in the
+    -- index (if any) for the benefit of clients who wish to walk through the
+    -- entire index.
+    indexLookupEntry :: DirectoryEntry
+                     -> IO (Some IndexEntry, Maybe DirectoryEntry)
+
+    -- | Look up an entry by 'IndexFile'
+    --
+    -- Returns 'Nothing' if the 'IndexFile' does not refer to an existing file.
+  , indexLookupFile :: forall dec.
+                       IndexFile dec
+                    -> IO (Maybe (IndexEntry dec))
+
+    -- | Variation if both the 'DirectoryEntry' and the 'IndexFile' are known
+    --
+    -- You might use this when scanning the index using 'directoryEntries'.
+  , indexLookupFileEntry :: forall dec.
+                            DirectoryEntry
+                         -> IndexFile dec
+                         -> IO (IndexEntry dec)
+
+    -- | Get (raw) cabal file (wrapper around 'indexLookupFile')
+  , indexLookupCabal :: Throws InvalidPackageException
+                     => PackageIdentifier
+                     -> IO (Trusted BS.L.ByteString)
+
+    -- | Lookup package metadata (wrapper around 'indexLookupFile')
+    --
+    -- This will throw an (unchecked) exception if the @targets.json@ file
+    -- could not be parsed.
+  , indexLookupMetadata :: Throws InvalidPackageException
+                        => PackageIdentifier
+                        -> IO (Trusted Targets)
+
+    -- | Get file info (including hash) (wrapper around 'indexLookupFile')
+  , indexLookupFileInfo :: ( Throws InvalidPackageException
+                           , Throws VerificationError
+                           )
+                        => PackageIdentifier
+                        -> IO (Trusted FileInfo)
+
+    -- | Get the SHA256 hash for a package (wrapper around 'indexLookupInfo')
+    --
+    -- In addition to the exceptions thrown by 'indexLookupInfo', this will also
+    -- throw an exception if the SHA256 is not listed in the 'FileMap' (again,
+    -- this will not happen with a well-formed Hackage index.)
+  , indexLookupHash :: ( Throws InvalidPackageException
+                       , Throws VerificationError
+                       )
+                    => PackageIdentifier
+                    -> IO (Trusted Hash)
+
+    -- | The 'Directory' for the index
+    --
+    -- We provide this here because 'withIndex' will have read this anyway.
+  , indexDirectory :: Directory
+  }
+
+-- | Look up entries in the Hackage index
+--
+-- This is in 'withFile' style so that clients can efficiently look up multiple
+-- files from the index.
+--
+-- Should only be called after 'checkForUpdates'.
+withIndex :: Repository down -> (IndexCallbacks -> IO a) -> IO a
+withIndex rep@Repository{..} callback = do
+    -- We need the cached root information in order to resolve key IDs and
+    -- verify signatures. Note that whenever we read a JSON file, we verify
+    -- signatures (even if we don't verify the keys); if this is a problem
+    -- (for performance) we need to parameterize parseJSON.
+    (_cachedRoot, keyEnv) <- readLocalRoot rep
+
+    -- We need the directory to resolve 'IndexFile's and to know the index of
+    -- the last entry.
+    dir@Directory{..} <- getDirectory rep
+
+    -- Open the index
+    repWithIndex $ \h -> do
+      let getEntry :: DirectoryEntry
+                   -> IO (Some IndexEntry, Maybe DirectoryEntry)
+          getEntry entry = do
+            (tarEntry, content, next) <- getTarEntry entry
+            let path = indexPath tarEntry
+            case indexFile path of
+              Nothing ->
+                return (Some (mkEntry tarEntry content Nothing), next)
+              Just (Some file) ->
+                return (Some (mkEntry tarEntry content (Just file)), next)
+
+          getFile :: IndexFile dec -> IO (Maybe (IndexEntry dec))
+          getFile file =
+            case directoryLookup file of
+              Nothing       -> return Nothing
+              Just dirEntry -> Just <$> getFileEntry dirEntry file
+
+          getFileEntry :: DirectoryEntry
+                       -> IndexFile dec
+                       -> IO (IndexEntry dec)
+          getFileEntry dirEntry file = do
+            (tarEntry, content, _next) <- getTarEntry dirEntry
+            return $ mkEntry tarEntry content (Just file)
+
+          mkEntry :: Tar.Entry
+                  -> BS.L.ByteString
+                  -> Maybe (IndexFile dec)
+                  -> IndexEntry dec
+          mkEntry tarEntry content mFile = IndexEntry {
+              indexEntryPath          = indexPath tarEntry
+            , indexEntryPathParsed    = mFile
+            , indexEntryContent       = content
+            , indexEntryContentParsed = parseContent mFile content
+            , indexEntryTime          = Tar.entryTime tarEntry
+            }
+
+          parseContent :: Maybe (IndexFile dec)
+                       -> BS.L.ByteString -> Either SomeException dec
+          parseContent Nothing     _   = Left pathNotRecognized
+          parseContent (Just file) raw = case file of
+            IndexPkgPrefs _ ->
+              Right () -- We don't currently parse preference files
+            IndexPkgCabal _ ->
+              Right () -- We don't currently parse .cabal files
+            IndexPkgMetadata _ ->
+              let mkEx = either
+                           (Left . SomeException . InvalidFileInIndex file raw)
+                           Right
+              in mkEx $ parseJSON_Keys_NoLayout keyEnv raw
+
+          -- Read an entry from the tar file. Returns entry content separately,
+          -- throwing an exception if the entry is not a regular file.
+          -- Also throws an exception if the 'DirectoryEntry' is invalid.
+          getTarEntry :: DirectoryEntry
+                      -> IO (Tar.Entry, BS.L.ByteString, Maybe DirectoryEntry)
+          getTarEntry (DirectoryEntry offset) = do
+            entry   <- Tar.hReadEntry h offset
+            content <- case Tar.entryContent entry of
+                         Tar.NormalFile content _sz -> return content
+                         _ -> throwIO $ userError "withIndex: unexpected entry"
+            let next  = DirectoryEntry $ Tar.nextEntryOffset entry offset
+                mNext = guard (next < directoryNext) >> return next
+            return (entry, content, mNext)
+
+          -- Get cabal file
+          getCabal :: Throws InvalidPackageException
+                   => PackageIdentifier -> IO (Trusted BS.L.ByteString)
+          getCabal pkgId = do
+            mCabal <- getFile $ IndexPkgCabal pkgId
+            case mCabal of
+              Nothing ->
+                throwChecked $ InvalidPackageException pkgId
+              Just IndexEntry{..} ->
+                return $ DeclareTrusted indexEntryContent
+
+          -- Get package metadata
+          getMetadata :: Throws InvalidPackageException
+                      => PackageIdentifier -> IO (Trusted Targets)
+          getMetadata pkgId = do
+            mEntry <- getFile $ IndexPkgMetadata pkgId
+            case mEntry of
+              Nothing ->
+                throwChecked $ InvalidPackageException pkgId
+              Just IndexEntry{indexEntryContentParsed = Left ex} ->
+                throwUnchecked $ ex
+              Just IndexEntry{indexEntryContentParsed = Right signed} ->
+                return $ trustLocalFile signed
+
+          -- Get package info
+          getFileInfo :: ( Throws InvalidPackageException
+                         , Throws VerificationError
+                         )
+                      => PackageIdentifier -> IO (Trusted FileInfo)
+          getFileInfo pkgId = do
+            targets <- getMetadata pkgId
+
+            let mTargetMetadata :: Maybe (Trusted FileInfo)
+                mTargetMetadata = trustElems
+                                $ trustStatic (static targetsLookup)
+                     `trustApply` DeclareTrusted (targetPath pkgId)
+                     `trustApply` targets
+
+            case mTargetMetadata of
+              Nothing ->
+                throwChecked $ VerificationErrorUnknownTarget (targetPath pkgId)
+              Just info ->
+                return info
+
+          -- Get package SHA256
+          getHash :: ( Throws InvalidPackageException
+                     , Throws VerificationError
+                     )
+                  => PackageIdentifier -> IO (Trusted Hash)
+          getHash pkgId = do
+            info <- getFileInfo pkgId
+
+            let mTrustedHash :: Maybe (Trusted Hash)
+                mTrustedHash = trustElems
+                             $ trustStatic (static fileInfoSHA256)
+                  `trustApply` info
+
+            case mTrustedHash of
+              Nothing ->
+                throwChecked $ VerificationErrorMissingSHA256 (targetPath pkgId)
+              Just hash ->
+                return hash
+
+      callback IndexCallbacks{
+          indexLookupEntry     = getEntry
+        , indexLookupFile      = getFile
+        , indexLookupFileEntry = getFileEntry
+        , indexDirectory       = dir
+        , indexLookupCabal     = getCabal
+        , indexLookupMetadata  = getMetadata
+        , indexLookupFileInfo  = getFileInfo
+        , indexLookupHash      = getHash
+        }
+  where
+    indexPath :: Tar.Entry -> IndexPath
+    indexPath = rootPath . fromUnrootedFilePath . Tar.entryPath
+
+    indexFile :: IndexPath -> Maybe (Some IndexFile)
+    indexFile = indexFileFromPath repIndexLayout
+
+    targetPath :: PackageIdentifier -> TargetPath
+    targetPath = TargetPathRepo . repoLayoutPkgTarGz repLayout
+
+    pathNotRecognized :: SomeException
+    pathNotRecognized = SomeException (userError "Path not recognized")
+
+{-------------------------------------------------------------------------------
+  Bootstrapping
+-------------------------------------------------------------------------------}
+
+-- | Check if we need to bootstrap (i.e., if we have root info)
+requiresBootstrap :: Repository down -> IO Bool
+requiresBootstrap rep = isNothing <$> repGetCached rep CachedRoot
+
+-- | Bootstrap the chain of trust
+--
+-- New clients might need to obtain a copy of the root metadata. This however
+-- represents a chicken-and-egg problem: how can we verify the root metadata
+-- we downloaded? The only possibility is to be provided with a set of an
+-- out-of-band set of root keys and an appropriate threshold.
+--
+-- Clients who provide a threshold of 0 can do an initial "unsafe" update
+-- of the root information, if they wish.
+--
+-- The downloaded root information will _only_ be verified against the
+-- provided keys, and _not_ against previously downloaded root info (if any).
+-- It is the responsibility of the client to call `bootstrap` only when this
+-- is the desired behaviour.
+bootstrap :: (Throws SomeRemoteError, Throws VerificationError)
+          => Repository down -> [KeyId] -> KeyThreshold -> IO ()
+bootstrap rep@Repository{..} trustedRootKeys keyThreshold = withMirror rep $ runVerify repLockCache $ do
+    _newRoot :: Trusted Root <- do
+      (targetPath, tempPath) <- getRemote' rep (AttemptNr 0) (RemoteRoot Nothing)
+      signed   <- throwErrorsChecked (VerificationErrorDeserialization targetPath) =<<
+                    readDownloadedJSON rep KeyEnv.empty tempPath
+      verified <- throwErrorsChecked id $ verifyFingerprints
+                    trustedRootKeys
+                    keyThreshold
+                    targetPath
+                    signed
+      return $ trustVerified verified
+
+    clearCache rep
+
+{-------------------------------------------------------------------------------
+  Wrapper around the Repository functions
+-------------------------------------------------------------------------------}
+
+getRemote :: forall fs down typ. Throws SomeRemoteError
+          => Repository down
+          -> AttemptNr
+          -> RemoteFile fs typ
+          -> Verify (Some Format, TargetPath, down typ)
+getRemote r attemptNr file = do
+    (Some format, downloaded) <- repGetRemote r attemptNr file
+    let targetPath = TargetPathRepo $ remoteRepoPath' (repLayout r) file format
+    return (Some (hasFormatGet format), targetPath, downloaded)
+
+-- | Variation on getRemote where we only expect one type of result
+getRemote' :: forall f down typ. Throws SomeRemoteError
+           => Repository down
+           -> AttemptNr
+           -> RemoteFile (f :- ()) typ
+           -> Verify (TargetPath, down typ)
+getRemote' r isRetry file = ignoreFormat <$> getRemote r isRetry file
+  where
+    ignoreFormat (_format, targetPath, tempPath) = (targetPath, tempPath)
+
+clearCache :: MonadIO m => Repository down -> m ()
+clearCache r = liftIO $ repClearCache r
+
+log :: MonadIO m => Repository down -> LogMessage -> m ()
+log r msg = liftIO $ repLog r msg
+
+-- Tries to load the cached mirrors file
+withMirror :: Repository down -> IO a -> IO a
+withMirror rep callback = do
+    mMirrors <- repGetCached rep CachedMirrors
+    mirrors  <- case mMirrors of
+      Nothing -> return Nothing
+      Just fp -> filterMirrors <$>
+                   (throwErrorsUnchecked LocalFileCorrupted =<<
+                     readJSON_NoKeys_NoLayout fp)
+    repWithMirror rep mirrors $ callback
+  where
+    filterMirrors :: UninterpretedSignatures Mirrors -> Maybe [Mirror]
+    filterMirrors = Just
+                  . filter (canUseMirror . mirrorContent)
+                  . mirrorsMirrors
+                  . uninterpretedSigned
+
+    -- Once we add support for partial mirrors, we wil need an additional
+    -- argument to 'repWithMirror' (here, not in the Repository API itself)
+    -- that tells us which files we will be requested from the mirror.
+    -- We can then compare that against the specification of the partial mirror
+    -- to see if all of those files are available from this mirror.
+    canUseMirror :: MirrorContent -> Bool
+    canUseMirror MirrorFull = True
+
+{-------------------------------------------------------------------------------
+  Exceptions
+-------------------------------------------------------------------------------}
+
+-- | Re-throw all exceptions thrown by the client API as unchecked exceptions
+uncheckClientErrors :: ( ( Throws VerificationError
+                         , Throws SomeRemoteError
+                         , Throws InvalidPackageException
+                         ) => IO a )
+                     -> IO a
+uncheckClientErrors act = handleChecked rethrowVerificationError
+                        $ handleChecked rethrowSomeRemoteError
+                        $ handleChecked rethrowInvalidPackageException
+                        $ act
+  where
+     rethrowVerificationError :: VerificationError -> IO a
+     rethrowVerificationError = throwIO
+
+     rethrowSomeRemoteError :: SomeRemoteError -> IO a
+     rethrowSomeRemoteError = throwIO
+
+     rethrowInvalidPackageException :: InvalidPackageException -> IO a
+     rethrowInvalidPackageException = throwIO
+
+data InvalidPackageException = InvalidPackageException PackageIdentifier
+  deriving (Typeable)
+
+data LocalFileCorrupted = LocalFileCorrupted DeserializationError
+  deriving (Typeable)
+
+data InvalidFileInIndex = forall dec. InvalidFileInIndex {
+    invalidFileInIndex      :: IndexFile dec
+  , invalidFileInIndexRaw   :: BS.L.ByteString
+  , invalidFileInIndexError :: DeserializationError
+  }
+  deriving (Typeable)
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show InvalidPackageException
+deriving instance Show LocalFileCorrupted
+deriving instance Show InvalidFileInIndex
+instance Exception InvalidPackageException where displayException = pretty
+instance Exception LocalFileCorrupted where displayException = pretty
+instance Exception InvalidFileInIndex where displayException = pretty
+#else
+instance Show InvalidPackageException where show = pretty
+instance Show LocalFileCorrupted where show = pretty
+instance Show InvalidFileInIndex where show = pretty
+instance Exception InvalidPackageException
+instance Exception LocalFileCorrupted
+instance Exception InvalidFileInIndex
+#endif
+
+instance Pretty InvalidPackageException where
+  pretty (InvalidPackageException pkgId) = "Invalid package " ++ display pkgId
+
+instance Pretty LocalFileCorrupted where
+  pretty (LocalFileCorrupted err) = "Local file corrupted: " ++ pretty err
+
+instance Pretty InvalidFileInIndex where
+  pretty (InvalidFileInIndex file raw err) = unlines [
+      "Invalid file in index: "  ++ pretty file
+    , "Error: " ++ pretty err
+    , "Unparsed file: " ++ BS.L.C8.unpack raw
+    ]
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Local files are assumed trusted
+--
+-- There is no point tracking chain of trust for local files because that chain
+-- would necessarily have to start at an implicitly trusted (though unverified)
+-- file: the root metadata.
+trustLocalFile :: Signed a -> Trusted a
+trustLocalFile Signed{..} = DeclareTrusted signed
+
+-- | Just a simple wrapper around 'verifyFileInfo'
+--
+-- Throws a VerificationError if verification failed.
+verifyFileInfo' :: (MonadIO m, DownloadedFile down)
+                => Maybe (Trusted FileInfo)
+                -> TargetPath  -- ^ For error messages
+                -> down typ    -- ^ File to verify
+                -> m ()
+verifyFileInfo' Nothing     _          _        = return ()
+verifyFileInfo' (Just info) targetPath tempPath = liftIO $ do
+    verified <- downloadedVerify tempPath info
+    unless verified $ throw $ VerificationErrorFileInfo targetPath
+
+readCachedJSON :: (MonadIO m, FromJSON ReadJSON_Keys_Layout a)
+               => Repository down -> KeyEnv -> Path Absolute
+               -> m (Either DeserializationError a)
+readCachedJSON Repository{..} keyEnv fp = liftIO $ do
+    bs <- readLazyByteString fp
+    evaluate $ parseJSON_Keys_Layout keyEnv repLayout bs
+
+readDownloadedJSON :: (MonadIO m, FromJSON ReadJSON_Keys_Layout a)
+                   => Repository down -> KeyEnv -> down Metadata
+                   -> m (Either DeserializationError a)
+readDownloadedJSON Repository{..} keyEnv fp = liftIO $ do
+    bs <- downloadedRead fp
+    evaluate $ parseJSON_Keys_Layout keyEnv repLayout bs
+
+throwErrorsUnchecked :: ( MonadIO m
+                        , Exception e'
+                        )
+                     => (e -> e') -> Either e a -> m a
+throwErrorsUnchecked f (Left err) = liftIO $ throwUnchecked (f err)
+throwErrorsUnchecked _ (Right a)  = return a
+
+throwErrorsChecked :: ( Throws e'
+                      , MonadIO m
+                      , Exception e'
+                      )
+                   => (e -> e') -> Either e a -> m a
+throwErrorsChecked f (Left err) = liftIO $ throwChecked (f err)
+throwErrorsChecked _ (Right a)  = return a
+
+eitherToMaybe :: Either a b -> Maybe b
+eitherToMaybe (Left  _) = Nothing
+eitherToMaybe (Right b) = Just b
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Formats.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Formats.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Formats.hs
@@ -0,0 +1,116 @@
+module Hackage.Security.Client.Formats (
+    -- * Formats
+    -- ** Type level
+    FormatUn
+  , FormatGz
+    -- ** Term level
+  , Format(..)
+  , Formats(..)
+    -- * Key membership
+  , HasFormat(..)
+    -- ** Utility
+  , hasFormatAbsurd
+  , hasFormatGet
+    -- * Map-like operations
+  , formatsMap
+  , formatsMember
+  , formatsLookup
+  ) where
+
+import Hackage.Security.Util.Stack
+import Hackage.Security.Util.TypedEmbedded
+
+{-------------------------------------------------------------------------------
+  Formats
+-------------------------------------------------------------------------------}
+
+data FormatUn
+data FormatGz
+
+-- | Format is a singleton type (reflection type to term level)
+--
+-- NOTE: In the future we might add further compression formats.
+data Format :: * -> * where
+  FUn :: Format FormatUn
+  FGz :: Format FormatGz
+
+deriving instance Show (Format f)
+deriving instance Eq   (Format f)
+
+instance Unify Format where
+  unify FUn FUn = Just Refl
+  unify FGz FGz = Just Refl
+  unify _   _   = Nothing
+
+{-------------------------------------------------------------------------------
+  Products
+-------------------------------------------------------------------------------}
+
+-- | Available formats
+--
+-- Rather than having a general list here, we enumerate all possibilities.
+-- This means we are very precise about what we expect, and we avoid any runtime
+-- errors about unexpect format definitions.
+--
+-- NOTE: If we add additional cases here (for dealing with additional formats)
+-- all calls to @error "inaccessible"@ need to be reevaluated.
+data Formats :: * -> * -> * where
+  FsNone :: Formats () a
+  FsUn   :: a -> Formats (FormatUn :- ()) a
+  FsGz   :: a -> Formats (FormatGz :- ()) a
+  FsUnGz :: a -> a -> Formats (FormatUn :- FormatGz :- ()) a
+
+deriving instance Eq   a => Eq   (Formats fs a)
+deriving instance Show a => Show (Formats fs a)
+
+instance Functor (Formats fs) where
+  fmap g = formatsMap (\_format -> g)
+
+{-------------------------------------------------------------------------------
+  Key membership
+-------------------------------------------------------------------------------}
+
+-- | @HasFormat fs f@ is a proof that @f@ is a key in @fs@.
+--
+-- See 'formatsMember' and 'formatsLookup' for typical usage.
+data HasFormat :: * -> * -> * where
+  HFZ :: Format f       -> HasFormat (f  :- fs) f
+  HFS :: HasFormat fs f -> HasFormat (f' :- fs) f
+
+deriving instance Eq   (HasFormat fs f)
+deriving instance Show (HasFormat fs f)
+
+hasFormatAbsurd :: HasFormat () f -> a
+hasFormatAbsurd _ = error "inaccessible"
+
+hasFormatGet :: HasFormat fs f -> Format f
+hasFormatGet (HFZ f)  = f
+hasFormatGet (HFS hf) = hasFormatGet hf
+
+{-------------------------------------------------------------------------------
+  Map-like functionality
+-------------------------------------------------------------------------------}
+
+formatsMap :: (forall f. Format f -> a -> b) -> Formats fs a -> Formats fs b
+formatsMap _ FsNone        = FsNone
+formatsMap f (FsUn   a)    = FsUn   (f FUn a)
+formatsMap f (FsGz   a)    = FsGz   (f FGz a)
+formatsMap f (FsUnGz a a') = FsUnGz (f FUn a) (f FGz a')
+
+formatsMember :: Format f -> Formats fs a -> Maybe (HasFormat fs f)
+formatsMember _   FsNone       = Nothing
+formatsMember FUn (FsUn   _  ) = Just $ HFZ FUn
+formatsMember FUn (FsGz     _) = Nothing
+formatsMember FUn (FsUnGz _ _) = Just $ HFZ FUn
+formatsMember FGz (FsUn   _  ) = Nothing
+formatsMember FGz (FsGz     _) = Just $ HFZ FGz
+formatsMember FGz (FsUnGz _ _) = Just $ HFS (HFZ FGz)
+
+formatsLookup :: HasFormat fs f -> Formats fs a -> a
+formatsLookup (HFZ FUn) (FsUn   a  ) = a
+formatsLookup (HFZ FUn) (FsUnGz a _) = a
+formatsLookup (HFZ FGz) (FsGz     a) = a
+formatsLookup (HFS hf)  (FsUn   _  ) = hasFormatAbsurd hf
+formatsLookup (HFS hf)  (FsGz     _) = hasFormatAbsurd hf
+formatsLookup (HFS hf)  (FsUnGz _ a) = formatsLookup hf (FsGz a)
+formatsLookup _         _            = error "inaccessible"
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Repository.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository.hs
@@ -0,0 +1,462 @@
+-- | Abstract definition of a Repository
+--
+-- Most clients should only need to import this module if they wish to define
+-- their own Repository implementations.
+{-# LANGUAGE CPP #-}
+module Hackage.Security.Client.Repository (
+    -- * Files
+    Metadata  -- type index (really a kind)
+  , Binary    -- type index (really a kind)
+  , RemoteFile(..)
+  , CachedFile(..)
+  , IndexFile(..)
+  , remoteFileDefaultFormat
+  , remoteFileDefaultInfo
+    -- * Repository proper
+  , Repository(..)
+  , AttemptNr(..)
+  , LogMessage(..)
+  , UpdateFailure(..)
+  , SomeRemoteError(..)
+    -- ** Downloaded files
+  , DownloadedFile(..)
+    -- ** Helpers
+  , mirrorsUnsupported
+    -- * Paths
+  , remoteRepoPath
+  , remoteRepoPath'
+    -- * Utility
+  , IsCached(..)
+  , mustCache
+  ) where
+
+import Control.Exception
+import Data.Typeable (Typeable)
+import qualified Codec.Archive.Tar.Index as Tar
+import qualified Data.ByteString.Lazy    as BS.L
+
+import Distribution.Package
+import Distribution.Text
+
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Verify
+import Hackage.Security.Trusted
+import Hackage.Security.TUF
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.Stack
+
+{-------------------------------------------------------------------------------
+  Files
+-------------------------------------------------------------------------------}
+
+data Metadata
+data Binary
+
+-- | Abstract definition of files we might have to download
+--
+-- 'RemoteFile' is parametrized by the type of the formats that we can accept
+-- from the remote repository, as well as with information on whether this file
+-- is metadata actual binary content.
+--
+-- NOTE: Haddock lacks GADT support so constructors have only regular comments.
+data RemoteFile :: * -> * -> * where
+    -- Timestamp metadata (@timestamp.json@)
+    --
+    -- We never have (explicit) file length available for timestamps.
+    RemoteTimestamp :: RemoteFile (FormatUn :- ()) Metadata
+
+    -- Root metadata (@root.json@)
+    --
+    -- For root information we may or may not have the file info available:
+    --
+    -- - If during the normal update process the new snapshot tells us the root
+    --   information has changed, we can use the file info from the snapshot.
+    -- - If however we need to update the root metadata due to a verification
+    --   exception we do not know the file info.
+    -- - We also do not know the file info during bootstrapping.
+    RemoteRoot :: Maybe (Trusted FileInfo) -> RemoteFile (FormatUn :- ()) Metadata
+
+    -- Snapshot metadata (@snapshot.json@)
+    --
+    -- We get file info of the snapshot from the timestamp.
+    RemoteSnapshot :: Trusted FileInfo -> RemoteFile (FormatUn :- ()) Metadata
+
+    -- Mirrors metadata (@mirrors.json@)
+    --
+    -- We get the file info from the snapshot.
+    RemoteMirrors :: Trusted FileInfo -> RemoteFile (FormatUn :- ()) Metadata
+
+    -- Index
+    --
+    -- The index file length comes from the snapshot.
+    --
+    -- When we request that the index is downloaded, it is up to the repository
+    -- to decide whether to download @00-index.tar@ or @00-index.tar.gz@.
+    -- The callback is told which format was requested.
+    --
+    -- It is a bug to request a file that the repository does not provide
+    -- (the snapshot should make it clear which files are available).
+    RemoteIndex :: HasFormat fs FormatGz
+                -> Formats fs (Trusted FileInfo)
+                -> RemoteFile fs Binary
+
+    -- Actual package
+    --
+    -- Package file length comes from the corresponding @targets.json@.
+    RemotePkgTarGz :: PackageIdentifier
+                   -> Trusted FileInfo
+                   -> RemoteFile (FormatGz :- ()) Binary
+
+deriving instance Show (RemoteFile fs typ)
+
+instance Pretty (RemoteFile fs typ) where
+  pretty RemoteTimestamp          = "timestamp"
+  pretty (RemoteRoot _)           = "root"
+  pretty (RemoteSnapshot _)       = "snapshot"
+  pretty (RemoteMirrors _)        = "mirrors"
+  pretty (RemoteIndex _ _)        = "index"
+  pretty (RemotePkgTarGz pkgId _) = "package " ++ display pkgId
+
+-- | Files that we might request from the local cache
+data CachedFile =
+    -- | Timestamp metadata (@timestamp.json@)
+    CachedTimestamp
+
+    -- | Root metadata (@root.json@)
+  | CachedRoot
+
+    -- | Snapshot metadata (@snapshot.json@)
+  | CachedSnapshot
+
+    -- | Mirrors list (@mirrors.json@)
+  | CachedMirrors
+  deriving (Eq, Ord, Show)
+
+instance Pretty CachedFile where
+  pretty CachedTimestamp = "timestamp"
+  pretty CachedRoot      = "root"
+  pretty CachedSnapshot  = "snapshot"
+  pretty CachedMirrors   = "mirrors"
+
+-- | Default format for each file type
+--
+-- For most file types we don't have a choice; for the index the repository
+-- is only required to offer the GZip-compressed format so that is the default.
+remoteFileDefaultFormat :: RemoteFile fs typ -> Some (HasFormat fs)
+remoteFileDefaultFormat RemoteTimestamp      = Some $ HFZ FUn
+remoteFileDefaultFormat (RemoteRoot _)       = Some $ HFZ FUn
+remoteFileDefaultFormat (RemoteSnapshot _)   = Some $ HFZ FUn
+remoteFileDefaultFormat (RemoteMirrors _)    = Some $ HFZ FUn
+remoteFileDefaultFormat (RemotePkgTarGz _ _) = Some $ HFZ FGz
+remoteFileDefaultFormat (RemoteIndex pf _)   = Some pf
+
+-- | Default file info (see also 'remoteFileDefaultFormat')
+remoteFileDefaultInfo :: RemoteFile fs typ -> Maybe (Trusted FileInfo)
+remoteFileDefaultInfo RemoteTimestamp         = Nothing
+remoteFileDefaultInfo (RemoteRoot info)       = info
+remoteFileDefaultInfo (RemoteSnapshot info)   = Just info
+remoteFileDefaultInfo (RemoteMirrors info)    = Just info
+remoteFileDefaultInfo (RemotePkgTarGz _ info) = Just info
+remoteFileDefaultInfo (RemoteIndex pf info)   = Just $ formatsLookup pf info
+
+{-------------------------------------------------------------------------------
+  Repository proper
+-------------------------------------------------------------------------------}
+
+-- | Repository
+--
+-- This is an abstract representation of a repository. It simply provides a way
+-- to download metafiles and target files, without specifying how this is done.
+-- For instance, for a local repository this could just be doing a file read,
+-- whereas for remote repositories this could be using any kind of HTTP client.
+data Repository down = DownloadedFile down => Repository {
+    -- | Get a file from the server
+    --
+    -- Responsibilies of 'repGetRemote':
+    --
+    -- * Download the file from the repository and make it available at a
+    --   temporary location
+    -- * Use the provided file length to protect against endless data attacks.
+    --   (Repositories such as local repositories that are not suspectible to
+    --   endless data attacks can safely ignore this argument.)
+    -- * Move the file from its temporary location to its permanent location
+    --   if verification succeeds.
+    --
+    -- NOTE: Calls to 'repGetRemote' should _always_ be in the scope of
+    -- 'repWithMirror'.
+    repGetRemote :: forall fs typ. Throws SomeRemoteError
+                 => AttemptNr
+                 -> RemoteFile fs typ
+                 -> Verify (Some (HasFormat fs), down typ)
+
+    -- | Get a cached file (if available)
+  , repGetCached :: CachedFile -> IO (Maybe (Path Absolute))
+
+    -- | Get the cached root
+    --
+    -- This is a separate method only because clients must ALWAYS have root
+    -- information available.
+  , repGetCachedRoot :: IO (Path Absolute)
+
+    -- | Clear all cached data
+    --
+    -- In particular, this should remove the snapshot and the timestamp.
+    -- It would also be okay, but not required, to delete the index.
+  , repClearCache :: IO ()
+
+    -- | Open the tarball for reading
+    --
+    -- This function has this shape so that:
+    --
+    -- * We can read multiple files from the tarball without having to open
+    --   and close the handle each time
+    -- * We can close the handle immediately when done.
+  , repWithIndex :: forall a. (Handle -> IO a) -> IO a
+
+    -- | Read the index index
+  , repGetIndexIdx :: IO Tar.TarIndex
+
+    -- | Lock the cache (during updates)
+  , repLockCache :: IO () -> IO ()
+
+    -- | Mirror selection
+    --
+    -- The purpose of 'repWithMirror' is to scope mirror selection. The idea
+    -- is that if we have
+    --
+    -- > repWithMirror mirrorList $
+    -- >   someCallback
+    --
+    -- then the repository may pick a mirror before calling @someCallback@,
+    -- catch exceptions thrown by @someCallback@, and potentially try the
+    -- callback again with a different mirror.
+    --
+    -- The list of mirrors may be @Nothing@ if we haven't yet downloaded the
+    -- list of mirrors from the repository, or when our cached list of mirrors
+    -- is invalid. Of course, if we did download it, then the list of mirrors
+    -- may still be empty. In this case the repository must fall back to its
+    -- primary download mechanism.
+    --
+    -- Mirrors as currently defined (in terms of a "base URL") are inherently a
+    -- HTTP (or related) concept, so in repository implementations such as the
+    -- local-repo 'repWithMirrors' is probably just an identity operation  (see
+    -- 'ignoreMirrors').  Conversely, HTTP implementations of repositories may
+    -- have other, out-of-band information (for example, coming from a cabal
+    -- config file) that they may use to influence mirror selection.
+  , repWithMirror :: forall a. Maybe [Mirror] -> IO a -> IO a
+
+    -- | Logging
+  , repLog :: LogMessage -> IO ()
+
+    -- | Layout of this repository
+  , repLayout :: RepoLayout
+
+    -- | Layout of the index
+    --
+    -- Since the repository hosts the index, the layout of the index is
+    -- not independent of the layout of the repository.
+  , repIndexLayout :: IndexLayout
+
+    -- | Description of the repository (used in the show instance)
+  , repDescription :: String
+  }
+
+instance Show (Repository down) where
+  show = repDescription
+
+-- | Helper function to implement 'repWithMirrors'.
+mirrorsUnsupported :: Maybe [Mirror] -> IO a -> IO a
+mirrorsUnsupported _ = id
+
+-- | Are we requesting this information because of a previous validation error?
+--
+-- Clients can take advantage of this to tell caches to revalidate files.
+newtype AttemptNr = AttemptNr Int
+  deriving (Eq, Ord, Num)
+
+-- | Log messages
+--
+-- We use a 'RemoteFile' rather than a 'RepoPath' here because we might not have
+-- a 'RepoPath' for the file that we were trying to download (that is, for
+-- example if the server does not provide an uncompressed tarball, it doesn't
+-- make much sense to list the path to that non-existing uncompressed tarball).
+data LogMessage =
+    -- | Root information was updated
+    --
+    -- This message is issued when the root information is updated as part of
+    -- the normal check for updates procedure. If the root information is
+    -- updated because of a verification error WarningVerificationError is
+    -- issued instead.
+    LogRootUpdated
+
+    -- | A verification error
+    --
+    -- Verification errors can be temporary, and may be resolved later; hence
+    -- these are just warnings. (Verification errors that cannot be resolved
+    -- are thrown as exceptions.)
+  | LogVerificationError VerificationError
+
+    -- | Download a file from a repository
+  | forall fs typ. LogDownloading (RemoteFile fs typ)
+
+    -- | Incrementally updating a file from a repository
+  | forall fs. LogUpdating (RemoteFile fs Binary)
+
+    -- | Selected a particular mirror
+  | LogSelectedMirror MirrorDescription
+
+    -- | Updating a file failed
+    -- (we will instead download it whole)
+  | forall fs. LogCannotUpdate (RemoteFile fs Binary) UpdateFailure
+
+    -- | We got an exception with a particular mirror
+    -- (we will try with a different mirror if any are available)
+  | LogMirrorFailed MirrorDescription SomeException
+
+-- | Records why we are downloading a file rather than updating it.
+data UpdateFailure =
+    -- | Server does not support incremental downloads
+    UpdateImpossibleUnsupported
+
+    -- | We don't have a local copy of the file to update
+  | UpdateImpossibleNoLocalCopy
+
+    -- | Update failed twice
+    --
+    -- If we attempt an incremental update the first time, and it fails,  we let
+    -- it go round the loop, update local security information, and try again.
+    -- But if an incremental update then fails _again_, we  instead attempt a
+    -- regular download.
+  | UpdateFailedTwice
+
+    -- | Update failed (for example: perhaps the local file got corrupted)
+  | UpdateFailed SomeException
+
+{-------------------------------------------------------------------------------
+  Downloaded files
+-------------------------------------------------------------------------------}
+
+class DownloadedFile (down :: * -> *) where
+  -- | Verify a download file
+  downloadedVerify :: down a -> Trusted FileInfo -> IO Bool
+
+  -- | Read the file we just downloaded into memory
+  --
+  -- We never read binary data, only metadata.
+  downloadedRead :: down Metadata -> IO BS.L.ByteString
+
+  -- | Copy a downloaded file to its destination
+  downloadedCopyTo :: down a -> Path Absolute -> IO ()
+
+{-------------------------------------------------------------------------------
+  Exceptions thrown by specific Repository implementations
+-------------------------------------------------------------------------------}
+
+-- | Repository-specific exceptions
+--
+-- For instance, for repositories using HTTP this might correspond to a 404;
+-- for local repositories this might correspond to file-not-found, etc.
+data SomeRemoteError :: * where
+    SomeRemoteError :: Exception e => e -> SomeRemoteError
+  deriving (Typeable)
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show SomeRemoteError
+instance Exception SomeRemoteError where displayException = pretty
+#else
+instance Exception SomeRemoteError
+instance Show SomeRemoteError where show = pretty
+#endif
+
+instance Pretty SomeRemoteError where
+    pretty (SomeRemoteError ex) = displayException ex
+
+{-------------------------------------------------------------------------------
+  Paths
+-------------------------------------------------------------------------------}
+
+remoteRepoPath :: RepoLayout -> RemoteFile fs typ -> Formats fs RepoPath
+remoteRepoPath RepoLayout{..} = go
+  where
+    go :: RemoteFile fs typ -> Formats fs RepoPath
+    go RemoteTimestamp        = FsUn $ repoLayoutTimestamp
+    go (RemoteRoot _)         = FsUn $ repoLayoutRoot
+    go (RemoteSnapshot _)     = FsUn $ repoLayoutSnapshot
+    go (RemoteMirrors _)      = FsUn $ repoLayoutMirrors
+    go (RemotePkgTarGz pId _) = FsGz $ repoLayoutPkgTarGz pId
+    go (RemoteIndex _ lens)   = formatsMap goIndex lens
+
+    goIndex :: Format f -> a -> RepoPath
+    goIndex FUn _ = repoLayoutIndexTar
+    goIndex FGz _ = repoLayoutIndexTarGz
+
+remoteRepoPath' :: RepoLayout -> RemoteFile fs typ -> HasFormat fs f -> RepoPath
+remoteRepoPath' repoLayout file format =
+    formatsLookup format $ remoteRepoPath repoLayout file
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+-- | Is a particular remote file cached?
+data IsCached :: * -> * where
+    -- | This remote file should be cached, and we ask for it by name
+    CacheAs :: CachedFile -> IsCached Metadata
+
+    -- | We don't cache this remote file
+    --
+    -- This doesn't mean a Repository should not feel free to cache the file
+    -- if desired, but it does mean the generic algorithms will never ask for
+    -- this file from the cache.
+    DontCache :: IsCached Binary
+
+    -- | The index is somewhat special: it should be cached, but we never
+    -- ask for it directly.
+    --
+    -- Instead, we will ask the Repository for files _from_ the index, which it
+    -- can serve however it likes. For instance, some repositories might keep
+    -- the index in uncompressed form, others in compressed form; some might
+    -- keep an index tarball index for quick access, others may scan the tarball
+    -- linearly, etc.
+    CacheIndex :: IsCached Binary
+
+deriving instance Eq   (IsCached typ)
+deriving instance Show (IsCached typ)
+
+-- | Which remote files should we cache locally?
+mustCache :: RemoteFile fs typ -> IsCached typ
+mustCache RemoteTimestamp      = CacheAs CachedTimestamp
+mustCache (RemoteRoot _)       = CacheAs CachedRoot
+mustCache (RemoteSnapshot _)   = CacheAs CachedSnapshot
+mustCache (RemoteMirrors _)    = CacheAs CachedMirrors
+mustCache (RemoteIndex {})     = CacheIndex
+mustCache (RemotePkgTarGz _ _) = DontCache
+
+instance Pretty LogMessage where
+  pretty LogRootUpdated =
+      "Root info updated"
+  pretty (LogVerificationError err) =
+      "Verification error: " ++ pretty err
+  pretty (LogDownloading file) =
+      "Downloading " ++ pretty file
+  pretty (LogUpdating file) =
+      "Updating " ++ pretty file
+  pretty (LogSelectedMirror mirror) =
+      "Selected mirror " ++ mirror
+  pretty (LogCannotUpdate file ex) =
+      "Cannot update " ++ pretty file ++ " (" ++ pretty ex ++ ")"
+  pretty (LogMirrorFailed mirror ex) =
+      "Exception " ++ displayException ex ++ " when using mirror " ++ mirror
+
+instance Pretty UpdateFailure where
+  pretty UpdateImpossibleUnsupported =
+      "server does not provide incremental downloads"
+  pretty UpdateImpossibleNoLocalCopy =
+      "no local copy"
+  pretty UpdateFailedTwice =
+      "update failed twice"
+  pretty (UpdateFailed ex) =
+      displayException ex
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Cache.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Cache.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Cache.hs
@@ -0,0 +1,204 @@
+{-# LANGUAGE BangPatterns #-}
+-- | The files we cache from the repository
+--
+-- Both the Local and the Remote repositories make use of this module.
+module Hackage.Security.Client.Repository.Cache (
+    Cache(..)
+  , getCached
+  , getCachedRoot
+  , getCachedIndex
+  , clearCache
+  , withIndex
+  , getIndexIdx
+  , cacheRemoteFile
+  , lockCache
+  ) where
+
+import Control.Exception
+import Control.Monad
+import Codec.Archive.Tar (Entries(..))
+import Codec.Archive.Tar.Index (TarIndex, IndexBuilder, TarEntryOffset)
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Index as TarIndex
+import qualified Codec.Compression.GZip  as GZip
+import qualified Data.ByteString         as BS
+import qualified Data.ByteString.Lazy    as BS.L
+
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Formats
+import Hackage.Security.TUF
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.IO
+import Hackage.Security.Util.Path
+
+-- | Location and layout of the local cache
+data Cache = Cache {
+      cacheRoot   :: Path Absolute
+    , cacheLayout :: CacheLayout
+    }
+
+-- | Cache a previously downloaded remote file
+cacheRemoteFile :: forall down typ f. DownloadedFile down
+                => Cache -> down typ -> Format f -> IsCached typ -> IO ()
+cacheRemoteFile cache downloaded f isCached = do
+    go f isCached
+    case isCached of
+      CacheIndex -> rebuildTarIndex cache
+      _otherwise -> return ()
+  where
+    go :: Format f -> IsCached typ -> IO ()
+    go _   DontCache      = return ()
+    go FUn (CacheAs file) = copyTo (cachedFilePath cache file)
+    go FGz CacheIndex     = copyTo (cachedIndexPath cache FGz) >> unzipIndex
+    go _ _ = error "cacheRemoteFile: unexpected case" -- TODO: enforce in types?
+
+    copyTo :: Path Absolute -> IO ()
+    copyTo fp = do
+      createDirectoryIfMissing True (takeDirectory fp)
+      downloadedCopyTo downloaded fp
+
+    -- Whether or not we downloaded the compressed index incrementally, we can
+    -- always update the uncompressed index incrementally.
+    -- NOTE: This assumes we already updated the compressed file.
+    unzipIndex :: typ ~ Binary => IO ()
+    unzipIndex = do
+        createDirectoryIfMissing True (takeDirectory indexUn)
+        compressed <- readLazyByteString indexGz
+        let uncompressed = GZip.decompress compressed
+        withFile indexUn ReadWriteMode $ \h -> do
+          currentSize <- hFileSize h
+          let seekTo = 0 `max` (currentSize - tarTrailer)
+          hSeek h AbsoluteSeek seekTo
+          BS.L.hPut h $ BS.L.drop (fromInteger seekTo) uncompressed
+      where
+        indexGz = cachedIndexPath cache FGz
+        indexUn = cachedIndexPath cache FUn
+
+    tarTrailer :: Integer
+    tarTrailer = 1024
+
+-- | Rebuild the tarball index
+--
+-- Attempts to add to the existing index, if one exists.
+--
+-- TODO: Use throwChecked rather than throwUnchecked, and deal with the fallout.
+-- See <https://github.com/well-typed/hackage-security/issues/84>.
+rebuildTarIndex :: Cache -> IO ()
+rebuildTarIndex cache = do
+    (builder, offset) <- initBuilder <$> tryReadIndex (cachedIndexIdxPath cache)
+    withFile (cachedIndexPath cache FUn) ReadMode $ \hTar -> do
+      TarIndex.hSeekEntryOffset hTar offset
+      newEntries <- Tar.read <$> BS.L.hGetContents hTar
+      case addEntries builder newEntries of
+        Left  ex  -> throwUnchecked ex
+        Right idx -> withFile (cachedIndexIdxPath cache) WriteMode $ \hIdx -> do
+                       hSetBuffering hIdx (BlockBuffering Nothing)
+                       BS.hPut hIdx $ TarIndex.serialise idx
+  where
+    -- The initial index builder
+    -- If we don't have an index (or it's broken), we start from scratch
+    initBuilder :: Either e TarIndex -> (IndexBuilder, TarEntryOffset)
+    initBuilder (Left  _)   = ( TarIndex.empty, 0 )
+    initBuilder (Right idx) = ( TarIndex.unfinalise          idx
+                              , TarIndex.indexEndEntryOffset idx
+                              )
+
+-- | Get a cached file (if available)
+getCached :: Cache -> CachedFile -> IO (Maybe (Path Absolute))
+getCached cache cachedFile = do
+    exists <- doesFileExist localPath
+    if exists then return $ Just localPath
+              else return $ Nothing
+  where
+    localPath = cachedFilePath cache cachedFile
+
+-- | Get the cached index (if available)
+getCachedIndex :: Cache -> Format f -> IO (Maybe (Path Absolute))
+getCachedIndex cache format = do
+    exists <- doesFileExist localPath
+    if exists then return $ Just localPath
+              else return $ Nothing
+  where
+    localPath = cachedIndexPath cache format
+
+-- | Get the cached root
+--
+-- Calling 'getCachedRoot' without root info available is a programmer error
+-- and will result in an unchecked exception. See 'requiresBootstrap'.
+getCachedRoot :: Cache -> IO (Path Absolute)
+getCachedRoot cache = do
+    mPath <- getCached cache CachedRoot
+    case mPath of
+      Just p  -> return p
+      Nothing -> internalError "Client missing root info"
+
+getIndexIdx :: Cache -> IO TarIndex
+getIndexIdx cache = do
+    mIndex <- tryReadIndex $ cachedIndexIdxPath cache
+    case mIndex of
+      Left  _   -> throwIO $ userError "Could not read index. Did you call 'checkForUpdates'?"
+      Right idx -> return idx
+
+withIndex :: Cache -> (Handle -> IO a) -> IO a
+withIndex cache = withFile (cachedIndexPath cache FUn) ReadMode
+
+-- | Delete a previously downloaded remote file
+clearCache :: Cache -> IO ()
+clearCache cache = void . handleDoesNotExist $ do
+    removeFile $ cachedFilePath cache CachedTimestamp
+    removeFile $ cachedFilePath cache CachedSnapshot
+
+-- | Lock the cache
+--
+-- This avoids two concurrent processes updating the cache at the same time,
+-- provided they both take the lock.
+lockCache :: Cache -> IO () -> IO ()
+lockCache Cache{..} = withDirLock cacheRoot
+
+{-------------------------------------------------------------------------------
+  Auxiliary: tar
+-------------------------------------------------------------------------------}
+
+-- | Variation on 'TarIndex.build' that takes in the initial 'IndexBuilder'
+addEntries :: IndexBuilder -> Entries e -> Either e TarIndex
+addEntries = go
+  where
+    go !builder (Next e es) = go (TarIndex.addNextEntry e builder) es
+    go !builder  Done       = Right $! TarIndex.finalise builder
+    go !_       (Fail err)  = Left err
+
+-- TODO: How come 'deserialise' uses _strict_ ByteStrings?
+tryReadIndex :: Path Absolute -> IO (Either (Maybe IOException) TarIndex)
+tryReadIndex fp =
+    aux <$> try (TarIndex.deserialise <$> readStrictByteString fp)
+  where
+    aux :: Either e (Maybe (a, leftover)) -> Either (Maybe e) a
+    aux (Left e)              = Left (Just e)
+    aux (Right Nothing)       = Left Nothing
+    aux (Right (Just (a, _))) = Right a
+
+{-------------------------------------------------------------------------------
+  Auxiliary: paths
+-------------------------------------------------------------------------------}
+
+cachedFilePath :: Cache -> CachedFile -> Path Absolute
+cachedFilePath Cache{cacheLayout=CacheLayout{..}, ..} file =
+    anchorCachePath cacheRoot $ go file
+  where
+    go :: CachedFile -> CachePath
+    go CachedRoot      = cacheLayoutRoot
+    go CachedTimestamp = cacheLayoutTimestamp
+    go CachedSnapshot  = cacheLayoutSnapshot
+    go CachedMirrors   = cacheLayoutMirrors
+
+cachedIndexPath :: Cache -> Format f -> Path Absolute
+cachedIndexPath Cache{..} format =
+    anchorCachePath cacheRoot $ go format
+  where
+    go :: Format f -> CachePath
+    go FUn = cacheLayoutIndexTar   cacheLayout
+    go FGz = cacheLayoutIndexTarGz cacheLayout
+
+cachedIndexIdxPath :: Cache -> Path Absolute
+cachedIndexIdxPath Cache{..} =
+    anchorCachePath cacheRoot $ cacheLayoutIndexIdx cacheLayout
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/HttpLib.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/HttpLib.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/HttpLib.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE CPP #-}
+-- | Abstracting over HTTP libraries
+module Hackage.Security.Client.Repository.HttpLib (
+    HttpLib(..)
+  , HttpRequestHeader(..)
+  , HttpResponseHeader(..)
+  , HttpStatus(..)
+  , ProxyConfig(..)
+    -- ** Body reader
+  , BodyReader
+  , bodyReaderFromBS
+  ) where
+
+import Data.IORef
+import Network.URI hiding (uriPath, path)
+import qualified Data.ByteString      as BS
+import qualified Data.ByteString.Lazy as BS.L
+
+import Hackage.Security.Util.Checked
+import Hackage.Security.Client.Repository (SomeRemoteError)
+
+{-------------------------------------------------------------------------------
+  Abstraction over HTTP clients (such as HTTP, http-conduit, etc.)
+-------------------------------------------------------------------------------}
+
+-- | Abstraction over HTTP clients
+--
+-- This avoids insisting on a particular implementation (such as the HTTP
+-- package) and allows for other implementations (such as a conduit based one).
+--
+-- NOTE: Library-specific exceptions MUST be wrapped in 'SomeRemoteError'.
+data HttpLib = HttpLib {
+    -- | Download a file
+    httpGet :: forall a. Throws SomeRemoteError
+            => [HttpRequestHeader]
+            -> URI
+            -> ([HttpResponseHeader] -> BodyReader -> IO a)
+            -> IO a
+
+    -- | Download a byte range
+    --
+    -- Range is starting and (exclusive) end offset in bytes.
+    --
+    -- HTTP servers are normally expected to respond to a range request with
+    -- a "206 Partial Content" response. However, servers can respond with a
+    -- "200 OK" response, sending the entire file instead (for instance, this
+    -- may happen for servers that don't actually support range rqeuests, but
+    -- for which we optimistically assumed they did). Implementations of
+    -- 'HttpLib' may accept such a response and inform the @hackage-security@
+    -- library that the whole file is being returned; the security library can
+    -- then decide to execute the 'BodyReader' anyway (downloading the entire
+    -- file) or abort the request and try something else. For this reason
+    -- the security library must be informed whether the server returned the
+    -- full file or the requested range.
+  , httpGetRange :: forall a. Throws SomeRemoteError
+                 => [HttpRequestHeader]
+                 -> URI
+                 -> (Int, Int)
+                 -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)
+                 -> IO a
+  }
+
+-- | Additional request headers
+--
+-- Since different libraries represent headers differently, here we just
+-- abstract over the few request headers that we might want to set
+data HttpRequestHeader =
+    -- | Set @Cache-Control: max-age=0@
+    HttpRequestMaxAge0
+
+    -- | Set @Cache-Control: no-transform@
+  | HttpRequestNoTransform
+  deriving (Eq, Ord, Show)
+
+-- | HTTP status code
+data HttpStatus =
+     -- | 200 OK
+     HttpStatus200OK
+
+     -- | 206 Partial Content
+   | HttpStatus206PartialContent
+
+-- | Response headers
+--
+-- Since different libraries represent headers differently, here we just
+-- abstract over the few response headers that we might want to know about.
+data HttpResponseHeader =
+    -- | Server accepts byte-range requests (@Accept-Ranges: bytes@)
+    HttpResponseAcceptRangesBytes
+  deriving (Eq, Ord, Show)
+
+-- | Proxy configuration
+--
+-- Although actually setting the proxy is the purview of the initialization
+-- function for individual 'HttpLib' implementations and therefore outside
+-- the scope of this module, we offer this 'ProxyConfiguration' type here as a
+-- way to uniformly configure proxies across all 'HttpLib's.
+data ProxyConfig a =
+    -- | Don't use a proxy
+    ProxyConfigNone
+
+    -- | Use this specific proxy
+    --
+    -- Individual HTTP backends use their own types for specifying proxies.
+  | ProxyConfigUse a
+
+    -- | Use automatic proxy settings
+    --
+    -- What precisely automatic means is 'HttpLib' specific, though
+    -- typically it will involve looking at the @HTTP_PROXY@ environment
+    -- variable or the (Windows) registry.
+  | ProxyConfigAuto
+
+{-------------------------------------------------------------------------------
+  Body readers
+-------------------------------------------------------------------------------}
+
+-- | An @IO@ action that represents an incoming response body coming from the
+-- server.
+--
+-- The action gets a single chunk of data from the response body, or an empty
+-- bytestring if no more data is available.
+--
+-- This definition is copied from the @http-client@ package.
+type BodyReader = IO BS.ByteString
+
+-- | Construct a 'Body' reader from a lazy bytestring
+--
+-- This is appropriate if the lazy bytestring is constructed, say, by calling
+-- 'hGetContents' on a network socket, and the chunks of the bytestring
+-- correspond to the chunks as they are returned from the OS network layer.
+--
+-- If the lazy bytestring needs to be re-chunked this function is NOT suitable.
+bodyReaderFromBS :: BS.L.ByteString -> IO BodyReader
+bodyReaderFromBS lazyBS = do
+    chunks <- newIORef $ BS.L.toChunks lazyBS
+    -- NOTE: Lazy bytestrings invariant: no empty chunks
+    let br = do bss <- readIORef chunks
+                case bss of
+                  []        -> return BS.empty
+                  (bs:bss') -> writeIORef chunks bss' >> return bs
+    return br
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Local.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Local.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Local.hs
@@ -0,0 +1,99 @@
+-- | Local repository
+module Hackage.Security.Client.Repository.Local (
+    LocalRepo
+  , LocalFile -- opaque
+  , withRepository
+  ) where
+
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Repository.Cache
+import Hackage.Security.Client.Verify
+import Hackage.Security.TUF
+import Hackage.Security.Trusted
+import Hackage.Security.Util.IO
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+
+-- | Location of the repository
+--
+-- Note that we regard the local repository as immutable; we cache files just
+-- like we do for remote repositories.
+type LocalRepo = Path Absolute
+
+-- | Initialize the repository (and cleanup resources afterwards)
+--
+-- Like a remote repository, a local repository takes a RepoLayout as argument;
+-- but where the remote repository interprets this RepoLayout relative to a URL,
+-- the local repository interprets it relative to a local directory.
+--
+-- It uses the same cache as the remote repository.
+withRepository
+  :: LocalRepo                       -- ^ Location of local repository
+  -> Cache                           -- ^ Location of local cache
+  -> RepoLayout                      -- ^ Repository layout
+  -> IndexLayout                     -- ^ Index layout
+  -> (LogMessage -> IO ())           -- ^ Logger
+  -> (Repository LocalFile -> IO a)  -- ^ Callback
+  -> IO a
+withRepository repo
+               cache
+               repLayout
+               repIndexLayout
+               logger
+               callback
+               =
+  callback Repository {
+      repGetRemote     = getRemote repLayout repo cache
+    , repGetCached     = getCached     cache
+    , repGetCachedRoot = getCachedRoot cache
+    , repClearCache    = clearCache    cache
+    , repWithIndex     = withIndex     cache
+    , repGetIndexIdx   = getIndexIdx   cache
+    , repLockCache     = lockCache     cache
+    , repWithMirror    = mirrorsUnsupported
+    , repLog           = logger
+    , repLayout        = repLayout
+    , repIndexLayout   = repIndexLayout
+    , repDescription   = "Local repository at " ++ pretty repo
+    }
+
+-- | Get a file from the server
+getRemote :: RepoLayout -> LocalRepo -> Cache
+          -> AttemptNr
+          -> RemoteFile fs typ
+          -> Verify (Some (HasFormat fs), LocalFile typ)
+getRemote repoLayout repo cache _attemptNr remoteFile = do
+    case remoteFileDefaultFormat remoteFile of
+      Some format -> do
+        let remotePath' = remoteRepoPath' repoLayout remoteFile format
+            remotePath  = anchorRepoPathLocally repo remotePath'
+            localFile   = LocalFile remotePath
+        ifVerified $
+          cacheRemoteFile cache
+                          localFile
+                          (hasFormatGet format)
+                          (mustCache remoteFile)
+        return (Some format, localFile)
+
+{-------------------------------------------------------------------------------
+  Files in the local repository
+-------------------------------------------------------------------------------}
+
+newtype LocalFile a = LocalFile (Path Absolute)
+
+instance DownloadedFile LocalFile where
+  downloadedVerify = verifyLocalFile
+  downloadedRead   = \(LocalFile local) -> readLazyByteString local
+  downloadedCopyTo = \(LocalFile local) -> copyFile local
+
+verifyLocalFile :: LocalFile typ -> Trusted FileInfo -> IO Bool
+verifyLocalFile (LocalFile fp) trustedInfo = do
+    -- Verify the file size before comparing the entire file info
+    sz <- FileLength <$> getFileSize fp
+    if sz /= fileInfoLength
+      then return False
+      else knownFileInfoEqual info <$> computeFileInfo fp
+  where
+    info@FileInfo{..} = trusted trustedInfo
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Remote.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Remote.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Remote.hs
@@ -0,0 +1,717 @@
+{-# LANGUAGE CPP #-}
+-- | An implementation of Repository that talks to repositories over HTTP.
+--
+-- This implementation is itself parameterized over a 'HttpClient', so that it
+-- it not tied to a specific library; for instance, 'HttpClient' can be
+-- implemented with the @HTTP@ library, the @http-client@ libary, or others.
+--
+-- It would also be possible to give _other_ Repository implementations that
+-- talk to repositories over HTTP, if you want to make other design decisions
+-- than we did here, in particular:
+--
+-- * We attempt to do incremental downloads of the index when possible.
+-- * We reuse the "Repository.Local"  to deal with the local cache.
+-- * We download @timestamp.json@ and @snapshot.json@ together. This is
+--   implemented here because:
+--   - One level down (HttpClient) we have no access to the local cache
+--   - One level up (Repository API) would require _all_ Repositories to
+--     implement this optimization.
+module Hackage.Security.Client.Repository.Remote (
+    -- * Top-level API
+    withRepository
+  , RepoOpts(..)
+  , defaultRepoOpts
+  , RemoteTemp
+     -- * File sizes
+  , FileSize(..)
+  , fileSizeWithinBounds
+  ) where
+
+import Control.Concurrent
+import Control.Exception
+import Control.Monad.Cont
+import Control.Monad.Except
+import Data.List (nub, intercalate)
+import Data.Typeable
+import Network.URI hiding (uriPath, path)
+import System.IO ()
+import qualified Data.ByteString      as BS
+import qualified Data.ByteString.Lazy as BS.L
+
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Repository.Cache (Cache)
+import Hackage.Security.Client.Repository.HttpLib
+import Hackage.Security.Client.Verify
+import Hackage.Security.Trusted
+import Hackage.Security.TUF
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.IO
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+import qualified Hackage.Security.Client.Repository.Cache as Cache
+
+{-------------------------------------------------------------------------------
+  Server capabilities
+-------------------------------------------------------------------------------}
+
+-- | Server capabilities
+--
+-- As the library interacts with the server and receives replies, we may
+-- discover more information about the server's capabilities; for instance,
+-- we may discover that it supports incremental downloads.
+newtype ServerCapabilities = SC (MVar ServerCapabilities_)
+
+-- | Internal type recording the various server capabilities we support
+data ServerCapabilities_ = ServerCapabilities {
+      -- | Does the server support range requests?
+      serverAcceptRangesBytes :: Bool
+    }
+
+newServerCapabilities :: IO ServerCapabilities
+newServerCapabilities = SC <$> newMVar ServerCapabilities {
+      serverAcceptRangesBytes      = False
+    }
+
+updateServerCapabilities :: ServerCapabilities -> [HttpResponseHeader] -> IO ()
+updateServerCapabilities (SC mv) responseHeaders = modifyMVar_ mv $ \caps ->
+    return $ caps {
+        serverAcceptRangesBytes = serverAcceptRangesBytes caps
+          || HttpResponseAcceptRangesBytes `elem` responseHeaders
+      }
+
+checkServerCapability :: MonadIO m
+                      => ServerCapabilities -> (ServerCapabilities_ -> a) -> m a
+checkServerCapability (SC mv) f = liftIO $ withMVar mv $ return . f
+
+{-------------------------------------------------------------------------------
+  File size
+-------------------------------------------------------------------------------}
+
+data FileSize =
+    -- | For most files we download we know the exact size beforehand
+    -- (because this information comes from the snapshot or delegated info)
+    FileSizeExact Int54
+
+    -- | For some files we might not know the size beforehand, but we might
+    -- be able to provide an upper bound (timestamp, root info)
+  | FileSizeBound Int54
+  deriving Show
+
+fileSizeWithinBounds :: Int54 -> FileSize -> Bool
+fileSizeWithinBounds sz (FileSizeExact sz') = sz <= sz'
+fileSizeWithinBounds sz (FileSizeBound sz') = sz <= sz'
+
+{-------------------------------------------------------------------------------
+  Top-level API
+-------------------------------------------------------------------------------}
+
+-- | Repository options with a reasonable default
+--
+-- Clients should use 'defaultRepositoryOpts' and override required settings.
+data RepoOpts = RepoOpts {
+      -- | Allow additional mirrors?
+      --
+      -- If this is set to True (default), in addition to the (out-of-band)
+      -- specified mirrors we will also use mirrors reported by those
+      -- out-of-band mirrors (that is, @mirrors.json@).
+      repoAllowAdditionalMirrors :: Bool
+    }
+
+-- | Default repository options
+defaultRepoOpts :: RepoOpts
+defaultRepoOpts = RepoOpts {
+      repoAllowAdditionalMirrors = True
+    }
+
+-- | Initialize the repository (and cleanup resources afterwards)
+--
+-- We allow to specify multiple mirrors to initialize the repository. These
+-- are mirrors that can be found "out of band" (out of the scope of the TUF
+-- protocol), for example in a @cabal.config@ file. The TUF protocol itself
+-- will specify that any of these mirrors can serve a @mirrors.json@ file
+-- that itself contains mirrors; we consider these as _additional_ mirrors
+-- to the ones that are passed here.
+--
+-- NOTE: The list of mirrors should be non-empty (and should typically include
+-- the primary server).
+--
+-- TODO: In the future we could allow finer control over precisely which
+-- mirrors we use (which combination of the mirrors that are passed as arguments
+-- here and the mirrors that we get from @mirrors.json@) as well as indicating
+-- mirror preferences.
+withRepository
+  :: HttpLib                          -- ^ Implementation of the HTTP protocol
+  -> [URI]                            -- ^ "Out of band" list of mirrors
+  -> RepoOpts                         -- ^ Repository options
+  -> Cache                            -- ^ Location of local cache
+  -> RepoLayout                       -- ^ Repository layout
+  -> IndexLayout                      -- ^ Index layout
+  -> (LogMessage -> IO ())            -- ^ Logger
+  -> (Repository RemoteTemp -> IO a)  -- ^ Callback
+  -> IO a
+withRepository httpLib
+               outOfBandMirrors
+               repoOpts
+               cache
+               repLayout
+               repIndexLayout
+               logger
+               callback
+               = do
+    selectedMirror <- newMVar Nothing
+    caps <- newServerCapabilities
+    let remoteConfig mirror = RemoteConfig {
+                                  cfgLayout   = repLayout
+                                , cfgHttpLib  = httpLib
+                                , cfgBase     = mirror
+                                , cfgCache    = cache
+                                , cfgCaps     = caps
+                                , cfgLogger   = liftIO . logger
+                                , cfgOpts     = repoOpts
+                                }
+    callback Repository {
+        repGetRemote     = getRemote remoteConfig selectedMirror
+      , repGetCached     = Cache.getCached     cache
+      , repGetCachedRoot = Cache.getCachedRoot cache
+      , repClearCache    = Cache.clearCache    cache
+      , repWithIndex     = Cache.withIndex     cache
+      , repGetIndexIdx   = Cache.getIndexIdx   cache
+      , repLockCache     = Cache.lockCache     cache
+      , repWithMirror    = withMirror httpLib
+                                      selectedMirror
+                                      logger
+                                      outOfBandMirrors
+                                      repoOpts
+      , repLog           = logger
+      , repLayout        = repLayout
+      , repIndexLayout   = repIndexLayout
+      , repDescription   = "Remote repository at " ++ show outOfBandMirrors
+      }
+
+{-------------------------------------------------------------------------------
+  Implementations of the various methods of Repository
+-------------------------------------------------------------------------------}
+
+-- | We select a mirror in 'withMirror' (the implementation of 'repWithMirror').
+-- Outside the scope of 'withMirror' no mirror is selected, and a call to
+-- 'getRemote' will throw an exception. If this exception is ever thrown its
+-- a bug: calls to 'getRemote' ('repGetRemote') should _always_ be in the
+-- scope of 'repWithMirror'.
+type SelectedMirror = MVar (Maybe URI)
+
+-- | Get the selected mirror
+--
+-- Throws an exception if no mirror was selected (this would be a bug in the
+-- client code).
+--
+-- NOTE: Cannot use 'withMVar' here, because the callback would be inside the
+-- scope of the withMVar, and there might be further calls to 'withRemote' made
+-- by the callback argument to 'withRemote', leading to deadlock.
+getSelectedMirror :: SelectedMirror -> IO URI
+getSelectedMirror selectedMirror = do
+     mBaseURI <- readMVar selectedMirror
+     case mBaseURI of
+       Nothing      -> internalError "Internal error: no mirror selected"
+       Just baseURI -> return baseURI
+
+-- | Get a file from the server
+getRemote :: Throws SomeRemoteError
+          => (URI -> RemoteConfig)
+          -> SelectedMirror
+          -> AttemptNr
+          -> RemoteFile fs typ
+          -> Verify (Some (HasFormat fs), RemoteTemp typ)
+getRemote remoteConfig selectedMirror attemptNr remoteFile = do
+    baseURI <- liftIO $ getSelectedMirror selectedMirror
+    let cfg = remoteConfig baseURI
+    downloadMethod <- liftIO $ pickDownloadMethod cfg attemptNr remoteFile
+    getFile cfg attemptNr remoteFile downloadMethod
+
+-- | HTTP options
+--
+-- We want to make sure caches don't transform files in any way (as this will
+-- mess things up with respect to hashes etc). Additionally, after a validation
+-- error we want to make sure caches get files upstream in case the validation
+-- error was because the cache updated files out of order.
+httpRequestHeaders :: RemoteConfig -> AttemptNr -> [HttpRequestHeader]
+httpRequestHeaders RemoteConfig{..} attemptNr =
+    if attemptNr == 0 then defaultHeaders
+                      else HttpRequestMaxAge0 : defaultHeaders
+  where
+    -- Headers we provide for _every_ attempt, first or not
+    defaultHeaders :: [HttpRequestHeader]
+    defaultHeaders = [HttpRequestNoTransform]
+
+-- | Mirror selection
+withMirror :: forall a.
+              HttpLib                -- ^ HTTP client
+           -> SelectedMirror         -- ^ MVar indicating currently mirror
+           -> (LogMessage -> IO ())  -- ^ Logger
+           -> [URI]                  -- ^ Out-of-band mirrors
+           -> RepoOpts               -- ^ Repository options
+           -> Maybe [Mirror]         -- ^ TUF mirrors
+           -> IO a                   -- ^ Callback
+           -> IO a
+withMirror HttpLib{..}
+           selectedMirror
+           logger
+           oobMirrors
+           repoOpts
+           tufMirrors
+           callback
+           =
+    go orderedMirrors
+  where
+    go :: [URI] -> IO a
+    -- Empty list of mirrors is a bug
+    go [] = internalError "No mirrors configured"
+    -- If we only have a single mirror left, let exceptions be thrown up
+    go [m] = do
+      logger $ LogSelectedMirror (show m)
+      select m $ callback
+    -- Otherwise, catch exceptions and if any were thrown, try with different
+    -- mirror
+    go (m:ms) = do
+      logger $ LogSelectedMirror (show m)
+      catchChecked (select m callback) $ \ex -> do
+        logger $ LogMirrorFailed (show m) ex
+        go ms
+
+    -- TODO: We will want to make the construction of this list configurable.
+    orderedMirrors :: [URI]
+    orderedMirrors = nub $ concat [
+        oobMirrors
+      , if repoAllowAdditionalMirrors repoOpts
+          then maybe [] (map mirrorUrlBase) tufMirrors
+          else []
+      ]
+
+    select :: URI -> IO a -> IO a
+    select uri =
+      bracket_ (modifyMVar_ selectedMirror $ \_ -> return $ Just uri)
+               (modifyMVar_ selectedMirror $ \_ -> return Nothing)
+
+{-------------------------------------------------------------------------------
+  Download methods
+-------------------------------------------------------------------------------}
+
+-- | Download method (downloading or updating)
+data DownloadMethod :: * -> * -> * where
+    -- | Download this file (we never attempt to update this type of file)
+    NeverUpdated :: {
+        neverUpdatedFormat :: HasFormat fs f
+      } -> DownloadMethod fs typ
+
+    -- | Download this file (we cannot update this file right now)
+    CannotUpdate :: {
+        cannotUpdateFormat :: HasFormat fs f
+      , cannotUpdateReason :: UpdateFailure
+      } -> DownloadMethod fs Binary
+
+    -- | Attempt an (incremental) update of this file
+    Update :: {
+        updateFormat :: HasFormat fs f
+      , updateInfo   :: Trusted FileInfo
+      , updateLocal  :: Path Absolute
+      , updateTail   :: Int54
+      } -> DownloadMethod fs Binary
+
+pickDownloadMethod :: forall fs typ. RemoteConfig
+                   -> AttemptNr
+                   -> RemoteFile fs typ
+                   -> IO (DownloadMethod fs typ)
+pickDownloadMethod RemoteConfig{..} attemptNr remoteFile =
+    case remoteFile of
+      RemoteTimestamp        -> return $ NeverUpdated (HFZ FUn)
+      (RemoteRoot _)         -> return $ NeverUpdated (HFZ FUn)
+      (RemoteSnapshot _)     -> return $ NeverUpdated (HFZ FUn)
+      (RemoteMirrors _)      -> return $ NeverUpdated (HFZ FUn)
+      (RemotePkgTarGz _ _)   -> return $ NeverUpdated (HFZ FGz)
+      (RemoteIndex hasGz formats) -> multipleExitPoints $ do
+        -- Server must support @Range@ with a byte-range
+        rangeSupport <- checkServerCapability cfgCaps serverAcceptRangesBytes
+        unless rangeSupport $ exit $ CannotUpdate hasGz UpdateImpossibleUnsupported
+
+        -- We must already have a local file to be updated
+        mCachedIndex <- lift $ Cache.getCachedIndex cfgCache (hasFormatGet hasGz)
+        cachedIndex  <- case mCachedIndex of
+          Nothing -> exit $ CannotUpdate hasGz UpdateImpossibleNoLocalCopy
+          Just fp -> return fp
+
+        -- We attempt an incremental update a maximum of 2 times
+        -- See 'UpdateFailedTwice' for details.
+        when (attemptNr >= 2) $ exit $ CannotUpdate hasGz UpdateFailedTwice
+
+        -- If all these checks pass try to do an incremental update.
+        return Update {
+             updateFormat = hasGz
+           , updateInfo   = formatsLookup hasGz formats
+           , updateLocal  = cachedIndex
+           , updateTail   = 65536 -- max gzip block size
+           }
+
+-- | Download the specified file using the given download method
+getFile :: forall fs typ. Throws SomeRemoteError
+        => RemoteConfig          -- ^ Internal configuration
+        -> AttemptNr             -- ^ Did a security check previously fail?
+        -> RemoteFile fs typ     -- ^ File to get
+        -> DownloadMethod fs typ -- ^ Selected format
+        -> Verify (Some (HasFormat fs), RemoteTemp typ)
+getFile cfg@RemoteConfig{..} attemptNr remoteFile method =
+    go method
+  where
+    go :: Throws SomeRemoteError
+       => DownloadMethod fs typ -> Verify (Some (HasFormat fs), RemoteTemp typ)
+    go NeverUpdated{..} = do
+        cfgLogger $ LogDownloading remoteFile
+        download neverUpdatedFormat
+    go CannotUpdate{..} = do
+        cfgLogger $ LogCannotUpdate remoteFile cannotUpdateReason
+        cfgLogger $ LogDownloading remoteFile
+        download cannotUpdateFormat
+    go Update{..} = do
+        cfgLogger $ LogUpdating remoteFile
+        update updateFormat updateInfo updateLocal updateTail
+
+    headers :: [HttpRequestHeader]
+    headers = httpRequestHeaders cfg attemptNr
+
+    -- Get any file from the server, without using incremental updates
+    download :: Throws SomeRemoteError => HasFormat fs f
+             -> Verify (Some (HasFormat fs), RemoteTemp typ)
+    download format = do
+        (tempPath, h) <- openTempFile (Cache.cacheRoot cfgCache) (uriTemplate uri)
+        liftIO $ do
+          httpGet headers uri $ \responseHeaders bodyReader -> do
+            updateServerCapabilities cfgCaps responseHeaders
+            execBodyReader targetPath sz h bodyReader
+          hClose h
+        cacheIfVerified format $ DownloadedWhole tempPath
+      where
+        targetPath = TargetPathRepo $ remoteRepoPath' cfgLayout remoteFile format
+        uri = formatsLookup format $ remoteFileURI cfgLayout cfgBase remoteFile
+        sz  = formatsLookup format $ remoteFileSize remoteFile
+
+    -- Get a file incrementally
+    update :: (typ ~ Binary)
+           => HasFormat fs f    -- ^ Selected format
+           -> Trusted FileInfo  -- ^ Expected info
+           -> Path Absolute     -- ^ Location of cached file (after callback)
+           -> Int54             -- ^ How much of the tail to overwrite
+           -> Verify (Some (HasFormat fs), RemoteTemp typ)
+    update format info cachedFile fileTail = do
+        currentSz <- liftIO $ getFileSize cachedFile
+        let fileSz    = fileLength' info
+            range     = (0 `max` (currentSz - fileTail), fileSz)
+            range'    = (fromIntegral (fst range), fromIntegral (snd range))
+            cacheRoot = Cache.cacheRoot cfgCache
+        (tempPath, h) <- openTempFile cacheRoot (uriTemplate uri)
+        statusCode <- liftIO $
+          httpGetRange headers uri range' $ \statusCode responseHeaders bodyReader -> do
+            updateServerCapabilities cfgCaps responseHeaders
+            let expectedSize =
+                  case statusCode of
+                    HttpStatus206PartialContent ->
+                      FileSizeExact (snd range - fst range)
+                    HttpStatus200OK ->
+                      FileSizeExact fileSz
+            execBodyReader targetPath expectedSize h bodyReader
+            hClose h
+            return statusCode
+        let downloaded =
+              case statusCode of
+                HttpStatus206PartialContent ->
+                  DownloadedDelta {
+                      deltaTemp     = tempPath
+                    , deltaExisting = cachedFile
+                    , deltaSeek     = fst range
+                    }
+                HttpStatus200OK ->
+                  DownloadedWhole tempPath
+        cacheIfVerified format downloaded
+      where
+        targetPath = TargetPathRepo repoPath
+        uri        = modifyUriPath cfgBase (`anchorRepoPathRemotely` repoPath)
+        repoPath   = remoteRepoPath' cfgLayout remoteFile format
+
+    cacheIfVerified :: HasFormat fs f -> RemoteTemp typ
+                    -> Verify (Some (HasFormat fs), RemoteTemp typ)
+    cacheIfVerified format remoteTemp = do
+        ifVerified $
+          Cache.cacheRemoteFile cfgCache
+                                remoteTemp
+                                (hasFormatGet format)
+                                (mustCache remoteFile)
+        return (Some format, remoteTemp)
+
+    HttpLib{..} = cfgHttpLib
+
+{-------------------------------------------------------------------------------
+  Execute body reader
+-------------------------------------------------------------------------------}
+
+-- | Execute a body reader
+--
+-- TODO: Deal with minimum download rate.
+execBodyReader :: Throws SomeRemoteError
+               => TargetPath  -- ^ File source (for error msgs only)
+               -> FileSize    -- ^ Maximum file size
+               -> Handle      -- ^ Handle to write data too
+               -> BodyReader  -- ^ The action to give us blocks from the file
+               -> IO ()
+execBodyReader file mlen h br = go 0
+  where
+    go :: Int54 -> IO ()
+    go sz = do
+      unless (sz `fileSizeWithinBounds` mlen) $
+        throwChecked $ SomeRemoteError $ FileTooLarge file mlen
+      bs <- br
+      if BS.null bs
+        then return ()
+        else BS.hPut h bs >> go (sz + fromIntegral (BS.length bs))
+
+-- | The file we requested from the server was larger than expected
+-- (potential endless data attack)
+data FileTooLarge = FileTooLarge {
+    fileTooLargePath     :: TargetPath
+  , fileTooLargeExpected :: FileSize
+  }
+  deriving (Typeable)
+
+instance Pretty FileTooLarge where
+  pretty FileTooLarge{..} = concat [
+      "file returned by server too large: "
+    , pretty fileTooLargePath
+    , " (expected " ++ expected fileTooLargeExpected ++ " bytes)"
+    ]
+    where
+      expected :: FileSize -> String
+      expected (FileSizeExact n) = "exactly " ++ show n
+      expected (FileSizeBound n) = "at most " ++ show n
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show FileTooLarge
+instance Exception FileTooLarge where displayException = pretty
+#else
+instance Exception FileTooLarge
+instance Show FileTooLarge where show = pretty
+#endif
+
+{-------------------------------------------------------------------------------
+  Information about remote files
+-------------------------------------------------------------------------------}
+
+remoteFileURI :: RepoLayout -> URI -> RemoteFile fs typ -> Formats fs URI
+remoteFileURI repoLayout baseURI = fmap aux . remoteRepoPath repoLayout
+  where
+    aux :: RepoPath -> URI
+    aux repoPath = modifyUriPath baseURI (`anchorRepoPathRemotely` repoPath)
+
+-- | Extracting or estimating file sizes
+remoteFileSize :: RemoteFile fs typ -> Formats fs FileSize
+remoteFileSize (RemoteTimestamp) =
+    FsUn $ FileSizeBound fileSizeBoundTimestamp
+remoteFileSize (RemoteRoot mLen) =
+    FsUn $ maybe (FileSizeBound fileSizeBoundRoot)
+                 (FileSizeExact . fileLength')
+                 mLen
+remoteFileSize (RemoteSnapshot len) =
+    FsUn $ FileSizeExact (fileLength' len)
+remoteFileSize (RemoteMirrors len) =
+    FsUn $ FileSizeExact (fileLength' len)
+remoteFileSize (RemoteIndex _ lens) =
+    fmap (FileSizeExact . fileLength') lens
+remoteFileSize (RemotePkgTarGz _pkgId len) =
+    FsGz $ FileSizeExact (fileLength' len)
+
+-- | Bound on the size of the timestamp
+--
+-- This is intended as a permissive rather than tight bound.
+--
+-- The timestamp signed with a single key is 420 bytes; the signature makes up
+-- just under 200 bytes of that. So even if the timestamp is signed with 10
+-- keys it would still only be 2420 bytes. Doubling this amount, an upper bound
+-- of 4kB should definitely be sufficient.
+fileSizeBoundTimestamp :: Int54
+fileSizeBoundTimestamp = 4096
+
+-- | Bound on the size of the root
+--
+-- This is intended as a permissive rather than tight bound.
+--
+-- The variable parts of the root metadata are
+--
+-- * Signatures, each of which are about 200 bytes
+-- * A key environment (mapping from key IDs to public keys), each is of
+--   which is also about 200 bytes
+-- * Mirrors, root, snapshot, targets, and timestamp role specifications.
+--   These contains key IDs, each of which is about 80 bytes.
+--
+-- A skeleton root metadata is about 580 bytes. Allowing for
+--
+-- * 100 signatures
+-- * 100 mirror keys, 1000 root keys, 100 snapshot keys, 1000 target keys,
+--   100 timestamp keys
+-- * the corresponding 2300 entries in the key environment
+--
+-- We end up with a bound of about 665,000 bytes. Doubling this amount, an
+-- upper bound of 2MB should definitely be sufficient.
+fileSizeBoundRoot :: Int54
+fileSizeBoundRoot = 2 * 1024 * 2014
+
+{-------------------------------------------------------------------------------
+  Configuration
+-------------------------------------------------------------------------------}
+
+-- | Remote repository configuration
+--
+-- This is purely for internal convenience.
+data RemoteConfig = RemoteConfig {
+      cfgLayout   :: RepoLayout
+    , cfgHttpLib  :: HttpLib
+    , cfgBase     :: URI
+    , cfgCache    :: Cache
+    , cfgCaps     :: ServerCapabilities
+    , cfgLogger   :: forall m. MonadIO m => LogMessage -> m ()
+    , cfgOpts     :: RepoOpts
+    }
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Template for the local file we use to download a URI to
+uriTemplate :: URI -> String
+uriTemplate = takeFileName . uriPath
+
+fileLength' :: Trusted FileInfo -> Int54
+fileLength' = fileLength . fileInfoLength . trusted
+
+{-------------------------------------------------------------------------------
+  Files downloaded from the remote repository
+-------------------------------------------------------------------------------}
+
+data RemoteTemp :: * -> * where
+    DownloadedWhole :: {
+        wholeTemp :: Path Absolute
+      } -> RemoteTemp a
+
+    -- | If we download only the delta, we record both the path to where the
+    -- "old" file is stored and the path to the temp file containing the delta.
+    -- Then:
+    --
+    -- * When we verify the file, we need both of these paths if we compute
+    --   the hash from scratch, or only the path to the delta if we attempt
+    --   to compute the hash incrementally (TODO: incremental verification
+    --   not currently implemented).
+    -- * When we copy a file over, we are additionally given a destination
+    --   path. In this case, we expect that destination path to be equal to
+    --   the path to the old file (and assert this to be the case).
+    DownloadedDelta :: {
+        deltaTemp     :: Path Absolute
+      , deltaExisting :: Path Absolute
+      , deltaSeek     :: Int54       -- ^ How much of the existing file to keep
+      } -> RemoteTemp Binary
+
+instance Pretty (RemoteTemp typ) where
+    pretty DownloadedWhole{..} = intercalate " " $ [
+        "DownloadedWhole"
+      , pretty wholeTemp
+      ]
+    pretty DownloadedDelta{..} = intercalate " " $ [
+        "DownloadedDelta"
+      , pretty deltaTemp
+      , pretty deltaExisting
+      , show deltaSeek
+      ]
+
+instance DownloadedFile RemoteTemp where
+  downloadedVerify = verifyRemoteFile
+  downloadedRead   = readLazyByteString . wholeTemp
+  downloadedCopyTo = \f dest ->
+    case f of
+      DownloadedWhole{..} ->
+        renameFile wholeTemp dest
+      DownloadedDelta{..} -> do
+        unless (deltaExisting == dest) $
+          throwIO $ userError "Assertion failure: deltaExisting /= dest"
+        -- We need ReadWriteMode in order to be able to seek
+        withFile deltaExisting ReadWriteMode $ \h -> do
+          hSeek h AbsoluteSeek (fromIntegral deltaSeek)
+          BS.L.hPut h =<< readLazyByteString deltaTemp
+
+-- | Verify a file downloaded from the remote repository
+--
+-- TODO: This currently still computes the hash for the whole file. If we cached
+-- the state of the hash generator we could compute the hash incrementally.
+-- However, profiling suggests that this would only be a minor improvement.
+verifyRemoteFile :: RemoteTemp typ -> Trusted FileInfo -> IO Bool
+verifyRemoteFile remoteTemp trustedInfo = do
+    sz <- FileLength <$> remoteSize remoteTemp
+    if sz /= fileInfoLength
+      then return False
+      else withRemoteBS remoteTemp $ knownFileInfoEqual info . fileInfo
+  where
+    remoteSize :: RemoteTemp typ -> IO Int54
+    remoteSize DownloadedWhole{..} = getFileSize wholeTemp
+    remoteSize DownloadedDelta{..} = do
+        deltaSize <- getFileSize deltaTemp
+        return $ deltaSeek + deltaSize
+
+    -- It is important that we close the file handles when we're done
+    -- (esp. since we may not read the whole file)
+    withRemoteBS :: RemoteTemp typ -> (BS.L.ByteString -> Bool) -> IO Bool
+    withRemoteBS DownloadedWhole{..} callback = do
+        withFile wholeTemp ReadMode $ \h -> do
+          bs <- BS.L.hGetContents h
+          evaluate $ callback bs
+    withRemoteBS DownloadedDelta{..} callback =
+        withFile deltaExisting ReadMode $ \hExisting ->
+          withFile deltaTemp ReadMode $ \hTemp -> do
+            existing <- BS.L.hGetContents hExisting
+            temp     <- BS.L.hGetContents hTemp
+            evaluate $ callback $ BS.L.concat [
+                BS.L.take (fromIntegral deltaSeek) existing
+              , temp
+              ]
+
+    info@FileInfo{..} = trusted trustedInfo
+
+{-------------------------------------------------------------------------------
+  Auxiliary: multiple exit points
+-------------------------------------------------------------------------------}
+
+-- | Multiple exit points
+--
+-- We can simulate the imperative code
+--
+-- > if (cond1)
+-- >   return exp1;
+-- > if (cond2)
+-- >   return exp2;
+-- > if (cond3)
+-- >   return exp3;
+-- > return exp4;
+--
+-- as
+--
+-- > multipleExitPoints $ do
+-- >   when (cond1) $
+-- >     exit exp1
+-- >   when (cond) $
+-- >     exit exp2
+-- >   when (cond)
+-- >     exit exp3
+-- >   return exp4
+multipleExitPoints :: Monad m => ExceptT a m a -> m a
+multipleExitPoints = liftM aux . runExceptT
+  where
+    aux :: Either a a -> a
+    aux (Left  a) = a
+    aux (Right a) = a
+
+-- | Function exit point (see 'multipleExitPoints')
+exit :: Monad m => e -> ExceptT e m a
+exit = throwError
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Client/Verify.hs b/hackage-security/hackage-security/src/Hackage/Security/Client/Verify.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Client/Verify.hs
@@ -0,0 +1,103 @@
+module Hackage.Security.Client.Verify (
+    -- * Verification monad
+    Verify -- opaque
+  , runVerify
+  , acquire
+  , ifVerified
+    -- * Specific resources
+  , openTempFile
+    -- * Re-exports
+  , liftIO
+  ) where
+
+import Control.Exception
+import Control.Monad.Reader
+import Data.IORef
+
+import Hackage.Security.Util.IO
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Verification monad
+-------------------------------------------------------------------------------}
+
+type Finaliser = IO ()
+type Cleanup   = IO ()
+
+-- | Verification monad
+--
+-- The verification monad is similar to 'ResourceT' in intent, in that we can
+-- register handlers to be run to release resources. Unlike 'ResourceT',
+-- however, we maintain _two_ handlers: a cleanup handler which is run  whether
+-- or not verification succeeds, and a finalisation handler which is run only if
+-- verification succeeds.
+--
+-- * Cleanup handlers are registered using 'acquire', and are guaranteed to run
+--   just before the computation terminates (after the finalisation handler).
+-- * The finalisation handlers are run only when verification succeeds, and can
+--   be registered with 'ifVerified'. Finalisation can be used for instance to
+--   update the local cache (which should only happen if verification is
+--   successful).
+newtype Verify a = Verify {
+    unVerify :: ReaderT (IORef Cleanup, IORef Finaliser) IO a
+  }
+  deriving (Functor, Applicative, Monad, MonadIO)
+
+-- | Run an action in the 'Verify' monad
+runVerify :: (Finaliser -> Finaliser) -> Verify a -> IO a
+runVerify modifyFinaliser v = do
+    rCleanup   <- newIORef $ return ()
+    rFinaliser <- newIORef $ return ()
+    mask $ \restore -> do
+      ma <- try $ restore $ runReaderT (unVerify v) (rCleanup, rFinaliser)
+      case ma of
+        Left ex -> do join $ readIORef rCleanup
+                      throwIO (ex :: SomeException)
+        Right a -> do modifyFinaliser $ join $ readIORef rFinaliser
+                      join $ readIORef rCleanup
+                      return a
+
+-- | Acquire a resource and register the corresponding cleanup handler
+--
+-- NOTE: Resource acquisition happens with exceptions masked. If it is important
+-- that the resource acquistion can be timed out (or receive other kinds of
+-- asynchronous exceptions), you will need to use an interruptible operation.
+-- See <http://www.well-typed.com/blog/2014/08/asynchronous-exceptions/> for
+-- details.
+acquire :: IO a -> (a -> IO ()) -> Verify a
+acquire get release = Verify $ do
+    (rCleanup, _rFinaliser) <- ask
+    liftIO $ mask_ $ do
+      a <- liftIO get
+      modifyIORef rCleanup (>> release a)
+      return a
+
+-- | Register an action to be run only if verification succeeds
+ifVerified :: IO () -> Verify ()
+ifVerified handler = Verify $ do
+    (_rCleanup, rFinaliser) <- ask
+    liftIO $ modifyIORef rFinaliser (>> handler)
+
+{-------------------------------------------------------------------------------
+  Specific resources
+-------------------------------------------------------------------------------}
+
+-- | Create a short-lived temporary file
+--
+-- Creates the directory where the temp file should live if it does not exist.
+openTempFile :: FsRoot root
+             => Path root  -- ^ Temp directory
+             -> String     -- ^ Template
+             -> Verify (Path Absolute, Handle)
+openTempFile tmpDir template =
+    acquire createTempFile closeAndDelete
+  where
+    createTempFile :: IO (Path Absolute, Handle)
+    createTempFile = do
+      createDirectoryIfMissing True tmpDir
+      openTempFile' tmpDir template
+
+    closeAndDelete :: (Path Absolute, Handle) -> IO ()
+    closeAndDelete (fp, h) = do
+      hClose h
+      void $ handleDoesNotExist $ removeFile fp
diff --git a/hackage-security/hackage-security/src/Hackage/Security/JSON.hs b/hackage-security/hackage-security/src/Hackage/Security/JSON.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/JSON.hs
@@ -0,0 +1,323 @@
+-- | Hackage-specific wrappers around the Util.JSON module
+{-# LANGUAGE CPP #-}
+module Hackage.Security.JSON (
+    -- * Deserialization errors
+    DeserializationError(..)
+  , validate
+  , verifyType
+    -- * MonadKeys
+  , MonadKeys(..)
+  , addKeys
+  , withKeys
+  , lookupKey
+  , readKeyAsId
+    -- * Reader monads
+  , ReadJSON_Keys_Layout
+  , ReadJSON_Keys_NoLayout
+  , ReadJSON_NoKeys_NoLayout
+  , runReadJSON_Keys_Layout
+  , runReadJSON_Keys_NoLayout
+  , runReadJSON_NoKeys_NoLayout
+    -- ** Utility
+  , parseJSON_Keys_Layout
+  , parseJSON_Keys_NoLayout
+  , parseJSON_NoKeys_NoLayout
+  , readJSON_Keys_Layout
+  , readJSON_Keys_NoLayout
+  , readJSON_NoKeys_NoLayout
+    -- * Writing
+  , WriteJSON
+  , runWriteJSON
+    -- ** Utility
+  , renderJSON
+  , renderJSON_NoLayout
+  , writeJSON
+  , writeJSON_NoLayout
+  , writeKeyAsId
+    -- * Re-exports
+  , module Hackage.Security.Util.JSON
+  ) where
+
+import Control.Arrow (first, second)
+import Control.Exception
+import Control.Monad.Except
+import Control.Monad.Reader
+import Data.Functor.Identity
+import Data.Typeable (Typeable)
+import qualified Data.ByteString.Lazy as BS.L
+
+import Hackage.Security.Key
+import Hackage.Security.Key.Env (KeyEnv)
+import Hackage.Security.TUF.Layout.Repo
+import Hackage.Security.Util.JSON
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+import Text.JSON.Canonical
+import qualified Hackage.Security.Key.Env as KeyEnv
+
+{-------------------------------------------------------------------------------
+  Deserialization errors
+-------------------------------------------------------------------------------}
+
+data DeserializationError =
+    -- | Malformed JSON has syntax errors in the JSON itself
+    -- (i.e., we cannot even parse it to a JSValue)
+    DeserializationErrorMalformed String
+
+    -- | Invalid JSON has valid syntax but invalid structure
+    --
+    -- The string gives a hint about what we expected instead
+  | DeserializationErrorSchema String
+
+    -- | The JSON file contains a key ID of an unknown key
+  | DeserializationErrorUnknownKey KeyId
+
+    -- | Some verification step failed
+  | DeserializationErrorValidation String
+
+    -- | Wrong file type
+    --
+    -- Records actual and expected types.
+  | DeserializationErrorFileType String String
+  deriving (Typeable)
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show DeserializationError
+instance Exception DeserializationError where displayException = pretty
+#else
+instance Show DeserializationError where show = pretty
+instance Exception DeserializationError
+#endif
+
+instance Pretty DeserializationError where
+  pretty (DeserializationErrorMalformed str) =
+      "Malformed: " ++ str
+  pretty (DeserializationErrorSchema str) =
+      "Schema error: " ++ str
+  pretty (DeserializationErrorUnknownKey kId) =
+      "Unknown key: " ++ keyIdString kId
+  pretty (DeserializationErrorValidation str) =
+      "Invalid: " ++ str
+  pretty (DeserializationErrorFileType actualType expectedType) =
+         "Expected file of type " ++ show expectedType
+      ++ " but got file of type " ++ show actualType
+
+validate :: MonadError DeserializationError m => String -> Bool -> m ()
+validate _   True  = return ()
+validate msg False = throwError $ DeserializationErrorValidation msg
+
+verifyType :: (ReportSchemaErrors m, MonadError DeserializationError m)
+           => JSValue -> String -> m ()
+verifyType enc expectedType = do
+    actualType <- fromJSField enc "_type"
+    unless (actualType == expectedType) $
+      throwError $ DeserializationErrorFileType actualType expectedType
+
+{-------------------------------------------------------------------------------
+  Access to keys
+-------------------------------------------------------------------------------}
+
+-- | MonadReader-like monad, specialized to key environments
+class (ReportSchemaErrors m, MonadError DeserializationError m) => MonadKeys m where
+  localKeys :: (KeyEnv -> KeyEnv) -> m a -> m a
+  askKeys   :: m KeyEnv
+
+readKeyAsId :: MonadKeys m => JSValue -> m (Some PublicKey)
+readKeyAsId (JSString kId) = lookupKey (KeyId kId)
+readKeyAsId val            = expected' "key ID" val
+
+addKeys :: MonadKeys m => KeyEnv -> m a -> m a
+addKeys keys = localKeys (KeyEnv.union keys)
+
+withKeys :: MonadKeys m => KeyEnv -> m a -> m a
+withKeys keys = localKeys (const keys)
+
+lookupKey :: MonadKeys m => KeyId -> m (Some PublicKey)
+lookupKey kId = do
+    keyEnv <- askKeys
+    case KeyEnv.lookup kId keyEnv of
+      Just key -> return key
+      Nothing  -> throwError $ DeserializationErrorUnknownKey kId
+
+{-------------------------------------------------------------------------------
+  Reading
+-------------------------------------------------------------------------------}
+
+newtype ReadJSON_Keys_Layout a = ReadJSON_Keys_Layout {
+    unReadJSON_Keys_Layout :: ExceptT DeserializationError (Reader (RepoLayout, KeyEnv)) a
+  }
+  deriving ( Functor
+           , Applicative
+           , Monad
+           , MonadError DeserializationError
+           )
+
+newtype ReadJSON_Keys_NoLayout a = ReadJSON_Keys_NoLayout {
+    unReadJSON_Keys_NoLayout :: ExceptT DeserializationError (Reader KeyEnv) a
+  }
+  deriving ( Functor
+           , Applicative
+           , Monad
+           , MonadError DeserializationError
+           )
+
+newtype ReadJSON_NoKeys_NoLayout a = ReadJSON_NoKeys_NoLayout {
+    unReadJSON_NoKeys_NoLayout :: Except DeserializationError a
+  }
+  deriving ( Functor
+           , Applicative
+           , Monad
+           , MonadError DeserializationError
+           )
+
+instance ReportSchemaErrors ReadJSON_Keys_Layout where
+  expected str mgot = throwError $ expectedError str mgot
+instance ReportSchemaErrors ReadJSON_Keys_NoLayout where
+  expected str mgot = throwError $ expectedError str mgot
+instance ReportSchemaErrors ReadJSON_NoKeys_NoLayout where
+  expected str mgot = throwError $ expectedError str mgot
+
+expectedError :: Expected -> Maybe Got -> DeserializationError
+expectedError str mgot = DeserializationErrorSchema msg
+  where
+    msg = case mgot of
+            Nothing  -> "Expected " ++ str
+            Just got -> "Expected " ++ str ++ " but got " ++ got
+
+instance MonadReader RepoLayout ReadJSON_Keys_Layout where
+  ask         = ReadJSON_Keys_Layout $ fst `liftM` ask
+  local f act = ReadJSON_Keys_Layout $ local (first f) act'
+    where
+      act' = unReadJSON_Keys_Layout act
+
+instance MonadKeys ReadJSON_Keys_Layout where
+  askKeys         = ReadJSON_Keys_Layout $ snd `liftM` ask
+  localKeys f act = ReadJSON_Keys_Layout $ local (second f) act'
+    where
+      act' = unReadJSON_Keys_Layout act
+
+instance MonadKeys ReadJSON_Keys_NoLayout where
+  askKeys         = ReadJSON_Keys_NoLayout $ ask
+  localKeys f act = ReadJSON_Keys_NoLayout $ local f act'
+    where
+      act' = unReadJSON_Keys_NoLayout act
+
+runReadJSON_Keys_Layout :: KeyEnv
+                        -> RepoLayout
+                        -> ReadJSON_Keys_Layout a
+                        -> Either DeserializationError a
+runReadJSON_Keys_Layout keyEnv repoLayout act =
+    runReader (runExceptT (unReadJSON_Keys_Layout act)) (repoLayout, keyEnv)
+
+runReadJSON_Keys_NoLayout :: KeyEnv
+                          -> ReadJSON_Keys_NoLayout a
+                          -> Either DeserializationError a
+runReadJSON_Keys_NoLayout keyEnv act =
+    runReader (runExceptT (unReadJSON_Keys_NoLayout act)) keyEnv
+
+runReadJSON_NoKeys_NoLayout :: ReadJSON_NoKeys_NoLayout a
+                            -> Either DeserializationError a
+runReadJSON_NoKeys_NoLayout act =
+    runExcept (unReadJSON_NoKeys_NoLayout act)
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+parseJSON_Keys_Layout :: FromJSON ReadJSON_Keys_Layout a
+                      => KeyEnv
+                      -> RepoLayout
+                      -> BS.L.ByteString
+                      -> Either DeserializationError a
+parseJSON_Keys_Layout keyEnv repoLayout bs =
+    case parseCanonicalJSON bs of
+      Left  err -> Left (DeserializationErrorMalformed err)
+      Right val -> runReadJSON_Keys_Layout keyEnv repoLayout (fromJSON val)
+
+parseJSON_Keys_NoLayout :: FromJSON ReadJSON_Keys_NoLayout a
+                        => KeyEnv
+                        -> BS.L.ByteString
+                        -> Either DeserializationError a
+parseJSON_Keys_NoLayout keyEnv bs =
+    case parseCanonicalJSON bs of
+      Left  err -> Left (DeserializationErrorMalformed err)
+      Right val -> runReadJSON_Keys_NoLayout keyEnv (fromJSON val)
+
+parseJSON_NoKeys_NoLayout :: FromJSON ReadJSON_NoKeys_NoLayout a
+                          => BS.L.ByteString
+                          -> Either DeserializationError a
+parseJSON_NoKeys_NoLayout bs =
+    case parseCanonicalJSON bs of
+      Left  err -> Left (DeserializationErrorMalformed err)
+      Right val -> runReadJSON_NoKeys_NoLayout (fromJSON val)
+
+readJSON_Keys_Layout :: ( FsRoot root
+                        , FromJSON ReadJSON_Keys_Layout a
+                        )
+                     => KeyEnv
+                     -> RepoLayout
+                     -> Path root
+                     -> IO (Either DeserializationError a)
+readJSON_Keys_Layout keyEnv repoLayout fp = do
+    withFile fp ReadMode $ \h -> do
+      bs <- BS.L.hGetContents h
+      evaluate $ parseJSON_Keys_Layout keyEnv repoLayout bs
+
+readJSON_Keys_NoLayout :: ( FsRoot root
+                          , FromJSON ReadJSON_Keys_NoLayout a
+                          )
+                       => KeyEnv
+                       -> Path root
+                       -> IO (Either DeserializationError a)
+readJSON_Keys_NoLayout keyEnv fp = do
+    withFile fp ReadMode $ \h -> do
+      bs <- BS.L.hGetContents h
+      evaluate $ parseJSON_Keys_NoLayout keyEnv bs
+
+readJSON_NoKeys_NoLayout :: ( FsRoot root
+                            , FromJSON ReadJSON_NoKeys_NoLayout a
+                            )
+                         => Path root
+                         -> IO (Either DeserializationError a)
+readJSON_NoKeys_NoLayout fp = do
+    withFile fp ReadMode $ \h -> do
+      bs <- BS.L.hGetContents h
+      evaluate $ parseJSON_NoKeys_NoLayout bs
+
+{-------------------------------------------------------------------------------
+  Writing
+-------------------------------------------------------------------------------}
+
+newtype WriteJSON a = WriteJSON {
+    unWriteJSON :: Reader RepoLayout a
+  }
+  deriving ( Functor
+           , Applicative
+           , Monad
+           , MonadReader RepoLayout
+           )
+
+runWriteJSON :: RepoLayout -> WriteJSON a -> a
+runWriteJSON repoLayout act = runReader (unWriteJSON act) repoLayout
+
+{-------------------------------------------------------------------------------
+  Writing: Utility
+-------------------------------------------------------------------------------}
+
+-- | Render to canonical JSON format
+renderJSON :: ToJSON WriteJSON a => RepoLayout -> a -> BS.L.ByteString
+renderJSON repoLayout = renderCanonicalJSON . runWriteJSON repoLayout . toJSON
+
+-- | Variation on 'renderJSON' for files that don't require the repo layout
+renderJSON_NoLayout :: ToJSON Identity a => a -> BS.L.ByteString
+renderJSON_NoLayout = renderCanonicalJSON . runIdentity . toJSON
+
+writeJSON :: ToJSON WriteJSON a => RepoLayout -> Path Absolute -> a -> IO ()
+writeJSON repoLayout fp = writeLazyByteString fp . renderJSON repoLayout
+
+writeJSON_NoLayout :: ToJSON Identity a => Path Absolute -> a -> IO ()
+writeJSON_NoLayout fp = writeLazyByteString fp . renderJSON_NoLayout
+
+writeKeyAsId :: Some PublicKey -> JSValue
+writeKeyAsId = JSString . keyIdString . someKeyId
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Key.hs b/hackage-security/hackage-security/src/Hackage/Security/Key.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Key.hs
@@ -0,0 +1,298 @@
+{-# LANGUAGE CPP #-}
+module Hackage.Security.Key (
+    -- * Key types
+    Ed25519
+    -- * Types abstracting over key types
+  , Key(..)
+  , PublicKey(..)
+  , PrivateKey(..)
+    -- * Key types in isolation
+  , KeyType(..)
+    -- * Hiding key types
+  , somePublicKey
+  , somePublicKeyType
+  , someKeyId
+    -- * Operations on keys
+  , publicKey
+  , privateKey
+  , createKey
+  , createKey'
+    -- * Key IDs
+  , KeyId(..)
+  , HasKeyId(..)
+    -- * Signing
+  , sign
+  , verify
+  ) where
+
+import Control.Monad
+import Data.Functor.Identity
+import Data.Typeable (Typeable)
+import Text.JSON.Canonical
+import qualified Crypto.Hash          as CH
+import qualified Crypto.Sign.Ed25519  as Ed25519
+import qualified Data.ByteString      as BS
+import qualified Data.ByteString.Lazy as BS.L
+
+#if !MIN_VERSION_base(4,7,0)
+import qualified Data.Typeable as Typeable
+#endif
+
+import Hackage.Security.Util.JSON
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.TypedEmbedded
+import qualified Hackage.Security.Util.Base64 as B64
+
+{-------------------------------------------------------------------------------
+  Generalization over key types
+-------------------------------------------------------------------------------}
+
+data Ed25519
+
+data Key a where
+    KeyEd25519 :: Ed25519.PublicKey -> Ed25519.SecretKey -> Key Ed25519
+  deriving (Typeable)
+
+data PublicKey a where
+    PublicKeyEd25519 :: Ed25519.PublicKey -> PublicKey Ed25519
+  deriving (Typeable)
+
+data PrivateKey a where
+    PrivateKeyEd25519 :: Ed25519.SecretKey -> PrivateKey Ed25519
+  deriving (Typeable)
+
+deriving instance Show (Key        typ)
+deriving instance Show (PublicKey  typ)
+deriving instance Show (PrivateKey typ)
+
+deriving instance Eq (Key        typ)
+deriving instance Eq (PublicKey  typ)
+deriving instance Eq (PrivateKey typ)
+
+instance SomeShow Key        where someShow = DictShow
+instance SomeShow PublicKey  where someShow = DictShow
+instance SomeShow PrivateKey where someShow = DictShow
+
+instance SomeEq Key        where someEq = DictEq
+instance SomeEq PublicKey  where someEq = DictEq
+instance SomeEq PrivateKey where someEq = DictEq
+
+publicKey :: Key a -> PublicKey a
+publicKey (KeyEd25519 pub _pri) = PublicKeyEd25519 pub
+
+privateKey :: Key a -> PrivateKey a
+privateKey (KeyEd25519 _pub pri) = PrivateKeyEd25519 pri
+
+{-------------------------------------------------------------------------------
+  Sometimes it's useful to talk about the type of a key independent of the key
+-------------------------------------------------------------------------------}
+
+data KeyType typ where
+  KeyTypeEd25519 :: KeyType Ed25519
+
+deriving instance Show (KeyType typ)
+deriving instance Eq   (KeyType typ)
+
+instance SomeShow KeyType where someShow = DictShow
+instance SomeEq   KeyType where someEq   = DictEq
+
+instance Unify KeyType where
+  unify KeyTypeEd25519 KeyTypeEd25519 = Just Refl
+
+type instance TypeOf Key        = KeyType
+type instance TypeOf PublicKey  = KeyType
+type instance TypeOf PrivateKey = KeyType
+
+instance Typed Key where
+  typeOf (KeyEd25519 _ _) = KeyTypeEd25519
+
+instance Typed PublicKey where
+  typeOf (PublicKeyEd25519 _) = KeyTypeEd25519
+
+instance Typed PrivateKey where
+  typeOf (PrivateKeyEd25519 _) = KeyTypeEd25519
+
+{-------------------------------------------------------------------------------
+  We don't always know the key type
+-------------------------------------------------------------------------------}
+
+somePublicKey :: Some Key -> Some PublicKey
+somePublicKey (Some key) = Some (publicKey key)
+
+somePublicKeyType :: Some PublicKey -> Some KeyType
+somePublicKeyType (Some pub) = Some (typeOf pub)
+
+someKeyId :: HasKeyId key => Some key -> KeyId
+someKeyId (Some a) = keyId a
+
+{-------------------------------------------------------------------------------
+  Creating keys
+-------------------------------------------------------------------------------}
+
+createKey :: KeyType key -> IO (Key key)
+createKey KeyTypeEd25519 = uncurry KeyEd25519 <$> Ed25519.createKeypair
+
+createKey' :: KeyType key -> IO (Some Key)
+createKey' = liftM Some . createKey
+
+{-------------------------------------------------------------------------------
+  Key IDs
+-------------------------------------------------------------------------------}
+
+-- | The key ID of a key, by definition, is the hexdigest of the SHA-256 hash of
+-- the canonical JSON form of the key where the private object key is excluded.
+--
+-- NOTE: The FromJSON and ToJSON instances for KeyId are ntentially omitted. Use
+-- writeKeyAsId instead.
+newtype KeyId = KeyId { keyIdString :: String }
+  deriving (Show, Eq, Ord)
+
+instance Monad m => ToObjectKey m KeyId where
+  toObjectKey = return . keyIdString
+
+instance Monad m => FromObjectKey m KeyId where
+  fromObjectKey = return . KeyId
+
+-- | Compute the key ID of a key
+class HasKeyId key where
+  keyId :: key typ -> KeyId
+
+instance HasKeyId PublicKey where
+  keyId = KeyId
+        . show
+        . (CH.hashlazy :: BS.L.ByteString -> CH.Digest CH.SHA256)
+        . renderCanonicalJSON
+        . runIdentity
+        . toJSON
+
+instance HasKeyId Key where
+  keyId = keyId . publicKey
+
+{-------------------------------------------------------------------------------
+  Signing
+-------------------------------------------------------------------------------}
+
+-- | Sign a bytestring and return the signature
+--
+-- TODO: It is unfortunate that we have to convert to a strict bytestring for
+-- ed25519
+sign :: PrivateKey typ -> BS.L.ByteString -> BS.ByteString
+sign (PrivateKeyEd25519 pri) =
+    Ed25519.unSignature . dsign pri . BS.concat . BS.L.toChunks
+  where
+#if MIN_VERSION_ed25519(0,0,4)
+    dsign = Ed25519.dsign
+#else
+    dsign = Ed25519.sign'
+#endif
+
+verify :: PublicKey typ -> BS.L.ByteString -> BS.ByteString -> Bool
+verify (PublicKeyEd25519 pub) inp sig =
+    dverify pub (BS.concat $ BS.L.toChunks inp) (Ed25519.Signature sig)
+  where
+#if MIN_VERSION_ed25519(0,0,4)
+    dverify = Ed25519.dverify
+#else
+    dverify = Ed25519.verify'
+#endif
+
+{-------------------------------------------------------------------------------
+  JSON encoding and decoding
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m (Key typ) where
+  toJSON key = case key of
+      KeyEd25519 pub pri ->
+        enc "ed25519" (Ed25519.unPublicKey pub) (Ed25519.unSecretKey pri)
+    where
+      enc :: String -> BS.ByteString -> BS.ByteString -> m JSValue
+      enc tag pub pri = mkObject [
+            ("keytype", return $ JSString tag)
+          , ("keyval", mkObject [
+                ("public",  toJSON (B64.fromByteString pub))
+              , ("private", toJSON (B64.fromByteString pri))
+              ])
+          ]
+
+instance ReportSchemaErrors m => FromJSON m (Some Key) where
+  fromJSON enc = do
+      (tag, pub, pri) <- dec enc
+      case tag of
+        "ed25519" -> return . Some $
+          KeyEd25519 (Ed25519.PublicKey pub) (Ed25519.SecretKey pri)
+        _otherwise ->
+          expected "valid key type" (Just tag)
+    where
+      dec :: JSValue -> m (String, BS.ByteString, BS.ByteString)
+      dec obj = do
+        tag <- fromJSField obj "keytype"
+        val <- fromJSField obj "keyval"
+        pub <- fromJSField val "public"
+        pri <- fromJSField val "private"
+        return (tag, B64.toByteString pub, B64.toByteString pri)
+
+instance Monad m => ToJSON m (PublicKey typ) where
+  toJSON key = case key of
+      PublicKeyEd25519 pub ->
+        enc "ed25519" (Ed25519.unPublicKey pub)
+    where
+      enc :: String -> BS.ByteString -> m JSValue
+      enc tag pub = mkObject [
+            ("keytype", return $ JSString tag)
+          , ("keyval", mkObject [
+                ("public", toJSON (B64.fromByteString pub))
+              ])
+          ]
+
+instance Monad m => ToJSON m (Some Key)        where toJSON (Some a) = toJSON a
+instance Monad m => ToJSON m (Some PublicKey)  where toJSON (Some a) = toJSON a
+instance Monad m => ToJSON m (Some KeyType)    where toJSON (Some a) = toJSON a
+
+instance ReportSchemaErrors m => FromJSON m (Some PublicKey) where
+  fromJSON enc = do
+      (tag, pub) <- dec enc
+      case tag of
+        "ed25519" -> return . Some $
+          PublicKeyEd25519 (Ed25519.PublicKey pub)
+        _otherwise ->
+          expected "valid key type" (Just tag)
+    where
+      dec :: JSValue -> m (String, BS.ByteString)
+      dec obj = do
+        tag <- fromJSField obj "keytype"
+        val <- fromJSField obj "keyval"
+        pub <- fromJSField val "public"
+        return (tag, B64.toByteString pub)
+
+instance Monad m => ToJSON m (KeyType typ) where
+  toJSON KeyTypeEd25519 = return $ JSString "ed25519"
+
+instance ReportSchemaErrors m => FromJSON m (Some KeyType) where
+  fromJSON enc = do
+    tag <- fromJSON enc
+    case tag of
+      "ed25519"  -> return . Some $ KeyTypeEd25519
+      _otherwise -> expected "valid key type" (Just tag)
+
+{-------------------------------------------------------------------------------
+  Orphans
+
+  Pre-7.8 (base 4.7) we cannot have Typeable instance for higher-kinded types.
+  Instead, here we provide some instance for specific instantiations.
+-------------------------------------------------------------------------------}
+
+#if !MIN_VERSION_base(4,7,0)
+tyConKey, tyConPublicKey, tyConPrivateKey :: Typeable.TyCon
+tyConKey        = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "Key"
+tyConPublicKey  = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PublicKey"
+tyConPrivateKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PrivateKey"
+
+instance Typeable (Some Key) where
+  typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConKey []]
+
+instance Typeable (Some PublicKey) where
+  typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPublicKey []]
+
+instance Typeable (Some PrivateKey) where
+  typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPrivateKey []]
+#endif
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Key/Env.hs b/hackage-security/hackage-security/src/Hackage/Security/Key/Env.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Key/Env.hs
@@ -0,0 +1,90 @@
+module Hackage.Security.Key.Env (
+    KeyEnv -- opaque
+  , keyEnvMap
+    -- * Convenience constructors
+  , fromPublicKeys
+  , fromKeys
+    -- * The usual accessors
+  , empty
+  , null
+  , insert
+  , lookup
+  , union
+  ) where
+
+import Prelude hiding (lookup, null)
+import Control.Monad
+import Data.Map (Map)
+import qualified Data.Map as Map
+
+import Hackage.Security.Key
+import Hackage.Security.Util.JSON
+import Hackage.Security.Util.Some
+
+{-------------------------------------------------------------------------------
+  Main datatype
+-------------------------------------------------------------------------------}
+
+-- | A key environment is a mapping from key IDs to the corresponding keys.
+--
+-- It should satisfy the invariant that these key IDs actually match the keys;
+-- see 'checkKeyEnvInvariant'.
+newtype KeyEnv = KeyEnv {
+    keyEnvMap :: Map KeyId (Some PublicKey)
+  }
+  deriving (Show)
+
+-- | Verify that each key ID is mapped to a key with that ID
+checkKeyEnvInvariant :: KeyEnv -> Bool
+checkKeyEnvInvariant = all (uncurry go) . Map.toList . keyEnvMap
+  where
+    go :: KeyId -> Some PublicKey -> Bool
+    go kId key = kId == someKeyId key
+
+{-------------------------------------------------------------------------------
+  Convenience constructors
+-------------------------------------------------------------------------------}
+
+fromPublicKeys :: [Some PublicKey] -> KeyEnv
+fromPublicKeys = KeyEnv . Map.fromList . map aux
+  where
+    aux :: Some PublicKey -> (KeyId, Some PublicKey)
+    aux pub = (someKeyId pub, pub)
+
+fromKeys :: [Some Key] -> KeyEnv
+fromKeys = fromPublicKeys . map somePublicKey
+
+{-------------------------------------------------------------------------------
+  The usual accessors
+-------------------------------------------------------------------------------}
+
+empty :: KeyEnv
+empty = KeyEnv Map.empty
+
+null :: KeyEnv -> Bool
+null (KeyEnv env) = Map.null env
+
+insert :: Some PublicKey -> KeyEnv -> KeyEnv
+insert key (KeyEnv env) = KeyEnv $ Map.insert (someKeyId key) key env
+
+lookup :: KeyId -> KeyEnv -> Maybe (Some PublicKey)
+lookup kId (KeyEnv env) = Map.lookup kId env
+
+union :: KeyEnv -> KeyEnv -> KeyEnv
+union (KeyEnv env) (KeyEnv env') = KeyEnv (env `Map.union` env')
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m KeyEnv where
+  toJSON (KeyEnv keyEnv) = toJSON keyEnv
+
+instance ReportSchemaErrors m => FromJSON m KeyEnv where
+  fromJSON enc = do
+    keyEnv <- KeyEnv <$> fromJSON enc
+    -- We should really use 'validate', but that causes module import cycles.
+    -- Sigh.
+    unless (checkKeyEnvInvariant keyEnv) $
+      expected "valid key environment" Nothing
+    return keyEnv
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Server.hs b/hackage-security/hackage-security/src/Hackage/Security/Server.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Server.hs
@@ -0,0 +1,29 @@
+-- | Main entry point into the Hackage Security framework for clients
+module Hackage.Security.Server (
+    -- * Re-exports
+    module Hackage.Security.JSON
+  , module Hackage.Security.Key
+  , module Hackage.Security.TUF
+  ) where
+
+import Hackage.Security.JSON (
+    ToJSON(..)
+  , FromJSON(..)
+  , DeserializationError(..)
+  , ReadJSON_Keys_Layout
+  , ReadJSON_Keys_NoLayout
+  , ReadJSON_NoKeys_NoLayout
+  , parseJSON_Keys_Layout
+  , parseJSON_Keys_NoLayout
+  , parseJSON_NoKeys_NoLayout
+  , readJSON_Keys_Layout
+  , readJSON_Keys_NoLayout
+  , readJSON_NoKeys_NoLayout
+  , WriteJSON
+  , renderJSON
+  , renderJSON_NoLayout
+  , writeJSON
+  , writeJSON_NoLayout
+  )
+import Hackage.Security.Key
+import Hackage.Security.TUF
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF.hs
@@ -0,0 +1,41 @@
+-- | Export all the TUF datatypes
+module Hackage.Security.TUF (
+    module Hackage.Security.TUF.Common
+  , module Hackage.Security.TUF.FileInfo
+  , module Hackage.Security.TUF.FileMap
+  , module Hackage.Security.TUF.Header
+  , module Hackage.Security.TUF.Layout.Cache
+  , module Hackage.Security.TUF.Layout.Index
+  , module Hackage.Security.TUF.Layout.Repo
+  , module Hackage.Security.TUF.Mirrors
+  , module Hackage.Security.TUF.Paths
+--  , module Hackage.Security.TUF.Patterns
+  , module Hackage.Security.TUF.Root
+  , module Hackage.Security.TUF.Signed
+  , module Hackage.Security.TUF.Snapshot
+  , module Hackage.Security.TUF.Targets
+  , module Hackage.Security.TUF.Timestamp
+  ) where
+
+import Hackage.Security.TUF.Common
+import Hackage.Security.TUF.FileInfo
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.Layout.Cache
+import Hackage.Security.TUF.Layout.Index
+import Hackage.Security.TUF.Layout.Repo
+import Hackage.Security.TUF.Mirrors
+-- import Hackage.Security.TUF.Patterns
+import Hackage.Security.TUF.Paths
+import Hackage.Security.TUF.Root
+import Hackage.Security.TUF.Signed
+import Hackage.Security.TUF.Snapshot
+import Hackage.Security.TUF.Targets
+import Hackage.Security.TUF.Timestamp
+
+-- FileMap is intended for qualified imports, so we only export a subset
+import Hackage.Security.TUF.FileMap (
+    FileMap
+  , TargetPath(..)
+  , FileChange(..)
+  , fileMapChanges
+  )
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Common.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Common.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Common.hs
@@ -0,0 +1,53 @@
+-- | Simple type wrappers
+module Hackage.Security.TUF.Common (
+    -- * Types
+    FileLength(..)
+  , Hash(..)
+  , KeyThreshold(..)
+  ) where
+
+import Hackage.Security.JSON
+
+{-------------------------------------------------------------------------------
+  Simple types
+-------------------------------------------------------------------------------}
+
+-- | File length
+--
+-- Having verified file length information means we can protect against
+-- endless data attacks and similar.
+newtype FileLength = FileLength { fileLength :: Int54 }
+  deriving (Eq, Ord, Show)
+
+-- | Key threshold
+--
+-- The key threshold is the minimum number of keys a document must be signed
+-- with. Key thresholds are specified in 'RoleSpec' or 'DelegationsSpec'.
+newtype KeyThreshold = KeyThreshold Int54
+  deriving (Eq, Ord, Show)
+
+-- | File hash
+newtype Hash = Hash String
+  deriving (Eq, Ord, Show)
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m KeyThreshold where
+  toJSON (KeyThreshold i) = toJSON i
+
+instance Monad m => ToJSON m FileLength where
+  toJSON (FileLength i) = toJSON i
+
+instance Monad m => ToJSON m Hash where
+  toJSON (Hash str) = toJSON str
+
+instance ReportSchemaErrors m => FromJSON m KeyThreshold where
+  fromJSON enc = KeyThreshold <$> fromJSON enc
+
+instance ReportSchemaErrors m => FromJSON m FileLength where
+  fromJSON enc = FileLength <$> fromJSON enc
+
+instance ReportSchemaErrors m => FromJSON m Hash where
+  fromJSON enc = Hash <$> fromJSON enc
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/FileInfo.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/FileInfo.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/FileInfo.hs
@@ -0,0 +1,103 @@
+-- | Information about files
+module Hackage.Security.TUF.FileInfo (
+    FileInfo(..)
+  , HashFn(..)
+  , Hash(..)
+    -- * Utility
+  , fileInfo
+  , computeFileInfo
+  , knownFileInfoEqual
+  , fileInfoSHA256
+    -- ** Re-exports
+  , Int54
+  ) where
+
+import Prelude hiding (lookup)
+import Data.Map (Map)
+import qualified Crypto.Hash          as CH
+import qualified Data.Map             as Map
+import qualified Data.ByteString.Lazy as BS.L
+
+import Hackage.Security.JSON
+import Hackage.Security.TUF.Common
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+data HashFn = HashFnSHA256
+  deriving (Show, Eq, Ord)
+
+-- | File information
+--
+-- This intentionally does not have an 'Eq' instance; see 'knownFileInfoEqual'
+-- and 'verifyFileInfo' instead.
+--
+-- NOTE: Throughout we compute file information always over the raw bytes.
+-- For example, when @timestamp.json@ lists the hash of @snapshot.json@, this
+-- hash is computed over the actual @snapshot.json@ file (as opposed to the
+-- canonical form of the embedded JSON). This brings it in line with the hash
+-- computed over target files, where that is the only choice available.
+data FileInfo = FileInfo {
+    fileInfoLength :: FileLength
+  , fileInfoHashes :: Map HashFn Hash
+  }
+  deriving (Show)
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+-- | Compute 'FileInfo'
+--
+-- TODO: Currently this will load the entire input bytestring into memory.
+-- We need to make this incremental, by computing the length and all hashes
+-- in a single traversal over the input.
+fileInfo :: BS.L.ByteString -> FileInfo
+fileInfo bs = FileInfo {
+      fileInfoLength = FileLength . fromIntegral $ BS.L.length bs
+    , fileInfoHashes = Map.fromList [
+          (HashFnSHA256, Hash $ show (CH.hashlazy bs :: CH.Digest CH.SHA256))
+        ]
+    }
+
+-- | Compute 'FileInfo'
+computeFileInfo :: FsRoot root => Path root -> IO FileInfo
+computeFileInfo fp = fileInfo <$> readLazyByteString fp
+
+-- | Compare known file info
+--
+-- This should be used only when the FileInfo is already known. If we want to
+-- compare known FileInfo against a file on disk we should delay until we know
+-- have confirmed that the file lengths don't match (see 'verifyFileInfo').
+knownFileInfoEqual :: FileInfo -> FileInfo -> Bool
+knownFileInfoEqual a b = (==) (fileInfoLength a, fileInfoHashes a)
+                              (fileInfoLength b, fileInfoHashes b)
+
+-- | Extract SHA256 hash from 'FileInfo' (if present)
+fileInfoSHA256 :: FileInfo -> Maybe Hash
+fileInfoSHA256 FileInfo{..} = Map.lookup HashFnSHA256 fileInfoHashes
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToObjectKey m HashFn where
+  toObjectKey HashFnSHA256 = return "sha256"
+
+instance ReportSchemaErrors m => FromObjectKey m HashFn where
+  fromObjectKey "sha256" = return HashFnSHA256
+  fromObjectKey str      = expected "valid hash function" (Just str)
+
+instance Monad m => ToJSON m FileInfo where
+  toJSON FileInfo{..} = mkObject [
+        ("length", toJSON fileInfoLength)
+      , ("hashes", toJSON fileInfoHashes)
+      ]
+
+instance ReportSchemaErrors m => FromJSON m FileInfo where
+  fromJSON enc = do
+    fileInfoLength <- fromJSField enc "length"
+    fileInfoHashes <- fromJSField enc "hashes"
+    return FileInfo{..}
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/FileMap.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/FileMap.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/FileMap.hs
@@ -0,0 +1,134 @@
+-- | Information about files
+--
+-- Intended to be double imported
+--
+-- > import Hackage.Security.TUF.FileMap (FileMap)
+-- > import qualified Hackage.Security.TUF.FileMap as FileMap
+module Hackage.Security.TUF.FileMap (
+    FileMap -- opaque
+  , TargetPath(..)
+    -- * Standard accessors
+  , empty
+  , lookup
+  , (!)
+  , insert
+  , fromList
+    -- * Convenience accessors
+  , lookupM
+    -- * Comparing file maps
+  , FileChange(..)
+  , fileMapChanges
+  ) where
+
+import Prelude hiding (lookup)
+import Control.Arrow (second)
+import Data.Map (Map)
+import qualified Data.Map as Map
+
+import Hackage.Security.JSON
+import Hackage.Security.TUF.FileInfo
+import Hackage.Security.TUF.Paths
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+-- | Mapping from paths to file info
+--
+-- File maps are used in target files; the paths are relative to the location
+-- of the target files containing the file map.
+newtype FileMap = FileMap { fileMap :: Map TargetPath FileInfo }
+  deriving (Show)
+
+-- | Entries in 'FileMap' either talk about the repository or the index
+data TargetPath =
+    TargetPathRepo  RepoPath
+  | TargetPathIndex IndexPath
+  deriving (Show, Eq, Ord)
+
+instance Pretty TargetPath where
+  pretty (TargetPathRepo  path) = pretty path
+  pretty (TargetPathIndex path) = pretty path
+
+{-------------------------------------------------------------------------------
+  Standard accessors
+-------------------------------------------------------------------------------}
+
+empty :: FileMap
+empty = FileMap Map.empty
+
+lookup :: TargetPath -> FileMap -> Maybe FileInfo
+lookup fp = Map.lookup fp . fileMap
+
+(!) :: FileMap -> TargetPath -> FileInfo
+fm ! fp = fileMap fm Map.! fp
+
+insert :: TargetPath -> FileInfo -> FileMap -> FileMap
+insert fp nfo = FileMap . Map.insert fp nfo . fileMap
+
+fromList :: [(TargetPath, FileInfo)] -> FileMap
+fromList = FileMap . Map.fromList
+
+{-------------------------------------------------------------------------------
+  Convenience accessors
+-------------------------------------------------------------------------------}
+
+lookupM :: Monad m => FileMap -> TargetPath -> m FileInfo
+lookupM m fp =
+    case lookup fp m of
+      Nothing  -> fail $ "No entry for " ++ pretty fp ++ " in filemap"
+      Just nfo -> return nfo
+
+{-------------------------------------------------------------------------------
+  Comparing filemaps
+-------------------------------------------------------------------------------}
+
+data FileChange =
+    -- | File got added or modified; we record the new file info
+    FileChanged FileInfo
+
+    -- | File got deleted
+  | FileDeleted
+  deriving (Show)
+
+fileMapChanges :: FileMap  -- ^ Old
+               -> FileMap  -- ^ New
+               -> Map TargetPath FileChange
+fileMapChanges (FileMap a) (FileMap b) =
+    Map.fromList $ go (Map.toList a) (Map.toList b)
+  where
+    -- Assumes the old and new lists are sorted alphabetically
+    -- (Map.toList guarantees this)
+    go :: [(TargetPath, FileInfo)]
+       -> [(TargetPath, FileInfo)]
+       -> [(TargetPath, FileChange)]
+    go [] new = map (second FileChanged) new
+    go old [] = map (second (const FileDeleted)) old
+    go old@((fp, nfo):old') new@((fp', nfo'):new')
+      | fp < fp'  = (fp , FileDeleted     ) : go old' new
+      | fp > fp'  = (fp', FileChanged nfo') : go old  new'
+      | knownFileInfoEqual nfo nfo' = (fp , FileChanged nfo') : go old' new'
+      | otherwise = go old' new'
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m FileMap where
+  toJSON (FileMap metaFiles) = toJSON metaFiles
+
+instance ReportSchemaErrors m => FromJSON m FileMap where
+  fromJSON enc = FileMap <$> fromJSON enc
+
+instance Monad m => ToObjectKey m TargetPath where
+  toObjectKey = return . pretty
+
+instance ReportSchemaErrors m => FromObjectKey m TargetPath where
+  fromObjectKey ('<':'r':'e':'p':'o':'>':'/':path) =
+    return . TargetPathRepo  . rootPath . fromUnrootedFilePath $ path
+  fromObjectKey ('<':'i':'n':'d':'e':'x':'>':'/':path) =
+    return . TargetPathIndex . rootPath . fromUnrootedFilePath $ path
+  fromObjectKey str =
+    expected "target path" (Just str)
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Header.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Header.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Header.hs
@@ -0,0 +1,119 @@
+-- | Header used by all TUF types
+module Hackage.Security.TUF.Header (
+    HasHeader(..)
+  , FileVersion(..)
+  , FileExpires(..)
+  , Header(..)
+    -- ** Utility
+  , expiresInDays
+  , expiresNever
+  , isExpired
+  , versionInitial
+  , versionIncrement
+  ) where
+
+import Data.Time
+import Data.Typeable (Typeable)
+
+import Hackage.Security.JSON
+import Hackage.Security.Util.Lens
+
+{-------------------------------------------------------------------------------
+  TUF header
+-------------------------------------------------------------------------------}
+
+class HasHeader a where
+  -- | File expiry date
+  fileExpires :: Lens' a FileExpires
+
+  -- | File version (monotonically increasing counter)
+  fileVersion :: Lens' a FileVersion
+
+-- | File version
+--
+-- The file version is a flat integer which must monotonically increase on
+-- every file update.
+--
+-- 'Show' and 'Read' instance are defined in terms of the underlying 'Int'
+-- (this is use for example by hackage during the backup process).
+newtype FileVersion = FileVersion Int54
+  deriving (Eq, Ord, Typeable)
+
+instance Show FileVersion where
+  show (FileVersion v) = show v
+
+instance Read FileVersion where
+  readsPrec p = map (\(v, xs) -> (FileVersion v, xs)) . readsPrec p
+
+-- | File expiry date
+--
+-- A 'Nothing' value here means no expiry. That makes it possible to set some
+-- files to never expire. (Note that not having the Maybe in the type here still
+-- allows that, because you could set an expiry date 2000 years into the future.
+-- By having the Maybe here we avoid the _need_ for such encoding issues.)
+newtype FileExpires = FileExpires (Maybe UTCTime)
+  deriving (Eq, Ord, Show, Typeable)
+
+-- | Occassionally it is useful to read only a header from a file.
+--
+-- 'HeaderOnly' intentionally only has a 'FromJSON' instance (no 'ToJSON').
+data Header = Header {
+    headerExpires :: FileExpires
+  , headerVersion :: FileVersion
+  }
+
+instance HasHeader Header where
+  fileExpires f x = (\y -> x { headerExpires = y }) <$> f (headerExpires x)
+  fileVersion f x = (\y -> x { headerVersion = y }) <$> f (headerVersion x)
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+expiresNever :: FileExpires
+expiresNever = FileExpires Nothing
+
+expiresInDays :: UTCTime -> Integer -> FileExpires
+expiresInDays now n =
+    FileExpires . Just $ addUTCTime (fromInteger n * oneDay) now
+
+isExpired :: UTCTime -> FileExpires -> Bool
+isExpired _   (FileExpires Nothing)  = False
+isExpired now (FileExpires (Just e)) = e < now
+
+versionInitial :: FileVersion
+versionInitial = FileVersion 1
+
+versionIncrement :: FileVersion -> FileVersion
+versionIncrement (FileVersion i) = FileVersion (i + 1)
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m FileVersion where
+  toJSON (FileVersion i) = toJSON i
+
+instance Monad m => ToJSON m FileExpires where
+  toJSON (FileExpires (Just e)) = toJSON e
+  toJSON (FileExpires Nothing)  = return JSNull
+
+instance ReportSchemaErrors m => FromJSON m FileVersion where
+  fromJSON enc = FileVersion <$> fromJSON enc
+
+instance ReportSchemaErrors m => FromJSON m FileExpires where
+  fromJSON JSNull = return $ FileExpires Nothing
+  fromJSON enc    = FileExpires . Just <$> fromJSON enc
+
+instance ReportSchemaErrors m => FromJSON m Header where
+  fromJSON enc = do
+    headerExpires <- fromJSField enc "expires"
+    headerVersion <- fromJSField enc "version"
+    return Header{..}
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+oneDay :: NominalDiffTime
+oneDay = 24 * 60 * 60
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Cache.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Cache.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Cache.hs
@@ -0,0 +1,64 @@
+module Hackage.Security.TUF.Layout.Cache (
+    -- * Cache layout
+    CacheLayout(..)
+  , cabalCacheLayout
+  ) where
+
+import Hackage.Security.TUF.Paths
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Cache layout
+-------------------------------------------------------------------------------}
+
+-- | Location of the various files we cache
+--
+-- Although the generic TUF algorithms do not care how we organize the cache,
+-- we nonetheless specity this here because as long as there are tools which
+-- access files in the cache directly we need to define the cache layout.
+-- See also comments for 'defaultCacheLayout'.
+data CacheLayout = CacheLayout {
+    -- | TUF root metadata
+    cacheLayoutRoot :: CachePath
+
+    -- | TUF timestamp
+  , cacheLayoutTimestamp :: CachePath
+
+    -- | TUF snapshot
+  , cacheLayoutSnapshot :: CachePath
+
+    -- | TUF mirrors list
+  , cacheLayoutMirrors :: CachePath
+
+    -- | Uncompressed index tarball
+  , cacheLayoutIndexTar :: CachePath
+
+    -- | Index to the uncompressed index tarball
+  , cacheLayoutIndexIdx :: CachePath
+
+    -- | Compressed index tarball
+    --
+    -- We cache both the compressed and the uncompressed tarballs, because
+    -- incremental updates happen through the compressed tarball, but reads
+    -- happen through the uncompressed one (with the help of the tarball index).
+  , cacheLayoutIndexTarGz :: CachePath
+  }
+
+-- | The cache layout cabal-install uses
+--
+-- We cache the index as @<cache>/00-index.tar@; this is important because
+-- `cabal-install` expects to find it there (and does not currently go through
+-- the hackage-security library to get files from the index).
+cabalCacheLayout :: CacheLayout
+cabalCacheLayout = CacheLayout {
+      cacheLayoutRoot       = rp $ fragment "root.json"
+    , cacheLayoutTimestamp  = rp $ fragment "timestamp.json"
+    , cacheLayoutSnapshot   = rp $ fragment "snapshot.json"
+    , cacheLayoutMirrors    = rp $ fragment "mirrors.json"
+    , cacheLayoutIndexTar   = rp $ fragment "00-index.tar"
+    , cacheLayoutIndexIdx   = rp $ fragment "00-index.tar.idx"
+    , cacheLayoutIndexTarGz = rp $ fragment "00-index.tar.gz"
+    }
+  where
+    rp :: Path Unrooted -> CachePath
+    rp = rootPath
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Index.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Index.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Index.hs
@@ -0,0 +1,116 @@
+module Hackage.Security.TUF.Layout.Index (
+    -- * Repository layout
+    IndexLayout(..)
+  , IndexFile(..)
+  , hackageIndexLayout
+    -- ** Utility
+  , indexLayoutPkgMetadata
+  , indexLayoutPkgCabal
+  , indexLayoutPkgPrefs
+  ) where
+
+import qualified System.FilePath as FP
+
+import Distribution.Package
+import Distribution.Text
+
+import Hackage.Security.TUF.Paths
+import Hackage.Security.TUF.Signed
+import Hackage.Security.TUF.Targets
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import Hackage.Security.Util.Some
+
+{-------------------------------------------------------------------------------
+  Index layout
+-------------------------------------------------------------------------------}
+
+-- | Layout of the files within the index tarball
+data IndexLayout = IndexLayout  {
+      -- | Translate an 'IndexFile' to a path
+      indexFileToPath :: forall dec. IndexFile dec -> IndexPath
+
+      -- | Parse an 'FilePath'
+    , indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
+    }
+
+-- | Files that we might request from the index
+--
+-- The type index tells us the type of the decoded file, if any. For files for
+-- which the library does not support decoding this will be @()@.
+-- NOTE: Clients should NOT rely on this type index being @()@, or they might
+-- break if we add support for parsing additional file formats in the future.
+--
+-- TODO: If we wanted to support legacy Hackage, we should also have a case for
+-- the global preferred-versions file. But supporting legacy Hackage will
+-- probably require more work anyway..
+data IndexFile :: * -> * where
+    -- | Package-specific metadata (@targets.json@)
+    IndexPkgMetadata :: PackageIdentifier -> IndexFile (Signed Targets)
+
+    -- | Cabal file for a package
+    IndexPkgCabal :: PackageIdentifier -> IndexFile ()
+
+    -- | Preferred versions a package
+    IndexPkgPrefs :: PackageName -> IndexFile ()
+
+deriving instance Show (IndexFile dec)
+
+instance Pretty (IndexFile dec) where
+  pretty (IndexPkgMetadata pkgId) = "metadata for " ++ display pkgId
+  pretty (IndexPkgCabal    pkgId) = ".cabal for " ++ display pkgId
+  pretty (IndexPkgPrefs    pkgNm) = "preferred-versions for " ++ display pkgNm
+
+instance SomeShow   IndexFile where someShow   = DictShow
+instance SomePretty IndexFile where somePretty = DictPretty
+
+-- | The layout of the index as maintained on Hackage
+hackageIndexLayout :: IndexLayout
+hackageIndexLayout = IndexLayout {
+      indexFileToPath   = toPath
+    , indexFileFromPath = fromPath . toUnrootedFilePath . unrootPath
+    }
+  where
+    toPath :: IndexFile dec -> IndexPath
+    toPath (IndexPkgCabal    pkgId) = fromFragments [
+                                          display (packageName    pkgId)
+                                        , display (packageVersion pkgId)
+                                        , display (packageName pkgId) ++ ".cabal"
+                                        ]
+    toPath (IndexPkgMetadata pkgId) = fromFragments [
+                                          display (packageName    pkgId)
+                                        , display (packageVersion pkgId)
+                                        , "package.json"
+                                        ]
+    toPath (IndexPkgPrefs    pkgNm) = fromFragments [
+                                          display pkgNm
+                                        , "preferred-versions"
+                                        ]
+
+    fromFragments :: [String] -> IndexPath
+    fromFragments = rootPath . joinFragments
+
+    fromPath :: FilePath -> Maybe (Some IndexFile)
+    fromPath fp = case FP.splitPath fp of
+      [pkg, version, file] -> do
+        pkgId <- simpleParse (init pkg ++ "-" ++ init version)
+        case FP.takeExtension file of
+          ".cabal"   -> return $ Some $ IndexPkgCabal    pkgId
+          ".json"    -> return $ Some $ IndexPkgMetadata pkgId
+          _otherwise -> Nothing
+      [pkg, "preferred-versions"] ->
+        Some . IndexPkgPrefs <$> simpleParse (init pkg)
+      _otherwise -> Nothing
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+indexLayoutPkgMetadata :: IndexLayout -> PackageIdentifier -> IndexPath
+indexLayoutPkgMetadata IndexLayout{..} = indexFileToPath . IndexPkgMetadata
+
+indexLayoutPkgCabal :: IndexLayout -> PackageIdentifier -> IndexPath
+indexLayoutPkgCabal IndexLayout{..} = indexFileToPath . IndexPkgCabal
+
+indexLayoutPkgPrefs :: IndexLayout -> PackageName -> IndexPath
+indexLayoutPkgPrefs IndexLayout{..} = indexFileToPath . IndexPkgPrefs
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Repo.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Repo.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Repo.hs
@@ -0,0 +1,79 @@
+module Hackage.Security.TUF.Layout.Repo (
+    -- * Repository layout
+    RepoLayout(..)
+  , hackageRepoLayout
+  , cabalLocalRepoLayout
+  ) where
+
+import Distribution.Package
+import Distribution.Text
+
+import Hackage.Security.TUF.Paths
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Repository layout
+-------------------------------------------------------------------------------}
+
+-- | Layout of a repository
+data RepoLayout = RepoLayout {
+      -- | TUF root metadata
+      repoLayoutRoot :: RepoPath
+
+      -- | TUF timestamp
+    , repoLayoutTimestamp :: RepoPath
+
+      -- | TUF snapshot
+    , repoLayoutSnapshot :: RepoPath
+
+      -- | TUF mirrors list
+    , repoLayoutMirrors :: RepoPath
+
+      -- | Compressed index tarball
+    , repoLayoutIndexTarGz :: RepoPath
+
+      -- | Uncompressed index tarball
+    , repoLayoutIndexTar :: RepoPath
+
+      -- | Path to the package tarball
+    , repoLayoutPkgTarGz :: PackageIdentifier -> RepoPath
+    }
+
+-- | The layout used on Hackage
+hackageRepoLayout :: RepoLayout
+hackageRepoLayout = RepoLayout {
+      repoLayoutRoot       = rp $ fragment "root.json"
+    , repoLayoutTimestamp  = rp $ fragment "timestamp.json"
+    , repoLayoutSnapshot   = rp $ fragment "snapshot.json"
+    , repoLayoutMirrors    = rp $ fragment "mirrors.json"
+    , repoLayoutIndexTarGz = rp $ fragment "01-index.tar.gz"
+    , repoLayoutIndexTar   = rp $ fragment "01-index.tar"
+    , repoLayoutPkgTarGz   = \pkgId -> rp $ fragment "package" </> pkgFile pkgId
+    }
+  where
+    pkgFile :: PackageIdentifier -> Path Unrooted
+    pkgFile pkgId = fragment (display pkgId) <.> "tar.gz"
+
+    rp :: Path Unrooted -> RepoPath
+    rp = rootPath
+
+-- | Layout used by cabal for ("legacy") local repos
+--
+-- Obviously, such repos do not normally contain any of the TUF files, so their
+-- location is more or less arbitrary here.
+cabalLocalRepoLayout :: RepoLayout
+cabalLocalRepoLayout = hackageRepoLayout {
+      repoLayoutPkgTarGz = \pkgId -> rp $ pkgLoc pkgId </> pkgFile pkgId
+    }
+  where
+    pkgLoc :: PackageIdentifier -> Path Unrooted
+    pkgLoc pkgId = joinFragments [
+          display (packageName    pkgId)
+        , display (packageVersion pkgId)
+        ]
+
+    pkgFile :: PackageIdentifier -> Path Unrooted
+    pkgFile pkgId = fragment (display pkgId) <.> "tar.gz"
+
+    rp :: Path Unrooted -> RepoPath
+    rp = rootPath
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Mirrors.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Mirrors.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Mirrors.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE UndecidableInstances #-}
+module Hackage.Security.TUF.Mirrors (
+    -- * TUF types
+    Mirrors(..)
+  , Mirror(..)
+  , MirrorContent(..)
+    -- ** Utility
+  , MirrorDescription
+  , describeMirror
+  ) where
+
+import Control.Monad.Except
+import Network.URI
+
+import Hackage.Security.JSON
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.Signed
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+data Mirrors = Mirrors {
+    mirrorsVersion :: FileVersion
+  , mirrorsExpires :: FileExpires
+  , mirrorsMirrors :: [Mirror]
+  }
+
+-- | Definition of a mirror
+--
+-- NOTE: Unlike the TUF specification, we require that all mirrors must have
+-- the same format. That is, we omit @metapath@ and @targetspath@.
+data Mirror = Mirror {
+    mirrorUrlBase :: URI
+  , mirrorContent :: MirrorContent
+  }
+  deriving Show
+
+-- | Full versus partial mirrors
+--
+-- The TUF spec explicitly allows for partial mirrors, with the mirrors file
+-- specifying (through patterns) what is available from partial mirrors.
+--
+-- For now we only support full mirrors; if we wanted to add partial mirrors,
+-- we would add a second @MirrorPartial@ constructor here with arguments
+-- corresponding to TUF's @metacontent@ and @targetscontent@ fields.
+data MirrorContent =
+    MirrorFull
+  deriving Show
+
+instance HasHeader Mirrors where
+  fileVersion f x = (\y -> x { mirrorsVersion = y }) <$> f (mirrorsVersion x)
+  fileExpires f x = (\y -> x { mirrorsExpires = y }) <$> f (mirrorsExpires x)
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+type MirrorDescription = String
+
+-- | Give a human-readable description of a particular mirror
+--
+-- (for use in error messages)
+describeMirror :: Mirror -> MirrorDescription
+describeMirror = show . mirrorUrlBase
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m Mirror where
+  toJSON Mirror{..} = mkObject $ concat [
+      [ ("urlbase", toJSON mirrorUrlBase) ]
+    , case mirrorContent of
+        MirrorFull -> []
+    ]
+
+instance Monad m => ToJSON m Mirrors where
+  toJSON Mirrors{..} = mkObject [
+      ("_type"   , return $ JSString "Mirrorlist")
+    , ("version" , toJSON mirrorsVersion)
+    , ("expires" , toJSON mirrorsExpires)
+    , ("mirrors" , toJSON mirrorsMirrors)
+    ]
+
+instance ReportSchemaErrors m => FromJSON m Mirror where
+  fromJSON enc = do
+    mirrorUrlBase <- fromJSField enc "urlbase"
+    let mirrorContent = MirrorFull
+    return Mirror{..}
+
+instance ( MonadError DeserializationError m
+         , ReportSchemaErrors m
+         ) => FromJSON m Mirrors where
+  fromJSON enc = do
+    verifyType enc "Mirrorlist"
+    mirrorsVersion <- fromJSField enc "version"
+    mirrorsExpires <- fromJSField enc "expires"
+    mirrorsMirrors <- fromJSField enc "mirrors"
+    return Mirrors{..}
+
+instance MonadKeys m => FromJSON m (Signed Mirrors) where
+  fromJSON = signedFromJSON
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Paths.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Paths.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Paths.hs
@@ -0,0 +1,72 @@
+-- | Paths used in the TUF data structures
+module Hackage.Security.TUF.Paths (
+    -- * Repository
+    RepoRoot
+  , RepoPath
+  , anchorRepoPathLocally
+  , anchorRepoPathRemotely
+    -- * Index
+  , IndexRoot
+  , IndexPath
+    -- * Cache
+  , CacheRoot
+  , CachePath
+  , anchorCachePath
+  ) where
+
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+
+{-------------------------------------------------------------------------------
+  Repo
+-------------------------------------------------------------------------------}
+
+-- | The root of the repository
+--
+-- Repository roots can be anchored at a remote URL or a local directory.
+--
+-- Note that even for remote repos 'RepoRoot' is (potentially) different from
+-- 'Web' -- for a repository located at, say, @http://hackage.haskell.org@
+-- they happen to coincide, but for one location at
+-- @http://example.com/some/subdirectory@ they do not.
+data RepoRoot
+
+-- | Paths relative to the root of the repository
+type RepoPath = Path RepoRoot
+
+instance Pretty (Path RepoRoot) where
+  pretty (Path fp) = "<repo>/" ++ fp
+
+anchorRepoPathLocally :: FsRoot root => Path root -> RepoPath -> Path root
+anchorRepoPathLocally localRoot repoPath = localRoot </> unrootPath repoPath
+
+anchorRepoPathRemotely :: Path Web -> RepoPath -> Path Web
+anchorRepoPathRemotely remoteRoot repoPath = remoteRoot </> unrootPath repoPath
+
+{-------------------------------------------------------------------------------
+  Index
+-------------------------------------------------------------------------------}
+
+-- | The root of the index tarball
+data IndexRoot
+
+-- | Paths relative to the root of the index tarball
+type IndexPath = Path IndexRoot
+
+instance Pretty (Path IndexRoot) where
+    pretty (Path fp) = "<index>/" ++ fp
+
+{-------------------------------------------------------------------------------
+  Cache
+-------------------------------------------------------------------------------}
+
+-- | The cache directory
+data CacheRoot
+type CachePath = Path CacheRoot
+
+instance Pretty (Path CacheRoot) where
+    pretty (Path fp) = "<cache>/" ++ fp
+
+-- | Anchor a cache path to the location of the cache
+anchorCachePath :: FsRoot root => Path root -> CachePath -> Path root
+anchorCachePath cacheRoot cachePath = cacheRoot </> unrootPath cachePath
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Patterns.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Patterns.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Patterns.hs
@@ -0,0 +1,346 @@
+-- | Patterns and replacements
+--
+-- NOTE: This module was developed to prepare for proper delegation (#39).
+-- It is currently unusued.
+{-# LANGUAGE CPP #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE TemplateHaskellQuotes #-}
+#else
+{-# LANGUAGE TemplateHaskell #-}
+#endif
+module Hackage.Security.TUF.Patterns (
+    -- * Patterns and replacements
+    FileName
+  , Directory
+  , Extension
+  , BaseName
+  , Pattern(..)
+  , Replacement(..)
+  , Delegation(..)
+    -- ** Utility
+  , identityReplacement
+  , matchDelegation
+    -- ** Parsing and quasi-quoting
+  , parseDelegation
+  , qqd
+  ) where
+
+import Control.Monad.Except
+import Language.Haskell.TH (Q, Exp)
+import System.FilePath
+import qualified Language.Haskell.TH.Syntax as TH
+
+import Hackage.Security.JSON
+import Hackage.Security.Util.Some
+import Hackage.Security.Util.Stack
+import Hackage.Security.Util.TypedEmbedded
+
+{-------------------------------------------------------------------------------
+  Patterns and replacements
+-------------------------------------------------------------------------------}
+
+type FileName  = String
+type Directory = String
+type Extension = String
+type BaseName  = String
+
+-- | Structured patterns over paths
+--
+-- The type argument indicates what kind of function we expect when the
+-- pattern matches. For example, we have the pattern @"*/*.txt"@:
+--
+-- > PathPatternDirAny (PathPatternFileExt ".txt")
+-- >   :: PathPattern (Directory :- BaseName :- ())
+--
+-- TODOs (see README.md):
+--
+-- * Update this to work with 'Path' rather than 'FilePath'/'String'
+-- * Add different kinds of wildcards
+-- * Add path roots
+--
+-- Currently this is a proof of concept more than anything else; the right
+-- structure is here, but it needs updating. However, until we add author
+-- signing (or out-of-tarball targets) we don't actually use this yet.
+--
+-- NOTE: Haddock lacks GADT support so constructors have only regular comments.
+data Pattern a where
+    -- Match against a specific filename
+    PatFileConst :: FileName -> Pattern ()
+
+    -- Match against a filename with the given extension
+    PatFileExt :: Extension -> Pattern (BaseName :- ())
+
+    -- Match against any filename
+    PatFileAny :: Pattern (FileName :- ())
+
+    -- Match against a specific directory
+    PatDirConst :: Directory -> Pattern a -> Pattern a
+
+    -- Match against any directory
+    PatDirAny :: Pattern a -> Pattern (Directory :- a)
+
+-- | Replacement patterns
+--
+-- These constructors match the ones in 'Pattern': wildcards must be used
+-- in the same order as they appear in the pattern, but they don't all have to
+-- be used (that's why the base constructors are polymorphic in the stack tail).
+data Replacement a where
+    RepFileConst :: FileName -> Replacement a
+    RepFileExt   :: Extension -> Replacement (BaseName :- a)
+    RepFileAny   :: Replacement (FileName :- a)
+    RepDirConst  :: Directory -> Replacement a -> Replacement a
+    RepDirAny    :: Replacement a -> Replacement (Directory :- a)
+
+deriving instance Eq   (Pattern typ)
+deriving instance Show (Pattern typ)
+
+deriving instance Eq   (Replacement typ)
+deriving instance Show (Replacement typ)
+
+-- | The identity replacement replaces a matched pattern with itself
+identityReplacement :: Pattern typ -> Replacement typ
+identityReplacement = go
+  where
+    go :: Pattern typ -> Replacement typ
+    go (PatFileConst fn)  = RepFileConst fn
+    go (PatFileExt   e)   = RepFileExt   e
+    go PatFileAny         = RepFileAny
+    go (PatDirConst  d p) = RepDirConst  d (go p)
+    go (PatDirAny      p) = RepDirAny      (go p)
+
+-- | A delegation
+--
+-- A delegation is a pair of a pattern and a replacement.
+--
+-- See 'match' for an example.
+data Delegation = forall a. Delegation (Pattern a) (Replacement a)
+
+deriving instance Show Delegation
+
+{-------------------------------------------------------------------------------
+  Matching
+-------------------------------------------------------------------------------}
+
+matchPattern :: String -> Pattern a -> Maybe a
+matchPattern = go . splitDirectories
+  where
+    go :: [String] -> Pattern a -> Maybe a
+    go []    _                    = Nothing
+    go [f]   (PatFileConst f')    = do guard (f == f')
+                                       return ()
+    go [f]   (PatFileExt   e')    = do let (bn, _:e) = splitExtension f
+                                       guard $ e == e'
+                                       return (bn :- ())
+    go [_]   _                    = Nothing
+    go (d:p) (PatDirConst  d' p') = do guard (d == d')
+                                       go p p'
+    go (d:p) (PatDirAny       p') = (d :-) <$> go p p'
+    go (_:_) _                    = Nothing
+
+constructReplacement :: Replacement a -> a -> String
+constructReplacement = \repl a -> joinPath $ go repl a
+  where
+    go :: Replacement a -> a -> [String]
+    go (RepFileConst c)   _         = [c]
+    go (RepFileExt   e)   (bn :- _) = [bn <.> e]
+    go RepFileAny         (fn :- _) = [fn]
+    go (RepDirConst  d p) a         = d : go p a
+    go (RepDirAny      p) (d  :- a) = d : go p a
+
+matchDelegation :: Delegation -> String -> Maybe String
+matchDelegation (Delegation pat repl) str =
+    constructReplacement repl <$> matchPattern str pat
+
+{-------------------------------------------------------------------------------
+  Typechecking patterns and replacements
+-------------------------------------------------------------------------------}
+
+-- | Types for pattern and replacements
+--
+-- We intentially are not very precise here, saying @String@ (instead of
+-- @FileName@, @BaseName@, or @Directory@, say) so that we can, for example,
+-- use a matched filename in a pattern as a directory in a replacement.
+data PatternType a where
+  PatTypeNil :: PatternType ()
+  PatTypeStr :: PatternType a -> PatternType (String :- a)
+
+instance Unify PatternType where
+  unify PatTypeNil     PatTypeNil       = Just Refl
+  unify (PatTypeStr p) (PatTypeStr  p') = case unify p p' of
+                                            Just Refl -> Just Refl
+                                            Nothing   -> Nothing
+  unify _              _                = Nothing
+
+type instance TypeOf Pattern     = PatternType
+type instance TypeOf Replacement = PatternType
+
+instance Typed Pattern where
+  typeOf (PatFileConst _)   = PatTypeNil
+  typeOf (PatFileExt   _)   = PatTypeStr PatTypeNil
+  typeOf (PatFileAny    )   = PatTypeStr PatTypeNil
+  typeOf (PatDirConst  _ p) = typeOf p
+  typeOf (PatDirAny      p) = PatTypeStr (typeOf p)
+
+instance AsType Replacement where
+  asType = go
+    where
+      go :: Replacement typ -> PatternType typ' -> Maybe (Replacement typ')
+      go (RepFileConst c)   _                = return $ RepFileConst c
+      go (RepFileExt   _)   PatTypeNil       = Nothing
+      go (RepFileExt   e)   (PatTypeStr _)   = return $ RepFileExt e
+      go RepFileAny         PatTypeNil       = Nothing
+      go RepFileAny         (PatTypeStr _)   = return $ RepFileAny
+      go (RepDirConst  c p) tp               = RepDirConst c <$> go p tp
+      go (RepDirAny      _) PatTypeNil       = Nothing
+      go (RepDirAny      p) (PatTypeStr tp)  = RepDirAny     <$> go p tp
+
+{-------------------------------------------------------------------------------
+  Pretty-printing and parsing patterns and replacements
+-------------------------------------------------------------------------------}
+
+prettyPattern :: Pattern typ -> String
+prettyPattern (PatFileConst f)   = f
+prettyPattern (PatFileExt   e)   = "*" <.> e
+prettyPattern PatFileAny         = "*"
+prettyPattern (PatDirConst  d p) = d   </> prettyPattern p
+prettyPattern (PatDirAny      p) = "*" </> prettyPattern p
+
+prettyReplacement :: Replacement typ -> String
+prettyReplacement (RepFileConst f)   = f
+prettyReplacement (RepFileExt   e)   = "*" <.> e
+prettyReplacement RepFileAny         = "*"
+prettyReplacement (RepDirConst  d p) = d   </> prettyReplacement p
+prettyReplacement (RepDirAny      p) = "*" </> prettyReplacement p
+
+-- | Parse a pattern
+parsePattern :: String -> Maybe (Some Pattern)
+parsePattern = go . splitDirectories
+  where
+    go :: [String] -> Maybe (Some Pattern)
+    go []     = Nothing
+    go ["*"]  = return . Some $ PatFileAny
+    go [p]    = if '*' `notElem` p
+                  then return . Some $ PatFileConst p
+                  else case splitExtension p of
+                         ("*", _:ext) -> return . Some $ PatFileExt ext
+                         _otherwise   -> Nothing
+    go (p:ps) = do Some p' <- go ps
+                   if '*' `notElem` p
+                     then return . Some $ PatDirConst p p'
+                     else case p of
+                            "*"        -> return . Some $ PatDirAny p'
+                            _otherwise -> Nothing
+
+-- | Parse a replacement
+--
+-- We cheat and use the parser for patterns and then translate using the
+-- identity replacement.
+parseReplacement :: String -> Maybe (Some Replacement)
+parseReplacement = fmap aux . parsePattern
+  where
+    aux :: Some Pattern -> Some Replacement
+    aux (Some pat) = Some (identityReplacement pat)
+
+parseDelegation :: String -> String -> Either String Delegation
+parseDelegation pat repl =
+    case (parsePattern pat, parseReplacement repl) of
+      (Just (Some pat'), Just (Some repl')) ->
+        case repl' `asType` typeOf pat' of
+          Just repl'' -> Right $ Delegation pat' repl''
+          Nothing     -> Left "Replacement does not match pattern type"
+      _otherwise ->
+        Left "Cannot parse delegation"
+
+{-------------------------------------------------------------------------------
+  Quasi-quotation
+
+  We cannot (easily) use dataToExpQ because of the use of GADTs, so we manually
+  give Lift instances.
+-------------------------------------------------------------------------------}
+
+-- | Quasi-quoter for delegations to make them easier to write in code
+--
+-- This allows to write delegations as
+--
+-- > $(qqd "targets/*/*/*.cabal" "targets/*/*/revisions.json")
+--
+-- (The alternative syntax which actually uses a quasi-quoter doesn't work very
+-- well because the '/*' bits confuse CPP: "unterminated comment")
+qqd :: String -> String -> Q Exp
+qqd pat repl  =
+    case parseDelegation pat repl of
+      Left  err -> fail $ "Invalid delegation: " ++ err
+      Right del -> TH.lift del
+
+instance TH.Lift (Pattern a) where
+  lift (PatFileConst fn)  = [| PatFileConst fn  |]
+  lift (PatFileExt   e)   = [| PatFileExt   e   |]
+  lift PatFileAny         = [| PatFileAny       |]
+  lift (PatDirConst  d p) = [| PatDirConst  d p |]
+  lift (PatDirAny      p) = [| PatDirAny      p |]
+
+instance TH.Lift (Replacement a) where
+  lift (RepFileConst fn)  = [| RepFileConst fn  |]
+  lift (RepFileExt   e )  = [| RepFileExt   e   |]
+  lift RepFileAny         = [| RepFileAny       |]
+  lift (RepDirConst  d r) = [| RepDirConst  d r |]
+  lift (RepDirAny      r) = [| RepDirAny      r |]
+
+instance TH.Lift Delegation where
+  lift (Delegation pat repl) = [| Delegation pat repl |]
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m (Pattern typ) where
+  toJSON = return . JSString . prettyPattern
+instance Monad m => ToJSON m (Replacement typ) where
+  toJSON = return . JSString . prettyReplacement
+
+instance Monad m => ToJSON m (Some Pattern) where
+  toJSON (Some p) = toJSON p
+instance Monad m => ToJSON m (Some Replacement) where
+  toJSON (Some r) = toJSON r
+
+instance ReportSchemaErrors m => FromJSON m (Some Pattern) where
+  fromJSON enc = do
+    str <- fromJSON enc
+    case parsePattern str of
+      Nothing -> expected "valid pattern" (Just str)
+      Just p  -> return p
+
+instance ReportSchemaErrors m => FromJSON m (Some Replacement) where
+  fromJSON enc = do
+    str <- fromJSON enc
+    case parseReplacement str of
+      Nothing -> expected "valid replacement" (Just str)
+      Just r  -> return r
+
+{-------------------------------------------------------------------------------
+  Debugging: examples
+-------------------------------------------------------------------------------}
+
+_ex1 :: Maybe String
+_ex1 = matchDelegation del "A/x/y/z.foo"
+  where
+    del = Delegation
+            ( PatDirConst "A"
+            $ PatDirAny
+            $ PatDirAny
+            $ PatFileExt "foo"
+            )
+            ( RepDirConst "B"
+            $ RepDirAny
+            $ RepDirConst "C"
+            $ RepDirAny
+            $ RepFileExt "bar"
+            )
+
+_ex2 :: Maybe String
+_ex2 = matchDelegation del "A/x/y/z.foo"
+  where
+    Right del = parseDelegation "A/*/*/*.foo" "B/*/C/*/*.bar"
+
+_ex3 :: Either String Delegation
+_ex3 = parseDelegation "foo" "*/bar"
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Root.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Root.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Root.hs
@@ -0,0 +1,117 @@
+-- | The root filetype
+module Hackage.Security.TUF.Root (
+    -- * Datatypes
+    Root(..)
+  , RootRoles(..)
+  , RoleSpec(..)
+  ) where
+
+import Hackage.Security.JSON
+import Hackage.Security.Key
+import Hackage.Security.Key.Env (KeyEnv)
+import Hackage.Security.TUF.Common
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.Mirrors
+import Hackage.Security.TUF.Signed
+import Hackage.Security.TUF.Snapshot
+import Hackage.Security.TUF.Targets
+import Hackage.Security.TUF.Timestamp
+import Hackage.Security.Util.Some
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+-- | The root metadata
+--
+-- NOTE: We must have the invariant that ALL keys (apart from delegation keys)
+-- must be listed in 'rootKeys'. (Delegation keys satisfy a similar invariant,
+-- see Targets.)
+data Root = Root {
+    rootVersion :: FileVersion
+  , rootExpires :: FileExpires
+  , rootKeys    :: KeyEnv
+  , rootRoles   :: RootRoles
+  }
+
+data RootRoles = RootRoles {
+    rootRolesRoot      :: RoleSpec Root
+  , rootRolesSnapshot  :: RoleSpec Snapshot
+  , rootRolesTargets   :: RoleSpec Targets
+  , rootRolesTimestamp :: RoleSpec Timestamp
+  , rootRolesMirrors   :: RoleSpec Mirrors
+  }
+
+-- | Role specification
+--
+-- The phantom type indicates what kind of type this role is meant to verify.
+data RoleSpec a = RoleSpec {
+    roleSpecKeys      :: [Some PublicKey]
+  , roleSpecThreshold :: KeyThreshold
+  }
+  deriving (Show)
+
+instance HasHeader Root where
+  fileVersion f x = (\y -> x { rootVersion = y }) <$> f (rootVersion x)
+  fileExpires f x = (\y -> x { rootExpires = y }) <$> f (rootExpires x)
+
+{-------------------------------------------------------------------------------
+  JSON encoding
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m RootRoles where
+  toJSON RootRoles{..} = mkObject [
+      ("root"      , toJSON rootRolesRoot)
+    , ("snapshot"  , toJSON rootRolesSnapshot)
+    , ("targets"   , toJSON rootRolesTargets)
+    , ("timestamp" , toJSON rootRolesTimestamp)
+    , ("mirrors"   , toJSON rootRolesMirrors)
+    ]
+
+instance MonadKeys m => FromJSON m RootRoles where
+  fromJSON enc = do
+    rootRolesRoot      <- fromJSField enc "root"
+    rootRolesSnapshot  <- fromJSField enc "snapshot"
+    rootRolesTargets   <- fromJSField enc "targets"
+    rootRolesTimestamp <- fromJSField enc "timestamp"
+    rootRolesMirrors   <- fromJSField enc "mirrors"
+    return RootRoles{..}
+
+instance Monad m => ToJSON m Root where
+  toJSON Root{..} = mkObject [
+         ("_type"   , return $ JSString "Root")
+       , ("version" , toJSON rootVersion)
+       , ("expires" , toJSON rootExpires)
+       , ("keys"    , toJSON rootKeys)
+       , ("roles"   , toJSON rootRoles)
+       ]
+
+instance Monad m => ToJSON m (RoleSpec a) where
+  toJSON RoleSpec{..} = mkObject [
+        ("keyids"    , return . JSArray . map writeKeyAsId $ roleSpecKeys)
+      , ("threshold" , toJSON roleSpecThreshold)
+      ]
+
+-- | We give an instance for Signed Root rather than Root because the key
+-- environment from the root data is necessary to resolve the explicit sharing
+-- in the signatures.
+instance MonadKeys m => FromJSON m (Signed Root) where
+  fromJSON envelope = do
+    enc      <- fromJSField envelope "signed"
+    rootKeys <- fromJSField enc      "keys"
+    withKeys rootKeys $ do
+      verifyType enc "Root"
+      rootVersion <- fromJSField enc "version"
+      rootExpires <- fromJSField enc "expires"
+      rootRoles   <- fromJSField enc "roles"
+      let signed = Root{..}
+
+      signatures <- fromJSField envelope "signatures"
+      validate "signatures" $ verifySignatures enc signatures
+      return Signed{..}
+
+instance MonadKeys m => FromJSON m (RoleSpec a) where
+  fromJSON enc = do
+    roleSpecKeys      <- mapM readKeyAsId =<< fromJSField enc "keyids"
+    roleSpecThreshold <- fromJSField enc "threshold"
+    return RoleSpec{..}
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Signed.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Signed.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Signed.hs
@@ -0,0 +1,238 @@
+-- | Wrapper around an arbitrary datatype that adds signatures
+--
+-- Note that in the spec there is explicit sharing of keys through key IDs;
+-- we translate this to implicit sharing in our Haskell datatypes, with the
+-- translation done in the JSON serialization/deserialization.
+module Hackage.Security.TUF.Signed (
+    -- * TUF types
+    Signed(..)
+  , Signatures(..)
+  , Signature(..)
+    -- * Construction and verification
+  , unsigned
+  , withSignatures
+  , withSignatures'
+  , signRendered
+  , verifySignature
+    -- * JSON aids
+  , signedFromJSON
+  , verifySignatures
+    -- * Avoid interpreting signatures
+  , UninterpretedSignatures(..)
+  , PreSignature(..)
+    -- ** Utility
+  , fromPreSignature
+  , fromPreSignatures
+  , toPreSignature
+  , toPreSignatures
+  ) where
+
+import Control.Monad
+import Data.Functor.Identity
+import qualified Data.ByteString      as BS
+import qualified Data.ByteString.Lazy as BS.L
+import qualified Data.Set             as Set
+
+import Hackage.Security.JSON
+import Hackage.Security.Key
+import Hackage.Security.TUF.Layout.Repo
+import Hackage.Security.Util.Some
+import Text.JSON.Canonical
+import qualified Hackage.Security.Util.Base64 as B64
+
+{-------------------------------------------------------------------------------
+  Signed objects
+-------------------------------------------------------------------------------}
+
+data Signed a = Signed {
+    signed     :: a
+  , signatures :: Signatures
+  }
+
+-- | A list of signatures
+--
+-- Invariant: each signature must be made with a different key.
+-- We enforce this invariant for incoming untrusted data ('fromPreSignatures')
+-- but not for lists of signatures that we create in code.
+newtype Signatures = Signatures [Signature]
+
+data Signature = Signature {
+    signature    :: BS.ByteString
+  , signatureKey :: Some PublicKey
+  }
+
+-- | Create a new document without any signatures
+unsigned :: a -> Signed a
+unsigned a = Signed { signed = a, signatures = Signatures [] }
+
+-- | Sign a document
+withSignatures :: ToJSON WriteJSON a => RepoLayout -> [Some Key] -> a -> Signed a
+withSignatures repoLayout keys doc = Signed {
+      signed     = doc
+    , signatures = signRendered keys $ renderJSON repoLayout doc
+    }
+
+-- | Variation on 'withSignatures' that doesn't need the repo layout
+withSignatures' :: ToJSON Identity a => [Some Key] -> a -> Signed a
+withSignatures' keys doc = Signed {
+      signed     = doc
+    , signatures = signRendered keys $ renderJSON_NoLayout doc
+    }
+
+-- | Construct signatures for already rendered value
+signRendered :: [Some Key] -> BS.L.ByteString -> Signatures
+signRendered keys rendered = Signatures $ map go keys
+  where
+    go :: Some Key -> Signature
+    go (Some key) = Signature {
+        signature    = sign (privateKey key) rendered
+      , signatureKey = Some $ publicKey key
+      }
+
+verifySignature :: BS.L.ByteString -> Signature -> Bool
+verifySignature inp Signature{signature = sig, signatureKey = Some pub} =
+  verify pub inp sig
+
+instance (Monad m, ToJSON m a) => ToJSON m (Signed a) where
+  toJSON Signed{..} = mkObject [
+         ("signed"     , toJSON signed)
+       , ("signatures" , toJSON signatures)
+       ]
+
+instance Monad m => ToJSON m Signatures where
+  toJSON = toJSON . toPreSignatures
+
+instance MonadKeys m => FromJSON m Signatures where
+  fromJSON = fromPreSignatures <=< fromJSON
+
+{-------------------------------------------------------------------------------
+  JSON aids
+-------------------------------------------------------------------------------}
+
+-- | General FromJSON instance for signed datatypes
+--
+-- We don't give a general FromJSON instance for Signed because for some
+-- datatypes we need to do something special (datatypes where we need to
+-- read key environments); for instance, see the "Signed Root" instance.
+signedFromJSON :: (MonadKeys m, FromJSON m a) => JSValue -> m (Signed a)
+signedFromJSON envelope = do
+    enc        <- fromJSField envelope "signed"
+    signed     <- fromJSON enc
+    signatures <- fromJSField envelope "signatures"
+    validate "signatures" $ verifySignatures enc signatures
+    return Signed{..}
+
+-- | Signature verification
+--
+-- NOTES:
+-- 1. By definition, the signature must be verified against the canonical
+--    JSON format. This means we _must_ parse and then pretty print (as
+--    we do here) because the document as stored may or may not be in
+--    canonical format.
+-- 2. However, it is important that we NOT translate from the JSValue
+--    to whatever internal datatype we are using and then back to JSValue,
+--    because that may not roundtrip: we must allow for additional fields
+--    in the JSValue that we ignore (and would therefore lose when we
+--    attempt to roundtrip).
+-- 3. We verify that all signatures are valid, but we cannot verify (here)
+--    that these signatures are signed with the right key, or that we
+--    have a sufficient number of signatures. This will be the
+--    responsibility of the calling code.
+verifySignatures :: JSValue -> Signatures -> Bool
+verifySignatures parsed (Signatures sigs) =
+    all (verifySignature $ renderCanonicalJSON parsed) sigs
+
+{-------------------------------------------------------------------------------
+  Uninterpreted signatures
+-------------------------------------------------------------------------------}
+
+-- | File with uninterpreted signatures
+--
+-- Sometimes we want to be able to read a file without interpreting the
+-- signatures (that is, resolving the key IDs) or doing any kind of checks on
+-- them. One advantage of this is that this allows us to read many file types
+-- without any key environment at all, which is sometimes useful.
+data UninterpretedSignatures a = UninterpretedSignatures {
+    uninterpretedSigned     :: a
+  , uninterpretedSignatures :: [PreSignature]
+  }
+  deriving (Show)
+
+-- | A signature with a key ID (rather than an actual key)
+--
+-- This corresponds precisely to the TUF representation of a signature.
+data PreSignature = PreSignature {
+    presignature :: BS.ByteString
+  , presigMethod :: Some KeyType
+  , presigKeyId  :: KeyId
+  }
+  deriving (Show)
+
+-- | Convert a pre-signature to a signature
+--
+-- Verifies that the key type matches the advertised method.
+fromPreSignature :: MonadKeys m => PreSignature -> m Signature
+fromPreSignature PreSignature{..} = do
+    key <- lookupKey presigKeyId
+    validate "key type" $ typecheckSome key presigMethod
+    return Signature {
+        signature    = presignature
+      , signatureKey = key
+      }
+
+-- | Convert signature to pre-signature
+toPreSignature :: Signature -> PreSignature
+toPreSignature Signature{..} = PreSignature {
+      presignature = signature
+    , presigMethod = somePublicKeyType signatureKey
+    , presigKeyId  = someKeyId         signatureKey
+    }
+
+-- | Convert a list of 'PreSignature's to a list of 'Signature's
+--
+-- This verifies the invariant that all signatures are made with different keys.
+-- We do this on the presignatures rather than the signatures so that we can do
+-- the check on key IDs, rather than keys (the latter don't have an Ord
+-- instance).
+fromPreSignatures :: MonadKeys m => [PreSignature] -> m Signatures
+fromPreSignatures sigs = do
+      validate "all signatures made with different keys" $
+        Set.size (Set.fromList (map presigKeyId sigs)) == length sigs
+      Signatures <$> mapM fromPreSignature sigs
+
+-- | Convert list of pre-signatures to a list of signatures
+toPreSignatures :: Signatures -> [PreSignature]
+toPreSignatures (Signatures sigs) = map toPreSignature sigs
+
+instance ReportSchemaErrors m => FromJSON m PreSignature where
+  fromJSON enc = do
+    kId    <- fromJSField enc "keyid"
+    method <- fromJSField enc "method"
+    sig    <- fromJSField enc "sig"
+    return PreSignature {
+        presignature = B64.toByteString sig
+      , presigMethod = method
+      , presigKeyId  = KeyId kId
+      }
+
+instance Monad m => ToJSON m PreSignature where
+  toJSON PreSignature{..} = mkObject [
+         ("keyid"  , return $ JSString . keyIdString $ presigKeyId)
+       , ("method" , toJSON $ presigMethod)
+       , ("sig"    , toJSON $ B64.fromByteString presignature)
+       ]
+
+instance ( ReportSchemaErrors m
+         , FromJSON m a
+         ) => FromJSON m (UninterpretedSignatures a) where
+  fromJSON envelope = do
+    enc <- fromJSField envelope "signed"
+    uninterpretedSigned     <- fromJSON enc
+    uninterpretedSignatures <- fromJSField envelope "signatures"
+    return UninterpretedSignatures{..}
+
+instance (Monad m, ToJSON m a) => ToJSON m (UninterpretedSignatures a) where
+  toJSON UninterpretedSignatures{..} = mkObject [
+         ("signed"     , toJSON uninterpretedSigned)
+       , ("signatures" , toJSON uninterpretedSignatures)
+       ]
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Snapshot.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Snapshot.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Snapshot.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE UndecidableInstances #-}
+module Hackage.Security.TUF.Snapshot (
+    Snapshot(..)
+  ) where
+
+import Control.Monad.Except
+import Control.Monad.Reader
+
+import Hackage.Security.JSON
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.FileInfo
+import Hackage.Security.TUF.FileMap
+import Hackage.Security.TUF.Layout.Repo
+import Hackage.Security.TUF.Signed
+import qualified Hackage.Security.TUF.FileMap as FileMap
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+data Snapshot = Snapshot {
+    snapshotVersion :: FileVersion
+  , snapshotExpires :: FileExpires
+
+    -- | File info for the root metadata
+    --
+    -- We list this explicitly in the snapshot so that we can check if we need
+    -- to update the root metadata without first having to download the entire
+    -- index tarball.
+  , snapshotInfoRoot :: FileInfo
+
+    -- | File info for the mirror metadata
+  , snapshotInfoMirrors :: FileInfo
+
+    -- | Compressed index tarball
+  , snapshotInfoTarGz :: FileInfo
+
+    -- | Uncompressed index tarball
+    --
+    -- Repositories are not required to provide this.
+  , snapshotInfoTar :: Maybe FileInfo
+  }
+
+instance HasHeader Snapshot where
+  fileVersion f x = (\y -> x { snapshotVersion = y }) <$> f (snapshotVersion x)
+  fileExpires f x = (\y -> x { snapshotExpires = y }) <$> f (snapshotExpires x)
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance MonadReader RepoLayout m => ToJSON m Snapshot where
+  toJSON Snapshot{..} = do
+      repoLayout <- ask
+      mkObject [
+          ("_type"   , return $ JSString "Snapshot")
+        , ("version" , toJSON snapshotVersion)
+        , ("expires" , toJSON snapshotExpires)
+        , ("meta"    , toJSON (snapshotMeta repoLayout))
+        ]
+    where
+      snapshotMeta repoLayout = FileMap.fromList $ [
+          (pathRoot       repoLayout , snapshotInfoRoot)
+        , (pathMirrors    repoLayout , snapshotInfoMirrors)
+        , (pathIndexTarGz repoLayout , snapshotInfoTarGz)
+        ] ++
+        [ (pathIndexTar   repoLayout , infoTar) | Just infoTar <- [snapshotInfoTar] ]
+
+instance ( MonadReader RepoLayout m
+         , MonadError DeserializationError m
+         , ReportSchemaErrors m
+         ) => FromJSON m Snapshot where
+  fromJSON enc = do
+    verifyType enc "Snapshot"
+    repoLayout          <- ask
+    snapshotVersion     <- fromJSField enc "version"
+    snapshotExpires     <- fromJSField enc "expires"
+    snapshotMeta        <- fromJSField enc "meta"
+    snapshotInfoRoot    <- FileMap.lookupM snapshotMeta (pathRoot       repoLayout)
+    snapshotInfoMirrors <- FileMap.lookupM snapshotMeta (pathMirrors    repoLayout)
+    snapshotInfoTarGz   <- FileMap.lookupM snapshotMeta (pathIndexTarGz repoLayout)
+    let snapshotInfoTar = FileMap.lookup (pathIndexTar repoLayout) snapshotMeta
+    return Snapshot{..}
+
+instance (MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Snapshot) where
+  fromJSON = signedFromJSON
+
+{-------------------------------------------------------------------------------
+  Paths used in the snapshot
+
+  NOTE: Since the snapshot lives in the top-level directory of the repository,
+  we can safely reinterpret "relative to the repo root" as "relative to the
+  snapshot"; hence, this use of 'castRoot' is okay.
+-------------------------------------------------------------------------------}
+
+pathRoot, pathMirrors, pathIndexTarGz, pathIndexTar :: RepoLayout -> TargetPath
+pathRoot       = TargetPathRepo . repoLayoutRoot
+pathMirrors    = TargetPathRepo . repoLayoutMirrors
+pathIndexTarGz = TargetPathRepo . repoLayoutIndexTarGz
+pathIndexTar   = TargetPathRepo . repoLayoutIndexTar
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Targets.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Targets.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Targets.hs
@@ -0,0 +1,130 @@
+module Hackage.Security.TUF.Targets (
+    -- * TUF types
+    Targets(..)
+  , Delegations(..)
+  , DelegationSpec(..)
+  , Delegation(..)
+    -- ** Util
+  , targetsLookup
+  ) where
+
+import Hackage.Security.JSON
+import Hackage.Security.Key
+import Hackage.Security.Key.Env (KeyEnv)
+import Hackage.Security.TUF.Common
+import Hackage.Security.TUF.FileInfo
+import Hackage.Security.TUF.FileMap (FileMap, TargetPath)
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.Patterns
+import Hackage.Security.TUF.Signed
+import Hackage.Security.Util.Some
+import qualified Hackage.Security.TUF.FileMap as FileMap
+
+{-------------------------------------------------------------------------------
+  TUF types
+-------------------------------------------------------------------------------}
+
+-- | Target metadata
+--
+-- Most target files do not need expiry dates because they are not subject to
+-- change (and hence attacks like freeze attacks are not a concern).
+data Targets = Targets {
+    targetsVersion     :: FileVersion
+  , targetsExpires     :: FileExpires
+  , targetsTargets     :: FileMap
+  , targetsDelegations :: Maybe Delegations
+  }
+  deriving (Show)
+
+-- | Delegations
+--
+-- Much like the Root datatype, this must have an invariant that ALL used keys
+-- (apart from the global keys, which are in the root key environment) must
+-- be listed in 'delegationsKeys'.
+data Delegations = Delegations {
+    delegationsKeys  :: KeyEnv
+  , delegationsRoles :: [DelegationSpec]
+  }
+  deriving (Show)
+
+-- | Delegation specification
+--
+-- NOTE: This is a close analogue of 'RoleSpec'.
+data DelegationSpec = DelegationSpec {
+    delegationSpecKeys      :: [Some PublicKey]
+  , delegationSpecThreshold :: KeyThreshold
+  , delegation              :: Delegation
+  }
+  deriving (Show)
+
+instance HasHeader Targets where
+  fileVersion f x = (\y -> x { targetsVersion = y }) <$> f (targetsVersion x)
+  fileExpires f x = (\y -> x { targetsExpires = y }) <$> f (targetsExpires x)
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+targetsLookup :: TargetPath -> Targets -> Maybe FileInfo
+targetsLookup fp Targets{..} = FileMap.lookup fp targetsTargets
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m DelegationSpec where
+  toJSON DelegationSpec{delegation = Delegation fp name, ..} = mkObject [
+        ("name"      , toJSON name)
+      , ("keyids"    , return . JSArray . map writeKeyAsId $ delegationSpecKeys)
+      , ("threshold" , toJSON delegationSpecThreshold)
+      , ("path"      , toJSON fp)
+      ]
+
+instance MonadKeys m => FromJSON m DelegationSpec where
+  fromJSON enc = do
+    delegationName          <- fromJSField enc "name"
+    delegationSpecKeys      <- mapM readKeyAsId =<< fromJSField enc "keyids"
+    delegationSpecThreshold <- fromJSField enc "threshold"
+    delegationPath          <- fromJSField enc "path"
+    case parseDelegation delegationName delegationPath of
+      Left  err        -> expected ("valid name/path combination: " ++ err) Nothing
+      Right delegation -> return DelegationSpec{..}
+
+-- NOTE: Unlike the Root object, the keys that are used to sign the delegations
+-- are NOT listed inside the delegations, so the same "bootstrapping" problems
+-- do not arise here.
+instance Monad m => ToJSON m Delegations where
+  toJSON Delegations{..} = mkObject [
+        ("keys"  , toJSON delegationsKeys)
+      , ("roles" , toJSON delegationsRoles)
+      ]
+
+instance MonadKeys m => FromJSON m Delegations where
+  fromJSON enc = do
+    delegationsKeys  <- fromJSField enc "keys"
+    delegationsRoles <- fromJSField enc "roles"
+    return Delegations{..}
+
+instance Monad m => ToJSON m Targets where
+  toJSON Targets{..} = mkObject $ mconcat [
+      [ ("_type"       , return $ JSString "Targets")
+      , ("version"     , toJSON targetsVersion)
+      , ("expires"     , toJSON targetsExpires)
+      , ("targets"     , toJSON targetsTargets)
+      ]
+    , [ ("delegations" , toJSON d) | Just d <- [ targetsDelegations ] ]
+    ]
+
+instance MonadKeys m => FromJSON m Targets where
+  fromJSON enc = do
+    verifyType enc "Targets"
+    targetsVersion     <- fromJSField    enc "version"
+    targetsExpires     <- fromJSField    enc "expires"
+    targetsTargets     <- fromJSField    enc "targets"
+    targetsDelegations <- fromJSOptField enc "delegations"
+    return Targets{..}
+
+-- TODO: This is okay right now because targets do not introduce additional
+-- keys, but will no longer be okay once we have author keys.
+instance MonadKeys m => FromJSON m (Signed Targets) where
+  fromJSON = signedFromJSON
diff --git a/hackage-security/hackage-security/src/Hackage/Security/TUF/Timestamp.hs b/hackage-security/hackage-security/src/Hackage/Security/TUF/Timestamp.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/TUF/Timestamp.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE UndecidableInstances #-}
+module Hackage.Security.TUF.Timestamp (
+    Timestamp(..)
+  ) where
+
+import Control.Monad.Except
+import Control.Monad.Reader
+
+import Hackage.Security.JSON
+import Hackage.Security.TUF.FileInfo
+import Hackage.Security.TUF.FileMap
+import Hackage.Security.TUF.Header
+import Hackage.Security.TUF.Layout.Repo
+import Hackage.Security.TUF.Signed
+import qualified Hackage.Security.TUF.FileMap as FileMap
+
+{-------------------------------------------------------------------------------
+  Datatypes
+-------------------------------------------------------------------------------}
+
+data Timestamp = Timestamp {
+    timestampVersion      :: FileVersion
+  , timestampExpires      :: FileExpires
+  , timestampInfoSnapshot :: FileInfo
+  }
+
+instance HasHeader Timestamp where
+  fileVersion f x = (\y -> x { timestampVersion = y }) <$> f (timestampVersion x)
+  fileExpires f x = (\y -> x { timestampExpires = y }) <$> f (timestampExpires x)
+
+{-------------------------------------------------------------------------------
+  JSON
+-------------------------------------------------------------------------------}
+
+instance MonadReader RepoLayout m => ToJSON m Timestamp where
+  toJSON Timestamp{..} = do
+      repoLayout <- ask
+      mkObject [
+          ("_type"   , return $ JSString "Timestamp")
+        , ("version" , toJSON timestampVersion)
+        , ("expires" , toJSON timestampExpires)
+        , ("meta"    , toJSON (timestampMeta repoLayout))
+        ]
+    where
+      timestampMeta repoLayout = FileMap.fromList [
+          (pathSnapshot repoLayout, timestampInfoSnapshot)
+        ]
+
+instance ( MonadReader RepoLayout m
+         , MonadError DeserializationError m
+         , ReportSchemaErrors m
+         ) => FromJSON m Timestamp where
+  fromJSON enc = do
+    verifyType enc "Timestamp"
+    repoLayout            <- ask
+    timestampVersion      <- fromJSField enc "version"
+    timestampExpires      <- fromJSField enc "expires"
+    timestampMeta         <- fromJSField enc "meta"
+    timestampInfoSnapshot <- FileMap.lookupM timestampMeta (pathSnapshot repoLayout)
+    return Timestamp{..}
+
+instance (MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Timestamp) where
+  fromJSON = signedFromJSON
+
+{-------------------------------------------------------------------------------
+  Paths used in the timestamp
+
+  NOTE: Since the timestamp lives in the top-level directory of the repository,
+  we can safely reinterpret "relative to the repo root" as "relative to the
+  timestamp"; hence, this use of 'castRoot' is okay.
+-------------------------------------------------------------------------------}
+
+pathSnapshot :: RepoLayout -> TargetPath
+pathSnapshot = TargetPathRepo . repoLayoutSnapshot
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Trusted.hs b/hackage-security/hackage-security/src/Hackage/Security/Trusted.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Trusted.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE CPP #-}
+#if __GLASGOW_HASKELL__ >= 710
+{-# LANGUAGE StaticPointers #-}
+#endif
+module Hackage.Security.Trusted (
+    module Hackage.Security.Trusted.TCB
+    -- * Derived functions
+  , (<$$>)
+    -- ** Role verification
+  , VerifyRole(..)
+    -- ** File info verification
+  , trustedFileInfoEqual
+  ) where
+
+import Data.Function (on)
+import Data.Time
+import Hackage.Security.TUF
+import Hackage.Security.Trusted.TCB hiding (DeclareTrusted)
+
+{-------------------------------------------------------------------------------
+  Combinators on trusted values
+-------------------------------------------------------------------------------}
+
+-- | Apply a static function to a trusted argument
+(<$$>) :: StaticPtr (a -> b) -> Trusted a -> Trusted b
+(<$$>) = trustApply . trustStatic
+
+{-------------------------------------------------------------------------------
+  Role verification
+-------------------------------------------------------------------------------}
+
+class VerifyRole a where
+  verifyRole :: Trusted Root      -- ^ Root data
+             -> TargetPath        -- ^ Source (for error messages)
+             -> Maybe FileVersion -- ^ Previous version (if available)
+             -> Maybe UTCTime     -- ^ Time now (if checking expiry)
+             -> Signed a          -- ^ Mirrors to verify
+             -> Either VerificationError (SignaturesVerified a)
+
+instance VerifyRole Root where
+  verifyRole = verifyRole' . (static (rootRolesRoot . rootRoles) <$$>)
+
+instance VerifyRole Timestamp where
+  verifyRole = verifyRole' . (static (rootRolesTimestamp . rootRoles) <$$>)
+
+instance VerifyRole Snapshot where
+  verifyRole = verifyRole' . (static (rootRolesSnapshot . rootRoles) <$$>)
+
+instance VerifyRole Mirrors where
+  verifyRole = verifyRole' . (static (rootRolesMirrors . rootRoles) <$$>)
+
+{-------------------------------------------------------------------------------
+  File info verification
+-------------------------------------------------------------------------------}
+
+-- | Variation on 'knownFileInfoEqual' for 'Trusted' 'FileInfo'
+trustedFileInfoEqual :: Trusted FileInfo -> Trusted FileInfo -> Bool
+trustedFileInfoEqual = knownFileInfoEqual `on` trusted
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Trusted/TCB.hs b/hackage-security/hackage-security/src/Hackage/Security/Trusted/TCB.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Trusted/TCB.hs
@@ -0,0 +1,320 @@
+{-# LANGUAGE CPP #-}
+module Hackage.Security.Trusted.TCB (
+    -- * Trusted values
+    Trusted(DeclareTrusted)
+  , trusted
+  , trustStatic
+  , trustVerified
+  , trustApply
+  , trustElems
+    -- * Verification errors
+  , VerificationError(..)
+  , RootUpdated(..)
+  , VerificationHistory
+    -- * Role verification
+  , SignaturesVerified -- opaque
+  , signaturesVerified
+  , verifyRole'
+  , verifyFingerprints
+#if __GLASGOW_HASKELL__ >= 710
+    -- * Re-exports
+  , StaticPtr
+#else
+    -- * Fake static pointers
+  , StaticPtr
+  , static
+#endif
+  ) where
+
+import Control.Exception
+import Control.Monad.Except
+import Data.Typeable
+import Data.Time
+import Hackage.Security.TUF
+import Hackage.Security.JSON
+import Hackage.Security.Key
+import Hackage.Security.Util.Pretty
+import qualified Hackage.Security.Util.Lens as Lens
+
+#if __GLASGOW_HASKELL__ >= 710
+import GHC.StaticPtr
+#else
+-- Fake static pointers for ghc < 7.10. This means Trusted offers no
+-- additional type safety, but that's okay: we can still verify the code
+-- with ghc 7.10 and get the additional checks.
+newtype StaticPtr a = StaticPtr { deRefStaticPtr :: a }
+
+static :: a -> StaticPtr a
+static = StaticPtr
+#endif
+
+-- | Trusted values
+--
+-- Trusted values originate in only two ways:
+--
+-- * Anything that is statically known is trusted ('trustStatic')
+-- * If we have "dynamic" data we can trust it once we have verified the
+--   the signatures (trustSigned).
+--
+-- NOTE: Trusted is NOT a functor. If it was we could define
+--
+-- > trustAnything :: a -> Trusted a
+-- > trustAnything a = fmap (const a) (trustStatic (static ()))
+--
+-- Consequently, it is neither a monad nor a comonad. However, we _can_ apply
+-- trusted functions to trusted arguments ('trustApply').
+--
+-- The 'DeclareTrusted' constructor is exported, but any use of it should be
+-- verified.
+newtype Trusted a = DeclareTrusted { trusted :: a }
+  deriving (Eq, Show)
+
+trustStatic :: StaticPtr a -> Trusted a
+trustStatic = DeclareTrusted . deRefStaticPtr
+
+trustVerified :: SignaturesVerified a -> Trusted a
+trustVerified = DeclareTrusted . signaturesVerified
+
+-- | Equivalent of '<*>'
+--
+-- Trusted isn't quite applicative (no pure, not a functor), but it is
+-- somehow Applicative-like: we have the equivalent of '<*>'
+trustApply :: Trusted (a -> b) -> Trusted a -> Trusted b
+trustApply (DeclareTrusted f) (DeclareTrusted x) = DeclareTrusted (f x)
+
+-- | Trust all elements of some trusted (traversable) container
+--
+-- If we have, say, a trusted list of values, we should be able to get a list
+-- of trusted values out of it.
+--
+-- > trustElems :: Trusted [a] -> [Trusted a]
+--
+-- NOTE. It might appear that the more natural primitive to offer is a
+-- 'sequenceA'-like operator such as
+--
+-- > trustSeq :: Applicative f => Trusted (f a) -> f (Trusted a)
+--
+-- However, this is unsound. To see this, consider that @((->) a)@ is
+-- 'Applicative' (it's the reader monad); hence, we can instantiate 'trustSeq'
+-- at
+--
+-- > trustSeq :: Trusted (a -> a) -> a -> Trusted a
+--
+-- and by passing @trustStatic (static id)@ make 'Trusted' a functor, which we
+-- certainly don't want to do (see comments for 'Trusted').
+--
+-- So why is it okay when we insist on 'Traversable' rather than 'Applicative'?
+-- To see this, it's instructive to consider how we might make a @((->) a)@ an
+-- instance of 'Traversable'. If we define the domain of enumerable types as
+--
+-- > class Eq a => Enumerable a where
+-- >   enumerate :: [a]
+--
+-- then we can make @((->) r)@ traversable by
+--
+-- > instance Enumerable r => Traversable ((->) r) where
+-- >   sequenceA f = rebuild <$> sequenceA ((\r -> (r,) <$> f r) <$> enumerate)
+-- >     where
+-- >       rebuild :: [(r, a)] -> r -> a
+-- >       rebuild fun arg = fromJust (lookup arg fun)
+--
+-- The idea is that if the domain of a function is enumerable, we can apply the
+-- function to each possible input, collect the outputs, and construct a new
+-- function by pairing the inputs with the outputs. I.e., if we had something of
+-- type
+--
+-- > a -> IO b
+--
+-- and @a@ is enumerable, we just run the @IO@ action on each possible @a@ and
+-- collect all @b@s to get a pure function @a -> b@. Of course, you probably
+-- don't want to be doing that, but the point is that as far as the type system
+-- is concerned you could.
+--
+-- In the context of 'Trusted', this means that we can derive
+--
+-- > enumPure :: Enumerable a => a -> Trusted a
+--
+-- but in a way this this makes sense anyway. If a domain is enumerable, it
+-- would not be unreasonable to change @Enumerable@ to
+--
+-- > class Eq a => Enumerable a where
+-- >   enumerate :: [StaticPtr a]
+--
+-- so we could define @enumPure@ as
+--
+-- > enumPure :: Enumerable a => a -> Trusted a
+-- > enumPure x = trustStatic
+-- >            $ fromJust (find ((== x) . deRefStaticPtr) enumerate)
+--
+-- In other words, we just enumerate the entire domain as trusted values
+-- (because we defined them locally) and then return the one that matched the
+-- untrusted value.
+--
+-- The conclusion from all of this is that the types of untrusted input  (like
+-- the types of the TUF files we download from the server) should probably not
+-- be considered enumerable.
+trustElems :: Traversable f => Trusted (f a) -> f (Trusted a)
+trustElems (DeclareTrusted fa) = DeclareTrusted `fmap` fa
+
+{-------------------------------------------------------------------------------
+  Role verification
+-------------------------------------------------------------------------------}
+
+newtype SignaturesVerified a = SignaturesVerified { signaturesVerified :: a }
+
+-- | Errors thrown during role validation
+data VerificationError =
+     -- | Not enough signatures signed with the appropriate keys
+     VerificationErrorSignatures TargetPath
+
+     -- | The file is expired
+   | VerificationErrorExpired TargetPath
+
+     -- | The file version is less than the previous version
+   | VerificationErrorVersion TargetPath
+
+     -- | File information mismatch
+   | VerificationErrorFileInfo TargetPath
+
+     -- | We tried to lookup file information about a particular target file,
+     -- but the information wasn't in the corresponding @targets.json@ file.
+   | VerificationErrorUnknownTarget TargetPath
+
+     -- | The metadata for the specified target is missing a SHA256
+   | VerificationErrorMissingSHA256 TargetPath
+
+     -- | Some verification errors materialize as deserialization errors
+     --
+     -- For example: if we try to deserialize a timestamp file but the timestamp
+     -- key has been rolled over, deserialization of the file will fail with
+     -- 'DeserializationErrorUnknownKey'.
+   | VerificationErrorDeserialization TargetPath DeserializationError
+
+     -- | The spec stipulates that if a verification error occurs during
+     -- the check for updates, we must download new root information and
+     -- start over. However, we limit how often we attempt this.
+     --
+     -- We record all verification errors that occurred before we gave up.
+   | VerificationErrorLoop VerificationHistory
+   deriving (Typeable)
+
+-- | Root metadata updated (as part of the normal update process)
+data RootUpdated = RootUpdated
+  deriving (Typeable)
+
+type VerificationHistory = [Either RootUpdated VerificationError]
+
+#if MIN_VERSION_base(4,8,0)
+deriving instance Show VerificationError
+deriving instance Show RootUpdated
+instance Exception VerificationError where displayException = pretty
+instance Exception RootUpdated where displayException = pretty
+#else
+instance Exception VerificationError
+instance Show VerificationError where show = pretty
+instance Show RootUpdated where show = pretty
+instance Exception RootUpdated
+#endif
+
+instance Pretty VerificationError where
+  pretty (VerificationErrorSignatures file) =
+      pretty file ++ " does not have enough signatures signed with the appropriate keys"
+  pretty (VerificationErrorExpired file) =
+      pretty file ++ " is expired"
+  pretty (VerificationErrorVersion file) =
+      "Version of " ++ pretty file ++ " is less than the previous version"
+  pretty (VerificationErrorFileInfo file) =
+      "Invalid hash for " ++ pretty file
+  pretty (VerificationErrorUnknownTarget file) =
+      pretty file ++ " not found in corresponding target metadata"
+  pretty (VerificationErrorMissingSHA256 file) =
+      "Missing SHA256 hash for " ++ pretty file
+  pretty (VerificationErrorDeserialization file err) =
+      "Could not deserialize " ++ pretty file ++ ": " ++ pretty err
+  pretty (VerificationErrorLoop es) =
+      "Verification loop. Errors in order:\n"
+   ++ unlines (map (("  " ++) . either pretty pretty) es)
+
+instance Pretty RootUpdated where
+  pretty RootUpdated = "Root information updated"
+
+-- | Role verification
+--
+-- NOTE: We throw an error when the version number _decreases_, but allow it
+-- to be the same. This is sufficient: the file number is there so that
+-- attackers cannot replay old files. It cannot protect against freeze attacks
+-- (that's what the expiry date is for), so "replaying" the same file is not
+-- a problem. If an attacker changes the contents of the file but not the
+-- version number we have an inconsistent situation, but this is not something
+-- we need to worry about: in this case the attacker will need to resign the
+-- file or otherwise the signature won't match, and if the attacker has
+-- compromised the key then he might just as well increase the version number
+-- and resign.
+--
+-- NOTE 2: We are not actually verifying the signatures _themselves_ here
+-- (we did that when we parsed the JSON). We are merely verifying the provenance
+-- of the keys.
+verifyRole' :: forall a. HasHeader a
+            => Trusted (RoleSpec a)     -- ^ For signature validation
+            -> TargetPath               -- ^ File source (for error messages)
+            -> Maybe FileVersion        -- ^ Previous version (if available)
+            -> Maybe UTCTime            -- ^ Time now (if checking expiry)
+            -> Signed a -> Either VerificationError (SignaturesVerified a)
+verifyRole' (trusted -> RoleSpec{roleSpecThreshold = KeyThreshold threshold, ..})
+            targetPath
+            mPrev
+            mNow
+            Signed{signatures = Signatures sigs, ..} =
+    runExcept go
+  where
+    go :: Except VerificationError (SignaturesVerified a)
+    go = do
+      -- Verify expiry date
+      case mNow of
+        Just now ->
+          when (isExpired now (Lens.get fileExpires signed)) $
+            throwError $ VerificationErrorExpired targetPath
+        _otherwise ->
+          return ()
+
+      -- Verify timestamp
+      case mPrev of
+        Nothing   -> return ()
+        Just prev ->
+          when (Lens.get fileVersion signed < prev) $
+            throwError $ VerificationErrorVersion targetPath
+
+      -- Verify signatures
+      -- NOTE: We only need to verify the keys that were used; if the signature
+      -- was invalid we would already have thrown an error constructing Signed.
+      -- (Similarly, if two signatures were made by the same key, the FromJSON
+      -- instance for Signatures would have thrown an error.)
+      unless (length (filter isRoleSpecKey sigs) >= fromIntegral threshold) $
+        throwError $ VerificationErrorSignatures targetPath
+
+      -- Everything is A-OK!
+      return $ SignaturesVerified signed
+
+    isRoleSpecKey :: Signature -> Bool
+    isRoleSpecKey Signature{..} = signatureKey `elem` roleSpecKeys
+
+-- | Variation on 'verifyRole' that uses key IDs rather than keys
+--
+-- This is used during the bootstrap process.
+--
+-- See <http://en.wikipedia.org/wiki/Public_key_fingerprint>.
+verifyFingerprints :: [KeyId]
+                   -> KeyThreshold
+                   -> TargetPath      -- ^ For error messages
+                   -> Signed Root
+                   -> Either VerificationError (SignaturesVerified Root)
+verifyFingerprints fingerprints
+                   (KeyThreshold threshold)
+                   targetPath
+                   Signed{signatures = Signatures sigs, ..} =
+    if length (filter isTrustedKey sigs) >= fromIntegral threshold
+      then Right $ SignaturesVerified signed
+      else Left $ VerificationErrorSignatures targetPath
+  where
+    isTrustedKey :: Signature -> Bool
+    isTrustedKey Signature{..} = someKeyId signatureKey `elem` fingerprints
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Base64.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Base64.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Base64.hs
@@ -0,0 +1,31 @@
+module Hackage.Security.Util.Base64 (
+    Base64 -- opaque
+  , fromByteString
+  , toByteString
+  ) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8  as C8  -- only called on B64-enc strings
+import qualified Data.ByteString.Base64 as B64
+
+import Hackage.Security.Util.JSON
+
+-- | Simple wrapper around bytestring with ToJSON and FromJSON instances that
+-- use base64 encoding.
+newtype Base64 = Base64 ByteString
+
+fromByteString :: ByteString -> Base64
+fromByteString = Base64
+
+toByteString :: Base64 -> ByteString
+toByteString (Base64 bs) = bs
+
+instance Monad m => ToJSON m Base64 where
+  toJSON (Base64 bs) = toJSON (C8.unpack (B64.encode bs))
+
+instance ReportSchemaErrors m => FromJSON m Base64 where
+  fromJSON val = do
+    str <- fromJSON val
+    case B64.decode (C8.pack str) of
+      Left _err -> expected "base-64 encoded string" Nothing
+      Right bs  -> return $ Base64 bs
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Checked.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Checked.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Checked.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-unused-binds #-}
+
+#if __GLASGOW_HASKELL__ >= 708
+{-# LANGUAGE RoleAnnotations     #-}
+{-# LANGUAGE IncoherentInstances #-}
+#endif
+
+-- | Checked exceptions
+module Hackage.Security.Util.Checked (
+    Throws
+  , unthrow
+    -- ** Base exceptions
+  , throwChecked
+  , catchChecked
+  , handleChecked
+  , tryChecked
+  , checkIO
+  , throwUnchecked
+  , internalError
+  ) where
+
+import Control.Exception (Exception, IOException)
+import qualified Control.Exception as Base
+
+#if __GLASGOW_HASKELL__ >= 708
+import GHC.Prim (coerce)
+#else
+import Unsafe.Coerce (unsafeCoerce)
+#endif
+
+{-------------------------------------------------------------------------------
+  Basic infrastructure
+-------------------------------------------------------------------------------}
+
+-- | Checked exceptions
+class Throws e where
+
+#if __GLASGOW_HASKELL__ >= 708
+type role Throws representational
+#endif
+
+unthrow :: forall a e proxy . proxy e -> (Throws e => a) -> a
+unthrow _ x = unWrap (coerceWrap (Wrap x :: Wrap e a))
+
+{-------------------------------------------------------------------------------
+  Base exceptions
+-------------------------------------------------------------------------------}
+
+-- | Throw a checked exception
+throwChecked :: (Exception e, Throws e) => e -> IO a
+throwChecked = Base.throwIO
+
+-- | Catch a checked exception
+catchChecked :: forall a e. Exception e
+             => (Throws e => IO a) -> (e -> IO a) -> IO a
+catchChecked act = Base.catch (unthrow (Proxy :: Proxy e) act)
+
+-- | 'catchChecked' with the arguments reversed
+handleChecked :: Exception e => (e -> IO a) -> (Throws e => IO a) -> IO a
+handleChecked act handler = catchChecked handler act
+
+-- | Like 'try', but for checked exceptions
+tryChecked :: Exception e => (Throws e => IO a) -> IO (Either e a)
+tryChecked act = catchChecked (Right <$> act) (return . Left)
+
+-- | Rethrow IO exceptions as checked exceptions
+checkIO :: Throws IOException => IO a -> IO a
+checkIO = Base.handle $ \(ex :: IOException) -> throwChecked ex
+
+-- | Throw an unchecked exception
+--
+-- This is just an alias for 'throw', but makes it evident that this is a very
+-- intentional use of an unchecked exception.
+throwUnchecked :: Exception e => e -> IO a
+throwUnchecked = Base.throwIO
+
+-- | Variation on 'throwUnchecked' for internal errors
+internalError :: String -> IO a
+internalError = throwUnchecked . userError
+
+{-------------------------------------------------------------------------------
+  Auxiliary definitions (not exported)
+-------------------------------------------------------------------------------}
+
+-- | Wrap an action that may throw a checked exception
+--
+-- This is used internally in 'unthrow' to avoid impredicative
+-- instantiation of the type of 'coerce'/'unsafeCoerce'.
+newtype Wrap e a = Wrap { unWrap :: Throws e => a }
+
+coerceWrap :: Wrap e a -> Wrap (Catch e) a
+#if __GLASGOW_HASKELL__ >= 708
+coerceWrap = coerce
+#else
+coerceWrap = unsafeCoerce
+#endif
+
+data Proxy a = Proxy
+
+newtype Catch a = Catch a
+instance Throws (Catch e) where
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/IO.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/IO.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/IO.hs
@@ -0,0 +1,61 @@
+module Hackage.Security.Util.IO (
+    -- * Miscelleneous
+    getFileSize
+  , handleDoesNotExist
+  , withDirLock
+    -- * Debugging
+  , timedIO
+  ) where
+
+import Control.Exception
+import Data.Time
+import System.IO hiding (openTempFile, withFile)
+import System.IO.Error
+
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  Miscelleneous
+-------------------------------------------------------------------------------}
+
+getFileSize :: (Num a, FsRoot root) => Path root -> IO a
+getFileSize fp = fromInteger <$> withFile fp ReadMode hFileSize
+
+handleDoesNotExist :: IO a -> IO (Maybe a)
+handleDoesNotExist act =
+   handle aux (Just <$> act)
+  where
+    aux e =
+      if isDoesNotExistError e
+        then return Nothing
+        else throwIO e
+
+-- | Attempt to create a filesystem lock in the specified directory
+--
+-- Given a file @/path/to@, we do this by attempting to create the directory
+-- @//path/to/hackage-security-lock@, and deleting the directory again
+-- afterwards. Creating a directory that already exists will throw an exception
+-- on most OSs (certainly Linux, OSX and Windows) and is a reasonably common way
+-- to implement a lock file.
+withDirLock :: Path Absolute -> IO a -> IO a
+withDirLock dir = bracket_ takeLock releaseLock
+  where
+    lock :: Path Absolute
+    lock = dir </> fragment "hackage-security-lock"
+
+    takeLock, releaseLock :: IO ()
+    takeLock    = createDirectory lock
+    releaseLock = removeDirectory lock
+
+{-------------------------------------------------------------------------------
+  Debugging
+-------------------------------------------------------------------------------}
+
+timedIO :: String -> IO a -> IO a
+timedIO label act = do
+    before <- getCurrentTime
+    result <- act
+    after  <- getCurrentTime
+    hPutStrLn stderr $ label ++ ": " ++ show (after `diffUTCTime` before)
+    hFlush stderr
+    return result
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/JSON.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/JSON.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/JSON.hs
@@ -0,0 +1,212 @@
+{-# LANGUAGE CPP #-}
+#if __GLASGOW_HASKELL__ < 710
+{-# LANGUAGE OverlappingInstances #-}
+#endif
+-- |
+module Hackage.Security.Util.JSON (
+    -- * Type classes
+    ToJSON(..)
+  , FromJSON(..)
+  , ToObjectKey(..)
+  , FromObjectKey(..)
+  , ReportSchemaErrors(..)
+  , Expected
+  , Got
+  , expected'
+    -- * Utility
+  , fromJSObject
+  , fromJSField
+  , fromJSOptField
+  , mkObject
+    -- * Re-exports
+  , JSValue(..)
+  , Int54
+  ) where
+
+import Control.Monad (liftM)
+import Data.Map (Map)
+import Data.Time
+import Text.JSON.Canonical
+import Network.URI
+import qualified Data.Map as Map
+
+#if !MIN_VERSION_time(1,5,0)
+import System.Locale (defaultTimeLocale)
+#endif
+
+import Hackage.Security.Util.Path
+
+{-------------------------------------------------------------------------------
+  ToJSON and FromJSON classes
+
+  We parameterize over the monad here to avoid mutual module dependencies.
+-------------------------------------------------------------------------------}
+
+class ToJSON m a where
+  toJSON :: a -> m JSValue
+
+class FromJSON m a where
+  fromJSON :: JSValue -> m a
+
+-- | Used in the 'ToJSON' instance for 'Map'
+class ToObjectKey m a where
+  toObjectKey :: a -> m String
+
+-- | Used in the 'FromJSON' instance for 'Map'
+class FromObjectKey m a where
+  fromObjectKey :: String -> m a
+
+-- | Monads in which we can report schema errors
+class (Applicative m, Monad m) => ReportSchemaErrors m where
+  expected :: Expected -> Maybe Got -> m a
+
+type Expected = String
+type Got      = String
+
+expected' :: ReportSchemaErrors m => Expected -> JSValue -> m a
+expected' descr val = expected descr (Just (describeValue val))
+  where
+    describeValue :: JSValue -> String
+    describeValue (JSNull    ) = "null"
+    describeValue (JSBool   _) = "bool"
+    describeValue (JSNum    _) = "num"
+    describeValue (JSString _) = "string"
+    describeValue (JSArray  _) = "array"
+    describeValue (JSObject _) = "object"
+
+unknownField :: ReportSchemaErrors m => String -> m a
+unknownField field = expected ("field " ++ show field) Nothing
+
+{-------------------------------------------------------------------------------
+  ToObjectKey and FromObjectKey instances
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToObjectKey m String where
+  toObjectKey = return
+
+instance Monad m => FromObjectKey m String where
+  fromObjectKey = return
+
+instance Monad m => ToObjectKey m (Path root) where
+  toObjectKey (Path fp) = return fp
+
+instance Monad m => FromObjectKey m (Path root) where
+  fromObjectKey = liftM Path . fromObjectKey
+
+{-------------------------------------------------------------------------------
+  ToJSON and FromJSON instances
+-------------------------------------------------------------------------------}
+
+instance Monad m => ToJSON m JSValue where
+  toJSON = return
+
+instance Monad m => FromJSON m JSValue where
+  fromJSON = return
+
+instance Monad m => ToJSON m String where
+  toJSON = return . JSString
+
+instance ReportSchemaErrors m => FromJSON m String where
+  fromJSON (JSString str) = return str
+  fromJSON val            = expected' "string" val
+
+instance Monad m => ToJSON m Int54 where
+  toJSON = return . JSNum
+
+instance ReportSchemaErrors m => FromJSON m Int54 where
+  fromJSON (JSNum i) = return i
+  fromJSON val       = expected' "int" val
+
+instance
+#if __GLASGOW_HASKELL__ >= 710
+  {-# OVERLAPPABLE #-}
+#endif
+    (Monad m, ToJSON m a) => ToJSON m [a] where
+  toJSON = liftM JSArray . mapM toJSON
+
+instance
+#if __GLASGOW_HASKELL__ >= 710
+  {-# OVERLAPPABLE #-}
+#endif
+    (ReportSchemaErrors m, FromJSON m a) => FromJSON m [a] where
+  fromJSON (JSArray as) = mapM fromJSON as
+  fromJSON val          = expected' "array" val
+
+instance Monad m => ToJSON m UTCTime where
+  toJSON = return . JSString . formatTime defaultTimeLocale "%FT%TZ"
+
+instance ReportSchemaErrors m => FromJSON m UTCTime where
+  fromJSON enc = do
+    str <- fromJSON enc
+    case parseTimeM False defaultTimeLocale "%FT%TZ" str of
+      Just time -> return time
+      Nothing   -> expected "valid date-time string" (Just str)
+#if !MIN_VERSION_time(1,5,0)
+    where
+      parseTimeM _trim = parseTime
+#endif
+
+instance ( Monad m
+         , ToObjectKey m k
+         , ToJSON m a
+         ) => ToJSON m (Map k a) where
+  toJSON = liftM JSObject . mapM aux . Map.toList
+    where
+      aux :: (k, a) -> m (String, JSValue)
+      aux (k, a) = do k' <- toObjectKey k; a' <- toJSON a; return (k', a')
+
+instance ( ReportSchemaErrors m
+         , Ord k
+         , FromObjectKey m k
+         , FromJSON m a
+         ) => FromJSON m (Map k a) where
+  fromJSON enc = do
+      obj <- fromJSObject enc
+      Map.fromList <$> mapM aux obj
+    where
+      aux :: (String, JSValue) -> m (k, a)
+      aux (k, a) = (,) <$> fromObjectKey k <*> fromJSON a
+
+instance Monad m => ToJSON m URI where
+  toJSON = toJSON . show
+
+instance ReportSchemaErrors m => FromJSON m URI where
+  fromJSON enc = do
+    str <- fromJSON enc
+    case parseURI str of
+      Nothing  -> expected "valid URI" (Just str)
+      Just uri -> return uri
+
+{-------------------------------------------------------------------------------
+  Utility
+-------------------------------------------------------------------------------}
+
+fromJSObject :: ReportSchemaErrors m => JSValue -> m [(String, JSValue)]
+fromJSObject (JSObject obj) = return obj
+fromJSObject val            = expected' "object" val
+
+-- | Extract a field from a JSON object
+fromJSField :: (ReportSchemaErrors m, FromJSON m a)
+            => JSValue -> String -> m a
+fromJSField val nm = do
+    obj <- fromJSObject val
+    case lookup nm obj of
+      Just fld -> fromJSON fld
+      Nothing  -> unknownField nm
+
+fromJSOptField :: (ReportSchemaErrors m, FromJSON m a)
+               => JSValue -> String -> m (Maybe a)
+fromJSOptField val nm = do
+    obj <- fromJSObject val
+    case lookup nm obj of
+      Just fld -> Just <$> fromJSON fld
+      Nothing  -> return Nothing
+
+mkObject :: forall m. Monad m => [(String, m JSValue)] -> m JSValue
+mkObject = liftM JSObject . sequenceFields
+  where
+    sequenceFields :: [(String, m JSValue)] -> m [(String, JSValue)]
+    sequenceFields []               = return []
+    sequenceFields ((fld,val):flds) = do val' <- val
+                                         flds' <- sequenceFields flds
+                                         return ((fld,val'):flds')
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Lens.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Lens.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Lens.hs
@@ -0,0 +1,48 @@
+-- | Some very simple lens definitions (to avoid further dependencies)
+--
+-- Intended to be double-imported
+-- > import Hackage.Security.Util.Lens (Lens)
+-- > import qualified Hackage.Security.Util.Lens as Lens
+module Hackage.Security.Util.Lens (
+    -- * Generic definitions
+    Lens
+  , Lens'
+  , get
+  , modify
+  , set
+    -- * Specific lenses
+  , lookupM
+  ) where
+
+import Control.Applicative
+import Data.Functor.Identity
+
+{-------------------------------------------------------------------------------
+  General definitions
+-------------------------------------------------------------------------------}
+
+-- | Polymorphic lens
+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
+
+-- | Monomorphic lens
+type Lens' s a = Lens s s a a
+
+get :: Lens' s a -> s -> a
+get l = getConst . l Const
+
+modify :: Lens s t a b -> (a -> b) -> s -> t
+modify l f = runIdentity . l (Identity . f)
+
+set :: Lens s t a b -> b -> s -> t
+set l = modify l . const
+
+{-------------------------------------------------------------------------------
+  Specific lenses
+-------------------------------------------------------------------------------}
+
+lookupM :: forall a b. (Eq a, Monoid b) => a -> Lens' [(a, b)] b
+lookupM a f = go
+  where
+    go []                       = (\b'  -> [(a, b')]  ) <$> f mempty
+    go ((a', b):xs) | a == a'   = (\b'  -> (a, b'):xs ) <$> f b
+                    | otherwise = (\xs' -> (a', b):xs') <$> go xs
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Path.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Path.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Path.hs
@@ -0,0 +1,459 @@
+-- | A more type-safe version of file paths
+--
+-- This module is intended to replace imports of System.FilePath, and
+-- additionally exports thin wrappers around common IO functions.  To facilitate
+-- importing this module unqualified we also re-export some  definitions from
+-- System.IO (importing both would likely lead to name clashes).
+--
+-- Note that his module does not import any other modules from Hackage.Security;
+-- everywhere else we use Path instead of FilePath directly.
+{-# LANGUAGE CPP #-}
+module Hackage.Security.Util.Path (
+    -- * Paths
+    Path(..)
+  , castRoot
+    -- * FilePath-like operations on paths with arbitrary roots
+  , takeDirectory
+  , takeFileName
+  , (<.>)
+  , splitExtension
+    -- * Unrooted paths
+  , Unrooted
+  , (</>)
+  , rootPath
+  , unrootPath
+  , toUnrootedFilePath
+  , fromUnrootedFilePath
+  , fragment
+  , joinFragments
+  , isPathPrefixOf
+    -- * File-system paths
+  , Relative
+  , Absolute
+  , HomeDir
+  , FsRoot(..)
+  , FsPath(..)
+    -- ** Conversions
+  , toFilePath
+  , fromFilePath
+  , makeAbsolute
+  , fromAbsoluteFilePath
+    -- ** Wrappers around System.IO
+  , withFile
+  , openTempFile'
+    -- ** Wrappers around Data.ByteString
+  , readLazyByteString
+  , readStrictByteString
+  , writeLazyByteString
+  , writeStrictByteString
+    -- ** Wrappers around System.Directory
+  , copyFile
+  , createDirectory
+  , createDirectoryIfMissing
+  , removeDirectory
+  , doesFileExist
+  , doesDirectoryExist
+  , removeFile
+  , getTemporaryDirectory
+  , getDirectoryContents
+  , getRecursiveContents
+  , renameFile
+  , getCurrentDirectory
+    -- * Wrappers around Codec.Archive.Tar
+  , Tar
+  , tarIndexLookup
+  , tarAppend
+    -- * Wrappers around Network.URI
+  , Web
+  , toURIPath
+  , fromURIPath
+  , uriPath
+  , modifyUriPath
+    -- * Re-exports
+  , IOMode(..)
+  , BufferMode(..)
+  , Handle
+  , SeekMode(..)
+  , IO.hSetBuffering
+  , IO.hClose
+  , IO.hFileSize
+  , IO.hSeek
+  ) where
+
+import Control.Monad
+import Data.List (isPrefixOf)
+import System.IO (IOMode(..), BufferMode(..), Handle, SeekMode(..))
+import System.IO.Unsafe (unsafeInterleaveIO)
+import qualified Data.ByteString         as BS
+import qualified Data.ByteString.Lazy    as BS.L
+import qualified System.FilePath         as FP
+import qualified System.IO               as IO
+import qualified System.Directory        as Dir
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Index as TarIndex
+import qualified Network.URI             as URI
+
+import Hackage.Security.Util.Pretty
+
+{-------------------------------------------------------------------------------
+  Paths
+-------------------------------------------------------------------------------}
+
+-- | Paths
+--
+-- A 'Path' is simply a 'FilePath' with a type-level tag indicating where this
+-- path is rooted (relative to the current directory, absolute path, relative to
+-- a web domain, whatever). Most operations on 'Path' are just lifted versions
+-- of the operations on the underlying 'FilePath'. The tag however allows us to
+-- give a lot of operations a more meaningful type. For instance, it does not
+-- make sense to append two absolute paths together; instead, we can only append
+-- an unrooted path to another path. It also means we avoid bugs where we use
+-- one kind of path where we expect another.
+newtype Path a = Path { unPath :: FilePath }
+  deriving (Show, Eq, Ord)
+
+-- | Reinterpret the root of a path
+--
+-- This literally just changes the type-level tag; use with caution!
+castRoot :: Path root -> Path root'
+castRoot (Path fp) = Path fp
+
+{-------------------------------------------------------------------------------
+  FilePath-like operations on paths with an arbitrary root
+-------------------------------------------------------------------------------}
+
+takeDirectory :: Path a -> Path a
+takeDirectory = liftFP FP.takeDirectory
+
+takeFileName :: Path a -> String
+takeFileName = liftFromFP FP.takeFileName
+
+(<.>) :: Path a -> String -> Path a
+fp <.> ext = liftFP (FP.<.> ext) fp
+
+splitExtension :: Path a -> (Path a, String)
+splitExtension (Path fp) = (Path fp', ext)
+  where
+    (fp', ext) = FP.splitExtension fp
+
+{-------------------------------------------------------------------------------
+  Unrooted paths
+-------------------------------------------------------------------------------}
+
+-- | Type-level tag for unrooted paths
+--
+-- Unrooted paths need a root before they can be interpreted.
+data Unrooted
+
+instance Pretty (Path Unrooted) where
+  pretty (Path fp) = fp
+
+(</>) :: Path a -> Path Unrooted -> Path a
+(</>) = liftFP2 (FP.</>)
+
+-- | Reinterpret an unrooted path
+--
+-- This is an alias for 'castRoot'; see comments there.
+rootPath :: Path Unrooted -> Path root
+rootPath (Path fp) = Path fp
+
+-- | Forget a path's root
+--
+-- This is an alias for 'castRoot'; see comments there.
+unrootPath :: Path root -> Path Unrooted
+unrootPath (Path fp) = Path fp
+
+toUnrootedFilePath :: Path Unrooted -> FilePath
+toUnrootedFilePath = unPath
+
+fromUnrootedFilePath :: FilePath -> Path Unrooted
+fromUnrootedFilePath = Path
+
+-- | A path fragment (like a single directory or filename)
+fragment :: String -> Path Unrooted
+fragment = fromUnrootedFilePath
+
+joinFragments :: [String] -> Path Unrooted
+joinFragments = liftToFP FP.joinPath
+
+isPathPrefixOf :: Path Unrooted -> Path Unrooted -> Bool
+isPathPrefixOf = liftFromFP2 isPrefixOf
+
+{-------------------------------------------------------------------------------
+  File-system paths
+-------------------------------------------------------------------------------}
+
+data Relative
+data Absolute
+data HomeDir
+
+instance Pretty (Path Absolute) where
+  pretty (Path fp) = fp
+
+instance Pretty (Path Relative) where
+  pretty (Path fp) = "./" ++ fp
+
+instance Pretty (Path HomeDir) where
+  pretty (Path fp) = "~/" ++ fp
+
+-- | A file system root can be interpreted as an (absolute) FilePath
+class FsRoot root where
+  toAbsoluteFilePath :: Path root -> IO FilePath
+
+instance FsRoot Relative where
+    toAbsoluteFilePath (Path fp) = go fp
+      where
+        go :: FilePath -> IO FilePath
+#if MIN_VERSION_directory(1,2,2)
+        go = Dir.makeAbsolute
+#else
+        -- copied implementation from the directory package
+        go = (FP.normalise <$>) . absolutize
+        absolutize path -- avoid the call to `getCurrentDirectory` if we can
+          | FP.isRelative path = (FP.</> path) . FP.addTrailingPathSeparator <$>
+                                 Dir.getCurrentDirectory
+          | otherwise          = return path
+#endif
+
+instance FsRoot Absolute where
+    toAbsoluteFilePath (Path fp) = return fp
+
+instance FsRoot HomeDir where
+    toAbsoluteFilePath (Path fp) = do
+      home <- Dir.getHomeDirectory
+      return $ home FP.</> fp
+
+-- | Abstract over a file system root
+--
+-- see 'fromFilePath'
+data FsPath = forall root. FsRoot root => FsPath (Path root)
+
+{-------------------------------------------------------------------------------
+  Conversions
+-------------------------------------------------------------------------------}
+
+toFilePath :: Path Absolute -> FilePath
+toFilePath (Path fp) = fp
+
+fromFilePath :: FilePath -> FsPath
+fromFilePath fp
+    | FP.isAbsolute      fp = FsPath (Path fp  :: Path Absolute)
+    | Just fp' <- atHome fp = FsPath (Path fp' :: Path HomeDir)
+    | otherwise             = FsPath (Path fp  :: Path Relative)
+  where
+    -- TODO: I don't know if there a standard way that Windows users refer to
+    -- their home directory. For now, we'll only interpret '~'. Everybody else
+    -- can specify an absolute path if this doesn't work.
+    atHome :: FilePath -> Maybe FilePath
+    atHome "~" = Just ""
+    atHome ('~':sep:fp') | FP.isPathSeparator sep = Just fp'
+    atHome _otherwise = Nothing
+
+makeAbsolute :: FsPath -> IO (Path Absolute)
+makeAbsolute (FsPath p) = Path <$> toAbsoluteFilePath p
+
+fromAbsoluteFilePath :: FilePath -> Path Absolute
+fromAbsoluteFilePath fp
+  | FP.isAbsolute fp = Path fp
+  | otherwise        = error "fromAbsoluteFilePath: not an absolute path"
+
+{-------------------------------------------------------------------------------
+  Wrappers around System.IO
+-------------------------------------------------------------------------------}
+
+-- | Wrapper around 'withFile'
+withFile :: FsRoot root => Path root -> IOMode -> (Handle -> IO r) -> IO r
+withFile path mode callback = do
+    filePath <- toAbsoluteFilePath path
+    IO.withFile filePath mode callback
+
+-- | Wrapper around 'openBinaryTempFileWithDefaultPermissions'
+--
+-- NOTE: The caller is responsible for cleaning up the temporary file.
+openTempFile' :: FsRoot root => Path root -> String -> IO (Path Absolute, Handle)
+openTempFile' path template = do
+    filePath <- toAbsoluteFilePath path
+    (tempFilePath, h) <- IO.openBinaryTempFileWithDefaultPermissions filePath template
+    return (fromAbsoluteFilePath tempFilePath, h)
+
+{-------------------------------------------------------------------------------
+  Wrappers around Data.ByteString.*
+-------------------------------------------------------------------------------}
+
+readLazyByteString :: FsRoot root => Path root -> IO BS.L.ByteString
+readLazyByteString path = do
+    filePath <- toAbsoluteFilePath path
+    BS.L.readFile filePath
+
+readStrictByteString :: FsRoot root => Path root -> IO BS.ByteString
+readStrictByteString path = do
+    filePath <- toAbsoluteFilePath path
+    BS.readFile filePath
+
+writeLazyByteString :: FsRoot root => Path root -> BS.L.ByteString -> IO ()
+writeLazyByteString path bs = do
+    filePath <- toAbsoluteFilePath path
+    BS.L.writeFile filePath bs
+
+writeStrictByteString :: FsRoot root => Path root -> BS.ByteString -> IO ()
+writeStrictByteString path bs = do
+    filePath <- toAbsoluteFilePath path
+    BS.writeFile filePath bs
+
+{-------------------------------------------------------------------------------
+  Wrappers around System.Directory
+-------------------------------------------------------------------------------}
+
+copyFile :: (FsRoot root, FsRoot root') => Path root -> Path root' -> IO ()
+copyFile src dst = do
+    src' <- toAbsoluteFilePath src
+    dst' <- toAbsoluteFilePath dst
+    Dir.copyFile src' dst'
+
+createDirectory :: FsRoot root => Path root -> IO ()
+createDirectory path = Dir.createDirectory =<< toAbsoluteFilePath path
+
+createDirectoryIfMissing :: FsRoot root => Bool -> Path root -> IO ()
+createDirectoryIfMissing createParents path = do
+    filePath <- toAbsoluteFilePath path
+    Dir.createDirectoryIfMissing createParents filePath
+
+removeDirectory :: FsRoot root => Path root -> IO ()
+removeDirectory path = Dir.removeDirectory =<< toAbsoluteFilePath path
+
+doesFileExist :: FsRoot root => Path root -> IO Bool
+doesFileExist path = do
+    filePath <- toAbsoluteFilePath path
+    Dir.doesFileExist filePath
+
+doesDirectoryExist :: FsRoot root => Path root -> IO Bool
+doesDirectoryExist path = do
+    filePath <- toAbsoluteFilePath path
+    Dir.doesDirectoryExist filePath
+
+removeFile :: FsRoot root => Path root -> IO ()
+removeFile path = do
+    filePath <- toAbsoluteFilePath path
+    Dir.removeFile filePath
+
+getTemporaryDirectory :: IO (Path Absolute)
+getTemporaryDirectory = fromAbsoluteFilePath <$> Dir.getTemporaryDirectory
+
+-- | Return the immediate children of a directory
+--
+-- Filters out @"."@ and @".."@.
+getDirectoryContents :: FsRoot root => Path root -> IO [Path Unrooted]
+getDirectoryContents path = do
+    filePath <- toAbsoluteFilePath path
+    fragments <$> Dir.getDirectoryContents filePath
+  where
+    fragments :: [String] -> [Path Unrooted]
+    fragments = map fromUnrootedFilePath . filter (not . skip)
+
+    skip :: String -> Bool
+    skip "."  = True
+    skip ".." = True
+    skip _    = False
+
+-- | Recursive traverse a directory structure
+--
+-- Returns a set of paths relative to the directory specified. The list is
+-- lazily constructed, so that directories are only read when required.
+-- (This is also essential to ensure that this function does not build the
+-- entire result in memory before returning, potentially running out of heap.)
+getRecursiveContents :: FsRoot root => Path root -> IO [Path Unrooted]
+getRecursiveContents root = go emptyPath
+  where
+    go :: Path Unrooted -> IO [Path Unrooted]
+    go subdir = unsafeInterleaveIO $ do
+      entries <- getDirectoryContents (root </> subdir)
+      liftM concat $ forM entries $ \entry -> do
+        let path = subdir </> entry
+        isDirectory <- doesDirectoryExist (root </> path)
+        if isDirectory then go path
+                       else return [path]
+
+    emptyPath :: Path Unrooted
+    emptyPath = Path (FP.joinPath [])
+
+renameFile :: (FsRoot root, FsRoot root')
+           => Path root  -- ^ Old
+           -> Path root' -- ^ New
+           -> IO ()
+renameFile old new = do
+    old' <- toAbsoluteFilePath old
+    new' <- toAbsoluteFilePath new
+    Dir.renameFile old' new'
+
+getCurrentDirectory :: IO (Path Absolute)
+getCurrentDirectory = do
+    cwd <- Dir.getCurrentDirectory
+    makeAbsolute $ fromFilePath cwd
+
+{-------------------------------------------------------------------------------
+  Wrappers around Codec.Archive.Tar.*
+-------------------------------------------------------------------------------}
+
+data Tar
+
+instance Pretty (Path Tar) where
+  pretty (Path fp) = "<tarball>/" ++ fp
+
+tarIndexLookup :: TarIndex.TarIndex -> Path Tar -> Maybe TarIndex.TarIndexEntry
+tarIndexLookup index path = TarIndex.lookup index path'
+  where
+    path' :: FilePath
+    path' = toUnrootedFilePath $ unrootPath path
+
+tarAppend :: (FsRoot root, FsRoot root')
+          => Path root   -- ^ Path of the @.tar@ file
+          -> Path root'  -- ^ Base directory
+          -> [Path Tar]  -- ^ Files to add, relative to the base dir
+          -> IO ()
+tarAppend tarFile baseDir contents = do
+    tarFile' <- toAbsoluteFilePath tarFile
+    baseDir' <- toAbsoluteFilePath baseDir
+    Tar.append tarFile' baseDir' contents'
+  where
+    contents' :: [FilePath]
+    contents' = map (toUnrootedFilePath . unrootPath) contents
+
+{-------------------------------------------------------------------------------
+  Wrappers around Network.URI
+-------------------------------------------------------------------------------}
+
+data Web
+
+toURIPath :: FilePath -> Path Web
+toURIPath = rootPath . fromUnrootedFilePath
+
+fromURIPath :: Path Web -> FilePath
+fromURIPath = toUnrootedFilePath . unrootPath
+
+uriPath :: URI.URI -> Path Web
+uriPath = toURIPath . URI.uriPath
+
+modifyUriPath :: URI.URI -> (Path Web -> Path Web) -> URI.URI
+modifyUriPath uri f = uri { URI.uriPath = f' (URI.uriPath uri) }
+  where
+    f' :: FilePath -> FilePath
+    f' = fromURIPath . f . toURIPath
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+liftFP :: (FilePath -> FilePath) -> Path a -> Path b
+liftFP f (Path fp) = Path (f fp)
+
+liftFP2 :: (FilePath -> FilePath -> FilePath) -> Path a -> Path b -> Path c
+liftFP2 f (Path fp) (Path fp') = Path (f fp fp')
+
+liftFromFP :: (FilePath -> x) -> Path a -> x
+liftFromFP f (Path fp) = f fp
+
+liftFromFP2 :: (FilePath -> FilePath -> x) -> Path a -> Path b -> x
+liftFromFP2 f (Path fp) (Path fp') = f fp fp'
+
+liftToFP :: (x -> FilePath) -> x -> Path a
+liftToFP f x = Path (f x)
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Pretty.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Pretty.hs
@@ -0,0 +1,8 @@
+-- | Producing human-reaadable strings
+module Hackage.Security.Util.Pretty (
+    Pretty(..)
+  ) where
+
+-- | Produce a human-readable string
+class Pretty a where
+  pretty :: a -> String
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Some.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Some.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Some.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE CPP #-}
+-- | Hiding existentials
+module Hackage.Security.Util.Some (
+    Some(..)
+    -- ** Equality
+  , DictEq(..)
+  , SomeEq(..)
+    -- ** Serialization
+  , DictShow(..)
+  , SomeShow(..)
+    -- ** Pretty-printing
+  , DictPretty(..)
+  , SomePretty(..)
+    -- ** Type checking
+  , typecheckSome
+#if !MIN_VERSION_base(4,7,0)
+    -- ** Compatibility with base < 4.7
+  , tyConSome
+#endif
+  ) where
+
+#if MIN_VERSION_base(4,7,0)
+import Data.Typeable (Typeable)
+#else
+import qualified Data.Typeable as Typeable
+#endif
+
+import Hackage.Security.Util.TypedEmbedded
+import Hackage.Security.Util.Pretty
+
+data Some f = forall a. Some (f a)
+
+#if MIN_VERSION_base(4,7,0)
+deriving instance Typeable Some
+#else
+tyConSome :: Typeable.TyCon
+tyConSome = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Util.Some" "Some"
+#endif
+
+{-------------------------------------------------------------------------------
+  Equality on Some types
+
+  Note that we cannot really do something similar for ordering; what value
+  should we return for
+
+  > Some (f x) `compare` Some (f x')
+
+  where @x :: a@, @x' :: a'@ and @a /= a'@? These are incomparable.
+-------------------------------------------------------------------------------}
+
+data DictEq a where
+  DictEq :: Eq a => DictEq a
+
+-- | Type @f@ satisfies @SomeEq f@ if @f a@ satisfies @Eq@ independent of @a@
+class SomeEq f where
+  someEq :: DictEq (f a)
+
+instance (Typed f, SomeEq f) => Eq (Some f) where
+  Some (x :: f a) == Some (y :: f a') =
+    case unify (typeOf x) (typeOf y) of
+      Nothing   -> False
+      Just Refl -> case someEq :: DictEq (f a) of DictEq -> x == y
+
+{-------------------------------------------------------------------------------
+  Showing Some types
+-------------------------------------------------------------------------------}
+
+data DictShow a where
+  DictShow :: Show a => DictShow a
+
+-- | Type @f@ satisfies @SomeShow f@ if @f a@ satisfies @Show@ independent of @a@
+class SomeShow f where
+  someShow :: DictShow (f a)
+
+instance SomeShow f => Show (Some f) where
+  show (Some (x :: f a)) =
+    case someShow :: DictShow (f a) of DictShow -> show x
+
+{-------------------------------------------------------------------------------
+  Pretty-printing Some types
+-------------------------------------------------------------------------------}
+
+data DictPretty a where
+  DictPretty :: Pretty a => DictPretty a
+
+-- | Type @f@ satisfies @SomeShow f@ if @f a@ satisfies @Show@ independent of @a@
+class SomePretty f where
+  somePretty :: DictPretty (f a)
+
+instance SomePretty f => Pretty (Some f) where
+  pretty (Some (x :: f a)) =
+    case somePretty :: DictPretty (f a) of DictPretty -> pretty x
+
+{-------------------------------------------------------------------------------
+  Typechecking Some types
+-------------------------------------------------------------------------------}
+
+typecheckSome :: Typed f => Some f -> Some (TypeOf f) -> Bool
+typecheckSome (Some x) (Some typ) =
+    case unify (typeOf x) typ of
+      Just Refl -> True
+      Nothing   -> False
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/Stack.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/Stack.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/Stack.hs
@@ -0,0 +1,8 @@
+-- | Heterogenous lists
+module Hackage.Security.Util.Stack (
+    (:-)(..)
+  ) where
+
+data h :- t = h :- t
+  deriving (Eq, Show)
+infixr 5 :-
diff --git a/hackage-security/hackage-security/src/Hackage/Security/Util/TypedEmbedded.hs b/hackage-security/hackage-security/src/Hackage/Security/Util/TypedEmbedded.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Hackage/Security/Util/TypedEmbedded.hs
@@ -0,0 +1,38 @@
+-- | Embedded languages with meta level types
+module Hackage.Security.Util.TypedEmbedded (
+    (:=:)(Refl)
+  , TypeOf
+  , Unify(..)
+  , Typed(..)
+  , AsType(..)
+  ) where
+
+-- | Type equality proofs
+--
+-- This is a direct copy of "type-equality:Data.Type.Equality"; if we don't
+-- mind the dependency we can use that package directly.
+data a :=: b where
+  Refl :: a :=: a
+
+type family TypeOf (f :: * -> *) :: * -> *
+
+-- | Equality check that gives us a type-level equality proof.
+class Unify f where
+  unify :: f typ -> f typ' -> Maybe (typ :=: typ')
+
+-- | Embedded languages with type inference
+class Unify (TypeOf f) => Typed f where
+  typeOf :: f typ -> TypeOf f typ
+
+-- | Cast from one type to another
+--
+-- By default (for language with type inference) we just compare the types
+-- returned by 'typeOf'; however, in languages in which terms can have more
+-- than one type this may not be the correct definition (indeed, for such
+-- languages we cannot give an instance of 'Typed').
+class AsType f where
+  asType :: f typ -> TypeOf f typ' -> Maybe (f typ')
+  default asType :: Typed f => f typ -> TypeOf f typ' -> Maybe (f typ')
+  asType x typ = case unify (typeOf x) typ of
+                   Just Refl -> Just x
+                   Nothing   -> Nothing
diff --git a/hackage-security/hackage-security/src/Prelude.hs b/hackage-security/hackage-security/src/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Prelude.hs
@@ -0,0 +1,32 @@
+-- | Smooth over differences between various ghc versions by making older
+-- preludes look like 4.8.0
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE CPP #-}
+module Prelude (
+    module P
+#if !MIN_VERSION_base(4,8,0)
+  , Applicative(..)
+  , Monoid(..)
+  , (<$>)
+  , (<$)
+  , Traversable(traverse)
+  , displayException
+#endif
+  ) where
+
+#if MIN_VERSION_base(4,8,0)
+import "base" Prelude as P
+#else
+#if MIN_VERSION_base(4,6,0)
+import "base" Prelude as P
+#else
+import "base" Prelude as P hiding (catch)
+#endif
+import Control.Applicative
+import Control.Exception (Exception)
+import Data.Monoid
+import Data.Traversable (Traversable(traverse))
+
+displayException :: Exception e => e -> String
+displayException = show
+#endif
diff --git a/hackage-security/hackage-security/src/Text/JSON/Canonical.hs b/hackage-security/hackage-security/src/Text/JSON/Canonical.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/src/Text/JSON/Canonical.hs
@@ -0,0 +1,272 @@
+{-# LANGUAGE CPP #-}
+--------------------------------------------------------------------
+-- |
+-- Module    : Text.JSON.Parsec
+-- Copyright : (c) Galois, Inc. 2007-2009, Duncan Coutts 2015
+--
+--
+-- Minimal implementation of Canonical JSON.
+--
+-- <http://wiki.laptop.org/go/Canonical_JSON>
+--
+-- A "canonical JSON" format is provided in order to provide meaningful and
+-- repeatable hashes of JSON-encoded data. Canonical JSON is parsable with any
+-- full JSON parser, but security-conscious applications will want to verify
+-- that input is in canonical form before authenticating any hash or signature
+-- on that input.
+--
+-- This implementation is derived from the json parser from the json package,
+-- with simplifications to meet the Canonical JSON grammar.
+--
+-- TODO: Known bugs/limitations:
+--
+--  * Decoding/encoding Unicode code-points beyond @U+00ff@ is currently broken
+--
+module Text.JSON.Canonical
+  ( JSValue(..)
+  , Int54
+  , parseCanonicalJSON
+  , renderCanonicalJSON
+  ) where
+
+import Text.ParserCombinators.Parsec
+         ( CharParser, (<|>), (<?>), many, between, sepBy
+         , satisfy, char, string, digit, spaces
+         , parse )
+import Control.Arrow (first)
+import Data.Bits (Bits)
+#if MIN_VERSION_base(4,7,0)
+import Data.Bits (FiniteBits)
+#endif
+import Data.Char (isDigit, digitToInt)
+import Data.Data (Data)
+import Data.Function (on)
+import Data.Int (Int64)
+import Data.Ix (Ix)
+import Data.List (foldl', sortBy)
+import Data.Typeable (Typeable)
+import Foreign.Storable (Storable)
+import Text.Printf (PrintfArg)
+import qualified Data.ByteString.Lazy.Char8 as BS
+
+data JSValue
+    = JSNull
+    | JSBool     !Bool
+    | JSNum      !Int54
+    | JSString   String
+    | JSArray    [JSValue]
+    | JSObject   [(String, JSValue)]
+    deriving (Show, Read, Eq, Ord)
+
+-- | 54-bit integer values
+--
+-- JavaScript can only safely represent numbers between @-(2^53 - 1)@ and
+-- @2^53 - 1@.
+--
+-- TODO: Although we introduce the type here, we don't actually do any bounds
+-- checking and just inherit all type class instance from Int64. We should
+-- probably define `fromInteger` to do bounds checking, give different instances
+-- for type classes such as `Bounded` and `FiniteBits`, etc.
+newtype Int54 = Int54 { int54ToInt64 :: Int64 }
+  deriving ( Bounded
+           , Enum
+           , Eq
+           , Integral
+           , Data
+           , Num
+           , Ord
+           , Real
+           , Ix
+#if MIN_VERSION_base(4,7,0)
+           , FiniteBits
+#endif
+           , Bits
+           , Storable
+           , PrintfArg
+           , Typeable
+           )
+
+instance Show Int54 where
+  show = show . int54ToInt64
+
+instance Read Int54 where
+  readsPrec p = map (first Int54) . readsPrec p
+
+------------------------------------------------------------------------------
+
+-- | Encode as \"Canonical\" JSON.
+--
+-- NB: Canonical JSON's string escaping rules deviate from RFC 7159
+-- JSON which requires
+--
+--    "All Unicode characters may be placed within the quotation
+--    marks, except for the characters that must be escaped: quotation
+--    mark, reverse solidus, and the control characters (@U+0000@
+--    through @U+001F@)."
+--
+-- Whereas the current specification of Canonical JSON explicitly
+-- requires to violate this by only escaping the quotation mark and
+-- the reverse solidus. This, however, contradicts Canonical JSON's
+-- statement that "Canonical JSON is parsable with any full JSON
+-- parser"
+--
+-- Consequently, Canonical JSON is not a proper subset of RFC 7159.
+renderCanonicalJSON :: JSValue -> BS.ByteString
+renderCanonicalJSON v = BS.pack (s_value v [])
+
+s_value :: JSValue -> ShowS
+s_value JSNull         = showString "null"
+s_value (JSBool False) = showString "false"
+s_value (JSBool True)  = showString "true"
+s_value (JSNum n)      = shows n
+s_value (JSString s)   = s_string s
+s_value (JSArray vs)   = s_array  vs
+s_value (JSObject fs)  = s_object (sortBy (compare `on` fst) fs)
+
+s_string :: String -> ShowS
+s_string s = showChar '"' . showl s
+  where showl []     = showChar '"'
+        showl (c:cs) = s_char c . showl cs
+
+        s_char '"'   = showChar '\\' . showChar '"'
+        s_char '\\'  = showChar '\\' . showChar '\\'
+        s_char c     = showChar c
+
+s_array :: [JSValue] -> ShowS
+s_array []           = showString "[]"
+s_array (v0:vs0)     = showChar '[' . s_value v0 . showl vs0
+  where showl []     = showChar ']'
+        showl (v:vs) = showChar ',' . s_value v . showl vs
+
+s_object :: [(String, JSValue)] -> ShowS
+s_object []               = showString "{}"
+s_object ((k0,v0):kvs0)   = showChar '{' . s_string k0
+                          . showChar ':' . s_value v0
+                          . showl kvs0
+  where showl []          = showChar '}'
+        showl ((k,v):kvs) = showChar ',' . s_string k
+                          . showChar ':' . s_value v
+                          . showl kvs
+
+------------------------------------------------------------------------------
+
+parseCanonicalJSON :: BS.ByteString -> Either String JSValue
+parseCanonicalJSON = either (Left . show) Right
+                   . parse p_value ""
+                   . BS.unpack
+
+p_value :: CharParser () JSValue
+p_value = spaces *> p_jvalue
+
+tok              :: CharParser () a -> CharParser () a
+tok p             = p <* spaces
+
+{-
+value:
+   string
+   number
+   object
+   array
+   true
+   false
+   null
+-}
+p_jvalue         :: CharParser () JSValue
+p_jvalue          =  (JSNull      <$  p_null)
+                 <|> (JSBool      <$> p_boolean)
+                 <|> (JSArray     <$> p_array)
+                 <|> (JSString    <$> p_string)
+                 <|> (JSObject    <$> p_object)
+                 <|> (JSNum       <$> p_number)
+                 <?> "JSON value"
+
+p_null           :: CharParser () ()
+p_null            = tok (string "null") >> return ()
+
+p_boolean        :: CharParser () Bool
+p_boolean         = tok
+                      (  (True  <$ string "true")
+                     <|> (False <$ string "false")
+                      )
+{-
+array:
+   []
+   [ elements ]
+elements:
+   value
+   value , elements
+-}
+p_array          :: CharParser () [JSValue]
+p_array           = between (tok (char '[')) (tok (char ']'))
+                  $ p_jvalue `sepBy` tok (char ',')
+
+{-
+string:
+   ""
+   " chars "
+chars:
+   char
+   char chars
+char:
+   any byte except hex 22 (") or hex 5C (\)
+   \\
+   \"
+-}
+p_string         :: CharParser () String
+p_string          = between (tok (char '"')) (tok (char '"')) (many p_char)
+  where p_char    =  (char '\\' >> p_esc)
+                 <|> (satisfy (\x -> x /= '"' && x /= '\\'))
+
+        p_esc     =  ('"'   <$ char '"')
+                 <|> ('\\'  <$ char '\\')
+                 <?> "escape character"
+{-
+object:
+    {}
+    { members }
+members:
+   pair
+   pair , members
+pair:
+   string : value
+-}
+p_object         :: CharParser () [(String,JSValue)]
+p_object          = between (tok (char '{')) (tok (char '}'))
+                  $ p_field `sepBy` tok (char ',')
+  where p_field   = (,) <$> (p_string <* tok (char ':')) <*> p_jvalue
+
+{-
+number:
+   int
+int:
+   digit
+   digit1-9 digits
+   - digit1-9
+   - digit1-9 digits
+digits:
+   digit
+   digit digits
+-}
+
+-- | Parse an int
+--
+-- TODO: Currently this allows for a maximum of 15 digits (i.e. a maximum value
+-- of @999,999,999,999,999@) as a crude approximation of the 'Int54' range.
+p_number         :: CharParser () Int54
+p_number          = tok
+                      (  (char '-' *> (negate <$> pnat))
+                     <|> pnat
+                     <|> zero
+                      )
+  where pnat      = (\d ds -> strToInt (d:ds)) <$> digit19 <*> manyN 14 digit
+        digit19   = satisfy (\c -> isDigit c && c /= '0') <?> "digit"
+        strToInt  = foldl' (\x d -> 10*x + digitToInt54 d) 0
+        zero      = 0 <$ char '0'
+
+digitToInt54 :: Char -> Int54
+digitToInt54 = fromIntegral . digitToInt
+
+manyN :: Int -> CharParser () a -> CharParser () [a]
+manyN 0 _ =  pure []
+manyN n p =  ((:) <$> p <*> manyN (n-1) p)
+         <|> pure []
diff --git a/hackage-security/hackage-security/tests/TestSuite.hs b/hackage-security/hackage-security/tests/TestSuite.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite.hs
@@ -0,0 +1,434 @@
+module Main (main) where
+
+-- stdlib
+import Control.Exception
+import Control.Monad
+import Data.Maybe (fromJust)
+import Data.Time
+import Network.URI (URI, parseURI)
+import Test.Tasty
+import Test.Tasty.HUnit
+import System.IO.Temp (withSystemTempDirectory)
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Repository
+import Hackage.Security.JSON (DeserializationError(..))
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Pretty
+import qualified Hackage.Security.Client.Repository.Remote as Remote
+import qualified Hackage.Security.Client.Repository.Cache  as Cache
+
+-- TestSuite
+import TestSuite.HttpMem
+import TestSuite.InMemCache
+import TestSuite.InMemRepo
+import TestSuite.InMemRepository
+import TestSuite.PrivateKeys
+import TestSuite.Util.StrictMVar
+
+{-------------------------------------------------------------------------------
+  TestSuite driver
+-------------------------------------------------------------------------------}
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "hackage-security" [
+      testGroup "InMem" [
+          testCase "testInMemInitialHasForUpdates" testInMemInitialHasUpdates
+        , testCase "testInMemNoUpdates"            testInMemNoUpdates
+        , testCase "testInMemUpdatesAfterCron"     testInMemUpdatesAfterCron
+        , testCase "testInMemKeyRollover"          testInMemKeyRollover
+        , testCase "testInMemOutdatedTimestamp"    testInMemOutdatedTimestamp
+        ]
+    , testGroup "HttpMem" [
+          testCase "testHttpMemInitialHasForUpdates" testHttpMemInitialHasUpdates
+        , testCase "testHttpMemNoUpdates"            testHttpMemNoUpdates
+        , testCase "testHttpMemUpdatesAfterCron"     testHttpMemUpdatesAfterCron
+        , testCase "testHttpMemKeyRollover"          testHttpMemKeyRollover
+        , testCase "testHttpMemOutdatedTimestamp"    testHttpMemOutdatedTimestamp
+        ]
+  ]
+
+{-------------------------------------------------------------------------------
+  In-memory tests
+
+  These tests test the core TUF infrastructure, but any specific Repository
+  implementation; instead, they use one specifically designed for testing
+  (almost a Repository mock-up).
+-------------------------------------------------------------------------------}
+
+-- | Initial check for updates: empty cache
+testInMemInitialHasUpdates :: Assertion
+testInMemInitialHasUpdates = inMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs [] $
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+
+-- | Check that if we run updates again, with no changes on the server,
+-- we get NoUpdates
+testInMemNoUpdates :: Assertion
+testInMemNoUpdates = inMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs [] $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs [] $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test that we have updates reported after the timestamp is resigned
+testInMemUpdatesAfterCron :: Assertion
+testInMemUpdatesAfterCron = inMemTest $ \inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs [] $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs [] $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    inMemRepoCron inMemRepo =<< getCurrentTime
+
+    withAssertLog "C" logMsgs [] $ do
+      assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "D" logMsgs [] $ do
+      assertEqual "D.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test what happens when the timestamp/snapshot keys rollover
+testInMemKeyRollover :: Assertion
+testInMemKeyRollover = inMemTest $ \inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs [] $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs [] $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    inMemRepoKeyRollover inMemRepo =<< getCurrentTime
+
+    let msgs = [verificationError $ unknownKeyError timestampPath]
+    withAssertLog "C" logMsgs msgs $ do
+      assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "D" logMsgs [] $ do
+      assertEqual "D.1" NoUpdates =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test what happens when server has an outdated timestamp
+-- (after a successful initial update)
+testInMemOutdatedTimestamp :: Assertion
+testInMemOutdatedTimestamp = inMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs [] $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs [] $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    now <- getCurrentTime
+    let (FileExpires fourDaysLater) = expiresInDays now 4
+
+    let msgs = replicate 5 (inHistory (Right (expired timestampPath)))
+    catchVerificationLoop msgs $ do
+      withAssertLog "C" logMsgs [] $ do
+        assertEqual "C.1" HasUpdates =<< checkForUpdates repo fourDaysLater
+
+{-------------------------------------------------------------------------------
+  Same tests, but going through the "real" Remote repository and Cache, though
+  still using an in-memory repository (with a HttpLib bridge)
+
+  These are almost hte same as the in-memory tests, but the log messages we
+  expect are slightly different because the Remote repository indicates what
+  is is downloading, and why.
+-------------------------------------------------------------------------------}
+
+-- | Initial check for updates: empty cache
+testHttpMemInitialHasUpdates :: Assertion
+testHttpMemInitialHasUpdates = httpMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs msgsInitialUpdate $
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+
+-- | Check that if we run updates again, with no changes on the server,
+-- we get NoUpdates
+testHttpMemNoUpdates :: Assertion
+testHttpMemNoUpdates = httpMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs msgsInitialUpdate $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs msgsNoUpdates $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test that we have updates reported after the timestamp is resigned
+testHttpMemUpdatesAfterCron :: Assertion
+testHttpMemUpdatesAfterCron = httpMemTest $ \inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs msgsInitialUpdate $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs msgsNoUpdates $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    inMemRepoCron inMemRepo =<< getCurrentTime
+
+    withAssertLog "C" logMsgs msgsResigned $ do
+      assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "D" logMsgs msgsNoUpdates $ do
+      assertEqual "D.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test what happens when the timestamp/snapshot keys rollover
+testHttpMemKeyRollover :: Assertion
+testHttpMemKeyRollover = httpMemTest $ \inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs msgsInitialUpdate $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs msgsNoUpdates $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    inMemRepoKeyRollover inMemRepo =<< getCurrentTime
+
+    withAssertLog "C" logMsgs msgsKeyRollover $ do
+      assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "D" logMsgs msgsNoUpdates $ do
+      assertEqual "D.1" NoUpdates =<< checkForUpdates repo =<< checkExpiry
+
+-- | Test what happens when server has an outdated timestamp
+-- (after a successful initial update)
+testHttpMemOutdatedTimestamp :: Assertion
+testHttpMemOutdatedTimestamp = httpMemTest $ \_inMemRepo logMsgs repo -> do
+    withAssertLog "A" logMsgs msgsInitialUpdate $ do
+      assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry
+    withAssertLog "B" logMsgs msgsNoUpdates $ do
+      assertEqual "B.2" NoUpdates  =<< checkForUpdates repo =<< checkExpiry
+
+    now <- getCurrentTime
+    let (FileExpires fourDaysLater) = expiresInDays now 4
+
+    let msgs = replicate 5 (inHistory (Right (expired timestampPath)))
+    catchVerificationLoop msgs $ do
+      withAssertLog "C" logMsgs [] $ do
+        assertEqual "C.1" HasUpdates =<< checkForUpdates repo fourDaysLater
+
+{-------------------------------------------------------------------------------
+  Log messages we expect when using the Remote repository
+-------------------------------------------------------------------------------}
+
+-- | The log messages we expect on the initial check for updates
+msgsInitialUpdate :: [LogMessage -> Bool]
+msgsInitialUpdate = [
+      selectedMirror inMemURI
+    , downloading isTimestamp
+    , downloading isSnapshot
+    , downloading isMirrors
+    , noLocalCopy
+    , downloading isIndex
+    ]
+
+-- | Log messages when we do a check for updates and there are no changes
+msgsNoUpdates :: [LogMessage -> Bool]
+msgsNoUpdates = [
+      selectedMirror inMemURI
+    , downloading isTimestamp
+    ]
+
+-- | Log messages we expect when the timestamp and snapshot have been resigned
+msgsResigned :: [LogMessage -> Bool]
+msgsResigned = [
+      selectedMirror inMemURI
+    , downloading isTimestamp
+    , downloading isSnapshot
+    ]
+
+-- | Log messages we expect when the timestamp key has been rolled over
+msgsKeyRollover :: [LogMessage -> Bool]
+msgsKeyRollover = [
+      selectedMirror inMemURI
+    , downloading isTimestamp
+    , verificationError $ unknownKeyError timestampPath
+    , downloading isRoot
+    , downloading isTimestamp
+    , downloading isSnapshot
+    -- Since we delete the timestamp and snapshot on a root info change,
+    -- we will then conclude that we need to update the mirrors and the index.
+    , downloading isMirrors
+    , updating isIndex
+    ]
+
+{-------------------------------------------------------------------------------
+  Classifying log messages
+-------------------------------------------------------------------------------}
+
+downloading :: (forall fs typ. RemoteFile fs typ -> Bool) -> LogMessage -> Bool
+downloading isFile (LogDownloading file) = isFile file
+downloading _ _ = False
+
+noLocalCopy :: LogMessage -> Bool
+noLocalCopy (LogCannotUpdate (RemoteIndex _ _) UpdateImpossibleNoLocalCopy) = True
+noLocalCopy _ = False
+
+selectedMirror :: URI -> LogMessage -> Bool
+selectedMirror mirror (LogSelectedMirror mirror') = mirror' == show mirror
+selectedMirror _ _ = False
+
+updating :: (forall fs typ. RemoteFile fs typ -> Bool) -> LogMessage -> Bool
+updating isFile (LogUpdating file) = isFile file
+updating _ _ = False
+
+expired :: TargetPath -> VerificationError -> Bool
+expired f (VerificationErrorExpired f') = f == f'
+expired _ _ = False
+
+unknownKeyError :: TargetPath -> VerificationError -> Bool
+unknownKeyError f (VerificationErrorDeserialization f' (DeserializationErrorUnknownKey _keyId)) =
+    f == f'
+unknownKeyError _ _ = False
+
+verificationError :: (VerificationError -> Bool) -> LogMessage -> Bool
+verificationError isErr (LogVerificationError err) = isErr err
+verificationError _ _ = False
+
+inHistory :: Either RootUpdated (VerificationError -> Bool) -> HistoryMsg -> Bool
+inHistory (Right isErr) (Right err) = isErr err
+inHistory (Left _)      (Left _)    = True
+inHistory _             _           = False
+
+type HistoryMsg = Either RootUpdated VerificationError
+
+catchVerificationLoop :: ([HistoryMsg -> Bool]) -> Assertion -> Assertion
+catchVerificationLoop history = handleJust isLoop handler
+  where
+    isLoop :: VerificationError -> Maybe VerificationHistory
+    isLoop (VerificationErrorLoop history') = Just history'
+    isLoop _ = Nothing
+
+    handler :: VerificationHistory -> Assertion
+    handler history' =
+      unless (length history == length history' && and (zipWith ($) history history')) $
+        assertFailure $ "Unexpected verification history:"
+                     ++ unlines (map pretty' history')
+
+    pretty' :: HistoryMsg -> String
+    pretty' (Left RootUpdated) = "root updated"
+    pretty' (Right err)        = pretty err
+
+{-------------------------------------------------------------------------------
+  Classifying files
+-------------------------------------------------------------------------------}
+
+isRoot :: RemoteFile fs typ -> Bool
+isRoot (RemoteRoot _) = True
+isRoot _ = False
+
+isIndex :: RemoteFile fs typ -> Bool
+isIndex (RemoteIndex _ _) = True
+isIndex _ = False
+
+isMirrors :: RemoteFile fs typ -> Bool
+isMirrors (RemoteMirrors _) = True
+isMirrors _ = False
+
+isSnapshot :: RemoteFile fs typ -> Bool
+isSnapshot (RemoteSnapshot _) = True
+isSnapshot _ = False
+
+isTimestamp :: RemoteFile fs typ -> Bool
+isTimestamp RemoteTimestamp = True
+isTimestamp _ = False
+
+timestampPath :: TargetPath
+timestampPath = TargetPathRepo $ repoLayoutTimestamp hackageRepoLayout
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Check the contents of the log
+assertLog :: String -> [LogMessage -> Bool] -> [LogMessage] -> Assertion
+assertLog label expected actual = go expected actual
+  where
+    go :: [LogMessage -> Bool] -> [LogMessage] -> Assertion
+    go []     []     = return ()
+    go []     as     = unexpected as
+    go (_:_)  []     = assertFailure $ label ++ ": expected log message"
+    go (e:es) (a:as) = if e a then go es as else unexpected [a]
+
+    unexpected :: [LogMessage] -> Assertion
+    unexpected msgs = assertFailure $ label ++ ": "
+                                   ++ "unexpected log messages:\n"
+                                   ++ unlines (map pretty msgs)
+                                   ++ "\nfull set of log messages was:\n"
+                                   ++ unlines (map pretty actual)
+
+-- | Run the actions and check its log messages
+withAssertLog :: String
+              -> MVar [LogMessage]
+              -> [LogMessage -> Bool]
+              -> Assertion -> Assertion
+withAssertLog label mv expected action = do
+    oldMsgs <- modifyMVar mv $ \old -> return ([], old)
+    action
+    newMsgs <- modifyMVar mv $ \new -> return (oldMsgs, new)
+    assertLog label expected newMsgs
+
+-- | Unit test using the in-memory repository/cache
+inMemTest :: ( ( Throws SomeRemoteError
+               , Throws VerificationError
+               ) => InMemRepo -> MVar [LogMessage] -> Repository InMemFile -> Assertion
+             )
+          -> Assertion
+inMemTest test = uncheckClientErrors $ do
+    now  <- getCurrentTime
+    keys <- createPrivateKeys
+    let root = initRoot now layout keys
+    withSystemTempDirectory "hackage-security-test" $ \tempDir' -> do
+      tempDir    <- makeAbsolute $ fromFilePath tempDir'
+      inMemRepo  <- newInMemRepo  layout root now keys
+      inMemCache <- newInMemCache tempDir layout
+      logMsgs    <- newMVar []
+
+      let logger msg = modifyMVar_ logMsgs $ \msgs -> return $ msgs ++ [msg]
+      repository <- newInMemRepository layout hackageIndexLayout inMemRepo inMemCache logger
+
+      bootstrap repository (map someKeyId (privateRoot keys)) (KeyThreshold 2)
+      test inMemRepo logMsgs repository
+  where
+    layout :: RepoLayout
+    layout = hackageRepoLayout
+
+-- | Unit test using the Remote repository but with the in-mem repo
+httpMemTest :: ( ( Throws SomeRemoteError
+                 , Throws VerificationError
+                 ) => InMemRepo -> MVar [LogMessage] -> Repository Remote.RemoteTemp -> Assertion
+               )
+            -> Assertion
+httpMemTest test = uncheckClientErrors $ do
+    now  <- getCurrentTime
+    keys <- createPrivateKeys
+    let root = initRoot now layout keys
+    withSystemTempDirectory "hackage-security-test" $ \tempDir' -> do
+      tempDir    <- makeAbsolute $ fromFilePath tempDir'
+      inMemRepo  <- newInMemRepo layout root now keys
+      logMsgs    <- newMVar []
+
+      let logger msg = modifyMVar_ logMsgs $ \msgs -> return $ msgs ++ [msg]
+          httpLib    = httpMem inMemRepo
+          cache      = Cache.Cache {
+                           cacheRoot   = tempDir </> fragment "cache"
+                         , cacheLayout = cabalCacheLayout
+                         }
+
+      Remote.withRepository httpLib
+                            [inMemURI]
+                            Remote.defaultRepoOpts
+                            cache
+                            hackageRepoLayout
+                            hackageIndexLayout
+                            logger
+                            $ \repository -> do
+        withAssertLog "bootstrap" logMsgs bootstrapMsgs $
+          bootstrap repository (map someKeyId (privateRoot keys)) (KeyThreshold 2)
+        test inMemRepo logMsgs repository
+  where
+    bootstrapMsgs :: [LogMessage -> Bool]
+    bootstrapMsgs = [ selectedMirror inMemURI
+                    , downloading isRoot
+                    ]
+
+    layout :: RepoLayout
+    layout = hackageRepoLayout
+
+-- | Base URI for the in-memory repository
+--
+-- This could really be anything at all
+inMemURI :: URI
+inMemURI = fromJust (parseURI "inmem://")
+
+-- | Return @Just@ the current time
+checkExpiry :: IO (Maybe UTCTime)
+checkExpiry = Just `fmap` getCurrentTime
diff --git a/hackage-security/hackage-security/tests/TestSuite/HttpMem.hs b/hackage-security/hackage-security/tests/TestSuite/HttpMem.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/HttpMem.hs
@@ -0,0 +1,70 @@
+-- | HttpLib bridge to the in-memory repository
+module TestSuite.HttpMem (
+    httpMem
+  ) where
+
+-- stdlib
+import Network.URI (URI)
+import qualified Data.ByteString.Lazy as BS.L
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Repository.HttpLib
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Some
+
+-- TestSuite
+import TestSuite.InMemRepo
+
+httpMem :: InMemRepo -> HttpLib
+httpMem inMemRepo = HttpLib {
+      httpGet      = get      inMemRepo
+    , httpGetRange = getRange inMemRepo
+    }
+
+{-------------------------------------------------------------------------------
+  Individual methods
+-------------------------------------------------------------------------------}
+
+-- | Download a file
+--
+-- Since we don't (yet?) make any attempt to simulate a cache, we ignore
+-- caching headers.
+get :: forall a. Throws SomeRemoteError
+    => InMemRepo
+    -> [HttpRequestHeader]
+    -> URI
+    -> ([HttpResponseHeader] -> BodyReader -> IO a)
+    -> IO a
+get InMemRepo{..} _requestHeaders uri callback = do
+    Some inMemFile <- inMemRepoGetPath $ castRoot (uriPath uri)
+    br <- bodyReaderFromBS $ inMemFileRender inMemFile
+    callback [HttpResponseAcceptRangesBytes] br
+
+-- | Download a byte range
+--
+-- Range is starting and (exclusive) end offset in bytes.
+--
+-- We ignore requests for compression; different servers deal with compression
+-- for byte range requests differently; in particular, Apache returns the range
+-- of the _compressed_ file, which is pretty useless for our purposes. For now
+-- we ignore this issue completely here.
+getRange :: forall a. Throws SomeRemoteError
+         => InMemRepo
+         -> [HttpRequestHeader]
+         -> URI
+         -> (Int, Int)
+         -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)
+         -> IO a
+getRange InMemRepo{..} _requestHeaders uri (fr, to) callback = do
+    Some inMemFile <- inMemRepoGetPath $ castRoot (uriPath uri)
+    br <- bodyReaderFromBS $ substr (inMemFileRender inMemFile)
+
+    let responseHeaders = concat [
+            [ HttpResponseAcceptRangesBytes ]
+          ]
+    callback HttpStatus206PartialContent responseHeaders br
+  where
+    substr :: BS.L.ByteString -> BS.L.ByteString
+    substr = BS.L.take (fromIntegral (to - fr)) . BS.L.drop (fromIntegral fr)
diff --git a/hackage-security/hackage-security/tests/TestSuite/InMemCache.hs b/hackage-security/hackage-security/tests/TestSuite/InMemCache.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/InMemCache.hs
@@ -0,0 +1,149 @@
+module TestSuite.InMemCache (
+    InMemCache(..)
+  , newInMemCache
+  ) where
+
+-- base
+import Control.Exception
+import qualified Codec.Compression.GZip as GZip
+import qualified Data.ByteString.Lazy   as BS.L
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.JSON
+import Hackage.Security.Util.Path
+
+-- TestSuite
+import TestSuite.Util.StrictMVar
+import TestSuite.InMemRepo
+
+data InMemCache = InMemCache {
+      inMemCacheGet     :: CachedFile -> IO (Maybe (Path Absolute))
+    , inMemCacheGetRoot :: IO (Path Absolute)
+    , inMemCacheClear   :: IO ()
+    , inMemCachePut     :: forall f typ. InMemFile typ -> Format f -> IsCached typ -> IO ()
+    }
+
+newInMemCache :: Path Absolute -> RepoLayout -> IO InMemCache
+newInMemCache tempDir layout = do
+    state <- newMVar $ initLocalState layout
+    return InMemCache {
+        inMemCacheGet     = get     state tempDir
+      , inMemCacheGetRoot = getRoot state tempDir
+      , inMemCacheClear   = clear   state
+      , inMemCachePut     = put     state
+      }
+
+{-------------------------------------------------------------------------------
+  "Local" state (the files we "cached")
+-------------------------------------------------------------------------------}
+
+data LocalState = LocalState {
+      cacheRepoLayout :: !RepoLayout
+    , cachedRoot      :: !(Maybe (Signed Root))
+    , cachedMirrors   :: !(Maybe (Signed Mirrors))
+    , cachedTimestamp :: !(Maybe (Signed Timestamp))
+    , cachedSnapshot  :: !(Maybe (Signed Snapshot))
+
+    -- We cache only the uncompressed index
+
+    -- (we can unambiguously construct the @.tar@ from the @.tar.gz@,
+    -- but not the other way around)
+    , cachedIndex :: Maybe BS.L.ByteString
+    }
+
+cachedRoot' :: LocalState -> Signed Root
+cachedRoot' LocalState{..} = needRoot cachedRoot
+
+needRoot :: Maybe a -> a
+needRoot Nothing    = error "InMemCache: no root info (did you bootstrap?)"
+needRoot (Just root) = root
+
+initLocalState :: RepoLayout -> LocalState
+initLocalState layout = LocalState {
+      cacheRepoLayout = layout
+    , cachedRoot      = Nothing
+    , cachedMirrors   = Nothing
+    , cachedTimestamp = Nothing
+    , cachedSnapshot  = Nothing
+    , cachedIndex     = Nothing
+    }
+
+{-------------------------------------------------------------------------------
+  Individual methods
+-------------------------------------------------------------------------------}
+
+-- | Get a cached file (if available)
+get :: MVar LocalState -> Path Absolute -> CachedFile -> IO (Maybe (Path Absolute))
+get state cacheTempDir cachedFile =
+      case cachedFile of
+        CachedRoot      -> serve "root.json"      $ render cachedRoot
+        CachedMirrors   -> serve "mirrors.json"   $ render cachedMirrors
+        CachedTimestamp -> serve "timestamp.json" $ render cachedTimestamp
+        CachedSnapshot  -> serve "snapshot.json"  $ render cachedSnapshot
+  where
+    render :: forall b. ToJSON WriteJSON b
+           => (LocalState -> Maybe b)
+           -> (LocalState -> Maybe BS.L.ByteString)
+    render f st = renderJSON (cacheRepoLayout st) `fmap` (f st)
+
+    serve :: String
+          -> (LocalState -> Maybe BS.L.ByteString)
+          -> IO (Maybe (Path Absolute))
+    serve template f =
+      withMVar state $ \st ->
+        case f st of
+          Nothing -> return Nothing
+          Just bs -> do (tempFile, h) <- openTempFile' cacheTempDir template
+                        BS.L.hPut h bs
+                        hClose h
+                        return $ Just tempFile
+
+-- | Get the cached root
+getRoot :: MVar LocalState -> Path Absolute -> IO (Path Absolute)
+getRoot state cacheTempDir =
+    needRoot `fmap` get state cacheTempDir CachedRoot
+
+-- | Clear all cached data
+clear :: MVar LocalState -> IO ()
+clear state = modifyMVar_ state $ \st -> return st {
+      cachedMirrors   = Nothing
+    , cachedTimestamp = Nothing
+    , cachedSnapshot  = Nothing
+    , cachedIndex     = Nothing
+    }
+
+-- | Cache a previously downloaded remote file
+put :: MVar LocalState -> InMemFile typ -> Format f -> IsCached typ -> IO ()
+put state = put' state . inMemFileRender
+
+put' :: MVar LocalState -> BS.L.ByteString -> Format f -> IsCached typ -> IO ()
+put' state bs = go
+  where
+    go :: Format f -> IsCached typ -> IO ()
+    go _   DontCache   = return ()
+    go FUn (CacheAs f) = go' f
+    go FGz (CacheAs _) = error "put: the impossible happened"
+    go FUn CacheIndex  = modifyMVar_ state $ \st -> return st {
+                             cachedIndex = Just bs
+                           }
+    go FGz CacheIndex  = modifyMVar_ state $ \st -> return st {
+                             cachedIndex = Just (GZip.decompress bs)
+                           }
+
+    go' :: CachedFile -> IO ()
+    go' CachedRoot      = go'' $ \x st -> st { cachedRoot      = Just x }
+    go' CachedTimestamp = go'' $ \x st -> st { cachedTimestamp = Just x }
+    go' CachedSnapshot  = go'' $ \x st -> st { cachedSnapshot  = Just x }
+    go' CachedMirrors   = go'' $ \x st -> st { cachedMirrors   = Just x }
+
+    go'' :: forall a. FromJSON ReadJSON_Keys_Layout a
+         => (a -> LocalState -> LocalState) -> IO ()
+    go'' f = do
+      modifyMVar_ state $ \st@LocalState{..} -> do
+        let keyEnv = rootKeys . signed . cachedRoot' $ st
+        case parseJSON_Keys_Layout keyEnv cacheRepoLayout bs of
+           Left  err    -> throwIO err
+           Right parsed -> return $ f parsed st
diff --git a/hackage-security/hackage-security/tests/TestSuite/InMemRepo.hs b/hackage-security/hackage-security/tests/TestSuite/InMemRepo.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/InMemRepo.hs
@@ -0,0 +1,262 @@
+module TestSuite.InMemRepo (
+    InMemRepo(..)
+  , newInMemRepo
+  , initRoot
+  , InMemFile(..)
+  , inMemFileRender
+  ) where
+
+-- stdlib
+import Control.Exception
+import Data.Time
+import qualified Codec.Archive.Tar      as Tar
+import qualified Codec.Compression.GZip as GZip
+import qualified Data.ByteString.Lazy   as BS.L
+
+-- Cabal
+import Distribution.Text
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Verify
+import Hackage.Security.JSON
+import Hackage.Security.Util.Path
+import Hackage.Security.Util.Some
+
+-- TestSuite
+import TestSuite.PrivateKeys
+import TestSuite.Util.StrictMVar
+
+{-------------------------------------------------------------------------------
+  "Files" from the in-memory repository
+-------------------------------------------------------------------------------}
+
+data InMemFile :: * -> * where
+    InMemMetadata :: ToJSON WriteJSON a => RepoLayout -> a -> InMemFile Metadata
+    InMemBinary   :: BS.L.ByteString -> InMemFile Binary
+
+inMemFileRender :: InMemFile typ -> BS.L.ByteString
+inMemFileRender (InMemMetadata layout file) = renderJSON layout file
+inMemFileRender (InMemBinary bs)            = bs
+
+instance DownloadedFile InMemFile where
+    downloadedRead file =
+      return $ inMemFileRender file
+
+    downloadedVerify file info =
+      return $ knownFileInfoEqual (fileInfo (inMemFileRender file))
+                                  (trusted info)
+
+    downloadedCopyTo file dest =
+      writeLazyByteString dest (inMemFileRender file)
+
+{-------------------------------------------------------------------------------
+  In-memory repository
+-------------------------------------------------------------------------------}
+
+data InMemRepo = InMemRepo {
+    -- | Get a file from the repository
+    inMemRepoGet :: forall fs typ.
+                    RemoteFile fs typ
+                 -> Verify (Some (HasFormat fs), InMemFile typ)
+
+    -- | Get a file, based on a path (uses hackageRepoLayout)
+  , inMemRepoGetPath :: RepoPath -> IO (Some InMemFile)
+
+    -- | Run the "cron job" on the server
+    --
+    -- That is, resign the timestamp and the snapshot
+  , inMemRepoCron :: UTCTime -> IO ()
+
+    -- | Rollover the timestamp and snapshot keys
+  , inMemRepoKeyRollover :: UTCTime -> IO ()
+  }
+
+newInMemRepo :: RepoLayout
+             -> Signed Root
+             -> UTCTime
+             -> PrivateKeys
+             -> IO InMemRepo
+newInMemRepo layout root now keys = do
+    state <- newMVar $ initRemoteState now layout keys root
+    return InMemRepo {
+        inMemRepoGet         = get         state
+      , inMemRepoGetPath     = getPath     state
+      , inMemRepoCron        = cron        state
+      , inMemRepoKeyRollover = keyRollover state
+      }
+
+{-------------------------------------------------------------------------------
+  "Remote" state (as it is "on the server")
+-------------------------------------------------------------------------------}
+
+data RemoteState = RemoteState {
+      remoteKeys      :: !PrivateKeys
+    , remoteLayout    :: !RepoLayout
+    , remoteRoot      :: !(Signed Root)
+    , remoteTimestamp :: !(Signed Timestamp)
+    , remoteSnapshot  :: !(Signed Snapshot)
+    , remoteMirrors   :: !(Signed Mirrors)
+    , remoteTar       :: !BS.L.ByteString
+    , remoteTarGz     :: !BS.L.ByteString
+    }
+
+initRoot :: UTCTime -> RepoLayout -> PrivateKeys -> Signed Root
+initRoot now layout keys = withSignatures layout (privateRoot keys) Root {
+      rootVersion = FileVersion 1
+    , rootExpires = expiresInDays now (365 * 10)
+    , rootKeys    = privateKeysEnv   keys
+    , rootRoles   = privateKeysRoles keys
+    }
+
+initRemoteState :: UTCTime
+                -> RepoLayout
+                -> PrivateKeys
+                -> Signed Root
+                -> RemoteState
+initRemoteState now layout keys signedRoot = RemoteState {
+      remoteKeys      = keys
+    , remoteLayout    = layout
+    , remoteRoot      = signedRoot
+    , remoteTimestamp = signedTimestamp
+    , remoteSnapshot  = signedSnapshot
+    , remoteMirrors   = signedMirrors
+    , remoteTar       = initTar
+    , remoteTarGz     = initTarGz
+    }
+  where
+    signedTimestamp = withSignatures layout [privateTimestamp keys] initTimestamp
+    signedSnapshot  = withSignatures layout [privateSnapshot  keys] initSnapshot
+    signedMirrors   = withSignatures layout [privateMirrors   keys] initMirrors
+
+    initMirrors :: Mirrors
+    initMirrors = Mirrors {
+        mirrorsVersion = FileVersion 1
+      , mirrorsExpires = expiresNever
+      , mirrorsMirrors = []
+      }
+
+    initSnapshot :: Snapshot
+    initSnapshot = Snapshot {
+        snapshotVersion     = FileVersion 1
+      , snapshotExpires     = expiresInDays now 3
+      , snapshotInfoRoot    = fileInfo $ renderJSON layout signedRoot
+      , snapshotInfoMirrors = fileInfo $ renderJSON layout signedMirrors
+      , snapshotInfoTarGz   = fileInfo $ initTarGz
+      , snapshotInfoTar     = Just $ fileInfo initTar
+      }
+
+    initTimestamp :: Timestamp
+    initTimestamp = Timestamp {
+        timestampVersion      = FileVersion 1
+      , timestampExpires      = expiresInDays now 3
+      , timestampInfoSnapshot = fileInfo $ renderJSON layout signedSnapshot
+      }
+
+    initTar :: BS.L.ByteString
+    initTar = Tar.write []
+
+    initTarGz :: BS.L.ByteString
+    initTarGz = GZip.compress initTar
+
+{-------------------------------------------------------------------------------
+  InMemRepo methods
+-------------------------------------------------------------------------------}
+
+-- | Get a file from the server
+get :: MVar RemoteState -> RemoteFile fs typ -> Verify (Some (HasFormat fs), InMemFile typ)
+get state remoteFile = do
+    RemoteState{..} <- liftIO $ readMVar state
+    case remoteFile of
+      RemoteTimestamp        -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteTimestamp)
+      RemoteSnapshot       _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteSnapshot)
+      RemoteMirrors        _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteMirrors)
+      RemoteRoot           _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteRoot)
+      RemoteIndex    hasGz _ -> return (Some hasGz, InMemBinary remoteTarGz)
+      RemotePkgTarGz pkgId _ -> error $ "withRemote: RemotePkgTarGz " ++ display pkgId
+
+getPath :: MVar RemoteState -> RepoPath -> IO (Some InMemFile)
+getPath state repoPath = do
+    RemoteState{..} <- readMVar state
+    case toUnrootedFilePath (unrootPath repoPath) of
+      "root.json"       -> return $ Some (InMemMetadata remoteLayout remoteRoot)
+      "timestamp.json"  -> return $ Some (InMemMetadata remoteLayout remoteTimestamp)
+      "snapshot.json"   -> return $ Some (InMemMetadata remoteLayout remoteSnapshot)
+      "mirrors.json"    -> return $ Some (InMemMetadata remoteLayout remoteMirrors)
+      "01-index.tar.gz" -> return $ Some (InMemBinary remoteTarGz)
+      "01-index.tar"    -> return $ Some (InMemBinary remoteTar)
+      otherPath -> throwIO . userError $ "getPath: Unknown path " ++ otherPath
+  where
+
+cron :: MVar RemoteState -> UTCTime -> IO ()
+cron state now = modifyMVar_ state $ \st@RemoteState{..} -> do
+    let snapshot, snapshot' :: Snapshot
+        snapshot  = signed remoteSnapshot
+        snapshot' = snapshot {
+            snapshotVersion = versionIncrement $ snapshotVersion snapshot
+          , snapshotExpires = expiresInDays now 3
+          }
+
+        timestamp, timestamp' :: Timestamp
+        timestamp  = signed remoteTimestamp
+        timestamp' = Timestamp {
+            timestampVersion      = versionIncrement $ timestampVersion timestamp
+          , timestampExpires      = expiresInDays now 3
+          , timestampInfoSnapshot = fileInfo $ renderJSON remoteLayout signedSnapshot
+          }
+
+        signedTimestamp = withSignatures remoteLayout [privateTimestamp remoteKeys] timestamp'
+        signedSnapshot  = withSignatures remoteLayout [privateSnapshot  remoteKeys] snapshot'
+
+    return st {
+        remoteTimestamp = signedTimestamp
+      , remoteSnapshot  = signedSnapshot
+      }
+
+keyRollover :: MVar RemoteState -> UTCTime -> IO ()
+keyRollover state now = modifyMVar_ state $ \st@RemoteState{..} -> do
+    newKeySnapshot  <- createKey' KeyTypeEd25519
+    newKeyTimestamp <- createKey' KeyTypeEd25519
+
+    let remoteKeys' :: PrivateKeys
+        remoteKeys' = remoteKeys {
+            privateSnapshot  = newKeySnapshot
+          , privateTimestamp = newKeyTimestamp
+          }
+
+        root, root' :: Root
+        root  = signed remoteRoot
+        root' = Root {
+            rootVersion = versionIncrement $ rootVersion root
+          , rootExpires = expiresInDays now (365 * 10)
+          , rootKeys    = privateKeysEnv   remoteKeys'
+          , rootRoles   = privateKeysRoles remoteKeys'
+          }
+
+        snapshot, snapshot' :: Snapshot
+        snapshot  = signed remoteSnapshot
+        snapshot' = snapshot {
+            snapshotVersion  = versionIncrement $ snapshotVersion snapshot
+          , snapshotExpires  = expiresInDays now 3
+          , snapshotInfoRoot = fileInfo $ renderJSON remoteLayout signedRoot
+          }
+
+        timestamp, timestamp' :: Timestamp
+        timestamp  = signed remoteTimestamp
+        timestamp' = Timestamp {
+            timestampVersion      = versionIncrement $ timestampVersion timestamp
+          , timestampExpires      = expiresInDays now 3
+          , timestampInfoSnapshot = fileInfo $ renderJSON remoteLayout signedSnapshot
+          }
+
+        signedRoot      = withSignatures remoteLayout (privateRoot      remoteKeys') root'
+        signedTimestamp = withSignatures remoteLayout [privateTimestamp remoteKeys'] timestamp'
+        signedSnapshot  = withSignatures remoteLayout [privateSnapshot  remoteKeys'] snapshot'
+
+    return st {
+        remoteRoot      = signedRoot
+      , remoteTimestamp = signedTimestamp
+      , remoteSnapshot  = signedSnapshot
+      }
diff --git a/hackage-security/hackage-security/tests/TestSuite/InMemRepository.hs b/hackage-security/hackage-security/tests/TestSuite/InMemRepository.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/InMemRepository.hs
@@ -0,0 +1,63 @@
+module TestSuite.InMemRepository (
+    newInMemRepository
+  ) where
+
+-- stdlib
+import Control.Concurrent
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Client.Formats
+import Hackage.Security.Client.Repository
+import Hackage.Security.Client.Verify
+import Hackage.Security.Util.Checked
+import Hackage.Security.Util.Some
+
+-- TestSuite
+import TestSuite.InMemCache
+import TestSuite.InMemRepo
+
+newInMemRepository :: RepoLayout
+                   -> IndexLayout
+                   -> InMemRepo
+                   -> InMemCache
+                   -> (LogMessage -> IO ())
+                   -> IO (Repository InMemFile)
+newInMemRepository layout indexLayout repo cache logger = do
+    cacheLock <- newMVar ()
+    return $ Repository {
+        repGetRemote     = getRemote     repo cache
+      , repGetCached     = inMemCacheGet      cache
+      , repGetCachedRoot = inMemCacheGetRoot  cache
+      , repClearCache    = inMemCacheClear    cache
+      , repLockCache     = withMVar cacheLock . const
+      , repWithIndex     = error "newInMemRepository: repWithIndex TODO"
+      , repGetIndexIdx   = error "newInMemRepository: repGetIndexIdx TODO"
+      , repWithMirror    = withMirror
+      , repLog           = logger
+      , repLayout        = layout
+      , repIndexLayout   = indexLayout
+      , repDescription   = "In memory repository"
+      }
+
+{-------------------------------------------------------------------------------
+  Repository methods
+-------------------------------------------------------------------------------}
+
+-- | Get a file from the server
+getRemote :: forall fs typ. Throws SomeRemoteError
+          => InMemRepo
+          -> InMemCache
+          -> AttemptNr
+          -> RemoteFile fs typ
+          -> Verify (Some (HasFormat fs), InMemFile typ)
+getRemote InMemRepo{..} InMemCache{..} _isRetry remoteFile = do
+    (Some format, inMemFile) <- inMemRepoGet remoteFile
+    ifVerified $ inMemCachePut inMemFile (hasFormatGet format) (mustCache remoteFile)
+    return (Some format, inMemFile)
+
+-- | Mirror selection
+withMirror :: forall a. Maybe [Mirror] -> IO a -> IO a
+withMirror Nothing   callback = callback
+withMirror (Just []) callback = callback
+withMirror _ _ = error "Mirror selection not implemented"
diff --git a/hackage-security/hackage-security/tests/TestSuite/PrivateKeys.hs b/hackage-security/hackage-security/tests/TestSuite/PrivateKeys.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/PrivateKeys.hs
@@ -0,0 +1,69 @@
+module TestSuite.PrivateKeys (
+    PrivateKeys(..)
+  , createPrivateKeys
+  , privateKeysEnv
+  , privateKeysRoles
+  ) where
+
+-- stdlib
+import Control.Monad
+
+-- hackage-security
+import Hackage.Security.Client
+import Hackage.Security.Key.Env (KeyEnv)
+import Hackage.Security.Util.Some
+import qualified Hackage.Security.Key.Env as KeyEnv
+
+{-------------------------------------------------------------------------------
+  All private keys
+-------------------------------------------------------------------------------}
+
+data PrivateKeys = PrivateKeys {
+      privateRoot      :: [Some Key]
+    , privateTarget    :: [Some Key]
+    , privateSnapshot  :: Some Key
+    , privateTimestamp :: Some Key
+    , privateMirrors   :: Some Key
+    }
+
+createPrivateKeys :: IO PrivateKeys
+createPrivateKeys = do
+    privateRoot      <- replicateM 3 $ createKey' KeyTypeEd25519
+    privateTarget    <- replicateM 3 $ createKey' KeyTypeEd25519
+    privateSnapshot  <- createKey' KeyTypeEd25519
+    privateTimestamp <- createKey' KeyTypeEd25519
+    privateMirrors   <- createKey' KeyTypeEd25519
+    return PrivateKeys{..}
+
+privateKeysEnv :: PrivateKeys -> KeyEnv
+privateKeysEnv PrivateKeys{..} = KeyEnv.fromKeys $ concat [
+      privateRoot
+    , privateTarget
+    , [privateSnapshot]
+    , [privateTimestamp]
+    , [privateMirrors]
+    ]
+
+privateKeysRoles :: PrivateKeys -> RootRoles
+privateKeysRoles PrivateKeys{..} = RootRoles {
+      rootRolesRoot      = RoleSpec {
+          roleSpecKeys      = map somePublicKey privateRoot
+        , roleSpecThreshold = KeyThreshold 2
+        }
+    , rootRolesSnapshot  = RoleSpec {
+          roleSpecKeys      = [somePublicKey privateSnapshot]
+        , roleSpecThreshold = KeyThreshold 1
+        }
+    , rootRolesTargets   = RoleSpec {
+          roleSpecKeys      = map somePublicKey privateTarget
+        , roleSpecThreshold = KeyThreshold 2
+        }
+    , rootRolesTimestamp = RoleSpec {
+          roleSpecKeys      = [somePublicKey privateTimestamp]
+        , roleSpecThreshold = KeyThreshold 1
+        }
+    , rootRolesMirrors   = RoleSpec {
+          roleSpecKeys      = [somePublicKey privateMirrors]
+        , roleSpecThreshold = KeyThreshold 1
+        }
+    }
diff --git a/hackage-security/hackage-security/tests/TestSuite/Util/StrictMVar.hs b/hackage-security/hackage-security/tests/TestSuite/Util/StrictMVar.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/hackage-security/tests/TestSuite/Util/StrictMVar.hs
@@ -0,0 +1,27 @@
+module TestSuite.Util.StrictMVar (
+    MVar -- opaque
+  , newMVar
+  , CC.withMVar
+  , modifyMVar
+  , modifyMVar_
+  , CC.readMVar
+  ) where
+
+import Control.Concurrent (MVar)
+import Control.Exception
+import qualified Control.Concurrent as CC
+
+newMVar :: a -> IO (MVar a)
+newMVar x = CC.newMVar =<< evaluate x
+
+modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b
+modifyMVar mv f = CC.modifyMVar mv $ \old -> do
+    (new, ret) <- f old
+    new' <- evaluate new
+    return (new', ret)
+
+modifyMVar_ :: MVar a -> (a -> IO a) -> IO ()
+modifyMVar_ mv f = modifyMVar mv (returnUnit . f)
+  where
+    returnUnit :: IO a -> IO (a, ())
+    returnUnit = fmap $ \a -> (a, ())
diff --git a/hackage-security/precompute-fileinfo/LICENSE b/hackage-security/precompute-fileinfo/LICENSE
new file mode 100644
--- /dev/null
+++ b/hackage-security/precompute-fileinfo/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Well-Typed LLP
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Well-Typed LLP nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hackage-security/precompute-fileinfo/Setup.hs b/hackage-security/precompute-fileinfo/Setup.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/precompute-fileinfo/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hackage-security/precompute-fileinfo/precompute-fileinfo.cabal b/hackage-security/precompute-fileinfo/precompute-fileinfo.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-security/precompute-fileinfo/precompute-fileinfo.cabal
@@ -0,0 +1,27 @@
+name:                precompute-fileinfo
+version:             0.1.0.0
+synopsis:            Precompute fileinfo for faster Hackage migration
+license:             BSD3
+license-file:        LICENSE
+author:              Edsko de Vries
+maintainer:          edsko@well-typed.com
+copyright:           Copyright 2015 Well-Typed LLP
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+
+executable precompute-fileinfo
+  main-is:             Main.hs
+  build-depends:       base                 >= 4.4,
+                       bytestring           >= 0.9,
+                       containers           >= 0.4,
+                       deepseq              >= 1.3,
+                       filepath             >= 1.2,
+                       optparse-applicative >= 0.11,
+                       SHA                  >= 1.6,
+                       tar                  >= 0.4,
+                       zlib                 >= 0.5
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:  RecordWildCards
+  ghc-options:         -Wall
diff --git a/hackage-security/precompute-fileinfo/src/Main.hs b/hackage-security/precompute-fileinfo/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/hackage-security/precompute-fileinfo/src/Main.hs
@@ -0,0 +1,196 @@
+module Main where
+
+import Control.Applicative
+import Control.Concurrent
+import Control.DeepSeq
+import Control.Exception
+import Control.Monad
+import Data.Map.Strict (Map)
+import Data.Monoid
+import Options.Applicative
+import System.FilePath
+import System.IO
+import qualified Codec.Archive.Tar       as Tar
+import qualified Codec.Archive.Tar.Entry as Tar
+import qualified Codec.Compression.GZip  as GZip
+import qualified Data.ByteString.Lazy    as BS.L
+import qualified Data.Digest.Pure.SHA    as SHA
+import qualified Data.Map                as Map
+
+{-------------------------------------------------------------------------------
+  Main application driver
+-------------------------------------------------------------------------------}
+
+main :: IO ()
+main = do
+    options    <- getOptions
+    tarEntries <- readTGz $ optionsArchive options
+    state      <- initState options
+
+    precomputeEntries state tarEntries `finally` writeState options state
+
+{-------------------------------------------------------------------------------
+  Command line options
+-------------------------------------------------------------------------------}
+
+data Options = Options {
+      optionsInput   :: Maybe FilePath
+    , optionsOutput  :: FilePath
+    , optionsArchive :: FilePath
+    }
+
+parseOptions :: Parser Options
+parseOptions = Options
+  <$> (optional . strOption $ mconcat [
+          short 'i'
+        , long "input"
+        , help "Input filename (if appending to existing map)"
+        ])
+  <*> (strOption $ mconcat [
+          short 'o'
+        , long "output"
+        , help "Output filename"
+        ])
+  <*> argument str (metavar "ARCHIVE")
+
+getOptions :: IO Options
+getOptions = execParser opts
+  where
+    opts = info (helper <*> parseOptions) $ mconcat [
+        fullDesc
+      , progDesc "Precompute file info from a Hackage --hardlink-blobs backup"
+      ]
+
+{-------------------------------------------------------------------------------
+  Core functionality
+-------------------------------------------------------------------------------}
+
+precomputeEntries :: Exception e => State -> Tar.Entries e -> IO ()
+precomputeEntries state = go
+  where
+    go (Tar.Fail e)    = throwIO e
+    go Tar.Done        = return ()
+    go (Tar.Next e es) = precomputeEntry state e >> go es
+
+precomputeEntry :: State -> Tar.Entry -> IO ()
+precomputeEntry state entry =
+    case Tar.entryContent entry of
+      Tar.SymbolicLink linkTarget ->
+        precomputeSymLink state
+                          (Tar.entryPath entry)
+                          (Tar.fromLinkTarget linkTarget)
+      _otherwise ->
+        return ()
+
+precomputeSymLink :: State -> FilePath -> FilePath -> IO ()
+precomputeSymLink state src dst =
+    case splitPath src of
+      [_date, "core/", "package/", pkg, _pkgTarGz] -> do
+        let blobPath = normalizePath (stateBackupDir state </> takeDirectory src </> dst)
+        precomputePkg state ("package " ++ init pkg) blobPath
+      [_date, "candidates/", "package/", pkg, _pkgTarGz] -> do
+        let blobPath = normalizePath (stateBackupDir state </> takeDirectory src </> dst)
+        precomputePkg state ("candidate " ++ init pkg) blobPath
+      _otherwise -> do
+        return ()
+
+precomputePkg :: State     -- ^ State to add the computed hash to
+              -> String    -- ^ Package name (for reporting only)
+              -> FilePath  -- ^ Location of the blob ID (ending in @../<md5>@)
+              -> IO ()
+precomputePkg state pkg dst = do
+    putStr pkg
+    let md5 = takeFileName dst
+    alreadyKnown <- isKnownHash state md5
+    if alreadyKnown
+      then putStrLn " (skipped)"
+      else do
+        (sha256, len) <- withFile dst ReadMode $ \h -> do
+          len    <- hFileSize h
+          sha256 <- SHA.showDigest . SHA.sha256 <$> BS.L.hGetContents h
+          evaluate $ rnf (sha256, len)
+          return (sha256, len)
+        recordHash state md5 sha256 len
+        putStrLn " OK"
+
+{-------------------------------------------------------------------------------
+  State
+-------------------------------------------------------------------------------}
+
+type MD5    = String
+type SHA256 = String
+type Length = Integer
+
+data State = State {
+      -- | The directory where the backup is stored
+      --
+      -- This is necessary so we can resolve relative paths inside the tarball
+      stateBackupDir :: FilePath
+
+      -- | Mutable variable where we store the hashes computed so far
+      --
+      -- We use this so that even on CTRL-C we can still output a partial map
+    , stateVar :: MVar (Map MD5 (SHA256, Length))
+    }
+
+initState :: Options -> IO State
+initState Options{..} = do
+    initMap <- case optionsInput of
+                 Nothing -> return Map.empty
+                 Just fn -> readMap fn
+    stateVar <- newMVar initMap
+    return State{..}
+  where
+    stateBackupDir = takeDirectory optionsArchive
+
+writeState :: Options -> State -> IO ()
+writeState Options{..} State{..} = do
+    putStrLn $ "Writing " ++ optionsOutput
+    writeMap optionsOutput =<< readMVar stateVar
+
+recordHash :: State -> MD5 -> SHA256 -> Length -> IO ()
+recordHash State{..} md5 sha256 len =
+    modifyMVar_ stateVar $ return . Map.insert md5 (sha256, len)
+
+isKnownHash :: State -> MD5 -> IO Bool
+isKnownHash State{..} md5 = withMVar stateVar $ return . Map.member md5
+
+writeMap :: FilePath -> Map MD5 (SHA256, Length) -> IO ()
+writeMap fp hashes = withFile fp WriteMode $ \h ->
+    mapM_ (uncurry (writeEntry h)) $ Map.toList hashes
+  where
+    writeEntry :: Handle -> MD5 -> (SHA256, Length) -> IO ()
+    writeEntry h md5 (sha256, len) =
+        hPutStrLn h $ unwords [md5, sha256, show len]
+
+-- | Read an existing hashmap
+--
+-- The result is guaranteed to be in normal form.
+readMap :: FilePath -> IO (Map MD5 (SHA256, Length))
+readMap fp =
+    withFile fp ReadMode $ \h -> do
+      hashes <- Map.fromList . map parseEntry . lines <$> hGetContents h
+      evaluate $ rnf hashes
+      return hashes
+  where
+    parseEntry :: String -> (MD5, (SHA256, Length))
+    parseEntry line = let [md5, sha256, len] = words line
+                      in (md5, (sha256, read len))
+
+{-------------------------------------------------------------------------------
+  Auxiliary
+-------------------------------------------------------------------------------}
+
+-- | Change @a/b/c/d/e/../../../../f@ to @a/f@
+normalizePath :: FilePath -> FilePath
+normalizePath = joinPath . go . splitPath . normalise
+  where
+    go :: [String] -> [String]
+    go [] = []
+    go (d : ds) =
+      case go ds of
+        "../" : ds'' | d /= "../" -> ds''
+        ds'                       -> d : ds'
+
+readTGz :: FilePath -> IO (Tar.Entries Tar.FormatError)
+readTGz = liftM (Tar.read . GZip.decompress) . BS.L.readFile
diff --git a/hackage-security/stack.yaml b/hackage-security/stack.yaml
new file mode 100644
--- /dev/null
+++ b/hackage-security/stack.yaml
@@ -0,0 +1,13 @@
+resolver: lts-3.4
+packages:
+- hackage-security
+- precompute-fileinfo
+- hackage-security-http-client
+- example-client
+- hackage-security-curl
+- hackage-root-tool
+- hackage-repo-tool
+- hackage-security-HTTP
+extra-deps:
+- ed25519-0.0.2.0
+- zlib-0.6.1.1
diff --git a/hackage-security/testscripts/squid.conf b/hackage-security/testscripts/squid.conf
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/squid.conf
@@ -0,0 +1,4 @@
+# Don't specify a cache dir; use in-memory cache
+http_port 3128
+access_log stdio:/dev/stdout combined 
+http_access allow all 
diff --git a/hackage-security/testscripts/test-outdated-index.log b/hackage-security/testscripts/test-outdated-index.log
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/test-outdated-index.log
@@ -0,0 +1,440 @@
++ BINDIR=../sandbox/7.8.3/bin
++ EXAMPLE_CLIENT=../sandbox/7.8.3/bin/example-client
++ SECURITY_UTILITY=../sandbox/7.8.3/bin/hackage-security
++ REPO=http://127.0.0.1/~e/local-repo
++ PROXY=http://localhost:3128
++ LOCAL_REPO=../unversioned/local-repo
++ KEYS=../unversioned/keys
++ LOCAL_CACHE=./tmp
++ rm -rf ../unversioned/local-repo/index
++ ../sandbox/7.8.3/bin/hackage-security -v bootstrap --repo ../unversioned/local-repo --keys ../unversioned/keys
+Info: Writing <repo>/root.json
+Info: Writing <repo>/mirrors.json
+Info: Writing <repo>/generics-sop/0.1.0.0/package.json
+Info: Writing <repo>/generics-sop/0.1.0.0/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.0.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.0.1/package.json
+Info: Writing <repo>/generics-sop/0.1.0.1/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.1.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.0.2/package.json
+Info: Writing <repo>/generics-sop/0.1.0.2/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.2.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.0.3/package.json
+Info: Writing <repo>/generics-sop/0.1.0.3/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.3.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.0.4/package.json
+Info: Writing <repo>/generics-sop/0.1.0.4/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.4.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.1.1/package.json
+Info: Writing <repo>/generics-sop/0.1.1.1/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.1.1.tar.gz)
+Info: Writing <repo>/generics-sop/0.1.1.2/package.json
+Warning: Failed to extract .cabal from package generics-sop-0.1.1.2: <repo>/package/generics-sop-0.1.1.2.tar.gz: Codec.Compression.Zlib: compressed data stream format error (incorrect header check)
+Info: Writing <repo>/pretty-show/1.4.1/package.json
+Info: Writing <repo>/pretty-show/1.4.1/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.4.1.tar.gz)
+Info: Writing <repo>/pretty-show/1.5/package.json
+Info: Writing <repo>/pretty-show/1.5/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.5.tar.gz)
+Info: Writing <repo>/pretty-show/1.6/package.json
+Info: Writing <repo>/pretty-show/1.6/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.6.tar.gz)
+Info: Writing <repo>/pretty-sop/0.1.0.0/package.json
+Info: Writing <repo>/pretty-sop/0.1.0.0/pretty-sop.cabal (extracted from <repo>/package/pretty-sop-0.1.0.0.tar.gz)
+Info: Writing <repo>/pretty-sop/0.1.0.1/package.json
+Info: Writing <repo>/pretty-sop/0.1.0.1/pretty-sop.cabal (extracted from <repo>/package/pretty-sop-0.1.0.1.tar.gz)
+Info: Writing <repo>/tagged/0.6.1/package.json
+Info: Writing <repo>/tagged/0.6.1/tagged.cabal (extracted from <repo>/package/tagged-0.6.1.tar.gz)
+Info: Writing <repo>/tagged/0.7.1/package.json
+Info: Writing <repo>/tagged/0.7.1/tagged.cabal (extracted from <repo>/package/tagged-0.7.1.tar.gz)
+Info: Writing <repo>/tagged/0.7.2/package.json
+Info: Writing <repo>/tagged/0.7.2/tagged.cabal (extracted from <repo>/package/tagged-0.7.2.tar.gz)
+Info: Writing <repo>/tagged/0.7.3/package.json
+Info: Writing <repo>/tagged/0.7.3/tagged.cabal (extracted from <repo>/package/tagged-0.7.3.tar.gz)
+Info: Writing <repo>/tagged/0.8.0.1/package.json
+Info: Writing <repo>/tagged/0.8.0.1/tagged.cabal (extracted from <repo>/package/tagged-0.8.0.1.tar.gz)
+Info: Writing <repo>/00-index.tar
+Info: Writing <repo>/00-index.tar.gz
+Info: Writing <repo>/snapshot.json
+Info: Writing <repo>/timestamp.json
++ unset HTTP_PROXY
++ unset http_proxy
++ rm -rf ./tmp
++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp bootstrap 0
+# Selected mirror http://127.0.0.1/~e/local-repo
+# Downloading root
+Sending:
+GET /~e/local-repo/root.json HTTP/1.1
+Host: 127.0.0.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+
+
+Creating new connection to 127.0.0.1
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"cfb-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 3323
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+
+
+OK
++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp check
+# Selected mirror http://127.0.0.1/~e/local-repo
+# Downloading timestamp
+Sending:
+GET /~e/local-repo/timestamp.json HTTP/1.1
+Host: 127.0.0.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+
+
+Creating new connection to 127.0.0.1
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"1a3-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 419
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+
+
+# Downloading snapshot
+Sending:
+GET /~e/local-repo/snapshot.json HTTP/1.1
+Host: 127.0.0.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+
+
+Recovering connection to 127.0.0.1
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"319-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 793
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+
+
+# Downloading mirrors
+Sending:
+GET /~e/local-repo/mirrors.json HTTP/1.1
+Host: 127.0.0.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+
+
+Recovering connection to 127.0.0.1
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"2ad-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 685
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+
+
+# Updating index failed (no local copy)
+# Downloading index
+Sending:
+GET /~e/local-repo/00-index.tar.gz HTTP/1.1
+Host: 127.0.0.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+
+
+Recovering connection to 127.0.0.1
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"1241-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 4673
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/x-gzip
+
+
+HasUpdates
++ export HTTP_PROXY=http://localhost:3128
++ HTTP_PROXY=http://localhost:3128
++ export http_proxy=http://localhost:3128
++ http_proxy=http://localhost:3128
++ curl -Lv http://127.0.0.1/~e/local-repo/00-index.tar
+* Hostname was NOT found in DNS cache
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
+* Connected to localhost (127.0.0.1) port 3128 (#0)
+> GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1
+> User-Agent: curl/7.37.1
+> Host: 127.0.0.1
+> Accept: */*
+> Proxy-Connection: Keep-Alive
+> 
+< HTTP/1.1 200 OK
+< Date: Mon, 13 Jul 2015 15:16:39 GMT
+* Server Apache/2.4.10 (Unix) is not blacklisted
+< Server: Apache/2.4.10 (Unix)
+< Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+< ETag: W/"10000-51ac3355103c0"
+< Accept-Ranges: bytes
+< Content-Length: 65536
+< Cache-Control: max-age=300, public, no-transform
+< Content-Type: application/x-tar
+< X-Cache: MISS from server.local
+< Via: 1.1 server.local (squid/3.5.6)
+< Connection: keep-alive
+< 
+{ [data not shown]
+100 65536  100 65536    0     0  7622k      0 --:--:-- --:--:-- --:--:-- 8000k
+* Connection #0 to host localhost left intact
++ sleep 5
++ dd bs=1 count=16384 if=/dev/random of=../unversioned/local-repo/index/extra-file
+16384+0 records in
+16384+0 records out
+16384 bytes transferred in 0.050569 secs (323993 bytes/sec)
++ ../sandbox/7.8.3/bin/hackage-security -v update --repo ../unversioned/local-repo --keys ../unversioned/keys
+Info: Skipping <repo>/generics-sop/0.1.0.0/package.json
+Info: Skipping <repo>/generics-sop/0.1.0.0/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.0.1/package.json
+Info: Skipping <repo>/generics-sop/0.1.0.1/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.0.2/package.json
+Info: Skipping <repo>/generics-sop/0.1.0.2/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.0.3/package.json
+Info: Skipping <repo>/generics-sop/0.1.0.3/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.0.4/package.json
+Info: Skipping <repo>/generics-sop/0.1.0.4/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.1.1/package.json
+Info: Skipping <repo>/generics-sop/0.1.1.1/generics-sop.cabal
+Info: Skipping <repo>/generics-sop/0.1.1.2/package.json
+Warning: Failed to extract .cabal from package generics-sop-0.1.1.2: <repo>/package/generics-sop-0.1.1.2.tar.gz: Codec.Compression.Zlib: compressed data stream format error (incorrect header check)
+Info: Skipping <repo>/pretty-show/1.4.1/package.json
+Info: Skipping <repo>/pretty-show/1.4.1/pretty-show.cabal
+Info: Skipping <repo>/pretty-show/1.5/package.json
+Info: Skipping <repo>/pretty-show/1.5/pretty-show.cabal
+Info: Skipping <repo>/pretty-show/1.6/package.json
+Info: Skipping <repo>/pretty-show/1.6/pretty-show.cabal
+Info: Skipping <repo>/pretty-sop/0.1.0.0/package.json
+Info: Skipping <repo>/pretty-sop/0.1.0.0/pretty-sop.cabal
+Info: Skipping <repo>/pretty-sop/0.1.0.1/package.json
+Info: Skipping <repo>/pretty-sop/0.1.0.1/pretty-sop.cabal
+Info: Skipping <repo>/tagged/0.6.1/package.json
+Info: Skipping <repo>/tagged/0.6.1/tagged.cabal
+Info: Skipping <repo>/tagged/0.7.1/package.json
+Info: Skipping <repo>/tagged/0.7.1/tagged.cabal
+Info: Skipping <repo>/tagged/0.7.2/package.json
+Info: Skipping <repo>/tagged/0.7.2/tagged.cabal
+Info: Skipping <repo>/tagged/0.7.3/package.json
+Info: Skipping <repo>/tagged/0.7.3/tagged.cabal
+Info: Skipping <repo>/tagged/0.8.0.1/package.json
+Info: Skipping <repo>/tagged/0.8.0.1/tagged.cabal
+Info: Appending 1 file(s) to <repo>/00-index.tar
+Info: Writing <repo>/00-index.tar.gz
+Info: Updating <repo>/snapshot.json
+Info: Updating <repo>/timestamp.json
++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp check
+# Selected mirror http://127.0.0.1/~e/local-repo
+# Downloading timestamp
+Sending:
+GET http://127.0.0.1/~e/local-repo/timestamp.json HTTP/1.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Creating new connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT
+ETag: "1a3-51ac3359d4f00"
+Accept-Ranges: bytes
+Content-Length: 419
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading snapshot
+Sending:
+GET http://127.0.0.1/~e/local-repo/snapshot.json HTTP/1.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT
+ETag: "31a-51ac3359d4f00"
+Accept-Ranges: bytes
+Content-Length: 794
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Updating index
+Sending:
+GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1
+Range: bytes=64512-82431
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 206 Partial Content 
+Date: Mon, 13 Jul 2015 15:16:39 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: W/"10000-51ac3355103c0"
+Accept-Ranges: bytes
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/x-tar
+Age: 6
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+Content-Range: bytes 64512-65535/65536
+Content-Length: 1024
+
+
+# Verification error: Invalid hash for <repo>/00-index.tar
+# Downloading root
+Sending:
+GET http://127.0.0.1/~e/local-repo/root.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT
+ETag: "cfb-51ac3355103c0"
+Accept-Ranges: bytes
+Content-Length: 3323
+Cache-Control: max-age=300, public, no-transform
+Content-Type: application/json
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading timestamp
+Sending:
+GET http://127.0.0.1/~e/local-repo/timestamp.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT
+Accept-Ranges: bytes
+Content-Length: 419
+Content-Type: application/json
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+ETag: "1a3-51ac3359d4f00"
+Cache-Control: max-age=300, public, no-transform
+Age: 0
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading snapshot
+Sending:
+GET http://127.0.0.1/~e/local-repo/snapshot.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT
+Accept-Ranges: bytes
+Content-Length: 794
+Content-Type: application/json
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+ETag: "31a-51ac3359d4f00"
+Cache-Control: max-age=300, public, no-transform
+Age: 0
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Updating index
+Sending:
+GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1
+Range: bytes=64512-82431
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 206 Partial Content 
+Date: Mon, 13 Jul 2015 15:16:45 GMT
+Server: Apache/2.4.10 (Unix)
+Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT
+ETag: "14200-51ac3359d4f00"
+Accept-Ranges: bytes
+Content-Length: 17920
+Cache-Control: max-age=300, public, no-transform
+Content-Range: bytes 64512-82431/82432
+Content-Type: application/x-tar
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+HasUpdates
diff --git a/hackage-security/testscripts/test-outdated-index.sh b/hackage-security/testscripts/test-outdated-index.sh
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/test-outdated-index.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+##
+# Cache incoherence test 2: outdated index
+#
+# DESCRIPTION
+#
+# The general approach is the same as in test 1, except we now set things up
+# so that the cache has an outdated index.
+#
+# EXPECTED OUTCOME
+#
+# We should recover from the cache incoherence problem and still download the
+# index incrementally (rather than downloading the entire index from scratch).
+# See example log file for a succcessful run.
+#
+# ASSUMPTIONS
+#
+# Since this relies on incremental downloads, it must currently be tested
+# against Apache rather than Hackage. Make sure to have
+#
+#   Header set Cache-Control "max-age=5, public, no-transform"
+#
+# in your .htaccess (and set AllowOverride: All for your domain, if necessary).
+# As for test 1, you should also have a fresh instance of squid running.
+##
+
+set -x
+
+BINDIR=../sandbox/7.8.3/bin
+EXAMPLE_CLIENT=${BINDIR}/example-client
+SECURITY_UTILITY=${BINDIR}/hackage-security
+REPO=http://127.0.0.1/~e/local-repo
+PROXY=http://localhost:3128
+LOCAL_REPO=../unversioned/local-repo
+KEYS=../unversioned/keys
+LOCAL_CACHE=./tmp                        # NOTE: We will delete this directory
+
+# Reset the repo
+rm -rf ${LOCAL_REPO}/index
+${SECURITY_UTILITY} -v bootstrap --repo ${LOCAL_REPO} --keys ${KEYS}
+
+# Start with a fresh local cache, and do an update (directly, without squid) so
+# that we have a local copy of the index that we can update incrementally later
+unset HTTP_PROXY
+unset http_proxy
+rm -rf ${LOCAL_CACHE}
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
+
+# Now start using squid
+export HTTP_PROXY=${PROXY}
+export http_proxy=${PROXY}
+
+# Get the index from the server so that it's in squid's cache
+curl -Lv ${REPO}/00-index.tar >/dev/null
+
+# Add something to the index, and update snapshot/timestamp
+# Sleep a bit first, so that the time on the extra file is definitely different
+# to the time on the index
+sleep 5
+dd bs=1 count=16384 if=/dev/random of=${LOCAL_REPO}/index/extra-file
+${SECURITY_UTILITY} -v update --repo ${LOCAL_REPO} --keys ${KEYS}
+
+# Now do another update.
+# The timestamp and snapshot will be out of sync with each other.
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
diff --git a/hackage-security/testscripts/test-outdated-timestamp.log b/hackage-security/testscripts/test-outdated-timestamp.log
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/test-outdated-timestamp.log
@@ -0,0 +1,280 @@
++ export HTTP_PROXY=http://localhost:3128
++ HTTP_PROXY=http://localhost:3128
++ export http_proxy=http://localhost:3128
++ http_proxy=http://localhost:3128
++ rm -rf ./tmp
++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1:8080 --cache ./tmp bootstrap 0
+# Selected mirror http://127.0.0.1:8080
+# Downloading root
+Sending:
+GET http://127.0.0.1:8080/root.json HTTP/1.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Creating new connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=60
+Content-Length: 3323
+Content-MD5: 914e6bed90bde8179af0b657937ca1d9
+Content-Type: text/json
+Date: Mon, 13 Jul 2015 14:46:41 GMT
+ETag: "914e6bed90bde8179af0b657937ca1d9"
+Last-Modified: Thu, 11 Jun 2015 14:37:51 GMT
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+OK
++ curl -Lv http://127.0.0.1:8080/timestamp.json
+* Hostname was NOT found in DNS cache
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
+* Connected to localhost (127.0.0.1) port 3128 (#0)
+> GET http://127.0.0.1:8080/timestamp.json HTTP/1.1
+> User-Agent: curl/7.37.1
+> Host: 127.0.0.1:8080
+> Accept: */*
+> Proxy-Connection: Keep-Alive
+> 
+< HTTP/1.1 200 OK
+< Cache-Control: public, no-transform, max-age=60
+< Content-Length: 420
+< Content-MD5: fea6f07b4caa591625bcf4a2f27fe9af
+< Content-Type: text/json
+< Date: Mon, 13 Jul 2015 14:46:41 GMT
+< ETag: "fea6f07b4caa591625bcf4a2f27fe9af"
+< Last-Modified: Mon, 13 Jul 2015 14:45:38 GMT
+* Server Happstack/7.4.4 is not blacklisted
+< Server: Happstack/7.4.4
+< X-Cache: MISS from server.local
+< Via: 1.1 server.local (squid/3.5.6)
+< Connection: keep-alive
+< 
+{ [data not shown]
+100   420  100   420    0     0  52135      0 --:--:-- --:--:-- --:--:-- 60000
+* Connection #0 to host localhost left intact
++ killall -SIGHUP hackage-server
++ sleep 5
++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1:8080 --cache ./tmp check
+# Selected mirror http://127.0.0.1:8080
+# Downloading timestamp
+Sending:
+GET http://127.0.0.1:8080/timestamp.json HTTP/1.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Creating new connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=60
+Content-Length: 420
+Content-MD5: fea6f07b4caa591625bcf4a2f27fe9af
+Content-Type: text/json
+Date: Mon, 13 Jul 2015 14:46:41 GMT
+ETag: "fea6f07b4caa591625bcf4a2f27fe9af"
+Last-Modified: Mon, 13 Jul 2015 14:45:38 GMT
+Server: Happstack/7.4.4
+Age: 6
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading snapshot
+Sending:
+GET http://127.0.0.1:8080/snapshot.json HTTP/1.1
+Cache-Control: no-transform
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=60
+Content-Length: 791
+Content-MD5: 90a469a7bedd38afe12951dc774a555d
+Content-Type: text/json
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "90a469a7bedd38afe12951dc774a555d"
+Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Verification error: Invalid hash for <repo>/snapshot.json
+# Downloading root
+Sending:
+GET http://127.0.0.1:8080/root.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Content-Length: 3323
+Content-MD5: 914e6bed90bde8179af0b657937ca1d9
+Content-Type: text/json
+Last-Modified: Thu, 11 Jun 2015 14:37:51 GMT
+Cache-Control: public, no-transform, max-age=60
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "914e6bed90bde8179af0b657937ca1d9"
+Server: Happstack/7.4.4
+Age: 1
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading timestamp
+Sending:
+GET http://127.0.0.1:8080/timestamp.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=60
+Content-Length: 420
+Content-MD5: 25aa54b986c278d7ec4a2d9514fe1316
+Content-Type: text/json
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "25aa54b986c278d7ec4a2d9514fe1316"
+Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading snapshot
+Sending:
+GET http://127.0.0.1:8080/snapshot.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Content-Length: 791
+Content-MD5: 90a469a7bedd38afe12951dc774a555d
+Content-Type: text/json
+Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT
+Cache-Control: public, no-transform, max-age=60
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "90a469a7bedd38afe12951dc774a555d"
+Server: Happstack/7.4.4
+Age: 1
+X-Cache: HIT from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Downloading mirrors
+Sending:
+GET http://127.0.0.1:8080/mirrors.json HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=60
+Content-Length: 685
+Content-MD5: e98e8b013404cf085be353a055b723be
+Content-Type: text/json
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "e98e8b013404cf085be353a055b723be"
+Last-Modified: Thu, 11 Jun 2015 14:37:59 GMT
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+# Updating index failed (server does not provide incremental downloads)
+# Downloading index
+Sending:
+GET http://127.0.0.1:8080/00-index.tar.gz HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 301 Moved Permanently 
+Content-Type: text/plain; charset=UTF-8
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+Location: /packages/index.tar.gz
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Transfer-Encoding: chunked
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+Content-Length: 0
+
+
+301 - redirect
+Redirecting to http://127.0.0.1:8080/packages/index.tar.gz ...
+Sending:
+GET http://127.0.0.1:8080/packages/index.tar.gz HTTP/1.1
+Cache-Control: no-transform, max-age=0
+Content-Length: 0
+User-Agent: haskell-HTTP/4000.2.20
+Host: 127.0.0.1:8080
+
+
+proxy uri host: localhost, port: :3128
+Recovering connection to localhost:3128
+Received:
+HTTP/1.1 200 OK 
+Cache-Control: public, no-transform, max-age=300
+Content-Length: 29
+Content-MD5: 31f6566d35ccd604be46ed5b1f813cdf
+Content-Type: application/x-gzip
+Date: Mon, 13 Jul 2015 14:46:46 GMT
+ETag: "31f6566d35ccd604be46ed5b1f813cdf"
+Last-Modified: Mon, 13 Jul 2015 08:18:42 GMT
+Server: Happstack/7.4.4
+X-Cache: MISS from server.local
+Via: 1.1 server.local (squid/3.5.6)
+Connection: keep-alive
+
+
+HasUpdates
diff --git a/hackage-security/testscripts/test-outdated-timestamp.sh b/hackage-security/testscripts/test-outdated-timestamp.sh
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/test-outdated-timestamp.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+##
+# Cache incoherence test 1: outdated timestamp
+#
+# DESCRIPTION
+#
+# We request the timestamp from the server through the proxy, so that it's in
+# the proxy's cache (assuming a `max-age` header was set on the file). We then
+# ask Hackage to resign, and do a check-for-updates using `example-client`.
+# This will cause the proxy to return the now-outdated `timestamp` but a newer
+# snapshot.
+#
+# NOTE: We refer to the cache maintained by the `hackage-security` library
+# (through `example-client`) as the "local cache", and the cache maintained by
+# squid as the "proxy cache".
+#
+# EXPECTED OUTCOME
+#
+# The client should detect this and try the request again, asking the
+# cache to fetch the file stream. See the example log file.
+#
+# ASSUMPTIONS
+#
+# We assume Hackage is running on the same machine as the test script (so that
+# we can send it SIGHUP), and that we have a fresh (nothing cached yet) instance
+# of squid. There is an example configuration file for squid in this directory;
+# start with
+#
+#     ~/homebrew/sbin/squid -f ./squid.conf -N -d 1
+#
+# (mutatis mutandis).
+##
+
+# Configuration
+BINDIR=../sandbox/7.8.3/bin
+EXAMPLE_CLIENT=${BINDIR}/example-client
+REPO=http://127.0.0.1:8080
+PROXY=http://localhost:3128
+LOCAL_CACHE=./tmp                        # NOTE: We will delete this directory
+
+set -x
+
+# Enable the proxy
+export HTTP_PROXY=${PROXY}
+export http_proxy=${PROXY}
+
+# Start with a fresh local cache (so that we definitely have updates)
+rm -rf ${LOCAL_CACHE}
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0
+
+# Get the timestamp from the server (so that it's in proxy cache)
+curl -Lv ${REPO}/timestamp.json >/dev/null
+
+# Make the server resign the timestamp
+killall -SIGHUP hackage-server
+sleep 5
+
+# Now do an update
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
diff --git a/hackage-security/testscripts/test-range-request.sh b/hackage-security/testscripts/test-range-request.sh
new file mode 100644
--- /dev/null
+++ b/hackage-security/testscripts/test-range-request.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+BINDIR=../sandbox/7.8.3/bin
+EXAMPLE_CLIENT=${BINDIR}/example-client
+LOCAL_CACHE=../unversioned/cache/
+#REPO=http://localhost:8080
+REPO=http://localhost/~e/local-repo
+
+rm -r ${LOCAL_CACHE}
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0
+dd if=/dev/zero of=${LOCAL_CACHE}/00-index.tar bs=1024 count=1
+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
+
diff --git a/hackport.cabal b/hackport.cabal
--- a/hackport.cabal
+++ b/hackport.cabal
@@ -1,5 +1,5 @@
 Name:           hackport
-Version:        0.4.7
+Version:        0.5
 License:        GPL
 License-file:   LICENSE
 Author:         Henning Günther, Duncan Coutts, Lennart Kolmodin
@@ -20,40 +20,80 @@
   ghc-prof-options: -caf-all -auto-all -rtsopts
   Main-Is:    Main.hs
   Default-Language: Haskell98
-  Hs-Source-Dirs: ., cabal, cabal/Cabal, cabal/cabal-install
+  Hs-Source-Dirs:
+      .,
+      cabal,
+      cabal/Cabal,
+      cabal/cabal-install,
+      hackage-security/hackage-security/src
   Build-Depends:
+    array,
     base >= 2.0 && < 5,
     deepseq >= 1.3,
+    extensible-exceptions,
     filepath,
-    parsec,
+    HTTP >= 4000.0.3,
+    MissingH,
     network >= 2.6, network-uri >= 2.6,
+    parsec,
     pretty,
-    regex-compat,
-    MissingH,
     old-locale,
-    HTTP >= 4000.0.3,
-    zlib,
+    regex-compat,
+    split,
     tar,
-    xml >= 1.3.7,
-    array,
-    extensible-exceptions,
     time,
+    zlib,
+    xml >= 1.3.7,
     -- cabal depends
     binary,
     random,
     stm,
-    unix
+    unix,
+    -- cabal-install depends
+    --  hackage-security depends
+    base64-bytestring,
+    cryptohash,
+    ed25519,
+    ghc-prim,
+    hashable,
+    mtl,
+    template-haskell,
+    transformers
 
-  -- extensions due to hackport
-  other-extensions:
+  default-extensions:
+    -- hackage-security
+    DefaultSignatures,
     DeriveDataTypeable,
-    PatternGuards
-
-  -- extensions due to bundled cabal-install
+    EmptyDataDecls,
+    ExistentialQuantification,
+    FlexibleContexts,
+    FlexibleInstances,
+    GADTs,
+    GeneralizedNewtypeDeriving,
+    KindSignatures,
+    MultiParamTypeClasses,
+    PatternGuards,
+    RankNTypes,
+    RecordWildCards,
+    ScopedTypeVariables,
+    StandaloneDeriving,
+    TypeFamilies,
+    TypeOperators,
+    ViewPatterns
   other-extensions:
+    DeriveDataTypeable,
+    PatternGuards,
+    -- extensions due to bundled cabal-install
     CPP,
     ForeignFunctionInterface,
-    PatternGuards
+    --  hackage-security extensions
+    DefaultSignatures,
+    GeneralizedNewtypeDeriving,
+    GADTs,
+    KindSignatures,
+    RankNTypes,
+    RecordWildCards,
+    TypeOperators
 
   Build-Depends:
     base >= 3 && < 5,
@@ -66,10 +106,7 @@
   other-modules:
     AnsiColor
     Cabal2Ebuild
-    CacheFile
-    Diff
     Error
-    Hackage
     Main
     Overlays
     Paths_hackport
@@ -87,40 +124,6 @@
     Merge
     Util
 
-
-Executable    hackport-guess-ghc-version
-  ghc-options: -Wall
-  Main-Is:    Main-GuessGHC.hs
-  Default-Language: Haskell98
-  Buildable:    False
-  -- this was used as a test while developing the
-  -- ghc-guessfeature. now we can disable building
-  Build-Depends:
-    base >= 2.0 && < 5,
-    filepath,
-    parsec,
-    network >= 2.6, network-uri >= 2.6,
-    pretty,
-    regex-compat,
-    HTTP >= 4000.0.3,
-    zlib,
-    tar,
-    array,
-  -- array is inherited from cabal-install 
-  -- tar >= 0.3.0.0 && < 0.4
-    extensible-exceptions
-
-  Build-Depends:
-    base >= 3 && < 5,
-    directory,
-    containers,
-    process,
-    old-time,
-    bytestring
-
-  other-modules:
-    Portage.GHCCore
-
 Test-Suite test-resolve-category
   ghc-options: -Wall
   Type:                 exitcode-stdio-1.0
@@ -138,6 +141,7 @@
                         HUnit,
                         pretty,
                         process,
+                        split,
                         time,
                         unix,
                         xml
