mirror of
https://github.com/commercialhaskell/stackage.git
synced 2026-01-12 23:38:29 +01:00
163 lines
5.7 KiB
Haskell
163 lines
5.7 KiB
Haskell
{-# LANGUAGE TupleSections #-}
|
|
{-# LANGUAGE DeriveDataTypeable #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE NoImplicitPrelude #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
-- | Confirm that a build plan has a consistent set of dependencies.
|
|
module Stackage.CheckBuildPlan
|
|
( checkBuildPlan
|
|
, BadBuildPlan
|
|
) where
|
|
|
|
import Control.Monad.Writer.Strict (Writer, execWriter, tell)
|
|
import qualified Data.Map.Strict as M
|
|
import qualified Data.Text as T
|
|
import Stackage.BuildConstraints
|
|
import Stackage.BuildPlan
|
|
import Stackage.PackageDescription
|
|
import Stackage.Prelude
|
|
|
|
-- | Check the build plan for missing deps, wrong versions, etc.
|
|
checkBuildPlan :: (MonadThrow m) => BuildPlan -> m ()
|
|
checkBuildPlan BuildPlan {..}
|
|
| null errs' = return ()
|
|
| otherwise = throwM errs
|
|
where
|
|
allPackages = map (,mempty) (siCorePackages bpSystemInfo) ++
|
|
map (ppVersion &&& M.keys . M.filter libAndExe . sdPackages . ppDesc) bpPackages
|
|
errs@(BadBuildPlan errs') =
|
|
execWriter $ mapM_ (checkDeps allPackages) $ mapToList bpPackages
|
|
-- Only looking at libraries and executables, benchmarks and tests
|
|
-- are allowed to create cycles (e.g. test-framework depends on
|
|
-- text, which uses test-framework in its test-suite).
|
|
libAndExe (DepInfo cs _) = any (flip elem [CompLibrary,CompExecutable]) cs
|
|
|
|
-- | For a given package name and plan, check that its dependencies are:
|
|
--
|
|
-- 1. Existent (existing in the provided package map)
|
|
-- 2. Within version range
|
|
-- 3. Check for dependency cycles.
|
|
checkDeps :: Map PackageName (Version,[PackageName])
|
|
-> (PackageName, PackagePlan)
|
|
-> Writer BadBuildPlan ()
|
|
checkDeps allPackages (user, pb) =
|
|
mapM_ go $ mapToList $ sdPackages $ ppDesc pb
|
|
where
|
|
go (dep, diRange -> range) =
|
|
case lookup dep allPackages of
|
|
Nothing -> tell $ BadBuildPlan $ singletonMap (dep, Nothing) errMap
|
|
Just (version,deps)
|
|
| version `withinRange` range ->
|
|
occursCheck allPackages
|
|
(\d v ->
|
|
tell $ BadBuildPlan $ singletonMap
|
|
(d,v)
|
|
errMap)
|
|
dep
|
|
deps
|
|
[]
|
|
| otherwise -> tell $ BadBuildPlan $ singletonMap
|
|
(dep, Just version)
|
|
errMap
|
|
where
|
|
errMap = singletonMap pu range
|
|
pu = PkgUser
|
|
{ puName = user
|
|
, puVersion = ppVersion pb
|
|
, puMaintainer = pcMaintainer $ ppConstraints pb
|
|
, puGithubPings = ppGithubPings pb
|
|
}
|
|
|
|
-- | Check whether the package(s) occurs within its own dependency
|
|
-- tree.
|
|
occursCheck
|
|
:: Monad m
|
|
=> Map PackageName (Version,[PackageName])
|
|
-- ^ All packages.
|
|
-> (PackageName -> Maybe Version -> m ())
|
|
-- ^ Report an erroneous package.
|
|
-> PackageName
|
|
-- ^ Starting package to check for cycles in.
|
|
-> [PackageName]
|
|
-- ^ Dependencies of the package.
|
|
-> [PackageName]
|
|
-- ^ Previously seen packages up the dependency tree.
|
|
-> m ()
|
|
occursCheck allPackages reportError =
|
|
go
|
|
where
|
|
go pkg deps seen =
|
|
case find (flip elem seen) deps of
|
|
Just cyclic ->
|
|
reportError cyclic $
|
|
fmap fst (lookup cyclic allPackages)
|
|
Nothing ->
|
|
forM_ deps $
|
|
\pkg' ->
|
|
case lookup pkg' allPackages of
|
|
Just (_v,deps')
|
|
| pkg' /= pkg -> go pkg' deps' seen'
|
|
_ -> return ()
|
|
where seen' = pkg : seen
|
|
|
|
data PkgUser = PkgUser
|
|
{ puName :: PackageName
|
|
, puVersion :: Version
|
|
, puMaintainer :: Maybe Maintainer
|
|
, puGithubPings :: Set Text
|
|
}
|
|
deriving (Eq, Ord)
|
|
|
|
pkgUserShow1 :: PkgUser -> Text
|
|
pkgUserShow1 PkgUser {..} = concat
|
|
[ display puName
|
|
, "-"
|
|
, display puVersion
|
|
]
|
|
|
|
pkgUserShow2 :: PkgUser -> Text
|
|
pkgUserShow2 PkgUser {..} = unwords
|
|
$ (maybe "No maintainer" unMaintainer puMaintainer ++ ".")
|
|
: map (cons '@') (setToList puGithubPings)
|
|
|
|
newtype BadBuildPlan =
|
|
BadBuildPlan (Map (PackageName, Maybe Version) (Map PkgUser VersionRange))
|
|
deriving Typeable
|
|
instance Exception BadBuildPlan
|
|
instance Show BadBuildPlan where
|
|
show (BadBuildPlan errs) =
|
|
unpack $ concatMap go $ mapToList errs
|
|
where
|
|
go ((dep, mdepVer), users) = unlines
|
|
$ ""
|
|
: showDepVer dep mdepVer
|
|
: map showUser (mapToList users)
|
|
|
|
showDepVer :: PackageName -> Maybe Version -> Text
|
|
showDepVer dep Nothing = display dep ++ " (not present) depended on by:"
|
|
showDepVer dep (Just version) = concat
|
|
[ display dep
|
|
, "-"
|
|
, display version
|
|
, " depended on by:"
|
|
]
|
|
|
|
showUser :: (PkgUser, VersionRange) -> Text
|
|
showUser (pu, range) = concat
|
|
[ "- "
|
|
, pkgUserShow1 pu
|
|
, " ("
|
|
-- add a space after < to avoid confusing Markdown processors (like
|
|
-- Github's issue tracker)
|
|
, T.replace "<" "< " $ display range
|
|
, "). "
|
|
, pkgUserShow2 pu
|
|
]
|
|
|
|
instance Monoid BadBuildPlan where
|
|
mempty = BadBuildPlan mempty
|
|
mappend (BadBuildPlan x) (BadBuildPlan y) =
|
|
BadBuildPlan $ unionWith (unionWith intersectVersionRanges) x y
|