bldsystemtools/commonbldutils/GenerateChangesReport.pl
branchRCL_3
changeset 24 d90029decf65
parent 20 a9d4531388d0
child 33 54aa4a06a075
child 34 5e522efbae7b
equal deleted inserted replaced
20:a9d4531388d0 24:d90029decf65
     1 #
       
     2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 #
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 #
       
    12 # Contributors:
       
    13 #
       
    14 # Description: 
       
    15 #
       
    16 #! perl
       
    17 
       
    18 use strict;
       
    19 use Getopt::Long;
       
    20 
       
    21 # TODO:
       
    22 # Improve performance by not accessing P4 for every source line in every MRP file
       
    23 # Handle situation where clean-src dir is missing by accessing MRP file from Perforce
       
    24 # Use autobuild database to get perforce information directly?
       
    25 # What should we do about change C reverts change B which reverted change A? 
       
    26 #  => Hope C has a good description, as this implementation will discard both A and B descriptions.
       
    27 
       
    28 #note: currently relies on existence of perforce report of previous build
       
    29 
       
    30 # Parameters passed to script
       
    31 my $Product = shift;           	# e.g. 9.3
       
    32 my $Platform = shift;          	# e.g. cedar       
       
    33 my $CurentBuild = shift;       	# e.g. 03935
       
    34 my $CurrentCL = shift;         	# e.g. 775517
       
    35 shift;				# e.g. 03935_Symbian_OS_v9.3
       
    36 my $CurrCodeLine = shift;   	# e.g. //epoc/master/
       
    37 my $PreviousBuild = shift;	# the build number of the previous release (eg 03925)
       
    38 my $LogDirs = shift;           	# e.g. \\builds01\devbuilds
       
    39 my $CleanSourceDir = shift;	# e.g. M:\clean-src (synched file from perforce)
       
    40 
       
    41 my $debug = 0; 		       	# set to 1 to print debug logfile and progress information
       
    42 
       
    43 GetOptions(
       
    44   	'v' => \$debug);
       
    45 
       
    46 # Derived parameters
       
    47 my $CurrBldName = "$CurentBuild\_Symbian_OS_v$Product";	# e.g. 03935_Symbian_OS_v9.3
       
    48 
       
    49 
       
    50 # Global variables
       
    51 
       
    52 my $PreviousLogDir;		# location of the logs associated with the previous release build 
       
    53 my $PrevFileAndPath;		# name of the previous release build's perforce report and its location
       
    54 my $PrevBldName;                # e.g. 03925_Symbian_OS_v9.3
       
    55 
       
    56 my $MainLineCL = 0;             # the CL of the build from which the current build
       
    57                                 #    was branched if it is on a delivery branch
       
    58 my $MainLineLine;               # the codeline of the build from which the current
       
    59                                 #    build was branched if on a delivery branch
       
    60 
       
    61 my %allDefects;			# hash containing all of the defect fixes
       
    62 my %allBreaks;			# hash contains all of the delivered breaks
       
    63 my %changeToComponents;	# hash containing lists of components affected by a given change
       
    64 my %reverted;			# hash of reverted changelists
       
    65 
       
    66 # Tidy up directories to ensure they are in a standard format
       
    67 $CurrCodeLine =~ s/[^\/]$/$&\//;# ensure a trailing fwdslash for codeline
       
    68 $LogDirs =~ s/\\$//;		# ensure no trailing back slash for codeline
       
    69 $CleanSourceDir =~ s/\\$//;	# ensure no trailing back slash for codeline
       
    70 
       
    71 # add information to the debug log if the debug flag is set
       
    72 if ($debug)
       
    73 {
       
    74     # Open an error log
       
    75     open ERRORLOG, "> Errorlog.txt";
       
    76     print ERRORLOG <<"END";
       
    77 Inputs:
       
    78 
       
    79 Product: $Product
       
    80 Platform: $Platform
       
    81 Current build: $CurentBuild
       
    82 Current build CL: $CurrentCL
       
    83 Current Build Name: $CurrBldName
       
    84 Previous Build: $PreviousBuild
       
    85 Log Directory: $LogDirs
       
    86 Clean Source Dir: $CleanSourceDir
       
    87 
       
    88 Errors:
       
    89 
       
    90 END
       
    91 }
       
    92 
       
    93 # If the previous release was from a delivery branch, then use the build from 
       
    94 # from which the delivery branch was created by removing the letters (changes
       
    95 # on the files on the branch will also have been made on the main codeline)
       
    96 $PreviousBuild =~ s/[a-z]+(\.\d+)?//;
       
    97 
       
    98 # If the current build is on a delivery branch, then store the information about
       
    99 # the build from which it was branched as it will be necessary to get the descriptions
       
   100 # from perforce for versions on the branch and versions on the main codeline up to 
       
   101 # where the branch was created separately
       
   102 
       
   103 if ($CurentBuild =~ /[a-z]+/)
       
   104 {
       
   105     my $MainLineBuild = $CurentBuild;
       
   106     $MainLineBuild =~ s/[a-z]+(\.\d+)?//;
       
   107     # There is insufficient information here anyway, e.g. if M09999 failed and we did 
       
   108     # M09999.01 as the original candidate, then there is no way of telling the tool
       
   109     
       
   110     my $MainLineBldName = "$MainLineBuild\_Symbian_OS_v$Product";
       
   111     my $MainLinePerforce = "$LogDirs\\$MainLineBldName\\logs\\$MainLineBuild\_$Product" . "PC_Perforce_report.html";
       
   112     ($MainLineCL, $MainLineLine) = GetPrevCodelineandCL($MainLinePerforce, $Platform);
       
   113 }
       
   114 
       
   115 # Construct the previous build name
       
   116 $PrevBldName = "$PreviousBuild\_Symbian_OS_v$Product";
       
   117 
       
   118 # Construct the name of the peforce report file for the previous external release
       
   119 # to look similar to this:  03925_9.3PC_Perforce_report.html
       
   120 my $PerforceReport = $PreviousBuild."_".$Product."PC_Perforce_report.html";
       
   121 
       
   122 # Look for $PerforceReport in the build logs directory and the log archive directory
       
   123 $PreviousLogDir = "$LogDirs\\$PrevBldName\\logs\\";
       
   124 
       
   125 $PrevFileAndPath = $PreviousLogDir.$PerforceReport;
       
   126 
       
   127 if (! -d $CleanSourceDir)
       
   128 {
       
   129     # if the report is found in neither directory then die
       
   130     if ($debug==1)
       
   131     {
       
   132       print ERRORLOG "Clean-src directory does not exist! ($CleanSourceDir)\n";
       
   133     }
       
   134     die "ERROR: Clean-src directory does not exist";
       
   135 }
       
   136 
       
   137 if (! -e $PrevFileAndPath)
       
   138 { 
       
   139     $PreviousLogDir = "$LogDirs\\logs\\$PrevBldName\\";  
       
   140     $PrevFileAndPath = $PreviousLogDir.$PerforceReport;
       
   141 }
       
   142 if (! -e $PrevFileAndPath)
       
   143 {
       
   144     # if the report is found in neither directory then die
       
   145     if ($debug==1)
       
   146     {
       
   147       print ERRORLOG "Could not find Perforce report of previous external release! ($PrevFileAndPath)\n";
       
   148     }
       
   149     die "ERROR: Cannot find previous Perforce report";
       
   150 }
       
   151 
       
   152 # Parse the Perforce report to extract the previous build's change list and codeline path
       
   153 my ($PreviousCL, $PrevCodeLine) = GetPrevCodelineandCL($PrevFileAndPath, $Platform);
       
   154 
       
   155 # Create the path of the current build's log directory
       
   156 my $CurrentLogDir = "$LogDirs\\$CurrBldName\\logs\\";
       
   157 
       
   158 # Obtain the component lists (arrays are passed back by reference, each element containing a component
       
   159 # name separated by one or more spaces from its associated mrp file as it appears in the component files)
       
   160 my ($GTprevCompList, $GTlatestCompList, 
       
   161     $TVprevCompList, $TVlatestCompList) = GetGTandTVcomponentLists($CurrentLogDir, $PreviousLogDir);
       
   162 
       
   163 # Consolidate the lists of components in to two hashes: one for the current build and one for the previous
       
   164 # release build. These contain an index to distinguish between GT and TechView components and another index
       
   165 # with the names of the components. The mrp files are the elements being indexed.
       
   166 my ($PrevMRP, $CurrMRP) = CreateMRPLists($GTprevCompList, 
       
   167                                          $GTlatestCompList, 
       
   168                                          $TVprevCompList, 
       
   169                                          $TVlatestCompList);
       
   170 
       
   171 # For each component, extract its source directories from its MRP file and add the information to a list 
       
   172 my (@ComponentAndSource) = ProcessLists($Product, 
       
   173                                         $PrevCodeLine, 
       
   174                                         $Platform , 
       
   175                                         $PrevMRP, 
       
   176                                         $CurrMRP, 
       
   177                                         $PreviousCL,
       
   178                                         $CleanSourceDir);
       
   179 
       
   180 # Put together the HTML file using the components list with their associated source files
       
   181 (my $ChangesFileHTML) = CreateReleaseNotes($Product, 
       
   182                                            $CurrCodeLine,
       
   183                                            $MainLineLine,
       
   184                                            $PreviousCL, 
       
   185                                            $CurrentCL, 
       
   186                                            $MainLineCL,
       
   187                                            @ComponentAndSource);
       
   188 
       
   189 if ($debug)
       
   190 {
       
   191     close ERRORLOG;
       
   192 }
       
   193 
       
   194 print "FINISHED!! - $ChangesFileHTML\n";
       
   195 
       
   196 exit;
       
   197 
       
   198 #
       
   199 #
       
   200 # Gets component lists from the builds' log directories. Reads the contents 
       
   201 # of the files in to arrays and returns references to these arrays.
       
   202 #
       
   203 # Inputs:  Current and previous build's logfile locations
       
   204 # Outputs: Arrays containing contents of previous and current GTcomponents.txt
       
   205 #          and TVcomponents.txt files (list of components with their associated
       
   206 #          mrp files). An example array elment might contain:
       
   207 #          "ChatScripts	\sf\os\unref\orphan\comtt\chatscripts\group\testtools_chatscripts.mrp"
       
   208 #
       
   209 sub GetGTandTVcomponentLists
       
   210 {
       
   211     my ($CurrLogPath, $PrevLogPath) = @_;
       
   212 
       
   213     # Data to return: array of refs to arrays of contents of files
       
   214     my @return;
       
   215 
       
   216     # Files to read from
       
   217     my @FileNames = (
       
   218         $PrevLogPath."GTcomponents.txt",
       
   219         $CurrLogPath."GTcomponents.txt",
       
   220         $PrevLogPath."TechViewComponents.txt",
       
   221         $CurrLogPath."TechViewComponents.txt",
       
   222     );
       
   223 
       
   224     foreach my $FileName (@FileNames)
       
   225     {
       
   226         if (!open(DAT, $FileName)) 
       
   227         { 
       
   228             if ($debug)
       
   229             {
       
   230                 print ERRORLOG "Could not open $FileName!\n";
       
   231             }
       
   232             die "ERROR: Could not open $FileName: $!";
       
   233         }
       
   234         push @return, [<DAT>];
       
   235         close DAT;
       
   236     }
       
   237 
       
   238     return @return;
       
   239 }
       
   240 
       
   241 #
       
   242 #
       
   243 # Creates two list of components and their associated mrp files - one for the previous 
       
   244 # build and one for the current build
       
   245 #
       
   246 # Inputs: the contents of the GT and TV components files
       
   247 # Outputs: Two lists of components and their mrp files, one from the previous build
       
   248 #          and one from the current build
       
   249 #
       
   250 #
       
   251 sub CreateMRPLists
       
   252 {
       
   253     # references to the contents of the components files              
       
   254     my $GTprevFile = shift; 
       
   255     my $GTlatestFile = shift; 
       
   256     my $TVprevFile = shift; 
       
   257     my $TVlatestFile = shift; 
       
   258 
       
   259     my %PreviousMrps;    #Hash of all Components and their MRP file locations for the previous build
       
   260     my %CurrentMrps;     #Hash of all Components and their MRP file locations for the current build
       
   261     
       
   262     # Add mrp files to a hash indexed under GT or TV and by component names 
       
   263     foreach my $case (
       
   264         [\%PreviousMrps, 'GT', $GTprevFile],
       
   265         [\%CurrentMrps,  'GT', $GTlatestFile],
       
   266         [\%PreviousMrps, 'TV', $TVprevFile],
       
   267         [\%CurrentMrps,  'TV', $TVlatestFile],
       
   268         )
       
   269     {
       
   270         foreach my $element (@{$case->[2]})
       
   271           {
       
   272             if ( $element =~ /(.*)\s+(\\sf\\.*)$/ )
       
   273             {
       
   274                 ${$case->[0]}{$case->[1]}{uc($1)} = lc($2);
       
   275             }
       
   276           }
       
   277     }
       
   278     
       
   279     return \%PreviousMrps, \%CurrentMrps;
       
   280 }
       
   281 
       
   282 
       
   283 #
       
   284 #
       
   285 # Use the contents of the MRP files to create a single list containing the GT and TechView
       
   286 # components paired with associated source files.
       
   287 #
       
   288 # Inputs: Product, Codeline, Previous Codeline, Platform, Both lists of mrps, 
       
   289 #         Previous Changelist number, Build machine's clean source directory which
       
   290 #         contains copies of the current build's mrp files.
       
   291 # Outputs: Array containing all components and their source locations
       
   292 #
       
   293 #
       
   294 sub ProcessLists
       
   295 {
       
   296     my $Product = shift;
       
   297     my $PrevCodeLine = shift;
       
   298     my $Platform = shift;
       
   299     my $PreviousMrps = shift;   #Hash of MRPs at the Previous changelist number
       
   300     my $CurrentMrps = shift;    #Hash of MRPs at the Current changelist number
       
   301     my $PrevCL = shift;
       
   302     my $CleanSourceDir = shift;
       
   303 
       
   304     my @PrevGTMrpComponents;    #Array of GT Components at Previous changelist number
       
   305     my @CurrGTMrpComponents;    #Array of GT Components at Current changelist number
       
   306     my @PrevTVMrpComponents;    #Array of TV Components at Previous changelist number
       
   307     my @CurrTVMrpComponents;    #Array of TV Components at Current changelist number
       
   308 	
       
   309     # Isolate hashes
       
   310     my $CurrGT = $CurrentMrps->{'GT'};
       
   311     my $CurrTV = $CurrentMrps->{'TV'};
       
   312     my $PrevGT = $PreviousMrps->{'GT'};
       
   313     my $PrevTV = $PreviousMrps->{'TV'};
       
   314 
       
   315     # Append the location of the clean source directory for the current build
       
   316     # to all the mrp files. If a file appears only in the previous build (i.e.  
       
   317     # a component has been removed) its location in perforce is later substituted
       
   318     # in place of this path
       
   319     foreach my $component (sort keys %$CurrGT)
       
   320     {
       
   321 	$CurrGT->{$component} =~ s|\\sf|$CleanSourceDir|ig;
       
   322         $CurrGT->{$component} =~ s|\\|\/|g;
       
   323 	push @CurrGTMrpComponents, "$component\t$CurrGT->{$component}";
       
   324     }
       
   325 
       
   326     foreach my $component (sort keys %$CurrTV)
       
   327     {
       
   328 	$CurrTV->{$component} =~ s|\\sf|$CleanSourceDir|ig;
       
   329 	$CurrTV->{$component} =~ s|\\|\/|g;
       
   330 	push @CurrTVMrpComponents, "$component\t$CurrTV->{$component}";
       
   331     }
       
   332 
       
   333     foreach my $component (sort keys %$PrevGT)
       
   334     {
       
   335 	$PrevGT->{$component} =~ s|\\sf|$CleanSourceDir|ig;
       
   336 	$PrevGT->{$component} =~ s|\\|\/|g; 
       
   337 	push @PrevGTMrpComponents, "$component"."\t"."$PrevGT->{$component}";
       
   338     }
       
   339 
       
   340     foreach my $component (sort keys %$PrevTV)
       
   341     {
       
   342 	$PrevTV->{$component} =~ s|\\sf|$CleanSourceDir|ig;
       
   343 	$PrevTV->{$component} =~ s|\\|\/|g;
       
   344 	push @PrevTVMrpComponents, "$component"."\t"."$PrevTV->{$component}";
       
   345     }
       
   346     
       
   347     # add any components that appear only in the previous build's list to the 
       
   348     # current build's list with its location in perforce.
       
   349     foreach my $PrevGTComp(@PrevGTMrpComponents)
       
   350     {
       
   351     	my $match = 0;
       
   352     	#Compare component lists to ensure they contain the same components.
       
   353     	foreach my $CurrGTComp(@CurrGTMrpComponents)
       
   354     	{
       
   355 	    $match = 1 if($PrevGTComp eq $CurrGTComp);
       
   356 	}
       
   357 	
       
   358 	#If a component is found in the Previous list which isn't in the Current list, 
       
   359         #then insert it into the Current list with the previous build's path
       
   360 	if($match == 0)
       
   361         {
       
   362             $PrevGTComp =~ s|\/|\\|g;
       
   363             $PrevGTComp =~ s|\Q$CleanSourceDir\E\\|$PrevCodeLine|ig;
       
   364             $PrevGTComp =~ s|\\|\/|g;
       
   365             push @CurrGTMrpComponents, $PrevGTComp;    
       
   366         }
       
   367     }
       
   368 
       
   369     # add any components that appear only in the previous build's list to the 
       
   370     # current build's list with its location in perforce.
       
   371     foreach my $PrevTVComp(@PrevTVMrpComponents)
       
   372     {
       
   373     	my $match = 0;
       
   374     	#Compare component lists to ensure they contain the same components.
       
   375     	foreach my $CurrTVComp(@CurrTVMrpComponents)
       
   376     	{
       
   377 	    $match = 1 if($PrevTVComp eq $CurrTVComp);
       
   378 	}
       
   379 	
       
   380 	#If a component is found in the Previous list which isn't in the Current list, 
       
   381         #then insert it into the Current list
       
   382         if($match == 0)
       
   383         {
       
   384            $PrevTVComp =~ s|$CleanSourceDir|$PrevCodeLine|ig;
       
   385 	   push @CurrTVMrpComponents, $PrevTVComp;
       
   386         }
       
   387     }
       
   388 
       
   389     # Combine current GT and TV components, with a boundary marker
       
   390     my @CurrMrpComponents = (@CurrGTMrpComponents, "**TECHVIEWCOMPONENTS**", @CurrTVMrpComponents);
       
   391 
       
   392     # Swap the back slashes for forward slashes
       
   393     $CleanSourceDir =~ s/\\/\//g;
       
   394     $PrevCodeLine =~ s/\\/\//g;
       
   395  
       
   396     #Use the MRP file for each component to obtain the source directory locations
       
   397     my @ComponentAndSource;
       
   398     foreach my $ComponentLine(@CurrMrpComponents)
       
   399     {
       
   400         #Array to hold mrp file contents
       
   401         my @MrpContents;
       
   402 
       
   403         # if the file is in the Clean Source Directory then read its contents from there
       
   404 	if($ComponentLine =~ /.*(\Q$CleanSourceDir\E.*)/)
       
   405 	{ 
       
   406 	    my $MrpFile = $1;
       
   407 	    $MrpFile =~ s/\.mrp.*$/\.mrp/;  #drop any trailing spaces or tabs
       
   408             if (-e $MrpFile)
       
   409             {
       
   410                 open(FILE, $MrpFile);
       
   411                 @MrpContents=<FILE>;
       
   412                 close FILE;
       
   413             }          
       
   414 	}
       
   415         elsif($ComponentLine =~ /.*(\Q$PrevCodeLine\E.*)/)
       
   416         {
       
   417 	    # If a component has been removed between the previous build and the current one then 
       
   418 	    # its MRP file will only exist at the PrevCL
       
   419             @MrpContents = `p4 print -q $1...\@$PrevCL`; # access Perforce via system command 
       
   420         }
       
   421 	elsif($ComponentLine =~ /\*\*TECHVIEWCOMPONENTS\*\*/)
       
   422 	{
       
   423 	    push @MrpContents, $ComponentLine;
       
   424 	}
       
   425         
       
   426 	#Construct an array containing components in uppercase followed by 
       
   427         #all their sourcelines in lowercase
       
   428 	foreach my $line(@MrpContents)
       
   429 	{
       
   430 	    if($line =~ /^component\s+(.*)/i)
       
   431 	    {
       
   432 		my $ComponentName = uc($1);
       
   433 		push @ComponentAndSource, $ComponentName;
       
   434 	    }
       
   435 	    elsif($line =~ /^source\s+(.*)/i)
       
   436 	    {
       
   437 		my $Source = lc($1);
       
   438 		$Source =~ s/\\/\//g;
       
   439 		$Source =~ s|/sf/||;
       
   440 		$Source =~ s|/product/|$Platform/product|ig;
       
   441 		push @ComponentAndSource, $Source;
       
   442             }
       
   443 	    elsif($line =~ /TECHVIEWCOMPONENTS/)
       
   444 	    {
       
   445 		push @ComponentAndSource, $line;
       
   446 	    }
       
   447 	}
       
   448     }
       
   449     return @ComponentAndSource;
       
   450 }
       
   451 
       
   452 #
       
   453 # Format the changes associated with a component
       
   454 #
       
   455 # Inputs: 
       
   456 #   reference to hash of change descriptions by changelist number, 
       
   457 #	component name, 
       
   458 #   reference to (formatted) list of names of changed components
       
   459 #   reference to list of names of unchanged components
       
   460 #
       
   461 # Updates: 
       
   462 #   adds component name to changed or unchanged list, as appropriate
       
   463 #
       
   464 sub PrintComponentDescriptions(\%$\@\@)
       
   465 {
       
   466 	my ($Descriptions,$CompName,$ChangedComponents,$UnchangedComponents) = @_;
       
   467 	
       
   468 	if (scalar keys %{$Descriptions} == 0)
       
   469 	{
       
   470 		# no changes in this component
       
   471 		push @{$UnchangedComponents}, $CompName;
       
   472 		return;
       
   473 	}
       
   474 	push @{$ChangedComponents}, "<a href=\"#$CompName\">$CompName</a>";
       
   475 	
       
   476     # Format the changes for this component
       
   477     
       
   478 	my @CompLines = ("<h2><a name=\"$CompName\"/>$CompName</h2>");
       
   479     foreach my $change (reverse sort keys %{$Descriptions})
       
   480     {
       
   481         # Heading for the change description
       
   482         my $summary = shift @{$$Descriptions{$change}};
       
   483         $summary =~ s/(on .*)\s+by.*//;
       
   484         $summary = "<a href=\"#$change\">Change $change</a> $1";
       
   485         push @CompLines, "<p><a name=\"#$CompName $change\"/><b>$summary</b>";
       
   486         # Body of the change description
       
   487         push @CompLines, "<pre>";
       
   488         push @CompLines,
       
   489             grep { $_; }	# ignore blank lines
       
   490             @{$$Descriptions{$change}};
       
   491         push @CompLines, "</pre>";
       
   492 
       
   493 		# record the component in the cross-reference table
       
   494         push @{$changeToComponents{$change}}, "<a href=\"#$CompName $change\">$CompName</a>";
       
   495     }
       
   496 
       
   497 	&PrintLines(@CompLines);
       
   498 }
       
   499 
       
   500 #
       
   501 #
       
   502 # Creates the Release Notes html file by extracting the perforce descriptions for each
       
   503 # change that has been made to the components
       
   504 #
       
   505 # Inputs: Product, Source path of the product (i.e.//EPOC/master), the source path on the
       
   506 #         main codeline if a delivery branch is being used, Previous changelist,
       
   507 #         Current changelist, changelist from which the branch was made if a branch is being 
       
   508 #         used, array of components and their source.
       
   509 # Outputs: Release Notes html file.
       
   510 #
       
   511 # Algorithm:
       
   512 #   Loop through the component&source array
       
   513 #       Determine whether a boundary, component name, a source dir
       
   514 #       If a source dir, query perforce to see what changelists have affected it in the period that we're interested in
       
   515 #       If it has been affected, add the changelist to this component's changelists, unless it's already there
       
   516 #
       
   517 #   This is rather inefficient :-(
       
   518 #
       
   519 sub CreateReleaseNotes
       
   520 {
       
   521     my $Product = shift;
       
   522     my $Srcpath = shift;
       
   523     my $MainLinePath = shift;
       
   524     my $PrevCL = shift;
       
   525     my $CurrentCL = shift;
       
   526     my $BranchCL = shift;
       
   527     my @ComponentAndSource = @_;
       
   528 
       
   529     #Reset all arrays to NULL
       
   530     my @PrevGTMrpComponents = ();
       
   531     my @CurrGTMrpComponents = ();
       
   532     my @PrevTVMrpComponents = ();
       
   533     my @CurrTVMrpComponents = ();
       
   534     my @CurrMrpComponents = ();
       
   535     my @Components = ();
       
   536     my @UnchangedComponents = ();
       
   537     my @ChangedComponents = ();
       
   538     my %changeProcessed = ();		# hash of changelists processed for this component
       
   539     my %changeToDescription = ();	# hash of descriptions for non-reverted changelists
       
   540     
       
   541     $PrevCL++;     # increment changelist number so we don't include the very first submission 
       
   542                    #  - it would have been picked up in the last run of this script
       
   543     
       
   544     my $ProductName = "Symbian_OS_v$Product\_Changes_Report";
       
   545     
       
   546     open OUTFILE, "> $ProductName.html" or die "ERROR: Can't open $ProductName.html for output\n$!";
       
   547     print OUTFILE <<HEADING_EOF;
       
   548 <html>\n\n<head>\n<title>$ProductName</title>\n</head>\n\n
       
   549 <body bgcolor="#ffffff" text="#000000" link="#5F9F9F" vlink="5F9F9F">\n
       
   550 <font face=verdana,arial,helvetica size=4>\n\n<hr>\n\n
       
   551 <a name="list"><h1><center>$ProductName</center></h1></a>
       
   552 <p><center>----------------------------------------</center>\n
       
   553 <h2><center><font color = "blue">GT Components</font></center></h2>\n
       
   554 HEADING_EOF
       
   555     
       
   556     my $CompName;
       
   557     my $dirCount = 0;
       
   558 	my $spacer = "\n";
       
   559 
       
   560 
       
   561     # Loop through the list of elements running perforce commands to obtain the descriptions
       
   562     # of any changes that have been made since the last release build
       
   563     foreach my $element(@ComponentAndSource)
       
   564     {
       
   565         # Is it the boundary?
       
   566         if($element =~ /\*\*TECHVIEWCOMPONENTS\*\*/)
       
   567 		{
       
   568 			# print out the accumulated GT component, if any
       
   569 		    &PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
       
   570 		    $dirCount = 0;
       
   571 		    # no need to clear the hashes, because that will happen at the first TV component
       
   572 		    
       
   573 			print OUTFILE "<h2><center><font color = \"blue\">Techview Components</font></center></h2>\n";
       
   574 		}
       
   575         # Is it a component name?
       
   576         elsif($element =~ /^([A-Z].*)/)
       
   577 		{
       
   578 		    my $newName = $1;
       
   579 		    
       
   580 		    &PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
       
   581 		    $dirCount = 0;
       
   582 		    
       
   583 		    $CompName = $newName;
       
   584 		    %changeProcessed = ();
       
   585 		    %changeToDescription = ();
       
   586 		    print "Processing $CompName...\n" if ($debug);
       
   587 		}
       
   588         # Is it a source directory?
       
   589 		elsif($element =~ /^([a-z].*?)\s*$/)
       
   590 		{
       
   591 		    my $Topdir = $1;
       
   592 		    $dirCount++;
       
   593 	            
       
   594 	        # If it contains spaces, quote it
       
   595 		    if($Topdir =~ /.*\s+.*/)
       
   596 		    {
       
   597 			$Topdir = "\"$Topdir\"";
       
   598 		    }
       
   599 
       
   600 		    # Find the changes that have affected this dir
       
   601 		    # (Changes come back in reverse chronological order)
       
   602             my @CompChange = `p4 changes -l $Srcpath$Topdir...\@$PrevCL,\@$CurrentCL`;
       
   603 
       
   604             # if the current release is on a branch then the p4 command also needs to be run
       
   605             # to capture changes on the codeline before the files were branched
       
   606             if ($MainLinePath)
       
   607             {
       
   608                 # So far, @CompChange contains the info from the delivery
       
   609                 # branch only
       
   610                 # The earliest changelist on the delivery branch is the creation
       
   611                 # of the branch, which is not interesting to anyone.
       
   612                 # Hence, remove it here:
       
   613                 
       
   614                 # Work backwards from the end of the output chopping off the
       
   615                 # last item till we've chopped off a changelist header
       
   616                 my $choppedText;
       
   617                 do
       
   618                 {
       
   619                     # Remove last line
       
   620                     $choppedText = pop @CompChange;
       
   621                 }
       
   622                 until (!defined $choppedText || $choppedText =~ m/^Change\s\d+\s/);
       
   623 
       
   624                 # Now append the earlier changes from the main line
       
   625                 my @extrainfo = `p4 changes -l $MainLinePath$Topdir...\@$PrevCL,\@$BranchCL`;
       
   626                 push @CompChange, @extrainfo;
       
   627             }
       
   628 
       
   629             # Normalise the output of P4
       
   630             @CompChange = map { split "\n"; } @CompChange;
       
   631 
       
   632             # Extract change descriptions into a hash keyed on changelist number
       
   633             my %moreChangeDescriptions;
       
   634             my $change;
       
   635             foreach my $line (@CompChange)
       
   636             {
       
   637                 if ($line =~ /^Change\s(\d+)\s/)
       
   638                 {
       
   639                     my $newchange = $1;
       
   640                     $change = "";
       
   641                     # ignore if already processed for this component
       
   642                     next if ($changeProcessed{$newchange});
       
   643                     $changeProcessed{$newchange} = 1;
       
   644                     $change = $newchange;
       
   645                 }
       
   646                 next if (!$change);
       
   647                 push @{$moreChangeDescriptions{$change}}, $line;
       
   648             }
       
   649             
       
   650             # Process changes (newest first), extracting the <EXTERNAL> section and 
       
   651             # processing any reversion information
       
   652             foreach my $change (reverse sort keys %moreChangeDescriptions)
       
   653             {
       
   654                 if ($reverted{$change})
       
   655                 {
       
   656                 	print "REMARK: $CompName - deleting description of reverted change $change\n";
       
   657                 	delete $moreChangeDescriptions{$change};
       
   658                 	next;
       
   659                 }
       
   660 
       
   661             	my @changeLines = @{$moreChangeDescriptions{$change}};
       
   662             	
       
   663             	my @revisedLines = ();
       
   664             	push @revisedLines, shift @changeLines;		# keep the "Change" line
       
   665             	
       
   666             	my $line;
       
   667             	while ($line = shift @changeLines)
       
   668             	{
       
   669             		last if ($line =~ /<EXTERNAL>$/);
       
   670             		
       
   671             		$line =~ s/^\t+//;
       
   672 					$line =~ s/\&/&amp;/g; 
       
   673 					$line =~ s/\</&lt;/g; 
       
   674 					$line =~ s/\>/&gt;/g; 
       
   675 					$line =~ s/\"/&quot;/g;	# quote the " character as well
       
   676 					
       
   677             		push @revisedLines, $line;
       
   678             	}
       
   679             	
       
   680             	if (scalar @changeLines == 0)
       
   681             	{
       
   682             		# consumed the whole description without seeing <EXTERNAL>
       
   683             		# must be old format
       
   684             		@{$changeToDescription{$change}} = @revisedLines;
       
   685             		printf ".. unformatted change %d - %d lines\n", $change, scalar @changeLines if ($debug);
       
   686             		next;
       
   687             	}
       
   688             	
       
   689             	# Must be properly formatted change description
       
   690             	# discard everything seen so far except the "Change" line.
       
   691             	@revisedLines = (shift @revisedLines);
       
   692                 
       
   693 		# Process all lines starting with the first <EXTERNAL> section.
       
   694             	while (($line =~ /<EXTERNAL>$/) || ($line = shift @changeLines))
       
   695             	{
       
   696 		    # Process an <EXTERNAL> section.
       
   697 		    while ($line =~ /<EXTERNAL>$/) 
       
   698 		    {
       
   699 			# Keep processing until an </EXTERNAL> line is encountered
       
   700             		while(($line = shift @changeLines) && ($line !~ /<\/EXTERNAL>$/))
       
   701 			{
       
   702 			    $line =~ s/^\t+//;
       
   703 			    $line =~ s/\&/&amp;/g; 
       
   704 			    $line =~ s/\</&lt;/g; 
       
   705 			    $line =~ s/\>/&gt;/g; 
       
   706 			    $line =~ s/\"/&quot;/g;	# quote the " character as well
       
   707 
       
   708 			    push @revisedLines, $line;
       
   709             		
       
   710 			    # Opportunity to pick information out of changes as they go past
       
   711 			    if ($line =~ /^\s*((DEF|PDEF|INC)\d+):?\s/)
       
   712 				{
       
   713 				$allDefects{$1} = $line;
       
   714 				next;
       
   715 				}
       
   716 			    if ($line =~ /^(BR[0-9.]+)\s/)
       
   717 				{
       
   718 				$allBreaks{$1} = $line;
       
   719 				next;
       
   720 				}
       
   721 			}
       
   722 			push @revisedLines, $spacer;
       
   723 		    }
       
   724             	}
       
   725             	
       
   726               	# update the description for this change
       
   727               	@{$changeToDescription{$change}} = @revisedLines;
       
   728             	printf ".. formatted change %d - %d external lines\n", $change, scalar @revisedLines if ($debug);
       
   729           	
       
   730                 # check for reversion information in rest of the formatted description
       
   731                 # submission checker delivers one <detail reverts=""> line per list element
       
   732                 foreach my $line (grep /<detail reverts=/, @changeLines)
       
   733         		{
       
   734         			if ($line =~ /<detail reverts=\s*\"(\d+)\"/)	# changelist surrounded by "
       
   735         			{
       
   736         				my $oldchange = $1;
       
   737         				print "REMARK: Change $change reverts $oldchange\n";
       
   738         				$reverted{$oldchange} = $change;
       
   739         			}
       
   740         		}
       
   741             }
       
   742 		}
       
   743 	}
       
   744 	
       
   745 	# print description of last component
       
   746 	&PrintComponentDescriptions(\%changeToDescription, $CompName, \@ChangedComponents, \@UnchangedComponents) if ($dirCount);
       
   747 
       
   748 	# Print additional tables of information
       
   749     &PrintLines("<h2>Changed Components</h2>\n<nobr>", join(",</nobr> \n<nobr>", sort @ChangedComponents), "</nobr>") if (@ChangedComponents);
       
   750 	
       
   751     &PrintLines("<h2>Unchanged Components</h2>\n<nobr>", join(",</nobr> \n<nobr>", sort @UnchangedComponents), "</nobr>") if (@UnchangedComponents);
       
   752 
       
   753 	if (scalar @ChangedComponents)
       
   754 	{
       
   755 	    &PrintLines("<h2>Components affected by each change</h2>");
       
   756 	    
       
   757 	    &PrintLines("<TABLE>\n");
       
   758 	    foreach my $change (reverse sort keys %changeToComponents)
       
   759 	    {
       
   760 	    	&PrintLines("<TR valign=\"top\"><TD><a name=\"#$change\"/>$change</TD><TD>", join(", ", @{$changeToComponents{$change}}), "</TD></TR>\n");
       
   761 	    }
       
   762 	    &PrintLines("</TABLE>\n");
       
   763 	}
       
   764 	
       
   765 	my @allDefectTitles = ();
       
   766 	foreach my $defect (sort keys %allDefects)
       
   767 	{
       
   768 		push @allDefectTitles,$allDefects{$defect};
       
   769 	}
       
   770 	&PrintLines("<h2>List of Defect Fixes</h2>", "<pre>", @allDefectTitles, "</pre>") if (scalar @allDefectTitles);
       
   771 	
       
   772 	my @allBreakTitles = ();
       
   773 	foreach my $break (sort keys %allBreaks)
       
   774 	{
       
   775 		push @allBreakTitles,$allBreaks{$break};
       
   776 	}
       
   777 	&PrintLines("<h2>List of Breaks</h2>", "<pre>", @allBreakTitles, "</pre>") if (scalar @allBreakTitles);
       
   778 	
       
   779     &PrintLines("</BODY></HTML>");
       
   780     close OUTFILE;
       
   781     
       
   782     return "$ProductName.html";
       
   783 }
       
   784 
       
   785 sub PrintLines
       
   786 {
       
   787     # Output field and record separators set (locally) to produce newlines
       
   788     # between items in list, and another newline on the end
       
   789     local $, = "\n";
       
   790     local $\ = "\n";
       
   791     print OUTFILE @_;
       
   792 }
       
   793 
       
   794 #
       
   795 #
       
   796 # Extracts the sourcecode path and changelist from a Perforce report file.
       
   797 #
       
   798 # Inputs: Location of a perforce report file, Platform (e.g. cedar)
       
   799 # Outputs: Source path of the product (e.g.//EPOC/master), changelist used in build
       
   800 #
       
   801 #
       
   802 sub GetPrevCodelineandCL
       
   803 {
       
   804     my $FileAndPath = shift;
       
   805     my $Platform = shift;
       
   806 
       
   807     my $LogFile;
       
   808     my $PrevCL;
       
   809     my $PrevCodeline;
       
   810 
       
   811     if (!open(DAT, $FileAndPath))
       
   812     {
       
   813         print ERRORLOG "Could not open $FileAndPath!\n" if $debug;
       
   814         die "ERROR: Cannot open $FileAndPath: $!\n";
       
   815     }
       
   816     {
       
   817         # Grab complete file in one string
       
   818         local $/ = undef;
       
   819         $LogFile = <DAT>;
       
   820     }
       
   821     close DAT;
       
   822     
       
   823     if ($LogFile =~ m{ClientSpec.*?<td[^>]*>.*?(//.+?)$Platform/.*?</tr>}s)
       
   824     {
       
   825        $PrevCodeline = $1;
       
   826     }
       
   827     
       
   828     if ($LogFile =~ m{Perforce Change[Ll]ist.*?<td[^>]*>(.*?)</tr>}s)
       
   829     {
       
   830         # Perforce changelist table data may either be in form of "nnnnnn" or "ssssss @ nnnnnn"
       
   831         # ssssss is the snapshot id, which may be a string of digits
       
   832         # nnnnnn is the changelist number
       
   833         # 
       
   834         # Get the later string of digits
       
   835         ($PrevCL) = $1 =~ m{(\d+)\D*$};
       
   836     }
       
   837     
       
   838     unless ($PrevCL) { die "ERROR: Unable to parse previous changelist from $FileAndPath\n"; }
       
   839     unless ($PrevCodeline) { die "ERROR: Unable to parse previous codeline from $FileAndPath\n"; }
       
   840     
       
   841     return $PrevCL, $PrevCodeline;
       
   842 }
       
   843 
       
   844