hxtools/sadmin/rpmdep.pl

266 lines
5.5 KiB
Perl
Executable File

#!/usr/bin/perl
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2005-2007 Jan Engelhardt
#
# rpmdep.pl
# Generate Graphviz tree from RPM dependencies
#
# Run `rpmdep.pl --help`.
use Data::Dumper;
use Getopt::Long;
use strict;
our $FOLEVEL = 4;
&main();
sub main ()
{
local *FH;
my($start_file, $start_level) = (undef, 1);
my($stage1_out, $stage2_out, $stage3_out) = (undef, undef, undef);
my $stage4_out = "-";
my $w;
&GetOptions(
"fanout-height|fanout-width=i" => \$FOLEVEL,
"start-file=s" => \$start_file,
"start-level=i" => \$start_level,
"stage1-out=s" => \$stage1_out,
"stage2-out=s" => \$stage2_out,
"stage3-out=s" => \$stage3_out,
"stage4-out=s" => \$stage4_out,
"h|help" => \&help,
);
if ($start_file) {
$w = do $start_file;
}
if ($start_level == 1) {
$w = &stage1($stage1_out);
++$start_level;
}
if ($start_level == 2) {
&stage2($stage2_out, $w);
++$start_level;
}
if ($start_level == 3) {
&stage3($stage3_out, $w);
++$start_level;
}
if ($start_level == 4) {
&printout($stage4_out, $w);
}
return;
}
sub strip_vra ()
{
my $pkg = shift @_;
$pkg =~ s/-[\w\.]+-[\w\.]+\.\w+$//; # version-release-arch
return $pkg
}
sub stage1 ()
{
print STDERR "# Stage 1: Finding dependencies\n";
my $stage1_out = shift @_;
my @pkglist = `rpm -qa --qf='\%{NAME}\\n'`;
my $pkghash = {};
for (my $count = 0; $count <= $#pkglist; ++$count) {
my $pkg = $pkglist[$count];
my %h;
chomp $pkg;
printf STDERR "#\t(%u/%u) $pkg\n", $count + 1, $#pkglist + 1;
foreach my $src (`rpm --test -e "$pkg" 2>&1`) {
if (substr($src, 0, 1) ne "\t") {
next;
}
chomp $src;
my($ess) = ($src =~ / is needed by \(installed\) (.*)/);
$h{&strip_vra($ess)} = 1;
}
%{$pkghash->{$pkg}} = %h;
}
if ($stage1_out ne "") {
local *FH;
open(FH, "> $stage1_out") ||
warn "Could not open $stage1_out: $!\n";
print FH Dumper($pkghash);
close FH;
}
return $pkghash;
}
sub count_deps ()
{
my $pkghash = shift @_;
my $ret = 0;
foreach my $pkg (keys %$pkghash) {
$ret += scalar keys %{$pkghash->{$pkg}};
}
return $ret;
}
sub stage2 ()
{
print STDERR "# Stage 2: Reduce visible links\n";
my $stage2_out = shift @_;
my $pkghash = shift @_;
my $prevtotal;
print STDERR "#\tinput: ", &count_deps($pkghash), " dependencies\n";
foreach my $firstpkg (keys %$pkghash) {
my $href = $pkghash->{$firstpkg};
foreach my $firstdep (keys %$href) {
foreach my $scndpkg (keys %$pkghash) {
if ($firstpkg eq $scndpkg) {
next;
}
if ($pkghash->{$scndpkg}->{$firstdep}) {
# print STDERR "#\t$firstdep->$firstpkg->$scndpkg, DELETE $firstdep->$scndpkg\n";
delete $pkghash->{$scndpkg}->{$firstdep};
}
}
}
}
print STDERR "#\toutput: ", &count_deps($pkghash), " dependencies\n";
if ($stage2_out ne "") {
local *FH;
open(FH, "> $stage2_out") ||
warn "Could not open $stage2_out: $!\n";
print FH Dumper($pkghash);
close FH;
}
return $pkghash;
}
sub alphanumeric ()
{
my($a_alpha, $a_num) = ($_[0] =~ /^(.*?)(?:<(\d+)>)?$/);
my($b_alpha, $b_num) = ($_[1] =~ /^(.*?)(?:<(\d+)>)?$/);
return $a_alpha cmp $b_alpha || $a_num <=> $b_num;
}
sub stage3 ()
{
print STDERR "# Stage 3: Link simplexer\n";
my $stage3_out = shift @_;
my $pkghash = shift @_;
my $counter = 1;
my $countmax = scalar keys %$pkghash;
my $newhash = {};
foreach my $pkg (keys %$pkghash) {
my $href = $pkghash->{$pkg};
my $iter = 1;
my $reloc = 0;
for (my $elem; ($elem = scalar keys %$href) > $FOLEVEL;
++$iter)
{
my $nphash = {};
printf STDERR "#\t(%u/%u) %s round %u, height %u\n",
$counter, $countmax, $pkg, $iter, $elem;
$reloc += $FOLEVEL - 1;
foreach my $ess (sort alphanumeric keys %$href) {
my $newpkg = "$pkg<".int($reloc++ / $FOLEVEL).">";
$nphash->{$newpkg}->{$ess} = 1;
delete $href->{$ess};
# print STDERR "#\t$ess => $newpkg\n";
}
foreach my $ess (keys %$nphash) {
$href->{$ess} = 1;
}
&merge_hash($newhash, $nphash);
}
++$counter;
}
&merge_hash($pkghash, $newhash);
if ($stage3_out ne "") {
local *FH;
open(FH, "> $stage3_out") ||
warn "Could not open $stage3_out: $!\n";
print FH Dumper($pkghash);
close FH;
}
return;
}
sub merge_hash ()
{
my $target = shift @_;
foreach my $source (@_) {
foreach my $key (keys %$source) {
$target->{$key} = $source->{$key};
}
}
return;
}
sub printout ()
{
my $stage4_out = shift @_;
my $pkghash = shift @_;
local *FH;
open(FH, "> $stage4_out") || warn "Could not open $stage4_out: $!\n";
print FH "digraph deps {\n";
print FH "\trankdir=LR;\n";
foreach my $pkg (sort alphanumeric keys %$pkghash) {
my $href = $pkghash->{$pkg};
if ($pkg =~ /<\d+>$/) {
# routing node
print FH "\t\"$pkg\" [shape=\"point\"];\n";
}
foreach my $ess (sort alphanumeric keys %$href) {
print FH "\t\"$ess\" -> \"$pkg\" [arrowhead=\"none\"];\n";
}
}
print FH "}\n";
return;
}
sub help ()
{
print <<"--EOF";
Syntax: $0 [options]
--fanout-height=N have at most N packages per level
--start-file=FILE source FILE for internal data
--start-level=N start at stage N
--stage1-out=FILE save post-stage1 data to FILE
--stage2-out=FILE save post-stage2 data to FILE
--stage3-out=FILE save post-stage3 data to FILE
--stage4-out=FILE output Graphviz data to FILE (default: stdout)
Common use (requires graphviz):
rpmdep.pl | dot -T svg >output.svg
Example for fast regeneration of graph:
rpmdep.pl --stage3-out=foo --fanout-height=32 | dot ...
rpmdep.pl --start-file=foo --start-level=4 --fanout-height=2 | dot ...
--EOF
exit 0;
}