Check In tool source code based on Build tool project revision r1655.
[mirror_edk2.git] / BaseTools / Source / Python / Trim / Trim.py
1 ## @file
2 # Trim files preprocessed by compiler
3 #
4 # Copyright (c) 2007, Intel Corporation
5 # All rights reserved. This program and the accompanying materials
6 # are licensed and made available under the terms and conditions of the BSD License
7 # which accompanies this distribution. The full text of the license may be found at
8 # http://opensource.org/licenses/bsd-license.php
9 #
10 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 #
13
14 ##
15 # Import Modules
16 #
17 import os
18 import sys
19 import re
20
21 from optparse import OptionParser
22 from optparse import make_option
23 from Common.BuildToolError import *
24 from Common.Misc import *
25
26 import Common.EdkLogger as EdkLogger
27
28 # Version and Copyright
29 __version_number__ = "0.10"
30 __version__ = "%prog Version " + __version_number__
31 __copyright__ = "Copyright (c) 2007-2008, Intel Corporation. All rights reserved."
32
33 ## Regular expression for matching Line Control directive like "#line xxx"
34 gLineControlDirective = re.compile('^\s*#(?:line)?\s+([0-9]+)\s+"*([^"]*)"')
35 ## Regular expression for matching "typedef struct"
36 gTypedefPattern = re.compile("^\s*typedef\s+struct\s*[{]*$", re.MULTILINE)
37 ## Regular expression for matching "#pragma pack"
38 gPragmaPattern = re.compile("^\s*#pragma\s+pack", re.MULTILINE)
39 ## Regular expression for matching HEX number
40 gHexNumberPattern = re.compile("0[xX]([0-9a-fA-F]+)")
41 ## Regular expression for matching "Include ()" in asl file
42 gAslIncludePattern = re.compile("^(\s*)[iI]nclude\s*\(\"?([^\"\(\)]+)\"\)", re.MULTILINE)
43 ## Patterns used to convert EDK conventions to EDK2 ECP conventions
44 gImportCodePatterns = [
45 [
46 re.compile('^(\s*)\(\*\*PeiServices\)\.PciCfg\s*=\s*([^;\s]+);', re.MULTILINE),
47 '''\\1{
48 \\1 STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = {
49 \\1 (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
50 \\1 &gEcpPeiPciCfgPpiGuid,
51 \\1 \\2
52 \\1 };
53 \\1 (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList);
54 \\1}'''
55 ],
56
57 [
58 re.compile('^(\s*)\(\*PeiServices\)->PciCfg\s*=\s*([^;\s]+);', re.MULTILINE),
59 '''\\1{
60 \\1 STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = {
61 \\1 (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
62 \\1 &gEcpPeiPciCfgPpiGuid,
63 \\1 \\2
64 \\1 };
65 \\1 (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList);
66 \\1}'''
67 ],
68
69 [
70 re.compile("(\s*).+->Modify[\s\n]*\(", re.MULTILINE),
71 '\\1PeiLibPciCfgModify ('
72 ],
73
74 [
75 re.compile("(\W*)gRT->ReportStatusCode[\s\n]*\(", re.MULTILINE),
76 '\\1EfiLibReportStatusCode ('
77 ],
78
79 [
80 re.compile('#include\s+["<]LoadFile\.h[">]', re.MULTILINE),
81 '#include <FvLoadFile.h>'
82 ],
83
84 [
85 re.compile("(\s*)\S*CreateEvent\s*\([\s\n]*EFI_EVENT_SIGNAL_READY_TO_BOOT[^,]*,((?:[^;]+\n)+)(\s*\));", re.MULTILINE),
86 '\\1EfiCreateEventReadyToBoot (\\2\\3;'
87 ],
88
89 [
90 re.compile("(\s*)\S*CreateEvent\s*\([\s\n]*EFI_EVENT_SIGNAL_LEGACY_BOOT[^,]*,((?:[^;]+\n)+)(\s*\));", re.MULTILINE),
91 '\\1EfiCreateEventLegacyBoot (\\2\\3;'
92 ],
93 # [
94 # re.compile("(\W)(PEI_PCI_CFG_PPI)(\W)", re.MULTILINE),
95 # '\\1ECP_\\2\\3'
96 # ]
97 ]
98
99 ## file cache to avoid circular include in ASL file
100 gIncludedAslFile = []
101
102 ## Trim preprocessed source code
103 #
104 # Remove extra content made by preprocessor. The preprocessor must enable the
105 # line number generation option when preprocessing.
106 #
107 # @param Source File to be trimmed
108 # @param Target File to store the trimmed content
109 # @param Convert If True, convert standard HEX format to MASM format
110 #
111 def TrimPreprocessedFile(Source, Target, Convert):
112 CreateDirectory(os.path.dirname(Target))
113 try:
114 f = open (Source, 'r')
115 except:
116 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source)
117
118 # read whole file
119 Lines = f.readlines()
120 f.close()
121
122 PreprocessedFile = ""
123 InjectedFile = ""
124 LineIndexOfOriginalFile = None
125 NewLines = []
126 LineControlDirectiveFound = False
127 for Index in range(len(Lines)):
128 Line = Lines[Index]
129 #
130 # Find out the name of files injected by preprocessor from the lines
131 # with Line Control directive
132 #
133 MatchList = gLineControlDirective.findall(Line)
134 if MatchList != []:
135 MatchList = MatchList[0]
136 if len(MatchList) == 2:
137 LineNumber = int(MatchList[0], 0)
138 InjectedFile = MatchList[1]
139 # The first injetcted file must be the preprocessed file itself
140 if PreprocessedFile == "":
141 PreprocessedFile = InjectedFile
142 LineControlDirectiveFound = True
143 continue
144 elif PreprocessedFile == "" or InjectedFile != PreprocessedFile:
145 continue
146
147 if LineIndexOfOriginalFile == None:
148 #
149 # Any non-empty lines must be from original preprocessed file.
150 # And this must be the first one.
151 #
152 LineIndexOfOriginalFile = Index
153 EdkLogger.verbose("Found original file content starting from line %d"
154 % (LineIndexOfOriginalFile + 1))
155
156 # convert HEX number format if indicated
157 if Convert:
158 Line = gHexNumberPattern.sub(r"0\1h", Line)
159
160 if LineNumber != None:
161 EdkLogger.verbose("Got line directive: line=%d" % LineNumber)
162 # in case preprocessor removed some lines, like blank or comment lines
163 if LineNumber <= len(NewLines):
164 # possible?
165 NewLines[LineNumber - 1] = Line
166 else:
167 if LineNumber > (len(NewLines) + 1):
168 for LineIndex in range(len(NewLines), LineNumber-1):
169 NewLines.append(os.linesep)
170 NewLines.append(Line)
171 LineNumber = None
172 EdkLogger.verbose("Now we have lines: %d" % len(NewLines))
173 else:
174 NewLines.append(Line)
175
176 # in case there's no line directive or linemarker found
177 if (not LineControlDirectiveFound) and NewLines == []:
178 NewLines = Lines
179
180 # save to file
181 try:
182 f = open (Target, 'wb')
183 except:
184 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target)
185 f.writelines(NewLines)
186 f.close()
187
188 ## Trim preprocessed VFR file
189 #
190 # Remove extra content made by preprocessor. The preprocessor doesn't need to
191 # enable line number generation option when preprocessing.
192 #
193 # @param Source File to be trimmed
194 # @param Target File to store the trimmed content
195 #
196 def TrimPreprocessedVfr(Source, Target):
197 CreateDirectory(os.path.dirname(Target))
198
199 try:
200 f = open (Source,'r')
201 except:
202 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source)
203 # read whole file
204 Lines = f.readlines()
205 f.close()
206
207 FoundTypedef = False
208 Brace = 0
209 TypedefStart = 0
210 TypedefEnd = 0
211 for Index in range(len(Lines)):
212 Line = Lines[Index]
213 # don't trim the lines from "formset" definition to the end of file
214 if Line.strip() == 'formset':
215 break
216
217 if FoundTypedef == False and (Line.find('#line') == 0 or Line.find('# ') == 0):
218 # empty the line number directive if it's not aomong "typedef struct"
219 Lines[Index] = "\n"
220 continue
221
222 if FoundTypedef == False and gTypedefPattern.search(Line) == None:
223 # keep "#pragram pack" directive
224 if gPragmaPattern.search(Line) == None:
225 Lines[Index] = "\n"
226 continue
227 elif FoundTypedef == False:
228 # found "typedef struct", keept its position and set a flag
229 FoundTypedef = True
230 TypedefStart = Index
231
232 # match { and } to find the end of typedef definition
233 if Line.find("{") >= 0:
234 Brace += 1
235 elif Line.find("}") >= 0:
236 Brace -= 1
237
238 # "typedef struct" must end with a ";"
239 if Brace == 0 and Line.find(";") >= 0:
240 FoundTypedef = False
241 TypedefEnd = Index
242 # keep all "typedef struct" except to GUID, EFI_PLABEL and PAL_CALL_RETURN
243 if Line.strip("} ;\r\n") in ["GUID", "EFI_PLABEL", "PAL_CALL_RETURN"]:
244 for i in range(TypedefStart, TypedefEnd+1):
245 Lines[i] = "\n"
246
247 # save all lines trimmed
248 try:
249 f = open (Target,'w')
250 except:
251 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target)
252 f.writelines(Lines)
253 f.close()
254
255 ## Read the content ASL file, including ASL included, recursively
256 #
257 # @param Source File to be read
258 # @param Indent Spaces before the Include() statement
259 #
260 def DoInclude(Source, Indent=''):
261 NewFileContent = []
262 # avoid A "include" B and B "include" A
263 if Source in gIncludedAslFile:
264 EdkLogger.warn("Trim", "Circular include",
265 ExtraData= "%s -> %s" % (" -> ".join(gIncludedAslFile), Source))
266 return []
267 gIncludedAslFile.append(Source)
268
269 try:
270 F = open(Source,'r')
271 except:
272 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source)
273
274 for Line in F:
275 Result = gAslIncludePattern.findall(Line)
276 if len(Result) == 0:
277 NewFileContent.append("%s%s" % (Indent, Line))
278 continue
279 CurrentIndent = Indent + Result[0][0]
280 IncludedFile = Result[0][1]
281 NewFileContent.extend(DoInclude(IncludedFile, CurrentIndent))
282
283 gIncludedAslFile.pop()
284 F.close()
285
286 return NewFileContent
287
288
289 ## Trim ASL file
290 #
291 # Replace ASL include statement with the content the included file
292 #
293 # @param Source File to be trimmed
294 # @param Target File to store the trimmed content
295 #
296 def TrimAslFile(Source, Target):
297 CreateDirectory(os.path.dirname(Target))
298
299 Cwd = os.getcwd()
300 SourceDir = os.path.dirname(Source)
301 if SourceDir == '':
302 SourceDir = '.'
303 os.chdir(SourceDir)
304 Lines = DoInclude(Source)
305 os.chdir(Cwd)
306
307 # save all lines trimmed
308 try:
309 f = open (Target,'w')
310 except:
311 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target)
312
313 f.writelines(Lines)
314 f.close()
315
316 ## Trim EDK source code file(s)
317 #
318 #
319 # @param Source File or directory to be trimmed
320 # @param Target File or directory to store the trimmed content
321 #
322 def TrimR8Sources(Source, Target):
323 if os.path.isdir(Source):
324 for CurrentDir, Dirs, Files in os.walk(Source):
325 if '.svn' in Dirs:
326 Dirs.remove('.svn')
327 elif "CVS" in Dirs:
328 Dirs.remove("CVS")
329
330 for FileName in Files:
331 Dummy, Ext = os.path.splitext(FileName)
332 if Ext.upper() not in ['.C', '.H']: continue
333 if Target == None or Target == '':
334 TrimR8SourceCode(
335 os.path.join(CurrentDir, FileName),
336 os.path.join(CurrentDir, FileName)
337 )
338 else:
339 TrimR8SourceCode(
340 os.path.join(CurrentDir, FileName),
341 os.path.join(Target, CurrentDir[len(Source)+1:], FileName)
342 )
343 else:
344 TrimR8SourceCode(Source, Target)
345
346 ## Trim one EDK source code file
347 #
348 # Do following replacement:
349 #
350 # (**PeiServices\).PciCfg = <*>;
351 # => {
352 # STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = {
353 # (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
354 # &gEcpPeiPciCfgPpiGuid,
355 # <*>
356 # };
357 # (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList);
358 #
359 # <*>Modify(<*>)
360 # => PeiLibPciCfgModify (<*>)
361 #
362 # gRT->ReportStatusCode (<*>)
363 # => EfiLibReportStatusCode (<*>)
364 #
365 # #include <LoadFile\.h>
366 # => #include <FvLoadFile.h>
367 #
368 # CreateEvent (EFI_EVENT_SIGNAL_READY_TO_BOOT, <*>)
369 # => EfiCreateEventReadyToBoot (<*>)
370 #
371 # CreateEvent (EFI_EVENT_SIGNAL_LEGACY_BOOT, <*>)
372 # => EfiCreateEventLegacyBoot (<*>)
373 #
374 # @param Source File to be trimmed
375 # @param Target File to store the trimmed content
376 #
377 def TrimR8SourceCode(Source, Target):
378 EdkLogger.verbose("\t%s -> %s" % (Source, Target))
379 CreateDirectory(os.path.dirname(Target))
380
381 try:
382 f = open (Source,'rb')
383 except:
384 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source)
385 # read whole file
386 Lines = f.read()
387 f.close()
388
389 NewLines = None
390 for Re,Repl in gImportCodePatterns:
391 if NewLines == None:
392 NewLines = Re.sub(Repl, Lines)
393 else:
394 NewLines = Re.sub(Repl, NewLines)
395
396 # save all lines if trimmed
397 if Source == Target and NewLines == Lines:
398 return
399
400 try:
401 f = open (Target,'wb')
402 except:
403 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target)
404 f.write(NewLines)
405 f.close()
406
407
408 ## Parse command line options
409 #
410 # Using standard Python module optparse to parse command line option of this tool.
411 #
412 # @retval Options A optparse.Values object containing the parsed options
413 # @retval InputFile Path of file to be trimmed
414 #
415 def Options():
416 OptionList = [
417 make_option("-s", "--source-code", dest="FileType", const="SourceCode", action="store_const",
418 help="The input file is preprocessed source code, including C or assembly code"),
419 make_option("-r", "--vfr-file", dest="FileType", const="Vfr", action="store_const",
420 help="The input file is preprocessed VFR file"),
421 make_option("-a", "--asl-file", dest="FileType", const="Asl", action="store_const",
422 help="The input file is ASL file"),
423 make_option("-8", "--r8-source-code", dest="FileType", const="R8SourceCode", action="store_const",
424 help="The input file is source code for R8 to be trimmed for ECP"),
425
426 make_option("-c", "--convert-hex", dest="ConvertHex", action="store_true",
427 help="Convert standard hex format (0xabcd) to MASM format (abcdh)"),
428
429 make_option("-o", "--output", dest="OutputFile",
430 help="File to store the trimmed content"),
431 make_option("-v", "--verbose", dest="LogLevel", action="store_const", const=EdkLogger.VERBOSE,
432 help="Run verbosely"),
433 make_option("-d", "--debug", dest="LogLevel", type="int",
434 help="Run with debug information"),
435 make_option("-q", "--quiet", dest="LogLevel", action="store_const", const=EdkLogger.QUIET,
436 help="Run quietly"),
437 make_option("-?", action="help", help="show this help message and exit"),
438 ]
439
440 # use clearer usage to override default usage message
441 UsageString = "%prog [-s|-r|-a] [-c] [-v|-d <debug_level>|-q] [-o <output_file>] <input_file>"
442
443 Parser = OptionParser(description=__copyright__, version=__version__, option_list=OptionList, usage=UsageString)
444 Parser.set_defaults(FileType="Vfr")
445 Parser.set_defaults(ConvertHex=False)
446 Parser.set_defaults(LogLevel=EdkLogger.INFO)
447
448 Options, Args = Parser.parse_args()
449
450 # error check
451 if len(Args) == 0:
452 EdkLogger.error("Trim", OPTION_MISSING, ExtraData=Parser.get_usage())
453 if len(Args) > 1:
454 EdkLogger.error("Trim", OPTION_NOT_SUPPORTED, ExtraData=Parser.get_usage())
455
456 InputFile = Args[0]
457 return Options, InputFile
458
459 ## Entrance method
460 #
461 # This method mainly dispatch specific methods per the command line options.
462 # If no error found, return zero value so the caller of this tool can know
463 # if it's executed successfully or not.
464 #
465 # @retval 0 Tool was successful
466 # @retval 1 Tool failed
467 #
468 def Main():
469 try:
470 EdkLogger.Initialize()
471 CommandOptions, InputFile = Options()
472 if CommandOptions.LogLevel < EdkLogger.DEBUG_9:
473 EdkLogger.SetLevel(CommandOptions.LogLevel + 1)
474 else:
475 EdkLogger.SetLevel(CommandOptions.LogLevel)
476 except FatalError, X:
477 return 1
478
479 try:
480 if CommandOptions.FileType == "Vfr":
481 if CommandOptions.OutputFile == None:
482 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii'
483 TrimPreprocessedVfr(InputFile, CommandOptions.OutputFile)
484 elif CommandOptions.FileType == "Asl":
485 if CommandOptions.OutputFile == None:
486 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii'
487 TrimAslFile(InputFile, CommandOptions.OutputFile)
488 elif CommandOptions.FileType == "R8SourceCode":
489 TrimR8Sources(InputFile, CommandOptions.OutputFile)
490 else :
491 if CommandOptions.OutputFile == None:
492 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii'
493 TrimPreprocessedFile(InputFile, CommandOptions.OutputFile, CommandOptions.ConvertHex)
494 except FatalError, X:
495 import platform
496 import traceback
497 if CommandOptions != None and CommandOptions.LogLevel <= EdkLogger.DEBUG_9:
498 EdkLogger.quiet("(Python %s on %s) " % (platform.python_version(), sys.platform) + traceback.format_exc())
499 return 1
500 except:
501 import traceback
502 import platform
503 EdkLogger.error(
504 "\nTrim",
505 CODE_ERROR,
506 "Unknown fatal error when trimming [%s]" % InputFile,
507 ExtraData="\n(Please send email to dev@buildtools.tianocore.org for help, attaching following call stack trace!)\n",
508 RaiseError=False
509 )
510 EdkLogger.quiet("(Python %s on %s) " % (platform.python_version(), sys.platform) + traceback.format_exc())
511 return 1
512
513 return 0
514
515 if __name__ == '__main__':
516 r = Main()
517 ## 0-127 is a safe return range, and 1 is a standard default error
518 if r < 0 or r > 127: r = 1
519 sys.exit(r)
520