Home | History | Annotate | Download | only in auxprogs
      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