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