Check In tool source code based on Build tool project revision r1655.
[mirror_edk2.git] / BaseTools / Source / Python / Trim / Trim.py
CommitLineData
30fdf114
LG
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#
17import os
18import sys
19import re
20
21from optparse import OptionParser
22from optparse import make_option
23from Common.BuildToolError import *
24from Common.Misc import *
25
26import 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"
34gLineControlDirective = re.compile('^\s*#(?:line)?\s+([0-9]+)\s+"*([^"]*)"')
35## Regular expression for matching "typedef struct"
36gTypedefPattern = re.compile("^\s*typedef\s+struct\s*[{]*$", re.MULTILINE)
37## Regular expression for matching "#pragma pack"
38gPragmaPattern = re.compile("^\s*#pragma\s+pack", re.MULTILINE)
39## Regular expression for matching HEX number
40gHexNumberPattern = re.compile("0[xX]([0-9a-fA-F]+)")
41## Regular expression for matching "Include ()" in asl file
42gAslIncludePattern = re.compile("^(\s*)[iI]nclude\s*\(\"?([^\"\(\)]+)\"\)", re.MULTILINE)
43## Patterns used to convert EDK conventions to EDK2 ECP conventions
44gImportCodePatterns = [
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
100gIncludedAslFile = []
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#
111def 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#
196def 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#
260def 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#
296def 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#
322def 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#
377def 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#
415def 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#
468def 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
515if __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