module-deps.pl: extend dependency graph generation

Induce the module name from the library name. Report the libraries that can
not be mapped to a module. Make the resulting module dependency graph unique.
Add diagnostic options:

--help
--verbose
--version

Add convenience options (primary for caching):

--from-file
--to-file

Add output option

--output

Provide a manual page with hints how to hack on it.

Change-Id: Ib5c029c6ea197ca4f66fe6958ecbc3f78452c603
This commit is contained in:
David Ostrovsky
2013-03-09 16:08:47 +01:00
parent 503cde2a28
commit 0670c3f3da
2 changed files with 288 additions and 95 deletions

View File

@@ -407,6 +407,9 @@ check: dev-install subsequentcheck
dump-deps: dump-deps:
@$(SRCDIR)/bin/module-deps.pl $(GNUMAKE) $(SRCDIR)/Makefile.gbuild @$(SRCDIR)/bin/module-deps.pl $(GNUMAKE) $(SRCDIR)/Makefile.gbuild
dump-deps-png:
@$(SRCDIR)/bin/module-deps.pl $(GNUMAKE) $(SRCDIR)/Makefile.gbuild | dot -Tpng -o lo.png
subsequentcheck :| $(if $(filter-out subsequentcheck,$(MAKECMDGOALS)),dev-install) subsequentcheck :| $(if $(filter-out subsequentcheck,$(MAKECMDGOALS)),dev-install)
$(GNUMAKE) -j $(CHECK_PARALLELISM) $(GMAKE_OPTIONS) -f $(SRCDIR)/Makefile.gbuild $@ $(GNUMAKE) -j $(CHECK_PARALLELISM) $(GMAKE_OPTIONS) -f $(SRCDIR)/Makefile.gbuild $@

View File

