packages feed

rpmbuild-order 0.2.1 → 0.3

raw patch · 8 files changed

+257/−368 lines, 8 filesdep +bytestringdep +optparse-applicativedep +simple-cmd-argsdep −explicit-exceptiondep −transformers

Dependencies added: bytestring, optparse-applicative, simple-cmd-args

Dependencies removed: explicit-exception, transformers

Files

− ChangeLog
@@ -1,9 +0,0 @@-* 0.2 (2018-08-17)-- error if no spec file-- filter out hidden files from deps search-- add --version-- generate manpage with help2man--* 0.1 (2018-07-18)-- initial release with sort, deps and rdeps (reverse dependencies) commands-
+ ChangeLog.md view
@@ -0,0 +1,15 @@+# 0.3 (2019-10-10)+- check package provides instead of rpms+- use ByteString for packages (provides)+- move to simple-cmd-args (optparse-applicative)+- add --lenient for rpmspec failures++# 0.2 (2018-08-17)+- error if no spec file+- filter out hidden files from deps search+- add --version+- generate manpage with help2man++# 0.1 (2018-07-18)+- initial release with sort, deps and rdeps (reverse dependencies) commands+
+ Main.hs view
@@ -0,0 +1,198 @@+import Data.Graph.Inductive.Query.DFS (xdfsWith, topsort', scc, components)+import Data.Graph.Inductive.Tree (Gr)+import qualified Data.Graph.Inductive.Graph as Graph++import qualified Data.Set as Set+import Control.Applicative (some,+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))+#else+                            (<$>), (<*>)+#endif+                           )+import Control.Monad (guard, when, unless)+import qualified Data.ByteString.Char8 as B+import Data.Maybe (catMaybes, fromMaybe, mapMaybe)+import Data.List (delete)+import Options.Applicative (str)+import System.Directory (doesDirectoryExist, doesFileExist,+#if (defined(MIN_VERSION_directory) && MIN_VERSION_directory(1,2,5))+                         listDirectory+#else+                         getDirectoryContents+#endif+  )+import System.Exit (ExitCode (..), exitFailure)+import System.FilePath+-- replace with warning+import System.IO (hPutStrLn, stderr)+import System.Process (readProcessWithExitCode)++import SimpleCmdArgs+import Paths_rpmbuild_order (version)++#if (defined(MIN_VERSION_directory) && MIN_VERSION_directory(1,2,5))+#else+listDirectory :: FilePath -> IO [FilePath]+listDirectory path =+  filter f <$> getDirectoryContents path+  where f filename = filename /= "." && filename /= ".."+#endif++main :: IO ()+main =+  simpleCmdArgs (Just version) "Order packages by build dependencies"+  "Sort package sources (spec files) in build dependency order" $+  subcommands+  [ Subcommand "sort" "sort packages" $+    sortSpecFiles <$> verboseOpt <*> lenientOpt <*> parallelOpt <*> subdirOpt <*> pkgArgs+  , Subcommand "deps" "sort dependencies" $+    depsSpecFiles False <$> verboseOpt <*> lenientOpt <*> parallelOpt <*> subdirOpt <*> pkgArgs+  , Subcommand "rdeps" "sort dependents" $+    depsSpecFiles True <$> verboseOpt <*> lenientOpt <*> parallelOpt <*> subdirOpt <*> pkgArgs+  ]+  where+    verboseOpt = switchWith 'v' "verbose" "Verbose output for debugging"+    lenientOpt = switchWith 'l' "lenient" "Ignore rpmspec errors"+    parallelOpt = switchWith 'p' "parallel" "Separate independent packages"+    subdirOpt = optional (strOptionWith 'd' "dir" "SUBDIR" "Branch directory")+    pkgArgs = some (argumentWith str "PKG...")++findSpec :: Maybe FilePath -> FilePath -> IO (Maybe FilePath)+findSpec mdir file =+  if takeExtension file == ".spec"+    then checkFile file+    else do+    dirp <- doesDirectoryExist file+    if dirp+      then+      let dir = maybe file (file </>) mdir+          pkg = takeBaseName file in+        checkFile $ dir </> pkg ++ ".spec"+      else error $ "No spec file found for " ++ file+  where+    checkFile :: FilePath -> IO (Maybe FilePath)+    checkFile f = do+      e <- doesFileExist f+      if e+        then return $ Just f+        else return Nothing++type Package = B.ByteString++data SourcePackage =+   SourcePackage {+      location :: FilePath,+      package :: Package,+      dependencies :: [Package]+   }+   deriving (Show, Eq)++createGraphNodes :: Bool -> Bool -> Maybe FilePath -> [Package] -> [Package] ->+                    IO (Gr SourcePackage (), [Graph.Node])+createGraphNodes verbose lenient mdir pkgs subset = do+  unless (all (`elem` pkgs) subset) $+    error "Packages must be in the current directory"+  specPaths <- catMaybes <$> mapM (findSpec mdir . B.unpack) pkgs+  let names = map (B.pack . takeBaseName) specPaths+  resolves <- catMaybes <$> mapM (readProvides verbose lenient) specPaths+  deps <- catMaybes <$> mapM (getDepsSrcResolved verbose lenient resolves) specPaths+  let spkgs = zipWith3 SourcePackage specPaths names deps+      graph = getBuildGraph spkgs+  checkForCycles graph+  let nodes = Graph.labNodes graph+      subnodes = mapMaybe (pkgNode nodes) subset+  return (graph, subnodes)+  where+    pkgNode [] _ = Nothing+    pkgNode ((i,l):ns) p = if p == package l then Just i else pkgNode ns p++sortSpecFiles :: Bool -> Bool -> Bool -> Maybe FilePath -> [Package] -> IO ()+sortSpecFiles verbose lenient parallel mdir pkgs = do+      (graph, _) <- createGraphNodes verbose lenient mdir pkgs []+      if parallel then+        mapM_ ((B.putStrLn . B.cons '\n' . B.unwords . map package) . topsort' . subgraph graph) (components graph)+        else mapM_ (B.putStrLn . package) $ topsort' graph+ +depsSpecFiles :: Bool -> Bool -> Bool -> Bool -> Maybe FilePath -> [Package] -> IO ()+depsSpecFiles rev verbose lenient parallel mdir pkgs = do+  allpkgs <- map B.pack . filter (\ f -> head f /= '.') <$> listDirectory "."+  (graph, nodes) <- createGraphNodes verbose lenient mdir allpkgs pkgs+  let direction = if rev then Graph.suc' else Graph.pre'+  sortSpecFiles verbose lenient parallel mdir $ map package $ xdfsWith direction third nodes graph+  where+    third (_, _, c, _) = c++readProvides :: Bool -> Bool -> FilePath -> IO (Maybe (Package,[Package]))+readProvides verbose lenient file = do+  when verbose $ hPutStrLn stderr file+  mpkgs <- rpmspecQuery lenient ["-q", "--provides", "--define", "ghc_version any"] file+  case mpkgs of+    Nothing -> return Nothing+    Just pkgs ->+      let pkg = B.pack $ takeBaseName file in+        return $ Just (pkg, delete pkg pkgs)++getDepsSrcResolved :: Bool -> Bool -> [(Package,[Package])] -> FilePath -> IO (Maybe [Package])+getDepsSrcResolved verbose lenient provides file = do+  when verbose $ hPutStrLn stderr file+  fmap (map resolveBase) <$>+    rpmspecQuery lenient ["--buildrequires", "--define", "ghc_version any"] file+  where+    resolveBase :: Package -> Package+    resolveBase br =+      case mapMaybe (\ (pkg,subs) -> if br `elem` subs then Just pkg else Nothing) provides of+        [] -> br+        [p] -> p+        ps -> error $ B.unpack br ++ " is provided by: " ++ (B.unpack . B.unwords) ps++getBuildGraph :: [SourcePackage] -> Gr SourcePackage ()+getBuildGraph srcPkgs =+   let nodes = zip [0..] srcPkgs+       nodeDict = zip (map package srcPkgs) [0..]+       edges = do+          (srcNode,srcPkg) <- nodes+          dstNode <- mapMaybe (`lookup` nodeDict) (dependencies srcPkg)+          guard (dstNode /= srcNode)+          return (dstNode, srcNode, ())+   in Graph.mkGraph nodes edges++checkForCycles :: Monad m => Gr SourcePackage () -> m ()+checkForCycles graph =+   case getCycles graph of+      [] -> return ()+      cycles ->+        error $ unlines $+        "Cycles in dependencies:" :+        map (unwords . map location . nodeLabels graph) cycles++nodeLabels :: Gr a b -> [Graph.Node] -> [a]+nodeLabels graph =+   map (fromMaybe (error "node not found in graph") .+        Graph.lab graph)++subgraph :: Gr a b -> [Graph.Node] -> Gr a b+subgraph graph nodes =+   let nodeSet = Set.fromList nodes+       edges = do+           from <- nodes+           (to, lab) <- Graph.lsuc graph from+           guard $ Set.member from nodeSet && Set.member to nodeSet+           return (from,to,lab)+   in  Graph.mkGraph (zip nodes $ nodeLabels graph nodes) edges++getCycles :: Gr a b -> [[Graph.Node]]+getCycles =+   filter (\ x -> length x == 3) . scc++-- returns the first word for each line+rpmspecQuery :: Bool -> [String] -> FilePath -> IO (Maybe [B.ByteString])+rpmspecQuery lenient args spec = do+  (res, out, err) <- readProcessWithExitCode "rpmspec" (["-q"] ++ args ++ [spec]) ""+  unless (null err) $ hPutStrLn stderr err+  case res of+    ExitFailure _ -> if lenient then return Nothing else exitFailure+    ExitSuccess -> return $ Just $ map takeFirst $ B.lines (B.pack out)+  where+    -- ignore version bounds+    takeFirst :: B.ByteString -> B.ByteString+    takeFirst = head . B.words
README.md view
@@ -22,13 +22,19 @@ from the current set of checked out package sources.  ## Known problems-Given packages A, B, C, where C depends on B, and B depends on A,+1. Given packages A, B, C, where C depends on B, and B depends on A, and you call -    rpmbuild-order C.spec A.spec+    rpmbuild-order sort C.spec A.spec  then the output may be wrong if C does not have a direct dependency on A. Even if the order is correct, B is missing in the output and thus in this case the list of packages cannot be reliably used for a sequence of builds. +However `rpmbuild-order deps` and `rpmbuild-order rdeps` take+other neighbouring package directories into account.++2. repoquery is not used to resolve Requires or filelists for Provides.+So if a package BuildRequires: /usr/bin/sometool it will not be resolved.+This may get addressed in a future version.
− RpmbuildOrder.hs
@@ -1,324 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module Main where--import System.Console.GetOpt-          (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo)-import System.Exit (exitSuccess, exitFailure)-import qualified System.Environment as Env-import System.FilePath--import System.Directory (doesDirectoryExist, doesFileExist,-#if (defined(MIN_VERSION_directory) && MIN_VERSION_directory(1,2,5))-                         listDirectory-#else-                         getDirectoryContents-#endif-  )-import System.IO (hPutStrLn, stderr)-import System.Process (readProcess)--import Data.Graph.Inductive.Query.DFS (xdfsWith, topsort', scc, components)-import Data.Graph.Inductive.Tree (Gr)-import qualified Data.Graph.Inductive.Graph as Graph--import qualified Control.Monad.Exception.Synchronous as E-import qualified Control.Monad.Trans.Class as T--import qualified Data.Set as Set-import Control.Monad (guard, when, unless)-import Data.Maybe (catMaybes, fromMaybe, mapMaybe)-import Data.List (delete, intersperse, stripPrefix)-import Data.Version  (showVersion)--import Paths_rpmbuild_order (version)--#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,2))-#else-import Control.Applicative ((<$>))-#endif--#if (defined(MIN_VERSION_directory) && MIN_VERSION_directory(1,2,5))-#else-listDirectory :: FilePath -> IO [FilePath]-listDirectory path =-  filter f <$> getDirectoryContents path-  where f filename = filename /= "." && filename /= ".."-#endif--main :: IO ()-main =-  E.resolveT handleException $ do-  argv <- T.lift Env.getArgs-  let (opts, args, errors) = getOpt Permute options argv-  flags <--    E.ExceptionalT $ return $-        foldr (=<<)-           (return-            Flags {optHelp = False,-                   optVersion = False,-                   optVerbosity = False,-                   optFormat = package,-                   optParallel = Nothing,-                   optBranch = Nothing})-           opts-  if optHelp flags-    then T.lift $ help >> exitSuccess-    else-    if optVersion flags-    then T.lift $ putStrLn $ showVersion version-    else-      if length args < 2-      then T.lift $ help >> exitFailure-      else do-        let (com:pkgs) = args-        unless (null errors) $ E.throwT $ concat errors-        unless (com `elem` ["sort", "deps", "rdeps"]) $-          E.throwT $ "Unknown command " ++ com-        runCommand flags com $ map (removeSuffix "/") pkgs-  where-    help =-      Env.getProgName >>= \programName ->-        putStrLn-             (usageInfo ("Usage: " ++ programName ++-                         " [OPTIONS] [sort|deps|rdeps] PKG-SPEC-OR-DIR ...") options)--handleException :: String -> IO ()-handleException msg = do-   putStrLn $ "Aborted: " ++ msg-   exitFailure--findSpec :: Maybe FilePath -> FilePath -> IO (Maybe FilePath)-findSpec mdir file =-  if takeExtension file == ".spec"-    then checkFile file-    else do-    dirp <- doesDirectoryExist file-    if dirp-      then-      let dir = maybe file (file </>) mdir-          pkg = takeBaseName file in-        checkFile $ dir </> pkg ++ ".spec"-      else error $ "No spec file found for " ++ file-  where-    checkFile :: FilePath -> IO (Maybe FilePath)-    checkFile f = do-      e <- doesFileExist f-      if e-        then return $ Just f-        else return Nothing--data Flags =-   Flags {-      optHelp :: Bool,-      optVersion :: Bool,-      optVerbosity :: Bool,-      optFormat :: SourcePackage -> String,-      optParallel :: Maybe String,-      optBranch :: Maybe FilePath-   }--options :: [OptDescr (Flags -> E.Exceptional String Flags)]-options =-  [-    Option ['h'] ["help"]-      (NoArg (\flags -> return $ flags{optHelp = True}))-      "Show options"-  , Option ['V'] ["version"]-      (NoArg (\flags -> return $ flags{optVersion = True}))-      "Show version"-  , Option ['p'] ["parallel"]-      (OptArg-        (\mstr flags ->-            fmap (\cs -> flags{optParallel = Just cs})-            (E.Success (fromMaybe "" mstr)))-         "SEPARATOR")-      "Display independently buildable groups of packages, optionally with separator"-  , Option ['b'] ["branch"]-      (ReqArg-         (\str flags ->-            fmap (\mb -> flags{optBranch = mb})-            (E.Success (Just str)))-         "BRANCHDIR")-    "branch directory"-  , Option ['f'] ["format"]-      (ReqArg-         (\str flags ->-            fmap (\select -> flags{optFormat = select}) $-            case str of-               "package" -> E.Success package-               "spec" -> E.Success location-               "dir"  -> E.Success (takeDirectory . location)-               _ ->-                  E.Exception $-                  "unknown info type " ++ str)-         "KIND")-      "output format: 'package' (default), 'spec', or 'dir'"-  , Option ['v'] ["verbose"]-      (NoArg-         (\flags ->-            return flags{optVerbosity = True}))-      "verbose output"-  ]--type Package = String--data SourcePackage =-   SourcePackage {-      location :: FilePath,-      package :: Package,-      dependencies :: [Package]-   }-   deriving (Show, Eq)--type Command = String--runCommand :: Flags -> Command -> [Package] -> E.ExceptionalT String IO ()-runCommand flags "sort" pkgs = sortSpecFiles flags pkgs-runCommand flags "deps" pkgs = depsSpecFiles False flags pkgs-runCommand flags "rdeps" pkgs = depsSpecFiles True flags pkgs-runCommand _ _ _ = E.throwT "impossible happened"--createGraphNodes :: Flags -> [Package] -> [Package] ->-               E.ExceptionalT String IO (Gr SourcePackage (), [Graph.Node])-createGraphNodes flags pkgs subset = do-  unless (all (`elem` pkgs) subset) $-    E.throwT "Packages must be in the current directory"-  specPaths <- T.lift $ catMaybes <$> mapM (findSpec (optBranch flags)) (filter (/= fromMaybe "" (optParallel flags)) pkgs)-  let names = map takeBaseName specPaths-  provs <--     T.lift $-     mapM (readProvides (optVerbosity flags)) specPaths-  let resolves = zip names provs-  deps <--     T.lift $-     mapM (getDepsSrcResolved (optVerbosity flags) resolves) specPaths-  let spkgs = zipWith3 SourcePackage specPaths names deps-      graph = getBuildGraph spkgs-  checkForCycles graph-  let nodes = Graph.labNodes graph-      subnodes = mapMaybe (pkgNode nodes) subset-  return (graph, subnodes)-  where-    pkgNode [] _ = Nothing-    pkgNode ((i,l):ns) p = if p == package l then Just i else pkgNode ns p--sortSpecFiles :: Flags -> [Package] -> E.ExceptionalT String IO ()-sortSpecFiles flags pkgs = do-      (graph, _) <- createGraphNodes flags pkgs []-      T.lift $-         case optParallel flags of-           Just s ->-             mapM_ ((putStrLn . unwords . (if null s then id else intersperse s) . map (optFormat flags)) . topsort' . subgraph graph)-                 (components graph)-           Nothing ->-             mapM_ (putStrLn . optFormat flags) $ topsort' graph- -depsSpecFiles :: Bool -> Flags -> [Package] -> E.ExceptionalT String IO ()-depsSpecFiles rev flags pkgs = do-  allpkgs <- T.lift $ filter (\ f -> head f /= '.') <$> listDirectory "."-  (graph, nodes) <- createGraphNodes flags allpkgs pkgs-  let dir = if rev then Graph.suc' else Graph.pre'-  sortSpecFiles flags $ map package $ xdfsWith dir third nodes graph-  where-    third (_, _, c, _) = c--readProvides :: Bool -> FilePath -> IO [String]-readProvides verbose file = do-  when verbose $ hPutStrLn stderr file-  pkgs <- lines <$>-    rpmspec ["--rpms", "--qf=%{name}\n", "--define", "ghc_version any"] Nothing file-  let pkg = takeBaseName file-  return $ delete pkg pkgs--getDepsSrcResolved :: Bool -> [(String,[String])] -> FilePath -> IO [String]-getDepsSrcResolved verbose provides file =-  map (resolveBase provides) <$> do-      when verbose $ hPutStrLn stderr file-      -- ignore version bounds-      map (head . words) . lines <$>-        rpmspec ["--buildrequires", "--define", "ghc_version any"] Nothing file-  where-    resolveBase :: [(String,[String])] -> String -> String-    resolveBase provs br =-      case mapMaybe (\ (pkg,subs) -> if br `elem` subs then Just pkg else Nothing) provs of-        [] -> br-        [p] -> p-        ps -> error $ br ++ "is provided by: " ++ unwords ps--removeSuffix :: String -> String -> String-removeSuffix suffix orig =-  fromMaybe orig $ stripSuffix suffix orig-  where-    stripSuffix sf str = reverse <$> stripPrefix (reverse sf) (reverse str)--cmdStdIn :: String -> [String] -> String -> IO String-cmdStdIn c as inp = removeTrailingNewline <$> readProcess c as inp-  where-    removeTrailingNewline :: String -> String-    removeTrailingNewline "" = ""-    removeTrailingNewline str =-      if last str == '\n'-      then init str-      else str--cmd :: String -> [String] -> IO String-cmd c as = cmdStdIn c as ""--rpmspec :: [String] -> Maybe String -> FilePath -> IO String-rpmspec args mqf spec = do-  let qf = maybe [] (\ q -> ["--queryformat", q]) mqf-  cmd "rpmspec" (["-q"] ++ args ++ qf ++ [spec])--getDeps :: Gr SourcePackage () -> [(SourcePackage, [SourcePackage])]-getDeps gr =-    let c2dep :: Graph.Context SourcePackage () -> (SourcePackage, [SourcePackage])-        c2dep ctx =-           (Graph.lab' ctx,-            map (Graph.lab' . Graph.context gr) (Graph.pre gr . Graph.node' $ ctx))-    in  Graph.ufold (\ctx ds -> c2dep ctx : ds) [] gr--getBuildGraph :: [SourcePackage] -> Gr SourcePackage ()-getBuildGraph srcPkgs =-   let nodes = zip [0..] srcPkgs-       nodeDict = zip (map package srcPkgs) [0..]-       edges = do-          (srcNode,srcPkg) <- nodes-          dstNode <- mapMaybe (`lookup` nodeDict) (dependencies srcPkg)-          guard (dstNode /= srcNode)-          return (dstNode, srcNode, ())-   in Graph.mkGraph nodes edges--checkForCycles ::-   Monad m =>-   Gr SourcePackage () ->-   E.ExceptionalT String m ()-checkForCycles graph =-   case getCycles graph of-      [] -> return ()-      cycles ->-         E.throwT $ unlines $-         "Cycles in dependencies:" :-         map (unwords . map location . nodeLabels graph) cycles--nodeLabels :: Gr a b -> [Graph.Node] -> [a]-nodeLabels graph =-   map (fromMaybe (error "node not found in graph") .-        Graph.lab graph)--subgraph :: Gr a b -> [Graph.Node] -> Gr a b-subgraph graph nodes =-   let nodeSet = Set.fromList nodes-       edges = do-           from <- nodes-           (to, lab) <- Graph.lsuc graph from-           guard $ Set.member from nodeSet && Set.member to nodeSet-           return (from,to,lab)-   in  Graph.mkGraph (zip nodes $ nodeLabels graph nodes) edges--getCycles :: Gr a b -> [[Graph.Node]]-getCycles =-   filter (\case-              _:_:_ -> True-              _ -> False)-   . scc
TODO view
@@ -1,1 +1,2 @@-add leaf command+- add leaf command+- better graph output
rpmbuild-order.1 view
@@ -1,26 +1,28 @@-.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.6.-.TH RPMBUILD-ORDER "1" "August 2018" "rpmbuild-order 0.2" "User Commands"+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.11.+.TH RPMBUILD-ORDER "1" "October 2019" "rpmbuild-order 0.3" "User Commands" .SH NAME-rpmbuild-order \- manual page for rpmbuild-order 0.2+rpmbuild-order \- manual page for rpmbuild-order 0.3 .SH SYNOPSIS .B rpmbuild-order-[\fI\,OPTIONS\/\fR] [\fI\,sort|deps|rdeps\/\fR] \fI\,PKG-SPEC-OR-DIR \/\fR...+[\fI\,--version\/\fR] \fI\,COMMAND\/\fR .SH DESCRIPTION+Order packages by build dependencies+.IP+Sort package sources (spec files) in build dependency order+.SS "Available options:" .TP-\fB\-h\fR             \fB\-\-help\fR-Show options+\fB\-h\fR,\-\-help+Show this help text .TP-\fB\-V\fR             \fB\-\-version\fR+\fB\-\-version\fR Show version-.TP-\fB\-p[SEPARATOR]\fR  \fB\-\-parallel\fR[=\fI\,SEPARATOR\/\fR]-Display independently buildable groups of packages, optionally with separator+.SS "Available commands:" .TP-\fB\-b\fR BRANCHDIR   \fB\-\-branch\fR=\fI\,BRANCHDIR\/\fR-branch directory+sort+sort packages .TP-\fB\-f\fR KIND        \fB\-\-format\fR=\fI\,KIND\/\fR-output format: 'package' (default), 'spec', or 'dir'+deps+sort dependencies .TP-\fB\-v\fR             \fB\-\-verbose\fR-verbose output+rdeps+sort dependents
rpmbuild-order.cabal view
@@ -1,11 +1,11 @@ Name:             rpmbuild-order-Version:          0.2.1+Version:          0.3 License:          BSD3 License-File:     LICENSE Author:           Henning Thielemann <haskell@henning-thielemann.de> Maintainer:       Jens Petersen <petersen@redhat.com> Copyright:        2010-2018  Henning Thielemann,-                  2018 Jens Petersen <petersen@redhat.com>+                  2018-2019 Jens Petersen <petersen@redhat.com> Category:         Distribution Homepage:         https://github.com/juhp/rpmbuild-order Bug-reports:      https://github.com/juhp/rpmbuild-order/issues@@ -17,32 +17,32 @@   The code originates from cabal-sort by Henning Thielemann.   It can also output the ordered dependencies or reverse depends   for one or more packages, provided all the packages are checked out-  in neighbouring directories. This is also useful to see what packages+  in neighboring directories. This is also useful to see what packages   are affected when a low-level package changes. Tested-with:       GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4,                    GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2,-                   GHC == 8.4.3+                   GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.1 Cabal-Version:     >=1.6 Build-Type:        Simple-Extra-source-files: README.md TODO ChangeLog rpmbuild-order.1+Extra-source-files: README.md TODO ChangeLog.md rpmbuild-order.1  source-repository head   type:     git   location: https://github.com/juhp/rpmbuild-order  Executable rpmbuild-order-  Build-Depends:-    base < 5,-    Cabal,-    containers,-    directory,-    explicit-exception,-    filepath,-    fgl, -    process,-    transformers+  Build-Depends: base < 5,+                 bytestring,+                 Cabal,+                 containers,+                 directory,+                 filepath,+                 fgl,+                 optparse-applicative,+                 process,+                 simple-cmd-args    GHC-Options:      -Wall-  Main-Is:          RpmbuildOrder.hs+  Main-Is:          Main.hs   Extensions:       CPP   Other-modules:    Paths_rpmbuild_order