#!/usr/bin/python # Usage (the graphviz package must be installed in your distribution) # ./support/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 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 -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-securetty", "target-generic-issue", "target-generic-getty-busybox", "target-generic-do-remount-rw", "target-generic-dont-remount-rw", "target-finalize", "erase-fakeroots", "target-generic-hostname", "target-root-passwd", "target-post-image", "target-purgelocales", ] # 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 "}"