module Debian.GenBuildDeps
( DepInfo(..)
, sourceName'
, relations'
, binaryNames'
, buildDependencies
, RelaxInfo
, relaxDeps
, BuildableInfo(..)
, ReadyTarget(..)
, buildable
, compareSource
, orderSource
, genDeps
, failPackage
, getSourceOrder
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Exception (throw)
import Control.Monad (filterM, foldM)
import Control.Monad.State (evalState, get, modify, State)
import Data.Graph (Graph, Edge, Vertex, buildG, topSort, reachable, transposeG, edges, scc)
import Data.List as List (elemIndex, find, map, nub, partition, tails)
import Data.Map as Map (empty, findWithDefault, fromList, insert, Map, lookup)
import Data.Maybe
import Data.Set as Set (fromList, intersection, null, Set)
import Data.Tree as Tree (Tree(Node, rootLabel, subForest))
import Debian.Control (parseControlFromFile)
import Debian.Control.Policy (HasDebianControl, DebianControl, ControlFileError(..), validateDebianControl, debianSourcePackageName, debianBinaryPackageNames, debianBuildDeps, debianBuildDepsIndep)
import Debian.Loc (__LOC__)
import Debian.Relation
import Debian.Relation.Text ()
import System.Directory (getDirectoryContents, doesFileExist)
data DepInfo = DepInfo {
sourceName :: SrcPkgName
, relations :: Relations
, binaryNames :: [BinPkgName]
, depSet :: Set.Set BinPkgName
, binSet :: Set.Set BinPkgName
} deriving Show
instance Eq DepInfo where
a == b = (sourceName a == sourceName b) &&
Set.fromList (map Set.fromList (relations a)) == Set.fromList (map Set.fromList (relations b)) &&
Set.fromList (binaryNames a) == Set.fromList (binaryNames b)
buildDependencies :: HasDebianControl control => control -> DepInfo
buildDependencies control = do
let rels = concat [fromMaybe [] (debianBuildDeps control),
fromMaybe [] (debianBuildDepsIndep control)]
bins = debianBinaryPackageNames control
DepInfo { sourceName = debianSourcePackageName control
, relations = rels
, binaryNames = bins
, depSet = Set.fromList (List.map (\(Rel x _ _) -> x) (concat rels))
, binSet = Set.fromList bins }
sourceName' :: HasDebianControl control => control -> SrcPkgName
sourceName' control = debianSourcePackageName control
relations' :: HasDebianControl control => control -> Relations
relations' control = concat [fromMaybe [] (debianBuildDeps control),
fromMaybe [] (debianBuildDepsIndep control)]
binaryNames' :: HasDebianControl control => control -> [BinPkgName]
binaryNames' control = debianBinaryPackageNames control
newtype OldRelaxInfo = RelaxInfo [(BinPkgName, Maybe SrcPkgName)] deriving Show
type RelaxInfo = SrcPkgName -> BinPkgName -> Bool
relaxDeps :: RelaxInfo -> [DepInfo] -> [DepInfo]
relaxDeps relaxInfo deps =
List.map relaxDep deps
where
relaxDep :: DepInfo -> DepInfo
relaxDep info = info {relations = filteredDependencies}
where
filteredDependencies :: Relations
filteredDependencies = filter (/= []) (List.map (filter keepDep) (relations info))
keepDep :: Relation -> Bool
keepDep (Rel name _ _) = not (relaxInfo (sourceName info) name)
data ReadyTarget a
= ReadyTarget { ready :: a
, waiting :: [a]
, other :: [a]
}
data BuildableInfo a
= BuildableInfo
{ readyTargets :: [ReadyTarget a]
, allBlocked :: [a] }
| CycleInfo
{ depPairs :: [(a, a)] }
buildable :: forall a. (a -> DepInfo) -> [a] -> BuildableInfo a
buildable relax packages =
case partition (\ x -> reachable hasDep x == [x]) verts of
([], _) -> CycleInfo {depPairs = List.map ofEdge $ head $ (allCycles hasDep)}
(allReady, blocked) ->
BuildableInfo { readyTargets = List.map (makeReady blocked allReady) allReady
, allBlocked = List.map ofVertex blocked }
where
makeReady :: [Vertex] -> [Vertex] -> Vertex -> ReadyTarget a
makeReady blocked ready thisReady =
let otherReady = filter (/= thisReady) ready
(directlyBlocked, otherBlocked) = partition (\ x -> elem x (reachable isDep thisReady)) blocked in
ReadyTarget { ready = ofVertex thisReady
, waiting = List.map ofVertex directlyBlocked
, other = List.map ofVertex (otherReady ++ otherBlocked) }
isDep :: Graph
isDep = transposeG hasDep
hasDep :: Graph
hasDep = buildG (0, length packages 1) hasDepEdges
hasDepEdges :: [(Int, Int)]
hasDepEdges =
#if 0
nub (foldr f [] (tails vertPairs))
where f :: [(Int, DepInfo)] -> [(Int, Int)] -> [(Int, Int)]
f [] es = es
f (x : xs) es = catMaybes (List.map (toEdge x) xs) ++ es
toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> Maybe Edge
toEdge (xv, xa) (yv, ya) =
case compareSource xa ya of
EQ -> Nothing
LT -> Just (yv, xv)
GT -> Just (xv, yv)
#else
nub (evalState (foldM f [] (tails vertPairs)) Map.empty)
where f :: [(Int, Int)] -> [(Int, DepInfo)] -> State (Map.Map (Int, Int) Ordering) [(Int, Int)]
f es [] = return es
f es (x : xs) = mapM (toEdge x) xs >>= \es' -> return (catMaybes es' ++ es)
toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> State (Map.Map (Int, Int) Ordering) (Maybe Edge)
toEdge (xv, xa) (yv, ya) = do
mp <- get
r <- case Map.lookup (xv, yv) mp of
Just r' -> return r'
Nothing -> do
let r' = compareSource xa ya
modify (Map.insert (xv, yv) r')
return r'
case r of
EQ -> return Nothing
LT -> return $ Just (yv, xv)
GT -> return $ Just (xv, yv)
#endif
ofEdge :: Edge -> (a, a)
ofEdge (a, b) = (ofVertex a, ofVertex b)
ofVertex :: Int -> a
ofVertex n = fromJust (Map.findWithDefault Nothing n (Map.fromList (zip [0..] (map Just packages))))
verts :: [Int]
verts = map fst vertPairs
vertPairs :: [(Int, DepInfo)]
vertPairs = zip [0..] $ map relax packages
allCycles :: Graph -> [[Edge]]
allCycles g =
concatMap sccCycles (scc g)
where
sccCycles :: Tree Vertex -> [[Edge]]
sccCycles t = mapMaybe addBackEdge (treePaths t)
addBackEdge :: [Vertex] -> Maybe [Edge]
addBackEdge path@(root : _) =
let back = (last path, root) in
if elem back (edges g) then Just (pathEdges (path ++ [root])) else Nothing
treePaths :: Tree a -> [[a]]
treePaths (Node {rootLabel = r, subForest = []}) = [[r]]
treePaths (Node {rootLabel = r, subForest = ts}) = map (r :) (concatMap treePaths ts)
pathEdges :: [a] -> [(a, a)]
pathEdges (v1 : v2 : vs) = (v1, v2) : pathEdges (v2 : vs)
pathEdges _ = []
failPackage :: Eq a => (a -> a -> Ordering) -> a -> [a] -> ([a], [a])
failPackage cmp failed packages =
let graph = buildGraph cmp packages in
let root = elemIndex failed packages in
let victims = maybe [] (map (fromJust . vertex) . reachable graph) root in
partition (\ x -> not . elem x $ victims) packages
where
vertex n = Map.findWithDefault Nothing n vertexMap
vertexMap = Map.fromList (zip [0..] (map Just packages))
orderSource :: (a -> a -> Ordering) -> [a] -> [a]
orderSource cmp packages =
map (fromJust . vertex) (topSort graph)
where
graph = buildGraph cmp packages
vertex n = Map.findWithDefault Nothing n vertexMap
vertexMap = Map.fromList (zip [0..] (map Just packages))
buildGraph :: (a -> a -> Ordering) -> [a] -> Graph
buildGraph cmp packages =
let es = someEdges (zip packages [0..]) in
buildG (0, length packages 1) es
where
someEdges [] = []
someEdges (a : etc) = aEdges a etc ++ someEdges etc
aEdges (ap, an) etc =
concat (map (\ (bp, bn) ->
case cmp ap bp of
LT -> [(an, bn)]
GT -> [(bn, an)]
EQ -> []) etc)
compareSource :: DepInfo -> DepInfo -> Ordering
compareSource p1 p2
#if 0
| any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p2))) (concat (relations p1)) = GT
| any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p1))) (concat (relations p2)) = LT
| otherwise = EQ
where
checkPackageNameReq :: Relation -> BinPkgName -> Bool
checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName
#else
| not (Set.null (Set.intersection (depSet p1) (binSet p2))) = GT
| not (Set.null (Set.intersection (depSet p2) (binSet p1))) = LT
| otherwise = EQ
#endif
compareSource' :: HasDebianControl control => control -> control -> Ordering
compareSource' control1 control2
| any (\rel -> isJust (find (checkPackageNameReq rel) bins2)) (concat depends1) = GT
| any (\rel -> isJust (find (checkPackageNameReq rel) bins1)) (concat depends2) = LT
| otherwise = EQ
where
bins1 = binaryNames' control1
bins2 = binaryNames' control2
depends1 = relations' control1
depends2 = relations' control2
checkPackageNameReq :: Relation -> BinPkgName -> Bool
checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName
genDeps :: [FilePath] -> IO [DebianControl]
genDeps controlFiles = do
orderSource compareSource' <$> mapM genDep' controlFiles
where
genDep' controlPath = parseControlFromFile controlPath >>=
either (\ x -> throw (ParseRelationsError [$__LOC__] x))
(\ x -> validateDebianControl x >>= either throw return)
getSourceOrder :: FilePath -> IO [SrcPkgName]
getSourceOrder fp =
findControlFiles fp >>= genDeps >>= return . map sourceName'
where
findControlFiles :: FilePath -> IO [FilePath]
findControlFiles root =
getDirectoryContents root >>=
mapM (\ x -> return $ root ++ "/" ++ x ++ "/debian/control") >>=
filterM doesFileExist