# Filename: sendbuildstats.pl # # Have any changes to this file reviewed by DavePr, BryanT, or WadeLa # before checking in. # Any changes need to verified in all standard build/rebuild scenarios. # require $ENV{'sdxroot'} . '\TOOLS\sendmsg.pl'; # # Globals # $BuildMachinesRelPathname = "TOOLS\\BuildMachines.txt"; $BuildMachinesFile = $ENV{ "RazzleToolPath" } . "\\BuildMachines.txt"; $SdDotMapPathname = "sd.map"; $MaxBuildErrSize = 32 * 1024; # # Usage variables # $PGM='SendBuildStats:'; $Usage = "\n" . 'Usage: SendBuildStats [-v] [-o | -t] -s | -w | -fb[:build.err] | -fpb[:pberr]' . " [-m msg]\n". "\n". " -? | -help help\n". " -v | -verbose verbose\n". " -o | -only send only to suspect list\n". " -t | -too put suspect list on To: line, DL on CC:\n". " -s | -success build success\n". " -w | -warn send out warning\n". " -fb[:fname] | -buildfailure build failed, fname is the build.err file\n". " -fpb[:fname] | -postbuildfailure post build failed\, fname is the postbuild.err file\n". " -f fname name of error file (i.e. build.err, postbuild.err)\n". " -m msg rest of line is a message to include in the mail\n"; # # debug routine for printing out variables # sub pvar { for (@_) { print "\$$_ = $$_\n"; } } # # signal catcher (at least this would work on unix) # sub catch_ctrlc { printf $LogHandle "$PGM Aborted.\n" if $LogHandle; die "$PGM Aborted.\n"; } $SIG{INT} = \&catch_ctrlc; # # Get the current directory # open CWD, 'cd 2>&1|'; $CurrDir = ; close CWD; chomp $CurrDir; $CurrDrive = substr($CurrDir, 0, 2); # # Check variables expected to be set in the environment. # $sdxroot = $ENV{'SDXROOT'} or die $PGM, "SDXROOT not set in environment\n"; $MyComputername = $ENV{'COMPUTERNAME'} or die $PGM, "COMPUTERNAME not set in environment\n"; $MyBuildArch = $ENV{'_BuildArch'} or die $PGM, "_BuildArch not set in environment\n"; $MyBuildType = $ENV{'_BuildType'} or die $PGM, "_BuildType not set in environment\n"; $MyBranch = $ENV{'_BuildBranch'} or die $PGM, "_BuildBranch not set in environment\n"; $BuildDotChanges = $ENV{'sdxroot'} . '\build.changes'; $BuildDotChangedFiles = $ENV{'sdxroot'} . '\build.changedfiles'; $BuildDateFile = $ENV{'sdxroot'} . '\__blddate__'; $BuildMailMsg = $ENV{'BuildMailMsg'}; $BuildNumberFile = $ENV{'sdxroot'} . '\__bldnum__'; # # Initialize variables # $Success = 0; # only one of these gets set $Warn = 0; $BuildFailed = 0; $PostBuildFailed = 0; $Fail = 0; # set if either BuildFailed or PostBuildFailed $ErrorFile = 0; # will hold the name of the build.err file $Fake = 0; # fake output flag $Verbose = 0; # verbose output flag $SuspectsOnly = 0; # flag to send mail only to the suspect list. $SuspectsToo = 0; # flag to send mail to the suspect list and CC everyone else. $BuildDate = ""; # set on first call to ReadBuildDate() $BuildNumber = ""; # set on first call to ReadBuildNumber() # # Determine if this machine is an Official Build machine or not # $OfficialBuildMachine = $ENV{'OFFICIAL_BUILD_MACHINE'}; $CoverageBuild = $ENV{'_COVERAGE_BUILD'}; if (!$BuildMailMsg) { if ($OfficialBuildMachine) { $BuildMailMsg = "Official Build"; } else { $BuildMailMsg = "Private Build"; } } # # BUGBUG - I don't understand why there is an OfficeBuildMachine flag and a PrivateBuild flag # they appear not to be mutually excusive # $BuildCategory = "Unknown"; $BuildCategory = "Private" if $BuildMailMsg =~ /priv/i; $BuildCategory = "OFFICIAL" if $BuildMailMsg =~ /official/i; $BuildCategory = "MiniLoop" if $BuildMailMsg =~ /mini/i; $BuildCategory = "Coverage" if $BuildMailMsg =~ /coverage/i; $SpecialMsg = 0; # # Get Complete Build Number if possible # $CompleteBuildNumber = ReadBuildNumber() . "\.$MyBuildArch$MyBuildType\.$MyBranch\." . ReadBuildDate(); # # process arguments # for (@ARGV) { if ($SpecialMsg) { $SpecialMsg .= " $_"; next; } if (/^-m$/i or /^-msg$/i or /^-message$/i) { $SpecialMsg = "***"; next; } if ($GetFname) { $fname = $_; $GetFname = 0; next; } if (/^-fake$/i) { $Fake++; next; } if (/^-v$/i or /^-verbose$/i) { $Verbose++; next; } if (/^-t$/i or /^-too$/) { $SuspectsToo++; next; } if (/^-o$/i or /^-only$/) { $SuspectsOnly++; next; } if (/^-s$/i or /^-success$/i or /^-successful$/i) { $Success++; next; } if (/^-w$/i or /^-warn$/i) { $Warn++; next; } if (/^-f$/i) { $GetFname = 1; next; } if (/^-fb(:.*)?$/i or /^-buildfailure(:.*)?$/i) { $BuildFailed++; $Fail++; if ($1) { $ErrorFile = $1; $ErrorFile =~ s/://; $SetErrFile++; } else { $ErrorFile = $ENV{'sdxroot'} . '\build.err'; } next; } if (/^-fpb(:.*)?$/i or /^-postbuildfailure(:.*)?$/i) { $PostBuildFailed++; $Fail++; if ($1) { $ErrorFile = $1; $ErrorFile =~ s/://; $SetErrFile++; } else { if ( -e $ENV{'_NTTREE'} . '\build_logs\postbuild.err') { $ErrorFile = $ENV{'_NTTREE'} . '\build_logs\postbuild.err'; } else { $ErrorFile = '\\\\' . $MyComputername . '\latest\build_logs\postbuild.err'; } } next; } if (/^-?$/i or /^-help$/) { print $Usage; exit 0; } die $Usage; } $SpecialMsg .= " ***\n" if $SpecialMsg; # # Sanity Check arguments # pvar Success, Warn, BuildFailed, ErrorFile, PostBuildFailed, fname, SetErrFile, SuspectsOnly if $Verbose; die $Usage unless $Success + $Warn + $BuildFailed + $PostBuildFailed == 1; die $Usage unless $SuspectsOnly + $SuspectsToo <= 1; die $Usage if $fname and $SetErrFile; $ErrorFile = $fname if $fname; # # Compute MyDl, BuildChanges, Changers, and Suspects # Will use to decide the recipients to send Message To # SetMyDl(); GetChangersAndSuspects(); # # Generate the appropriate build message. # $BuildMail = FormatBuildMailStart(); $PrivateBuild = ($BuildCategory =~ /private/i); # PriveteBuild gets messed up if it is a coverage build # so set PrivateBuild to true if it is a non-offical coverage build if(!$PrivateBuild && $CoverageBuild && !$OfficialBuildMachine) { $PrivateBuild = 1; } if ($Success) { # # Build was successful, so format a successful build message, and # send success build mail # #$BuildMailSubject = "Build Succeeded: $MyBuildArch$MyBuildType $CompleteBuildNumber"; $BuildMailSubject = "BUILD $MyComputername/$MyBranch: $BuildCategory $BuildDate $MyBuildArch$MyBuildType SUCCEEDED"; if ($PrivateBuild) { $BuildMail .= "Build is available on $ENV{'_NTTREE'}\n"; } else { if($CoverageBuild){ $ReleaseShare = "latest_cov"; }else{ $ReleaseShare = "latest"; } $BuildMail .= "Build is available on \\\\$MyComputername\\$ReleaseShare\n" . "\nChanges for this build include\n\n\n" . $BuildChanges; } } elsif ($Warn) { # # We are sending a warning message. # $BuildMailSubject = "BUILD $MyComputername/$MyBranch: $BuildCategory $BuildDate $MyBuildArch$MyBuildType warning"; } elsif ($BuildFailed) { # # Build failed, so format either a build.exe failure email and log data, # #$BuildMailSubject = "Build Failed: $MyBuildArch$MyBuildType $CompleteBuildNumber"; $BuildMailSubject = "BUILD $MyComputername/$MyBranch: $BuildCategory $BuildDate $MyBuildArch$MyBuildType FAILED"; $BuildMail .= "These failures occurred:\n\n\n" . $BuildErrs . "\n"; $BuildMail .= "\nChanges for this build include\n\n\n" . $BuildChanges unless $PrivateBuild; } else { # $PostBuildFailed # # or a postbuild failure email and log data # #$BuildMailSubject = "Build Failed (PostBuild): $MyBuildArch$MyBuildType $CompleteBuildNumber"; $BuildMailSubject = "BUILD $MyComputername/$MyBranch: $BuildCategory $BuildDate $MyBuildArch$MyBuildType FAILED - POSTBUILD"; $BuildMail .= "PostBuild Failure:\n\n\n" . ReadErrorFile(); $BuildMail .= $ErrorFileContents if $ErrorFileContents; $BuildMail .= "\nChanges for this build include\n\n\n" . $BuildChanges unless $PrivateBuild; } pvar BuildMailSubject,MyDl,MyComputername,MyBuildArch,MyBuildType if $Verbose; print $BuildMail if $Verbose; if ($PrivateBuild) { @MailTargets = ($MyDl); } elsif ($SuspectsOnly and scalar @Suspects) { @MailTargets = @Suspects; } elsif ($SuspectsToo and scalar @Suspects) { @MailTargets = ("CC:$MyDl", @Suspects); } elsif (scalar @Changers) { @MailTargets = ("CC:$MyDl", @Changers); } else { @MailTargets = ("$MyDl"); } if ($Fake) { print "sendmsg parameters:\n"; for ('-v', $MyDl."DisableOof", $BuildMailSubject, $BuildMail, @MailTargets) { print "<<$_>>"; } print "\n"; } else { # # Really send the message # $rc = sendmsg ('-v', $MyDl."DisableOof", $BuildMailSubject, $BuildMail, @MailTargets); print "WARNING: sendmsg failed!\n" if $rc; } exit 0; ## ## Support Subroutine Section ## # # Set MyDl. # For official build machines, extract this from BuildMachines.txt, # otherwise use _NT_BUILD_DL, USERNAME, or the script maintainer -- in that order. # sub SetMyDl { if ($OfficialBuildMachine) { $fname = $BuildMachinesFile; open BMFILE, $fname or die "Could not open: $fname\n"; for () { s/\s+//g; s/;.*$//; next if /^$/; my($vblmach, $vblprime, $vblbranch, $vblarch, $vbldbgtype, $vbldl, $disttype ) = split /,/; # # The BuildMachines.txt record is keyed by computername, architecture, type, and branch # if ( ($vblmach =~ /\Q$MyComputername\E/io) && ($vblarch =~ /\Q$MyBuildArch\E/io) && ($vbldbgtype =~ /\Q$MyBuildType\E/io) && ($vblbranch =~ /\Q$MyBranch\E/io) ) { close BMFILE; $MyPrime = $vblprime; $MyDl = $vbldl; return; } } printf $PGM . "Problem Encounterd. $MyComputername was NOT found in buildmachines.txt. dl defaults to DavePr\n"; $MyDl = "DavePr"; close BMFILE; } else { $MyDl = $ENV{'_NT_BUILD_DL'}; if (!$MyDl) { $MyDl = $ENV{'USERNAME'} or $MyDl = "DavePr"; } } } # # Construct the base message used in the various cases. # sub FormatBuildMailStart { my($msg); my($BuildDate); my($MacroName); if ($Success) { $msg = "Build Was Successful\n\n"; } elsif ($Warn) { $msg = "Build early-warning message\n\n"; } elsif ($BuildFailed) { $msg = "Build errors were found\n\n"; } else { # $PostBuildFailed $msg = "Postbuild errors were found\n\n"; } if (scalar @Suspects) { $msg .= "SUSPECTS:"; for (@Suspects) { $msg .= " $_"; } $msg .= "\n\n"; } $msg .= $BuildMailMsg . "\n" if $BuildMailMsg; $msg .= $SpecialMsg . "\n" if $SpecialMsg; $msg .= "\nBuild Name : $CompleteBuildNumber\n"; $msg .= "\nBuild Date : " . ReadBuildDate() . "\n"; $msg .= "Build Machine: $MyComputername\n"; $msg .= "Architecture : $MyBuildArch\n"; $msg .= "DbgType : $MyBuildType\n"; $msg .= "Branch : $MyBranch\n"; $msg .= "SdxRoot : $sdxroot\n"; $msg .= "DL Notified : $MyDl\n"; $msg .= "\n\n"; return $msg; } # # Canonicalize the prefix of a build path so we can make guesses about who # might have caused a build error based on who made changes to what. # sub CanonicalizeBuildPath { $_ = @_[0]; s/\\[^\\]+$//; # remove filename s/\\obj[^\\]*\\.*//i; # ignore OBJ directories s/\\daytona\\.*//i; # ignore common sub-directories ( s/\\i386\\.*//i; s/\\amd64\\.*//i; s/\\ia64\\.*//i; s/\\daytona\\.*//i; s/\\i386\\.*//i; s/\\amd64\\.*//i; s/\\ia64\\.*//i; s/^[a-z]:\\[^\\]+\\//i; # remove sdxroot # remove last directory component -- if we have at least three s/\\[^\\]+$// if 3 <= split /\\/; return $_; } # # As build error file is read, we are called to record the canonicalized paths found. # sub CaptureBuildFailure { $_ = @_[0]; chomp; $capture = ""; if (/NMAKE/) { if (/U1073/) { s/'$//; s/.*//; $capture = $_; } } else { s/[ (].*//; s/^[0-9]*>//; $capture = $_; } if ($capture) { $capture = CanonicalizeBuildPath $capture; $BuildFailure{$capture}++; } } # # Set $BuildChanges, $BuildErrs, @Changers, and @Suspects -- as appropriate. # sub GetChangersAndSuspects { $BuildChanges = ""; $BuildErrs = "Build Errors from $ErrorFile\n"; @Changers = (); @Suspects = (); $TruncatedBuildErrLines = 0; # # If this was a build failure, process ErrorFile # if ($BuildFailed) { my($rc) = open FD, $ErrorFile or warn $ErrorFile, ": ", $!, "\n"; if ($rc) { for () { if (length $BuildErrs < $MaxBuildErrSize) { $BuildErrs .= $_; } else { $TruncatedBuildErrLines++; } CaptureBuildFailure($_); } close FD; $BuildErrs .= "\n< Truncated last $TruncatedBuildErrLines lines of $ErrorFile >\n" if $TruncatedBuildErrLines; } else { $BuildErrs = "Sorry, unable to locate $ErrorFile\n"; } } # # Get the Changers and record the BuildChanges for use in the BuildMail. # my($rcc) = open FD, $BuildDotChanges or warn $BuildDotChanges, ": ", $!, "\n"; if ($rcc) { %Checklist=(); for () { $BuildChanges .= $_; next unless /^Change /; chop; s/'.*$//; s/.* by //; s/@.*//; s/.*\\//; tr/A-Z/a-z/; $Checklist{$_}++; } close FD; @Changers = sort keys %Checklist; } else { $BuildChanges = "Sorry, unable to locate $BuildDotChanges\n"; } # # Get the Suspects # if ($BuildFailed) { my($project, $change, $dev, $date, $time, $sdpath, $type); $rcc = open FD, $BuildDotChangedFiles or warn $BuildDotChangedFiles, ": ", $!, "\n"; if ($rcc) { %Checklist=(); for () { my($project, $change, $dev, $date, $time, $sdpath, $type) = split; next unless $type; $_ = $sdpath; s|#.*||; # strip #change s|//depot/[^/]*/||i; # strip //depot/lab tr|/|\\|; # / -> \ $canonpath = CanonicalizeBuildPath $_; next unless $BuildFailure{$canonpath}; print "Suspect $dev because of change $change affecting $canonpath\n" if $Verbose and not $Pinged{$change}; $Pinged{$change}++; $Checklist{$dev}++; } close FD; @Suspects = sort keys %Checklist; } } } # # Return the contents of the ErrorFile file. # sub ReadErrorFile { my($pbcontents) = ""; my(@errfiles) = (); $ErrorFileContents = ""; # global $rc = open FD, $ErrorFile; return "Sorry, unable to locate $ErrorFile\n" unless $rc; for () { $pbcontents .= $_; if (/\ssee:?\s+(\S+)/i) { $foo = $1; $foo =~ s/\.$//; push @errfiles, $foo; } } close FD; for (@errfiles) { $rc = open FD, $_; if (not $rc) { $ErrorFileContents .= "\nUnable to open $_: $!\n"; next; } $ErrorFileContents .= "\nContents of $_\n"; for () { $ErrorFileContents .= $_; } close FD; $ErrorFileContents .= "\n"; } return $pbcontents; } # # Set BuildDate from the contents of the BuildDate file and return it. # sub ReadBuildDate { my($rc, $mname, $bd); return $BuildDate if $BuildDate; $BuildDate = "UnknownBuildDate"; $rc = open FD, $BuildDateFile or warn $BuildDateFile, ": ", $!, "\n"; return $BuildDate unless $rc; for () { chomp; ($mname, $bd) = split /=/; $BuildDate = $bd if $mname =~ /BUILDDATE/i; } close FD; return $BuildDate; } # # Set BuildDate from the contents of the BuildDate file and return it. # sub ReadBuildNumber { my($rc, $mname, $bn); return $BuildNumber if $BuildNumber; $BuildNumber = "UnknownBuildNumber"; $rc = open FD, $BuildNumberFile or warn $BuildNumberFile, ": ", $!, "\n"; return $BuildNumber unless $rc; for () { chomp; ($mname, $bn) = split /=/; $BuildNumber = $bn if $mname =~ /BUILDNUMBER/i; } close FD; return $BuildNumber; }