#!/usr/bin/perl -w # NAME: msvclog.pl # AIM: Read a MSVC build log output, and report success and failed projects # 07/05/2016 - Show project name with warning/error # 13/01/2016 - Show each warning count, to add to CMakeLists.txt... # 01/01/2016 - Add a clean build message # 20/10/2015 - Deal with fatal error on link line # 21/09/2015 - Reduce, simplify output # 24/09/2014 - default to defacto standard log, bldlog-1.txt, if it exists # 21/07/2014 - Skip ZERO_CHECK and ALL_BUILD projects. # 20/07/2013 geoff mclane http://geoffair.net/mperl use strict; use warnings; use File::Basename; # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] ) use Cwd; my $os = $^O; my $perl_dir = '/home/geoff/bin'; my $PATH_SEP = '/'; my $temp_dir = '/tmp'; if ($os =~ /win/i) { $perl_dir = 'C:\GTools\perl'; $temp_dir = $perl_dir; $PATH_SEP = "\\"; } unshift(@INC, $perl_dir); require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n"; # log file stuff our ($LF); my $pgmname = $0; if ($pgmname =~ /(\\|\/)/) { my @tmpsp = split(/(\\|\/)/,$pgmname); $pgmname = $tmpsp[-1]; } my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt"; open_log($outfile); # user variables my $VERS = "0.0.5 2016-05-07"; ##my $VERS = "0.0.4 2016-01-01"; ##my $VERS = "0.0.3 2015-09-21"; ##my $VERS = "0.0.2 2014-07-21"; ##my $VERS = "0.0.1 2013-07-20"; my $load_log = 0; my $in_file = ''; my $verbosity = 0; my $out_file = ''; my $skip_zero_all = 1; my $show_warnings = 1; my $show_errors = 1; my $show_fatal = 1; my $show_duplicate_projects = 0; # ### DEBUG ### my $debug_on = 0; my $def_file = 'F:\Projects\mozjs-24.2.0\js\src\msvc\bldlog-1.txt'; ###my $def_file = 'C:\FG\18\build-sdl2\bldlog-1.txt'; ### program variables my @warnings = (); my $cwd = cwd(); sub VERB1() { return $verbosity >= 1; } sub VERB2() { return $verbosity >= 2; } sub VERB5() { return $verbosity >= 5; } sub VERB9() { return $verbosity >= 9; } sub show_warnings($) { my ($val) = @_; if (@warnings) { prt( "\nGot ".scalar @warnings." WARNINGS...\n" ); foreach my $itm (@warnings) { prt("$itm\n"); } prt("\n"); } else { prt( "\nNo warnings issued.\n\n" ) if (VERB9()); } } sub pgm_exit($$) { my ($val,$msg) = @_; if (length($msg)) { $msg .= "\n" if (!($msg =~ /\n$/)); prt($msg); } show_warnings($val); close_log($outfile,$load_log); exit($val); } sub prtw($) { my ($tx) = shift; $tx =~ s/\n$//; prt("$tx\n"); push(@warnings,$tx); } # file lines - cmake header #-- Configuring done #-- Generating done #-- Build files have been written to: C:/FG/18/build-sdl2 # #Microsoft (R) Visual Studio Version 10.0.40219.1. #Copyright (C) Microsoft Corp. All rights reserved. #------ Build started: Project: ZERO_CHECK, Configuration: Debug Win32 ------ #Build started 20/07/2013 16:00:33. #InitializeBuildStatus: # Creating "Win32\Debug\ZERO_CHECK\ZERO_CHECK.unsuccessfulbuild" because "AlwaysCreate" was specified. #FinalizeBuildStatus: # Deleting file "Win32\Debug\ZERO_CHECK\ZERO_CHECK.unsuccessfulbuild". # Touching "Win32\Debug\ZERO_CHECK\ZERO_CHECK.lastbuildstate". # #Build succeeded. # #Time Elapsed 00:00:00.06 #Time Elapsed 00:00:00.39 #------ Build started: Project: ALL_BUILD, Configuration: Debug Win32 ------ #Build started 20/07/2013 16:01:02. #InitializeBuildStatus: # Creating "Win32\Debug\ALL_BUILD\ALL_BUILD.unsuccessfulbuild" because "AlwaysCreate" was specified. #CustomBuild: # Build all projects #FinalizeBuildStatus: # Deleting file "Win32\Debug\ALL_BUILD\ALL_BUILD.unsuccessfulbuild". # Touching "Win32\Debug\ALL_BUILD\ALL_BUILD.lastbuildstate". # #Build succeeded. # #Time Elapsed 00:00:00.26 #========== Build: 47 succeeded, 21 failed, 0 up-to-date, 0 skipped ========== my $act_file = ''; my $act_include = ''; sub trim_leading_path($) { my $line = shift; my $len = length($line); my ($i,$ch,$hadsp); $hadsp = 0; my $nline = ''; my $file = ''; for ($i = 0; $i < $len; $i++) { $ch = substr($line,$i,1); if (!$hadsp) { $file .= $ch; # if ($ch =~ /\s/) { if (($ch eq ':')&&($i > 1)) { $hadsp = 1; } else { if (($ch eq '/')||($ch eq '\\')) { $nline = ''; $ch = ''; } } } $nline .= $ch; } $file =~ s/\(\d+\):$//; $act_file = $file; # Cannot open include file: 'idn/res.h': $act_include = ''; if ($nline =~ /Cannot open include file: '(.+)': /) { $act_include = $1; } return $nline; } sub strip_trailing_vcxproj($) { my $line = shift; my $len = length($line); my ($ch,$i); my $open = 0; my $close = 0; for ($i = 0; $i < $len; $i++) { $ch = substr($line,$i,1); if ($ch eq ']') { $close = $i + 1; } elsif ($ch eq '[') { $open = $i; } } if (($open && $close)&&($open < $close)) { $line = substr($line,0,$open); } return $line; } sub get_trim_fatal($) { my $line = shift; $line = strip_trailing_vcxproj($line); $line = trim_leading_path($line); #$line =~ s/\s+\(\?.+\)\s*$//; return $line; } sub get_trim_error($) { my $line = shift; $line = strip_trailing_vcxproj($line); $line = trim_leading_path($line); my ($ch); $ch = index($line," referenced in "); if ($ch > 0) { $line = substr($line,0,$ch); } $line =~ s/\s+\(\?.+\)\s*$//; return $line; } sub get_trim_warn($) { my $line = shift; $line = strip_trailing_vcxproj($line); $line = trim_leading_path($line); return $line; } sub mycmp_decend_n0 { return 1 if (${$a}[0] < ${$b}[0]); return -1 if (${$a}[0] > ${$b}[0]); return 0; } sub process_in_file($) { my ($inf) = @_; if (! open INF, "<$inf") { pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); } my @lines = ; close INF; my $lncnt = scalar @lines; prt("Processing $lncnt lines, from [$inf]...\n"); my ($i,$line,$inc,$lnn,$tline,$len,$dncmake,$proj,$conf,$had_blank); my ($hour,$mins,$secs,$tsecs,$time,$csecs,$ok,@arr,$trline,$ra); my ($hadprj,$hadtm,$hadbld,$nproj,$nconf,$show,$msg,$cnt,$val,$cmd1,$size); $lnn = 0; $dncmake = 0; $had_blank = 0; $csecs = 0; $ok = ''; @arr = (); my %h = (); # store ALL in this project $hadprj = 0; $hadtm = 0; $hadbld = 0; my @warns = (); my @error = (); my @fatal = (); my %projects = (); my %warn_lines = (); my %error_lines = (); my %fatal_lines = (); my %warnvalues = (); my %errorvalues = (); my %fatalvalues = (); my %shown_files = (); my %linkwarn = (); my %linkerror = (); my %linkfatal = (); my %missing_files = (); my $fatalcnt = 0; my $errorcnt = 0; my $warningcnt = 0; my @lnarr = (); $conf = ''; my $comp_file = ''; my $incomptarg = 0; my $projcnt = 0; my ($pname1,$pdir,$pext); my ($line1,$j,$tmp); # process the file lines... for ($i = 0; $i < $lncnt; $i++) { $line = $lines[$i]; chomp $line; $lnn = $i + 1; $tline = trim_all($line); $len = length($tline); if ($len == 0) { $had_blank = 1; next; } # Project "F:\Projects\mozjs-24.2.0\js\src\msvc\ALL_BUILD.vcxproj" on node 1 (default targets). # Project "F:\Projects\mozjs-24.2.0\js\src\msvc\ALL_BUILD.vcxproj" (1) is building "F:\Projects\mozjs-24.2.0\js\src\msvc\ZERO_CHECK.vcxproj" (2) on node 1 (default targets). # and # Done Building Project "F:\Projects\mozjs-24.2.0\js\src\msvc\ZERO_CHECK.vcxproj" (default targets). @lnarr = space_split($tline); $size = scalar @lnarr; $cmd1 = $lnarr[0]; if ($cmd1 eq 'Project') { if ($size > 1) { $nproj = strip_double_quotes($lnarr[1]); ($pname1,$pdir,$pext) = fileparse($nproj, qr/\.[^.]*/ ); ### next if ($pname1 eq 'INSTALL'); } if ($tline =~ /\s+is\s+building\s+\"(.+)\"/) { $nproj = $1; my ($n,$d,$e) = fileparse($nproj, qr/\.[^.]*/ ); # pgm_exit(1,"TEMP EXIT '$nproj'\n"); $proj = $n; if (($proj ne 'ZERO_CHECK')&&($proj ne 'ALL_BUILD')) { if (defined $projects{$proj}) { if ($show_duplicate_projects) { my $flnn = $projects{$proj}; $line1 = $lines[$flnn]; $flnn++; prtw("WARNING: Repeated project name $proj\n". "$lnn: $tline\n". "First:$flnn $line1\n"); } } else { $projects{$proj} = $lnn - 1; } prt("Project: $proj\n") if (VERB5()); $projcnt++; } } next; } elsif ($cmd1 eq 'Done') { next; } elsif ($cmd1 eq 'Link:') { # process a LINK block for project - need includes, and libraries linked my $linklin = ''; for ($j = $lnn; $j < $lncnt; $j++) { $tmp = $lines[$j]; last if ( !($tmp =~ /^\s/) ); $tmp =~ s/^\s+//; $linklin .= " $tmp"; } #pgm_exit(1,"$lnn: $line\n$linklin\nTEMP EXIT\n"); prt("$lnn:$line:$proj:\n$linklin\n") if (VERB9()); } if ( ($size == 1) && is_c_source($cmd1) ) { $comp_file = $cmd1; prt("$lnn: Compile $comp_file\n") if (VERB9()); next; } if ($incomptarg) { if ($line =~ /^\s*(\d+)\s+Warning\(s\)/) { $incomptarg = 0; prt("$lnn: Exit ClCompile target\n") if (VERB9()); } else { # 9860 Error(s) # Time Elapsed 00:02:11.24 next; } } elsif ($line =~ /\(ClCompile target\) ->/) { $incomptarg = 1; prt("$lnn: Entered ClCompile target\n") if (VERB9()); next; } #------ Build started: Project: ZERO_CHECK, Configuration: Debug Win32 ------ # Oops this ouput has CHANGED - See new stuff above... if ($line =~ /------\s+Build\s+started:\s+Project:\s+(.+),\s+Configuration:\s+(.+)\s+------/) { $nproj = $1; # reached the NEXT project $nconf = $2; if ($hadprj && $hadbld && $hadtm) { # time to store the RESULTS my %ph = (); $ph{'name'} = $proj; $ph{'conf'} = $conf; $ph{'time'} = $time; $ph{'res'} = $ok; $ph{'lines'} = [ @arr ]; $h{$proj.':'.$conf} = \%ph; } prt("Proj: $nproj Conf: $nconf\n") if (VERB9()); $proj = $nproj; $conf = $nconf; $dncmake = 1; @arr = (); if (($proj eq 'ZERO_CHECK')||($proj eq 'ALL_BUILD')) { $hadprj = 0; } else { $hadprj = 1; } } elsif ($line =~ /Time\s+Elapsed\s+(\d{2}):(\d{2}):(\d{2})\.(\d{2})/) { # This is a LAST LINE of a PROJECT:configuration $hour = $1; $mins = $2; $secs = $3; $tsecs = $4; $time = sprintf("Elapsed %02d:%02d:%02d.%02d", $hour, $mins, $secs, $tsecs); $csecs += ($hour * 60 * 60); $csecs += ($mins * 60); $csecs += $secs; $csecs += $tsecs / 100; # prt("$time Cum $csecs secs\n"); $show = 1; if ($skip_zero_all) { $show = 0 if (($proj eq 'ZERO_CHECK')||($proj eq 'ALL_BUILD')); } if ($show) { $msg = "Proj: $proj Conf: $conf $time Cum $csecs secs $ok"; if ($show_warnings && @warns) { prt("\n$msg\n") if (length($msg)); $msg = ''; prt("\n".join("\n",@warns)."\n"); } if ($show_errors && @error) { prt("\n$msg\n") if (length($msg)); $msg = ''; prt("\n".join("\n",@error)."\n"); } if ($show_fatal && @fatal) { prt("\n$msg\n") if (length($msg)); $msg = ''; prt("\n".join("\n",@fatal)."\n"); } } $hadtm = 1; @warns = (); @error = (); @fatal = (); } elsif ($line =~ /==========\s+Build:\s+(\d+)\s+succeeded,\s+(\d+)\s+failed,\s+(\d+)\s+up-to-date,\s+(\d+)\s+skipped\s+==========/) { # this is the LAST line of a configuration prt("$line\n"); } elsif ($line =~ /:\s+fatal\s+error\s+/) { if (! defined $fatal_lines{$line}) { $fatalcnt++; $fatal_lines{$line} = 1; $trline = get_trim_fatal($line); if ($trline =~ /:\s+fatal\s+error\s+C(\d+):/) { # F:\Projects\bind-9.10.3-P2\lib\isc\win32\include\isc/net.h(81): fatal error C1083: Cannot open include file: 'isc/platform.h': No such file or directory [F:\Projects\bind-9.10.3-P2\build\a_11.vcxproj] $val = $1; if (!defined $fatalvalues{$val}) { $fatalvalues{$val} = 1; if (!defined $shown_files{$act_file}) { $shown_files{$act_file} = 1; push(@fatal,$trline); # push(@fatal,"$act_file $trline"); } } if ($val == 1083) { prt("$lnn: $act_file $act_include\n") if (VERB9()); $missing_files{$act_include} = [] if (!defined $missing_files{$act_include}); $ra = $missing_files{$act_include}; push(@{$ra},$act_file); ###prt("$lnn: $trline\n"); } } elsif ($trline =~ /:\s+fatal\s+error\s+LNK(\d+):/) { $val = $1; if (!defined $linkfatal{$val}) { $linkfatal{$val} = 1; push(@fatal,$trline); } } else { pgm_exit(1,"ERROR:$lnn: fatal regex failed [$trline]! ** FIX ME **\nLine:$lnn: '$line'\n"); push(@fatal,$trline); } } } elsif ($line =~ /:\s+error\s+/) { if (! defined $error_lines{$line}) { $errorcnt++; $error_lines{$line} = 1; $trline = get_trim_error($line); if ($trline =~ /:\s+error\s+C(\d+):/) { $val = $1; if (!defined $errorvalues{$val}) { $errorvalues{$val} = 1; if (!defined $shown_files{$act_file}) { $shown_files{$act_file} = 1; push(@fatal,$trline); # push(@fatal,"$act_file $trline"); } } } elsif ($trline =~ /:\s+error\s+LNK(\d+):/) { $val = $1; if (!defined $linkerror{$val}) { $linkerror{$val} = 1; if (VERB1()) { push(@error,"$lnn:$proj: $trline"); } else { push(@error,$trline); } } } else { pgm_exit(1,"ERROR:$lnn: error regex failed [$trline]! ** FIX ME **\nLine:$lnn: '$line'\n"); push(@error,$trline); } } } elsif ($line =~ /:\s+warning\s+/) { if (! defined $warn_lines{$line}) { $warningcnt++; $warn_lines{$line} = 1; $trline = get_trim_warn($line); if ($trline =~ /:\s+warning\s+C(\d+):/) { $val = $1; if (defined $warnvalues{$val}) { $warnvalues{$val}++; } else { $warnvalues{$val} = 1; if (!defined $shown_files{$act_file}) { $shown_files{$act_file} = 1; push(@warns,$trline); # push(@fatal,"$act_file $trline"); } } } elsif ($trline =~ /:\s+warning\s+LNK(\d+):/) { $val = $1; if (defined $linkwarn{$val}) { $linkwarn{$val}++; } else { $linkwarn{$val} = 1; push(@warns,$trline); } } else { pgm_exit(1,"ERROR:$lnn: warning regex failed [$trline]! ** FIX ME **\nLine:$lnn: '$line'\n"); push(@warns,$tline); } } } if ($had_blank) { if ($line =~ /^Build\s+(.+)\./ ) { $ok = $1; $hadbld = 1; } } if ($hadprj) { push(@arr,$line); } $had_blank = 0; } prt("\n"); #################################################### ### sumary $cnt = $fatalcnt + $errorcnt + $warningcnt; if ($cnt) { prt("$lnn: $projcnt projects: Had $fatalcnt fatal, errors $errorcnt, warnings $warningcnt, total $cnt\n"); } else { prt("$lnn: $projcnt projects: Had no errors or warnings... clean build...\n"); } @arr = sort keys %fatalvalues; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt fatal values ".join(" ",@arr)."\n"); } @arr = sort keys %errorvalues; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt error values ".join(" ",@arr)."\n"); } @arr = sort keys %warnvalues; $cnt = scalar @arr; if ($cnt) { my $wcnt = $cnt; ##prt("Got $cnt warning values ".join(" ",@arr)."\n"); prt("Got $warningcnt warnings: $wcnt diff: "); my @order = (); for ($j = 0; $j < $wcnt; $j++) { $proj = $arr[$j]; $cnt = $warnvalues{$proj}; push(@order,[ $cnt, $proj ]); } @order = sort mycmp_decend_n0 @order; foreach $ra (@order) { $proj = ${$ra}[1]; $cnt = $warnvalues{$proj}; prt("$proj ($cnt) "); } #foreach $proj (@arr) { # $cnt = $warnvalues{$proj}; # prt("$proj ($cnt) "); #} prt("\n"); } @arr = sort keys %linkfatal; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt fatal link ".join(" ",@arr)."\n"); } @arr = sort keys %linkerror; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt error link ".join(" ",@arr)."\n"); } @arr = sort keys %linkwarn; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt warn link ".join(" ",@arr)."\n"); } @arr = sort keys %missing_files; $cnt = scalar @arr; if ($cnt) { prt("Got $cnt MISSING FILES! ".join(" ",@arr)."\n"); } } ######################################### ### MAIN ### parse_args(@ARGV); process_in_file($in_file); pgm_exit(0,""); ######################################## sub need_arg { my ($arg,@av) = @_; pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av); } sub parse_args { my (@av) = @_; my ($arg,$sarg); while (@av) { $arg = $av[0]; if ($arg =~ /^-/) { $sarg = substr($arg,1); $sarg = substr($sarg,1) while ($sarg =~ /^-/); if (($sarg =~ /^h/i)||($sarg eq '?')) { give_help(); pgm_exit(0,"Help exit(0)"); } elsif ($sarg =~ /^v/) { if ($sarg =~ /^v.*(\d+)$/) { $verbosity = $1; } else { while ($sarg =~ /^v/) { $verbosity++; $sarg = substr($sarg,1); } } prt("Verbosity = $verbosity\n") if (VERB1()); } elsif ($sarg =~ /^l/) { if ($sarg =~ /^ll/) { $load_log = 2; } else { $load_log = 1; } prt("Set to load log at end. ($load_log)\n") if (VERB1()); } elsif ($sarg =~ /^o/) { need_arg(@av); shift @av; $sarg = $av[0]; $out_file = $sarg; prt("Set out file to [$out_file].\n") if (VERB1()); } elsif ($sarg =~ /^f/) { $show_fatal = 0; prt("Set fatal warnings.\n") if (VERB1()); } elsif ($sarg =~ /^e/) { $show_errors = 0; prt("Set skip errors.\n") if (VERB1()); } elsif ($sarg =~ /^w/) { $show_warnings = 0; prt("Set skip warnings.\n") if (VERB1()); } else { pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n"); } } else { $in_file = $arg; prt("Set input to [$in_file]\n") if (VERB1()); } shift @av; } if ($debug_on) { prtw("WARNING: DEBUG is ON!\n"); if (length($in_file) == 0) { $in_file = $def_file; prt("Set DEFAULT input to [$in_file]\n"); } $load_log = 1; } if (length($in_file) == 0) { if (-f 'bldlog-1.txt') { $in_file = 'bldlog-1.txt'; prt("Set DEFAULT input to [$in_file]\n"); } else { give_help(); pgm_exit(1,"\nERROR: No input files found in command!\n"); } } if (! -f $in_file) { pgm_exit(1,"ERROR: Unable to find in file [$in_file]! Check name, location...\n"); } } sub give_help { prt("\n"); prt("$pgmname: version $VERS\n"); prt("Usage: $pgmname [options] in-file\n"); prt("Options:\n"); prt(" --help (-h or -?) = This help, and exit 0.\n"); prt(" --verb[n] (-v) = Bump [or set] verbosity. def=$verbosity\n"); prt(" --load (-l) = Load LOG at end. ($outfile)\n"); prt(" --out (-o) = Write output to this file.\n"); prt(" --fatal (-f) = Skip fatal (def=$show_fatal).\n"); prt(" --errors (-e) = Skip fatal (def=$show_errors).\n"); prt(" --warnings (-w) = Skip warnings (def=$show_warnings).\n"); } # eof - msvclog.pl