2 # The engine for building files
4 # Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
5 # SPDX-License-Identifier: BSD-2-Clause-Patent
11 from __future__
import print_function
12 import Common
.LongFilePathOs
as os
16 from Common
.LongFilePathSupport
import OpenLongFilePath
as open
18 from Common
.GlobalData
import *
19 from Common
.BuildToolError
import *
20 from Common
.Misc
import tdict
, PathClass
21 from Common
.StringUtils
import NormPath
22 from Common
.DataType
import *
23 from Common
.TargetTxtClassObject
import TargetTxtDict
24 gDefaultBuildRuleFile
= 'build_rule.txt'
25 AutoGenReqBuildRuleVerNum
= '0.1'
27 import Common
.EdkLogger
as EdkLogger
29 ## Convert file type to file list macro name
31 # @param FileType The name of file type
33 # @retval string The name of macro
35 def FileListMacro(FileType
):
36 return "%sS" % FileType
.replace("-", "_").upper()
38 ## Convert file type to list file macro name
40 # @param FileType The name of file type
42 # @retval string The name of macro
44 def ListFileMacro(FileType
):
45 return "%s_LIST" % FileListMacro(FileType
)
47 class TargetDescBlock(object):
48 def __init__(self
, Inputs
, Outputs
, Commands
, Dependencies
):
49 self
.InitWorker(Inputs
, Outputs
, Commands
, Dependencies
)
51 def InitWorker(self
, Inputs
, Outputs
, Commands
, Dependencies
):
53 self
.Outputs
= Outputs
54 self
.Commands
= Commands
55 self
.Dependencies
= Dependencies
57 self
.Target
= self
.Outputs
[0]
62 return self
.Target
.Path
65 return hash(self
.Target
.Path
)
67 def __eq__(self
, Other
):
68 if isinstance(Other
, type(self
)):
69 return Other
.Target
.Path
== self
.Target
.Path
71 return str(Other
) == self
.Target
.Path
73 def AddInput(self
, Input
):
74 if Input
not in self
.Inputs
:
75 self
.Inputs
.append(Input
)
77 def IsMultipleInput(self
):
78 return len(self
.Inputs
) > 1
80 ## Class for one build rule
82 # This represents a build rule which can give out corresponding command list for
83 # building the given source file(s). The result can be used for generating the
84 # target for makefile.
87 INC_LIST_MACRO
= "INC_LIST"
92 # @param Input The dictionary representing input file(s) for a rule
93 # @param Output The list representing output file(s) for a rule
94 # @param Command The list containing commands to generate the output from input
96 def __init__(self
, Type
, Input
, Output
, Command
, ExtraDependency
=None):
97 # The Input should not be empty
105 self
.FileListMacro
= FileListMacro(Type
)
106 self
.ListFileMacro
= ListFileMacro(Type
)
107 self
.IncListFileMacro
= self
.INC_LIST_MACRO
109 self
.SourceFileType
= Type
110 # source files listed not in TAB_STAR or "?" pattern format
111 if not ExtraDependency
:
112 self
.ExtraSourceFileList
= []
114 self
.ExtraSourceFileList
= ExtraDependency
117 # Search macros used in command lines for <FILE_TYPE>_LIST and INC_LIST.
118 # If found, generate a file to keep the input files used to get over the
119 # limitation of command line length
122 self
.CommandList
= []
123 for CmdLine
in Command
:
124 self
.MacroList
.extend(gMacroRefPattern
.findall(CmdLine
))
125 # replace path separator with native one
126 self
.CommandList
.append(CmdLine
)
128 # Indicate what should be generated
129 if self
.FileListMacro
in self
.MacroList
:
130 self
.GenFileListMacro
= True
132 self
.GenFileListMacro
= False
134 if self
.ListFileMacro
in self
.MacroList
:
135 self
.GenListFile
= True
136 self
.GenFileListMacro
= True
138 self
.GenListFile
= False
140 if self
.INC_LIST_MACRO
in self
.MacroList
:
141 self
.GenIncListFile
= True
143 self
.GenIncListFile
= False
146 self
.IsMultipleInput
= False
147 self
.SourceFileExtList
= set()
149 Base
, Ext
= os
.path
.splitext(File
)
150 if Base
.find(TAB_STAR
) >= 0:
151 # There's TAB_STAR in the file name
152 self
.IsMultipleInput
= True
153 self
.GenFileListMacro
= True
154 elif Base
.find("?") < 0:
155 # There's no TAB_STAR and "?" in file name
156 self
.ExtraSourceFileList
.append(File
)
158 self
.SourceFileExtList
.add(Ext
)
161 self
.DestFileList
= []
163 self
.DestFileList
.append(File
)
165 # All build targets generated by this rule for a module
166 self
.BuildTargets
= {}
168 ## str() function support
174 SourceString
+= " %s %s %s" % (self
.SourceFileType
, " ".join(self
.SourceFileExtList
), self
.ExtraSourceFileList
)
175 DestString
= ", ".join([str(i
) for i
in self
.DestFileList
])
176 CommandString
= "\n\t".join(self
.CommandList
)
177 return "%s : %s\n\t%s" % (DestString
, SourceString
, CommandString
)
179 def Instantiate(self
, Macros
= None):
182 NewRuleObject
= copy
.copy(self
)
183 NewRuleObject
.BuildTargets
= {}
184 NewRuleObject
.DestFileList
= []
185 for File
in self
.DestFileList
:
186 NewRuleObject
.DestFileList
.append(PathClass(NormPath(File
, Macros
)))
189 ## Apply the rule to given source file(s)
191 # @param SourceFile One file or a list of files to be built
192 # @param RelativeToDir The relative path of the source file
193 # @param PathSeparator Path separator
195 # @retval tuple (Source file in full path, List of individual sourcefiles, Destination file, List of build commands)
197 def Apply(self
, SourceFile
, BuildRuleOrder
=None):
198 if not self
.CommandList
or not self
.DestFileList
:
202 if self
.IsMultipleInput
:
208 # SourceFile must be a list
209 SrcFile
= "$(%s)" % self
.FileListMacro
211 SrcFileName
, SrcFileBase
, SrcFileExt
= SourceFile
.Name
, SourceFile
.BaseName
, SourceFile
.Ext
213 SrcFileDir
= SourceFile
.SubDir
218 SrcFile
= SourceFile
.Path
219 SrcPath
= SourceFile
.Dir
221 # destination file (the first one)
222 if self
.DestFileList
:
223 DestFile
= self
.DestFileList
[0].Path
224 DestPath
= self
.DestFileList
[0].Dir
225 DestFileName
= self
.DestFileList
[0].Name
226 DestFileBase
, DestFileExt
= self
.DestFileList
[0].BaseName
, self
.DestFileList
[0].Ext
234 BuildRulePlaceholderDict
= {
238 "s_dir" : SrcFileDir
,
239 "s_name" : SrcFileName
,
240 "s_base" : SrcFileBase
,
241 "s_ext" : SrcFileExt
,
245 "d_name" : DestFileName
,
246 "d_base" : DestFileBase
,
247 "d_ext" : DestFileExt
,
251 for File
in self
.DestFileList
:
252 File
= string
.Template(str(File
)).safe_substitute(BuildRulePlaceholderDict
)
253 File
= string
.Template(str(File
)).safe_substitute(BuildRulePlaceholderDict
)
254 DstFile
.append(PathClass(File
, IsBinary
=True))
256 if DstFile
[0] in self
.BuildTargets
:
257 TargetDesc
= self
.BuildTargets
[DstFile
[0]]
258 if BuildRuleOrder
and SourceFile
.Ext
in BuildRuleOrder
:
259 Index
= BuildRuleOrder
.index(SourceFile
.Ext
)
260 for Input
in TargetDesc
.Inputs
:
261 if Input
.Ext
not in BuildRuleOrder
or BuildRuleOrder
.index(Input
.Ext
) > Index
:
263 # Command line should be regenerated since some macros are different
265 CommandList
= self
._BuildCommand
(BuildRulePlaceholderDict
)
266 TargetDesc
.InitWorker([SourceFile
], DstFile
, CommandList
, self
.ExtraSourceFileList
)
269 TargetDesc
.AddInput(SourceFile
)
271 CommandList
= self
._BuildCommand
(BuildRulePlaceholderDict
)
272 TargetDesc
= TargetDescBlock([SourceFile
], DstFile
, CommandList
, self
.ExtraSourceFileList
)
273 TargetDesc
.ListFileMacro
= self
.ListFileMacro
274 TargetDesc
.FileListMacro
= self
.FileListMacro
275 TargetDesc
.IncListFileMacro
= self
.IncListFileMacro
276 TargetDesc
.GenFileListMacro
= self
.GenFileListMacro
277 TargetDesc
.GenListFile
= self
.GenListFile
278 TargetDesc
.GenIncListFile
= self
.GenIncListFile
279 self
.BuildTargets
[DstFile
[0]] = TargetDesc
282 def _BuildCommand(self
, Macros
):
284 for CommandString
in self
.CommandList
:
285 CommandString
= string
.Template(CommandString
).safe_substitute(Macros
)
286 CommandString
= string
.Template(CommandString
).safe_substitute(Macros
)
287 CommandList
.append(CommandString
)
290 ## Class for build rules
292 # BuildRule class parses rules defined in a file or passed by caller, and converts
293 # the rule into FileBuildRule object.
296 _SectionHeader
= "SECTIONHEADER"
298 _SubSectionHeader
= "SUBSECTIONHEADER"
299 _SubSection
= "SUBSECTION"
300 _InputFile
= "INPUTFILE"
301 _OutputFile
= "OUTPUTFILE"
302 _ExtraDependency
= "EXTRADEPENDENCY"
304 _UnknownSection
= "UNKNOWNSECTION"
306 _SubSectionList
= [_InputFile
, _OutputFile
, _Command
]
309 _FileTypePattern
= re
.compile("^[_a-zA-Z][_\-0-9a-zA-Z]*$")
310 _BinaryFileRule
= FileBuildRule(TAB_DEFAULT_BINARY_FILE
, [], [os
.path
.join("$(OUTPUT_DIR)", "${s_name}")],
311 ["$(CP) ${src} ${dst}"], [])
315 # @param File The file containing build rules in a well defined format
316 # @param Content The string list of build rules in a well defined format
317 # @param LineIndex The line number from which the parsing will begin
318 # @param SupportedFamily The list of supported tool chain families
320 def __init__(self
, File
=None, Content
=None, LineIndex
=0, SupportedFamily
=[TAB_COMPILER_MSFT
, "INTEL", "GCC", "RVCT"]):
322 # Read build rules from file if it's not none
325 self
.RuleContent
= open(File
, 'r').readlines()
327 EdkLogger
.error("build", FILE_OPEN_FAILURE
, ExtraData
=File
)
328 elif Content
is not None:
329 self
.RuleContent
= Content
331 EdkLogger
.error("build", PARAMETER_MISSING
, ExtraData
="No rule file or string given")
333 self
.SupportedToolChainFamilyList
= SupportedFamily
334 self
.RuleDatabase
= tdict(True, 4) # {FileExt, ModuleType, Arch, Family : FileBuildRule object}
335 self
.Ext2FileType
= {} # {ext : file-type}
336 self
.FileTypeList
= set()
338 self
._LineIndex
= LineIndex
340 self
._RuleInfo
= tdict(True, 2) # {toolchain family : {"InputFile": {}, "OutputFile" : [], "Command" : []}}
342 self
._BuildTypeList
= set()
343 self
._ArchList
= set()
344 self
._FamilyList
= []
345 self
._TotalToolChainFamilySet
= set()
346 self
._RuleObjectList
= [] # FileBuildRule object list
347 self
._FileVersion
= ""
351 # some intrinsic rules
352 self
.RuleDatabase
[TAB_DEFAULT_BINARY_FILE
, TAB_COMMON
, TAB_COMMON
, TAB_COMMON
] = self
._BinaryFileRule
353 self
.FileTypeList
.add(TAB_DEFAULT_BINARY_FILE
)
355 ## Parse the build rule strings
357 self
._State
= self
._Section
358 for Index
in range(self
._LineIndex
, len(self
.RuleContent
)):
359 # Clean up the line and replace path separator with native one
360 Line
= self
.RuleContent
[Index
].strip().replace(self
._PATH
_SEP
, os
.path
.sep
)
361 self
.RuleContent
[Index
] = Line
363 # find the build_rule_version
364 if Line
and Line
[0] == "#" and Line
.find(TAB_BUILD_RULE_VERSION
) != -1:
365 if Line
.find("=") != -1 and Line
.find("=") < (len(Line
) - 1) and (Line
[(Line
.find("=") + 1):]).split():
366 self
._FileVersion
= (Line
[(Line
.find("=") + 1):]).split()[0]
367 # skip empty or comment line
368 if Line
== "" or Line
[0] == "#":
371 # find out section header, enclosed by []
372 if Line
[0] == '[' and Line
[-1] == ']':
373 # merge last section information into rule database
375 self
._State
= self
._SectionHeader
376 # find out sub-section header, enclosed by <>
377 elif Line
[0] == '<' and Line
[-1] == '>':
378 if self
._State
!= self
._UnknownSection
:
379 self
._State
= self
._SubSectionHeader
381 # call section handler to parse each (sub)section
382 self
._StateHandler
[self
._State
](self
, Index
)
383 # merge last section information into rule database
386 ## Parse definitions under a section
388 # @param LineIndex The line index of build rule text
390 def ParseSection(self
, LineIndex
):
393 ## Parse definitions under a subsection
395 # @param LineIndex The line index of build rule text
397 def ParseSubSection(self
, LineIndex
):
398 # currently nothing here
401 ## Placeholder for not supported sections
403 # @param LineIndex The line index of build rule text
405 def SkipSection(self
, LineIndex
):
408 ## Merge section information just got into rule database
409 def EndOfSection(self
):
410 Database
= self
.RuleDatabase
411 # if there's specific toolchain family, 'COMMON' doesn't make sense any more
412 if len(self
._TotalToolChainFamilySet
) > 1 and TAB_COMMON
in self
._TotalToolChainFamilySet
:
413 self
._TotalToolChainFamilySet
.remove(TAB_COMMON
)
414 for Family
in self
._TotalToolChainFamilySet
:
415 Input
= self
._RuleInfo
[Family
, self
._InputFile
]
416 Output
= self
._RuleInfo
[Family
, self
._OutputFile
]
417 Command
= self
._RuleInfo
[Family
, self
._Command
]
418 ExtraDependency
= self
._RuleInfo
[Family
, self
._ExtraDependency
]
420 BuildRule
= FileBuildRule(self
._FileType
, Input
, Output
, Command
, ExtraDependency
)
421 for BuildType
in self
._BuildTypeList
:
422 for Arch
in self
._ArchList
:
423 Database
[self
._FileType
, BuildType
, Arch
, Family
] = BuildRule
424 for FileExt
in BuildRule
.SourceFileExtList
:
425 self
.Ext2FileType
[FileExt
] = self
._FileType
427 ## Parse section header
429 # @param LineIndex The line index of build rule text
431 def ParseSectionHeader(self
, LineIndex
):
432 self
._RuleInfo
= tdict(True, 2)
433 self
._BuildTypeList
= set()
434 self
._ArchList
= set()
435 self
._FamilyList
= []
436 self
._TotalToolChainFamilySet
= set()
438 RuleNameList
= self
.RuleContent
[LineIndex
][1:-1].split(',')
439 for RuleName
in RuleNameList
:
441 BuildType
= TAB_COMMON
442 TokenList
= [Token
.strip().upper() for Token
in RuleName
.split('.')]
443 # old format: Build.File-Type
444 if TokenList
[0] == "BUILD":
445 if len(TokenList
) == 1:
446 EdkLogger
.error("build", FORMAT_INVALID
, "Invalid rule section",
447 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
448 ExtraData
=self
.RuleContent
[LineIndex
])
450 FileType
= TokenList
[1]
452 EdkLogger
.error("build", FORMAT_INVALID
, "No file type given",
453 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
454 ExtraData
=self
.RuleContent
[LineIndex
])
455 if self
._FileTypePattern
.match(FileType
) is None:
456 EdkLogger
.error("build", FORMAT_INVALID
, File
=self
.RuleFile
, Line
=LineIndex
+ 1,
457 ExtraData
="Only character, number (non-first character), '_' and '-' are allowed in file type")
458 # new format: File-Type.Build-Type.Arch
461 FileType
= TokenList
[0]
462 elif FileType
!= TokenList
[0]:
463 EdkLogger
.error("build", FORMAT_INVALID
,
464 "Different file types are not allowed in the same rule section",
465 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
466 ExtraData
=self
.RuleContent
[LineIndex
])
467 if len(TokenList
) > 1:
468 BuildType
= TokenList
[1]
469 if len(TokenList
) > 2:
471 self
._BuildTypeList
.add(BuildType
)
472 self
._ArchList
.add(Arch
)
474 if TAB_COMMON
in self
._BuildTypeList
and len(self
._BuildTypeList
) > 1:
475 EdkLogger
.error("build", FORMAT_INVALID
,
476 "Specific build types must not be mixed with common one",
477 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
478 ExtraData
=self
.RuleContent
[LineIndex
])
479 if TAB_COMMON
in self
._ArchList
and len(self
._ArchList
) > 1:
480 EdkLogger
.error("build", FORMAT_INVALID
,
481 "Specific ARCH must not be mixed with common one",
482 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
483 ExtraData
=self
.RuleContent
[LineIndex
])
485 self
._FileType
= FileType
486 self
._State
= self
._Section
487 self
.FileTypeList
.add(FileType
)
489 ## Parse sub-section header
491 # @param LineIndex The line index of build rule text
493 def ParseSubSectionHeader(self
, LineIndex
):
495 List
= self
.RuleContent
[LineIndex
][1:-1].split(',')
498 TokenList
= Section
.split('.')
499 Type
= TokenList
[0].strip().upper()
501 if SectionType
== "":
503 elif SectionType
!= Type
:
504 EdkLogger
.error("build", FORMAT_INVALID
,
505 "Two different section types are not allowed in the same sub-section",
506 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
507 ExtraData
=self
.RuleContent
[LineIndex
])
509 if len(TokenList
) > 1:
510 Family
= TokenList
[1].strip().upper()
514 if Family
not in FamilyList
:
515 FamilyList
.append(Family
)
517 self
._FamilyList
= FamilyList
518 self
._TotalToolChainFamilySet
.update(FamilyList
)
519 self
._State
= SectionType
.upper()
520 if TAB_COMMON
in FamilyList
and len(FamilyList
) > 1:
521 EdkLogger
.error("build", FORMAT_INVALID
,
522 "Specific tool chain family should not be mixed with general one",
523 File
=self
.RuleFile
, Line
=LineIndex
+ 1,
524 ExtraData
=self
.RuleContent
[LineIndex
])
525 if self
._State
not in self
._StateHandler
:
526 EdkLogger
.error("build", FORMAT_INVALID
, File
=self
.RuleFile
, Line
=LineIndex
+ 1,
527 ExtraData
="Unknown subsection: %s" % self
.RuleContent
[LineIndex
])
528 ## Parse <InputFile> sub-section
530 # @param LineIndex The line index of build rule text
532 def ParseInputFileSubSection(self
, LineIndex
):
533 FileList
= [File
.strip() for File
in self
.RuleContent
[LineIndex
].split(",")]
534 for ToolChainFamily
in self
._FamilyList
:
535 if self
._RuleInfo
[ToolChainFamily
, self
._State
] is None:
536 self
._RuleInfo
[ToolChainFamily
, self
._State
] = []
537 self
._RuleInfo
[ToolChainFamily
, self
._State
].extend(FileList
)
539 ## Parse <ExtraDependency> sub-section
540 ## Parse <OutputFile> sub-section
541 ## Parse <Command> sub-section
543 # @param LineIndex The line index of build rule text
545 def ParseCommonSubSection(self
, LineIndex
):
546 for ToolChainFamily
in self
._FamilyList
:
547 if self
._RuleInfo
[ToolChainFamily
, self
._State
] is None:
548 self
._RuleInfo
[ToolChainFamily
, self
._State
] = []
549 self
._RuleInfo
[ToolChainFamily
, self
._State
].append(self
.RuleContent
[LineIndex
])
551 ## Get a build rule via [] operator
553 # @param FileExt The extension of a file
554 # @param ToolChainFamily The tool chain family name
555 # @param BuildVersion The build version number. TAB_STAR means any rule
558 # @retval FileType The file type string
559 # @retval FileBuildRule The object of FileBuildRule
561 # Key = (FileExt, ModuleType, Arch, ToolChainFamily)
562 def __getitem__(self
, Key
):
566 if Key
[0] in self
.Ext2FileType
:
567 Type
= self
.Ext2FileType
[Key
[0]]
568 elif Key
[0].upper() in self
.FileTypeList
:
569 Type
= Key
[0].upper()
574 Key
= (Type
,) + Key
[1:]
577 return self
.RuleDatabase
[Key
]
580 _SectionHeader
: ParseSectionHeader
,
581 _Section
: ParseSection
,
582 _SubSectionHeader
: ParseSubSectionHeader
,
583 _SubSection
: ParseSubSection
,
584 _InputFile
: ParseInputFileSubSection
,
585 _OutputFile
: ParseCommonSubSection
,
586 _ExtraDependency
: ParseCommonSubSection
,
587 _Command
: ParseCommonSubSection
,
588 _UnknownSection
: SkipSection
,
591 class ToolBuildRule():
593 def __new__(cls
, *args
, **kw
):
594 if not hasattr(cls
, '_instance'):
595 orig
= super(ToolBuildRule
, cls
)
596 cls
._instance
= orig
.__new
__(cls
, *args
, **kw
)
600 if not hasattr(self
, 'ToolBuildRule'):
601 self
._ToolBuildRule
= None
604 def ToolBuildRule(self
):
605 if not self
._ToolBuildRule
:
607 return self
._ToolBuildRule
609 def _GetBuildRule(self
):
611 TargetObj
= TargetTxtDict()
612 TargetTxt
= TargetObj
.Target
613 if TAB_TAT_DEFINES_BUILD_RULE_CONF
in TargetTxt
.TargetTxtDictionary
:
614 BuildRuleFile
= TargetTxt
.TargetTxtDictionary
[TAB_TAT_DEFINES_BUILD_RULE_CONF
]
615 if not BuildRuleFile
:
616 BuildRuleFile
= gDefaultBuildRuleFile
617 RetVal
= BuildRule(BuildRuleFile
)
618 if RetVal
._FileVersion
== "":
619 RetVal
._FileVersion
= AutoGenReqBuildRuleVerNum
621 if RetVal
._FileVersion
< AutoGenReqBuildRuleVerNum
:
622 # If Build Rule's version is less than the version number required by the tools, halting the build.
623 EdkLogger
.error("build", AUTOGEN_ERROR
,
624 ExtraData
="The version number [%s] of build_rule.txt is less than the version number required by the AutoGen.(the minimum required version number is [%s])"\
625 % (RetVal
._FileVersion
, AutoGenReqBuildRuleVerNum
))
626 self
._ToolBuildRule
= RetVal
628 # This acts like the main() function for the script, unless it is 'import'ed into another
630 if __name__
== '__main__':
632 EdkLogger
.Initialize()
633 if len(sys
.argv
) > 1:
634 Br
= BuildRule(sys
.argv
[1])
635 print(str(Br
[".c", SUP_MODULE_DXE_DRIVER
, "IA32", TAB_COMPILER_MSFT
][1]))
637 print(str(Br
[".c", SUP_MODULE_DXE_DRIVER
, "IA32", "INTEL"][1]))
639 print(str(Br
[".c", SUP_MODULE_DXE_DRIVER
, "IA32", "GCC"][1]))
641 print(str(Br
[".ac", "ACPI_TABLE", "IA32", TAB_COMPILER_MSFT
][1]))
643 print(str(Br
[".h", "ACPI_TABLE", "IA32", "INTEL"][1]))
645 print(str(Br
[".ac", "ACPI_TABLE", "IA32", TAB_COMPILER_MSFT
][1]))
647 print(str(Br
[".s", SUP_MODULE_SEC
, "IPF", "COMMON"][1]))
649 print(str(Br
[".s", SUP_MODULE_SEC
][1]))