{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} -- | Dependency solver -- -- This is a relatively simple problem due to the choice of the -- Minimum Package Version algorithm. In fact, the only failure mode -- is referencing an unknown package or revision. module Futhark.Pkg.Solve ( solveDeps , solveDepsPure , PkgRevDepInfo ) where import Control.Monad.State import qualified Data.Set as S import qualified Data.Map as M import qualified Data.Text as T import Data.Monoid ((<>)) import Control.Monad.Free.Church import Futhark.Pkg.Info import Futhark.Pkg.Types import Prelude data PkgOp a = OpGetDeps PkgPath SemVer (Maybe T.Text) (PkgRevDeps -> a) instance Functor PkgOp where fmap f (OpGetDeps p v h c) = OpGetDeps p v h (f . c) -- | A rough build list is like a build list, but may contain packages -- that are not reachable from the root. Also contains the -- dependencies of each package. newtype RoughBuildList = RoughBuildList (M.Map PkgPath (SemVer, [PkgPath])) deriving (Show) emptyRoughBuildList :: RoughBuildList emptyRoughBuildList = RoughBuildList mempty depRoots :: PkgRevDeps -> S.Set PkgPath depRoots (PkgRevDeps m) = S.fromList $ M.keys m -- | Construct a 'BuildList' from a 'RoughBuildList'. This involves -- pruning all packages that cannot be reached from the root. buildList :: S.Set PkgPath -> RoughBuildList -> BuildList buildList roots (RoughBuildList pkgs) = BuildList $ execState (mapM_ addPkg roots) mempty where addPkg p = case M.lookup p pkgs of Nothing -> return () Just (v, deps) -> do listed <- gets $ M.member p modify $ M.insert p v unless listed $ mapM_ addPkg deps type SolveM = StateT RoughBuildList (F PkgOp) getDeps :: PkgPath -> SemVer -> Maybe T.Text -> SolveM PkgRevDeps getDeps p v h = lift $ liftF $ OpGetDeps p v h id -- | Given a list of immediate dependency minimum version constraints, -- find dependency versions that fit, including transitive -- dependencies. doSolveDeps :: PkgRevDeps -> SolveM () doSolveDeps (PkgRevDeps deps) = mapM_ add $ M.toList deps where add (p, (v, maybe_h)) = do RoughBuildList l <- get case M.lookup p l of -- Already satisfied? Just (cur_v, _) | v <= cur_v -> return () -- No; add 'p' and its dependencies. _ -> do PkgRevDeps p_deps <- getDeps p v maybe_h put $ RoughBuildList $ M.insert p (v, M.keys p_deps) l mapM_ add $ M.toList p_deps -- | Run the solver, producing both a package registry containing -- a cache of the lookups performed, as well as a build list. solveDeps :: MonadPkgRegistry m => PkgRevDeps -> m BuildList solveDeps deps = buildList (depRoots deps) <$> runF (execStateT (doSolveDeps deps) emptyRoughBuildList) return step where step (OpGetDeps p v h c) = do pinfo <- lookupPackageRev p v checkHash p v pinfo h d <- fmap pkgRevDeps . getManifest $ pkgRevGetManifest pinfo c d checkHash _ _ _ Nothing = return () checkHash p v pinfo (Just h) | h == pkgRevCommit pinfo = return () | otherwise = fail $ T.unpack $ "Package " <> p <> " " <> prettySemVer v <> " has commit hash " <> pkgRevCommit pinfo <> ", but expected " <> h <> " from package manifest." -- | A mapping of package revisions to the dependencies of that -- package. Can be considered a 'PkgRegistry' without the option of -- obtaining more information from the Internet. Probably useful only -- for testing the solver. type PkgRevDepInfo = M.Map (PkgPath, SemVer) PkgRevDeps -- | Perform package resolution with only pre-known information. This -- is useful for testing. solveDepsPure :: PkgRevDepInfo -> PkgRevDeps -> Either T.Text BuildList solveDepsPure r deps = buildList (depRoots deps) <$> runF (execStateT (doSolveDeps deps) emptyRoughBuildList) Right step where step (OpGetDeps p v _ c) = do let errmsg = "Unknown package/version: " <> p <> "-" <> prettySemVer v d <- maybe (Left errmsg) Right $ M.lookup (p,v) r c d