mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
656 lines
16 KiB
656 lines
16 KiB
# 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";
|
|
|
|
#
|
|
# 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 = <CWD>;
|
|
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'};
|
|
|
|
if (!$BuildMailMsg) {
|
|
if ($OfficialBuildMachine) {
|
|
$BuildMailMsg = "Official Build";
|
|
} else {
|
|
$BuildMailMsg = "Private Build";
|
|
}
|
|
}
|
|
|
|
$BuildCategory = "Unknown";
|
|
$BuildCategory = "Private" if $BuildMailMsg =~ /priv/i;
|
|
$BuildCategory = "OFFICIAL" if $BuildMailMsg =~ /official/i;
|
|
$BuildCategory = "MiniLoop" if $BuildMailMsg =~ /mini/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);
|
|
|
|
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 {
|
|
$BuildMail .= "Build is available on \\\\$MyComputername\\latest\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" . ReadPostBuildErrors();
|
|
|
|
$BuildMail .= $PostBuildErrorContents if $PostBuildErrorContents;
|
|
|
|
$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 = (split /;/, $MyDl);
|
|
|
|
} elsif ($SuspectsOnly and scalar @Suspects) {
|
|
@MailTargets = @Suspects;
|
|
|
|
} elsif ($SuspectsToo and scalar @Suspects) {
|
|
@MailTargets = ((map {"CC:$_"} split /;/, $MyDl), @Suspects);
|
|
|
|
} elsif (scalar @Changers) {
|
|
@MailTargets = ((map {"CC:$_"} split /;/, $MyDl), @Changers);
|
|
|
|
} else {
|
|
@MailTargets = (split /;/, $MyDl);
|
|
}
|
|
|
|
($FromAddr) = split /;/, $MyDl;
|
|
|
|
if ($Fake) {
|
|
print "sendmsg parameters:\n";
|
|
for ('-v', $FromAddr."DisableOof", $BuildMailSubject, $BuildMail, @MailTargets) {
|
|
print "<<$_>>";
|
|
}
|
|
print "\n";
|
|
|
|
} else {
|
|
#
|
|
# Really send the message
|
|
#
|
|
$rc = sendmsg ('-v', $FromAddr."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 (<BMFILE>) {
|
|
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 = "";
|
|
@Changers = ();
|
|
@Suspects = ();
|
|
|
|
#
|
|
# If this was a build failure, process ErrorFile
|
|
#
|
|
if ($BuildFailed) {
|
|
my($rc) = open FD, $ErrorFile or warn $ErrorFile, ": ", $!, "\n";
|
|
if ($rc) {
|
|
for (<FD>) {
|
|
$BuildErrs .= $_;
|
|
CaptureBuildFailure($_);
|
|
}
|
|
close FD;
|
|
|
|
} 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 (<FD>) {
|
|
$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 (<FD>) {
|
|
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 ReadPostBuildErrors {
|
|
my($pbcontents) = "";
|
|
my(@errfiles) = ();
|
|
|
|
$PostBuildErrorContents = ""; # global
|
|
|
|
$rc = open FD, $ErrorFile;
|
|
return "Sorry, unable to locate $ErrorFile\n" unless $rc;
|
|
|
|
for (<FD>) {
|
|
$pbcontents .= $_;
|
|
if (/\ssee:?\s+(\S+)/i) {
|
|
$foo = $1;
|
|
$foo =~ s/\.$//;
|
|
push @errfiles, $foo;
|
|
}
|
|
}
|
|
close FD;
|
|
|
|
for (@errfiles) {
|
|
$rc = open FD, $_;
|
|
if (not $rc) {
|
|
$PostBuildErrorContents .= "\nUnable to open $_: $!\n";
|
|
next;
|
|
}
|
|
|
|
|
|
$PostBuildErrorContents .= "\nContents of $_\n";
|
|
for (<FD>) {
|
|
$PostBuildErrorContents .= $_;
|
|
}
|
|
close FD;
|
|
$PostBuildErrorContents .= "\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 (<FD>) {
|
|
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 (<FD>) {
|
|
chomp;
|
|
($mname, $bn) = split /=/;
|
|
$BuildNumber = $bn if $mname =~ /BUILDNUMBER/i;
|
|
}
|
|
close FD;
|
|
|
|
return $BuildNumber;
|
|
}
|