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