1## @file
2# Common routines used by all tools
3#
4# Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR>
5#
6# This program and the accompanying materials are licensed and made available
7# under the terms and conditions of the BSD License which accompanies this
8# distribution. The full text of the license may be found at
9# http://opensource.org/licenses/bsd-license.php
10#
11# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13#
14
15'''
16Misc
17'''
18
19##
20# Import Modules
21#
22import os.path
23from os import access
24from os import F_OK
25from os import makedirs
26from os import getcwd
27from os import chdir
28from os import listdir
29from os import remove
30from os import rmdir
31from os import linesep
32from os import walk
33from os import environ
34import re
35from UserDict import IterableUserDict
36
37import Logger.Log as Logger
38from Logger import StringTable as ST
39from Logger import ToolError
40from Library import GlobalData
41from Library.DataType import SUP_MODULE_LIST
42from Library.DataType import END_OF_LINE
43from Library.DataType import TAB_SPLIT
44from Library.DataType import TAB_LANGUAGE_EN_US
45from Library.DataType import TAB_LANGUAGE_EN
46from Library.DataType import TAB_LANGUAGE_EN_X
47from Library.DataType import TAB_UNI_FILE_SUFFIXS
48from Library.String import GetSplitValueList
49from Library.ParserValidate import IsValidHexVersion
50from Library.ParserValidate import IsValidPath
51from Object.POM.CommonObject import TextObject
52from Core.FileHook import __FileHookOpen__
53from Common.MultipleWorkspace import MultipleWorkspace as mws
54
55## Convert GUID string in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx style to C
56# structure style
57#
58# @param      Guid:    The GUID string
59#
60def GuidStringToGuidStructureString(Guid):
61    GuidList = Guid.split('-')
62    Result = '{'
63    for Index in range(0, 3, 1):
64        Result = Result + '0x' + GuidList[Index] + ', '
65    Result = Result + '{0x' + GuidList[3][0:2] + ', 0x' + GuidList[3][2:4]
66    for Index in range(0, 12, 2):
67        Result = Result + ', 0x' + GuidList[4][Index:Index + 2]
68    Result += '}}'
69    return Result
70
71## Check whether GUID string is of format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
72#
73# @param      GuidValue:   The GUID value
74#
75def CheckGuidRegFormat(GuidValue):
76    ## Regular expression used to find out register format of GUID
77    #
78    RegFormatGuidPattern = re.compile("^\s*([0-9a-fA-F]){8}-"
79                                       "([0-9a-fA-F]){4}-"
80                                       "([0-9a-fA-F]){4}-"
81                                       "([0-9a-fA-F]){4}-"
82                                       "([0-9a-fA-F]){12}\s*$")
83
84    if RegFormatGuidPattern.match(GuidValue):
85        return True
86    else:
87        return False
88
89
90## Convert GUID string in C structure style to
91# xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
92#
93# @param      GuidValue:   The GUID value in C structure format
94#
95def GuidStructureStringToGuidString(GuidValue):
96    GuidValueString = GuidValue.lower().replace("{", "").replace("}", "").\
97    replace(" ", "").replace(";", "")
98    GuidValueList = GuidValueString.split(",")
99    if len(GuidValueList) != 11:
100        return ''
101    try:
102        return "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % (
103                int(GuidValueList[0], 16),
104                int(GuidValueList[1], 16),
105                int(GuidValueList[2], 16),
106                int(GuidValueList[3], 16),
107                int(GuidValueList[4], 16),
108                int(GuidValueList[5], 16),
109                int(GuidValueList[6], 16),
110                int(GuidValueList[7], 16),
111                int(GuidValueList[8], 16),
112                int(GuidValueList[9], 16),
113                int(GuidValueList[10], 16)
114                )
115    except BaseException:
116        return ''
117
118## Create directories
119#
120# @param      Directory:   The directory name
121#
122def CreateDirectory(Directory):
123    if Directory == None or Directory.strip() == "":
124        return True
125    try:
126        if not access(Directory, F_OK):
127            makedirs(Directory)
128    except BaseException:
129        return False
130    return True
131
132## Remove directories, including files and sub-directories in it
133#
134# @param      Directory:   The directory name
135#
136def RemoveDirectory(Directory, Recursively=False):
137    if Directory == None or Directory.strip() == "" or not \
138    os.path.exists(Directory):
139        return
140    if Recursively:
141        CurrentDirectory = getcwd()
142        chdir(Directory)
143        for File in listdir("."):
144            if os.path.isdir(File):
145                RemoveDirectory(File, Recursively)
146            else:
147                remove(File)
148        chdir(CurrentDirectory)
149    rmdir(Directory)
150
151## Store content in file
152#
153# This method is used to save file only when its content is changed. This is
154# quite useful for "make" system to decide what will be re-built and what
155# won't.
156#
157# @param      File:            The path of file
158# @param      Content:         The new content of the file
159# @param      IsBinaryFile:    The flag indicating if the file is binary file
160#                              or not
161#
162def SaveFileOnChange(File, Content, IsBinaryFile=True):
163    if not IsBinaryFile:
164        Content = Content.replace("\n", linesep)
165
166    if os.path.exists(File):
167        try:
168            if Content == __FileHookOpen__(File, "rb").read():
169                return False
170        except BaseException:
171            Logger.Error(None, ToolError.FILE_OPEN_FAILURE, ExtraData=File)
172
173    CreateDirectory(os.path.dirname(File))
174    try:
175        FileFd = __FileHookOpen__(File, "wb")
176        FileFd.write(Content)
177        FileFd.close()
178    except BaseException:
179        Logger.Error(None, ToolError.FILE_CREATE_FAILURE, ExtraData=File)
180
181    return True
182
183## Get all files of a directory
184#
185# @param Root:       Root dir
186# @param SkipList :  The files need be skipped
187#
188def GetFiles(Root, SkipList=None, FullPath=True):
189    OriPath = os.path.normpath(Root)
190    FileList = []
191    for Root, Dirs, Files in walk(Root):
192        if SkipList:
193            for Item in SkipList:
194                if Item in Dirs:
195                    Dirs.remove(Item)
196                if Item in Files:
197                    Files.remove(Item)
198        for Dir in Dirs:
199            if Dir.startswith('.'):
200                Dirs.remove(Dir)
201
202        for File in Files:
203            if File.startswith('.'):
204                continue
205            File = os.path.normpath(os.path.join(Root, File))
206            if not FullPath:
207                File = File[len(OriPath) + 1:]
208            FileList.append(File)
209
210    return FileList
211
212## Get all non-metadata files of a directory
213#
214# @param Root:       Root Dir
215# @param SkipList :  List of path need be skipped
216# @param FullPath:  True if the returned file should be full path
217# @param PrefixPath: the path that need to be added to the files found
218# @return: the list of files found
219#
220def GetNonMetaDataFiles(Root, SkipList, FullPath, PrefixPath):
221    FileList = GetFiles(Root, SkipList, FullPath)
222    NewFileList = []
223    for File in FileList:
224        ExtName = os.path.splitext(File)[1]
225        #
226        # skip '.dec', '.inf', '.dsc', '.fdf' files
227        #
228        if ExtName.lower() not in ['.dec', '.inf', '.dsc', '.fdf']:
229            NewFileList.append(os.path.normpath(os.path.join(PrefixPath, File)))
230
231    return NewFileList
232
233## Check if given file exists or not
234#
235# @param      File:    File name or path to be checked
236# @param      Dir:     The directory the file is relative to
237#
238def ValidFile(File, Ext=None):
239    File = File.replace('\\', '/')
240    if Ext != None:
241        FileExt = os.path.splitext(File)[1]
242        if FileExt.lower() != Ext.lower():
243            return False
244    if not os.path.exists(File):
245        return False
246    return True
247
248## RealPath
249#
250# @param      File:    File name or path to be checked
251# @param      Dir:     The directory the file is relative to
252# @param      OverrideDir:     The override directory
253#
254def RealPath(File, Dir='', OverrideDir=''):
255    NewFile = os.path.normpath(os.path.join(Dir, File))
256    NewFile = GlobalData.gALL_FILES[NewFile]
257    if not NewFile and OverrideDir:
258        NewFile = os.path.normpath(os.path.join(OverrideDir, File))
259        NewFile = GlobalData.gALL_FILES[NewFile]
260    return NewFile
261
262## RealPath2
263#
264# @param      File:    File name or path to be checked
265# @param      Dir:     The directory the file is relative to
266# @param      OverrideDir:     The override directory
267#
268def RealPath2(File, Dir='', OverrideDir=''):
269    if OverrideDir:
270        NewFile = GlobalData.gALL_FILES[os.path.normpath(os.path.join\
271                                                        (OverrideDir, File))]
272        if NewFile:
273            if OverrideDir[-1] == os.path.sep:
274                return NewFile[len(OverrideDir):], NewFile[0:len(OverrideDir)]
275            else:
276                return NewFile[len(OverrideDir) + 1:], \
277            NewFile[0:len(OverrideDir)]
278
279    NewFile = GlobalData.gALL_FILES[os.path.normpath(os.path.join(Dir, File))]
280    if NewFile:
281        if Dir:
282            if Dir[-1] == os.path.sep:
283                return NewFile[len(Dir):], NewFile[0:len(Dir)]
284            else:
285                return NewFile[len(Dir) + 1:], NewFile[0:len(Dir)]
286        else:
287            return NewFile, ''
288
289    return None, None
290
291## A dict which can access its keys and/or values orderly
292#
293#  The class implements a new kind of dict which its keys or values can be
294#  accessed in the order they are added into the dict. It guarantees the order
295#  by making use of an internal list to keep a copy of keys.
296#
297class Sdict(IterableUserDict):
298    ## Constructor
299    #
300    def __init__(self):
301        IterableUserDict.__init__(self)
302        self._key_list = []
303
304    ## [] operator
305    #
306    def __setitem__(self, Key, Value):
307        if Key not in self._key_list:
308            self._key_list.append(Key)
309        IterableUserDict.__setitem__(self, Key, Value)
310
311    ## del operator
312    #
313    def __delitem__(self, Key):
314        self._key_list.remove(Key)
315        IterableUserDict.__delitem__(self, Key)
316
317    ## used in "for k in dict" loop to ensure the correct order
318    #
319    def __iter__(self):
320        return self.iterkeys()
321
322    ## len() support
323    #
324    def __len__(self):
325        return len(self._key_list)
326
327    ## "in" test support
328    #
329    def __contains__(self, Key):
330        return Key in self._key_list
331
332    ## indexof support
333    #
334    def index(self, Key):
335        return self._key_list.index(Key)
336
337    ## insert support
338    #
339    def insert(self, Key, Newkey, Newvalue, Order):
340        Index = self._key_list.index(Key)
341        if Order == 'BEFORE':
342            self._key_list.insert(Index, Newkey)
343            IterableUserDict.__setitem__(self, Newkey, Newvalue)
344        elif Order == 'AFTER':
345            self._key_list.insert(Index + 1, Newkey)
346            IterableUserDict.__setitem__(self, Newkey, Newvalue)
347
348    ## append support
349    #
350    def append(self, Sdict2):
351        for Key in Sdict2:
352            if Key not in self._key_list:
353                self._key_list.append(Key)
354            IterableUserDict.__setitem__(self, Key, Sdict2[Key])
355    ## hash key
356    #
357    def has_key(self, Key):
358        return Key in self._key_list
359
360    ## Empty the dict
361    #
362    def clear(self):
363        self._key_list = []
364        IterableUserDict.clear(self)
365
366    ## Return a copy of keys
367    #
368    def keys(self):
369        Keys = []
370        for Key in self._key_list:
371            Keys.append(Key)
372        return Keys
373
374    ## Return a copy of values
375    #
376    def values(self):
377        Values = []
378        for Key in self._key_list:
379            Values.append(self[Key])
380        return Values
381
382    ## Return a copy of (key, value) list
383    #
384    def items(self):
385        Items = []
386        for Key in self._key_list:
387            Items.append((Key, self[Key]))
388        return Items
389
390    ## Iteration support
391    #
392    def iteritems(self):
393        return iter(self.items())
394
395    ## Keys interation support
396    #
397    def iterkeys(self):
398        return iter(self.keys())
399
400    ## Values interation support
401    #
402    def itervalues(self):
403        return iter(self.values())
404
405    ## Return value related to a key, and remove the (key, value) from the dict
406    #
407    def pop(self, Key, *Dv):
408        Value = None
409        if Key in self._key_list:
410            Value = self[Key]
411            self.__delitem__(Key)
412        elif len(Dv) != 0 :
413            Value = Dv[0]
414        return Value
415
416    ## Return (key, value) pair, and remove the (key, value) from the dict
417    #
418    def popitem(self):
419        Key = self._key_list[-1]
420        Value = self[Key]
421        self.__delitem__(Key)
422        return Key, Value
423    ## update method
424    #
425    def update(self, Dict=None, **Kwargs):
426        if Dict != None:
427            for Key1, Val1 in Dict.items():
428                self[Key1] = Val1
429        if len(Kwargs):
430            for Key1, Val1 in Kwargs.items():
431                self[Key1] = Val1
432
433## CommonPath
434#
435# @param PathList: PathList
436#
437def CommonPath(PathList):
438    Path1 = min(PathList).split(os.path.sep)
439    Path2 = max(PathList).split(os.path.sep)
440    for Index in xrange(min(len(Path1), len(Path2))):
441        if Path1[Index] != Path2[Index]:
442            return os.path.sep.join(Path1[:Index])
443    return os.path.sep.join(Path1)
444
445## PathClass
446#
447class PathClass(object):
448    def __init__(self, File='', Root='', AlterRoot='', Type='', IsBinary=False,
449                 Arch='COMMON', ToolChainFamily='', Target='', TagName='', \
450                 ToolCode=''):
451        self.Arch = Arch
452        self.File = str(File)
453        if os.path.isabs(self.File):
454            self.Root = ''
455            self.AlterRoot = ''
456        else:
457            self.Root = str(Root)
458            self.AlterRoot = str(AlterRoot)
459
460        #
461        # Remove any '.' and '..' in path
462        #
463        if self.Root:
464            self.Path = os.path.normpath(os.path.join(self.Root, self.File))
465            self.Root = os.path.normpath(CommonPath([self.Root, self.Path]))
466            #
467            # eliminate the side-effect of 'C:'
468            #
469            if self.Root[-1] == ':':
470                self.Root += os.path.sep
471            #
472            # file path should not start with path separator
473            #
474            if self.Root[-1] == os.path.sep:
475                self.File = self.Path[len(self.Root):]
476            else:
477                self.File = self.Path[len(self.Root) + 1:]
478        else:
479            self.Path = os.path.normpath(self.File)
480
481        self.SubDir, self.Name = os.path.split(self.File)
482        self.BaseName, self.Ext = os.path.splitext(self.Name)
483
484        if self.Root:
485            if self.SubDir:
486                self.Dir = os.path.join(self.Root, self.SubDir)
487            else:
488                self.Dir = self.Root
489        else:
490            self.Dir = self.SubDir
491
492        if IsBinary:
493            self.Type = Type
494        else:
495            self.Type = self.Ext.lower()
496
497        self.IsBinary = IsBinary
498        self.Target = Target
499        self.TagName = TagName
500        self.ToolCode = ToolCode
501        self.ToolChainFamily = ToolChainFamily
502
503        self._Key = None
504
505    ## Convert the object of this class to a string
506    #
507    #  Convert member Path of the class to a string
508    #
509    def __str__(self):
510        return self.Path
511
512    ## Override __eq__ function
513    #
514    # Check whether PathClass are the same
515    #
516    def __eq__(self, Other):
517        if type(Other) == type(self):
518            return self.Path == Other.Path
519        else:
520            return self.Path == str(Other)
521
522    ## Override __hash__ function
523    #
524    # Use Path as key in hash table
525    #
526    def __hash__(self):
527        return hash(self.Path)
528
529    ## _GetFileKey
530    #
531    def _GetFileKey(self):
532        if self._Key == None:
533            self._Key = self.Path.upper()
534        return self._Key
535    ## Validate
536    #
537    def Validate(self, Type='', CaseSensitive=True):
538        if GlobalData.gCASE_INSENSITIVE:
539            CaseSensitive = False
540        if Type and Type.lower() != self.Type:
541            return ToolError.FILE_TYPE_MISMATCH, '%s (expect %s but got %s)' % \
542        (self.File, Type, self.Type)
543
544        RealFile, RealRoot = RealPath2(self.File, self.Root, self.AlterRoot)
545        if not RealRoot and not RealFile:
546            RealFile = self.File
547            if self.AlterRoot:
548                RealFile = os.path.join(self.AlterRoot, self.File)
549            elif self.Root:
550                RealFile = os.path.join(self.Root, self.File)
551            return ToolError.FILE_NOT_FOUND, os.path.join(self.AlterRoot, RealFile)
552
553        ErrorCode = 0
554        ErrorInfo = ''
555        if RealRoot != self.Root or RealFile != self.File:
556            if CaseSensitive and (RealFile != self.File or \
557                                  (RealRoot != self.Root and RealRoot != \
558                                   self.AlterRoot)):
559                ErrorCode = ToolError.FILE_CASE_MISMATCH
560                ErrorInfo = self.File + '\n\t' + RealFile + \
561                 " [in file system]"
562
563            self.SubDir, self.Name = os.path.split(RealFile)
564            self.BaseName, self.Ext = os.path.splitext(self.Name)
565            if self.SubDir:
566                self.Dir = os.path.join(RealRoot, self.SubDir)
567            else:
568                self.Dir = RealRoot
569            self.File = RealFile
570            self.Root = RealRoot
571            self.Path = os.path.join(RealRoot, RealFile)
572        return ErrorCode, ErrorInfo
573
574    Key = property(_GetFileKey)
575
576## Get current workspace
577#
578#  get WORKSPACE from environment variable if present,if not use current working directory as WORKSPACE
579#
580def GetWorkspace():
581    #
582    # check WORKSPACE
583    #
584    if "WORKSPACE" in environ:
585        WorkspaceDir = os.path.normpath(environ["WORKSPACE"])
586        if not os.path.exists(WorkspaceDir):
587            Logger.Error("UPT",
588                         ToolError.UPT_ENVIRON_MISSING_ERROR,
589                         ST.ERR_WORKSPACE_NOTEXIST,
590                         ExtraData="%s" % WorkspaceDir)
591    else:
592        WorkspaceDir = os.getcwd()
593
594    if WorkspaceDir[-1] == ':':
595        WorkspaceDir += os.sep
596
597    PackagesPath = os.environ.get("PACKAGES_PATH")
598    mws.setWs(WorkspaceDir, PackagesPath)
599
600    return WorkspaceDir, mws.PACKAGES_PATH
601
602## Get relative path
603#
604#  use full path and workspace to get relative path
605#  the destination of this function is mainly to resolve the root path issue(like c: or c:\)
606#
607#  @param Fullpath: a string of fullpath
608#  @param Workspace: a string of workspace
609#
610def GetRelativePath(Fullpath, Workspace):
611
612    RelativePath = ''
613    if Workspace.endswith(os.sep):
614        RelativePath = Fullpath[Fullpath.upper().find(Workspace.upper())+len(Workspace):]
615    else:
616        RelativePath = Fullpath[Fullpath.upper().find(Workspace.upper())+len(Workspace)+1:]
617
618    return RelativePath
619
620## Check whether all module types are in list
621#
622# check whether all module types (SUP_MODULE_LIST) are in list
623#
624# @param ModuleList:  a list of ModuleType
625#
626def IsAllModuleList(ModuleList):
627    NewModuleList = [Module.upper() for Module in ModuleList]
628    for Module in SUP_MODULE_LIST:
629        if Module not in NewModuleList:
630            return False
631    else:
632        return True
633
634## Dictionary that use comment(GenericComment, TailComment) as value,
635# if a new comment which key already in the dic is inserted, then the
636# comment will be merged.
637# Key is (Statement, SupArch), when TailComment is added, it will ident
638# according to Statement
639#
640class MergeCommentDict(dict):
641    ## []= operator
642    #
643    def __setitem__(self, Key, CommentVal):
644        GenericComment, TailComment = CommentVal
645        if Key in self:
646            OrigVal1, OrigVal2 = dict.__getitem__(self, Key)
647            Statement = Key[0]
648            dict.__setitem__(self, Key, (OrigVal1 + GenericComment, OrigVal2 \
649                                         + len(Statement) * ' ' + TailComment))
650        else:
651            dict.__setitem__(self, Key, (GenericComment, TailComment))
652
653    ## =[] operator
654    #
655    def __getitem__(self, Key):
656        return dict.__getitem__(self, Key)
657
658
659## GenDummyHelpTextObj
660#
661# @retval HelpTxt:   Generated dummy help text object
662#
663def GenDummyHelpTextObj():
664    HelpTxt = TextObject()
665    HelpTxt.SetLang(TAB_LANGUAGE_EN_US)
666    HelpTxt.SetString(' ')
667    return HelpTxt
668
669## ConvertVersionToDecimal, the minor version should be within 0 - 99
670# <HexVersion>          ::=  "0x" <Major> <Minor>
671# <Major>               ::=  (a-fA-F0-9){4}
672# <Minor>               ::=  (a-fA-F0-9){4}
673# <DecVersion>          ::=  (0-65535) ["." (0-99)]
674#
675# @param StringIn:  The string contains version defined in INF file.
676#                   It can be Decimal or Hex
677#
678def ConvertVersionToDecimal(StringIn):
679    if IsValidHexVersion(StringIn):
680        Value = int(StringIn, 16)
681        Major = Value >> 16
682        Minor = Value & 0xFFFF
683        MinorStr = str(Minor)
684        if len(MinorStr) == 1:
685            MinorStr = '0' + MinorStr
686        return str(Major) + '.' + MinorStr
687    else:
688        if StringIn.find(TAB_SPLIT) != -1:
689            return StringIn
690        elif StringIn:
691            return StringIn + '.0'
692        else:
693            #
694            # when StringIn is '', return it directly
695            #
696            return StringIn
697
698## GetHelpStringByRemoveHashKey
699#
700# Remove hash key at the header of string and return the remain.
701#
702# @param String:  The string need to be processed.
703#
704def GetHelpStringByRemoveHashKey(String):
705    ReturnString = ''
706    PattenRemoveHashKey = re.compile(r"^[#+\s]+", re.DOTALL)
707    String = String.strip()
708    if String == '':
709        return String
710
711    LineList = GetSplitValueList(String, END_OF_LINE)
712    for Line in LineList:
713        ValueList = PattenRemoveHashKey.split(Line)
714        if len(ValueList) == 1:
715            ReturnString += ValueList[0] + END_OF_LINE
716        else:
717            ReturnString += ValueList[1] + END_OF_LINE
718
719    if ReturnString.endswith('\n') and not ReturnString.endswith('\n\n') and ReturnString != '\n':
720        ReturnString = ReturnString[:-1]
721
722    return ReturnString
723
724## ConvPathFromAbsToRel
725#
726# Get relative file path from absolute path.
727#
728# @param Path:  The string contain file absolute path.
729# @param Root:  The string contain the parent path of Path in.
730#
731#
732def ConvPathFromAbsToRel(Path, Root):
733    Path = os.path.normpath(Path)
734    Root = os.path.normpath(Root)
735    FullPath = os.path.normpath(os.path.join(Root, Path))
736
737    #
738    # If Path is absolute path.
739    # It should be in Root.
740    #
741    if os.path.isabs(Path):
742        return FullPath[FullPath.find(Root) + len(Root) + 1:]
743
744    else:
745        return Path
746
747## ConvertPath
748#
749# Convert special characters to '_', '\' to '/'
750# return converted path: Test!1.inf -> Test_1.inf
751#
752# @param Path: Path to be converted
753#
754def ConvertPath(Path):
755    RetPath = ''
756    for Char in Path.strip():
757        if Char.isalnum() or Char in '.-_/':
758            RetPath = RetPath + Char
759        elif Char == '\\':
760            RetPath = RetPath + '/'
761        else:
762            RetPath = RetPath + '_'
763    return RetPath
764
765## ConvertSpec
766#
767# during install, convert the Spec string extract from UPD into INF allowable definition,
768# the difference is period is allowed in the former (not the first letter) but not in the latter.
769# return converted Spec string
770#
771# @param SpecStr: SpecStr to be converted
772#
773def ConvertSpec(SpecStr):
774    RetStr = ''
775    for Char in SpecStr:
776        if Char.isalnum() or Char == '_':
777            RetStr = RetStr + Char
778        else:
779            RetStr = RetStr + '_'
780
781    return RetStr
782
783
784## IsEqualList
785#
786# Judge two lists are identical(contain same item).
787# The rule is elements in List A are in List B and elements in List B are in List A.
788#
789# @param ListA, ListB  Lists need to be judged.
790#
791# @return True  ListA and ListB are identical
792# @return False ListA and ListB are different with each other
793#
794def IsEqualList(ListA, ListB):
795    if ListA == ListB:
796        return True
797
798    for ItemA in ListA:
799        if not ItemA in ListB:
800            return False
801
802    for ItemB in ListB:
803        if not ItemB in ListA:
804            return False
805
806    return True
807
808## ConvertArchList
809#
810# Convert item in ArchList if the start character is lower case.
811# In UDP spec, Arch is only allowed as: [A-Z]([a-zA-Z0-9])*
812#
813# @param ArchList The ArchList need to be converted.
814#
815# @return NewList  The ArchList been converted.
816#
817def ConvertArchList(ArchList):
818    NewArchList = []
819    if not ArchList:
820        return NewArchList
821
822    if type(ArchList) == list:
823        for Arch in ArchList:
824            Arch = Arch.upper()
825            NewArchList.append(Arch)
826    elif type(ArchList) == str:
827        ArchList = ArchList.upper()
828        NewArchList.append(ArchList)
829
830    return NewArchList
831
832## ProcessLineExtender
833#
834# Process the LineExtender of Line in LineList.
835# If one line ends with a line extender, then it will be combined together with next line.
836#
837# @param LineList The LineList need to be processed.
838#
839# @return NewList  The ArchList been processed.
840#
841def ProcessLineExtender(LineList):
842    NewList = []
843    Count = 0
844    while Count < len(LineList):
845        if LineList[Count].strip().endswith("\\") and Count + 1 < len(LineList):
846            NewList.append(LineList[Count].strip()[:-2] + LineList[Count + 1])
847            Count = Count + 1
848        else:
849            NewList.append(LineList[Count])
850
851        Count = Count + 1
852
853    return NewList
854
855## ProcessEdkComment
856#
857# Process EDK style comment in LineList: c style /* */ comment or cpp style // comment
858#
859#
860# @param LineList The LineList need to be processed.
861#
862# @return LineList  The LineList been processed.
863# @return FirstPos  Where Edk comment is first found, -1 if not found
864#
865def ProcessEdkComment(LineList):
866    FindEdkBlockComment = False
867    Count = 0
868    StartPos = -1
869    EndPos = -1
870    FirstPos = -1
871
872    while(Count < len(LineList)):
873        Line = LineList[Count].strip()
874        if Line.startswith("/*"):
875            #
876            # handling c style comment
877            #
878            StartPos = Count
879            while Count < len(LineList):
880                Line = LineList[Count].strip()
881                if Line.endswith("*/"):
882                    if (Count == StartPos) and Line.strip() == '/*/':
883                        Count = Count + 1
884                        continue
885                    EndPos = Count
886                    FindEdkBlockComment = True
887                    break
888                Count = Count + 1
889
890            if FindEdkBlockComment:
891                if FirstPos == -1:
892                    FirstPos = StartPos
893                for Index in xrange(StartPos, EndPos+1):
894                    LineList[Index] = ''
895                FindEdkBlockComment = False
896        elif Line.find("//") != -1 and not Line.startswith("#"):
897            #
898            # handling cpp style comment
899            #
900            LineList[Count] = Line.replace("//", '#')
901            if FirstPos == -1:
902                FirstPos = Count
903
904        Count = Count + 1
905
906    return LineList, FirstPos
907
908## GetLibInstanceInfo
909#
910# Get the information from Library Instance INF file.
911#
912# @param string.  A string start with # and followed by INF file path
913# @param WorkSpace. The WorkSpace directory used to combined with INF file path.
914#
915# @return GUID, Version
916def GetLibInstanceInfo(String, WorkSpace, LineNo):
917
918    FileGuidString = ""
919    VerString = ""
920
921    OrignalString = String
922    String = String.strip()
923    if not String:
924        return None, None
925    #
926    # Remove "#" characters at the beginning
927    #
928    String = GetHelpStringByRemoveHashKey(String)
929    String = String.strip()
930
931    #
932    # Validate file name exist.
933    #
934    FullFileName = os.path.normpath(os.path.realpath(os.path.join(WorkSpace, String)))
935    if not (ValidFile(FullFileName)):
936        Logger.Error("InfParser",
937                     ToolError.FORMAT_INVALID,
938                     ST.ERR_FILELIST_EXIST % (String),
939                     File=GlobalData.gINF_MODULE_NAME,
940                     Line=LineNo,
941                     ExtraData=OrignalString)
942
943    #
944    # Validate file exist/format.
945    #
946    if IsValidPath(String, WorkSpace):
947        IsValidFileFlag = True
948    else:
949        Logger.Error("InfParser",
950                     ToolError.FORMAT_INVALID,
951                     ST.ERR_INF_PARSER_FILE_NOT_EXIST_OR_NAME_INVALID % (String),
952                     File=GlobalData.gINF_MODULE_NAME,
953                     Line=LineNo,
954                     ExtraData=OrignalString)
955        return False
956    if IsValidFileFlag:
957        FileLinesList = []
958
959        try:
960            FInputfile = open(FullFileName, "rb", 0)
961            try:
962                FileLinesList = FInputfile.readlines()
963            except BaseException:
964                Logger.Error("InfParser",
965                             ToolError.FILE_READ_FAILURE,
966                             ST.ERR_FILE_OPEN_FAILURE,
967                             File=FullFileName)
968            finally:
969                FInputfile.close()
970        except BaseException:
971            Logger.Error("InfParser",
972                         ToolError.FILE_READ_FAILURE,
973                         ST.ERR_FILE_OPEN_FAILURE,
974                         File=FullFileName)
975
976        ReFileGuidPattern = re.compile("^\s*FILE_GUID\s*=.*$")
977        ReVerStringPattern = re.compile("^\s*VERSION_STRING\s*=.*$")
978
979        FileLinesList = ProcessLineExtender(FileLinesList)
980
981        for Line in FileLinesList:
982            if ReFileGuidPattern.match(Line):
983                FileGuidString = Line
984            if ReVerStringPattern.match(Line):
985                VerString = Line
986
987        if FileGuidString:
988            FileGuidString = GetSplitValueList(FileGuidString, '=', 1)[1]
989        if VerString:
990            VerString = GetSplitValueList(VerString, '=', 1)[1]
991
992        return FileGuidString, VerString
993
994## GetLocalValue
995#
996# Generate the local value for INF and DEC file. If Lang attribute not present, then use this value.
997# If present, and there is no element without the Lang attribute, and one of the elements has the rfc1766 code is
998# "en-x-tianocore", or "en-US" if "en-x-tianocore" was not found, or "en" if "en-US" was not found, or startswith 'en'
999# if 'en' was not found, then use this value.
1000# If multiple entries of a tag exist which have the same language code, use the last entry.
1001#
1002# @param ValueList  A list need to be processed.
1003# @param UseFirstValue: True to use the first value, False to use the last value
1004#
1005# @return LocalValue
1006def GetLocalValue(ValueList, UseFirstValue=False):
1007    Value1 = ''
1008    Value2 = ''
1009    Value3 = ''
1010    Value4 = ''
1011    Value5 = ''
1012    for (Key, Value) in ValueList:
1013        if Key == TAB_LANGUAGE_EN_X:
1014            if UseFirstValue:
1015                if not Value1:
1016                    Value1 = Value
1017            else:
1018                Value1 = Value
1019        if Key == TAB_LANGUAGE_EN_US:
1020            if UseFirstValue:
1021                if not Value2:
1022                    Value2 = Value
1023            else:
1024                Value2 = Value
1025        if Key == TAB_LANGUAGE_EN:
1026            if UseFirstValue:
1027                if not Value3:
1028                    Value3 = Value
1029            else:
1030                Value3 = Value
1031        if Key.startswith(TAB_LANGUAGE_EN):
1032            if UseFirstValue:
1033                if not Value4:
1034                    Value4 = Value
1035            else:
1036                Value4 = Value
1037        if Key == '':
1038            if UseFirstValue:
1039                if not Value5:
1040                    Value5 = Value
1041            else:
1042                Value5 = Value
1043
1044    if Value1:
1045        return Value1
1046    if Value2:
1047        return Value2
1048    if Value3:
1049        return Value3
1050    if Value4:
1051        return Value4
1052    if Value5:
1053        return Value5
1054
1055    return ''
1056
1057
1058## GetCharIndexOutStr
1059#
1060# Get comment character index outside a string
1061#
1062# @param Line:              The string to be checked
1063# @param CommentCharacter:  Comment char, used to ignore comment content
1064#
1065# @retval Index
1066#
1067def GetCharIndexOutStr(CommentCharacter, Line):
1068    #
1069    # remove whitespace
1070    #
1071    Line = Line.strip()
1072
1073    #
1074    # Check whether comment character is in a string
1075    #
1076    InString = False
1077    for Index in range(0, len(Line)):
1078        if Line[Index] == '"':
1079            InString = not InString
1080        elif Line[Index] == CommentCharacter and InString :
1081            pass
1082        elif Line[Index] == CommentCharacter and (Index +1) < len(Line) and Line[Index+1] == CommentCharacter \
1083            and not InString :
1084            return Index
1085    return -1
1086
1087## ValidateUNIFilePath
1088#
1089# Check the UNI file path
1090#
1091# @param FilePath: The UNI file path
1092#
1093def ValidateUNIFilePath(Path):
1094    Suffix = Path[Path.rfind(TAB_SPLIT):]
1095
1096    #
1097    # Check if the suffix is one of the '.uni', '.UNI', '.Uni'
1098    #
1099    if Suffix not in TAB_UNI_FILE_SUFFIXS:
1100        Logger.Error("Unicode File Parser",
1101                        ToolError.FORMAT_INVALID,
1102                        Message=ST.ERR_UNI_FILE_SUFFIX_WRONG,
1103                        ExtraData=Path)
1104
1105    #
1106    # Check if '..' in the file name(without suffixe)
1107    #
1108    if (TAB_SPLIT + TAB_SPLIT) in Path:
1109        Logger.Error("Unicode File Parser",
1110                        ToolError.FORMAT_INVALID,
1111                        Message=ST.ERR_UNI_FILE_NAME_INVALID,
1112                        ExtraData=Path)
1113
1114    #
1115    # Check if the file name is valid according to the DEC and INF specification
1116    #
1117    Pattern = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*'
1118    FileName = Path.replace(Suffix, '')
1119    InvalidCh = re.sub(Pattern, '', FileName)
1120    if InvalidCh:
1121        Logger.Error("Unicode File Parser",
1122                        ToolError.FORMAT_INVALID,
1123                        Message=ST.ERR_INF_PARSER_FILE_NOT_EXIST_OR_NAME_INVALID,
1124                        ExtraData=Path)
1125
1126