]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/build/BuildReport.py
BaseTools: Add two new sections for PCD in the build report
[mirror_edk2.git] / BaseTools / Source / Python / build / BuildReport.py
1 ## @file
2 # Routines for generating build report.
3 #
4 # This module contains the functionality to generate build report after
5 # build all target completes successfully.
6 #
7 # Copyright (c) 2010 - 2016, Intel Corporation. All rights reserved.<BR>
8 # This program and the accompanying materials
9 # are licensed and made available under the terms and conditions of the BSD License
10 # which accompanies this distribution. The full text of the license may be found at
11 # http://opensource.org/licenses/bsd-license.php
12 #
13 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15 #
16
17 ## Import Modules
18 #
19 import Common.LongFilePathOs as os
20 import re
21 import platform
22 import textwrap
23 import traceback
24 import sys
25 import time
26 import struct
27 from datetime import datetime
28 from StringIO import StringIO
29 from Common import EdkLogger
30 from Common.Misc import SaveFileOnChange
31 from Common.Misc import GuidStructureByteArrayToGuidString
32 from Common.Misc import GuidStructureStringToGuidString
33 from Common.InfClassObject import gComponentType2ModuleType
34 from Common.BuildToolError import FILE_WRITE_FAILURE
35 from Common.BuildToolError import CODE_ERROR
36 from Common.DataType import TAB_LINE_BREAK
37 from Common.DataType import TAB_DEPEX
38 from Common.DataType import TAB_SLASH
39 from Common.DataType import TAB_SPACE_SPLIT
40 from Common.DataType import TAB_BRG_PCD
41 from Common.DataType import TAB_BRG_LIBRARY
42 from Common.DataType import TAB_BACK_SLASH
43 from Common.LongFilePathSupport import OpenLongFilePath as open
44 from Common.MultipleWorkspace import MultipleWorkspace as mws
45 import Common.GlobalData as GlobalData
46
47 ## Pattern to extract contents in EDK DXS files
48 gDxsDependencyPattern = re.compile(r"DEPENDENCY_START(.+)DEPENDENCY_END", re.DOTALL)
49
50 ## Pattern to find total FV total size, occupied size in flash report intermediate file
51 gFvTotalSizePattern = re.compile(r"EFI_FV_TOTAL_SIZE = (0x[0-9a-fA-F]+)")
52 gFvTakenSizePattern = re.compile(r"EFI_FV_TAKEN_SIZE = (0x[0-9a-fA-F]+)")
53
54 ## Pattern to find module size and time stamp in module summary report intermediate file
55 gModuleSizePattern = re.compile(r"MODULE_SIZE = (\d+)")
56 gTimeStampPattern = re.compile(r"TIME_STAMP = (\d+)")
57
58 ## Pattern to find GUID value in flash description files
59 gPcdGuidPattern = re.compile(r"PCD\((\w+)[.](\w+)\)")
60
61 ## Pattern to collect offset, GUID value pair in the flash report intermediate file
62 gOffsetGuidPattern = re.compile(r"(0x[0-9A-Fa-f]+) ([-A-Fa-f0-9]+)")
63
64 ## Pattern to find module base address and entry point in fixed flash map file
65 gModulePattern = r"\n[-\w]+\s*\(([^,]+),\s*BaseAddress=%(Address)s,\s*EntryPoint=%(Address)s\)\s*\(GUID=([-0-9A-Fa-f]+)[^)]*\)"
66 gMapFileItemPattern = re.compile(gModulePattern % {"Address" : "(-?0[xX][0-9A-Fa-f]+)"})
67
68 ## Pattern to find all module referenced header files in source files
69 gIncludePattern = re.compile(r'#include\s*["<]([^">]+)[">]')
70 gIncludePattern2 = re.compile(r"#include\s+EFI_([A-Z_]+)\s*[(]\s*(\w+)\s*[)]")
71
72 ## Pattern to find the entry point for EDK module using EDKII Glue library
73 gGlueLibEntryPoint = re.compile(r"__EDKII_GLUE_MODULE_ENTRY_POINT__\s*=\s*(\w+)")
74
75 ## Tags for MaxLength of line in report
76 gLineMaxLength = 120
77
78 ## Tags for end of line in report
79 gEndOfLine = "\r\n"
80
81 ## Tags for section start, end and separator
82 gSectionStart = ">" + "=" * (gLineMaxLength - 2) + "<"
83 gSectionEnd = "<" + "=" * (gLineMaxLength - 2) + ">" + "\n"
84 gSectionSep = "=" * gLineMaxLength
85
86 ## Tags for subsection start, end and separator
87 gSubSectionStart = ">" + "-" * (gLineMaxLength - 2) + "<"
88 gSubSectionEnd = "<" + "-" * (gLineMaxLength - 2) + ">"
89 gSubSectionSep = "-" * gLineMaxLength
90
91
92 ## The look up table to map PCD type to pair of report display type and DEC type
93 gPcdTypeMap = {
94 'FixedAtBuild' : ('FIXED', 'FixedAtBuild'),
95 'PatchableInModule': ('PATCH', 'PatchableInModule'),
96 'FeatureFlag' : ('FLAG', 'FeatureFlag'),
97 'Dynamic' : ('DYN', 'Dynamic'),
98 'DynamicHii' : ('DYNHII', 'Dynamic'),
99 'DynamicVpd' : ('DYNVPD', 'Dynamic'),
100 'DynamicEx' : ('DEX', 'DynamicEx'),
101 'DynamicExHii' : ('DEXHII', 'DynamicEx'),
102 'DynamicExVpd' : ('DEXVPD', 'DynamicEx'),
103 }
104
105 ## The look up table to map module type to driver type
106 gDriverTypeMap = {
107 'SEC' : '0x3 (SECURITY_CORE)',
108 'PEI_CORE' : '0x4 (PEI_CORE)',
109 'PEIM' : '0x6 (PEIM)',
110 'DXE_CORE' : '0x5 (DXE_CORE)',
111 'DXE_DRIVER' : '0x7 (DRIVER)',
112 'DXE_SAL_DRIVER' : '0x7 (DRIVER)',
113 'DXE_SMM_DRIVER' : '0x7 (DRIVER)',
114 'DXE_RUNTIME_DRIVER': '0x7 (DRIVER)',
115 'UEFI_DRIVER' : '0x7 (DRIVER)',
116 'UEFI_APPLICATION' : '0x9 (APPLICATION)',
117 'SMM_CORE' : '0xD (SMM_CORE)',
118 'SMM_DRIVER' : '0xA (SMM)', # Extension of module type to support PI 1.1 SMM drivers
119 }
120
121 ## The look up table of the supported opcode in the dependency expression binaries
122 gOpCodeList = ["BEFORE", "AFTER", "PUSH", "AND", "OR", "NOT", "TRUE", "FALSE", "END", "SOR"]
123
124 ##
125 # Writes a string to the file object.
126 #
127 # This function writes a string to the file object and a new line is appended
128 # afterwards. It may optionally wraps the string for better readability.
129 #
130 # @File The file object to write
131 # @String The string to be written to the file
132 # @Wrapper Indicates whether to wrap the string
133 #
134 def FileWrite(File, String, Wrapper=False):
135 if Wrapper:
136 String = textwrap.fill(String, 120)
137 File.write(String + gEndOfLine)
138
139 ##
140 # Find all the header file that the module source directly includes.
141 #
142 # This function scans source code to find all header files the module may
143 # include. This is not accurate but very effective to find all the header
144 # file the module might include with #include statement.
145 #
146 # @Source The source file name
147 # @IncludePathList The list of include path to find the source file.
148 # @IncludeFiles The dictionary of current found include files.
149 #
150 def FindIncludeFiles(Source, IncludePathList, IncludeFiles):
151 FileContents = open(Source).read()
152 #
153 # Find header files with pattern #include "XXX.h" or #include <XXX.h>
154 #
155 for Match in gIncludePattern.finditer(FileContents):
156 FileName = Match.group(1).strip()
157 for Dir in [os.path.dirname(Source)] + IncludePathList:
158 FullFileName = os.path.normpath(os.path.join(Dir, FileName))
159 if os.path.exists(FullFileName):
160 IncludeFiles[FullFileName.lower().replace("\\", "/")] = FullFileName
161 break
162
163 #
164 # Find header files with pattern like #include EFI_PPI_CONSUMER(XXX)
165 #
166 for Match in gIncludePattern2.finditer(FileContents):
167 Key = Match.group(2)
168 Type = Match.group(1)
169 if "ARCH_PROTOCOL" in Type:
170 FileName = "ArchProtocol/%(Key)s/%(Key)s.h" % {"Key" : Key}
171 elif "PROTOCOL" in Type:
172 FileName = "Protocol/%(Key)s/%(Key)s.h" % {"Key" : Key}
173 elif "PPI" in Type:
174 FileName = "Ppi/%(Key)s/%(Key)s.h" % {"Key" : Key}
175 elif "GUID" in Type:
176 FileName = "Guid/%(Key)s/%(Key)s.h" % {"Key" : Key}
177 else:
178 continue
179 for Dir in IncludePathList:
180 FullFileName = os.path.normpath(os.path.join(Dir, FileName))
181 if os.path.exists(FullFileName):
182 IncludeFiles[FullFileName.lower().replace("\\", "/")] = FullFileName
183 break
184
185 ## Split each lines in file
186 #
187 # This method is used to split the lines in file to make the length of each line
188 # less than MaxLength.
189 #
190 # @param Content The content of file
191 # @param MaxLength The Max Length of the line
192 #
193 def FileLinesSplit(Content=None, MaxLength=None):
194 ContentList = Content.split(TAB_LINE_BREAK)
195 NewContent = ''
196 NewContentList = []
197 for Line in ContentList:
198 while len(Line.rstrip()) > MaxLength:
199 LineSpaceIndex = Line.rfind(TAB_SPACE_SPLIT, 0, MaxLength)
200 LineSlashIndex = Line.rfind(TAB_SLASH, 0, MaxLength)
201 LineBackSlashIndex = Line.rfind(TAB_BACK_SLASH, 0, MaxLength)
202 if max(LineSpaceIndex, LineSlashIndex, LineBackSlashIndex) > 0:
203 LineBreakIndex = max(LineSpaceIndex, LineSlashIndex, LineBackSlashIndex)
204 else:
205 LineBreakIndex = MaxLength
206 NewContentList.append(Line[:LineBreakIndex])
207 Line = Line[LineBreakIndex:]
208 if Line:
209 NewContentList.append(Line)
210 for NewLine in NewContentList:
211 NewContent += NewLine + TAB_LINE_BREAK
212
213 NewContent = NewContent.replace(TAB_LINE_BREAK, gEndOfLine).replace('\r\r\n', gEndOfLine)
214 return NewContent
215
216
217
218 ##
219 # Parse binary dependency expression section
220 #
221 # This utility class parses the dependency expression section and translate the readable
222 # GUID name and value.
223 #
224 class DepexParser(object):
225 ##
226 # Constructor function for class DepexParser
227 #
228 # This constructor function collect GUID values so that the readable
229 # GUID name can be translated.
230 #
231 # @param self The object pointer
232 # @param Wa Workspace context information
233 #
234 def __init__(self, Wa):
235 self._GuidDb = {}
236 for Pa in Wa.AutoGenObjectList:
237 for Package in Pa.PackageList:
238 for Protocol in Package.Protocols:
239 GuidValue = GuidStructureStringToGuidString(Package.Protocols[Protocol])
240 self._GuidDb[GuidValue.upper()] = Protocol
241 for Ppi in Package.Ppis:
242 GuidValue = GuidStructureStringToGuidString(Package.Ppis[Ppi])
243 self._GuidDb[GuidValue.upper()] = Ppi
244 for Guid in Package.Guids:
245 GuidValue = GuidStructureStringToGuidString(Package.Guids[Guid])
246 self._GuidDb[GuidValue.upper()] = Guid
247
248 ##
249 # Parse the binary dependency expression files.
250 #
251 # This function parses the binary dependency expression file and translate it
252 # to the instruction list.
253 #
254 # @param self The object pointer
255 # @param DepexFileName The file name of binary dependency expression file.
256 #
257 def ParseDepexFile(self, DepexFileName):
258 DepexFile = open(DepexFileName, "rb")
259 DepexStatement = []
260 OpCode = DepexFile.read(1)
261 while OpCode:
262 Statement = gOpCodeList[struct.unpack("B", OpCode)[0]]
263 if Statement in ["BEFORE", "AFTER", "PUSH"]:
264 GuidValue = "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X" % \
265 struct.unpack("=LHHBBBBBBBB", DepexFile.read(16))
266 GuidString = self._GuidDb.get(GuidValue, GuidValue)
267 Statement = "%s %s" % (Statement, GuidString)
268 DepexStatement.append(Statement)
269 OpCode = DepexFile.read(1)
270
271 return DepexStatement
272
273 ##
274 # Reports library information
275 #
276 # This class reports the module library subsection in the build report file.
277 #
278 class LibraryReport(object):
279 ##
280 # Constructor function for class LibraryReport
281 #
282 # This constructor function generates LibraryReport object for
283 # a module.
284 #
285 # @param self The object pointer
286 # @param M Module context information
287 #
288 def __init__(self, M):
289 self.LibraryList = []
290 if int(str(M.AutoGenVersion), 0) >= 0x00010005:
291 self._EdkIIModule = True
292 else:
293 self._EdkIIModule = False
294
295 for Lib in M.DependentLibraryList:
296 LibInfPath = str(Lib)
297 LibClassList = Lib.LibraryClass[0].LibraryClass
298 LibConstructorList = Lib.ConstructorList
299 LibDesstructorList = Lib.DestructorList
300 LibDepexList = Lib.DepexExpression[M.Arch, M.ModuleType]
301 self.LibraryList.append((LibInfPath, LibClassList, LibConstructorList, LibDesstructorList, LibDepexList))
302
303 ##
304 # Generate report for module library information
305 #
306 # This function generates report for the module library.
307 # If the module is EDKII style one, the additional library class, library
308 # constructor/destructor and dependency expression may also be reported.
309 #
310 # @param self The object pointer
311 # @param File The file object for report
312 #
313 def GenerateReport(self, File):
314 FileWrite(File, gSubSectionStart)
315 FileWrite(File, TAB_BRG_LIBRARY)
316 if len(self.LibraryList) > 0:
317 FileWrite(File, gSubSectionSep)
318 for LibraryItem in self.LibraryList:
319 LibInfPath = LibraryItem[0]
320 FileWrite(File, LibInfPath)
321
322 #
323 # Report library class, library constructor and destructor for
324 # EDKII style module.
325 #
326 if self._EdkIIModule:
327 LibClass = LibraryItem[1]
328 EdkIILibInfo = ""
329 LibConstructor = " ".join(LibraryItem[2])
330 if LibConstructor:
331 EdkIILibInfo += " C = " + LibConstructor
332 LibDestructor = " ".join(LibraryItem[3])
333 if LibDestructor:
334 EdkIILibInfo += " D = " + LibDestructor
335 LibDepex = " ".join(LibraryItem[4])
336 if LibDepex:
337 EdkIILibInfo += " Depex = " + LibDepex
338 if EdkIILibInfo:
339 FileWrite(File, "{%s: %s}" % (LibClass, EdkIILibInfo))
340 else:
341 FileWrite(File, "{%s}" % LibClass)
342
343 FileWrite(File, gSubSectionEnd)
344
345 ##
346 # Reports dependency expression information
347 #
348 # This class reports the module dependency expression subsection in the build report file.
349 #
350 class DepexReport(object):
351 ##
352 # Constructor function for class DepexReport
353 #
354 # This constructor function generates DepexReport object for
355 # a module. If the module source contains the DXS file (usually EDK
356 # style module), it uses the dependency in DXS file; otherwise,
357 # it uses the dependency expression from its own INF [Depex] section
358 # and then merges with the ones from its dependent library INF.
359 #
360 # @param self The object pointer
361 # @param M Module context information
362 #
363 def __init__(self, M):
364 self.Depex = ""
365 self._DepexFileName = os.path.join(M.BuildDir, "OUTPUT", M.Module.BaseName + ".depex")
366 ModuleType = M.ModuleType
367 if not ModuleType:
368 ModuleType = gComponentType2ModuleType.get(M.ComponentType, "")
369
370 if ModuleType in ["SEC", "PEI_CORE", "DXE_CORE", "SMM_CORE", "UEFI_APPLICATION"]:
371 return
372
373 for Source in M.SourceFileList:
374 if os.path.splitext(Source.Path)[1].lower() == ".dxs":
375 Match = gDxsDependencyPattern.search(open(Source.Path).read())
376 if Match:
377 self.Depex = Match.group(1).strip()
378 self.Source = "DXS"
379 break
380 else:
381 self.Depex = M.DepexExpressionList.get(M.ModuleType, "")
382 self.ModuleDepex = " ".join(M.Module.DepexExpression[M.Arch, M.ModuleType])
383 if not self.ModuleDepex:
384 self.ModuleDepex = "(None)"
385
386 LibDepexList = []
387 for Lib in M.DependentLibraryList:
388 LibDepex = " ".join(Lib.DepexExpression[M.Arch, M.ModuleType]).strip()
389 if LibDepex != "":
390 LibDepexList.append("(" + LibDepex + ")")
391 self.LibraryDepex = " AND ".join(LibDepexList)
392 if not self.LibraryDepex:
393 self.LibraryDepex = "(None)"
394 self.Source = "INF"
395
396 ##
397 # Generate report for module dependency expression information
398 #
399 # This function generates report for the module dependency expression.
400 #
401 # @param self The object pointer
402 # @param File The file object for report
403 # @param GlobalDepexParser The platform global Dependency expression parser object
404 #
405 def GenerateReport(self, File, GlobalDepexParser):
406 if not self.Depex:
407 FileWrite(File, gSubSectionStart)
408 FileWrite(File, TAB_DEPEX)
409 FileWrite(File, gSubSectionEnd)
410 return
411 FileWrite(File, gSubSectionStart)
412 if os.path.isfile(self._DepexFileName):
413 try:
414 DepexStatements = GlobalDepexParser.ParseDepexFile(self._DepexFileName)
415 FileWrite(File, "Final Dependency Expression (DEPEX) Instructions")
416 for DepexStatement in DepexStatements:
417 FileWrite(File, " %s" % DepexStatement)
418 FileWrite(File, gSubSectionSep)
419 except:
420 EdkLogger.warn(None, "Dependency expression file is corrupted", self._DepexFileName)
421
422 FileWrite(File, "Dependency Expression (DEPEX) from %s" % self.Source)
423
424 if self.Source == "INF":
425 FileWrite(File, "%s" % self.Depex, True)
426 FileWrite(File, gSubSectionSep)
427 FileWrite(File, "From Module INF: %s" % self.ModuleDepex, True)
428 FileWrite(File, "From Library INF: %s" % self.LibraryDepex, True)
429 else:
430 FileWrite(File, "%s" % self.Depex)
431 FileWrite(File, gSubSectionEnd)
432
433 ##
434 # Reports dependency expression information
435 #
436 # This class reports the module build flags subsection in the build report file.
437 #
438 class BuildFlagsReport(object):
439 ##
440 # Constructor function for class BuildFlagsReport
441 #
442 # This constructor function generates BuildFlagsReport object for
443 # a module. It reports the build tool chain tag and all relevant
444 # build flags to build the module.
445 #
446 # @param self The object pointer
447 # @param M Module context information
448 #
449 def __init__(self, M):
450 BuildOptions = {}
451 #
452 # Add build flags according to source file extension so that
453 # irrelevant ones can be filtered out.
454 #
455 for Source in M.SourceFileList:
456 Ext = os.path.splitext(Source.File)[1].lower()
457 if Ext in [".c", ".cc", ".cpp"]:
458 BuildOptions["CC"] = 1
459 elif Ext in [".s", ".asm"]:
460 BuildOptions["PP"] = 1
461 BuildOptions["ASM"] = 1
462 elif Ext in [".vfr"]:
463 BuildOptions["VFRPP"] = 1
464 BuildOptions["VFR"] = 1
465 elif Ext in [".dxs"]:
466 BuildOptions["APP"] = 1
467 BuildOptions["CC"] = 1
468 elif Ext in [".asl"]:
469 BuildOptions["ASLPP"] = 1
470 BuildOptions["ASL"] = 1
471 elif Ext in [".aslc"]:
472 BuildOptions["ASLCC"] = 1
473 BuildOptions["ASLDLINK"] = 1
474 BuildOptions["CC"] = 1
475 elif Ext in [".asm16"]:
476 BuildOptions["ASMLINK"] = 1
477 BuildOptions["SLINK"] = 1
478 BuildOptions["DLINK"] = 1
479
480 #
481 # Save module build flags.
482 #
483 self.ToolChainTag = M.ToolChain
484 self.BuildFlags = {}
485 for Tool in BuildOptions:
486 self.BuildFlags[Tool + "_FLAGS"] = M.BuildOption.get(Tool, {}).get("FLAGS", "")
487
488 ##
489 # Generate report for module build flags information
490 #
491 # This function generates report for the module build flags expression.
492 #
493 # @param self The object pointer
494 # @param File The file object for report
495 #
496 def GenerateReport(self, File):
497 FileWrite(File, gSubSectionStart)
498 FileWrite(File, "Build Flags")
499 FileWrite(File, "Tool Chain Tag: %s" % self.ToolChainTag)
500 for Tool in self.BuildFlags:
501 FileWrite(File, gSubSectionSep)
502 FileWrite(File, "%s = %s" % (Tool, self.BuildFlags[Tool]), True)
503
504 FileWrite(File, gSubSectionEnd)
505
506
507 ##
508 # Reports individual module information
509 #
510 # This class reports the module section in the build report file.
511 # It comprises of module summary, module PCD, library, dependency expression,
512 # build flags sections.
513 #
514 class ModuleReport(object):
515 ##
516 # Constructor function for class ModuleReport
517 #
518 # This constructor function generates ModuleReport object for
519 # a separate module in a platform build.
520 #
521 # @param self The object pointer
522 # @param M Module context information
523 # @param ReportType The kind of report items in the final report file
524 #
525 def __init__(self, M, ReportType):
526 self.ModuleName = M.Module.BaseName
527 self.ModuleInfPath = M.MetaFile.File
528 self.FileGuid = M.Guid
529 self.Size = 0
530 self.BuildTimeStamp = None
531 self.DriverType = ""
532 if not M.IsLibrary:
533 ModuleType = M.ModuleType
534 if not ModuleType:
535 ModuleType = gComponentType2ModuleType.get(M.ComponentType, "")
536 #
537 # If a module complies to PI 1.1, promote Module type to "SMM_DRIVER"
538 #
539 if ModuleType == "DXE_SMM_DRIVER":
540 PiSpec = M.Module.Specification.get("PI_SPECIFICATION_VERSION", "0x00010000")
541 if int(PiSpec, 0) >= 0x0001000A:
542 ModuleType = "SMM_DRIVER"
543 self.DriverType = gDriverTypeMap.get(ModuleType, "0x2 (FREE_FORM)")
544 self.UefiSpecVersion = M.Module.Specification.get("UEFI_SPECIFICATION_VERSION", "")
545 self.PiSpecVersion = M.Module.Specification.get("PI_SPECIFICATION_VERSION", "")
546 self.PciDeviceId = M.Module.Defines.get("PCI_DEVICE_ID", "")
547 self.PciVendorId = M.Module.Defines.get("PCI_VENDOR_ID", "")
548 self.PciClassCode = M.Module.Defines.get("PCI_CLASS_CODE", "")
549
550 self._BuildDir = M.BuildDir
551 self.ModulePcdSet = {}
552 if "PCD" in ReportType:
553 #
554 # Collect all module used PCD set: module INF referenced directly or indirectly.
555 # It also saves module INF default values of them in case they exist.
556 #
557 for Pcd in M.ModulePcdList + M.LibraryPcdList:
558 self.ModulePcdSet.setdefault((Pcd.TokenCName, Pcd.TokenSpaceGuidCName, Pcd.Type), (Pcd.InfDefaultValue, Pcd.DefaultValue))
559
560 self.LibraryReport = None
561 if "LIBRARY" in ReportType:
562 self.LibraryReport = LibraryReport(M)
563
564 self.DepexReport = None
565 if "DEPEX" in ReportType:
566 self.DepexReport = DepexReport(M)
567
568 if "BUILD_FLAGS" in ReportType:
569 self.BuildFlagsReport = BuildFlagsReport(M)
570
571
572 ##
573 # Generate report for module information
574 #
575 # This function generates report for separate module expression
576 # in a platform build.
577 #
578 # @param self The object pointer
579 # @param File The file object for report
580 # @param GlobalPcdReport The platform global PCD report object
581 # @param GlobalPredictionReport The platform global Prediction report object
582 # @param GlobalDepexParser The platform global Dependency expression parser object
583 # @param ReportType The kind of report items in the final report file
584 #
585 def GenerateReport(self, File, GlobalPcdReport, GlobalPredictionReport, GlobalDepexParser, ReportType):
586 FileWrite(File, gSectionStart)
587
588 FwReportFileName = os.path.join(self._BuildDir, "DEBUG", self.ModuleName + ".txt")
589 if os.path.isfile(FwReportFileName):
590 try:
591 FileContents = open(FwReportFileName).read()
592 Match = gModuleSizePattern.search(FileContents)
593 if Match:
594 self.Size = int(Match.group(1))
595
596 Match = gTimeStampPattern.search(FileContents)
597 if Match:
598 self.BuildTimeStamp = datetime.fromtimestamp(int(Match.group(1)))
599 except IOError:
600 EdkLogger.warn(None, "Fail to read report file", FwReportFileName)
601
602 FileWrite(File, "Module Summary")
603 FileWrite(File, "Module Name: %s" % self.ModuleName)
604 FileWrite(File, "Module INF Path: %s" % self.ModuleInfPath)
605 FileWrite(File, "File GUID: %s" % self.FileGuid)
606 if self.Size:
607 FileWrite(File, "Size: 0x%X (%.2fK)" % (self.Size, self.Size / 1024.0))
608 if self.BuildTimeStamp:
609 FileWrite(File, "Build Time Stamp: %s" % self.BuildTimeStamp)
610 if self.DriverType:
611 FileWrite(File, "Driver Type: %s" % self.DriverType)
612 if self.UefiSpecVersion:
613 FileWrite(File, "UEFI Spec Version: %s" % self.UefiSpecVersion)
614 if self.PiSpecVersion:
615 FileWrite(File, "PI Spec Version: %s" % self.PiSpecVersion)
616 if self.PciDeviceId:
617 FileWrite(File, "PCI Device ID: %s" % self.PciDeviceId)
618 if self.PciVendorId:
619 FileWrite(File, "PCI Vendor ID: %s" % self.PciVendorId)
620 if self.PciClassCode:
621 FileWrite(File, "PCI Class Code: %s" % self.PciClassCode)
622
623 FileWrite(File, gSectionSep)
624
625 if "PCD" in ReportType:
626 GlobalPcdReport.GenerateReport(File, self.ModulePcdSet)
627
628 if "LIBRARY" in ReportType:
629 self.LibraryReport.GenerateReport(File)
630
631 if "DEPEX" in ReportType:
632 self.DepexReport.GenerateReport(File, GlobalDepexParser)
633
634 if "BUILD_FLAGS" in ReportType:
635 self.BuildFlagsReport.GenerateReport(File)
636
637 if "FIXED_ADDRESS" in ReportType and self.FileGuid:
638 GlobalPredictionReport.GenerateReport(File, self.FileGuid)
639
640 FileWrite(File, gSectionEnd)
641
642 ##
643 # Reports platform and module PCD information
644 #
645 # This class reports the platform PCD section and module PCD subsection
646 # in the build report file.
647 #
648 class PcdReport(object):
649 ##
650 # Constructor function for class PcdReport
651 #
652 # This constructor function generates PcdReport object a platform build.
653 # It collects the whole PCD database from platform DSC files, platform
654 # flash description file and package DEC files.
655 #
656 # @param self The object pointer
657 # @param Wa Workspace context information
658 #
659 def __init__(self, Wa):
660 self.AllPcds = {}
661 self.UnusedPcds = {}
662 self.ConditionalPcds = {}
663 self.MaxLen = 0
664 if Wa.FdfProfile:
665 self.FdfPcdSet = Wa.FdfProfile.PcdDict
666 else:
667 self.FdfPcdSet = {}
668
669 self.ModulePcdOverride = {}
670 for Pa in Wa.AutoGenObjectList:
671 #
672 # Collect all platform referenced PCDs and grouped them by PCD token space
673 # GUID C Names
674 #
675 for Pcd in Pa.AllPcdList:
676 PcdList = self.AllPcds.setdefault(Pcd.TokenSpaceGuidCName, {}).setdefault(Pcd.Type, [])
677 if Pcd not in PcdList:
678 PcdList.append(Pcd)
679 if len(Pcd.TokenCName) > self.MaxLen:
680 self.MaxLen = len(Pcd.TokenCName)
681 #
682 # Collect the PCD defined in DSC/FDF file, but not used in module
683 #
684 UnusedPcdFullList = []
685 for item in Pa.Platform.Pcds:
686 Pcd = Pa.Platform.Pcds[item]
687 if not Pcd.Type:
688 PcdTypeFlag = False
689 for package in Pa.PackageList:
690 for T in ["FixedAtBuild", "PatchableInModule", "FeatureFlag", "Dynamic", "DynamicEx"]:
691 if (Pcd.TokenCName, Pcd.TokenSpaceGuidCName, T) in package.Pcds:
692 Pcd.Type = T
693 PcdTypeFlag = True
694 if not Pcd.DatumType:
695 Pcd.DatumType = package.Pcds[(Pcd.TokenCName, Pcd.TokenSpaceGuidCName, T)].DatumType
696 break
697 if PcdTypeFlag:
698 break
699 if not Pcd.DatumType:
700 PcdType = Pcd.Type
701 # Try to remove Hii and Vpd suffix
702 if PcdType.startswith("DynamicEx"):
703 PcdType = "DynamicEx"
704 elif PcdType.startswith("Dynamic"):
705 PcdType = "Dynamic"
706 for package in Pa.PackageList:
707 if (Pcd.TokenCName, Pcd.TokenSpaceGuidCName, PcdType) in package.Pcds:
708 Pcd.DatumType = package.Pcds[(Pcd.TokenCName, Pcd.TokenSpaceGuidCName, PcdType)].DatumType
709 break
710
711 PcdList = self.AllPcds.setdefault(Pcd.TokenSpaceGuidCName, {}).setdefault(Pcd.Type, [])
712 if Pcd not in PcdList and Pcd not in UnusedPcdFullList:
713 UnusedPcdFullList.append(Pcd)
714 if len(Pcd.TokenCName) > self.MaxLen:
715 self.MaxLen = len(Pcd.TokenCName)
716
717 if GlobalData.gConditionalPcds:
718 for PcdItem in GlobalData.gConditionalPcds:
719 if '.' in PcdItem:
720 (TokenSpaceGuidCName, TokenCName) = PcdItem.split('.')
721 if (TokenCName, TokenSpaceGuidCName) in Pa.Platform.Pcds.keys():
722 Pcd = Pa.Platform.Pcds[(TokenCName, TokenSpaceGuidCName)]
723 PcdList = self.ConditionalPcds.setdefault(Pcd.TokenSpaceGuidCName, {}).setdefault(Pcd.Type, [])
724 if Pcd not in PcdList:
725 PcdList.append(Pcd)
726
727 UnusedPcdList = []
728 if UnusedPcdFullList:
729 for Pcd in UnusedPcdFullList:
730 if Pcd.TokenSpaceGuidCName + '.' + Pcd.TokenCName in GlobalData.gConditionalPcds:
731 continue
732 UnusedPcdList.append(Pcd)
733
734 for Pcd in UnusedPcdList:
735 PcdList = self.UnusedPcds.setdefault(Pcd.TokenSpaceGuidCName, {}).setdefault(Pcd.Type, [])
736 if Pcd not in PcdList:
737 PcdList.append(Pcd)
738
739 for Module in Pa.Platform.Modules.values():
740 #
741 # Collect module override PCDs
742 #
743 for ModulePcd in Module.M.ModulePcdList + Module.M.LibraryPcdList:
744 TokenCName = ModulePcd.TokenCName
745 TokenSpaceGuid = ModulePcd.TokenSpaceGuidCName
746 ModuleDefault = ModulePcd.DefaultValue
747 ModulePath = os.path.basename(Module.M.MetaFile.File)
748 self.ModulePcdOverride.setdefault((TokenCName, TokenSpaceGuid), {})[ModulePath] = ModuleDefault
749
750
751 #
752 # Collect PCD DEC default value.
753 #
754 self.DecPcdDefault = {}
755 for Pa in Wa.AutoGenObjectList:
756 for Package in Pa.PackageList:
757 for (TokenCName, TokenSpaceGuidCName, DecType) in Package.Pcds:
758 DecDefaultValue = Package.Pcds[TokenCName, TokenSpaceGuidCName, DecType].DefaultValue
759 self.DecPcdDefault.setdefault((TokenCName, TokenSpaceGuidCName, DecType), DecDefaultValue)
760 #
761 # Collect PCDs defined in DSC common section
762 #
763 self.DscPcdDefault = {}
764 for Arch in Wa.ArchList:
765 Platform = Wa.BuildDatabase[Wa.MetaFile, Arch, Wa.BuildTarget, Wa.ToolChain]
766 for (TokenCName, TokenSpaceGuidCName) in Platform.Pcds:
767 DscDefaultValue = Platform.Pcds[(TokenCName, TokenSpaceGuidCName)].DefaultValue
768 if DscDefaultValue:
769 self.DscPcdDefault[(TokenCName, TokenSpaceGuidCName)] = DscDefaultValue
770
771 def GenerateReport(self, File, ModulePcdSet):
772 if self.ConditionalPcds:
773 self.GenerateReportDetail(File, ModulePcdSet, 1)
774 if self.UnusedPcds:
775 self.GenerateReportDetail(File, ModulePcdSet, 2)
776 self.GenerateReportDetail(File, ModulePcdSet)
777
778 ##
779 # Generate report for PCD information
780 #
781 # This function generates report for separate module expression
782 # in a platform build.
783 #
784 # @param self The object pointer
785 # @param File The file object for report
786 # @param ModulePcdSet Set of all PCDs referenced by module or None for
787 # platform PCD report
788 # @param ReportySubType 0 means platform/module PCD report, 1 means Conditional
789 # directives section report, 2 means Unused Pcds section report
790 # @param DscOverridePcds Module DSC override PCDs set
791 #
792 def GenerateReportDetail(self, File, ModulePcdSet, ReportSubType = 0):
793 PcdDict = self.AllPcds
794 if ReportSubType == 1:
795 PcdDict = self.ConditionalPcds
796 elif ReportSubType == 2:
797 PcdDict = self.UnusedPcds
798
799 if ModulePcdSet == None:
800 FileWrite(File, gSectionStart)
801 if ReportSubType == 1:
802 FileWrite(File, "Conditional Directives used by the build system")
803 elif ReportSubType == 2:
804 FileWrite(File, "PCDs not used by modules or in conditional directives")
805 else:
806 FileWrite(File, "Platform Configuration Database Report")
807
808 FileWrite(File, " *B - PCD override in the build option")
809 FileWrite(File, " *P - Platform scoped PCD override in DSC file")
810 FileWrite(File, " *F - Platform scoped PCD override in FDF file")
811 if not ReportSubType:
812 FileWrite(File, " *M - Module scoped PCD override")
813 FileWrite(File, gSectionSep)
814 else:
815 if not ReportSubType:
816 #
817 # For module PCD sub-section
818 #
819 FileWrite(File, gSubSectionStart)
820 FileWrite(File, TAB_BRG_PCD)
821 FileWrite(File, gSubSectionSep)
822
823 for Key in PcdDict:
824 #
825 # Group PCD by their token space GUID C Name
826 #
827 First = True
828 for Type in PcdDict[Key]:
829 #
830 # Group PCD by their usage type
831 #
832 TypeName, DecType = gPcdTypeMap.get(Type, ("", Type))
833 for Pcd in PcdDict[Key][Type]:
834 #
835 # Get PCD default value and their override relationship
836 #
837 DecDefaultValue = self.DecPcdDefault.get((Pcd.TokenCName, Pcd.TokenSpaceGuidCName, DecType))
838 DscDefaultValue = self.DscPcdDefault.get((Pcd.TokenCName, Pcd.TokenSpaceGuidCName))
839 DscDefaultValue = self.FdfPcdSet.get((Pcd.TokenCName, Key), DscDefaultValue)
840 InfDefaultValue = None
841
842 PcdValue = DecDefaultValue
843 if DscDefaultValue:
844 PcdValue = DscDefaultValue
845 if ModulePcdSet != None:
846 if (Pcd.TokenCName, Pcd.TokenSpaceGuidCName, Type) not in ModulePcdSet:
847 continue
848 InfDefault, PcdValue = ModulePcdSet[Pcd.TokenCName, Pcd.TokenSpaceGuidCName, Type]
849 if InfDefault == "":
850 InfDefault = None
851
852 BuildOptionMatch = False
853 if GlobalData.BuildOptionPcd:
854 for pcd in GlobalData.BuildOptionPcd:
855 if (Pcd.TokenSpaceGuidCName, Pcd.TokenCName) == (pcd[0], pcd[1]):
856 PcdValue = pcd[2]
857 BuildOptionMatch = True
858 break
859
860 if First:
861 if ModulePcdSet == None:
862 FileWrite(File, "")
863 FileWrite(File, Key)
864 First = False
865
866
867 if Pcd.DatumType in ('UINT8', 'UINT16', 'UINT32', 'UINT64'):
868 PcdValueNumber = int(PcdValue.strip(), 0)
869 if DecDefaultValue == None:
870 DecMatch = True
871 else:
872 DecDefaultValueNumber = int(DecDefaultValue.strip(), 0)
873 DecMatch = (DecDefaultValueNumber == PcdValueNumber)
874
875 if InfDefaultValue == None:
876 InfMatch = True
877 else:
878 InfDefaultValueNumber = int(InfDefaultValue.strip(), 0)
879 InfMatch = (InfDefaultValueNumber == PcdValueNumber)
880
881 if DscDefaultValue == None:
882 DscMatch = True
883 else:
884 DscDefaultValueNumber = int(DscDefaultValue.strip(), 0)
885 DscMatch = (DscDefaultValueNumber == PcdValueNumber)
886 else:
887 if DecDefaultValue == None:
888 DecMatch = True
889 else:
890 DecMatch = (DecDefaultValue.strip() == PcdValue.strip())
891
892 if InfDefaultValue == None:
893 InfMatch = True
894 else:
895 InfMatch = (InfDefaultValue.strip() == PcdValue.strip())
896
897 if DscDefaultValue == None:
898 DscMatch = True
899 else:
900 DscMatch = (DscDefaultValue.strip() == PcdValue.strip())
901
902 #
903 # Report PCD item according to their override relationship
904 #
905 if BuildOptionMatch:
906 FileWrite(File, ' *B %-*s: %6s %10s = %-22s' % (self.MaxLen, Pcd.TokenCName, TypeName, '(' + Pcd.DatumType + ')', PcdValue.strip()))
907 elif DecMatch and InfMatch:
908 FileWrite(File, ' %-*s: %6s %10s = %-22s' % (self.MaxLen, Pcd.TokenCName, TypeName, '(' + Pcd.DatumType + ')', PcdValue.strip()))
909 else:
910 if DscMatch:
911 if (Pcd.TokenCName, Key) in self.FdfPcdSet:
912 FileWrite(File, ' *F %-*s: %6s %10s = %-22s' % (self.MaxLen, Pcd.TokenCName, TypeName, '(' + Pcd.DatumType + ')', PcdValue.strip()))
913 else:
914 FileWrite(File, ' *P %-*s: %6s %10s = %-22s' % (self.MaxLen, Pcd.TokenCName, TypeName, '(' + Pcd.DatumType + ')', PcdValue.strip()))
915 else:
916 FileWrite(File, ' *M %-*s: %6s %10s = %-22s' % (self.MaxLen, Pcd.TokenCName, TypeName, '(' + Pcd.DatumType + ')', PcdValue.strip()))
917
918 if TypeName in ('DYNHII', 'DEXHII', 'DYNVPD', 'DEXVPD'):
919 for SkuInfo in Pcd.SkuInfoList.values():
920 if TypeName in ('DYNHII', 'DEXHII'):
921 FileWrite(File, '%*s: %s: %s' % (self.MaxLen + 4, SkuInfo.VariableGuid, SkuInfo.VariableName, SkuInfo.VariableOffset))
922 else:
923 FileWrite(File, '%*s' % (self.MaxLen + 4, SkuInfo.VpdOffset))
924
925 if not DscMatch and DscDefaultValue != None:
926 FileWrite(File, ' %*s = %s' % (self.MaxLen + 19, 'DSC DEFAULT', DscDefaultValue.strip()))
927
928 if not InfMatch and InfDefaultValue != None:
929 FileWrite(File, ' %*s = %s' % (self.MaxLen + 19, 'INF DEFAULT', InfDefaultValue.strip()))
930
931 if not DecMatch and DecDefaultValue != None:
932 FileWrite(File, ' %*s = %s' % (self.MaxLen + 19, 'DEC DEFAULT', DecDefaultValue.strip()))
933
934 if ModulePcdSet == None:
935 if not BuildOptionMatch:
936 ModuleOverride = self.ModulePcdOverride.get((Pcd.TokenCName, Pcd.TokenSpaceGuidCName), {})
937 for ModulePath in ModuleOverride:
938 ModuleDefault = ModuleOverride[ModulePath]
939 if Pcd.DatumType in ('UINT8', 'UINT16', 'UINT32', 'UINT64'):
940 ModulePcdDefaultValueNumber = int(ModuleDefault.strip(), 0)
941 Match = (ModulePcdDefaultValueNumber == PcdValueNumber)
942 else:
943 Match = (ModuleDefault.strip() == PcdValue.strip())
944 if Match:
945 continue
946 FileWrite(File, ' *M %-*s = %s' % (self.MaxLen + 19, ModulePath, ModuleDefault.strip()))
947
948 if ModulePcdSet == None:
949 FileWrite(File, gSectionEnd)
950 else:
951 if not ReportSubType:
952 FileWrite(File, gSubSectionEnd)
953
954
955
956 ##
957 # Reports platform and module Prediction information
958 #
959 # This class reports the platform execution order prediction section and
960 # module load fixed address prediction subsection in the build report file.
961 #
962 class PredictionReport(object):
963 ##
964 # Constructor function for class PredictionReport
965 #
966 # This constructor function generates PredictionReport object for the platform.
967 #
968 # @param self: The object pointer
969 # @param Wa Workspace context information
970 #
971 def __init__(self, Wa):
972 self._MapFileName = os.path.join(Wa.BuildDir, Wa.Name + ".map")
973 self._MapFileParsed = False
974 self._EotToolInvoked = False
975 self._FvDir = Wa.FvDir
976 self._EotDir = Wa.BuildDir
977 self._FfsEntryPoint = {}
978 self._GuidMap = {}
979 self._SourceList = []
980 self.FixedMapDict = {}
981 self.ItemList = []
982 self.MaxLen = 0
983
984 #
985 # Collect all platform reference source files and GUID C Name
986 #
987 for Pa in Wa.AutoGenObjectList:
988 for Module in Pa.LibraryAutoGenList + Pa.ModuleAutoGenList:
989 #
990 # BASE typed modules are EFI agnostic, so we need not scan
991 # their source code to find PPI/Protocol produce or consume
992 # information.
993 #
994 if Module.ModuleType == "BASE":
995 continue
996 #
997 # Add module referenced source files
998 #
999 self._SourceList.append(str(Module))
1000 IncludeList = {}
1001 for Source in Module.SourceFileList:
1002 if os.path.splitext(str(Source))[1].lower() == ".c":
1003 self._SourceList.append(" " + str(Source))
1004 FindIncludeFiles(Source.Path, Module.IncludePathList, IncludeList)
1005 for IncludeFile in IncludeList.values():
1006 self._SourceList.append(" " + IncludeFile)
1007
1008 for Guid in Module.PpiList:
1009 self._GuidMap[Guid] = GuidStructureStringToGuidString(Module.PpiList[Guid])
1010 for Guid in Module.ProtocolList:
1011 self._GuidMap[Guid] = GuidStructureStringToGuidString(Module.ProtocolList[Guid])
1012 for Guid in Module.GuidList:
1013 self._GuidMap[Guid] = GuidStructureStringToGuidString(Module.GuidList[Guid])
1014
1015 if Module.Guid and not Module.IsLibrary:
1016 EntryPoint = " ".join(Module.Module.ModuleEntryPointList)
1017 if int(str(Module.AutoGenVersion), 0) >= 0x00010005:
1018 RealEntryPoint = "_ModuleEntryPoint"
1019 else:
1020 RealEntryPoint = EntryPoint
1021 if EntryPoint == "_ModuleEntryPoint":
1022 CCFlags = Module.BuildOption.get("CC", {}).get("FLAGS", "")
1023 Match = gGlueLibEntryPoint.search(CCFlags)
1024 if Match:
1025 EntryPoint = Match.group(1)
1026
1027 self._FfsEntryPoint[Module.Guid.upper()] = (EntryPoint, RealEntryPoint)
1028
1029
1030 #
1031 # Collect platform firmware volume list as the input of EOT.
1032 #
1033 self._FvList = []
1034 if Wa.FdfProfile:
1035 for Fd in Wa.FdfProfile.FdDict:
1036 for FdRegion in Wa.FdfProfile.FdDict[Fd].RegionList:
1037 if FdRegion.RegionType != "FV":
1038 continue
1039 for FvName in FdRegion.RegionDataList:
1040 if FvName in self._FvList:
1041 continue
1042 self._FvList.append(FvName)
1043 for Ffs in Wa.FdfProfile.FvDict[FvName.upper()].FfsList:
1044 for Section in Ffs.SectionList:
1045 try:
1046 for FvSection in Section.SectionList:
1047 if FvSection.FvName in self._FvList:
1048 continue
1049 self._FvList.append(FvSection.FvName)
1050 except AttributeError:
1051 pass
1052
1053
1054 ##
1055 # Parse platform fixed address map files
1056 #
1057 # This function parses the platform final fixed address map file to get
1058 # the database of predicted fixed address for module image base, entry point
1059 # etc.
1060 #
1061 # @param self: The object pointer
1062 #
1063 def _ParseMapFile(self):
1064 if self._MapFileParsed:
1065 return
1066 self._MapFileParsed = True
1067 if os.path.isfile(self._MapFileName):
1068 try:
1069 FileContents = open(self._MapFileName).read()
1070 for Match in gMapFileItemPattern.finditer(FileContents):
1071 AddressType = Match.group(1)
1072 BaseAddress = Match.group(2)
1073 EntryPoint = Match.group(3)
1074 Guid = Match.group(4).upper()
1075 List = self.FixedMapDict.setdefault(Guid, [])
1076 List.append((AddressType, BaseAddress, "*I"))
1077 List.append((AddressType, EntryPoint, "*E"))
1078 except:
1079 EdkLogger.warn(None, "Cannot open file to read", self._MapFileName)
1080
1081 ##
1082 # Invokes EOT tool to get the predicted the execution order.
1083 #
1084 # This function invokes EOT tool to calculate the predicted dispatch order
1085 #
1086 # @param self: The object pointer
1087 #
1088 def _InvokeEotTool(self):
1089 if self._EotToolInvoked:
1090 return
1091
1092 self._EotToolInvoked = True
1093 FvFileList = []
1094 for FvName in self._FvList:
1095 FvFile = os.path.join(self._FvDir, FvName + ".Fv")
1096 if os.path.isfile(FvFile):
1097 FvFileList.append(FvFile)
1098
1099 if len(FvFileList) == 0:
1100 return
1101 #
1102 # Write source file list and GUID file list to an intermediate file
1103 # as the input for EOT tool and dispatch List as the output file
1104 # from EOT tool.
1105 #
1106 SourceList = os.path.join(self._EotDir, "SourceFile.txt")
1107 GuidList = os.path.join(self._EotDir, "GuidList.txt")
1108 DispatchList = os.path.join(self._EotDir, "Dispatch.txt")
1109
1110 TempFile = open(SourceList, "w+")
1111 for Item in self._SourceList:
1112 FileWrite(TempFile, Item)
1113 TempFile.close()
1114 TempFile = open(GuidList, "w+")
1115 for Key in self._GuidMap:
1116 FileWrite(TempFile, "%s %s" % (Key, self._GuidMap[Key]))
1117 TempFile.close()
1118
1119 try:
1120 from Eot.Eot import Eot
1121
1122 #
1123 # Invoke EOT tool and echo its runtime performance
1124 #
1125 EotStartTime = time.time()
1126 Eot(CommandLineOption=False, SourceFileList=SourceList, GuidList=GuidList,
1127 FvFileList=' '.join(FvFileList), Dispatch=DispatchList, IsInit=True)
1128 EotEndTime = time.time()
1129 EotDuration = time.strftime("%H:%M:%S", time.gmtime(int(round(EotEndTime - EotStartTime))))
1130 EdkLogger.quiet("EOT run time: %s\n" % EotDuration)
1131
1132 #
1133 # Parse the output of EOT tool
1134 #
1135 for Line in open(DispatchList):
1136 if len(Line.split()) < 4:
1137 continue
1138 (Guid, Phase, FfsName, FilePath) = Line.split()
1139 Symbol = self._FfsEntryPoint.get(Guid, [FfsName, ""])[0]
1140 if len(Symbol) > self.MaxLen:
1141 self.MaxLen = len(Symbol)
1142 self.ItemList.append((Phase, Symbol, FilePath))
1143 except:
1144 EdkLogger.quiet("(Python %s on %s\n%s)" % (platform.python_version(), sys.platform, traceback.format_exc()))
1145 EdkLogger.warn(None, "Failed to generate execution order prediction report, for some error occurred in executing EOT.")
1146
1147
1148 ##
1149 # Generate platform execution order report
1150 #
1151 # This function generates the predicted module execution order.
1152 #
1153 # @param self The object pointer
1154 # @param File The file object for report
1155 #
1156 def _GenerateExecutionOrderReport(self, File):
1157 self._InvokeEotTool()
1158 if len(self.ItemList) == 0:
1159 return
1160 FileWrite(File, gSectionStart)
1161 FileWrite(File, "Execution Order Prediction")
1162 FileWrite(File, "*P PEI phase")
1163 FileWrite(File, "*D DXE phase")
1164 FileWrite(File, "*E Module INF entry point name")
1165 FileWrite(File, "*N Module notification function name")
1166
1167 FileWrite(File, "Type %-*s %s" % (self.MaxLen, "Symbol", "Module INF Path"))
1168 FileWrite(File, gSectionSep)
1169 for Item in self.ItemList:
1170 FileWrite(File, "*%sE %-*s %s" % (Item[0], self.MaxLen, Item[1], Item[2]))
1171
1172 FileWrite(File, gSectionStart)
1173
1174 ##
1175 # Generate Fixed Address report.
1176 #
1177 # This function generate the predicted fixed address report for a module
1178 # specified by Guid.
1179 #
1180 # @param self The object pointer
1181 # @param File The file object for report
1182 # @param Guid The module Guid value.
1183 # @param NotifyList The list of all notify function in a module
1184 #
1185 def _GenerateFixedAddressReport(self, File, Guid, NotifyList):
1186 self._ParseMapFile()
1187 FixedAddressList = self.FixedMapDict.get(Guid)
1188 if not FixedAddressList:
1189 return
1190
1191 FileWrite(File, gSubSectionStart)
1192 FileWrite(File, "Fixed Address Prediction")
1193 FileWrite(File, "*I Image Loading Address")
1194 FileWrite(File, "*E Entry Point Address")
1195 FileWrite(File, "*N Notification Function Address")
1196 FileWrite(File, "*F Flash Address")
1197 FileWrite(File, "*M Memory Address")
1198 FileWrite(File, "*S SMM RAM Offset")
1199 FileWrite(File, "TOM Top of Memory")
1200
1201 FileWrite(File, "Type Address Name")
1202 FileWrite(File, gSubSectionSep)
1203 for Item in FixedAddressList:
1204 Type = Item[0]
1205 Value = Item[1]
1206 Symbol = Item[2]
1207 if Symbol == "*I":
1208 Name = "(Image Base)"
1209 elif Symbol == "*E":
1210 Name = self._FfsEntryPoint.get(Guid, ["", "_ModuleEntryPoint"])[1]
1211 elif Symbol in NotifyList:
1212 Name = Symbol
1213 Symbol = "*N"
1214 else:
1215 continue
1216
1217 if "Flash" in Type:
1218 Symbol += "F"
1219 elif "Memory" in Type:
1220 Symbol += "M"
1221 else:
1222 Symbol += "S"
1223
1224 if Value[0] == "-":
1225 Value = "TOM" + Value
1226
1227 FileWrite(File, "%s %-16s %s" % (Symbol, Value, Name))
1228
1229 ##
1230 # Generate report for the prediction part
1231 #
1232 # This function generate the predicted fixed address report for a module or
1233 # predicted module execution order for a platform.
1234 # If the input Guid is None, then, it generates the predicted module execution order;
1235 # otherwise it generated the module fixed loading address for the module specified by
1236 # Guid.
1237 #
1238 # @param self The object pointer
1239 # @param File The file object for report
1240 # @param Guid The module Guid value.
1241 #
1242 def GenerateReport(self, File, Guid):
1243 if Guid:
1244 self._GenerateFixedAddressReport(File, Guid.upper(), [])
1245 else:
1246 self._GenerateExecutionOrderReport(File)
1247
1248 ##
1249 # Reports FD region information
1250 #
1251 # This class reports the FD subsection in the build report file.
1252 # It collects region information of platform flash device.
1253 # If the region is a firmware volume, it lists the set of modules
1254 # and its space information; otherwise, it only lists its region name,
1255 # base address and size in its sub-section header.
1256 # If there are nesting FVs, the nested FVs will list immediate after
1257 # this FD region subsection
1258 #
1259 class FdRegionReport(object):
1260 ##
1261 # Discover all the nested FV name list.
1262 #
1263 # This is an internal worker function to discover the all the nested FV information
1264 # in the parent firmware volume. It uses deep first search algorithm recursively to
1265 # find all the FV list name and append them to the list.
1266 #
1267 # @param self The object pointer
1268 # @param FvName The name of current firmware file system
1269 # @param Wa Workspace context information
1270 #
1271 def _DiscoverNestedFvList(self, FvName, Wa):
1272 FvDictKey=FvName.upper()
1273 if FvDictKey in Wa.FdfProfile.FvDict:
1274 for Ffs in Wa.FdfProfile.FvDict[FvName.upper()].FfsList:
1275 for Section in Ffs.SectionList:
1276 try:
1277 for FvSection in Section.SectionList:
1278 if FvSection.FvName in self.FvList:
1279 continue
1280 self._GuidsDb[Ffs.NameGuid.upper()] = FvSection.FvName
1281 self.FvList.append(FvSection.FvName)
1282 self.FvInfo[FvSection.FvName] = ("Nested FV", 0, 0)
1283 self._DiscoverNestedFvList(FvSection.FvName, Wa)
1284 except AttributeError:
1285 pass
1286
1287 ##
1288 # Constructor function for class FdRegionReport
1289 #
1290 # This constructor function generates FdRegionReport object for a specified FdRegion.
1291 # If the FdRegion is a firmware volume, it will recursively find all its nested Firmware
1292 # volume list. This function also collects GUID map in order to dump module identification
1293 # in the final report.
1294 #
1295 # @param self: The object pointer
1296 # @param FdRegion The current FdRegion object
1297 # @param Wa Workspace context information
1298 #
1299 def __init__(self, FdRegion, Wa):
1300 self.Type = FdRegion.RegionType
1301 self.BaseAddress = FdRegion.Offset
1302 self.Size = FdRegion.Size
1303 self.FvList = []
1304 self.FvInfo = {}
1305 self._GuidsDb = {}
1306 self._FvDir = Wa.FvDir
1307
1308 #
1309 # If the input FdRegion is not a firmware volume,
1310 # we are done.
1311 #
1312 if self.Type != "FV":
1313 return
1314
1315 #
1316 # Find all nested FVs in the FdRegion
1317 #
1318 for FvName in FdRegion.RegionDataList:
1319 if FvName in self.FvList:
1320 continue
1321 self.FvList.append(FvName)
1322 self.FvInfo[FvName] = ("Fd Region", self.BaseAddress, self.Size)
1323 self._DiscoverNestedFvList(FvName, Wa)
1324
1325 PlatformPcds = {}
1326 #
1327 # Collect PCDs declared in DEC files.
1328 #
1329 for Pa in Wa.AutoGenObjectList:
1330 for Package in Pa.PackageList:
1331 for (TokenCName, TokenSpaceGuidCName, DecType) in Package.Pcds:
1332 DecDefaultValue = Package.Pcds[TokenCName, TokenSpaceGuidCName, DecType].DefaultValue
1333 PlatformPcds[(TokenCName, TokenSpaceGuidCName)] = DecDefaultValue
1334 #
1335 # Collect PCDs defined in DSC file
1336 #
1337 for arch in Wa.ArchList:
1338 Platform = Wa.BuildDatabase[Wa.MetaFile, arch]
1339 for (TokenCName, TokenSpaceGuidCName) in Platform.Pcds:
1340 DscDefaultValue = Platform.Pcds[(TokenCName, TokenSpaceGuidCName)].DefaultValue
1341 PlatformPcds[(TokenCName, TokenSpaceGuidCName)] = DscDefaultValue
1342
1343 #
1344 # Add PEI and DXE a priori files GUIDs defined in PI specification.
1345 #
1346 self._GuidsDb["1B45CC0A-156A-428A-AF62-49864DA0E6E6"] = "PEI Apriori"
1347 self._GuidsDb["FC510EE7-FFDC-11D4-BD41-0080C73C8881"] = "DXE Apriori"
1348 #
1349 # Add ACPI table storage file
1350 #
1351 self._GuidsDb["7E374E25-8E01-4FEE-87F2-390C23C606CD"] = "ACPI table storage"
1352
1353 for Pa in Wa.AutoGenObjectList:
1354 for ModuleKey in Pa.Platform.Modules:
1355 M = Pa.Platform.Modules[ModuleKey].M
1356 InfPath = mws.join(Wa.WorkspaceDir, M.MetaFile.File)
1357 self._GuidsDb[M.Guid.upper()] = "%s (%s)" % (M.Module.BaseName, InfPath)
1358
1359 #
1360 # Collect the GUID map in the FV firmware volume
1361 #
1362 for FvName in self.FvList:
1363 FvDictKey=FvName.upper()
1364 if FvDictKey in Wa.FdfProfile.FvDict:
1365 for Ffs in Wa.FdfProfile.FvDict[FvName.upper()].FfsList:
1366 try:
1367 #
1368 # collect GUID map for binary EFI file in FDF file.
1369 #
1370 Guid = Ffs.NameGuid.upper()
1371 Match = gPcdGuidPattern.match(Ffs.NameGuid)
1372 if Match:
1373 PcdTokenspace = Match.group(1)
1374 PcdToken = Match.group(2)
1375 if (PcdToken, PcdTokenspace) in PlatformPcds:
1376 GuidValue = PlatformPcds[(PcdToken, PcdTokenspace)]
1377 Guid = GuidStructureByteArrayToGuidString(GuidValue).upper()
1378 for Section in Ffs.SectionList:
1379 try:
1380 ModuleSectFile = mws.join(Wa.WorkspaceDir, Section.SectFileName)
1381 self._GuidsDb[Guid] = ModuleSectFile
1382 except AttributeError:
1383 pass
1384 except AttributeError:
1385 pass
1386
1387
1388 ##
1389 # Internal worker function to generate report for the FD region
1390 #
1391 # This internal worker function to generate report for the FD region.
1392 # It the type is firmware volume, it lists offset and module identification.
1393 #
1394 # @param self The object pointer
1395 # @param File The file object for report
1396 # @param Title The title for the FD subsection
1397 # @param BaseAddress The base address for the FD region
1398 # @param Size The size of the FD region
1399 # @param FvName The FV name if the FD region is a firmware volume
1400 #
1401 def _GenerateReport(self, File, Title, Type, BaseAddress, Size=0, FvName=None):
1402 FileWrite(File, gSubSectionStart)
1403 FileWrite(File, Title)
1404 FileWrite(File, "Type: %s" % Type)
1405 FileWrite(File, "Base Address: 0x%X" % BaseAddress)
1406
1407 if self.Type == "FV":
1408 FvTotalSize = 0
1409 FvTakenSize = 0
1410 FvFreeSize = 0
1411 FvReportFileName = os.path.join(self._FvDir, FvName + ".Fv.txt")
1412 try:
1413 #
1414 # Collect size info in the firmware volume.
1415 #
1416 FvReport = open(FvReportFileName).read()
1417 Match = gFvTotalSizePattern.search(FvReport)
1418 if Match:
1419 FvTotalSize = int(Match.group(1), 16)
1420 Match = gFvTakenSizePattern.search(FvReport)
1421 if Match:
1422 FvTakenSize = int(Match.group(1), 16)
1423 FvFreeSize = FvTotalSize - FvTakenSize
1424 #
1425 # Write size information to the report file.
1426 #
1427 FileWrite(File, "Size: 0x%X (%.0fK)" % (FvTotalSize, FvTotalSize / 1024.0))
1428 FileWrite(File, "Fv Name: %s (%.1f%% Full)" % (FvName, FvTakenSize * 100.0 / FvTotalSize))
1429 FileWrite(File, "Occupied Size: 0x%X (%.0fK)" % (FvTakenSize, FvTakenSize / 1024.0))
1430 FileWrite(File, "Free Size: 0x%X (%.0fK)" % (FvFreeSize, FvFreeSize / 1024.0))
1431 FileWrite(File, "Offset Module")
1432 FileWrite(File, gSubSectionSep)
1433 #
1434 # Write module offset and module identification to the report file.
1435 #
1436 OffsetInfo = {}
1437 for Match in gOffsetGuidPattern.finditer(FvReport):
1438 Guid = Match.group(2).upper()
1439 OffsetInfo[Match.group(1)] = self._GuidsDb.get(Guid, Guid)
1440 OffsetList = OffsetInfo.keys()
1441 OffsetList.sort()
1442 for Offset in OffsetList:
1443 FileWrite (File, "%s %s" % (Offset, OffsetInfo[Offset]))
1444 except IOError:
1445 EdkLogger.warn(None, "Fail to read report file", FvReportFileName)
1446 else:
1447 FileWrite(File, "Size: 0x%X (%.0fK)" % (Size, Size / 1024.0))
1448 FileWrite(File, gSubSectionEnd)
1449
1450 ##
1451 # Generate report for the FD region
1452 #
1453 # This function generates report for the FD region.
1454 #
1455 # @param self The object pointer
1456 # @param File The file object for report
1457 #
1458 def GenerateReport(self, File):
1459 if (len(self.FvList) > 0):
1460 for FvItem in self.FvList:
1461 Info = self.FvInfo[FvItem]
1462 self._GenerateReport(File, Info[0], "FV", Info[1], Info[2], FvItem)
1463 else:
1464 self._GenerateReport(File, "FD Region", self.Type, self.BaseAddress, self.Size)
1465
1466 ##
1467 # Reports FD information
1468 #
1469 # This class reports the FD section in the build report file.
1470 # It collects flash device information for a platform.
1471 #
1472 class FdReport(object):
1473 ##
1474 # Constructor function for class FdReport
1475 #
1476 # This constructor function generates FdReport object for a specified
1477 # firmware device.
1478 #
1479 # @param self The object pointer
1480 # @param Fd The current Firmware device object
1481 # @param Wa Workspace context information
1482 #
1483 def __init__(self, Fd, Wa):
1484 self.FdName = Fd.FdUiName
1485 self.BaseAddress = Fd.BaseAddress
1486 self.Size = Fd.Size
1487 self.FdRegionList = [FdRegionReport(FdRegion, Wa) for FdRegion in Fd.RegionList]
1488 self.FvPath = os.path.join(Wa.BuildDir, "FV")
1489 self.VpdFilePath = os.path.join(self.FvPath, "%s.map" % Wa.Platform.VpdToolGuid)
1490 self.VPDBaseAddress = 0
1491 self.VPDSize = 0
1492 self.VPDInfoList = []
1493 for index, FdRegion in enumerate(Fd.RegionList):
1494 if str(FdRegion.RegionType) is 'FILE' and Wa.Platform.VpdToolGuid in str(FdRegion.RegionDataList):
1495 self.VPDBaseAddress = self.FdRegionList[index].BaseAddress
1496 self.VPDSize = self.FdRegionList[index].Size
1497 break
1498
1499 if os.path.isfile(self.VpdFilePath):
1500 fd = open(self.VpdFilePath, "r")
1501 Lines = fd.readlines()
1502 for Line in Lines:
1503 Line = Line.strip()
1504 if len(Line) == 0 or Line.startswith("#"):
1505 continue
1506 try:
1507 PcdName, SkuId, Offset, Size, Value = Line.split("#")[0].split("|")
1508 PcdName, SkuId, Offset, Size, Value = PcdName.strip(), SkuId.strip(), Offset.strip(), Size.strip(), Value.strip()
1509 Offset = '0x%08X' % (int(Offset, 16) + self.VPDBaseAddress)
1510 self.VPDInfoList.append("%s | %s | %s | %s | %s" % (PcdName, SkuId, Offset, Size, Value))
1511 except:
1512 EdkLogger.error("BuildReport", CODE_ERROR, "Fail to parse VPD information file %s" % self.VpdFilePath)
1513 fd.close()
1514
1515 ##
1516 # Generate report for the firmware device.
1517 #
1518 # This function generates report for the firmware device.
1519 #
1520 # @param self The object pointer
1521 # @param File The file object for report
1522 #
1523 def GenerateReport(self, File):
1524 FileWrite(File, gSectionStart)
1525 FileWrite(File, "Firmware Device (FD)")
1526 FileWrite(File, "FD Name: %s" % self.FdName)
1527 FileWrite(File, "Base Address: %s" % self.BaseAddress)
1528 FileWrite(File, "Size: 0x%X (%.0fK)" % (self.Size, self.Size / 1024.0))
1529 if len(self.FdRegionList) > 0:
1530 FileWrite(File, gSectionSep)
1531 for FdRegionItem in self.FdRegionList:
1532 FdRegionItem.GenerateReport(File)
1533
1534 if len(self.VPDInfoList) > 0:
1535 FileWrite(File, gSubSectionStart)
1536 FileWrite(File, "FD VPD Region")
1537 FileWrite(File, "Base Address: 0x%X" % self.VPDBaseAddress)
1538 FileWrite(File, "Size: 0x%X (%.0fK)" % (self.VPDSize, self.VPDSize / 1024.0))
1539 FileWrite(File, gSubSectionSep)
1540 for item in self.VPDInfoList:
1541 FileWrite(File, item)
1542 FileWrite(File, gSubSectionEnd)
1543 FileWrite(File, gSectionEnd)
1544
1545
1546
1547 ##
1548 # Reports platform information
1549 #
1550 # This class reports the whole platform information
1551 #
1552 class PlatformReport(object):
1553 ##
1554 # Constructor function for class PlatformReport
1555 #
1556 # This constructor function generates PlatformReport object a platform build.
1557 # It generates report for platform summary, flash, global PCDs and detailed
1558 # module information for modules involved in platform build.
1559 #
1560 # @param self The object pointer
1561 # @param Wa Workspace context information
1562 # @param MaList The list of modules in the platform build
1563 #
1564 def __init__(self, Wa, MaList, ReportType):
1565 self._WorkspaceDir = Wa.WorkspaceDir
1566 self.PlatformName = Wa.Name
1567 self.PlatformDscPath = Wa.Platform
1568 self.Architectures = " ".join(Wa.ArchList)
1569 self.ToolChain = Wa.ToolChain
1570 self.Target = Wa.BuildTarget
1571 self.OutputPath = os.path.join(Wa.WorkspaceDir, Wa.OutputDir)
1572 self.BuildEnvironment = platform.platform()
1573
1574 self.PcdReport = None
1575 if "PCD" in ReportType:
1576 self.PcdReport = PcdReport(Wa)
1577
1578 self.FdReportList = []
1579 if "FLASH" in ReportType and Wa.FdfProfile and MaList == None:
1580 for Fd in Wa.FdfProfile.FdDict:
1581 self.FdReportList.append(FdReport(Wa.FdfProfile.FdDict[Fd], Wa))
1582
1583 self.PredictionReport = None
1584 if "FIXED_ADDRESS" in ReportType or "EXECUTION_ORDER" in ReportType:
1585 self.PredictionReport = PredictionReport(Wa)
1586
1587 self.DepexParser = None
1588 if "DEPEX" in ReportType:
1589 self.DepexParser = DepexParser(Wa)
1590
1591 self.ModuleReportList = []
1592 if MaList != None:
1593 self._IsModuleBuild = True
1594 for Ma in MaList:
1595 self.ModuleReportList.append(ModuleReport(Ma, ReportType))
1596 else:
1597 self._IsModuleBuild = False
1598 for Pa in Wa.AutoGenObjectList:
1599 for ModuleKey in Pa.Platform.Modules:
1600 self.ModuleReportList.append(ModuleReport(Pa.Platform.Modules[ModuleKey].M, ReportType))
1601
1602
1603
1604 ##
1605 # Generate report for the whole platform.
1606 #
1607 # This function generates report for platform information.
1608 # It comprises of platform summary, global PCD, flash and
1609 # module list sections.
1610 #
1611 # @param self The object pointer
1612 # @param File The file object for report
1613 # @param BuildDuration The total time to build the modules
1614 # @param ReportType The kind of report items in the final report file
1615 #
1616 def GenerateReport(self, File, BuildDuration, ReportType):
1617 FileWrite(File, "Platform Summary")
1618 FileWrite(File, "Platform Name: %s" % self.PlatformName)
1619 FileWrite(File, "Platform DSC Path: %s" % self.PlatformDscPath)
1620 FileWrite(File, "Architectures: %s" % self.Architectures)
1621 FileWrite(File, "Tool Chain: %s" % self.ToolChain)
1622 FileWrite(File, "Target: %s" % self.Target)
1623 FileWrite(File, "Output Path: %s" % self.OutputPath)
1624 FileWrite(File, "Build Environment: %s" % self.BuildEnvironment)
1625 FileWrite(File, "Build Duration: %s" % BuildDuration)
1626 FileWrite(File, "Report Content: %s" % ", ".join(ReportType))
1627
1628 if not self._IsModuleBuild:
1629 if "PCD" in ReportType:
1630 self.PcdReport.GenerateReport(File, None)
1631
1632 if "FLASH" in ReportType:
1633 for FdReportListItem in self.FdReportList:
1634 FdReportListItem.GenerateReport(File)
1635
1636 for ModuleReportItem in self.ModuleReportList:
1637 ModuleReportItem.GenerateReport(File, self.PcdReport, self.PredictionReport, self.DepexParser, ReportType)
1638
1639 if not self._IsModuleBuild:
1640 if "EXECUTION_ORDER" in ReportType:
1641 self.PredictionReport.GenerateReport(File, None)
1642
1643 ## BuildReport class
1644 #
1645 # This base class contain the routines to collect data and then
1646 # applies certain format to the output report
1647 #
1648 class BuildReport(object):
1649 ##
1650 # Constructor function for class BuildReport
1651 #
1652 # This constructor function generates BuildReport object a platform build.
1653 # It generates report for platform summary, flash, global PCDs and detailed
1654 # module information for modules involved in platform build.
1655 #
1656 # @param self The object pointer
1657 # @param ReportFile The file name to save report file
1658 # @param ReportType The kind of report items in the final report file
1659 #
1660 def __init__(self, ReportFile, ReportType):
1661 self.ReportFile = ReportFile
1662 if ReportFile:
1663 self.ReportList = []
1664 self.ReportType = []
1665 if ReportType:
1666 for ReportTypeItem in ReportType:
1667 if ReportTypeItem not in self.ReportType:
1668 self.ReportType.append(ReportTypeItem)
1669 else:
1670 self.ReportType = ["PCD", "LIBRARY", "BUILD_FLAGS", "DEPEX", "FLASH", "FIXED_ADDRESS"]
1671 ##
1672 # Adds platform report to the list
1673 #
1674 # This function adds a platform report to the final report list.
1675 #
1676 # @param self The object pointer
1677 # @param Wa Workspace context information
1678 # @param MaList The list of modules in the platform build
1679 #
1680 def AddPlatformReport(self, Wa, MaList=None):
1681 if self.ReportFile:
1682 self.ReportList.append((Wa, MaList))
1683
1684 ##
1685 # Generates the final report.
1686 #
1687 # This function generates platform build report. It invokes GenerateReport()
1688 # method for every platform report in the list.
1689 #
1690 # @param self The object pointer
1691 # @param BuildDuration The total time to build the modules
1692 #
1693 def GenerateReport(self, BuildDuration):
1694 if self.ReportFile:
1695 try:
1696 File = StringIO('')
1697 for (Wa, MaList) in self.ReportList:
1698 PlatformReport(Wa, MaList, self.ReportType).GenerateReport(File, BuildDuration, self.ReportType)
1699 Content = FileLinesSplit(File.getvalue(), gLineMaxLength)
1700 SaveFileOnChange(self.ReportFile, Content, True)
1701 EdkLogger.quiet("Build report can be found at %s" % os.path.abspath(self.ReportFile))
1702 except IOError:
1703 EdkLogger.error(None, FILE_WRITE_FAILURE, ExtraData=self.ReportFile)
1704 except:
1705 EdkLogger.error("BuildReport", CODE_ERROR, "Unknown fatal error when generating build report", ExtraData=self.ReportFile, RaiseError=False)
1706 EdkLogger.quiet("(Python %s on %s\n%s)" % (platform.python_version(), sys.platform, traceback.format_exc()))
1707 File.close()
1708
1709 # This acts like the main() function for the script, unless it is 'import'ed into another script.
1710 if __name__ == '__main__':
1711 pass
1712