1#!/usr/bin/env perl
2
3# Lame script to compare two build logs.
4#
5# The script intercepts directory changes and compiler invocations and
6# compares the compiler invocations for equality. Equality is defined
7# as "same options in both invocations ignoring order". So we only test
8# a necessary condition.
9#
10# Both builds must be configured with the same --prefix. Otherwise,
11# the value of -DVG_LIBDIR= will differ. They also need to use the same
12# compiler.
13
14use Getopt::Long;
15use strict;
16use warnings;
17
18my $prog_name = "compare-build-logs";
19
20my $usage=<<EOF;
21USAGE
22
23  $prog_name   log1 log2
24
25    [--gcc]            intercept GCC invocations (this is default)
26
27    [--clang]          intercept clang invocations
28
29    [-v]               verbose mode
30
31    log-file1
32
33    log-file2
34EOF
35
36my $compiler = "gcc";
37my $verbose  = 0;
38
39GetOptions( "gcc"   => sub { $compiler = "gcc" },
40            "clang" => sub { $compiler = "clang" },
41            "v" => sub { $verbose = 1 },
42            ) || die $usage;
43
44my $num_arg = $#ARGV + 1;
45
46if ($num_arg != 2) {
47    die $usage;
48}
49
50my $log1  = $ARGV[0];
51my $log2  = $ARGV[1];
52
53# Hashes:  relative filename -> compiler invocation
54my %cmd1 = read_log_file($log1);
55my %cmd2 = read_log_file($log2);
56
57# Compare the log files
58
59foreach my $file (keys %cmd1) {
60    if (! $cmd2{$file}) {
61        print "*** $file missing in $log2\n";
62    } else {
63        compare_invocations($file, $cmd1{$file }, $cmd2{$file});
64    }
65}
66foreach my $file (keys %cmd2) {
67    if (! $cmd1{$file}) {
68        print "*** $file missing in $log1\n";
69    }
70}
71
72exit 0;
73
74# Compare two lines |c1| and |c2| which are compiler invocations for |file|.
75# Basically, we look at a compiler invocation as a sequence of blank-separated
76# options.
77sub compare_invocations {
78    my ($file, $c1, $c2) = @_;
79    my ($found1, $found2);
80#    print "COMPARE $file\n";
81
82# Remove stuff that has embedded spaces
83
84# Remove: `test -f 'whatever.c' || echo './'`
85    $c1 =~ s|(`[^`]*`)||;
86    $found1 = $1;
87    $c2 =~ s|(`[^`]*`)||;
88    $found2 = $1;
89    if ($found1 && $found2) {
90        die if ($found1 ne $found2);
91    }
92
93# Remove: -o whatever
94    $c1 =~ s|-o[ ][ ]*([^ ][^ ]*)||;
95    $found1 = $1;
96    $c2 =~ s|-o[ ][ ]*([^ ][^ ]*)||;
97    $found2 = $1;
98    if ($found1 && $found2) {
99        die if ($found1 ne $found2);
100    }
101
102# The remaining lines are considered to be blank-separated options and file
103# names. They should be identical in both invocations (but in any order).
104    my %o1 = ();
105    my %o2 = ();
106    foreach my $k (split /\s+/,$c1) {
107        $o1{$k} = 1;
108    }
109    foreach my $k (split /\s+/,$c2) {
110        $o2{$k} = 1;
111    }
112#    foreach my $zz (keys %o1) {
113#        print "$zz\n";
114#    }
115    foreach my $k (keys %o1) {
116        if (! $o2{$k}) {
117            print "*** '$k' is missing in compilation of '$file' in $log2\n";
118        }
119    }
120    foreach my $k (keys %o2) {
121        if (! $o1{$k}) {
122            print "*** '$k' is missing in compilation of '$file' in $log1\n";
123        }
124    }
125}
126
127# Read a compiler log file.
128# Return hash: relative (to build root) file name -> compiler invocation
129sub read_log_file
130{
131    my ($log) = @_;
132    my $dir = "";
133    my $root = "";
134    my %cmd = ();
135
136    print "...reading $log\n" if ($verbose);
137    
138    open(LOG, "$log") || die "cannot open $log\n";
139    while (my $line = <LOG>) {
140        chomp $line;
141        if ($line =~ /^make/) {
142            if ($line =~ /Entering directory/) {
143                $dir = $line;
144                $dir =~ s/^.*`//;
145                $dir =~ s/'.*//;
146                if ($root eq "") {
147                    $root = $dir;
148                    # Append a slash if not present
149                    $root = "$root/" if (! ($root =~ /\/$/));
150                    print "...build root is $root\n" if ($verbose);
151                }
152#                print "DIR = $dir\n";
153                next;
154            }
155        }
156        if ($line =~ /^\s*[^\s]*$compiler\s/) {
157            # If line ends in \ read continuation line.
158            while ($line =~ /\\$/) {
159                my $next = <LOG>;
160                chomp($next);
161                $line =~ s/\\$//;
162                $line .= $next;
163            }
164
165            my $file = extract_file($line);
166            $file = "$dir/$file";     # make absolute
167            $file =~ s/$root//;       # remove build root
168#            print "FILE $file\n";
169            $cmd{"$file"} = $line;
170        }
171    }
172    close(LOG);
173
174    my $num_invocations = int(keys %cmd);
175
176    if ($num_invocations == 0) {
177        print "*** File $log does not contain any compiler invocations\n";
178    } else {
179        print "...found $num_invocations invocations\n" if ($verbose);
180    }
181    return %cmd;
182}
183
184# Extract a file name from the command line. Assume there is a -o filename
185# present. If not, issue a warning.
186#
187sub extract_file {
188    my($line) = @_;
189# Look for -o executable
190    if ($line =~ /-o[ ][ ]*([^ ][^ ]*)/) {
191        return $1;
192    } else {
193        print "*** Could not extract file name from $line\n";
194        return "UNKNOWN";
195    }
196}
197