@@ -1,18 +1,35 @@
#!/usr/bin/perl -w #!/usr/bin/perl
use strict; use strict;
use warnings;
use Getopt::Long qw(GetOptions VersionMessage);
use Pod::Usage;
my $gnumake; my $gnumake;
my $makefile_build; my $makefile_build;
my $verbose = 0;
my $from_file;
my $to_file;
my $graph_file;
sub logit($)
{
print STDERR shift if ($verbose);
}
sub read_deps() sub read_deps()
{ {
my $p; my $p;
my $to;
my $invalid_tolerance = 100; my $invalid_tolerance = 100;
my $line_count = 0; my $line_count = 0;
my %deps; my %deps;
if (defined $ENV{DEP_CACHE_FILE}) { if (defined $to_file)
open ($p, $ENV{DEP_CACHE_FILE}) || die "can't read deps from cache: $!"; {
open($to, ">$to_file") or die "can not open file for writing $to_file";
}
if (defined $from_file) {
open ($p, $from_file) || die "can't read deps from cache file: $!";
} else { } else {
open ($p, "ENABLE_PRINT_DEPS=1 $gnumake -n -f $makefile_build all|") || die "can't launch make: $!"; open ($p, "ENABLE_PRINT_DEPS=1 $gnumake -n -f $makefile_build all|") || die "can't launch make: $!";
} }
@@ -22,7 +39,8 @@ sub read_deps()
my $line = $_; my $line = $_;
$line_count++; $line_count++;
print STDERR '.' if ($line_count % 10 == 0); print STDERR '.' if ($line_count % 10 == 0);
# print STDERR $line; logit($line);
print $to $line if defined $to_file;
chomp ($line); chomp ($line);
if ($line =~ m/^LibraryDep:\s+(\S+) links against (.*)$/) { if ($line =~ m/^LibraryDep:\s+(\S+) links against (.*)$/) {
# if ($line =~ m/^LibraryDep:\s+(\S+)\s+links against/) { # if ($line =~ m/^LibraryDep:\s+(\S+)\s+links against/) {
@@ -84,7 +102,7 @@ sub clean_tree($)
} }
$tree{$target} = \%result; $tree{$target} = \%result;
# print "$target ($type): " . join (',', @clean_needs) . "\n"; logit("$target ($type): " . join (',', @clean_needs) . "\n");
} }
return \%tree; return \%tree;
} }
@@ -121,8 +139,8 @@ sub build_flat_dep_hash($$)
# useful debugging ... # useful debugging ...
if (defined $ENV{DEP_CACHE_FILE}) { if (defined $ENV{DEP_CACHE_FILE}) {
print "node '$name' has flat-deps: '" . join(',', keys %flat_deps) . "' " . logit("node '$name' has flat-deps: '" . join(',', keys %flat_deps) . "' " .
"vs. '" . join(',', @{$node->{deps}}) . "'\n"; "vs. '" . join(',', @{$node->{deps}}) . "'\n");
} }
} }
@@ -137,59 +155,231 @@ sub prune_redundant_deps($)
} }
} }
sub create_lib_module_map()
{
my %l2m;
for (glob("*/Library_*.mk"))
{
/(.*)\/Library_(.*)\.mk/;
# add module -> module
$l2m{$1} = $1;
# add lib -> module
$l2m{$2} = $1;
}
return \%l2m;
}
sub dump_graphviz($) sub dump_graphviz($)
{ {
my $tree = shift; my $tree = shift;
print "digraph LibreOffice {\n"; my $to;
if (defined($graph_file)) {
open ($to, ">$graph_file");
}
else
{
$to = \*STDOUT;
}
my $l2m = create_lib_module_map();
my %unknown_libs;
my %digraph;
print $to <<END;
digraph LibreOffice {
node [shape="Mrecord", color="#BBBBBB"]
node [fontname=Verdana, color="#BBBBBB", fontsize=10, height=0.02, width=0.02]
edge [color="#31CEF0", len=0.4]
edge [fontname=Arial, fontsize=10, fontcolor="#31CEF0"]
END
for my $name (sort keys %{$tree}) { for my $name (sort keys %{$tree}) {
my $result = $tree->{$name}; my $result = $tree->{$name};
if ($result->{type} eq 'CppunitTest' || if ($result->{type} eq 'CppunitTest' ||
($result->{type} eq 'Executable' && $result->{target} ne 'soffice_bin')) { ($result->{type} eq 'Executable' &&
$result->{target} ne 'soffice_bin')) {
next; # de-bloat the tree next; # de-bloat the tree
} }
# print STDERR "minimising deps for $result->{target}\n"; logit("minimising deps for $result->{target}\n");
my @newdeps; my @newdeps;
for my $dep (@{$result->{deps}}) { for my $dep (@{$result->{deps}}) {
my $print = 1; my $print = 1;
# is this implied by any other child ? # is this implied by any other child ?
# print STDERR "checking if '$dep' is redundant\n"; logit("checking if '$dep' is redundant\n");
for my $other_dep (@{$result->{deps}}) { for my $other_dep (@{$result->{deps}}) {
next if ($other_dep eq $dep); next if ($other_dep eq $dep);
if (has_child_dep($tree,$dep,$other_dep)) { if (has_child_dep($tree,$dep,$other_dep)) {
$print = 0; $print = 0;
# print STDERR "$dep is implied by $other_dep - ignoring\n"; logit("$dep is implied by $other_dep - ignoring\n");
} }
} }
print "$name -> $dep;\n" if ($print); if (!grep {/$name/} keys $l2m)
{
$unknown_libs{$name} = 1;
}
else
{
if ($print)
{
$name = $l2m->{$name};
$dep = $l2m->{$dep};
# two libraries from the same module depend on
# each other: hide it
if ($name eq $dep)
{
$print = 0;
}
# making digraph unique
if (exists($digraph{$name}))
{
my @deps = @{$digraph{$name}};
# have seen already that edge?
if (grep {/$dep/} @deps)
{
# hide then
$print = 0;
}
else
{
push @deps, $dep;
$digraph{$name} = \@deps;
}
}
else
{
my @deps;
push @deps, $dep;
$digraph{$name} = \@deps;
}
}
}
print $to "$name -> $dep;\n" if ($print);
push @newdeps, $dep; push @newdeps, $dep;
} }
# re-write the shrunk set to accelerate things # re-write the shrunk set to accelerate things
$result->{deps} = \@newdeps; $result->{deps} = \@newdeps;
} }
print "}\n"; print $to "}\n";
}
logit("warn: no module for lib found: [" .
my $graphviz = 1; join(",", (sort (keys(%unknown_libs)))) . "]\n");
while (my $arg = shift @ARGV) {
if ($arg eq '--graph' || $arg eq '-g') {
$graphviz = 1;
} elsif (!defined $gnumake) {
$gnumake = $arg;
} elsif (!defined $makefile_build) {
$makefile_build = $arg;
} else {
die "un-needed argument '$arg'";
}
} }
sub parse_options()
{
my %h = (
'verbose|v' => \$verbose,
'help|h' => \my $help,
'man|m' => \my $man,
'version|r' => sub {
VersionMessage(-msg => "You are using: 1.0 of ");
},
'write-dep-file|w=s' => \$to_file,
'read-dep-file|f=s' => \$from_file,
'graph-file|o=s' => \$graph_file);
GetOptions(%h) or pod2usage(2);
pod2usage(1) if $help;
pod2usage(-exitstatus => 0, -verbose => 2) if $man;
($gnumake, $makefile_build) = @ARGV if $#ARGV == 1;
$gnumake = 'make' if (!defined $gnumake); $gnumake = 'make' if (!defined $gnumake);
$makefile_build = 'Makefile.gbuild' if (!defined $makefile_build); $makefile_build = 'Makefile.gbuild' if (!defined $makefile_build);
}
sub main()
{
parse_options();
my $deps = read_deps(); my $deps = read_deps();
my $tree = clean_tree($deps); my $tree = clean_tree($deps);
prune_redundant_deps($tree); prune_redundant_deps($tree);
dump_graphviz($tree); dump_graphviz($tree);
}
main()
__END__
=head1 NAME
module-deps - Generate module dependencies for LibreOffice build system
=head1 SYNOPSIS
module_deps [options] [gnumake] [makefile]
=head1 OPTIONS
=over 8
=item B<--help>
=item B<-h>
Print a brief help message and exits.
=item B<--man>
=item B<-m>
Prints the manual page and exits.
=item B<--version>
=item B<-v>
Prints the version and exits.
=item B<--read-dep-file file>
=item B<-f>
Read dependency from file.
=item B<--write-dep-file file>
=item B<-w>
Write dependency to file.
=item B<--graph-file file>
=item B<-o>
Write output to graph file
=back
=head1 DESCRIPTION
B<This program> parses the output of LibreOffice make process
(or cached input file) and generates the digraph build dependency,
that must be piped to B<graphviz> program (typically B<dot>).
B<Hacking on it>:
The typical (optimized) B<workflow> includes 3 steps:
=over 3
=item 1
Create cache dependency file: module_deps --write-dep-file lo.dep
=item 2
Use cache dependency file: module_deps --read-dep-file lo.dep -o lo.graphviz
=item 3
Pipe the output to graphviz: cat lo.graphviz | dot -Tpng -o lo.png
=back
=head1 AUTHORS
=over 2
=item Michael Meeks
=item David Ostrovsky
=back
=cut