#!/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