packages feed

rpmbuild-order 0.4.3.2 → 0.4.4

raw patch · 7 files changed

+127/−58 lines, 7 filesdep +graphvizdep ~basedep ~fglPVP ok

version bump matches the API change (PVP)

Dependencies added: graphviz

Dependency ranges changed: base, fgl

API changes (from Hackage documentation)

+ Distribution.RPM.Build.Graph: createGraph'''' :: Bool -> [String] -> [String] -> Bool -> Bool -> Bool -> Maybe FilePath -> [FilePath] -> IO PackageGraph
+ Distribution.RPM.Build.Graph: printGraph :: PackageGraph -> IO ()
+ Distribution.RPM.Build.Graph: renderGraph :: PackageGraph -> IO ()

Files

ChangeLog.md view
@@ -1,3 +1,10 @@+# 0.4.4 (2021-05-16)+- add 'render' command to display graph graphically with graphviz+  with an option to output dot format+- fix subcycle algorithm: now lists correct shortest path subcycles+- improve cycles output (#2)+- library Graph: add printGraph, renderGraph, createGraph''''+ # 0.4.3.2 (2021-02-03) - intercalate newlines between cycles - only list subcycles with over 2 packages
README.md view
@@ -4,20 +4,30 @@  # rpmbuild-order -This package originally based on code from [cabal-sort](http://hackage.haskell.org/package/cabal-sort), sorts rpm package spec files by build order.+This is a tool to sort RPM source packages in build dependency order.+This code is originally derived from+[cabal-sort](http://hackage.haskell.org/package/cabal-sort)+by Henning Thielemann. +## Usage+     $ rpmbuild-order --help+    :     $ rpmbuild-order sort mycore mylib myapp     mylib mycore myapp -The arguments passed can either be directories containing the package, or spec files.+The arguments passed can either be directories containing the package+or spec files.  If the dependency graph has cycles then an error will be output with-a list of cycles and any subcycles.+a list of cycles and any shortest path subcycles.  Using the rpmbuild-order `deps` and `rdeps` commands the ordered dependencies and reverse dependencies of a package can be obtained-from the current set of checked out package sources.+within the current set of checked out package sources.++The `render` command displays a graph of package dependencies+using graphviz and X11 or optionally instead prints the dot format to stdout.  ## Library As of version 0.4, a library is also provided.
TODO view
@@ -1,11 +1,6 @@ - try topograph- - use Set?- * warn about uninstalled (specific?) macros packages- - handle duplicate packages?- - (r)deps: optimize using actual deps- - option to somehow display subpackage deps for cycles
rpmbuild-order.cabal view
@@ -1,9 +1,9 @@ Name:             rpmbuild-order-Version:          0.4.3.2+Version:          0.4.4 License:          BSD3 License-File:     LICENSE-Author:           Henning Thielemann <haskell@henning-thielemann.de>,-                  Jens Petersen <petersen@redhat.com>+Author:           Jens Petersen <petersen@redhat.com>,+                  Henning Thielemann <haskell@henning-thielemann.de> Maintainer:       Jens Petersen <petersen@redhat.com> Copyright:        2010-2018  Henning Thielemann,                   2018-2021 Jens Petersen <petersen@redhat.com>@@ -12,21 +12,21 @@ Bug-reports:      https://github.com/juhp/rpmbuild-order/issues Synopsis:         Order RPM packages by dependencies Description:-  The rpmbuild-order tool orders RPM packages by dependencies, so that they can-  be built in the correct order. It does this by reading RPM package spec files-  and then topologically sorts them according to their dependencies.-  The code evolved 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 neighboring directories. This is also useful to see what packages-  are affected when a low-level package changes. It also has support-  for setting RPM options for bcond etc, which can affect dependencies.+  The rpmbuild-order tool sorts source RPM packages by build dependencies,+  so that they can be built in a correct order. It does this by reading RPM+  package spec files and then topologically sorts them according to their+  build dependencies. The code evolved from cabal-sort by Henning Thielemann.+  It can also order the dependencies or reverse depends of one or more packages+  among the packages checked out in neighboring directories (which can be+  useful to see what packages are affected when a low-level package changes).+  It also has support for setting RPM options for bcond etc,+  which can affect dependencies. It can also output dependency graphs.    Since version 0.4, a library API is also provided. 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.4, GHC == 8.6.5, GHC == 8.8.4,-                   GHC == 8.10.2+                   GHC == 8.10.4, GHC == 9.0.1 Cabal-Version:     1.18 Build-Type:        Simple Extra-doc-files:    README.md TODO ChangeLog.md@@ -48,7 +48,6 @@                  optparse-applicative,                  rpmbuild-order,                  simple-cmd-args-   Main-Is:          src/Main.hs   Other-modules:    Paths_rpmbuild_order   GHC-Options:         -Wall@@ -74,7 +73,8 @@                  directory,                  extra >= 1.6.4,                  filepath,-                 fgl,+                 fgl >= 5.5.4,+                 graphviz,                  process   HS-Source-Dirs:      src   GHC-Options:         -Wall@@ -106,3 +106,4 @@                  , rpmbuild-order                  , simple-cmd                  , unix+    build-tools:   rpmbuild-order
src/Distribution/RPM/Build/Graph.hs view
@@ -18,13 +18,16 @@    createGraph',    createGraph'',    createGraph''',+   createGraph'''',    dependencyNodes,    subgraph',    packageLayers,    lowestLayer,    lowestLayer',    packageLeaves,-   separatePackages+   separatePackages,+   printGraph,+   renderGraph   ) where  import qualified Data.CaseInsensitive as CI@@ -36,9 +39,10 @@ #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif-import Control.Monad (guard, when, unless)+import Control.Monad (forM_, guard, when, unless) import Data.Maybe (catMaybes, fromMaybe, mapMaybe)-import Data.List.Extra (dropSuffix, find, intercalate, nubOrdOn, sort, sortOn)+import Data.List.Extra (dropSuffix, find, intercalate, nub, nubOrdOn, sort, sortOn)+import Data.GraphViz import System.Directory (doesDirectoryExist, doesFileExist, #if MIN_VERSION_directory(1,2,5)                          listDirectory@@ -146,13 +150,30 @@                -> Maybe FilePath -- ^ look for spec file in a subdirectory                -> [FilePath] -- ^ package paths (directories or spec filepaths)                -> IO PackageGraph -- ^ dependency graph labelled by package paths-createGraph''' ignoredBRs rpmopts verbose lenient rev mdir paths = do+createGraph''' = createGraph'''' True++-- | Create a directed dependency graph for a set of packages+--+-- Like createGraph''' but can disable check for cycles+--+-- @since 0.4.4+createGraph'''' :: Bool -- ^ check for cycles+                -> [String] -- ^ ignored BuildRequires+                -> [String] -- ^ rpmspec options+                -> Bool -- ^ verbose+                -> Bool -- ^ lenient (skip rpmspec failures)+                -> Bool -- ^ reverse dependency graph+                -> Maybe FilePath -- ^ look for spec file in a subdirectory+                -> [FilePath] -- ^ package paths (directories or spec filepaths)+                -> IO PackageGraph -- ^ dependency graph labelled by package paths+createGraph'''' checkcycles ignoredBRs rpmopts verbose lenient rev mdir paths = do   metadata <- catMaybes <$> mapM readSpecMetadata paths   let realpkgs = map fst3 metadata       deps = mapMaybe (getDepsSrcResolved metadata) realpkgs       spkgs = zipWith SourcePackage realpkgs deps       graph = getBuildGraph spkgs-  checkForCycles graph+  when checkcycles $+    checkForCycles graph   return graph   where     readSpecMetadata :: FilePath -> IO (Maybe (FilePath,[String],[String]))@@ -191,10 +212,11 @@                     case specs of                       [spec] -> return $ Just spec                       _ -> if lenient then return Nothing-                           else error $ if null specs-                                        then "No spec file found in " ++ path-                                        else "More than one .spec file found in " ++ dir-              else error $ "No spec file found for " ++ path+                           else errorWithoutStackTrace $+                                if null specs+                                then "No spec file found in " ++ path+                                else "More than one .spec file found in " ++ dir+              else errorWithoutStackTrace $ "No spec file found for " ++ path           where             checkFile :: Bool -> FilePath -> IO (Maybe FilePath)             checkFile may f = do@@ -203,7 +225,7 @@                 then return $ Just f                 else return $ if may                               then Nothing-                              else error $ f ++ " not found"+                              else errorWithoutStackTrace $ f ++ " not found"              filesWithExtension :: FilePath -> String -> IO [FilePath]             filesWithExtension dir ext =@@ -242,33 +264,32 @@               return $ if rev                        then (dstNode, srcNode, ())                        else (srcNode, dstNode,  ())-       in G.mkGraph (map (fmap packagePath) nodes) edges+       in G.mkGraph (map (fmap packagePath) nodes) $ nub edges -    checkForCycles :: Monad m => PackageGraph -> m ()-    checkForCycles graph =-      unless (null cycles) $-        error $ unlines $-        "Cycles in dependencies:" :-        intercalate [""] (map (renderCycles . subcycles) cycles)+    checkForCycles :: PackageGraph -> IO ()+    checkForCycles graph = do+      let cycles = filter ((>= 2) . length) (scc graph)+      unless (null cycles) $ do+        let plural = if length cycles > 1 then "s" else ""+        errorWithoutStackTrace $ unlines $+          ("ordering not possible due to build dependency cycle" ++ plural ++ ":\n") : intercalate [""] (map (renderCycles . subcycles) cycles)       where-        cycles :: [[G.Node]]-        cycles =-           (filter ((>= 2) . length) . scc) graph-         -- shortest subcycle         subcycles :: [G.Node] -> ([FilePath],[[FilePath]])         subcycles [] = error "cyclic graph with no nodes!"-        subcycles cycle'@(n:ns) =-          let sg = G.emap (const (1 :: Int)) $ G.subgraph cycle' graph-              shorter = nubOrdOn sort $ sortOn length $ filter ((\l -> l < length ns && l > 2) . length) $ catMaybes $ mapAdjacent (\ i j-> sp j i sg) (cycle' ++ [n])+        subcycles cycle' =+          let shorter = nubOrdOn sort $ sortOn length $ filter ((< length cycle') . length) $ mapMaybe findSp $ G.labEdges sg           in (nodeLabels graph cycle', map (nodeLabels sg) shorter)+          where+            sg = G.emap (const (1 :: Int)) $ G.subgraph cycle' graph -        mapAdjacent :: (a -> a -> b) -> [a] -> [b]-        mapAdjacent f xs = zipWith f xs (tail xs)+            findSp (i,j,_) | G.hasEdge sg (i,j) = sp j i sg+                           | G.hasEdge sg (j,i) = sp i j sg+                           | otherwise = Nothing          renderCycles :: ([FilePath],[[FilePath]]) -> [String]         renderCycles (c,sc) =-          unwords c : if null sc then [] else "Subcycles: " : map unwords sc+          unwords c : if null sc then [] else "\nShortest path subcycles: " : map unwords sc      getDepsSrcResolved :: [(FilePath,[String],[String])] -> FilePath -> Maybe [FilePath]     getDepsSrcResolved metadata pkg =@@ -279,7 +300,7 @@           case mapMaybe (\ (p,provs,_) -> if br `elem` provs then Just p else Nothing) metadata of             [] -> br             [p] -> p-            ps -> error $ pkg ++ ": " ++ br ++ " is provided by: " ++ unwords ps+            ps -> errorWithoutStackTrace $ pkg ++ ": " ++ br ++ " is provided by: " ++ unwords ps          thd (_,_,c) = c @@ -333,3 +354,23 @@ separatePackages :: PackageGraph -> [FilePath] separatePackages graph =   map snd $ G.labNodes $ G.nfilter ((==0) . G.deg graph) graph++-- | Return graphviz dot format of graph+printGraph :: PackageGraph -> IO ()+printGraph g = do+  putStrLn "digraph {"+  forM_ (G.labNodes g) $ \ (n,l) -> do+    putStr $ show l+    putStrLn $ renderDeps $ map show $ mapMaybe (G.lab g . fst) $ G.lsuc g n+  putStrLn "}"+  where+    renderDeps :: [String] -> String+    renderDeps [] = ""+    renderDeps [d] = " -> " ++ d+    renderDeps ds = " -> {" ++ unwords ds ++ "}"++-- | Render graph with graphviz X11 preview+renderGraph :: PackageGraph -> IO ()+renderGraph graph =+  let g = G.emap (const ("" :: String)) graph+  in runGraphvizCanvas' (setDirectedness graphToDot quickParams g) Xlib
src/Main.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE CPP #-} +module Main (main) where+ import Control.Applicative ( #if !MIN_VERSION_simple_cmd_args(0,1,3)                             (<|>),@@ -51,6 +53,8 @@     leavesPackages <$> rpmOpts <*> verboseOpt <*> lenientOpt <*> subdirOpt <*> pkgArgs   , Subcommand "roots" "List lowest root packages" $     rootPackages <$> rpmOpts <*> verboseOpt <*> lenientOpt <*> subdirOpt <*> pkgArgs+  , Subcommand "render" "Show graph with graphviz" $+    renderPkgGraph <$> switchWith 'o' "output" "Print graph in dot format" <*> rpmOpts <*> verboseOpt <*> lenientOpt <*> subdirOpt <*> pkgArgs   ]   where     verboseOpt = switchWith 'v' "verbose" "Verbose output for debugging"@@ -73,7 +77,7 @@ depsPackages :: Bool -> [String] -> Bool-> [String] -> [String] -> Bool ->  Bool -> Maybe FilePath -> [FilePath] -> IO () depsPackages rev rpmopts verbose excludedPkgs ignoredBRs lenient parallel mdir pkgs = do   unlessM (and <$> mapM doesDirectoryExist pkgs) $-    error "Please use package directory paths"+    errorWithoutStackTrace "Please use package directory paths"   listDirectory "." >>=     -- filter out dotfiles     createGraph''' ignoredBRs rpmopts verbose lenient (not rev) mdir . filter ((/= '.') . head) . filter (`notElem` excludedPkgs) >>=@@ -115,3 +119,9 @@ rootPackages rpmopts verbose lenient mdir pkgs = do   graph <- createGraph'' rpmopts verbose lenient True mdir pkgs   mapM_ putStrLn $ lowestLayer graph++renderPkgGraph :: Bool -> [String] -> Bool -> Bool -> Maybe FilePath+               -> [FilePath] -> IO ()+renderPkgGraph dot rpmopts verbose lenient mdir pkgs =+  createGraph'''' False [] rpmopts verbose lenient True mdir pkgs >>=+  if dot then printGraph else renderGraph
test/Spec.hs view
@@ -1,18 +1,23 @@ import Test.Hspec import Control.Monad.Extra+import Data.Version.Extra --import Distribution.RPM.Build.Graph import Distribution.RPM.Build.Order import SimpleCmd import System.Posix.Files  main :: IO ()-main = setupSymlinks >> hspec spec+main = do+  setupSymlinks+  -- rpmspec < 4.15 does not support "--with"+  rpmver <- readVersion . last . words <$> cmd "rpmspec" ["--version"]+  hspec (spec rpmver)  pkg :: FilePath -> FilePath pkg = ("test/pkgs/" ++) -spec :: Spec-spec = do+spec :: Version -> Spec+spec rpmver = do   describe "sorting" $ do     it "sort A B" $       dependencySort [pkg "A", pkg "B"] >>=@@ -50,7 +55,8 @@       dependencySort [pkg "A", pkg "B/", pkg "D1.0"] >>=       (`shouldBe` [pkg "B/", pkg "D1.0", pkg "A"]) -    it "circular A B C boot" $+    when (rpmver > makeVersion [4,15]) $+      it "circular A B C boot" $       dependencySortRpmOpts ["--with=boot"] [pkg "A", pkg "B", pkg "C"] >>=       (`shouldBe` [pkg "C", pkg "B", pkg "A"]) @@ -64,7 +70,6 @@       leafPackages [pkg "A", pkg "B", pkg "D1.0"] >>=       (`shouldBe` [pkg "A", pkg "D1.0"]) -  -- NB hack: this requires it to be "cabal install"'ed already   describe "rpmbuild-order" $ do     it "sort A B" $       cmd "rpmbuild-order" ["sort", pkg "A", pkg "B"] >>=