#include "HsVersions.h"
import PackageConfig
-import ParsePkgConf ( loadPackageConfig )
import DynFlags ( dopt, DynFlag(..), DynFlags(..), PackageFlag(..) )
import StaticFlags
import Config ( cProjectVersion )
import Name ( Name, nameModule_maybe )
import UniqFM
-import FiniteMap
import Module
import Util
import Panic
import System.Environment ( getEnv )
import Distribution.InstalledPackageInfo
+import Distribution.InstalledPackageInfo.Binary
import Distribution.Package hiding (PackageId,depends)
import FastString
import ErrUtils ( debugTraceMsg, putMsg, Message )
import System.FilePath
import Control.Monad
import Data.List as List
+import Data.Map (Map)
+import qualified Data.Map as Map
+import qualified FiniteMap as Map
+import qualified Data.Set as Set
-- ---------------------------------------------------------------------------
-- The Package state
-- | A PackageConfigMap maps a 'PackageId' to a 'PackageConfig'
type PackageConfigMap = UniqFM PackageConfig
-type InstalledPackageIdMap = FiniteMap InstalledPackageId PackageId
+type InstalledPackageIdMap = Map InstalledPackageId PackageId
+
+type InstalledPackageIndex = Map InstalledPackageId PackageConfig
emptyPackageConfigMap :: PackageConfigMap
emptyPackageConfigMap = emptyUFM
-- the we tack on the system paths.
pkgs <- mapM (readPackageConfig dflags)
- (reverse pkgconfs ++ reverse (extraPkgConfs dflags))
+ (pkgconfs ++ reverse (extraPkgConfs dflags))
-- later packages shadow earlier ones. extraPkgConfs
-- is in the opposite order to the flags on the
-- command line.
-- System one always comes first
let system_pkgconf = systemPackageConfig dflags
- -- allow package.conf.d to contain a bunch of .conf files
- -- containing package specifications. This is an easier way
- -- to maintain the package database on systems with a package
- -- management system, or systems that don't want to run ghc-pkg
- -- to register or unregister packages. Undocumented feature for now.
- let system_pkgconf_dir = system_pkgconf <.> "d"
- system_pkgconf_dir_exists <- doesDirectoryExist system_pkgconf_dir
- system_pkgconfs <-
- if system_pkgconf_dir_exists
- then do files <- getDirectoryContents system_pkgconf_dir
- return [ system_pkgconf_dir </> file
- | file <- files
- , takeExtension file == ".conf" ]
- else return []
-
-- Read user's package conf (eg. ~/.ghc/i386-linux-6.3/package.conf)
-- unless the -no-user-package-conf flag was given.
- -- We only do this when getAppUserDataDirectory is available
- -- (GHC >= 6.3).
user_pkgconf <- do
+ if not (dopt Opt_ReadUserPackageConf dflags) then return [] else do
appdir <- getAppUserDataDirectory "ghc"
let
- pkgconf = appdir
- </> (TARGET_ARCH ++ '-':TARGET_OS ++ '-':cProjectVersion)
- </> "package.conf"
- flg <- doesFileExist pkgconf
- if (flg && dopt Opt_ReadUserPackageConf dflags)
- then return [pkgconf]
- else return []
+ dir = appdir </> (TARGET_ARCH ++ '-':TARGET_OS ++ '-':cProjectVersion)
+ pkgconf = dir </> "package.conf.d"
+ --
+ exist <- doesDirectoryExist pkgconf
+ if exist then return [pkgconf] else return []
`catchIO` (\_ -> return [])
- return (user_pkgconf ++ system_pkgconfs ++ [system_pkgconf])
-
+ return (system_pkgconf : user_pkgconf)
readPackageConfig :: DynFlags -> FilePath -> IO [PackageConfig]
readPackageConfig dflags conf_file = do
- debugTraceMsg dflags 2 (text "Using package config file:" <+> text conf_file)
- proto_pkg_configs <- loadPackageConfig dflags conf_file
+ isdir <- doesDirectoryExist conf_file
+
+ proto_pkg_configs <-
+ if isdir
+ then do let filename = conf_file </> "package.cache"
+ debugTraceMsg dflags 2 (text "Using binary package database:" <+> text filename)
+ conf <- readBinPackageDB filename
+ return (map installedPackageInfoToPackageConfig conf)
+
+ else do
+ isfile <- doesFileExist conf_file
+ when (not isfile) $
+ ghcError $ InstallationError $
+ "can't find a package database at " ++ conf_file
+ debugTraceMsg dflags 2 (text "Using package config file:" <+> text conf_file)
+ str <- readFile conf_file
+ return (map installedPackageInfoToPackageConfig $ read str)
+
let
top_dir = topDir dflags
pkg_configs1 = mungePackagePaths top_dir proto_pkg_configs
selectPackages matches pkgs unusable
= let
(ps,rest) = partition matches pkgs
- reasons = [ (p, lookupFM unusable (installedPackageId p))
+ reasons = [ (p, Map.lookup (installedPackageId p) unusable)
| p <- ps ]
in
if all (isJust.snd) reasons
packageFlagErr :: PackageFlag
-> [(PackageConfig, UnusablePackageReason)]
-> IO a
+
+-- for missing DPH package we emit a more helpful error message, because
+-- this may be the result of using -fdph-par or -fdph-seq.
+packageFlagErr (ExposePackage pkg) [] | is_dph_package pkg
+ = ghcError (CmdLineError (showSDoc $ dph_err))
+ where dph_err = text "the " <> text pkg <> text " package is not installed."
+ $$ text "To install it: \"cabal install dph\"."
+ is_dph_package pkg = "dph" `isPrefixOf` pkg
+
packageFlagErr flag reasons = ghcError (CmdLineError (showSDoc $ err))
where err = text "cannot satisfy " <> ppr_flag <>
(if null reasons then empty else text ": ") $$
| MissingDependencies [InstalledPackageId]
| ShadowedBy InstalledPackageId
-type UnusablePackages = FiniteMap InstalledPackageId UnusablePackageReason
+type UnusablePackages = Map InstalledPackageId UnusablePackageReason
pprReason :: SDoc -> UnusablePackageReason -> SDoc
pprReason pref reason = case reason of
pref <+> ptext (sLit "shadowed by package ") <> text (display ipid)
reportUnusable :: DynFlags -> UnusablePackages -> IO ()
-reportUnusable dflags pkgs = mapM_ report (fmToList pkgs)
+reportUnusable dflags pkgs = mapM_ report (Map.toList pkgs)
where
report (ipid, reason) =
debugTraceMsg dflags 2 $
-- satisfied until no more can be added.
--
findBroken :: [PackageConfig] -> UnusablePackages
-findBroken pkgs = go [] emptyFM pkgs
+findBroken pkgs = go [] Map.empty pkgs
where
go avail ipids not_avail =
case partitionWith (depsAvailable ipids) not_avail of
([], not_avail) ->
- listToFM [ (installedPackageId p, MissingDependencies deps)
- | (p,deps) <- not_avail ]
+ Map.fromList [ (installedPackageId p, MissingDependencies deps)
+ | (p,deps) <- not_avail ]
(new_avail, not_avail) ->
go (new_avail ++ avail) new_ipids (map fst not_avail)
- where new_ipids = addListToFM ipids
+ where new_ipids = Map.insertList
[ (installedPackageId p, p) | p <- new_avail ]
+ ipids
- depsAvailable :: FiniteMap InstalledPackageId PackageConfig
+ depsAvailable :: InstalledPackageIndex
-> PackageConfig
-> Either PackageConfig (PackageConfig, [InstalledPackageId])
depsAvailable ipids pkg
| null dangling = Left pkg
| otherwise = Right (pkg, dangling)
- where dangling = filter (not . (`elemFM` ipids)) (depends pkg)
+ where dangling = filter (not . (`Map.member` ipids)) (depends pkg)
-- -----------------------------------------------------------------------------
-- Eliminate shadowed packages, giving the user some feedback
-- later packages in the list should shadow earlier ones with the same
--- package name/version.
-shadowPackages :: [PackageConfig] -> UnusablePackages
-shadowPackages pkgs
- = let (_,shadowed) = foldl check (emptyUFM,[]) pkgs
- in listToFM shadowed
+-- package name/version. Additionally, a package may be preferred if
+-- it is in the transitive closure of packages selected using -package-id
+-- flags.
+shadowPackages :: [PackageConfig] -> [InstalledPackageId] -> UnusablePackages
+shadowPackages pkgs preferred
+ = let (shadowed,_) = foldl check ([],emptyUFM) pkgs
+ in Map.fromList shadowed
where
- check (pkgmap,shadowed) pkg
- = (addToUFM pkgmap (packageConfigId pkg) pkg, shadowed')
- where
- shadowed'
+ check (shadowed,pkgmap) pkg
| Just oldpkg <- lookupUFM pkgmap (packageConfigId pkg)
- = (installedPackageId oldpkg, ShadowedBy (installedPackageId pkg))
- :shadowed
+ , let
+ ipid_new = installedPackageId pkg
+ ipid_old = installedPackageId oldpkg
+ --
+ , ipid_old /= ipid_new
+ = if ipid_old `elem` preferred
+ then ( (ipid_new, ShadowedBy ipid_old) : shadowed, pkgmap )
+ else ( (ipid_old, ShadowedBy ipid_new) : shadowed, pkgmap' )
| otherwise
- = shadowed
+ = (shadowed, pkgmap')
+ where
+ pkgmap' = addToUFM pkgmap (packageConfigId pkg) pkg
-- -----------------------------------------------------------------------------
ignorePackages :: [PackageFlag] -> [PackageConfig] -> UnusablePackages
-ignorePackages flags pkgs = listToFM (concatMap doit flags)
+ignorePackages flags pkgs = Map.fromList (concatMap doit flags)
where
doit (IgnorePackage str) =
case partition (matchingStr str) pkgs of
doit _ = panic "ignorePackages"
-- -----------------------------------------------------------------------------
+
+depClosure :: InstalledPackageIndex
+ -> [InstalledPackageId]
+ -> [InstalledPackageId]
+depClosure index ipids = closure Map.empty ipids
+ where
+ closure set [] = Map.keys set
+ closure set (ipid : ipids)
+ | ipid `Map.member` set = closure set ipids
+ | Just p <- Map.lookup ipid index = closure (Map.insert ipid p set)
+ (depends p ++ ipids)
+ | otherwise = closure set ipids
+
+-- -----------------------------------------------------------------------------
-- When all the command-line options are in, we can process our package
-- settings and populate the package state.
mkPackageState dflags pkgs0 preload0 this_package = do
+{-
+ Plan.
+
+ 1. P = transitive closure of packages selected by -package-id
+
+ 2. Apply shadowing. When there are multiple packages with the same
+ sourcePackageId,
+ * if one is in P, use that one
+ * otherwise, use the one highest in the package stack
+ [
+ rationale: we cannot use two packages with the same sourcePackageId
+ in the same program, because sourcePackageId is the symbol prefix.
+ Hence we must select a consistent set of packages to use. We have
+ a default algorithm for doing this: packages higher in the stack
+ shadow those lower down. This default algorithm can be overriden
+ by giving explicit -package-id flags; then we have to take these
+ preferences into account when selecting which other packages are
+ made available.
+
+ Our simple algorithm throws away some solutions: there may be other
+ consistent sets that would satisfy the -package flags, but it's
+ not GHC's job to be doing constraint solving.
+ ]
+
+ 3. remove packages selected by -ignore-package
+
+ 4. remove any packages with missing dependencies, or mutually recursive
+ dependencies.
+
+ 5. report (with -v) any packages that were removed by steps 2-4
+
+ 6. apply flags to set exposed/hidden on the resulting packages
+ - if any flag refers to a package which was removed by 2-4, then
+ we can give an error message explaining why
+
+ 7. hide any packages which are superseded by later exposed packages
+-}
+
let
flags = reverse (packageFlags dflags)
+
+ -- pkgs0 with duplicate packages filtered out. This is
+ -- important: it is possible for a package in the global package
+ -- DB to have the same IPID as a package in the user DB, and
+ -- we want the latter to take precedence. This is not the same
+ -- as shadowing (below), since in this case the two packages
+ -- have the same ABI and are interchangeable.
+ --
+ -- #4072: note that we must retain the ordering of the list here
+ -- so that shadowing behaves as expected when we apply it later.
+ pkgs0_unique = snd $ foldr del (Set.empty,[]) pkgs0
+ where del p (s,ps)
+ | pid `Set.member` s = (s,ps)
+ | otherwise = (Set.insert pid s, p:ps)
+ where pid = installedPackageId p
+ -- XXX this is just a variant of nub
+
+ ipid_map = Map.fromList [ (installedPackageId p, p) | p <- pkgs0 ]
+
+ ipid_selected = depClosure ipid_map [ InstalledPackageId i
+ | ExposePackageId i <- flags ]
+
(ignore_flags, other_flags) = partition is_ignore flags
is_ignore IgnorePackage{} = True
is_ignore _ = False
- shadowed = shadowPackages pkgs0
- ignored = ignorePackages ignore_flags pkgs0
+ shadowed = shadowPackages pkgs0_unique ipid_selected
+
+ ignored = ignorePackages ignore_flags pkgs0_unique
- pkgs0' = filter (not . (`elemFM` (plusFM shadowed ignored)) . installedPackageId) pkgs0
+ pkgs0' = filter (not . (`Map.member` (Map.union shadowed ignored)) . installedPackageId) pkgs0_unique
broken = findBroken pkgs0'
- unusable = shadowed `plusFM` ignored `plusFM` broken
+ unusable = shadowed `Map.union` ignored `Map.union` broken
reportUnusable dflags unusable
-- Modify the package database according to the command-line flags
-- (-package, -hide-package, -ignore-package, -hide-all-packages).
--
- pkgs1 <- foldM (applyPackageFlag unusable) pkgs0 other_flags
- let pkgs2 = filter (not . (`elemFM` unusable) . installedPackageId) pkgs1
+ pkgs1 <- foldM (applyPackageFlag unusable) pkgs0_unique other_flags
+ let pkgs2 = filter (not . (`Map.member` unusable) . installedPackageId) pkgs1
-- Here we build up a set of the packages mentioned in -package
-- flags on the command line; these are called the "preload"
let pkg_db = extendPackageConfigMap emptyPackageConfigMap pkgs4
- ipid_map = listToFM [ (installedPackageId p, packageConfigId p)
- | p <- pkgs4 ]
+ ipid_map = Map.fromList [ (installedPackageId p, packageConfigId p)
+ | p <- pkgs4 ]
lookupIPID ipid@(InstalledPackageId str)
- | Just pid <- lookupFM ipid_map ipid = return pid
- | otherwise = missingPackageErr str
+ | Just pid <- Map.lookup ipid ipid_map = return pid
+ | otherwise = missingPackageErr str
preload2 <- mapM lookupIPID preload1
packageHsLibs :: DynFlags -> PackageConfig -> [String]
packageHsLibs dflags p = map (mkDynName . addSuffix) (hsLibraries p)
where
- non_dyn_ways = filter ((/= WayDyn) . wayName) (ways dflags)
+ ways0 = ways dflags
+
+ ways1 = filter ((/= WayDyn) . wayName) ways0
-- the name of a shared library is libHSfoo-ghc<version>.so
-- we leave out the _dyn, because it is superfluous
- tag = mkBuildTag (filter (not . wayRTSOnly) non_dyn_ways)
- rts_tag = mkBuildTag non_dyn_ways
+ -- debug RTS includes support for -eventlog
+ ways2 | WayDebug `elem` map wayName ways1
+ = filter ((/= WayEventLog) . wayName) ways1
+ | otherwise
+ = ways1
+
+ tag = mkBuildTag (filter (not . wayRTSOnly) ways2)
+ rts_tag = mkBuildTag ways2
mkDynName | opt_Static = id
| otherwise = (++ ("-ghc" ++ cProjectVersion))
-- Takes a list of packages, and returns the list with dependencies included,
-- in reverse dependency order (a package appears before those it depends on).
closeDeps :: PackageConfigMap
- -> FiniteMap InstalledPackageId PackageId
+ -> Map InstalledPackageId PackageId
-> [(PackageId, Maybe PackageId)]
-> IO [PackageId]
closeDeps pkg_map ipid_map ps = throwErr (closeDepsErr pkg_map ipid_map ps)
Succeeded r -> return r
closeDepsErr :: PackageConfigMap
- -> FiniteMap InstalledPackageId PackageId
+ -> Map InstalledPackageId PackageId
-> [(PackageId,Maybe PackageId)]
-> MaybeErr Message [PackageId]
closeDepsErr pkg_map ipid_map ps = foldM (add_package pkg_map ipid_map) [] ps
-- internal helper
add_package :: PackageConfigMap
- -> FiniteMap InstalledPackageId PackageId
+ -> Map InstalledPackageId PackageId
-> [PackageId]
-> (PackageId,Maybe PackageId)
-> MaybeErr Message [PackageId]
return (p : ps')
where
add_package_ipid ps ipid@(InstalledPackageId str)
- | Just pid <- lookupFM ipid_map ipid
+ | Just pid <- Map.lookup ipid ipid_map
= add_package pkg_db ipid_map ps (pid, Just p)
| otherwise
= Failed (missingPackageMsg str <> missingDependencyMsg mb_parent)
-- | Will the 'Name' come from a dynamically linked library?
isDllName :: PackageId -> Name -> Bool
+-- Despite the "dll", I think this function just means that
+-- the synbol comes from another dynamically-linked package,
+-- and applies on all platforms, not just Windows
isDllName this_pkg name
| opt_Static = False
| Just mod <- nameModule_maybe name = modulePackageId mod /= this_pkg