]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/AutoGen/BuildEngine.py
Sync BaseTools Trunk (version r2518) to EDKII main trunk.
[mirror_edk2.git] / BaseTools / Source / Python / AutoGen / BuildEngine.py
1 ## @file
2 # The engine for building files
3 #
4 # Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
5 # 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 re
19 import copy
20 import string
21
22 from Common.GlobalData import *
23 from Common.BuildToolError import *
24 from Common.Misc import tdict, PathClass
25 from Common.String import NormPath
26 from Common.DataType import *
27
28 import Common.EdkLogger as EdkLogger
29
30 ## Convert file type to file list macro name
31 #
32 # @param FileType The name of file type
33 #
34 # @retval string The name of macro
35 #
36 def FileListMacro(FileType):
37 return "%sS" % FileType.replace("-", "_").upper()
38
39 ## Convert file type to list file macro name
40 #
41 # @param FileType The name of file type
42 #
43 # @retval string The name of macro
44 #
45 def ListFileMacro(FileType):
46 return "%s_LIST" % FileListMacro(FileType)
47
48 class TargetDescBlock(object):
49 _Cache_ = {} # {TargetFile : TargetDescBlock object}
50
51 # Factory method
52 def __new__(Class, Inputs, Outputs, Commands, Dependencies):
53 if Outputs[0] in Class._Cache_:
54 Tdb = Class._Cache_[Outputs[0]]
55 for File in Inputs:
56 Tdb.AddInput(File)
57 else:
58 Tdb = super(TargetDescBlock, Class).__new__(Class)
59 Tdb._Init(Inputs, Outputs, Commands, Dependencies)
60 #Class._Cache_[Outputs[0]] = Tdb
61 return Tdb
62
63 def _Init(self, Inputs, Outputs, Commands, Dependencies):
64 self.Inputs = Inputs
65 self.Outputs = Outputs
66 self.Commands = Commands
67 self.Dependencies = Dependencies
68 if self.Outputs:
69 self.Target = self.Outputs[0]
70 else:
71 self.Target = None
72
73 def __str__(self):
74 return self.Target.Path
75
76 def __hash__(self):
77 return hash(self.Target.Path)
78
79 def __eq__(self, Other):
80 if type(Other) == type(self):
81 return Other.Target.Path == self.Target.Path
82 else:
83 return str(Other) == self.Target.Path
84
85 def AddInput(self, Input):
86 if Input not in self.Inputs:
87 self.Inputs.append(Input)
88
89 def IsMultipleInput(self):
90 return len(self.Inputs) > 1
91
92 @staticmethod
93 def Renew():
94 TargetDescBlock._Cache_ = {}
95
96 ## Class for one build rule
97 #
98 # This represents a build rule which can give out corresponding command list for
99 # building the given source file(s). The result can be used for generating the
100 # target for makefile.
101 #
102 class FileBuildRule:
103 INC_LIST_MACRO = "INC_LIST"
104 INC_MACRO = "INC"
105
106 ## constructor
107 #
108 # @param Input The dictionary represeting input file(s) for a rule
109 # @param Output The list represeting output file(s) for a rule
110 # @param Command The list containing commands to generate the output from input
111 #
112 def __init__(self, Type, Input, Output, Command, ExtraDependency=None):
113 # The Input should not be empty
114 if not Input:
115 Input = []
116 if not Output:
117 Output = []
118 if not Command:
119 Command = []
120
121 self.FileListMacro = FileListMacro(Type)
122 self.ListFileMacro = ListFileMacro(Type)
123 self.IncListFileMacro = self.INC_LIST_MACRO
124
125 self.SourceFileType = Type
126 # source files listed not in "*" or "?" pattern format
127 if not ExtraDependency:
128 self.ExtraSourceFileList = []
129 else:
130 self.ExtraSourceFileList = ExtraDependency
131
132 #
133 # Search macros used in command lines for <FILE_TYPE>_LIST and INC_LIST.
134 # If found, generate a file to keep the input files used to get over the
135 # limitation of command line length
136 #
137 self.MacroList = []
138 self.CommandList = []
139 for CmdLine in Command:
140 self.MacroList.extend(gMacroRefPattern.findall(CmdLine))
141 # replace path separator with native one
142 self.CommandList.append(CmdLine)
143
144 # Indicate what should be generated
145 if self.FileListMacro in self.MacroList:
146 self.GenFileListMacro = True
147 else:
148 self.GenFileListMacro = False
149
150 if self.ListFileMacro in self.MacroList:
151 self.GenListFile = True
152 self.GenFileListMacro = True
153 else:
154 self.GenListFile = False
155
156 if self.INC_LIST_MACRO in self.MacroList:
157 self.GenIncListFile = True
158 else:
159 self.GenIncListFile = False
160
161 # Check input files
162 self.IsMultipleInput = False
163 self.SourceFileExtList = []
164 for File in Input:
165 Base, Ext = os.path.splitext(File)
166 if Base.find("*") >= 0:
167 # There's "*" in the file name
168 self.IsMultipleInput = True
169 self.GenFileListMacro = True
170 elif Base.find("?") < 0:
171 # There's no "*" and "?" in file name
172 self.ExtraSourceFileList.append(File)
173 continue
174 if Ext not in self.SourceFileExtList:
175 self.SourceFileExtList.append(Ext)
176
177 # Check output files
178 self.DestFileList = []
179 for File in Output:
180 self.DestFileList.append(File)
181
182 # All build targets generated by this rule for a module
183 self.BuildTargets = {}
184
185 ## str() function support
186 #
187 # @retval string
188 #
189 def __str__(self):
190 SourceString = ""
191 SourceString += " %s %s %s" % (self.SourceFileType, " ".join(self.SourceFileExtList), self.ExtraSourceFileList)
192 DestString = ", ".join(self.DestFileList)
193 CommandString = "\n\t".join(self.CommandList)
194 return "%s : %s\n\t%s" % (DestString, SourceString, CommandString)
195
196 ## Check if given file extension is supported by this rule
197 #
198 # @param FileExt The extension of a file
199 #
200 # @retval True If the extension is supported
201 # @retval False If the extension is not supported
202 #
203 def IsSupported(self, FileExt):
204 return FileExt in self.SourceFileExtList
205
206 def Instantiate(self, Macros={}):
207 NewRuleObject = copy.copy(self)
208 NewRuleObject.BuildTargets = {}
209 NewRuleObject.DestFileList = []
210 for File in self.DestFileList:
211 NewRuleObject.DestFileList.append(PathClass(NormPath(File, Macros)))
212 return NewRuleObject
213
214 ## Apply the rule to given source file(s)
215 #
216 # @param SourceFile One file or a list of files to be built
217 # @param RelativeToDir The relative path of the source file
218 # @param PathSeparator Path separator
219 #
220 # @retval tuple (Source file in full path, List of individual sourcefiles, Destionation file, List of build commands)
221 #
222 def Apply(self, SourceFile):
223 if not self.CommandList or not self.DestFileList:
224 return None
225
226 # source file
227 if self.IsMultipleInput:
228 SrcFileName = ""
229 SrcFileBase = ""
230 SrcFileExt = ""
231 SrcFileDir = ""
232 SrcPath = ""
233 # SourceFile must be a list
234 SrcFile = "$(%s)" % self.FileListMacro
235 else:
236 SrcFileName, SrcFileBase, SrcFileExt = SourceFile.Name, SourceFile.BaseName, SourceFile.Ext
237 if SourceFile.Root:
238 SrcFileDir = SourceFile.SubDir
239 if SrcFileDir == "":
240 SrcFileDir = "."
241 else:
242 SrcFileDir = "."
243 SrcFile = SourceFile.Path
244 SrcPath = SourceFile.Dir
245
246 # destination file (the first one)
247 if self.DestFileList:
248 DestFile = self.DestFileList[0].Path
249 DestPath = self.DestFileList[0].Dir
250 DestFileName = self.DestFileList[0].Name
251 DestFileBase, DestFileExt = self.DestFileList[0].BaseName, self.DestFileList[0].Ext
252 else:
253 DestFile = ""
254 DestPath = ""
255 DestFileName = ""
256 DestFileBase = ""
257 DestFileExt = ""
258
259 BuildRulePlaceholderDict = {
260 # source file
261 "src" : SrcFile,
262 "s_path" : SrcPath,
263 "s_dir" : SrcFileDir,
264 "s_name" : SrcFileName,
265 "s_base" : SrcFileBase,
266 "s_ext" : SrcFileExt,
267 # destination file
268 "dst" : DestFile,
269 "d_path" : DestPath,
270 "d_name" : DestFileName,
271 "d_base" : DestFileBase,
272 "d_ext" : DestFileExt,
273 }
274
275 DstFile = []
276 for File in self.DestFileList:
277 File = string.Template(str(File)).safe_substitute(BuildRulePlaceholderDict)
278 File = string.Template(str(File)).safe_substitute(BuildRulePlaceholderDict)
279 DstFile.append(PathClass(File, IsBinary=True))
280
281 if DstFile[0] in self.BuildTargets:
282 TargetDesc = self.BuildTargets[DstFile[0]]
283 TargetDesc.AddInput(SourceFile)
284 else:
285 CommandList = []
286 for CommandString in self.CommandList:
287 CommandString = string.Template(CommandString).safe_substitute(BuildRulePlaceholderDict)
288 CommandString = string.Template(CommandString).safe_substitute(BuildRulePlaceholderDict)
289 CommandList.append(CommandString)
290 TargetDesc = TargetDescBlock([SourceFile], DstFile, CommandList, self.ExtraSourceFileList)
291 TargetDesc.ListFileMacro = self.ListFileMacro
292 TargetDesc.FileListMacro = self.FileListMacro
293 TargetDesc.IncListFileMacro = self.IncListFileMacro
294 TargetDesc.GenFileListMacro = self.GenFileListMacro
295 TargetDesc.GenListFile = self.GenListFile
296 TargetDesc.GenIncListFile = self.GenIncListFile
297 self.BuildTargets[DstFile[0]] = TargetDesc
298 return TargetDesc
299
300 ## Class for build rules
301 #
302 # BuildRule class parses rules defined in a file or passed by caller, and converts
303 # the rule into FileBuildRule object.
304 #
305 class BuildRule:
306 _SectionHeader = "SECTIONHEADER"
307 _Section = "SECTION"
308 _SubSectionHeader = "SUBSECTIONHEADER"
309 _SubSection = "SUBSECTION"
310 _InputFile = "INPUTFILE"
311 _OutputFile = "OUTPUTFILE"
312 _ExtraDependency = "EXTRADEPENDENCY"
313 _Command = "COMMAND"
314 _UnknownSection = "UNKNOWNSECTION"
315
316 _SubSectionList = [_InputFile, _OutputFile, _Command]
317
318 _PATH_SEP = "(+)"
319 _FileTypePattern = re.compile("^[_a-zA-Z][_\-0-9a-zA-Z]*$")
320 _BinaryFileRule = FileBuildRule(TAB_DEFAULT_BINARY_FILE, [], [os.path.join("$(OUTPUT_DIR)", "${s_name}")],
321 ["$(CP) ${src} ${dst}"], [])
322
323 ## Constructor
324 #
325 # @param File The file containing build rules in a well defined format
326 # @param Content The string list of build rules in a well defined format
327 # @param LineIndex The line number from which the parsing will begin
328 # @param SupportedFamily The list of supported tool chain families
329 #
330 def __init__(self, File=None, Content=None, LineIndex=0, SupportedFamily=["MSFT", "INTEL", "GCC", "RVCT"]):
331 self.RuleFile = File
332 # Read build rules from file if it's not none
333 if File != None:
334 try:
335 self.RuleContent = open(File, 'r').readlines()
336 except:
337 EdkLogger.error("build", FILE_OPEN_FAILURE, ExtraData=File)
338 elif Content != None:
339 self.RuleContent = Content
340 else:
341 EdkLogger.error("build", PARAMETER_MISSING, ExtraData="No rule file or string given")
342
343 self.SupportedToolChainFamilyList = SupportedFamily
344 self.RuleDatabase = tdict(True, 4) # {FileExt, ModuleType, Arch, Family : FileBuildRule object}
345 self.Ext2FileType = {} # {ext : file-type}
346 self.FileTypeList = set()
347
348 self._LineIndex = LineIndex
349 self._State = ""
350 self._RuleInfo = tdict(True, 2) # {toolchain family : {"InputFile": {}, "OutputFile" : [], "Command" : []}}
351 self._FileType = ''
352 self._BuildTypeList = []
353 self._ArchList = []
354 self._FamilyList = []
355 self._TotalToolChainFamilySet = set()
356 self._RuleObjectList = [] # FileBuildRule object list
357 self._FileVersion = ""
358
359 self.Parse()
360
361 # some intrinsic rules
362 self.RuleDatabase[TAB_DEFAULT_BINARY_FILE, "COMMON", "COMMON", "COMMON"] = self._BinaryFileRule
363 self.FileTypeList.add(TAB_DEFAULT_BINARY_FILE)
364
365 ## Parse the build rule strings
366 def Parse(self):
367 self._State = self._Section
368 for Index in range(self._LineIndex, len(self.RuleContent)):
369 # Clean up the line and replace path separator with native one
370 Line = self.RuleContent[Index].strip().replace(self._PATH_SEP, os.path.sep)
371 self.RuleContent[Index] = Line
372
373 # find the build_rule_version
374 if Line and Line[0] == "#" and Line.find(TAB_BUILD_RULE_VERSION) <> -1:
375 if Line.find("=") <> -1 and Line.find("=") < (len(Line)-1) and (Line[(Line.find("=") + 1):]).split():
376 self._FileVersion = (Line[(Line.find("=") + 1):]).split()[0]
377 # skip empty or comment line
378 if Line == "" or Line[0] == "#":
379 continue
380
381 # find out section header, enclosed by []
382 if Line[0] == '[' and Line[-1] == ']':
383 # merge last section information into rule database
384 self.EndOfSection()
385 self._State = self._SectionHeader
386 # find out sub-section header, enclosed by <>
387 elif Line[0] == '<' and Line[-1] == '>':
388 if self._State != self._UnknownSection:
389 self._State = self._SubSectionHeader
390
391 # call section handler to parse each (sub)section
392 self._StateHandler[self._State](self, Index)
393 # merge last section information into rule database
394 self.EndOfSection()
395
396 ## Parse definitions under a section
397 #
398 # @param LineIndex The line index of build rule text
399 #
400 def ParseSection(self, LineIndex):
401 pass
402
403 ## Parse definitions under a subsection
404 #
405 # @param LineIndex The line index of build rule text
406 #
407 def ParseSubSection(self, LineIndex):
408 # currenly nothing here
409 pass
410
411 ## Placeholder for not supported sections
412 #
413 # @param LineIndex The line index of build rule text
414 #
415 def SkipSection(self, LineIndex):
416 pass
417
418 ## Merge section information just got into rule database
419 def EndOfSection(self):
420 Database = self.RuleDatabase
421 # if there's specific toochain family, 'COMMON' doesn't make sense any more
422 if len(self._TotalToolChainFamilySet) > 1 and 'COMMON' in self._TotalToolChainFamilySet:
423 self._TotalToolChainFamilySet.remove('COMMON')
424 for Family in self._TotalToolChainFamilySet:
425 Input = self._RuleInfo[Family, self._InputFile]
426 Output = self._RuleInfo[Family, self._OutputFile]
427 Command = self._RuleInfo[Family, self._Command]
428 ExtraDependency = self._RuleInfo[Family, self._ExtraDependency]
429
430 BuildRule = FileBuildRule(self._FileType, Input, Output, Command, ExtraDependency)
431 for BuildType in self._BuildTypeList:
432 for Arch in self._ArchList:
433 Database[self._FileType, BuildType, Arch, Family] = BuildRule
434 for FileExt in BuildRule.SourceFileExtList:
435 self.Ext2FileType[FileExt] = self._FileType
436
437 ## Parse section header
438 #
439 # @param LineIndex The line index of build rule text
440 #
441 def ParseSectionHeader(self, LineIndex):
442 self._RuleInfo = tdict(True, 2)
443 self._BuildTypeList = []
444 self._ArchList = []
445 self._FamilyList = []
446 self._TotalToolChainFamilySet = set()
447 FileType = ''
448 RuleNameList = self.RuleContent[LineIndex][1:-1].split(',')
449 for RuleName in RuleNameList:
450 Arch = 'COMMON'
451 BuildType = 'COMMON'
452 TokenList = [Token.strip().upper() for Token in RuleName.split('.')]
453 # old format: Build.File-Type
454 if TokenList[0] == "BUILD":
455 if len(TokenList) == 1:
456 EdkLogger.error("build", FORMAT_INVALID, "Invalid rule section",
457 File=self.RuleFile, Line=LineIndex+1,
458 ExtraData=self.RuleContent[LineIndex])
459
460 FileType = TokenList[1]
461 if FileType == '':
462 EdkLogger.error("build", FORMAT_INVALID, "No file type given",
463 File=self.RuleFile, Line=LineIndex+1,
464 ExtraData=self.RuleContent[LineIndex])
465 if self._FileTypePattern.match(FileType) == None:
466 EdkLogger.error("build", FORMAT_INVALID, File=self.RuleFile, Line=LineIndex+1,
467 ExtraData="Only character, number (non-first character), '_' and '-' are allowed in file type")
468 # new format: File-Type.Build-Type.Arch
469 else:
470 if FileType == '':
471 FileType = TokenList[0]
472 elif FileType != TokenList[0]:
473 EdkLogger.error("build", FORMAT_INVALID,
474 "Different file types are not allowed in the same rule section",
475 File=self.RuleFile, Line=LineIndex+1,
476 ExtraData=self.RuleContent[LineIndex])
477 if len(TokenList) > 1:
478 BuildType = TokenList[1]
479 if len(TokenList) > 2:
480 Arch = TokenList[2]
481 if BuildType not in self._BuildTypeList:
482 self._BuildTypeList.append(BuildType)
483 if Arch not in self._ArchList:
484 self._ArchList.append(Arch)
485
486 if 'COMMON' in self._BuildTypeList and len(self._BuildTypeList) > 1:
487 EdkLogger.error("build", FORMAT_INVALID,
488 "Specific build types must not be mixed with common one",
489 File=self.RuleFile, Line=LineIndex+1,
490 ExtraData=self.RuleContent[LineIndex])
491 if 'COMMON' in self._ArchList and len(self._ArchList) > 1:
492 EdkLogger.error("build", FORMAT_INVALID,
493 "Specific ARCH must not be mixed with common one",
494 File=self.RuleFile, Line=LineIndex+1,
495 ExtraData=self.RuleContent[LineIndex])
496
497 self._FileType = FileType
498 self._State = self._Section
499 self.FileTypeList.add(FileType)
500
501 ## Parse sub-section header
502 #
503 # @param LineIndex The line index of build rule text
504 #
505 def ParseSubSectionHeader(self, LineIndex):
506 SectionType = ""
507 List = self.RuleContent[LineIndex][1:-1].split(',')
508 FamilyList = []
509 for Section in List:
510 TokenList = Section.split('.')
511 Type = TokenList[0].strip().upper()
512
513 if SectionType == "":
514 SectionType = Type
515 elif SectionType != Type:
516 EdkLogger.error("build", FORMAT_INVALID,
517 "Two different section types are not allowed in the same sub-section",
518 File=self.RuleFile, Line=LineIndex+1,
519 ExtraData=self.RuleContent[LineIndex])
520
521 if len(TokenList) > 1:
522 Family = TokenList[1].strip().upper()
523 else:
524 Family = "COMMON"
525
526 if Family not in FamilyList:
527 FamilyList.append(Family)
528
529 self._FamilyList = FamilyList
530 self._TotalToolChainFamilySet.update(FamilyList)
531 self._State = SectionType.upper()
532 if 'COMMON' in FamilyList and len(FamilyList) > 1:
533 EdkLogger.error("build", FORMAT_INVALID,
534 "Specific tool chain family should not be mixed with general one",
535 File=self.RuleFile, Line=LineIndex+1,
536 ExtraData=self.RuleContent[LineIndex])
537 if self._State not in self._StateHandler:
538 EdkLogger.error("build", FORMAT_INVALID, File=self.RuleFile, Line=LineIndex+1,
539 ExtraData="Unknown subsection: %s" % self.RuleContent[LineIndex])
540 ## Parse <InputFile> sub-section
541 #
542 # @param LineIndex The line index of build rule text
543 #
544 def ParseInputFile(self, LineIndex):
545 FileList = [File.strip() for File in self.RuleContent[LineIndex].split(",")]
546 for ToolChainFamily in self._FamilyList:
547 InputFiles = self._RuleInfo[ToolChainFamily, self._State]
548 if InputFiles == None:
549 InputFiles = []
550 self._RuleInfo[ToolChainFamily, self._State] = InputFiles
551 InputFiles.extend(FileList)
552
553 ## Parse <ExtraDependency> sub-section
554 #
555 # @param LineIndex The line index of build rule text
556 #
557 def ParseCommon(self, LineIndex):
558 for ToolChainFamily in self._FamilyList:
559 Items = self._RuleInfo[ToolChainFamily, self._State]
560 if Items == None:
561 Items = []
562 self._RuleInfo[ToolChainFamily, self._State] = Items
563 Items.append(self.RuleContent[LineIndex])
564
565 ## Get a build rule via [] operator
566 #
567 # @param FileExt The extension of a file
568 # @param ToolChainFamily The tool chain family name
569 # @param BuildVersion The build version number. "*" means any rule
570 # is applicalbe.
571 #
572 # @retval FileType The file type string
573 # @retval FileBuildRule The object of FileBuildRule
574 #
575 # Key = (FileExt, ModuleType, Arch, ToolChainFamily)
576 def __getitem__(self, Key):
577 if not Key:
578 return None
579
580 if Key[0] in self.Ext2FileType:
581 Type = self.Ext2FileType[Key[0]]
582 elif Key[0].upper() in self.FileTypeList:
583 Type = Key[0].upper()
584 else:
585 return None
586
587 if len(Key) > 1:
588 Key = (Type,) + Key[1:]
589 else:
590 Key = (Type,)
591 return self.RuleDatabase[Key]
592
593 _StateHandler = {
594 _SectionHeader : ParseSectionHeader,
595 _Section : ParseSection,
596 _SubSectionHeader : ParseSubSectionHeader,
597 _SubSection : ParseSubSection,
598 _InputFile : ParseInputFile,
599 _OutputFile : ParseCommon,
600 _ExtraDependency : ParseCommon,
601 _Command : ParseCommon,
602 _UnknownSection : SkipSection,
603 }
604
605 # This acts like the main() function for the script, unless it is 'import'ed into another
606 # script.
607 if __name__ == '__main__':
608 import sys
609 EdkLogger.Initialize()
610 if len(sys.argv) > 1:
611 Br = BuildRule(sys.argv[1])
612 print str(Br[".c", "DXE_DRIVER", "IA32", "MSFT"][1])
613 print
614 print str(Br[".c", "DXE_DRIVER", "IA32", "INTEL"][1])
615 print
616 print str(Br[".c", "DXE_DRIVER", "IA32", "GCC"][1])
617 print
618 print str(Br[".ac", "ACPI_TABLE", "IA32", "MSFT"][1])
619 print
620 print str(Br[".h", "ACPI_TABLE", "IA32", "INTEL"][1])
621 print
622 print str(Br[".ac", "ACPI_TABLE", "IA32", "MSFT"][1])
623 print
624 print str(Br[".s", "SEC", "IPF", "COMMON"][1])
625 print
626 print str(Br[".s", "SEC"][1])
627