#!/usr/bin/env perl # Lame script to compare two build logs. # # The script intercepts directory changes and compiler invocations and # compares the compiler invocations for equality. Equality is defined # as "same options in both invocations ignoring order". So we only test # a necessary condition. # # Both builds must be configured with the same --prefix. Otherwise, # the value of -DVG_LIBDIR= will differ. They also need to use the same # compiler. use Getopt::Long; use strict; use warnings; my $prog_name = "compare-build-logs"; my $usage=<<EOF; USAGE $prog_name log1 log2 [--gcc] intercept GCC invocations (this is default) [--clang] intercept clang invocations [-v] verbose mode log-file1 log-file2 EOF my $compiler = "gcc"; my $verbose = 0; GetOptions( "gcc" => sub { $compiler = "gcc" }, "clang" => sub { $compiler = "clang" }, "v" => sub { $verbose = 1 }, ) || die $usage; my $num_arg = $#ARGV + 1; if ($num_arg != 2) { die $usage; } my $log1 = $ARGV[0]; my $log2 = $ARGV[1]; # Hashes: relative filename -> compiler invocation my %cmd1 = read_log_file($log1); my %cmd2 = read_log_file($log2); # Compare the log files foreach my $file (keys %cmd1) { if (! $cmd2{$file}) { print "*** $file missing in $log2\n"; } else { compare_invocations($file, $cmd1{$file }, $cmd2{$file}); } } foreach my $file (keys %cmd2) { if (! $cmd1{$file}) { print "*** $file missing in $log1\n"; } } exit 0; # Compare two lines |c1| and |c2| which are compiler invocations for |file|. # Basically, we look at a compiler invocation as a sequence of blank-separated # options. sub compare_invocations { my ($file, $c1, $c2) = @_; my ($found1, $found2); # print "COMPARE $file\n"; # Remove stuff that has embedded spaces # Remove: `test -f 'whatever.c' || echo './'` $c1 =~ s|(`[^`]*`)||; $found1 = $1; $c2 =~ s|(`[^`]*`)||; $found2 = $1; if ($found1 && $found2) { die if ($found1 ne $found2); } # Remove: -o whatever $c1 =~ s|-o[ ][ ]*([^ ][^ ]*)||; $found1 = $1; $c2 =~ s|-o[ ][ ]*([^ ][^ ]*)||; $found2 = $1; if ($found1 && $found2) { die if ($found1 ne $found2); } # The remaining lines are considered to be blank-separated options and file # names. They should be identical in both invocations (but in any order). my %o1 = (); my %o2 = (); foreach my $k (split /\s+/,$c1) { $o1{$k} = 1; } foreach my $k (split /\s+/,$c2) { $o2{$k} = 1; } # foreach my $zz (keys %o1) { # print "$zz\n"; # } foreach my $k (keys %o1) { if (! $o2{$k}) { print "*** '$k' is missing in compilation of '$file' in $log2\n"; } } foreach my $k (keys %o2) { if (! $o1{$k}) { print "*** '$k' is missing in compilation of '$file' in $log1\n"; } } } # Read a compiler log file. # Return hash: relative (to build root) file name -> compiler invocation sub read_log_file { my ($log) = @_; my $dir = ""; my $root = ""; my %cmd = (); print "...reading $log\n" if ($verbose); open(LOG, "$log") || die "cannot open $log\n"; while (my $line = <LOG>) { chomp $line; if ($line =~ /^make/) { if ($line =~ /Entering directory/) { $dir = $line; $dir =~ s/^.*`//; $dir =~ s/'.*//; if ($root eq "") { $root = $dir; # Append a slash if not present $root = "$root/" if (! ($root =~ /\/$/)); print "...build root is $root\n" if ($verbose); } # print "DIR = $dir\n"; next; } } if ($line =~ /^\s*[^\s]*$compiler\s/) { # If line ends in \ read continuation line. while ($line =~ /\\$/) { my $next = <LOG>; chomp($next); $line =~ s/\\$//; $line .= $next; } my $file = extract_file($line); $file = "$dir/$file"; # make absolute $file =~ s/$root//; # remove build root # print "FILE $file\n"; $cmd{"$file"} = $line; } } close(LOG); my $num_invocations = int(keys %cmd); if ($num_invocations == 0) { print "*** File $log does not contain any compiler invocations\n"; } else { print "...found $num_invocations invocations\n" if ($verbose); } return %cmd; } # Extract a file name from the command line. Assume there is a -o filename # present. If not, issue a warning. # sub extract_file { my($line) = @_; # Look for -o executable if ($line =~ /-o[ ][ ]*([^ ][^ ]*)/) { return $1; } else { print "*** Could not extract file name from $line\n"; return "UNKNOWN"; } }