|
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/\&/&/g; |
|
657 $line =~ s/\</</g; |
|
658 $line =~ s/\>/>/g; |
|
659 $line =~ s/\"/"/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/\&/&/g; |
|
682 $line =~ s/\</</g; |
|
683 $line =~ s/\>/>/g; |
|
684 $line =~ s/\"/"/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 |