blob: 5f62034bb2a1f343cb39328519a0c1898ef0c92f [file] [log] [blame]
Reid Spencerf2722ca2006-03-22 15:59:55 +00001#!/usr/bin/perl
2#
3# Program: find-cycles.pl
4#
5# Synopsis: Given a list of possibly cyclic dependencies, merge all the
6# cycles. This makes it possible to topologically sort the
7# dependencies between different parts of LLVM.
8#
9# Syntax: find-cycles.pl < LibDeps.txt > FinalLibDeps.txt
10#
11# Input: cycmem1: cycmem2 dep1 dep2
12# cycmem2: cycmem1 dep3 dep4
13# boring: dep4
14#
15# Output: cycmem1 cycmem2: dep1 dep2 dep3 dep4
16# boring: dep4
17#
18# This file was written by Eric Kidd, and is placed into the public domain.
19#
20
Reid Spencerb195d9d2006-03-23 23:21:29 +000021use 5.006;
Reid Spencerf2722ca2006-03-22 15:59:55 +000022use strict;
23use warnings;
24
25my %DEPS;
26my @CYCLES;
27sub find_all_cycles;
28
29# Read our dependency information.
30while (<>) {
31 chomp;
32 my ($module, $dependency_str) = /^([^:]*): ?(.*)$/;
33 die "Malformed data: $_" unless defined $dependency_str;
34 my @dependencies = split(/ /, $dependency_str);
35 $DEPS{$module} = \@dependencies;
36}
37
38# Partition our raw dependencies into sets of cyclically-connected nodes.
39find_all_cycles();
40
41# Print out the finished cycles, with their dependencies.
42my @output;
Reid Spencerdd3f6aa2006-07-26 17:10:54 +000043my $cycles_found = 0;
Reid Spencerf2722ca2006-03-22 15:59:55 +000044foreach my $cycle (@CYCLES) {
45 my @modules = sort keys %{$cycle};
46
47 # Merge the dependencies of all modules in this cycle.
48 my %dependencies;
49 foreach my $module (@modules) {
50 @dependencies{@{$DEPS{$module}}} = 1;
51 }
52
53 # Prune the known cyclic dependencies.
54 foreach my $module (@modules) {
55 delete $dependencies{$module};
56 }
57
58 # Warn about possible linker problems.
59 my @archives = grep(/\.a$/, @modules);
60 if (@archives > 1) {
Reid Spencerdd3f6aa2006-07-26 17:10:54 +000061 $cycles_found = $cycles_found + 1;
Reid Spencerf2722ca2006-03-22 15:59:55 +000062 print STDERR "find-cycles.pl: Circular dependency between *.a files:\n";
63 print STDERR "find-cycles.pl: ", join(' ', @archives), "\n";
64 print STDERR "find-cycles.pl: Some linkers may have problems.\n";
65 push @modules, @archives; # WORKAROUND: Duplicate *.a files. Ick.
66 }
67
68 # Add to our output. (@modules is already as sorted as we need it to be.)
69 push @output, (join(' ', @modules) . ': ' .
70 join(' ', sort keys %dependencies) . "\n");
71}
72print sort @output;
Chris Lattner7686b572006-07-26 21:14:04 +000073
Reid Spencerecfe0882006-08-03 21:46:42 +000074exit $cycles_found;
Reid Spencerf2722ca2006-03-22 15:59:55 +000075
76#==========================================================================
77# Depedency Cycle Support
78#==========================================================================
79# For now, we have cycles in our dependency graph. Ideally, each cycle
80# would be collapsed down to a single *.a file, saving us all this work.
81#
82# To understand this code, you'll need a working knowledge of Perl 5,
83# and possibly some quality time with 'man perlref'.
84
85my %SEEN;
86my %CYCLES;
87sub find_cycles ($@);
88sub found_cycles ($@);
89
90sub find_all_cycles {
91 # Find all multi-item cycles.
92 my @modules = sort keys %DEPS;
93 foreach my $module (@modules) { find_cycles($module); }
94
95 # Build fake one-item "cycles" for the remaining modules, so we can
96 # treat them uniformly.
97 foreach my $module (@modules) {
98 unless (defined $CYCLES{$module}) {
99 my %cycle = ($module, 1);
100 $CYCLES{$module} = \%cycle;
101 }
102 }
103
104 # Find all our unique cycles. We have to do this the hard way because
105 # we apparently can't store hash references as hash keys without making
106 # 'strict refs' sad.
107 my %seen;
108 foreach my $cycle (values %CYCLES) {
109 unless ($seen{$cycle}) {
110 $seen{$cycle} = 1;
111 push @CYCLES, $cycle;
112 }
113 }
114}
115
116# Walk through our graph depth-first (keeping a trail in @path), and report
117# any cycles we find.
118sub find_cycles ($@) {
119 my ($module, @path) = @_;
120 if (str_in_list($module, @path)) {
121 found_cycle($module, @path);
122 } else {
123 return if defined $SEEN{$module};
124 $SEEN{$module} = 1;
125 foreach my $dep (@{$DEPS{$module}}) {
126 find_cycles($dep, @path, $module);
127 }
128 }
129}
130
131# Give a cycle, attempt to merge it with pre-existing cycle data.
132sub found_cycle ($@) {
133 my ($module, @path) = @_;
134
135 # Pop any modules which aren't part of our cycle.
136 while ($path[0] ne $module) { shift @path; }
137 #print join("->", @path, $module) . "\n";
138
139 # Collect the modules in our cycle into a hash.
140 my %cycle;
141 foreach my $item (@path) {
142 $cycle{$item} = 1;
143 if (defined $CYCLES{$item}) {
144 # Looks like we intersect with an existing cycle, so merge
145 # all those in, too.
146 foreach my $old_item (keys %{$CYCLES{$item}}) {
147 $cycle{$old_item} = 1;
148 }
149 }
150 }
151
152 # Update our global cycle table.
153 my $cycle_ref = \%cycle;
154 foreach my $item (keys %cycle) {
155 $CYCLES{$item} = $cycle_ref;
156 }
157 #print join(":", sort keys %cycle) . "\n";
158}
159
160sub str_in_list ($@) {
161 my ($str, @list) = @_;
162 foreach my $item (@list) {
163 return 1 if ($item eq $str);
164 }
165 return 0;
166}