| ## @file |
| # The engine for building files |
| # |
| # Copyright (c) 2007, Intel Corporation. All rights reserved.<BR> |
| # 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 os |
| import re |
| import copy |
| import string |
| |
| 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 <FILE_TYPE>_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): |
| 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]] |
| TargetDesc.AddInput(SourceFile) |
| else: |
| CommandList = [] |
| for CommandString in self.CommandList: |
| CommandString = string.Template(CommandString).safe_substitute(BuildRulePlaceholderDict) |
| CommandString = string.Template(CommandString).safe_substitute(BuildRulePlaceholderDict) |
| CommandList.append(CommandString) |
| 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 |
| |
| ## 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.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 |
| |
| # 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 <InputFile> 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 <ExtraDependency> 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]) |
| |