#!/usr/bin/perl
=head1 NAME
depsdot - Generate graph of ProteanOS source package dependencies
=cut
use strict;
use warnings;
no indirect;
use autovivification;
use English qw(-no_match_vars);
use LWP::Simple;
use POSIX qw(strftime);
my $SOURCE_URL = 'http://git.proteanos.com/depsdot/depsdot.git/';
my $BASE_URL = 'http://files.proteanos.com/pub/proteanos/feeds/dev/trunk';
my @DEP_FIELDS = qw(Build-Depends Depends Recommends Suggests Pre-Depends);
my @IGNORE_DEPS = qw(libc.6 opkbuild);
my @src_pkgs;
my %bin_src_map;
my %rdeps_graph;
my %non_leaf_src_pkgs;
my %deps_graph;
sub read_list
{
my ($list, $is_src) = @_;
foreach my $para (split(m{\n\n}, get($list))) {
my $package = undef;
my $source = undef;
my @deps;
foreach my $line (split(m{\n}, $para)) {
my ($name, $value) = split(m{\s*:\s*}, $line);
if ($name eq 'Package') {
$package = $value;
}
if ($name eq 'Source') {
$source = $value;
}
if (grep(m{^\Q$name\E$}, @DEP_FIELDS)) {
push(@deps, split(m{\s*,\s*}, $value));
}
}
next if not defined($source);
if ($is_src) {
push(@src_pkgs, $source);
} else {
$bin_src_map{$package}{$source} = 1;
}
map({ $_ =~ s{[\s(].*$}{}; } @deps); # Vim: )
foreach my $dep (@deps) {
next if grep(m{^\Q$dep\E$}, @IGNORE_DEPS);
$rdeps_graph{$dep}{$source} = 1;
}
}
return;
}
sub main
{
my @manifest;
@manifest = split(m{\n}, get($BASE_URL . '/Manifest'));
foreach my $aps (@manifest) {
read_list($BASE_URL . '/' . $aps . '/Packages',
($aps =~ m{^src/}));
}
foreach my $dep_bin (keys(%rdeps_graph)) {
foreach my $src (keys(%{$rdeps_graph{$dep_bin}})) {
my @dep_srcs = keys(%{$bin_src_map{$dep_bin}});
foreach my $dep_src (@dep_srcs) {
next if $dep_src eq $src;
$non_leaf_src_pkgs{$dep_src} = 1;
$deps_graph{$src}{$dep_src} = scalar(@dep_srcs);
}
}
}
STDOUT->print("/*\n * ProteanOS source package dependencies\n" .
" * Generated by depsdot, (C) 2019 Patrick McDermott, GPLv3+:" .
"\n<" . $SOURCE_URL . ">\n */\n\nstrict digraph deps {\n" .
"\toverlap = false;\n\tsplines = true;\n\tlayout = neato;\n" .
"\tlabel = <ProteanOS Source Package Dependencies as of " .
strftime('%Y-%m-%d at %H:%M:%S %Z', localtime()) . "
" .
"\n\t\tGray nodes are leaf packages (no reverse dependencies)" .
"
\n\t\tGray edges are dependencies on multiple source " .
"packages \n\t\t\tthat build binaries of the same name" .
"
\n\t\tMany build-time dependency fields are missing " .
"from the package archive and will be added over time>;\n" .
"\tgraph [fontname=\"FreeSans\"];\n" .
"\tnode [fontname=\"FreeSans\"];\n" .
"\tedge [fontname=\"FreeSans\"];\n\n\t/* Source packages */\n");
foreach my $src (sort(@src_pkgs)) {
my $attr = '';
if (not defined($non_leaf_src_pkgs{$src})) {
$attr = ' [style=filled,fillcolor="#808080"]';
}
STDOUT->print("\t\"" . $src . '"' . $attr . ";\n");
}
STDOUT->print("\n\t/* Dependencies */\n");
foreach my $src (sort(keys(%deps_graph))) {
foreach my $dep_src (sort(keys(%{$deps_graph{$src}}))) {
my $attr = '';
if ($deps_graph{$src}{$dep_src} gt 1) {
$attr = ' [color="#808080"]';
}
STDOUT->print("\t\"" . $src . '" -> "' . $dep_src .
'"' . $attr . ";\n");
}
}
STDOUT->print("}\n");
return 0;
}
exit(main());
__END__
=head1 SYNOPSIS
B
=head1 DESCRIPTION
B generates a DOT-language graph of build- and run-time dependencies
between ProteanOS source packages. Graphviz is recommended for rendering the
graph.
=head1 OUTPUT
B writes a DOT-language graph to standard output.
=head1 COPYRIGHT
Copyright (C) 2019 Patrick McDermott
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
=cut