kumquat-buildroot/support/scripts/graph-depends
Thomas Petazzoni 1063539c53 graph-depends: remove support for "unknown" packages
The "unknown" packages mechanism was used to render packages that did
not implement the make <pkg>-show-depends target, i.e the packages
that were not yet converted to one of the package infrastructures.

Since now all packages have been converted, we can remove this
"unknown" packages feature.

Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Signed-off-by: Peter Korsgaard <jacmet@sunsite.dk>
2013-01-02 19:00:04 +01:00

219 lines
6.8 KiB
Python
Executable File

#!/usr/bin/python
# Usage (the graphviz package must be installed in your distribution)
# ./scripts/graph-depends [package-name] > test.dot
# dot -Tpdf test.dot -o test.pdf
#
# With no arguments, graph-depends will draw a complete graph of
# dependencies for the current configuration. With an argument,
# graph-depends will draw a graph of dependencies for the given
# package name.
#
# Limitations
#
# * Some packages have dependencies that depend on the Buildroot
# configuration. For example, many packages have a dependency on
# openssl if openssl has been enabled. This tool will graph the
# dependencies as they are with the current Buildroot
# configuration.
#
# Copyright (C) 2010-2013 Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
import sys
import subprocess
# In FULL_MODE, we draw the full dependency graph for all selected
# packages
FULL_MODE = 1
# In PKG_MODE, we only draw the dependency graph for a given package
PKG_MODE = 2
mode = 0
if len(sys.argv) == 1:
mode = FULL_MODE
elif len(sys.argv) == 2:
mode = PKG_MODE
rootpkg = sys.argv[1]
else:
print "Usage: graph-depends [package-name]"
sys.exit(1)
allpkgs = []
# Execute the "make show-targets" command to get the list of the main
# Buildroot TARGETS and return it formatted as a Python list. This
# list is used as the starting point for full dependency graphs
def get_targets():
sys.stderr.write("Getting targets\n")
cmd = ["make", "-s", "show-targets"]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = p.communicate()[0].strip()
if p.returncode != 0:
return None
if output == '':
return []
return output.split(' ')
# Execute the "make <pkg>-show-depends" command to get the list of
# dependencies of a given list of packages, and return the list of
# dependencies formatted as a Python dictionary.
def get_depends(pkgs):
sys.stderr.write("Getting dependencies for %s\n" % pkgs)
cmd = ["make", "-s" ]
for pkg in pkgs:
cmd.append("%s-show-depends" % pkg)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = p.communicate()[0]
if p.returncode != 0:
sys.stderr.write("Error getting dependencies %s\n" % pkgs)
sys.exit(1)
output = output.split("\n")
if len(output) != len(pkgs) + 1:
sys.stderr.write("Error getting dependencies\n")
sys.exit(1)
deps = {}
for i in range(0, len(pkgs)):
pkg = pkgs[i]
pkg_deps = output[i].split(" ")
if pkg_deps == ['']:
deps[pkg] = []
else:
deps[pkg] = pkg_deps
return deps
# Recursive function that builds the tree of dependencies for a given
# list of packages. The dependencies are built in a list called
# 'dependencies', which contains tuples of the form (pkg1 ->
# pkg2_on_which_pkg1_depends, pkg3 -> pkg4_on_which_pkg3_depends) and
# the function finally returns this list.
def get_all_depends(pkgs):
dependencies = []
# Filter the packages for which we already have the dependencies
filtered_pkgs = []
for pkg in pkgs:
if pkg in allpkgs:
continue
filtered_pkgs.append(pkg)
allpkgs.append(pkg)
if len(filtered_pkgs) == 0:
return []
depends = get_depends(filtered_pkgs)
deps = set()
for pkg in filtered_pkgs:
pkg_deps = depends[pkg]
# This package has no dependency.
if pkg_deps == []:
continue
# Add dependencies to the list of dependencies
for dep in pkg_deps:
dependencies.append((pkg, dep))
deps.add(dep)
if len(deps) != 0:
newdeps = get_all_depends(deps)
if newdeps != None:
dependencies += newdeps
return dependencies
# The Graphviz "dot" utility doesn't like dashes in node names. So for
# node names, we strip all dashes.
def pkg_node_name(pkg):
return pkg.replace("-","")
# Helper function for remove_redundant_deps(). This function tells
# whether package "pkg" is the dependency of another package that is
# not the special "all" package.
def has_redundant_deps(deps, pkg):
for dep in deps:
if dep[0] != "all" and dep[1] == pkg:
return True
return False
# This function removes redundant dependencies of the special "all"
# package. This "all" package is created to reflect the origin of the
# selection for all packages that are not themselves selected by any
# other package. So for example if you enable libpng, zlib is enabled
# as a dependency. But zlib being selected by libpng, it also appears
# as a dependency of the "all" package. This needlessly complicates
# the generated dependency graph. So when we have the dependency list
# (all -> zlib, all -> libpn, libpng -> zlib), we get rid of the 'all
# -> zlib' dependency, because zlib is already a dependency of a
# regular package.
def remove_redundant_deps(deps):
newdeps = []
for dep in deps:
if dep[0] != "all":
newdeps.append(dep)
continue
if not has_redundant_deps(deps, dep[1]):
newdeps.append(dep)
continue
sys.stderr.write("Removing redundant dep all -> %s\n" % dep[1])
return newdeps
TARGET_EXCEPTIONS = [
"target-generic-issue",
"target-generic-getty-busybox",
"target-generic-do-remount-rw",
"target-finalize",
"erase-fakeroots",
"target-generic-hostname",
]
# In full mode, start with the result of get_targets() to get the main
# targets and then use get_all_depends() for all targets
if mode == FULL_MODE:
targets = get_targets()
dependencies = []
allpkgs.append('all')
filtered_targets = []
for tg in targets:
# Skip uninteresting targets
if tg in TARGET_EXCEPTIONS:
continue
dependencies.append(('all', tg))
filtered_targets.append(tg)
deps = get_all_depends(filtered_targets)
if deps != None:
dependencies += deps
# In pkg mode, start directly with get_all_depends() on the requested
# package
elif mode == PKG_MODE:
dependencies = get_all_depends([rootpkg])
dependencies = remove_redundant_deps(dependencies)
# Start printing the graph data
print "digraph G {"
# First, the dependencies. Usage of set allows to remove duplicated
# dependencies in the graph
for dep in set(dependencies):
print "%s -> %s" % (pkg_node_name(dep[0]), pkg_node_name(dep[1]))
# Then, the node attributes: color, style and label.
for pkg in allpkgs:
if pkg == 'all':
print "all [label = \"ALL\"]"
print "all [color=lightblue,style=filled]"
continue
print "%s [label = \"%s\"]" % (pkg_node_name(pkg), pkg)
if mode == PKG_MODE and pkg == rootpkg:
print "%s [color=lightblue,style=filled]" % pkg_node_name(rootpkg)
else:
print "%s [color=grey,style=filled]" % pkg_node_name(pkg)
print "}"