module Main where
import qualified Distribution.PackageDescription as P
import Distribution.PackageDescription
(GenericPackageDescription, PackageDescription,
package, packageDescription, buildDepends, )
import Distribution.PackageDescription.Parse (readPackageDescription, )
import Distribution.Package
(Dependency(Dependency), PackageName(PackageName), pkgName, )
import qualified Distribution.Verbosity as Verbosity
import qualified Distribution.ReadE as ReadE
import System.Console.GetOpt
(getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo, )
import System.Exit (exitWith, ExitCode(..), )
import qualified System.Environment as Env
import qualified System.FilePath as FilePath
import Data.Graph.Inductive.Query.DFS (topsort', scc, )
import Data.Graph.Inductive.Tree (Gr, )
import qualified Data.Graph.Inductive.Graph as Graph
import qualified Control.Monad.Exception.Synchronous as Exc
import qualified Control.Monad.Trans.Class as Trans
import qualified Data.Set as Set
import Control.Monad (when, )
import Data.Maybe (mapMaybe, )
main :: IO ()
main =
Exc.resolveT (\e -> putStr $ "Aborted: " ++ e ++ "\n") $ do
argv <- Trans.lift Env.getArgs
let (opts, cabalPaths, errors) =
getOpt RequireOrder options argv
when (not (null errors)) $ Exc.throwT $ concat $ errors
flags <-
Exc.ExceptionalT $ return $
foldr (flip (>>=))
(return $
Flags {optHelp = False,
optVerbosity = Verbosity.silent,
optInfo = location})
opts
when (optHelp flags)
(Trans.lift $
Env.getProgName >>= \programName ->
putStrLn
(usageInfo ("Usage: " ++ programName ++
" [OPTIONS] CABAL-FILES ...") options) >>
exitWith ExitSuccess)
Trans.lift $ sortCabalFiles flags cabalPaths
data Flags =
Flags {
optHelp :: Bool,
optVerbosity :: Verbosity.Verbosity,
optInfo :: SourcePackage -> String
}
options :: [OptDescr (Flags -> Exc.Exceptional String Flags)]
options =
Option ['h'] ["help"]
(NoArg (\flags -> return $ flags{optHelp = True}))
"show options" :
Option ['v'] ["verbose"]
(ReqArg
(\str flags ->
fmap (\n -> flags{optVerbosity = n}) $
Exc.fromEither $
ReadE.runReadE Verbosity.flagToVerbosity str)
"N")
"verbosity level: 0..3" :
Option [] ["info"]
(ReqArg
(\str flags ->
fmap (\select -> flags{optInfo = select}) $
case str of
"name" -> Exc.Success
(getPkgName . pkgName . package .
packageDescription . description)
"path" -> Exc.Success location
"dir" -> Exc.Success (FilePath.takeDirectory . location)
_ ->
Exc.Exception $
"unknown info type " ++ str)
"KIND")
"kind of output: name, path, dir" :
[]
data SourcePackage =
SourcePackage {
location :: FilePath,
description :: GenericPackageDescription
}
sortCabalFiles :: Flags -> [FilePath] -> IO ()
sortCabalFiles flags cabalPaths =
do pkgDescs <-
mapM (readPackageDescription (optVerbosity flags)) cabalPaths
when (optVerbosity flags >= Verbosity.verbose) $
flip mapM_ pkgDescs $ \pkgDesc -> do
putStrLn
((getPkgName . pkgName . package . packageDescription $ pkgDesc) ++ ":")
let deps =
Set.toAscList $ Set.fromList $
map (getPkgName . depName) $
allDependencies pkgDesc
flip mapM_ deps $ \dep ->
putStrLn $ " " ++ dep
mapM_ (putStrLn . optInfo flags) $
getBuildOrder $
zipWith SourcePackage cabalPaths pkgDescs
getBuildOrder ::
[SourcePackage] ->
[SourcePackage]
getBuildOrder srcPkgs =
let nodes = zip [0..] srcPkgs
nodeDict =
zip
(map (pkgName . package . packageDescription . description)
srcPkgs)
[0..]
edges = do
(srcNode,desc) <- nodes
dstNode <-
mapMaybe
(flip lookup nodeDict . depName)
(allDependencies $ description desc)
return (dstNode, srcNode, ())
graph :: Gr SourcePackage ()
graph = Graph.mkGraph nodes edges
in if hasCycle graph
then error "cycle in dependencies"
else topsort' graph
allDependencies :: GenericPackageDescription -> [Dependency]
allDependencies pkg =
P.buildDepends (packageDescription pkg) ++
maybe [] (concatMap snd . flattenCondTree) (P.condLibrary pkg) ++
concatMap (concatMap snd . flattenCondTree . snd) (P.condExecutables pkg)
flattenCondTree :: P.CondTree v c a -> [(a,c)]
flattenCondTree tree =
(P.condTreeData tree, P.condTreeConstraints tree) :
concatMap
(\(_, thenBranch, elseBranch) ->
flattenCondTree thenBranch ++
maybe [] flattenCondTree elseBranch)
(P.condTreeComponents tree)
depName :: Dependency -> PackageName
depName (Dependency name _) = name
getPkgName :: PackageName -> String
getPkgName (PackageName name) = name
hasCycle :: Gr a b -> Bool
hasCycle graph =
length (scc graph) < length (Graph.nodes graph)