bump product version to 5.0.4.1
[LibreOffice.git] / bin / module-deps.pl
blob08126d07d9363ad94fcc376b4380144bf0b96d30
1 #!/usr/bin/perl
3 use strict;
4 use warnings;
5 use Getopt::Long qw(GetOptions VersionMessage);
6 use Pod::Usage;
8 my $gnumake;
9 my $src_root;
10 my $makefile_build;
11 my $verbose = 0;
12 my $no_leaf;
13 my $from_file;
14 my $to_file;
15 my $output_file;
16 my $preserve_libs = 0;
17 my $toposort = 0;
19 sub logit($)
21 print STDERR shift if ($verbose);
24 sub read_deps()
26 my $p;
27 my $to;
28 my $invalid_tolerance = 100;
29 my $line_count = 0;
30 my %deps;
31 if (defined $to_file)
33 open($to, ">$to_file") or die "can not open file for writing $to_file";
35 if (defined $from_file) {
36 open ($p, $from_file) || die "can't read deps from cache file: $!";
37 } else {
38 open ($p, "ENABLE_PRINT_DEPS=1 $gnumake -qrf $makefile_build|") || die "can't launch make: $!";
40 $|=1;
41 print STDERR "reading deps ";
42 while (<$p>) {
43 my $line = $_;
44 $line_count++;
45 print STDERR '.' if ($line_count % 10 == 0);
46 logit($line);
47 print $to $line if defined $to_file;
48 chomp ($line);
49 if ($line =~ m/^LibraryDep:\s+(\S+) links against (.*)$/) {
50 # if ($line =~ m/^LibraryDep:\s+(\S+)\s+links against/) {
51 $deps{$1} = ' ' if (!defined $deps{$1});
52 $deps{$1} = $deps{$1} . ' ' . $2;
53 } elsif ($line =~ m/^LibraryDep:\s+links against/) {
54 # these need fixing, we call gb_LinkTarget__use_$...
55 # and get less than normal data back to gb_LinkTarget_use_libraries
56 # print STDERR "ignoring unhelpful external dep\n";
57 } elsif ($invalid_tolerance < 0) {
58 # print "read all dependencies to: '$line'\n";
59 last;
60 } else {
61 # print "no match '$line'\n";
62 $invalid_tolerance--;
65 close ($p);
66 print STDERR " done\n";
68 return \%deps;
71 # graphviz etc. don't like some names
72 sub clean_name($)
74 my $name = shift;
75 $name =~ s/[\-\/\.]/_/g;
76 return $name;
79 # first create nodes for each entry
80 sub clean_tree($)
82 my $deps = shift;
83 my %tree;
84 for my $name (sort keys %{$deps}) {
85 my $need_str = $deps->{$name};
86 $need_str =~ s/^\s+//g;
87 $need_str =~ s/\s+$//g;
88 my @needs = split /\s+/, $need_str;
89 $name =~ m/^([^_]+)_(\S+)$/ || die "invalid target name: '$name'";
90 my $type = $1;
91 my $target = clean_name ($2);
92 $type eq 'Executable' || $type eq 'Library' ||
93 $type eq 'CppunitTest' || die "Unknown type '$type'";
95 my %result;
96 $result{type} = $type;
97 $result{target} = $target;
98 $result{generation} = 0;
99 my @clean_needs;
100 for my $need (@needs) {
101 push @clean_needs, clean_name($need);
103 $result{deps} = \@clean_needs;
104 if (defined $tree{$target}) {
105 logit("warning -duplicate target: '$target'\n");
106 delete($tree{$target});
108 $tree{$target} = \%result;
110 logit("$target ($type): " . join (',', @clean_needs) . "\n");
112 return \%tree;
115 sub has_child_dep($$$)
117 my ($tree,$search,$name) = @_;
118 my $node = $tree->{$name};
119 return defined $node->{flat_deps}->{$search};
122 # flatten deps recursively into a single hash per module
123 sub build_flat_dep_hash($$);
124 sub build_flat_dep_hash($$)
126 my ($tree, $name) = @_;
127 my %flat_deps;
129 my $node = $tree->{$name};
130 return if (defined $node->{flat_deps});
132 # build flat deps for children
133 for my $child (@{$node->{deps}}) {
134 build_flat_dep_hash($tree, $child)
137 for my $child (@{$node->{deps}}) {
138 $flat_deps{$child} = 1;
139 for my $dep (@{$tree->{$child}->{deps}}) {
140 $flat_deps{$dep} = 1;
143 $node->{flat_deps} = \%flat_deps;
145 # useful debugging ...
146 if (defined $ENV{DEP_CACHE_FILE}) {
147 logit("node '$name' has flat-deps: '" . join(',', keys %flat_deps) . "' " .
148 "vs. '" . join(',', @{$node->{deps}}) . "'\n");
152 # many modules depend on vcl + sal, but vcl depends on sal
153 # so we want to strip sal out - and the same for many
154 # similar instances
155 sub prune_redundant_deps($)
157 my $tree = shift;
158 for my $name (sort keys %{$tree}) {
159 build_flat_dep_hash($tree, $name);
163 # glob on libo directory
164 sub create_lib_module_map()
166 my %l2m;
167 # hardcode the libs that don't have a directory
168 $l2m{'merged'} = 'merged';
170 for (glob($src_root."/*/Library_*.mk"))
172 /.*\/(.*)\/Library_(.*)\.mk/;
173 # add module -> module
174 $l2m{$1} = $1;
175 # add lib -> module
176 $l2m{$2} = $1;
178 return \%l2m;
181 # call prune redundant_deps
182 # rewrite the deps array
183 sub optimize_tree($)
185 my $tree = shift;
186 prune_redundant_deps($tree);
187 for my $name (sort keys %{$tree}) {
188 my $result = $tree->{$name};
189 logit("minimising deps for $result->{target}\n");
190 my @newdeps;
191 for my $dep (@{$result->{deps}}) {
192 # is this implied by any other child ?
193 logit("checking if '$dep' is redundant\n");
194 my $preserve = 1;
195 for my $other_dep (@{$result->{deps}}) {
196 next if ($other_dep eq $dep);
197 if (has_child_dep($tree,$dep,$other_dep)) {
198 logit("$dep is implied by $other_dep - ignoring\n");
199 $preserve = 0;
200 last;
203 push @newdeps, $dep if ($preserve);
205 # re-write the shrunk set to accelerate things
206 $result->{deps} = \@newdeps;
208 return $tree;
211 # walking through the library based graph and creating a module based graph.
212 sub collapse_lib_to_module($)
214 my $tree = shift;
215 my %digraph;
216 my $l2m = create_lib_module_map();
217 my %unknown_libs;
218 for my $name (sort keys %{$tree}) {
219 my $result = $tree->{$name};
220 $unknown_libs{$name} = 1 && next if (!grep {/$name/} keys $l2m);
221 $name = $l2m->{$name};
222 # sal has no dependencies, take care of it
223 # otherwise it doesn't have target key
224 if (!@{$result->{deps}}) {
225 if (!exists($digraph{$name})) {
226 my @empty;
227 $digraph{$name}{deps} = \@empty;
228 $digraph{$name}{target} = $result->{target};
231 for my $dep (@{$result->{deps}}) {
232 $dep = $l2m->{$dep};
233 # ignore: two libraries from the same module depend on each other
234 next if ($name eq $dep);
235 if (exists($digraph{$name}))
237 my @deps = @{$digraph{$name}{deps}};
238 # only add if we haven't seen already that edge?
239 if (!grep {/$dep/} @deps)
241 push @deps, $dep;
242 $digraph{$name}{deps} = \@deps;
245 else
247 my @deps;
248 push @deps, $dep;
249 $digraph{$name}{deps} = \@deps;
250 $digraph{$name}{target} = $result->{target};
254 logit("warn: no module for libs were found and dropped: [" .
255 join(",", (sort (keys(%unknown_libs)))) . "]\n");
256 return optimize_tree(\%digraph);
259 sub prune_leaves($)
261 my $tree = shift;
262 my %newtree;
263 my %name_has_deps;
265 # we like a few leaves around:
266 for my $nonleaf ('desktop', 'sw', 'sc', 'sd', 'starmath') {
267 $name_has_deps{$nonleaf} = 1;
270 # find which modules are depended on by others
271 for my $name (keys %{$tree}) {
272 for my $dep (@{$tree->{$name}->{deps}}) {
273 $name_has_deps{$dep} = 1;
277 # prune modules with no deps
278 for my $name (keys %{$tree}) {
279 delete $tree->{$name} if (!defined $name_has_deps{$name});
282 return optimize_tree($tree);
286 sub dump_graphviz($)
288 my $tree = shift;
289 my $to = \*STDOUT;
290 open($to, ">$output_file") if defined($output_file);
291 print $to <<END;
292 digraph LibreOffice {
293 node [shape="Mrecord", color="#BBBBBB"]
294 node [fontname=Verdana, color="#BBBBBB", fontsize=10, height=0.02, width=0.02]
295 edge [color="#31CEF0", len=0.4]
296 edge [fontname=Arial, fontsize=10, fontcolor="#31CEF0"]
298 for my $name (sort keys %{$tree}) {
299 my $result = $tree->{$name};
300 logit("minimising deps for $result->{target}\n");
301 for my $dep (@{$result->{deps}}) {
302 print $to "$name -> $dep;\n" ;
305 print $to "}\n";
308 sub toposort_visit($$$$);
309 sub toposort_visit($$$$)
311 my $tree = shift;
312 my $list = shift;
313 my $tags = shift;
314 my $name = shift;
315 die "dependencies don't form a DAG"
316 if (defined($tags->{$name}) && $tags->{$name} == 1);
317 if (!$tags->{$name}) {
318 $tags->{$name} = 1;
319 my $result = $tree->{$name};
320 for my $dep (@{$result->{deps}}) {
321 toposort_visit($tree, $list, $tags, $dep);
323 $tags->{$name} = 2;
324 push @{$list}, $name;
328 sub dump_toposort($)
330 my $tree = shift;
331 my @list;
332 my %tags;
333 for my $name (sort keys %{$tree}) {
334 toposort_visit($tree, \@list, \%tags, $name);
336 my $to = \*STDOUT;
337 open($to, ">$output_file") if defined($output_file);
338 for (my $i = 0; $i <= $#list; ++$i) {
339 print $to "$list[$i]\n";
343 sub filter_targets($)
345 my $tree = shift;
346 for my $name (sort keys %{$tree})
348 my $result = $tree->{$name};
349 if ($result->{type} eq 'CppunitTest' ||
350 ($result->{type} eq 'Executable' &&
351 $result->{target} ne 'soffice_bin'))
353 delete($tree->{$name});
358 sub parse_options()
360 my %h = (
361 'verbose|v' => \$verbose,
362 'help|h' => \my $help,
363 'man|m' => \my $man,
364 'version|r' => sub {
365 VersionMessage(-msg => "You are using: 1.0 of ");
367 'preserve-libs|p' => \$preserve_libs,
368 'toposort|t' => \$toposort,
369 'write-dep-file|w=s' => \$to_file,
370 'read-dep-file|f=s' => \$from_file,
371 'no-leaf|l' => \$no_leaf,
372 'output-file|o=s' => \$output_file);
373 GetOptions(%h) or pod2usage(2);
374 pod2usage(1) if $help;
375 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
376 ($gnumake, $makefile_build) = @ARGV if $#ARGV == 1;
377 $gnumake = 'make' if (!defined $gnumake);
378 $makefile_build = 'Makefile.gbuild' if (!defined $makefile_build);
379 $src_root = defined $ENV{SRC_ROOT} ? $ENV{SRC_ROOT} : ".";
382 sub main()
384 parse_options();
385 my $deps = read_deps();
386 my $tree = clean_tree($deps);
387 filter_targets($tree);
388 optimize_tree($tree);
389 if (!$preserve_libs && !defined($ENV{PRESERVE_LIBS})) {
390 $tree = collapse_lib_to_module($tree);
392 if ($no_leaf) {
393 $tree = prune_leaves($tree);
395 if ($toposort) {
396 dump_toposort($tree);
397 } else {
398 dump_graphviz($tree);
402 main()
404 __END__
406 =head1 NAME
408 module-deps - Generate module dependencies for LibreOffice build system
410 =head1 SYNOPSIS
412 module_deps [options] [gnumake] [makefile]
414 =head1 OPTIONS
416 =over 8
418 =item B<--help>
420 =item B<-h>
422 Print a brief help message and exits.
424 =item B<--man>
426 =item B<-m>
428 Prints the manual page and exits.
430 =item B<--version>
432 =item B<-v>
434 Prints the version and exits.
436 =item B<--preserve-libs>
438 =item B<-p>
440 Don't collapse libs to modules
442 =item B<--toposort>
444 =item B<-t>
446 Output a topological sorting instead of a graph
448 =item B<--read-dep-file file>
450 =item B<-f>
452 Read dependency from file.
454 =item B<--write-dep-file file>
456 =item B<-w>
458 Write dependency to file.
460 =item B<--output-file file>
462 =item B<-o>
464 Write graph or sort output to file
466 =back
468 =head1 DESCRIPTION
470 B<This program> parses the output of LibreOffice make process
471 (or cached input file) and generates the digraph build dependency,
472 that must be piped to B<graphviz> program (typically B<dot>).
474 B<Hacking on it>:
476 The typical (optimized) B<workflow> includes 3 steps:
478 =over 3
480 =item 1
481 Create cache dependency file: module_deps --write-dep-file lo.dep
483 =item 2
484 Use cache dependency file: module_deps --read-dep-file lo.dep -o lo.graphviz
486 =item 3
487 Pipe the output to graphviz: cat lo.graphviz | dot -Tpng -o lo.png
489 =back
491 =head1 TODO
493 =over 2
495 =item 1
496 Add soft (include only) dependency
498 =item 2
499 Add dependency on external modules
501 =back
503 =head1 AUTHOR
505 =over 2
507 =item Michael Meeks
509 =item David Ostrovsky
511 =back
513 =cut