Pull request for branch graph-depends-improvements
Hello,
Here are a few improvements to the graph-depends script. The most important improvements are:
* Removal of useless dependencies for the meta "all" package to many packages. This makes the dependency graphs a lot more readable.
* Drastic optimization of the execution speed: on a given configuration, it used to take 5 minutes 14 seconds to generate the graph, now it takes 33 seconds.
Best regards,
Thomas
The following changes since commit f1d44593a04ff3be981c8a3b01a502f0b18193ee:
docs/manual: small fixes and enhancements to adding generic packages (2012-12-30 22:39:13 +0100)
are available in the git repository at:
git://git.free-electrons.com/users/thomas-petazzoni/buildroot.git graph-depends-improvements
for you to fetch changes up to 99c288aaaa42b38f2c841abbf2db0fff1e228656:
graph-depends: remove support for "unknown" packages (2013-01-02 18:06:12 +0100)
----------------------------------------------------------------
Thomas Petazzoni (6):
graph-depends: remove redundant dependencies
graph-depends: use a separate TARGET_EXCEPTIONS variable
graph-depends: optimize execution speed
graph-depends: fix comment
graph-depends: update copyright
graph-depends: remove support for "unknown" packages
support/scripts/graph-depends | 169 ++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 61 deletions(-)
Thanks,
Signature · 1 line
Thomas Petazzoni
PATCH 1/6 graph-depends: remove redundant dependencies
When doing a full graph of the dependencies, graph-depends starts by doing a "make show-targets", which lists all the packages registered in the $(TARGETS) variable. This variable contains all packages that are enabled according to the .config file. Then, for each of those packages, we used to create a "all" -> "package" dependency, even if in fact most of some packages are already dependencies of other packages. This creates a needlessly complex dependency graph.
This patch modifies graph-depends so that it filters out the unneeded "all" -> "package" dependencies when "package" is already the dependency of another package.
For example, if you have a configuration with libpng (which selects zlib), "make show-targets" displays "libpng zlib", so graph-depends used to create the following dependencies: (all -> libpng, all -> zlib, libpng -> zlib). However, the (all -> zlib) dependency is not really needed, as zlib is already the dependency of libpng. Those dependencies are now filtered out.
---
For an example of the difference on a bigger configuration, see:
http://free-electrons.com/~thomas/pub/buildroot/graph-depends-before-redundant-removal.pdf
http://free-electrons.com/~thomas/pub/buildroot/graph-depends-after-redundant-removal.pdf
--- support/scripts/graph-depends | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
support/scripts/graph-depends +33 -0
@@ -119,6 +119,37 @@ def get_all_depends(pkg): 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 + # In full mode, start with the result of get_targets() to get the main # targets and then use get_all_depends() for each individual target. if mode == FULL_MODE:
@@ -144,6 +175,8 @@ if mode == FULL_MODE: elif mode == PKG_MODE: dependencies = get_all_depends(rootpkg) +dependencies = remove_redundant_deps(dependencies) + # Start printing the graph data print "digraph G {"
Signature · 1 line
1.7.9.5
PATCH 2/6 graph-depends: use a separate TARGET_EXCEPTIONS variable
In reply to Thomas PetazzoniIn preparation for more graph-depends improvements, use a TARGET_EXCEPTIONS list to list all the targets that should be ignored while building the dependency graph.
--- support/scripts/graph-depends | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
support/scripts/graph-depends +10 -6
@@ -150,6 +150,15 @@ def remove_redundant_deps(deps): 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 each individual target. if mode == FULL_MODE:
@@ -158,12 +167,7 @@ if mode == FULL_MODE: allpkgs.append('all') for tg in targets: # Skip uninteresting targets - if tg == 'target-generic-issue' or \ - tg == 'target-generic-getty-busybox' or \ - tg == 'target-generic-do-remount-rw' or \ - tg == 'target-finalize' or \ - tg == 'erase-fakeroots' or \ - tg == 'target-generic-hostname': + if tg in TARGET_EXCEPTIONS: continue dependencies.append(('all', tg)) deps = get_all_depends(tg)
Signature · 1 line
1.7.9.5
PATCH 3/6 graph-depends: optimize execution speed
In reply to Thomas PetazzoniUntil now, graph-depends was calling "make <pkg>-show-depends" individually for eack package, which was very slow. Now, it calls "make <pkg1>-show-depends <pkg2>-show-depends ... <pkgN>-show-depends" for all packages it knows, and then does that recursively. It reduces the number of make invocations to the deepest dependency chain in the current configuration, instead of having a number of make invocations equal to the number of enabled packages.
For a configuration with xvkbd enabled (which brings a significant number of X.org dependencies) and a tar root filesystem, the time to execute graph-depends was:
real 5m14.944s
user 4m53.590s
sys 0m14.069s
After our optimizations, it is now:
real 0m33.096s
user 0m30.878s
sys 0m1.472s
---
Note that the script becomes a bit tricky. It would be nice to have some careful testing (beware: output of the script before and after the optimization cannot be directly compared, as dependencies are not examined in the same order) or even better careful review.
--- support/scripts/graph-depends | 116 +++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 45 deletions(-)
support/scripts/graph-depends +71 -45
@@ -63,52 +63,76 @@ def get_targets(): return output.split(' ') # Execute the "make <pkg>-show-depends" command to get the list of -# dependencies of a given package, and return the list of dependencies -# formatted as a Python list. -def get_depends(pkg): - sys.stderr.write("Getting dependencies for %s\n" % pkg) - cmd = ["make", "-s", "%s-show-depends" % pkg] +# 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].strip() + output = p.communicate()[0] if p.returncode != 0: - return None - if output == '': - return [] - return output.split(' ') + 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 -# package. The dependencies are built in a list called 'dependencies', -# which contains tuples of the form (pkg1 -> -# pkg2_on_which_pkg1_depends) and the function finally returns this -# list. -def get_all_depends(pkg): +# 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 = [] - # We already have the dependencies for this package - if pkg in allpkgs: - return - allpkgs.append(pkg) - depends = get_depends(pkg) - - # We couldn't get the dependencies of this package, because it - # doesn't use the generic or autotools infrastructure. Add it to - # unknownpkgs so that it is later rendered in red color to warn - # the user. - if depends == None: - unknownpkgs.append(pkg) - return - - # This package has no dependency. - if depends == []: - return - - # Add dependencies to the list of dependencies - for dep in depends: - dependencies.append((pkg, dep)) - - # Recurse into the dependencies - for dep in depends: - newdeps = get_all_depends(dep) + # 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] + + # We couldn't get the dependencies of this package, because it + # doesn't use the generic or autotools infrastructure. Add it to + # unknownpkgs so that it is later rendered in red color to warn + # the user. + if pkg_deps == None: + unknownpkgs.append(pkg) + continue + + # 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
@@ -160,24 +184,26 @@ TARGET_EXCEPTIONS = [ ] # In full mode, start with the result of get_targets() to get the main -# targets and then use get_all_depends() for each individual target. +# 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)) - deps = get_all_depends(tg) - if deps != None: - dependencies += deps + 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 = get_all_depends([rootpkg]) dependencies = remove_redundant_deps(dependencies)
Signature · 1 line
1.7.9.5
PATCH 4/6 graph-depends: fix comment
In reply to Thomas PetazzoniSince 9bc7b1d4ae694b818f941410d1ff59316a2bba6e, all X.org .mk files are parsed unconditionally, even if BR2_PACKAGE_XORG7 is disabled.
--- support/scripts/graph-depends | 5 ----- 1 file changed, 5 deletions(-)
support/scripts/graph-depends +0 -5
@@ -17,11 +17,6 @@ # dependencies as they are with the current Buildroot # configuration. # -# * The X.org package definitions are only included when -# BR2_PACKAGE_XORG7 is enabled, so if this option is not enabled, -# it isn't possible to graph the dependencies of X.org stack -# components. -# # Copyright (C) 2010 Thomas Petazzoni <[email protected]> import sys
Signature · 1 line
1.7.9.5
PATCH 5/6 graph-depends: update copyright
In reply to Thomas Petazzoni--- support/scripts/graph-depends | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
support/scripts/graph-depends +1 -1
@@ -17,7 +17,7 @@ # dependencies as they are with the current Buildroot # configuration. # -# Copyright (C) 2010 Thomas Petazzoni <[email protected]> +# Copyright (C) 2010-2013 Thomas Petazzoni <[email protected]> import sys import subprocess
Signature · 1 line
1.7.9.5
PATCH 6/6 graph-depends: remove support for "unknown" packages
In reply to Thomas PetazzoniThe "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.
--- support/scripts/graph-depends | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-)
support/scripts/graph-depends +1 -12
@@ -41,7 +41,6 @@ else: sys.exit(1) allpkgs = [] -unknownpkgs = [] # Execute the "make show-targets" command to get the list of the main # Buildroot TARGETS and return it formatted as a Python list. This
@@ -109,14 +108,6 @@ def get_all_depends(pkgs): for pkg in filtered_pkgs: pkg_deps = depends[pkg] - # We couldn't get the dependencies of this package, because it - # doesn't use the generic or autotools infrastructure. Add it to - # unknownpkgs so that it is later rendered in red color to warn - # the user. - if pkg_deps == None: - unknownpkgs.append(pkg) - continue - # This package has no dependency. if pkg_deps == []: continue
@@ -219,9 +210,7 @@ for pkg in allpkgs: print "%s [label = \"%s\"]" % (pkg_node_name(pkg), pkg) - if pkg in unknownpkgs: - print "%s [color=red,style=filled]" % pkg_node_name(pkg) - elif mode == PKG_MODE and pkg == rootpkg: + 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)
Signature · 1 line
1.7.9.5
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Thomas" == Thomas Petazzoni <[email protected]> writes:
Thomas> Hello,
Thomas> Here are a few improvements to the graph-depends script. The most Thomas> important improvements are:
Thomas> * Removal of useless dependencies for the meta "all" package to many
Thomas> packages. This makes the dependency graphs a lot more readable.
Thomas> * Drastic optimization of the execution speed: on a given
Thomas> configuration, it used to take 5 minutes 14 seconds to generate the
Thomas> graph, now it takes 33 seconds.
Nice! Committed, thanks.
Signature · 1 line
Bye, Peter Korsgaard
Thomas, All,
Quoted 9 lines · On Wednesday 02 January 2013 Thomas Petazzoni wrote:
Here are a few improvements to the graph-depends script. The most important improvements are:
* Removal of useless dependencies for the meta "all" package to many packages. This makes the dependency graphs a lot more readable.
* Drastic optimization of the execution speed: on a given configuration, it used to take 5 minutes 14 seconds to generate the graph, now it takes 33 seconds.
It is indeed much faster now, but is broken here (with the attached defconfig):
$ ../br.package-create-users/support/scripts/graph-depends
Getting targets
Getting dependencies for ['argp-standalone', 'avahi', 'busybox',
'cifs-utils', 'connman', 'dbus', 'dropbear', 'expat', 'gmp', 'gnutls',
'htop', 'hwdata', 'iptables', 'kexec', 'kmod', 'libdaemon', 'libffi',
'libglib2', 'libusb-compat', 'libusb', 'lshw', 'ncurses', 'nettle',
'ntfs-3g', 'ntp', 'openssl', 'readline', 'rng-tools', 'tcl', 'tvheadend',
'udev', 'usb_modeswitch_data', 'usb_modeswitch', 'usbutils', 'util-linux',
'wpa_supplicant', 'zlib', 'target-root-passwd', 'rootfs-ext2',
'rootfs-tar']
Error getting dependencies ['argp-standalone', 'avahi', 'busybox',
'cifs-utils', 'connman', 'dbus', 'dropbear', 'expat', 'gmp', 'gnutls',
'htop', 'hwdata', 'iptables', 'kexec', 'kmod', 'libdaemon', 'libffi',
'libglib2', 'libusb-compat', 'libusb', 'lshw', 'ncurses', 'nettle',
'ntfs-3g', 'ntp', 'openssl', 'readline', 'rng-tools', 'tcl', 'tvheadend',
'udev', 'usb_modeswitch_data', 'usb_modeswitch', 'usbutils', 'util-linux',
'wpa_supplicant', 'zlib', 'target-root-passwd', 'rootfs-ext2',
'rootfs-tar']Does it require a specific Python version? Mine is:
$ python --version
Python 2.6.6git bisect gives as culprit:
Bisecting: 0 revisions left to test after this (roughly 0 steps) [4359685e145798f06d88eb2e5ba5979ba5de882e] graph-depends: optimize execution speed
I'll try to look at it tonight...
Regards,
Yann E. MORIN.
Signature · 47 lines
.-----------------.--------------------.------------------.--------------------. | Yann E. MORIN | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: | | +33 662 376 056 | Software Designer | \ / CAMPAIGN | ___ | | +33 223 225 172 `------------.-------: X AGAINST | \e/ There is no | | http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL | v conspiracy. | '------------------------------^-------^------------------^--------------------' -------------- next part -------------- BR2_arm=y BR2_DL_DIR="$(HOME)/dev/src" BR2_OPTIMIZE_3=y BR2_TOOLCHAIN_EXTERNAL=y BR2_TOOLCHAIN_EXTERNAL_CUSTOM=y BR2_TOOLCHAIN_EXTERNAL_PATH="/home/ymorin/x-tools/arm-hardrpi-linux-gnueabi" BR2_TOOLCHAIN_EXTERNAL_CUSTOM_PREFIX="arm-hardrpi-linux-gnueabi" BR2_TOOLCHAIN_EXTERNAL_CUSTOM_GLIBC=y BR2_TOOLCHAIN_EXTERNAL_CXX=y BR2_TARGET_GENERIC_HOSTNAME="tvheadend" BR2_TARGET_GENERIC_ROOT_PASSWD="root" BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_UDEV=y BR2_TARGET_GENERIC_GETTY_PORT="tty1" BR2_ROOTFS_POST_BUILD_SCRIPT="$(BASE_DIR)/../rpi-modules-install.sh" BR2_PACKAGE_KEXEC=y BR2_PACKAGE_KEXEC_ZLIB=y BR2_PACKAGE_CIFS_UTILS=y BR2_PACKAGE_NTFS_3G=y BR2_PACKAGE_HWDATA=y BR2_PACKAGE_LSHW=y BR2_PACKAGE_RNG_TOOLS=y BR2_PACKAGE_USB_MODESWITCH_DATA=y BR2_PACKAGE_USBUTILS=y BR2_PACKAGE_USBUTILS_ZLIB=y BR2_PACKAGE_AVAHI=y BR2_PACKAGE_AVAHI_DAEMON=y BR2_PACKAGE_CONNMAN=y BR2_PACKAGE_CONNMAN_WIFI=y BR2_PACKAGE_CONNMAN_LOOPBACK=y BR2_PACKAGE_CONNMAN_NTPD=y BR2_PACKAGE_CONNMAN_CLIENT=y BR2_PACKAGE_DROPBEAR=y # BR2_PACKAGE_DROPBEAR_SMALL is not set BR2_PACKAGE_NTP=y BR2_PACKAGE_NTP_NTPDATE=y BR2_PACKAGE_NTP_NTPQ=y BR2_PACKAGE_NTP_TICKADJ=y BR2_PACKAGE_TVHEADEND=y BR2_PACKAGE_HTOP=y BR2_TARGET_ROOTFS_EXT2=y
Thomas, All,
Quoted 6 lines · On Wednesday 02 January 2013 Yann E. MORIN wrote:
Quoted 3 lines
* Drastic optimization of the execution speed: on a given configuration, it used to take 5 minutes 14 seconds to generate the graph, now it takes 33 seconds.
It is indeed much faster now, but is broken here (with the attached defconfig):
[--SNIP--]
Quoted 2 lines
Error getting dependencies [...
'target-root-passwd'
This should go in the exclusion list. I'll cook a patch.
Regards,
Yann E. MORIN.
Signature · 6 lines
.-----------------.--------------------.------------------.--------------------. | Yann E. MORIN | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: | | +33 662 376 056 | Software Designer | \ / CAMPAIGN | ___ | | +33 223 225 172 `------------.-------: X AGAINST | \e/ There is no | | http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL | v conspiracy. | '------------------------------^-------^------------------^--------------------'
PATCH 1/1 graph-depends: add to exclusion list
Add the root-password internal target to the exclusion list.
Fixes failures like:
Getting dependencies for [... 'target-root-passwd' ...] Error getting dependencies [... 'target-root-passwd' ...]
Which is easily singled out with:
$ make target-root-passwd-show-depends
make[1]: *** No rule to make target `target-root-passwd-show-depends'.--- support/scripts/graph-depends | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
support/scripts/graph-depends +1 -0
@@ -167,6 +167,7 @@ TARGET_EXCEPTIONS = [ "target-finalize", "erase-fakeroots", "target-generic-hostname", + "target-root-passwd", ] # In full mode, start with the result of get_targets() to get the main
Signature · 1 line
1.7.2.5
PATCH 1/1 graph-depends: add to exclusion list
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Yann" == Yann E MORIN <[email protected]> writes:
Yann> Add the root-password internal target to the exclusion list. Yann> Fixes failures like:
Yann> Getting dependencies for [... 'target-root-passwd' ...]
Yann> Error getting dependencies [... 'target-root-passwd' ...]
Yann> Which is easily singled out with:
Yann> $ make target-root-passwd-show-depends
Yann> make[1]: *** No rule to make target `target-root-passwd-show-depends'.
Committed, thanks.
Signature · 1 line
Bye, Peter Korsgaard
PATCH 1/1 graph-depends: add to exclusion list
In reply to Yann E. MORINDear Yann E. MORIN,
Quoted 6 lines · Yann E. MORIN
Add the root-password internal target to the exclusion list.
Fixes failures like:
Getting dependencies for [... 'target-root-passwd' ...] Error getting dependencies [... 'target-root-passwd' ...]
Quoted 7 lines
Which is easily singled out with:
$ make target-root-passwd-show-depends make[1]: *** No rule to make target `target-root-passwd-show-depends'.Signed-off-by "Yann E. MORIN" <[email protected]>Cc Thomas Petazzoni <[email protected]>
Thanks, indeed. I was in the train, and my master was not fully up-to-date with the latest root password related changes.
Ideally, the script should parse the make output a bit better and be capable of showing the error, but well...
Thomas
Signature · 4 lines
Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com