2019-09-03 11:58:42 +02:00
## @file
# The engine for building files
#
# Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
# Import Modules
#
from __future__ import print_function
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 . StringUtils import NormPath
from Common . DataType import *
from Common . TargetTxtClassObject import TargetTxt
gDefaultBuildRuleFile = ' build_rule.txt '
AutoGenReqBuildRuleVerNum = ' 0.1 '
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 " %s S " % 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 ) :
def __init__ ( self , Inputs , Outputs , Commands , Dependencies ) :
self . InitWorker ( Inputs , Outputs , Commands , Dependencies )
def InitWorker ( 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 isinstance ( 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
## 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 representing input file(s) for a rule
# @param Output The list representing 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 TAB_STAR 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 = set ( )
for File in Input :
Base , Ext = os . path . splitext ( File )
if Base . find ( TAB_STAR ) > = 0 :
# There's TAB_STAR in the file name
self . IsMultipleInput = True
self . GenFileListMacro = True
elif Base . find ( " ? " ) < 0 :
# There's no TAB_STAR and "?" in file name
self . ExtraSourceFileList . append ( File )
continue
self . SourceFileExtList . add ( 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 )
2019-09-20 06:56:20 +02:00
def Instantiate ( self , Macros = None ) :
if Macros is None :
Macros = { }
2019-09-03 11:58:42 +02:00
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, Destination 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 . InitWorker ( [ 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 = [ TAB_COMPILER_MSFT , " INTEL " , " GCC " , " RVCT " ] ) :
self . RuleFile = File
# Read build rules from file if it's not none
if File is not None :
try :
self . RuleContent = open ( File , ' r ' ) . readlines ( )
except :
EdkLogger . error ( " build " , FILE_OPEN_FAILURE , ExtraData = File )
elif Content is not 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 = set ( )
self . _ArchList = set ( )
self . _FamilyList = [ ]
self . _TotalToolChainFamilySet = set ( )
self . _RuleObjectList = [ ] # FileBuildRule object list
self . _FileVersion = " "
self . Parse ( )
# some intrinsic rules
self . RuleDatabase [ TAB_DEFAULT_BINARY_FILE , TAB_COMMON , TAB_COMMON , TAB_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 ) :
# currently 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 toolchain family, 'COMMON' doesn't make sense any more
if len ( self . _TotalToolChainFamilySet ) > 1 and TAB_COMMON in self . _TotalToolChainFamilySet :
self . _TotalToolChainFamilySet . remove ( TAB_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 = set ( )
self . _ArchList = set ( )
self . _FamilyList = [ ]
self . _TotalToolChainFamilySet = set ( )
FileType = ' '
RuleNameList = self . RuleContent [ LineIndex ] [ 1 : - 1 ] . split ( ' , ' )
for RuleName in RuleNameList :
Arch = TAB_COMMON
BuildType = TAB_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 ) is 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 ]
self . _BuildTypeList . add ( BuildType )
self . _ArchList . add ( Arch )
if TAB_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 TAB_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 = TAB_COMMON
if Family not in FamilyList :
FamilyList . append ( Family )
self . _FamilyList = FamilyList
self . _TotalToolChainFamilySet . update ( FamilyList )
self . _State = SectionType . upper ( )
if TAB_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 ParseInputFileSubSection ( self , LineIndex ) :
FileList = [ File . strip ( ) for File in self . RuleContent [ LineIndex ] . split ( " , " ) ]
for ToolChainFamily in self . _FamilyList :
if self . _RuleInfo [ ToolChainFamily , self . _State ] is None :
self . _RuleInfo [ ToolChainFamily , self . _State ] = [ ]
self . _RuleInfo [ ToolChainFamily , self . _State ] . extend ( FileList )
## Parse <ExtraDependency> sub-section
## Parse <OutputFile> sub-section
## Parse <Command> sub-section
#
# @param LineIndex The line index of build rule text
#
def ParseCommonSubSection ( self , LineIndex ) :
for ToolChainFamily in self . _FamilyList :
if self . _RuleInfo [ ToolChainFamily , self . _State ] is None :
self . _RuleInfo [ ToolChainFamily , self . _State ] = [ ]
self . _RuleInfo [ ToolChainFamily , self . _State ] . 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. TAB_STAR means any rule
# is applicable.
#
# @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 : ParseInputFileSubSection ,
_OutputFile : ParseCommonSubSection ,
_ExtraDependency : ParseCommonSubSection ,
_Command : ParseCommonSubSection ,
_UnknownSection : SkipSection ,
}
def GetBuildRule ( ) :
BuildRuleFile = None
if TAB_TAT_DEFINES_BUILD_RULE_CONF in TargetTxt . TargetTxtDictionary :
BuildRuleFile = TargetTxt . TargetTxtDictionary [ TAB_TAT_DEFINES_BUILD_RULE_CONF ]
if not BuildRuleFile :
BuildRuleFile = gDefaultBuildRuleFile
RetVal = BuildRule ( BuildRuleFile )
if RetVal . _FileVersion == " " :
RetVal . _FileVersion = AutoGenReqBuildRuleVerNum
else :
if RetVal . _FileVersion < AutoGenReqBuildRuleVerNum :
# If Build Rule's version is less than the version number required by the tools, halting the build.
EdkLogger . error ( " build " , AUTOGEN_ERROR ,
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 ]) " \
% ( RetVal . _FileVersion , AutoGenReqBuildRuleVerNum ) )
return RetVal
BuildRuleObj = GetBuildRule ( )
# 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 " , SUP_MODULE_DXE_DRIVER , " IA32 " , TAB_COMPILER_MSFT ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .c " , SUP_MODULE_DXE_DRIVER , " IA32 " , " INTEL " ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .c " , SUP_MODULE_DXE_DRIVER , " IA32 " , " GCC " ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .ac " , " ACPI_TABLE " , " IA32 " , TAB_COMPILER_MSFT ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .h " , " ACPI_TABLE " , " IA32 " , " INTEL " ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .ac " , " ACPI_TABLE " , " IA32 " , TAB_COMPILER_MSFT ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .s " , SUP_MODULE_SEC , " IPF " , " COMMON " ] [ 1 ] ) )
print ( )
print ( str ( Br [ " .s " , SUP_MODULE_SEC ] [ 1 ] ) )