## @file # The engine for building files # # Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved.
# This program and the accompanying materials # are licensed and made available under the terms and conditions of the BSD License # which accompanies this distribution. The full text of the license may be found at # http://opensource.org/licenses/bsd-license.php # # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. # ## # Import Modules # import Common.LongFilePathOs as os import re import copy import string from Common.LongFilePathSupport import OpenLongFilePath as open from Common.GlobalData import * from Common.BuildToolError import * from Common.Misc import tdict, PathClass from Common.String import NormPath from Common.DataType import * import Common.EdkLogger as EdkLogger ## Convert file type to file list macro name # # @param FileType The name of file type # # @retval string The name of macro # def FileListMacro(FileType): return "%sS" % FileType.replace("-", "_").upper() ## Convert file type to list file macro name # # @param FileType The name of file type # # @retval string The name of macro # def ListFileMacro(FileType): return "%s_LIST" % FileListMacro(FileType) class TargetDescBlock(object): _Cache_ = {} # {TargetFile : TargetDescBlock object} # Factory method def __new__(Class, Inputs, Outputs, Commands, Dependencies): if Outputs[0] in Class._Cache_: Tdb = Class._Cache_[Outputs[0]] for File in Inputs: Tdb.AddInput(File) else: Tdb = super(TargetDescBlock, Class).__new__(Class) Tdb._Init(Inputs, Outputs, Commands, Dependencies) #Class._Cache_[Outputs[0]] = Tdb return Tdb def _Init(self, Inputs, Outputs, Commands, Dependencies): self.Inputs = Inputs self.Outputs = Outputs self.Commands = Commands self.Dependencies = Dependencies if self.Outputs: self.Target = self.Outputs[0] else: self.Target = None def __str__(self): return self.Target.Path def __hash__(self): return hash(self.Target.Path) def __eq__(self, Other): if type(Other) == type(self): return Other.Target.Path == self.Target.Path else: return str(Other) == self.Target.Path def AddInput(self, Input): if Input not in self.Inputs: self.Inputs.append(Input) def IsMultipleInput(self): return len(self.Inputs) > 1 @staticmethod def Renew(): TargetDescBlock._Cache_ = {} ## Class for one build rule # # This represents a build rule which can give out corresponding command list for # building the given source file(s). The result can be used for generating the # target for makefile. # class FileBuildRule: INC_LIST_MACRO = "INC_LIST" INC_MACRO = "INC" ## constructor # # @param Input The dictionary represeting input file(s) for a rule # @param Output The list represeting output file(s) for a rule # @param Command The list containing commands to generate the output from input # def __init__(self, Type, Input, Output, Command, ExtraDependency=None): # The Input should not be empty if not Input: Input = [] if not Output: Output = [] if not Command: Command = [] self.FileListMacro = FileListMacro(Type) self.ListFileMacro = ListFileMacro(Type) self.IncListFileMacro = self.INC_LIST_MACRO self.SourceFileType = Type # source files listed not in "*" or "?" pattern format if not ExtraDependency: self.ExtraSourceFileList = [] else: self.ExtraSourceFileList = ExtraDependency # # Search macros used in command lines for _LIST and INC_LIST. # If found, generate a file to keep the input files used to get over the # limitation of command line length # self.MacroList = [] self.CommandList = [] for CmdLine in Command: self.MacroList.extend(gMacroRefPattern.findall(CmdLine)) # replace path separator with native one self.CommandList.append(CmdLine) # Indicate what should be generated if self.FileListMacro in self.MacroList: self.GenFileListMacro = True else: self.GenFileListMacro = False if self.ListFileMacro in self.MacroList: self.GenListFile = True self.GenFileListMacro = True else: self.GenListFile = False if self.INC_LIST_MACRO in self.MacroList: self.GenIncListFile = True else: self.GenIncListFile = False # Check input files self.IsMultipleInput = False self.SourceFileExtList = [] for File in Input: Base, Ext = os.path.splitext(File) if Base.find("*") >= 0: # There's "*" in the file name self.IsMultipleInput = True self.GenFileListMacro = True elif Base.find("?") < 0: # There's no "*" and "?" in file name self.ExtraSourceFileList.append(File) continue if Ext not in self.SourceFileExtList: self.SourceFileExtList.append(Ext) # Check output files self.DestFileList = [] for File in Output: self.DestFileList.append(File) # All build targets generated by this rule for a module self.BuildTargets = {} ## str() function support # # @retval string # def __str__(self): SourceString = "" SourceString += " %s %s %s" % (self.SourceFileType, " ".join(self.SourceFileExtList), self.ExtraSourceFileList) DestString = ", ".join(self.DestFileList) CommandString = "\n\t".join(self.CommandList) return "%s : %s\n\t%s" % (DestString, SourceString, CommandString) ## Check if given file extension is supported by this rule # # @param FileExt The extension of a file # # @retval True If the extension is supported # @retval False If the extension is not supported # def IsSupported(self, FileExt): return FileExt in self.SourceFileExtList def Instantiate(self, Macros={}): NewRuleObject = copy.copy(self) NewRuleObject.BuildTargets = {} NewRuleObject.DestFileList = [] for File in self.DestFileList: NewRuleObject.DestFileList.append(PathClass(NormPath(File, Macros))) return NewRuleObject ## Apply the rule to given source file(s) # # @param SourceFile One file or a list of files to be built # @param RelativeToDir The relative path of the source file # @param PathSeparator Path separator # # @retval tuple (Source file in full path, List of individual sourcefiles, Destionation file, List of build commands) # def Apply(self, SourceFile, BuildRuleOrder=None): if not self.CommandList or not self.DestFileList: return None # source file if self.IsMultipleInput: SrcFileName = "" SrcFileBase = "" SrcFileExt = "" SrcFileDir = "" SrcPath = "" # SourceFile must be a list SrcFile = "$(%s)" % self.FileListMacro else: SrcFileName, SrcFileBase, SrcFileExt = SourceFile.Name, SourceFile.BaseName, SourceFile.Ext if SourceFile.Root: SrcFileDir = SourceFile.SubDir if SrcFileDir == "": SrcFileDir = "." else: SrcFileDir = "." SrcFile = SourceFile.Path SrcPath = SourceFile.Dir # destination file (the first one) if self.DestFileList: DestFile = self.DestFileList[0].Path DestPath = self.DestFileList[0].Dir DestFileName = self.DestFileList[0].Name DestFileBase, DestFileExt = self.DestFileList[0].BaseName, self.DestFileList[0].Ext else: DestFile = "" DestPath = "" DestFileName = "" DestFileBase = "" DestFileExt = "" BuildRulePlaceholderDict = { # source file "src" : SrcFile, "s_path" : SrcPath, "s_dir" : SrcFileDir, "s_name" : SrcFileName, "s_base" : SrcFileBase, "s_ext" : SrcFileExt, # destination file "dst" : DestFile, "d_path" : DestPath, "d_name" : DestFileName, "d_base" : DestFileBase, "d_ext" : DestFileExt, } DstFile = [] for File in self.DestFileList: File = string.Template(str(File)).safe_substitute(BuildRulePlaceholderDict) File = string.Template(str(File)).safe_substitute(BuildRulePlaceholderDict) DstFile.append(PathClass(File, IsBinary=True)) if DstFile[0] in self.BuildTargets: TargetDesc = self.BuildTargets[DstFile[0]] if BuildRuleOrder and SourceFile.Ext in BuildRuleOrder: Index = BuildRuleOrder.index(SourceFile.Ext) for Input in TargetDesc.Inputs: if Input.Ext not in BuildRuleOrder or BuildRuleOrder.index(Input.Ext) > Index: # # Command line should be regenerated since some macros are different # CommandList = self._BuildCommand(BuildRulePlaceholderDict) TargetDesc._Init([SourceFile], DstFile, CommandList, self.ExtraSourceFileList) break else: TargetDesc.AddInput(SourceFile) else: CommandList = self._BuildCommand(BuildRulePlaceholderDict) TargetDesc = TargetDescBlock([SourceFile], DstFile, CommandList, self.ExtraSourceFileList) TargetDesc.ListFileMacro = self.ListFileMacro TargetDesc.FileListMacro = self.FileListMacro TargetDesc.IncListFileMacro = self.IncListFileMacro TargetDesc.GenFileListMacro = self.GenFileListMacro TargetDesc.GenListFile = self.GenListFile TargetDesc.GenIncListFile = self.GenIncListFile self.BuildTargets[DstFile[0]] = TargetDesc return TargetDesc def _BuildCommand(self, Macros): CommandList = [] for CommandString in self.CommandList: CommandString = string.Template(CommandString).safe_substitute(Macros) CommandString = string.Template(CommandString).safe_substitute(Macros) CommandList.append(CommandString) return CommandList ## Class for build rules # # BuildRule class parses rules defined in a file or passed by caller, and converts # the rule into FileBuildRule object. # class BuildRule: _SectionHeader = "SECTIONHEADER" _Section = "SECTION" _SubSectionHeader = "SUBSECTIONHEADER" _SubSection = "SUBSECTION" _InputFile = "INPUTFILE" _OutputFile = "OUTPUTFILE" _ExtraDependency = "EXTRADEPENDENCY" _Command = "COMMAND" _UnknownSection = "UNKNOWNSECTION" _SubSectionList = [_InputFile, _OutputFile, _Command] _PATH_SEP = "(+)" _FileTypePattern = re.compile("^[_a-zA-Z][_\-0-9a-zA-Z]*$") _BinaryFileRule = FileBuildRule(TAB_DEFAULT_BINARY_FILE, [], [os.path.join("$(OUTPUT_DIR)", "${s_name}")], ["$(CP) ${src} ${dst}"], []) ## Constructor # # @param File The file containing build rules in a well defined format # @param Content The string list of build rules in a well defined format # @param LineIndex The line number from which the parsing will begin # @param SupportedFamily The list of supported tool chain families # def __init__(self, File=None, Content=None, LineIndex=0, SupportedFamily=["MSFT", "INTEL", "GCC", "RVCT"]): self.RuleFile = File # Read build rules from file if it's not none if File != None: try: self.RuleContent = open(File, 'r').readlines() except: EdkLogger.error("build", FILE_OPEN_FAILURE, ExtraData=File) elif Content != None: self.RuleContent = Content else: EdkLogger.error("build", PARAMETER_MISSING, ExtraData="No rule file or string given") self.SupportedToolChainFamilyList = SupportedFamily self.RuleDatabase = tdict(True, 4) # {FileExt, ModuleType, Arch, Family : FileBuildRule object} self.Ext2FileType = {} # {ext : file-type} self.FileTypeList = set() self._LineIndex = LineIndex self._State = "" self._RuleInfo = tdict(True, 2) # {toolchain family : {"InputFile": {}, "OutputFile" : [], "Command" : []}} self._FileType = '' self._BuildTypeList = [] self._ArchList = [] self._FamilyList = [] self._TotalToolChainFamilySet = set() self._RuleObjectList = [] # FileBuildRule object list self._FileVersion = "" self.Parse() # some intrinsic rules self.RuleDatabase[TAB_DEFAULT_BINARY_FILE, "COMMON", "COMMON", "COMMON"] = self._BinaryFileRule self.FileTypeList.add(TAB_DEFAULT_BINARY_FILE) ## Parse the build rule strings def Parse(self): self._State = self._Section for Index in range(self._LineIndex, len(self.RuleContent)): # Clean up the line and replace path separator with native one Line = self.RuleContent[Index].strip().replace(self._PATH_SEP, os.path.sep) self.RuleContent[Index] = Line # find the build_rule_version if Line and Line[0] == "#" and Line.find(TAB_BUILD_RULE_VERSION) <> -1: if Line.find("=") <> -1 and Line.find("=") < (len(Line) - 1) and (Line[(Line.find("=") + 1):]).split(): self._FileVersion = (Line[(Line.find("=") + 1):]).split()[0] # skip empty or comment line if Line == "" or Line[0] == "#": continue # find out section header, enclosed by [] if Line[0] == '[' and Line[-1] == ']': # merge last section information into rule database self.EndOfSection() self._State = self._SectionHeader # find out sub-section header, enclosed by <> elif Line[0] == '<' and Line[-1] == '>': if self._State != self._UnknownSection: self._State = self._SubSectionHeader # call section handler to parse each (sub)section self._StateHandler[self._State](self, Index) # merge last section information into rule database self.EndOfSection() ## Parse definitions under a section # # @param LineIndex The line index of build rule text # def ParseSection(self, LineIndex): pass ## Parse definitions under a subsection # # @param LineIndex The line index of build rule text # def ParseSubSection(self, LineIndex): # currenly nothing here pass ## Placeholder for not supported sections # # @param LineIndex The line index of build rule text # def SkipSection(self, LineIndex): pass ## Merge section information just got into rule database def EndOfSection(self): Database = self.RuleDatabase # if there's specific toochain family, 'COMMON' doesn't make sense any more if len(self._TotalToolChainFamilySet) > 1 and 'COMMON' in self._TotalToolChainFamilySet: self._TotalToolChainFamilySet.remove('COMMON') for Family in self._TotalToolChainFamilySet: Input = self._RuleInfo[Family, self._InputFile] Output = self._RuleInfo[Family, self._OutputFile] Command = self._RuleInfo[Family, self._Command] ExtraDependency = self._RuleInfo[Family, self._ExtraDependency] BuildRule = FileBuildRule(self._FileType, Input, Output, Command, ExtraDependency) for BuildType in self._BuildTypeList: for Arch in self._ArchList: Database[self._FileType, BuildType, Arch, Family] = BuildRule for FileExt in BuildRule.SourceFileExtList: self.Ext2FileType[FileExt] = self._FileType ## Parse section header # # @param LineIndex The line index of build rule text # def ParseSectionHeader(self, LineIndex): self._RuleInfo = tdict(True, 2) self._BuildTypeList = [] self._ArchList = [] self._FamilyList = [] self._TotalToolChainFamilySet = set() FileType = '' RuleNameList = self.RuleContent[LineIndex][1:-1].split(',') for RuleName in RuleNameList: Arch = 'COMMON' BuildType = 'COMMON' TokenList = [Token.strip().upper() for Token in RuleName.split('.')] # old format: Build.File-Type if TokenList[0] == "BUILD": if len(TokenList) == 1: EdkLogger.error("build", FORMAT_INVALID, "Invalid rule section", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) FileType = TokenList[1] if FileType == '': EdkLogger.error("build", FORMAT_INVALID, "No file type given", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) if self._FileTypePattern.match(FileType) == None: EdkLogger.error("build", FORMAT_INVALID, File=self.RuleFile, Line=LineIndex + 1, ExtraData="Only character, number (non-first character), '_' and '-' are allowed in file type") # new format: File-Type.Build-Type.Arch else: if FileType == '': FileType = TokenList[0] elif FileType != TokenList[0]: EdkLogger.error("build", FORMAT_INVALID, "Different file types are not allowed in the same rule section", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) if len(TokenList) > 1: BuildType = TokenList[1] if len(TokenList) > 2: Arch = TokenList[2] if BuildType not in self._BuildTypeList: self._BuildTypeList.append(BuildType) if Arch not in self._ArchList: self._ArchList.append(Arch) if 'COMMON' in self._BuildTypeList and len(self._BuildTypeList) > 1: EdkLogger.error("build", FORMAT_INVALID, "Specific build types must not be mixed with common one", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) if 'COMMON' in self._ArchList and len(self._ArchList) > 1: EdkLogger.error("build", FORMAT_INVALID, "Specific ARCH must not be mixed with common one", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) self._FileType = FileType self._State = self._Section self.FileTypeList.add(FileType) ## Parse sub-section header # # @param LineIndex The line index of build rule text # def ParseSubSectionHeader(self, LineIndex): SectionType = "" List = self.RuleContent[LineIndex][1:-1].split(',') FamilyList = [] for Section in List: TokenList = Section.split('.') Type = TokenList[0].strip().upper() if SectionType == "": SectionType = Type elif SectionType != Type: EdkLogger.error("build", FORMAT_INVALID, "Two different section types are not allowed in the same sub-section", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) if len(TokenList) > 1: Family = TokenList[1].strip().upper() else: Family = "COMMON" if Family not in FamilyList: FamilyList.append(Family) self._FamilyList = FamilyList self._TotalToolChainFamilySet.update(FamilyList) self._State = SectionType.upper() if 'COMMON' in FamilyList and len(FamilyList) > 1: EdkLogger.error("build", FORMAT_INVALID, "Specific tool chain family should not be mixed with general one", File=self.RuleFile, Line=LineIndex + 1, ExtraData=self.RuleContent[LineIndex]) if self._State not in self._StateHandler: EdkLogger.error("build", FORMAT_INVALID, File=self.RuleFile, Line=LineIndex + 1, ExtraData="Unknown subsection: %s" % self.RuleContent[LineIndex]) ## Parse sub-section # # @param LineIndex The line index of build rule text # def ParseInputFile(self, LineIndex): FileList = [File.strip() for File in self.RuleContent[LineIndex].split(",")] for ToolChainFamily in self._FamilyList: InputFiles = self._RuleInfo[ToolChainFamily, self._State] if InputFiles == None: InputFiles = [] self._RuleInfo[ToolChainFamily, self._State] = InputFiles InputFiles.extend(FileList) ## Parse sub-section # # @param LineIndex The line index of build rule text # def ParseCommon(self, LineIndex): for ToolChainFamily in self._FamilyList: Items = self._RuleInfo[ToolChainFamily, self._State] if Items == None: Items = [] self._RuleInfo[ToolChainFamily, self._State] = Items Items.append(self.RuleContent[LineIndex]) ## Get a build rule via [] operator # # @param FileExt The extension of a file # @param ToolChainFamily The tool chain family name # @param BuildVersion The build version number. "*" means any rule # is applicalbe. # # @retval FileType The file type string # @retval FileBuildRule The object of FileBuildRule # # Key = (FileExt, ModuleType, Arch, ToolChainFamily) def __getitem__(self, Key): if not Key: return None if Key[0] in self.Ext2FileType: Type = self.Ext2FileType[Key[0]] elif Key[0].upper() in self.FileTypeList: Type = Key[0].upper() else: return None if len(Key) > 1: Key = (Type,) + Key[1:] else: Key = (Type,) return self.RuleDatabase[Key] _StateHandler = { _SectionHeader : ParseSectionHeader, _Section : ParseSection, _SubSectionHeader : ParseSubSectionHeader, _SubSection : ParseSubSection, _InputFile : ParseInputFile, _OutputFile : ParseCommon, _ExtraDependency : ParseCommon, _Command : ParseCommon, _UnknownSection : SkipSection, } # This acts like the main() function for the script, unless it is 'import'ed into another # script. if __name__ == '__main__': import sys EdkLogger.Initialize() if len(sys.argv) > 1: Br = BuildRule(sys.argv[1]) print str(Br[".c", "DXE_DRIVER", "IA32", "MSFT"][1]) print print str(Br[".c", "DXE_DRIVER", "IA32", "INTEL"][1]) print print str(Br[".c", "DXE_DRIVER", "IA32", "GCC"][1]) print print str(Br[".ac", "ACPI_TABLE", "IA32", "MSFT"][1]) print print str(Br[".h", "ACPI_TABLE", "IA32", "INTEL"][1]) print print str(Br[".ac", "ACPI_TABLE", "IA32", "MSFT"][1]) print print str(Br[".s", "SEC", "IPF", "COMMON"][1]) print print str(Br[".s", "SEC"][1])