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 14 use Getopt::Long; 15 use strict; 16 use warnings; 17 18 my $prog_name = "compare-build-logs"; 19 20 my $usage=<<EOF; 21 USAGE 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 34 EOF 35 36 my $compiler = "gcc"; 37 my $verbose = 0; 38 39 GetOptions( "gcc" => sub { $compiler = "gcc" }, 40 "clang" => sub { $compiler = "clang" }, 41 "v" => sub { $verbose = 1 }, 42 ) || die $usage; 43 44 my $num_arg = $#ARGV + 1; 45 46 if ($num_arg != 2) { 47 die $usage; 48 } 49 50 my $log1 = $ARGV[0]; 51 my $log2 = $ARGV[1]; 52 53 # Hashes: relative filename -> compiler invocation 54 my %cmd1 = read_log_file($log1); 55 my %cmd2 = read_log_file($log2); 56 57 # Compare the log files 58 59 foreach 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 } 66 foreach my $file (keys %cmd2) { 67 if (! $cmd1{$file}) { 68 print "*** $file missing in $log1\n"; 69 } 70 } 71 72 exit 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. 77 sub 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 129 sub 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 # 187 sub 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