]>
Commit | Line | Data |
---|---|---|
1 | ## @file\r | |
2 | # This file is used to generate DEPEX file for module's dependency expression\r | |
3 | #\r | |
4 | # Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved.<BR>\r | |
5 | # This program and the accompanying materials\r | |
6 | # are licensed and made available under the terms and conditions of the BSD License\r | |
7 | # which accompanies this distribution. The full text of the license may be found at\r | |
8 | # http://opensource.org/licenses/bsd-license.php\r | |
9 | #\r | |
10 | # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r | |
11 | # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r | |
12 | \r | |
13 | ## Import Modules\r | |
14 | #\r | |
15 | import sys\r | |
16 | import Common.LongFilePathOs as os\r | |
17 | import re\r | |
18 | import traceback\r | |
19 | from Common.LongFilePathSupport import OpenLongFilePath as open\r | |
20 | from StringIO import StringIO\r | |
21 | from struct import pack\r | |
22 | from Common.BuildToolError import *\r | |
23 | from Common.Misc import SaveFileOnChange\r | |
24 | from Common.Misc import GuidStructureStringToGuidString\r | |
25 | from Common import EdkLogger as EdkLogger\r | |
26 | from Common.BuildVersion import gBUILD_VERSION\r | |
27 | \r | |
28 | ## Regular expression for matching "DEPENDENCY_START ... DEPENDENCY_END"\r | |
29 | gStartClosePattern = re.compile(".*DEPENDENCY_START(.+)DEPENDENCY_END.*", re.S)\r | |
30 | \r | |
31 | ## Mapping between module type and EFI phase\r | |
32 | gType2Phase = {\r | |
33 | "BASE" : None,\r | |
34 | "SEC" : "PEI",\r | |
35 | "PEI_CORE" : "PEI",\r | |
36 | "PEIM" : "PEI",\r | |
37 | "DXE_CORE" : "DXE",\r | |
38 | "DXE_DRIVER" : "DXE",\r | |
39 | "DXE_SMM_DRIVER" : "DXE",\r | |
40 | "DXE_RUNTIME_DRIVER": "DXE",\r | |
41 | "DXE_SAL_DRIVER" : "DXE",\r | |
42 | "UEFI_DRIVER" : "DXE",\r | |
43 | "UEFI_APPLICATION" : "DXE",\r | |
44 | "SMM_CORE" : "DXE",\r | |
45 | }\r | |
46 | \r | |
47 | ## Convert dependency expression string into EFI internal representation\r | |
48 | #\r | |
49 | # DependencyExpression class is used to parse dependency expression string and\r | |
50 | # convert it into its binary form.\r | |
51 | #\r | |
52 | class DependencyExpression:\r | |
53 | \r | |
54 | ArchProtocols = set([\r | |
55 | '665e3ff6-46cc-11d4-9a38-0090273fc14d', # 'gEfiBdsArchProtocolGuid'\r | |
56 | '26baccb1-6f42-11d4-bce7-0080c73c8881', # 'gEfiCpuArchProtocolGuid'\r | |
57 | '26baccb2-6f42-11d4-bce7-0080c73c8881', # 'gEfiMetronomeArchProtocolGuid'\r | |
58 | '1da97072-bddc-4b30-99f1-72a0b56fff2a', # 'gEfiMonotonicCounterArchProtocolGuid'\r | |
59 | '27cfac87-46cc-11d4-9a38-0090273fc14d', # 'gEfiRealTimeClockArchProtocolGuid'\r | |
60 | '27cfac88-46cc-11d4-9a38-0090273fc14d', # 'gEfiResetArchProtocolGuid'\r | |
61 | 'b7dfb4e1-052f-449f-87be-9818fc91b733', # 'gEfiRuntimeArchProtocolGuid'\r | |
62 | 'a46423e3-4617-49f1-b9ff-d1bfa9115839', # 'gEfiSecurityArchProtocolGuid'\r | |
63 | '26baccb3-6f42-11d4-bce7-0080c73c8881', # 'gEfiTimerArchProtocolGuid'\r | |
64 | '6441f818-6362-4e44-b570-7dba31dd2453', # 'gEfiVariableWriteArchProtocolGuid'\r | |
65 | '1e5668e2-8481-11d4-bcf1-0080c73c8881', # 'gEfiVariableArchProtocolGuid'\r | |
66 | '665e3ff5-46cc-11d4-9a38-0090273fc14d' # 'gEfiWatchdogTimerArchProtocolGuid'\r | |
67 | ]\r | |
68 | )\r | |
69 | \r | |
70 | OpcodePriority = {\r | |
71 | "AND" : 1,\r | |
72 | "OR" : 1,\r | |
73 | "NOT" : 2,\r | |
74 | # "SOR" : 9,\r | |
75 | # "BEFORE": 9,\r | |
76 | # "AFTER" : 9,\r | |
77 | }\r | |
78 | \r | |
79 | Opcode = {\r | |
80 | "PEI" : {\r | |
81 | "PUSH" : 0x02,\r | |
82 | "AND" : 0x03,\r | |
83 | "OR" : 0x04,\r | |
84 | "NOT" : 0x05,\r | |
85 | "TRUE" : 0x06,\r | |
86 | "FALSE" : 0x07,\r | |
87 | "END" : 0x08\r | |
88 | },\r | |
89 | \r | |
90 | "DXE" : {\r | |
91 | "BEFORE": 0x00,\r | |
92 | "AFTER" : 0x01,\r | |
93 | "PUSH" : 0x02,\r | |
94 | "AND" : 0x03,\r | |
95 | "OR" : 0x04,\r | |
96 | "NOT" : 0x05,\r | |
97 | "TRUE" : 0x06,\r | |
98 | "FALSE" : 0x07,\r | |
99 | "END" : 0x08,\r | |
100 | "SOR" : 0x09\r | |
101 | }\r | |
102 | }\r | |
103 | \r | |
104 | # all supported op codes and operands\r | |
105 | SupportedOpcode = ["BEFORE", "AFTER", "PUSH", "AND", "OR", "NOT", "END", "SOR"]\r | |
106 | SupportedOperand = ["TRUE", "FALSE"]\r | |
107 | \r | |
108 | OpcodeWithSingleOperand = ['NOT', 'BEFORE', 'AFTER']\r | |
109 | OpcodeWithTwoOperand = ['AND', 'OR']\r | |
110 | \r | |
111 | # op code that should not be the last one\r | |
112 | NonEndingOpcode = ["AND", "OR", "NOT", 'SOR']\r | |
113 | # op code must not present at the same time\r | |
114 | ExclusiveOpcode = ["BEFORE", "AFTER"]\r | |
115 | # op code that should be the first one if it presents\r | |
116 | AboveAllOpcode = ["SOR", "BEFORE", "AFTER"]\r | |
117 | \r | |
118 | #\r | |
119 | # open and close brace must be taken as individual tokens\r | |
120 | #\r | |
121 | TokenPattern = re.compile("(\(|\)|\{[^{}]+\{?[^{}]+\}?[ ]*\}|\w+)")\r | |
122 | \r | |
123 | ## Constructor\r | |
124 | #\r | |
125 | # @param Expression The list or string of dependency expression\r | |
126 | # @param ModuleType The type of the module using the dependency expression\r | |
127 | #\r | |
128 | def __init__(self, Expression, ModuleType, Optimize=False):\r | |
129 | self.ModuleType = ModuleType\r | |
130 | self.Phase = gType2Phase[ModuleType]\r | |
131 | if type(Expression) == type([]):\r | |
132 | self.ExpressionString = " ".join(Expression)\r | |
133 | self.TokenList = Expression\r | |
134 | else:\r | |
135 | self.ExpressionString = Expression\r | |
136 | self.GetExpressionTokenList()\r | |
137 | \r | |
138 | self.PostfixNotation = []\r | |
139 | self.OpcodeList = []\r | |
140 | \r | |
141 | self.GetPostfixNotation()\r | |
142 | self.ValidateOpcode()\r | |
143 | \r | |
144 | EdkLogger.debug(EdkLogger.DEBUG_8, repr(self))\r | |
145 | if Optimize:\r | |
146 | self.Optimize()\r | |
147 | EdkLogger.debug(EdkLogger.DEBUG_8, "\n Optimized: " + repr(self))\r | |
148 | \r | |
149 | def __str__(self):\r | |
150 | return " ".join(self.TokenList)\r | |
151 | \r | |
152 | def __repr__(self):\r | |
153 | WellForm = ''\r | |
154 | for Token in self.PostfixNotation:\r | |
155 | if Token in self.SupportedOpcode:\r | |
156 | WellForm += "\n " + Token\r | |
157 | else:\r | |
158 | WellForm += ' ' + Token\r | |
159 | return WellForm\r | |
160 | \r | |
161 | ## Split the expression string into token list\r | |
162 | def GetExpressionTokenList(self):\r | |
163 | self.TokenList = self.TokenPattern.findall(self.ExpressionString)\r | |
164 | \r | |
165 | ## Convert token list into postfix notation\r | |
166 | def GetPostfixNotation(self):\r | |
167 | Stack = []\r | |
168 | LastToken = ''\r | |
169 | for Token in self.TokenList:\r | |
170 | if Token == "(":\r | |
171 | if LastToken not in self.SupportedOpcode + ['(', '', None]:\r | |
172 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before open parentheses",\r | |
173 | ExtraData="Near %s" % LastToken)\r | |
174 | Stack.append(Token)\r | |
175 | elif Token == ")":\r | |
176 | if '(' not in Stack:\r | |
177 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses",\r | |
178 | ExtraData=str(self))\r | |
179 | elif LastToken in self.SupportedOpcode + ['', None]:\r | |
180 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before close parentheses",\r | |
181 | ExtraData="Near %s" % LastToken)\r | |
182 | while len(Stack) > 0:\r | |
183 | if Stack[-1] == '(':\r | |
184 | Stack.pop()\r | |
185 | break\r | |
186 | self.PostfixNotation.append(Stack.pop())\r | |
187 | elif Token in self.OpcodePriority:\r | |
188 | if Token == "NOT":\r | |
189 | if LastToken not in self.SupportedOpcode + ['(', '', None]:\r | |
190 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before NOT",\r | |
191 | ExtraData="Near %s" % LastToken)\r | |
192 | elif LastToken in self.SupportedOpcode + ['(', '', None]:\r | |
193 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before " + Token,\r | |
194 | ExtraData="Near %s" % LastToken)\r | |
195 | \r | |
196 | while len(Stack) > 0:\r | |
197 | if Stack[-1] == "(" or self.OpcodePriority[Token] >= self.OpcodePriority[Stack[-1]]:\r | |
198 | break\r | |
199 | self.PostfixNotation.append(Stack.pop())\r | |
200 | Stack.append(Token)\r | |
201 | self.OpcodeList.append(Token)\r | |
202 | else:\r | |
203 | if Token not in self.SupportedOpcode:\r | |
204 | # not OP, take it as GUID\r | |
205 | if LastToken not in self.SupportedOpcode + ['(', '', None]:\r | |
206 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before %s" % Token,\r | |
207 | ExtraData="Near %s" % LastToken)\r | |
208 | if len(self.OpcodeList) == 0 or self.OpcodeList[-1] not in self.ExclusiveOpcode:\r | |
209 | if Token not in self.SupportedOperand:\r | |
210 | self.PostfixNotation.append("PUSH")\r | |
211 | # check if OP is valid in this phase\r | |
212 | elif Token in self.Opcode[self.Phase]:\r | |
213 | if Token == "END":\r | |
214 | break\r | |
215 | self.OpcodeList.append(Token)\r | |
216 | else:\r | |
217 | EdkLogger.error("GenDepex", PARSER_ERROR,\r | |
218 | "Opcode=%s doesn't supported in %s stage " % (Token, self.Phase),\r | |
219 | ExtraData=str(self))\r | |
220 | self.PostfixNotation.append(Token)\r | |
221 | LastToken = Token\r | |
222 | \r | |
223 | # there should not be parentheses in Stack\r | |
224 | if '(' in Stack or ')' in Stack:\r | |
225 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses",\r | |
226 | ExtraData=str(self))\r | |
227 | while len(Stack) > 0:\r | |
228 | self.PostfixNotation.append(Stack.pop())\r | |
229 | if self.PostfixNotation[-1] != 'END':\r | |
230 | self.PostfixNotation.append("END")\r | |
231 | \r | |
232 | ## Validate the dependency expression\r | |
233 | def ValidateOpcode(self):\r | |
234 | for Op in self.AboveAllOpcode:\r | |
235 | if Op in self.PostfixNotation:\r | |
236 | if Op != self.PostfixNotation[0]:\r | |
237 | EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the first opcode in the expression" % Op,\r | |
238 | ExtraData=str(self))\r | |
239 | if len(self.PostfixNotation) < 3:\r | |
240 | EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op,\r | |
241 | ExtraData=str(self))\r | |
242 | for Op in self.ExclusiveOpcode:\r | |
243 | if Op in self.OpcodeList:\r | |
244 | if len(self.OpcodeList) > 1:\r | |
245 | EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the only opcode in the expression" % Op,\r | |
246 | ExtraData=str(self))\r | |
247 | if len(self.PostfixNotation) < 3:\r | |
248 | EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op,\r | |
249 | ExtraData=str(self))\r | |
250 | if self.TokenList[-1] != 'END' and self.TokenList[-1] in self.NonEndingOpcode:\r | |
251 | EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-1],\r | |
252 | ExtraData=str(self))\r | |
253 | if self.TokenList[-1] == 'END' and self.TokenList[-2] in self.NonEndingOpcode:\r | |
254 | EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-2],\r | |
255 | ExtraData=str(self))\r | |
256 | if "END" in self.TokenList and "END" != self.TokenList[-1]:\r | |
257 | EdkLogger.error("GenDepex", PARSER_ERROR, "Extra expressions after END",\r | |
258 | ExtraData=str(self))\r | |
259 | \r | |
260 | ## Simply optimize the dependency expression by removing duplicated operands\r | |
261 | def Optimize(self):\r | |
262 | ValidOpcode = list(set(self.OpcodeList))\r | |
263 | if len(ValidOpcode) != 1 or ValidOpcode[0] not in ['AND', 'OR']:\r | |
264 | return\r | |
265 | Op = ValidOpcode[0]\r | |
266 | NewOperand = []\r | |
267 | AllOperand = set()\r | |
268 | for Token in self.PostfixNotation:\r | |
269 | if Token in self.SupportedOpcode or Token in NewOperand:\r | |
270 | continue\r | |
271 | AllOperand.add(Token)\r | |
272 | if Token == 'TRUE':\r | |
273 | if Op == 'AND':\r | |
274 | continue\r | |
275 | else:\r | |
276 | NewOperand.append(Token)\r | |
277 | break\r | |
278 | elif Token == 'FALSE':\r | |
279 | if Op == 'OR':\r | |
280 | continue\r | |
281 | else:\r | |
282 | NewOperand.append(Token)\r | |
283 | break\r | |
284 | NewOperand.append(Token)\r | |
285 | \r | |
286 | # don't generate depex if only TRUE operand left\r | |
287 | if self.ModuleType == 'PEIM' and len(NewOperand) == 1 and NewOperand[0] == 'TRUE':\r | |
288 | self.PostfixNotation = []\r | |
289 | return \r | |
290 | \r | |
291 | # don't generate depex if all operands are architecture protocols\r | |
292 | if self.ModuleType in ['UEFI_DRIVER', 'DXE_DRIVER', 'DXE_RUNTIME_DRIVER', 'DXE_SAL_DRIVER', 'DXE_SMM_DRIVER'] and \\r | |
293 | Op == 'AND' and \\r | |
294 | self.ArchProtocols == set([GuidStructureStringToGuidString(Guid) for Guid in AllOperand]):\r | |
295 | self.PostfixNotation = []\r | |
296 | return\r | |
297 | \r | |
298 | if len(NewOperand) == 0:\r | |
299 | self.TokenList = list(AllOperand)\r | |
300 | else:\r | |
301 | self.TokenList = []\r | |
302 | while True:\r | |
303 | self.TokenList.append(NewOperand.pop(0))\r | |
304 | if NewOperand == []:\r | |
305 | break\r | |
306 | self.TokenList.append(Op)\r | |
307 | self.PostfixNotation = []\r | |
308 | self.GetPostfixNotation()\r | |
309 | \r | |
310 | \r | |
311 | ## Convert a GUID value in C structure format into its binary form\r | |
312 | #\r | |
313 | # @param Guid The GUID value in C structure format\r | |
314 | #\r | |
315 | # @retval array The byte array representing the GUID value\r | |
316 | #\r | |
317 | def GetGuidValue(self, Guid):\r | |
318 | GuidValueString = Guid.replace("{", "").replace("}", "").replace(" ", "")\r | |
319 | GuidValueList = GuidValueString.split(",")\r | |
320 | if len(GuidValueList) != 11:\r | |
321 | EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid GUID value string or opcode: %s" % Guid)\r | |
322 | return pack("1I2H8B", *(int(value, 16) for value in GuidValueList))\r | |
323 | \r | |
324 | ## Save the binary form of dependency expression in file\r | |
325 | #\r | |
326 | # @param File The path of file. If None is given, put the data on console\r | |
327 | #\r | |
328 | # @retval True If the file doesn't exist or file is changed\r | |
329 | # @retval False If file exists and is not changed.\r | |
330 | #\r | |
331 | def Generate(self, File=None):\r | |
332 | Buffer = StringIO()\r | |
333 | if len(self.PostfixNotation) == 0:\r | |
334 | return False\r | |
335 | \r | |
336 | for Item in self.PostfixNotation:\r | |
337 | if Item in self.Opcode[self.Phase]:\r | |
338 | Buffer.write(pack("B", self.Opcode[self.Phase][Item]))\r | |
339 | elif Item in self.SupportedOpcode:\r | |
340 | EdkLogger.error("GenDepex", FORMAT_INVALID,\r | |
341 | "Opcode [%s] is not expected in %s phase" % (Item, self.Phase),\r | |
342 | ExtraData=self.ExpressionString)\r | |
343 | else:\r | |
344 | Buffer.write(self.GetGuidValue(Item))\r | |
345 | \r | |
346 | FilePath = ""\r | |
347 | FileChangeFlag = True\r | |
348 | if File == None:\r | |
349 | sys.stdout.write(Buffer.getvalue())\r | |
350 | FilePath = "STDOUT"\r | |
351 | else:\r | |
352 | FileChangeFlag = SaveFileOnChange(File, Buffer.getvalue(), True)\r | |
353 | \r | |
354 | Buffer.close()\r | |
355 | return FileChangeFlag\r | |
356 | \r | |
357 | versionNumber = ("0.04" + " " + gBUILD_VERSION)\r | |
358 | __version__ = "%prog Version " + versionNumber\r | |
359 | __copyright__ = "Copyright (c) 2007-2010, Intel Corporation All rights reserved."\r | |
360 | __usage__ = "%prog [options] [dependency_expression_file]"\r | |
361 | \r | |
362 | ## Parse command line options\r | |
363 | #\r | |
364 | # @retval OptionParser\r | |
365 | #\r | |
366 | def GetOptions():\r | |
367 | from optparse import OptionParser\r | |
368 | \r | |
369 | Parser = OptionParser(description=__copyright__, version=__version__, usage=__usage__)\r | |
370 | \r | |
371 | Parser.add_option("-o", "--output", dest="OutputFile", default=None, metavar="FILE",\r | |
372 | help="Specify the name of depex file to be generated")\r | |
373 | Parser.add_option("-t", "--module-type", dest="ModuleType", default=None,\r | |
374 | help="The type of module for which the dependency expression serves")\r | |
375 | Parser.add_option("-e", "--dependency-expression", dest="Expression", default="",\r | |
376 | help="The string of dependency expression. If this option presents, the input file will be ignored.")\r | |
377 | Parser.add_option("-m", "--optimize", dest="Optimize", default=False, action="store_true",\r | |
378 | help="Do some simple optimization on the expression.")\r | |
379 | Parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true",\r | |
380 | help="build with verbose information")\r | |
381 | Parser.add_option("-d", "--debug", action="store", type="int", help="Enable debug messages at specified level.")\r | |
382 | Parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true",\r | |
383 | help="build with little information")\r | |
384 | \r | |
385 | return Parser.parse_args()\r | |
386 | \r | |
387 | \r | |
388 | ## Entrance method\r | |
389 | #\r | |
390 | # @retval 0 Tool was successful\r | |
391 | # @retval 1 Tool failed\r | |
392 | #\r | |
393 | def Main():\r | |
394 | EdkLogger.Initialize()\r | |
395 | Option, Input = GetOptions()\r | |
396 | \r | |
397 | # Set log level\r | |
398 | if Option.quiet:\r | |
399 | EdkLogger.SetLevel(EdkLogger.QUIET)\r | |
400 | elif Option.verbose:\r | |
401 | EdkLogger.SetLevel(EdkLogger.VERBOSE)\r | |
402 | elif Option.debug != None:\r | |
403 | EdkLogger.SetLevel(Option.debug + 1)\r | |
404 | else:\r | |
405 | EdkLogger.SetLevel(EdkLogger.INFO)\r | |
406 | \r | |
407 | try:\r | |
408 | if Option.ModuleType == None or Option.ModuleType not in gType2Phase:\r | |
409 | EdkLogger.error("GenDepex", OPTION_MISSING, "Module type is not specified or supported")\r | |
410 | \r | |
411 | DxsFile = ''\r | |
412 | if len(Input) > 0 and Option.Expression == "":\r | |
413 | DxsFile = Input[0]\r | |
414 | DxsString = open(DxsFile, 'r').read().replace("\n", " ").replace("\r", " ")\r | |
415 | DxsString = gStartClosePattern.sub("\\1", DxsString)\r | |
416 | elif Option.Expression != "":\r | |
417 | if Option.Expression[0] == '"':\r | |
418 | DxsString = Option.Expression[1:-1]\r | |
419 | else:\r | |
420 | DxsString = Option.Expression\r | |
421 | else:\r | |
422 | EdkLogger.error("GenDepex", OPTION_MISSING, "No expression string or file given")\r | |
423 | \r | |
424 | Dpx = DependencyExpression(DxsString, Option.ModuleType, Option.Optimize)\r | |
425 | if Option.OutputFile != None:\r | |
426 | FileChangeFlag = Dpx.Generate(Option.OutputFile)\r | |
427 | if not FileChangeFlag and DxsFile: \r | |
428 | #\r | |
429 | # Touch the output file if its time stamp is older than the original\r | |
430 | # DXS file to avoid re-invoke this tool for the dependency check in build rule.\r | |
431 | #\r | |
432 | if os.stat(DxsFile)[8] > os.stat(Option.OutputFile)[8]:\r | |
433 | os.utime(Option.OutputFile, None)\r | |
434 | else:\r | |
435 | Dpx.Generate()\r | |
436 | except BaseException, X:\r | |
437 | EdkLogger.quiet("")\r | |
438 | if Option != None and Option.debug != None:\r | |
439 | EdkLogger.quiet(traceback.format_exc())\r | |
440 | else:\r | |
441 | EdkLogger.quiet(str(X))\r | |
442 | return 1\r | |
443 | \r | |
444 | return 0\r | |
445 | \r | |
446 | if __name__ == '__main__':\r | |
447 | sys.exit(Main())\r | |
448 | \r |