--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bldsystemtools/commonbldutils/GenerateChangesReport.pl Tue Feb 02 01:39:43 2010 +0200
@@ -0,0 +1,820 @@
+#! perl
+
+use strict;
+use Getopt::Long;
+
+# TODO:
+# Improve performance by not accessing P4 for every source line in every MRP file
+# Handle situation where clean-src dir is missing by accessing MRP file from Perforce
+# Use autobuild database to get perforce information directly?
+# What should we do about change C reverts change B which reverted change A?
+# => Hope C has a good description, as this implementation will discard both A and B descriptions.
+
+#note: currently relies on existence of perforce report of previous build
+
+# Parameters passed to script
+my $Product = shift; # e.g. 9.3
+my $Platform = shift; # e.g. cedar
+my $CurentBuild = shift; # e.g. 03935
+my $CurrentCL = shift; # e.g. 775517
+shift; # e.g. 03935_Symbian_OS_v9.3
+my $CurrCodeLine = shift; # e.g. //epoc/master/
+my $PreviousBuild = shift; # the build number of the previous release (eg 03925)
+my $LogDirs = shift; # e.g. \\builds01\devbuilds
+my $CleanSourceDir = shift; # e.g. M:\clean-src (synched file from perforce)
+
+my $debug = 0; # set to 1 to print debug logfile and progress information
+
+GetOptions(
+ 'v' => \$debug);
+
+# Derived parameters
+my $CurrBldName = "$CurentBuild\_Symbian_OS_v$Product"; # e.g. 03935_Symbian_OS_v9.3
+
+
+# Global variables
+
+my $PreviousLogDir; # location of the logs associated with the previous release build
+my $PrevFileAndPath; # name of the previous release build's perforce report and its location
+my $PrevBldName; # e.g. 03925_Symbian_OS_v9.3
+
+my $MainLineCL = 0; # the CL of the build from which the current build
+ # was branched if it is on a delivery branch
+my $MainLineLine; # the codeline of the build from which the current
+ # build was branched if on a delivery branch
+
+my %allDefects; # hash containing all of the defect fixes
+my %allBreaks; # hash contains all of the delivered breaks
+my %changeToComponents; # hash containing lists of components affected by a given change
+my %reverted; # hash of reverted changelists
+
+# Tidy up directories to ensure they are in a standard format
+$CurrCodeLine =~ s/[^\/]$/$&\//;# ensure a trailing fwdslash for codeline
+$LogDirs =~ s/\\$//; # ensure no trailing back slash for codeline
+$CleanSourceDir =~ s/\\$//; # ensure no trailing back slash for codeline
+
+# add information to the debug log if the debug flag is set
+if ($debug)
+{
+ # Open an error log
+ open ERRORLOG, "> Errorlog.txt";
+ print ERRORLOG <<"END";
+Inputs:
+
+Product: $Product
+Platform: $Platform
+Current build: $CurentBuild
+Current build CL: $CurrentCL
+Current Build Name: $CurrBldName
+Previous Build: $PreviousBuild
+Log Directory: $LogDirs
+Clean Source Dir: $CleanSourceDir
+
+Errors:
+
+END
+}
+
+# If the previous release was from a delivery branch, then use the build from
+# from which the delivery branch was created by removing the letters (changes
+# on the files on the branch will also have been made on the main codeline)
+$PreviousBuild =~ s/[a-z]+(\.\d+)?//;
+
+# If the current build is on a delivery branch, then store the information about
+# the build from which it was branched as it will be necessary to get the descriptions
+# from perforce for versions on the branch and versions on the main codeline up to
+# where the branch was created separately
+
+if ($CurentBuild =~ /[a-z]+/)
+{
+ my $MainLineBuild = $CurentBuild;
+ $MainLineBuild =~ s/[a-z]+(\.\d+)?//;
+ # There is insufficient information here anyway, e.g. if M09999 failed and we did
+ # M09999.01 as the original candidate, then there is no way of telling the tool
+
+ my $MainLineBldName = "$MainLineBuild\_Symbian_OS_v$Product";
+ my $MainLinePerforce = "$LogDirs\\$MainLineBldName\\logs\\$MainLineBuild\_$Product" . "PC_Perforce_report.html";
+ ($MainLineCL, $MainLineLine) = GetPrevCodelineandCL($MainLinePerforce, $Platform);
+}
+
+# Construct the previous build name
+$PrevBldName = "$PreviousBuild\_Symbian_OS_v$Product";
+
+# Construct the name of the peforce report file for the previous external release
+# to look similar to this: 03925_9.3PC_Perforce_report.html
+my $PerforceReport = $PreviousBuild."_".$Product."PC_Perforce_report.html";
+
+# Look for $PerforceReport in the build logs directory and the log archive directory
+$PreviousLogDir = "$LogDirs\\$PrevBldName\\logs\\";
+
+$PrevFileAndPath = $PreviousLogDir.$PerforceReport;
+
+if (! -d $CleanSourceDir)
+{
+ # if the report is found in neither directory then die
+ if ($debug==1)
+ {
+ print ERRORLOG "Clean-src directory does not exist! ($CleanSourceDir)\n";
+ }
+ die "ERROR: Clean-src directory does not exist";
+}
+
+if (! -e $PrevFileAndPath)
+{
+ $PreviousLogDir = "$LogDirs\\logs\\$PrevBldName\\";
+ $PrevFileAndPath = $PreviousLogDir.$PerforceReport;
+}
+if (! -e $PrevFileAndPath)
+{
+ # if the report is found in neither directory then die
+ if ($debug==1)
+ {
+ print ERRORLOG "Could not find Perforce report of previous external release! ($PrevFileAndPath)\n";
+ }
+ die "ERROR: Cannot find previous Perforce report";
+}
+
+# Parse the Perforce report to extract the previous build's change list and codeline path
+my ($PreviousCL, $PrevCodeLine) = GetPrevCodelineandCL($PrevFileAndPath, $Platform);
+
+# Create the path of the current build's log directory
+my $CurrentLogDir = "$LogDirs\\$CurrBldName\\logs\\";
+
+# Obtain the component lists (arrays are passed back by reference, each element containing a component
+# name separated by one or more spaces from its associated mrp file as it appears in the component files)
+my ($GTprevCompList, $GTlatestCompList,
+ $TVprevCompList, $TVlatestCompList) = GetGTandTVcomponentLists($CurrentLogDir, $PreviousLogDir);
+
+# Consolidate the lists of components in to two hashes: one for the current build and one for the previous
+# release build. These contain an index to distinguish between GT and TechView components and another index
+# with the names of the components. The mrp files are the elements being indexed.
+my ($PrevMRP, $CurrMRP) = CreateMRPLists($GTprevCompList,
+ $GTlatestCompList,
+ $TVprevCompList,
+ $TVlatestCompList);
+
+# For each component, extract its source directories from its MRP file and add the information to a list
+my (@ComponentAndSource) = ProcessLists($Product,
+ $PrevCodeLine,
+ $Platform ,
+ $PrevMRP,
+ $CurrMRP,
+ $PreviousCL,
+ $CleanSourceDir);
+
+# Put together the HTML file using the components list with their associated source files
+(my $ChangesFileHTML) = CreateReleaseNotes($Product,
+ $CurrCodeLine,
+ $MainLineLine,
+ $PreviousCL,
+ $CurrentCL,
+ $MainLineCL,
+ @ComponentAndSource);
+
+if ($debug)
+{
+ close ERRORLOG;
+}
+
+print "FINISHED!! - $ChangesFileHTML\n";
+
+exit;
+
+#
+#
+# Gets component lists from the builds' log directories. Reads the contents
+# of the files in to arrays and returns references to these arrays.
+#
+# Inputs: Current and previous build's logfile locations
+# Outputs: Arrays containing contents of previous and current GTcomponents.txt
+# and TVcomponents.txt files (list of components with their associated
+# mrp files). An example array elment might contain:
+# "ChatScripts \sf\os\unref\orphan\comtt\chatscripts\group\testtools_chatscripts.mrp"
+#
+sub GetGTandTVcomponentLists
+{
+ my ($CurrLogPath, $PrevLogPath) = @_;
+
+ # Data to return: array of refs to arrays of contents of files
+ my @return;
+
+ # Files to read from
+ my @FileNames = (
+ $PrevLogPath."GTcomponents.txt",
+ $CurrLogPath."GTcomponents.txt",
+ $PrevLogPath."TechViewComponents.txt",
+ $CurrLogPath."TechViewComponents.txt",
+ );
+
+ foreach my $FileName (@FileNames)
+ {
+ if (!open(DAT, $FileName))
+ {
+ if ($debug)
+ {
+ print ERRORLOG "Could not open $FileName!\n";
+ }
+ die "ERROR: Could not open $FileName: $!";
+ }
+ push @return, [<DAT>];
+ close DAT;
+ }
+
+ return @return;
+}
+
+#
+#
+# Creates two list of components and their associated mrp files - one for the previous
+# build and one for the current build
+#
+# Inputs: the contents of the GT and TV components files
+# Outputs: Two lists of components and their mrp files, one from the previous build
+# and one from the current build
+#
+#
+sub CreateMRPLists
+{
+ # references to the contents of the components files
+ my $GTprevFile = shift;
+ my $GTlatestFile = shift;
+ my $TVprevFile = shift;
+ my $TVlatestFile = shift;
+
+ my %PreviousMrps; #Hash of all Components and their MRP file locations for the previous build
+ my %CurrentMrps; #Hash of all Components and their MRP file locations for the current build
+
+ # Add mrp files to a hash indexed under GT or TV and by component names
+ foreach my $case (
+ [\%PreviousMrps, 'GT', $GTprevFile],
+ [\%CurrentMrps, 'GT', $GTlatestFile],
+ [\%PreviousMrps, 'TV', $TVprevFile],
+ [\%CurrentMrps, 'TV', $TVlatestFile],
+ )
+ {
+ foreach my $element (@{$case->[2]})
+ {
+ if ( $element =~ /(.*)\s+(\\sf\\.*)$/ )
+ {
+ ${$case->[0]}{$case->[1]}{uc($1)} = lc($2);
+ }
+ }
+ }
+
+ return \%PreviousMrps, \%CurrentMrps;
+}
+
+
+#
+#
+# Use the contents of the MRP files to create a single list containing the GT and TechView
+# components paired with associated source files.
+#
+# Inputs: Product, Codeline, Previous Codeline, Platform, Both lists of mrps,
+# Previous Changelist number, Build machine's clean source directory which
+# contains copies of the current build's mrp files.
+# Outputs: Array containing all components and their source locations
+#
+#
+sub ProcessLists
+{
+ my $Product = shift;
+ my $PrevCodeLine = shift;
+ my $Platform = shift;
+ my $PreviousMrps = shift; #Hash of MRPs at the Previous changelist number
+ my $CurrentMrps = shift; #Hash of MRPs at the Current changelist number
+ my $PrevCL = shift;
+ my $CleanSourceDir = shift;
+
+ my @PrevGTMrpComponents; #Array of GT Components at Previous changelist number
+ my @CurrGTMrpComponents; #Array of GT Components at Current changelist number
+ my @PrevTVMrpComponents; #Array of TV Components at Previous changelist number
+ my @CurrTVMrpComponents; #Array of TV Components at Current changelist number
+
+ # Isolate hashes
+ my $CurrGT = $CurrentMrps->{'GT'};
+ my $CurrTV = $CurrentMrps->{'TV'};
+ my $PrevGT = $PreviousMrps->{'GT'};
+ my $PrevTV = $PreviousMrps->{'TV'};
+
+ # Append the location of the clean source directory for the current build
+ # to all the mrp files. If a file appears only in the previous build (i.e.
+ # a component has been removed) its location in perforce is later substituted
+ # in place of this path
+ foreach my $component (sort keys %$CurrGT)
+ {
+ $CurrGT->{$component} =~ s|\\sf|$CleanSourceDir|ig;
+ $CurrGT->{$component} =~ s|\\|\/|g;
+ push @CurrGTMrpComponents, "$component\t$CurrGT->{$component}";
+ }
+
+ foreach my $component (sort keys %$CurrTV)
+ {
+ $CurrTV->{$component} =~ s|\\sf|$CleanSourceDir|ig;
+ $CurrTV->{$component} =~ s|\\|\/|g;
+ push @CurrTVMrpComponents, "$component\t$CurrTV->{$component}";
+ }
+
+ foreach my $component (sort keys %$PrevGT)
+ {
+ $PrevGT->{$component} =~ s|\\sf|$CleanSourceDir|ig;
+ $PrevGT->{$component} =~ s|\\|\/|g;
+ push @PrevGTMrpComponents, "$component"."\t"."$PrevGT->{$component}";
+ }
+
+ foreach my $component (sort keys %$PrevTV)
+ {
+ $PrevTV->{$component} =~ s|\\sf|$CleanSourceDir|ig;
+ $PrevTV->{$component} =~ s|\\|\/|g;
+ push @PrevTVMrpComponents, "$component"."\t"."$PrevTV->{$component}";
+ }
+
+ # add any components that appear only in the previous build's list to the
+ # current build's list with its location in perforce.
+ foreach my $PrevGTComp(@PrevGTMrpComponents)
+ {
+ my $match = 0;
+ #Compare component lists to ensure they contain the same components.
+ foreach my $CurrGTComp(@CurrGTMrpComponents)
+ {
+ $match = 1 if($PrevGTComp eq $CurrGTComp);
+ }
+
+ #If a component is found in the Previous list which isn't in the Current list,
+ #then insert it into the Current list with the previous build's path
+ if($match == 0)
+ {
+ $PrevGTComp =~ s|\/|\\|g;
+ $PrevGTComp =~ s|\Q$CleanSourceDir\E\\|$PrevCodeLine|ig;
+ $PrevGTComp =~ s|\\|\/|g;
+ push @CurrGTMrpComponents, $PrevGTComp;
+ }
+ }
+
+ # add any components that appear only in the previous build's list to the
+ # current build's list with its location in perforce.
+ foreach my $PrevTVComp(@PrevTVMrpComponents)
+ {
+ my $match = 0;
+ #Compare component lists to ensure they contain the same components.
+ foreach my $CurrTVComp(@CurrTVMrpComponents)
+ {
+ $match = 1 if($PrevTVComp eq $CurrTVComp);
+ }
+
+ #If a component is found in the Previous list which isn't in the Current list,
+ #then insert it into the Current list
+ if($match == 0)
+ {
+ $PrevTVComp =~ s|$CleanSourceDir|$PrevCodeLine|ig;
+ push @CurrTVMrpComponents, $PrevTVComp;
+ }
+ }
+
+ # Combine current GT and TV components, with a boundary marker
+ my @CurrMrpComponents = (@CurrGTMrpComponents, "**TECHVIEWCOMPONENTS**", @CurrTVMrpComponents);
+
+ # Swap the back slashes for forward slashes
+ $CleanSourceDir =~ s/\\/\//g;
+ $PrevCodeLine =~ s/\\/\//g;
+
+ #Use the MRP file for each component to obtain the source directory locations
+ my @ComponentAndSource;
+ foreach my $ComponentLine(@CurrMrpComponents)
+ {
+ #Array to hold mrp file contents
+ my @MrpContents;
+
+ # if the file is in the Clean Source Directory then read its contents from there
+ if($ComponentLine =~ /.*(\Q$CleanSourceDir\E.*)/)
+ {
+ my $MrpFile = $1;
+ $MrpFile =~ s/\.mrp.*$/\.mrp/; #drop any trailing spaces or tabs
+ if (-e $MrpFile)
+ {
+ open(FILE, $MrpFile);
+ @MrpContents=<FILE>;
+ close FILE;
+ }
+ }
+ elsif($ComponentLine =~ /.*(\Q$PrevCodeLine\E.*)/)
+ {
+ # If a component has been removed between the previous build and the current one then
+ # its MRP file will only exist at the PrevCL
+ @MrpContents = `p4 print -q $1...\@$PrevCL`; # access Perforce via system command
+ }
+ elsif($ComponentLine =~ /\*\*TECHVIEWCOMPONENTS\*\*/)
+ {
+ push @MrpContents, $ComponentLine;
+ }
+
+ #Construct an array containing components in uppercase followed by
+ #all their sourcelines in lowercase
+ foreach my $line(@MrpContents)
+ {
+ if($line =~ /^component\s+(.*)/i)
+ {
+ my $ComponentName = uc($1);
+ push @ComponentAndSource, $ComponentName;
+ }
+ elsif($line =~ /^source\s+(.*)/i)
+ {
+ my $Source = lc($1);
+ $Source =~ s/\\/\//g;
+ $Source =~ s|/sf/||;
+ $Source =~ s|/product/|$Platform/product|ig;
+ push @ComponentAndSource, $Source;
+ }
+ elsif($line =~ /TECHVIEWCOMPONENTS/)
+ {
+ push @ComponentAndSource, $line;
+ }
+ }
+ }
+ return @ComponentAndSource;
+}
+
+#
+# Format the changes associated with a component
+#
+# Inputs:
+# reference to hash of change descriptions by changelist number,
+# component name,
+# reference to (formatted) list of names of changed components
+# reference to list of names of unchanged components
+#
+# Updates:
+# adds component name to changed or unchanged list, as appropriate
+#
+sub PrintComponentDescriptions(\%$\@\@)
+{
+ my ($Descriptions,$CompName,$ChangedComponents,$UnchangedComponents) = @_;
+
+ if (scalar keys %{$Descriptions} == 0)
+ {
+ # no changes in this component
+ push @{$UnchangedComponents}, $CompName;
+ return;
+ }
+ push @{$ChangedComponents}, "<a href=\"#$CompName\">$CompName</a>";
+
+ # Format the changes for this component
+
+ my @CompLines = ("<h2><a name=\"$CompName\"/>$CompName</h2>");
+ foreach my $change (reverse sort keys %{$Descriptions})
+ {
+ # Heading for the change description
+ my $summary = shift @{$$Descriptions{$change}};
+ $summary =~ s/(on .*)\s+by.*//;
+ $summary = "<a href=\"#$change\">Change $change</a> $1";
+ push @CompLines, "<p><a name=\"#$CompName $change\"/><b>$summary</b>";
+ # Body of the change description
+ push @CompLines, "<pre>";
+ push @CompLines,
+ grep { $_; } # ignore blank lines
+ @{$$Descriptions{$change}};
+ push @CompLines, "</pre>";
+
+ # record the component in the cross-reference table
+ push @{$changeToComponents{$change}}, "<a href=\"#$CompName $change\">$CompName</a>";
+ }
+
+ &PrintLines(@CompLines);
+}
+
+#
+#
+# Creates the Release Notes html file by extracting the perforce descriptions for each
+# change that has been made to the components
+#
+# Inputs: Product, Source path of the product (i.e.//EPOC/master), the source path on the
+# main codeline if a delivery branch is being used, Previous changelist,
+# Current changelist, changelist from which the branch was made if a branch is being
+# used, array of components and their source.
+# Outputs: Release Notes html file.
+#
+# Algorithm:
+# Loop through the component&source array
+# Determine whether a boundary, component name, a source dir
+# If a source dir, query perforce to see what changelists have affected it in the period that we're interested in
+# If it has been affected, add the changelist to this component's changelists, unless it's already there
+#
+# This is rather inefficient :-(
+#
+sub CreateReleaseNotes
+{
+ my $Product = shift;
+ my $Srcpath = shift;
+ my $MainLinePath = shift;
+ my $PrevCL = shift;
+ my $CurrentCL = shift;
+ my $BranchCL = shift;
+ my @ComponentAndSource = @_;
+
+ #Reset all arrays to NULL
+ my @PrevGTMrpComponents = ();
+ my @CurrGTMrpComponents = ();
+ my @PrevTVMrpComponents = ();
+ my @CurrTVMrpComponents = ();
+ my @CurrMrpComponents = ();
+ my @Components = ();
+ my @UnchangedComponents = ();
+ my @ChangedComponents = ();
+ my %changeProcessed = (); # hash of changelists processed for this component
+ my %changeToDescription = (); # hash of descriptions for non-reverted changelists
+
+ $PrevCL++; # increment changelist number so we don't include the very first submission
+ # - it would have been picked up in the last run of this script
+
+ my $ProductName = "Symbian_OS_v$Product\_Changes_Report";
+
+ open OUTFILE, "> $ProductName.html" or die "ERROR: Can't open $ProductName.html for output\n$!";
+ print OUTFILE <<HEADING_EOF;
+<html>\n\n<head>\n<title>$ProductName</title>\n</head>\n\n
+<body bgcolor="#ffffff" text="#000000" link="#5F9F9F" vlink="5F9F9F">\n
+<font face=verdana,arial,helvetica size=4>\n\n<hr>\n\n
+<a name="list"><h1><center>$ProductName</center></h1></a>
+<p><center>----------------------------------------</center>\n
+<h2><center><font color = "blue">GT Components</font></center></h2>\n
+HEADING_EOF
+
+ my $CompName;
+ my $dirCount = 0;
+
+
+ # Loop through the list of elements running perforce commands to obtain the descriptions
+ # of any changes that have been made since the last release build
+ foreach my $element(@ComponentAndSource)
+ {
+ # Is it the boundary?
+ if($element =~ /\*\*TECHVIEWCOMPONENTS\*\*/)
+ {
+ # print out the accumulated GT component, if any
+ &PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
+ $dirCount = 0;
+ # no need to clear the hashes, because that will happen at the first TV component
+
+ print OUTFILE "<h2><center><font color = \"blue\">Techview Components</font></center></h2>\n";
+ }
+ # Is it a component name?
+ elsif($element =~ /^([A-Z].*)/)
+ {
+ my $newName = $1;
+
+ &PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
+ $dirCount = 0;
+
+ $CompName = $newName;
+ %changeProcessed = ();
+ %changeToDescription = ();
+ print "Processing $CompName...\n" if ($debug);
+ }
+ # Is it a source directory?
+ elsif($element =~ /^([a-z].*?)\s*$/)
+ {
+ my $Topdir = $1;
+ $dirCount++;
+
+ # If it contains spaces, quote it
+ if($Topdir =~ /.*\s+.*/)
+ {
+ $Topdir = "\"$Topdir\"";
+ }
+
+ # Find the changes that have affected this dir
+ # (Changes come back in reverse chronological order)
+ my @CompChange = `p4 changes -l $Srcpath$Topdir...\@$PrevCL,\@$CurrentCL`;
+
+ # if the current release is on a branch then the p4 command also needs to be run
+ # to capture changes on the codeline before the files were branched
+ if ($MainLinePath)
+ {
+ # So far, @CompChange contains the info from the delivery
+ # branch only
+ # The earliest changelist on the delivery branch is the creation
+ # of the branch, which is not interesting to anyone.
+ # Hence, remove it here:
+
+ # Work backwards from the end of the output chopping off the
+ # last item till we've chopped off a changelist header
+ my $choppedText;
+ do
+ {
+ # Remove last line
+ $choppedText = pop @CompChange;
+ }
+ until (!defined $choppedText || $choppedText =~ m/^Change\s\d+\s/);
+
+ # Now append the earlier changes from the main line
+ my @extrainfo = `p4 changes -l $MainLinePath$Topdir...\@$PrevCL,\@$BranchCL`;
+ push @CompChange, @extrainfo;
+ }
+
+ # Normalise the output of P4
+ @CompChange = map { split "\n"; } @CompChange;
+
+ # Extract change descriptions into a hash keyed on changelist number
+ my %moreChangeDescriptions;
+ my $change;
+ foreach my $line (@CompChange)
+ {
+ if ($line =~ /^Change\s(\d+)\s/)
+ {
+ my $newchange = $1;
+ $change = "";
+ # ignore if already processed for this component
+ next if ($changeProcessed{$newchange});
+ $changeProcessed{$newchange} = 1;
+ $change = $newchange;
+ }
+ next if (!$change);
+ push @{$moreChangeDescriptions{$change}}, $line;
+ }
+
+ # Process changes (newest first), extracting the <EXTERNAL> section and
+ # processing any reversion information
+ foreach my $change (reverse sort keys %moreChangeDescriptions)
+ {
+ if ($reverted{$change})
+ {
+ print "REMARK: $CompName - deleting description of reverted change $change\n";
+ delete $moreChangeDescriptions{$change};
+ next;
+ }
+
+ my @changeLines = @{$moreChangeDescriptions{$change}};
+
+ my @revisedLines = ();
+ push @revisedLines, shift @changeLines; # keep the "Change" line
+
+ my $line;
+ while ($line = shift @changeLines)
+ {
+ last if ($line =~ /<EXTERNAL>$/);
+
+ $line =~ s/^\t+//;
+ $line =~ s/\&/&/g;
+ $line =~ s/\</</g;
+ $line =~ s/\>/>/g;
+ $line =~ s/\"/"/g; # quote the " character as well
+
+ push @revisedLines, $line;
+ }
+
+ if (scalar @changeLines == 0)
+ {
+ # consumed the whole description without seeing <EXTERNAL>
+ # must be old format
+ @{$changeToDescription{$change}} = @revisedLines;
+ printf ".. unformatted change %d - %d lines\n", $change, scalar @changeLines if ($debug);
+ next;
+ }
+
+ # Must be properly formatted change description
+ # discard everything seen so far except the "Change" line.
+ @revisedLines = (shift @revisedLines);
+ while ($line = shift @changeLines)
+ {
+ last if ($line =~ /<\/EXTERNAL>$/);
+
+ $line =~ s/^\t+//;
+ $line =~ s/\&/&/g;
+ $line =~ s/\</</g;
+ $line =~ s/\>/>/g;
+ $line =~ s/\"/"/g; # quote the " character as well
+
+ push @revisedLines, $line;
+
+ # Opportunity to pick information out of changes as they go past
+ if ($line =~ /^\s*((DEF|PDEF|INC)\d+):?\s/)
+ {
+ $allDefects{$1} = $line;
+ next;
+ }
+ if ($line =~ /^(BR[0-9.]+)\s/)
+ {
+ $allBreaks{$1} = $line;
+ next;
+ }
+
+ }
+
+ # update the description for this change
+ @{$changeToDescription{$change}} = @revisedLines;
+ printf ".. formatted change %d - %d external lines\n", $change, scalar @revisedLines if ($debug);
+
+ # check for reversion information in rest of the formatted description
+ # submission checker delivers one <detail reverts=""> line per list element
+ foreach my $line (grep /<detail reverts=/, @changeLines)
+ {
+ if ($line =~ /<detail reverts=\s*\"(\d+)\"/) # changelist surrounded by "
+ {
+ my $oldchange = $1;
+ print "REMARK: Change $change reverts $oldchange\n";
+ $reverted{$oldchange} = $change;
+ }
+ }
+ }
+ }
+ }
+
+ # print description of last component
+ &PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
+
+ # Print additional tables of information
+ &PrintLines("<h2>Changed Components</h2>\n<nobr>", join(",</nobr> \n<nobr>", sort @ChangedComponents), "</nobr>") if (@ChangedComponents);
+
+ &PrintLines("<h2>Unchanged Components</h2>\n<nobr>", join(",</nobr> \n<nobr>", sort @UnchangedComponents), "</nobr>") if (@UnchangedComponents);
+
+ if (scalar @ChangedComponents)
+ {
+ &PrintLines("<h2>Components affected by each change</h2>");
+
+ &PrintLines("<TABLE>\n");
+ foreach my $change (reverse sort keys %changeToComponents)
+ {
+ &PrintLines("<TR valign=\"top\"><TD><a name=\"#$change\"/>$change</TD><TD>", join(", ", @{$changeToComponents{$change}}), "</TD></TR>\n");
+ }
+ &PrintLines("</TABLE>\n");
+ }
+
+ my @allDefectTitles = ();
+ foreach my $defect (sort keys %allDefects)
+ {
+ push @allDefectTitles,$allDefects{$defect};
+ }
+ &PrintLines("<h2>List of Defect Fixes</h2>", "<pre>", @allDefectTitles, "</pre>") if (scalar @allDefectTitles);
+
+ my @allBreakTitles = ();
+ foreach my $break (sort keys %allBreaks)
+ {
+ push @allBreakTitles,$allBreaks{$break};
+ }
+ &PrintLines("<h2>List of Breaks</h2>", "<pre>", @allBreakTitles, "</pre>") if (scalar @allBreakTitles);
+
+ &PrintLines("</BODY></HTML>");
+ close OUTFILE;
+
+ return "$ProductName.html";
+}
+
+sub PrintLines
+{
+ # Output field and record separators set (locally) to produce newlines
+ # between items in list, and another newline on the end
+ local $, = "\n";
+ local $\ = "\n";
+ print OUTFILE @_;
+}
+
+#
+#
+# Extracts the sourcecode path and changelist from a Perforce report file.
+#
+# Inputs: Location of a perforce report file, Platform (e.g. cedar)
+# Outputs: Source path of the product (e.g.//EPOC/master), changelist used in build
+#
+#
+sub GetPrevCodelineandCL
+{
+ my $FileAndPath = shift;
+ my $Platform = shift;
+
+ my $LogFile;
+ my $PrevCL;
+ my $PrevCodeline;
+
+ if (!open(DAT, $FileAndPath))
+ {
+ print ERRORLOG "Could not open $FileAndPath!\n" if $debug;
+ die "ERROR: Cannot open $FileAndPath: $!\n";
+ }
+ {
+ # Grab complete file in one string
+ local $/ = undef;
+ $LogFile = <DAT>;
+ }
+ close DAT;
+
+ if ($LogFile =~ m{ClientSpec.*?<td[^>]*>.*?(//.+?)$Platform/.*?</tr>}s)
+ {
+ $PrevCodeline = $1;
+ }
+
+ if ($LogFile =~ m{Perforce Change[Ll]ist.*?<td[^>]*>(.*?)</tr>}s)
+ {
+ # Perforce changelist table data may either be in form of "nnnnnn" or "ssssss @ nnnnnn"
+ # ssssss is the snapshot id, which may be a string of digits
+ # nnnnnn is the changelist number
+ #
+ # Get the later string of digits
+ ($PrevCL) = $1 =~ m{(\d+)\D*$};
+ }
+
+ unless ($PrevCL) { die "ERROR: Unable to parse previous changelist from $FileAndPath\n"; }
+ unless ($PrevCodeline) { die "ERROR: Unable to parse previous codeline from $FileAndPath\n"; }
+
+ return $PrevCL, $PrevCodeline;
+}
+
+