stack-2.11.1: src/Stack/Build/Installed.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
-- Determine which packages are already installed
module Stack.Build.Installed
( InstalledMap
, Installed (..)
, getInstalled
, InstallMap
, toInstallMap
) where
import Data.Conduit ( ZipSink (..), getZipSink )
import qualified Data.Conduit.List as CL
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import Stack.Build.Cache ( getInstalledExes )
import Stack.Constants ( wiredInPackages )
import Stack.PackageDump
( conduitDumpPackage, ghcPkgDump, pruneDeps )
import Stack.Prelude
import Stack.SourceMap ( getPLIVersion, loadVersion )
import Stack.Types.CompilerPaths ( getGhcPkgExe )
import Stack.Types.DumpPackage ( DumpPackage (..) )
import Stack.Types.EnvConfig
( HasEnvConfig, packageDatabaseDeps, packageDatabaseExtra
, packageDatabaseLocal
)
import Stack.Types.GhcPkgId ( GhcPkgId )
import Stack.Types.Package
( InstallLocation (..), InstallMap, Installed (..)
, InstalledMap, InstalledPackageLocation (..)
)
import Stack.Types.SourceMap
( DepPackage (..), ProjectPackage (..), SourceMap (..) )
toInstallMap :: MonadIO m => SourceMap -> m InstallMap
toInstallMap sourceMap = do
projectInstalls <-
for (smProject sourceMap) $ \pp -> do
version <- loadVersion (ppCommon pp)
pure (Local, version)
depInstalls <-
for (smDeps sourceMap) $ \dp ->
case dpLocation dp of
PLImmutable pli -> pure (Snap, getPLIVersion pli)
PLMutable _ -> do
version <- loadVersion (dpCommon dp)
pure (Local, version)
pure $ projectInstalls <> depInstalls
-- | Returns the new InstalledMap and all of the locally registered packages.
getInstalled :: HasEnvConfig env
=> InstallMap -- ^ does not contain any installed information
-> RIO env
( InstalledMap
, [DumpPackage] -- globally installed
, [DumpPackage] -- snapshot installed
, [DumpPackage] -- locally installed
)
getInstalled {-opts-} installMap = do
logDebug "Finding out which packages are already installed"
snapDBPath <- packageDatabaseDeps
localDBPath <- packageDatabaseLocal
extraDBPaths <- packageDatabaseExtra
let loadDatabase' = loadDatabase {-opts mcache-} installMap
(installedLibs0, globalDumpPkgs) <- loadDatabase' Nothing []
(installedLibs1, _extraInstalled) <-
foldM (\lhs' pkgdb ->
loadDatabase' (Just (ExtraGlobal, pkgdb)) (fst lhs')
) (installedLibs0, globalDumpPkgs) extraDBPaths
(installedLibs2, snapshotDumpPkgs) <-
loadDatabase' (Just (InstalledTo Snap, snapDBPath)) installedLibs1
(installedLibs3, localDumpPkgs) <-
loadDatabase' (Just (InstalledTo Local, localDBPath)) installedLibs2
let installedLibs = Map.fromList $ map lhPair installedLibs3
-- Add in the executables that are installed, making sure to only trust a
-- listed installation under the right circumstances (see below)
let exesToSM loc = Map.unions . map (exeToSM loc)
exeToSM loc (PackageIdentifier name version) =
case Map.lookup name installMap of
-- Doesn't conflict with anything, so that's OK
Nothing -> m
Just (iLoc, iVersion)
-- Not the version we want, ignore it
| version /= iVersion || mismatchingLoc loc iLoc -> Map.empty
| otherwise -> m
where
m = Map.singleton name (loc, Executable $ PackageIdentifier name version)
mismatchingLoc installed target
| target == installed = False
| installed == Local = False -- snapshot dependency could end up
-- in a local install as being mutable
| otherwise = True
exesSnap <- getInstalledExes Snap
exesLocal <- getInstalledExes Local
let installedMap = Map.unions
[ exesToSM Local exesLocal
, exesToSM Snap exesSnap
, installedLibs
]
pure ( installedMap
, globalDumpPkgs
, snapshotDumpPkgs
, localDumpPkgs
)
-- | Outputs both the modified InstalledMap and the Set of all installed
-- packages in this database
--
-- The goal is to ascertain that the dependencies for a package are present,
-- that it has profiling if necessary, and that it matches the version and
-- location needed by the SourceMap
loadDatabase ::
HasEnvConfig env
=> InstallMap -- ^ to determine which installed things we should include
-> Maybe (InstalledPackageLocation, Path Abs Dir)
-- ^ package database, Nothing for global
-> [LoadHelper] -- ^ from parent databases
-> RIO env ([LoadHelper], [DumpPackage])
loadDatabase installMap mdb lhs0 = do
pkgexe <- getGhcPkgExe
(lhs1', dps) <- ghcPkgDump pkgexe (fmap snd (maybeToList mdb)) $
conduitDumpPackage .| sink
lhs1 <- mapMaybeM (processLoadResult mdb) lhs1'
let lhs = pruneDeps id lhId lhDeps const (lhs0 ++ lhs1)
pure (map (\lh -> lh { lhDeps = [] }) $ Map.elems lhs, dps)
where
mloc = fmap fst mdb
sinkDP = CL.map (isAllowed installMap mloc &&& toLoadHelper mloc)
.| CL.consume
sink = getZipSink $ (,)
<$> ZipSink sinkDP
<*> ZipSink CL.consume
processLoadResult :: HasLogFunc env
=> Maybe (InstalledPackageLocation, Path Abs Dir)
-> (Allowed, LoadHelper)
-> RIO env (Maybe LoadHelper)
processLoadResult _ (Allowed, lh) = pure (Just lh)
processLoadResult mdb (reason, lh) = do
logDebug $
"Ignoring package "
<> fromString (packageNameString (fst (lhPair lh)))
<> maybe
mempty
( \db -> ", from "
<> displayShow db
<> ","
)
mdb
<> " due to"
<> case reason of
UnknownPkg -> " it being unknown to the resolver / extra-deps."
WrongLocation mloc loc -> " wrong location: " <> displayShow (mloc, loc)
WrongVersion actual wanted ->
" wanting version "
<> fromString (versionString wanted)
<> " instead of "
<> fromString (versionString actual)
pure Nothing
data Allowed
= Allowed
| UnknownPkg
| WrongLocation (Maybe InstalledPackageLocation) InstallLocation
| WrongVersion Version Version
deriving (Eq, Show)
-- | Check if a can be included in the set of installed packages or not, based
-- on the package selections made by the user. This does not perform any
-- dirtiness or flag change checks.
isAllowed :: InstallMap
-> Maybe InstalledPackageLocation
-> DumpPackage
-> Allowed
isAllowed installMap mloc dp = case Map.lookup name installMap of
Nothing ->
-- If the sourceMap has nothing to say about this package,
-- check if it represents a sublibrary first
-- See: https://github.com/commercialhaskell/stack/issues/3899
case dpParentLibIdent dp of
Just (PackageIdentifier parentLibName version') ->
case Map.lookup parentLibName installMap of
Nothing -> checkNotFound
Just instInfo
| version' == version -> checkFound instInfo
| otherwise -> checkNotFound -- different versions
Nothing -> checkNotFound
Just pii -> checkFound pii
where
PackageIdentifier name version = dpPackageIdent dp
-- Ensure that the installed location matches where the sourceMap says it
-- should be installed
checkLocation Snap = True -- snapshot deps could become mutable after getting
-- any mutable dependency
checkLocation Local =
mloc == Just (InstalledTo Local) || mloc == Just ExtraGlobal -- 'locally' installed snapshot packages can come from extra dbs
-- Check if a package is allowed if it is found in the sourceMap
checkFound (installLoc, installVer)
| not (checkLocation installLoc) = WrongLocation mloc installLoc
| version /= installVer = WrongVersion version installVer
| otherwise = Allowed
-- check if a package is allowed if it is not found in the sourceMap
checkNotFound = case mloc of
-- The sourceMap has nothing to say about this global package, so we can use it
Nothing -> Allowed
Just ExtraGlobal -> Allowed
-- For non-global packages, don't include unknown packages.
-- See: https://github.com/commercialhaskell/stack/issues/292
Just _ -> UnknownPkg
data LoadHelper = LoadHelper
{ lhId :: !GhcPkgId
, lhDeps :: ![GhcPkgId]
, lhPair :: !(PackageName, (InstallLocation, Installed))
}
deriving Show
toLoadHelper :: Maybe InstalledPackageLocation -> DumpPackage -> LoadHelper
toLoadHelper mloc dp = LoadHelper
{ lhId = gid
, lhDeps =
-- We always want to consider the wired in packages as having all of their
-- dependencies installed, since we have no ability to reinstall them.
-- This is especially important for using different minor versions of GHC,
-- where the dependencies of wired-in packages may change slightly and
-- therefore not match the snapshot.
if name `Set.member` wiredInPackages
then []
else dpDepends dp
, lhPair = (name, (toPackageLocation mloc, Library ident gid (Right <$> dpLicense dp)))
}
where
gid = dpGhcPkgId dp
ident@(PackageIdentifier name _) = dpPackageIdent dp
toPackageLocation :: Maybe InstalledPackageLocation -> InstallLocation
toPackageLocation Nothing = Snap
toPackageLocation (Just ExtraGlobal) = Snap
toPackageLocation (Just (InstalledTo loc)) = loc