Managing ports and their dependencies.

Chris BeHanna chris at behanna.org
Sat Dec 6 22:24:50 PST 2003


On Saturday 06 December 2003 13:15, Josef Karthauser wrote:
> I've got loads and loads of ports installed on my box, most of which I
> probably don't use, but I've no idea which ones these are.  Things like
> kde install lots of dependancies, and so I've no idea just by looking at
> the installed port names which ones I need and which ones I don't need.
>
> It should be possible to display ports and their dependancies as a
> connected graph, and then the ports that appear on the leaves of the
> tree would be the ones that I can consider removing.
>
> Does anyone have any code for doing something like this?  I thought
> I'd ask before I started knocking it up myself.

    I started out trying to do this with p5-GraphViz, but the result
was an unreadable mess.

    I'll attach what I have, which I intended to submit as a port
someday, but it's not ready for that just now.

--
Chris BeHanna
Software Engineer                   (Remove "bogus" before responding.)
chris at bogus.behanna.org
                 Turning coffee into software since 1990.

-------------- next part --------------
#!/usr/bin/perl
#
# $RCSfile: portgraph,v $
#
# Copyright 2002 Christopher G. BeHanna, all rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
# 
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY CHRISTOPHER G. BEHANNA ("THE AUTHOR") "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE THE AUTHOR BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# $Author: behanna $
# $Date: 2003/03/09 07:31:59 $
# $Revision: 1.1 $
# $Source: /raid5/chris_cvs/personal/utils/portgraph/portgraph,v $
#
# Analyze FreeBSD installed ports dependencies.
#
 
use POSIX;
use GraphViz;
use strict;

my $usage = "portsgraph [-g] [dbdir]\n" .
            "\t-g  Generate image of dependency graph, named portdeps.png\n";

my $graphics = "false";
my $pngfile  = "portdeps.png";

if (scalar(@ARGV) < 1 or $ARGV[0] =~ "/\-help/") {
  die "$usage\n";
}

if ($ARGV[0] eq "-g") {
  $graphics = "true";
  shift;
}

#
# Yes, we grovel through all the +REQUIRES files.  It would be faster to take
# advantage of portupgrade's pkgdb, but portupgrade doesn't have a "show me only
# the immediate dependencies rather than all the recursive dependencies" mode.
#
my $dbdir = $ARGV[0] || "/var/db/pkg";

my %node = (
            'name'    => undef,
            'deps'    => undef,
            'parents' => undef,
            'seen'    => undef
          );

my $blanks   = "                                                           ";
my %depshash = ();

my $dbdirp    = POSIX::opendir($dbdir) or die "Couldn't open $dbdir: $!\n";
my @portslist = POSIX::readdir($dbdirp);
POSIX::closedir($dbdirp);

my $line = "";
my $port = "";

for my $i (@portslist) {
    $port = $i;
    $port =~ s/\-[0-9].*$//;

    my $reqfile = "$dbdir/$i/+REQUIRED_BY";

    if ( -e "$reqfile" ) {
        open(REQFILE, $reqfile) or die "Couldn't open $reqfile: $!\n";

        while ($line = <REQFILE>) {
            chomp($line);
            $line =~ s/\-[0-9].*$//;
            push(@{$depshash{$line}}, $port);
        }

        close( REQFILE );
    }
}

#
# Now we have a hash of arrays.  Each key is a port, and each value is a
# list of that port's dependencies.
#

if (1 eq 0) {
    for my $i (keys %depshash) {
      print "$i depends upon:\n";

      for my $j (@{$depshash{$i}}) {
        print "\t$j\n";
      }
    }
}

#
# If we're generating a PNG file, build it and get out.
#

if ($graphics eq "true") {
  my $graph = GraphViz->new('nooverlap'   => 1,
                            'directed'    => 1,
                            'concentrate' => 1);

  for my $i (keys %depshash) {
    $graph->add_node($i);
  }

  for my $i (keys %depshash) {
    for my $j (@{$depshash{$i}}) {
      $graph->add_edge($i          => $j,
                       'color'     => 'black',
                       'dir'       => 'forward',
                       'arrowhead' => 'normal');
    }
  }

  open(PNGFILE, ">$pngfile") or die "$pngfile: $!\n";
  binmode(\*PNGFILE);

  $graph->as_png(\*PNGFILE);

  close( PNGFILE );

  exit 0;
}

#
# Build a dependency tree.  Start by making a node for each project.
#

my %deptree = ();

for my $i (keys %depshash) {
  %{$deptree{$i}} = %node;
  $deptree{$i}{'name'} = $i;
}

#
# Now, for each dependency listed in the original hash of arrays, insert the
# reference to that dependency's node in the port's dependency list.
#

for my $i (keys %deptree) {
  for my $j (@{$depshash{$i}}) {
    if (not defined $deptree{$i}{'deps'}) {
      $deptree{$i}{'deps'} = {};
    }

    $deptree{$i}{'deps'}->{$j} = $deptree{$j};

    if (not defined $deptree{$j}{'parents'}) {
      $deptree{$j}{'parents'} = {};
    }

    $deptree{$j}{'parents'}->{$i} = $deptree{$i};
  }
}

#
# Identify the root(s).  These are nodes that have no parents.
#

my @roots = ();

for my $i (keys %deptree) {
  if (not defined $deptree{$i}{'parents'}) {
    push(@roots, ($deptree{$i}));
  }
}

#
# Print the tree.
#
my $indent = 0;

if ($graphics eq "false") {

  for my $i (@roots) {
    printdeps($i);
  }
}


#####
#
# sub printdeps
#
# Param:  reference to a node.
#
#####

sub printdeps {
  my $root = shift;

  $indent+=2;

#  if (defined $root->{'seen'}) {
#    print substr($blanks, 0, $indent) .
#          "Cycle detected for node $root->{'name'}!\n";
#    $indent--;
#    return;
#  }

  print substr($blanks, 0, $indent) . "$root->{'name'}\n";
  $root->{'seen'} = "true";

  for my $i (keys %{$root->{'deps'}}) {
    printdeps($root->{'deps'}->{$i});
  }

  $indent-=2;
}


More information about the freebsd-ports mailing list