1## @file
2# This file is for installed package information database operations
3#
4# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
5#
6# SPDX-License-Identifier: BSD-2-Clause-Patent
7#
8
9'''
10Dependency
11'''
12
13##
14# Import Modules
15#
16from os.path import dirname
17import os
18
19import Logger.Log as Logger
20from Logger import StringTable as ST
21from Library.Parsing import GetWorkspacePackage
22from Library.Parsing import GetWorkspaceModule
23from Library.Parsing import GetPkgInfoFromDec
24from Library.Misc import GetRelativePath
25from Library import GlobalData
26from Logger.ToolError import FatalError
27from Logger.ToolError import EDK1_INF_ERROR
28from Logger.ToolError import UNKNOWN_ERROR
29(DEPEX_CHECK_SUCCESS, DEPEX_CHECK_MODULE_NOT_FOUND, \
30DEPEX_CHECK_PACKAGE_NOT_FOUND, DEPEX_CHECK_DP_NOT_FOUND) = (0, 1, 2, 3)
31
32
33## DependencyRules
34#
35# This class represents the dependency rule check mechanism
36#
37# @param object:      Inherited from object class
38#
39class DependencyRules(object):
40    def __init__(self, Datab, ToBeInstalledPkgList=None):
41        self.IpiDb = Datab
42        self.WsPkgList = GetWorkspacePackage()
43        self.WsModuleList = GetWorkspaceModule()
44
45        self.PkgsToBeDepend = [(PkgInfo[1], PkgInfo[2]) for PkgInfo in self.WsPkgList]
46
47        # Add package info from the DIST to be installed.
48        self.PkgsToBeDepend.extend(self.GenToBeInstalledPkgList(ToBeInstalledPkgList))
49
50    def GenToBeInstalledPkgList(self, ToBeInstalledPkgList):
51        if not ToBeInstalledPkgList:
52            return []
53        RtnList = []
54        for Dist in ToBeInstalledPkgList:
55            for Package in Dist.PackageSurfaceArea:
56                RtnList.append((Package[0], Package[1]))
57
58        return RtnList
59
60    ## Check whether a module exists by checking the Guid+Version+Name+Path combination
61    #
62    # @param Guid:  Guid of a module
63    # @param Version: Version of a module
64    # @param Name: Name of a module
65    # @param Path: Path of a module
66    # @return:  True if module existed, else False
67    #
68    def CheckModuleExists(self, Guid, Version, Name, Path):
69        Logger.Verbose(ST.MSG_CHECK_MODULE_EXIST)
70        ModuleList = self.IpiDb.GetModInPackage(Guid, Version, Name, Path)
71        ModuleList.extend(self.IpiDb.GetStandaloneModule(Guid, Version, Name, Path))
72        Logger.Verbose(ST.MSG_CHECK_MODULE_EXIST_FINISH)
73        if len(ModuleList) > 0:
74            return True
75        else:
76            return False
77
78    ## Check whether a module depex satisfied.
79    #
80    # @param ModuleObj: A module object
81    # @param DpObj: A distribution object
82    # @return: True if module depex satisfied
83    #          False else
84    #
85    def CheckModuleDepexSatisfied(self, ModuleObj, DpObj=None):
86        Logger.Verbose(ST.MSG_CHECK_MODULE_DEPEX_START)
87        Result = True
88        Dep = None
89        if ModuleObj.GetPackageDependencyList():
90            Dep = ModuleObj.GetPackageDependencyList()[0]
91        for Dep in ModuleObj.GetPackageDependencyList():
92            #
93            # first check whether the dependency satisfied by current workspace
94            #
95            Exist = self.CheckPackageExists(Dep.GetGuid(), Dep.GetVersion())
96            #
97            # check whether satisfied by current distribution
98            #
99            if not Exist:
100                if DpObj is None:
101                    Result = False
102                    break
103                for GuidVerPair in DpObj.PackageSurfaceArea.keys():
104                    if Dep.GetGuid() == GuidVerPair[0]:
105                        if Dep.GetVersion() is None or \
106                        len(Dep.GetVersion()) == 0:
107                            Result = True
108                            break
109                        if Dep.GetVersion() == GuidVerPair[1]:
110                            Result = True
111                            break
112                else:
113                    Result = False
114                    break
115
116        if not Result:
117            Logger.Error("CheckModuleDepex", UNKNOWN_ERROR, \
118                         ST.ERR_DEPENDENCY_NOT_MATCH % (ModuleObj.GetName(), \
119                                                        Dep.GetPackageFilePath(), \
120                                                        Dep.GetGuid(), \
121                                                        Dep.GetVersion()))
122        return Result
123
124    ## Check whether a package exists in a package list specified by PkgsToBeDepend.
125    #
126    # @param Guid: Guid of a package
127    # @param Version: Version of a package
128    # @return: True if package exist
129    #          False else
130    #
131    def CheckPackageExists(self, Guid, Version):
132        Logger.Verbose(ST.MSG_CHECK_PACKAGE_START)
133        Found = False
134        for (PkgGuid, PkgVer) in self.PkgsToBeDepend:
135            if (PkgGuid == Guid):
136                #
137                # if version is not empty and not equal, then not match
138                #
139                if Version and (PkgVer != Version):
140                    Found = False
141                    break
142                else:
143                    Found = True
144                    break
145        else:
146            Found = False
147
148        Logger.Verbose(ST.MSG_CHECK_PACKAGE_FINISH)
149        return Found
150
151    ## Check whether a package depex satisfied.
152    #
153    # @param PkgObj: A package object
154    # @param DpObj: A distribution object
155    # @return: True if package depex satisfied
156    #          False else
157    #
158    def CheckPackageDepexSatisfied(self, PkgObj, DpObj=None):
159        ModuleDict = PkgObj.GetModuleDict()
160        for ModKey in ModuleDict.keys():
161            ModObj = ModuleDict[ModKey]
162            if self.CheckModuleDepexSatisfied(ModObj, DpObj):
163                continue
164            else:
165                return False
166        return True
167
168    ## Check whether a DP exists.
169    #
170    # @param Guid: Guid of a Distribution
171    # @param Version: Version of a Distribution
172    # @return: True if Distribution exist
173    #          False else
174    def CheckDpExists(self, Guid, Version):
175        Logger.Verbose(ST.MSG_CHECK_DP_START)
176        DpList = self.IpiDb.GetDp(Guid, Version)
177        if len(DpList) > 0:
178            Found = True
179        else:
180            Found = False
181
182        Logger.Verbose(ST.MSG_CHECK_DP_FINISH)
183        return Found
184
185    ## Check whether a DP depex satisfied by current workspace for Install
186    #
187    # @param DpObj:  A distribution object
188    # @return: True if distribution depex satisfied
189    #          False else
190    #
191    def CheckInstallDpDepexSatisfied(self, DpObj):
192        return self.CheckDpDepexSatisfied(DpObj)
193
194    # # Check whether multiple DP depex satisfied by current workspace for Install
195    #
196    # @param DpObjList:  A distribution object list
197    # @return: True if distribution depex satisfied
198    #          False else
199    #
200    def CheckTestInstallPdDepexSatisfied(self, DpObjList):
201        for DpObj in DpObjList:
202            if self.CheckDpDepexSatisfied(DpObj):
203                for PkgKey in DpObj.PackageSurfaceArea.keys():
204                    PkgObj = DpObj.PackageSurfaceArea[PkgKey]
205                    self.PkgsToBeDepend.append((PkgObj.Guid, PkgObj.Version))
206            else:
207                return False, DpObj
208
209        return True, DpObj
210
211
212    ## Check whether a DP depex satisfied by current workspace
213    #  (excluding the original distribution's packages to be replaced) for Replace
214    #
215    # @param DpObj:  A distribution object
216    # @param OrigDpGuid: The original distribution's Guid
217    # @param OrigDpVersion: The original distribution's Version
218    #
219    def ReplaceCheckNewDpDepex(self, DpObj, OrigDpGuid, OrigDpVersion):
220        self.PkgsToBeDepend = [(PkgInfo[1], PkgInfo[2]) for PkgInfo in self.WsPkgList]
221        OrigDpPackageList = self.IpiDb.GetPackageListFromDp(OrigDpGuid, OrigDpVersion)
222        for OrigPkgInfo in OrigDpPackageList:
223            Guid, Version = OrigPkgInfo[0], OrigPkgInfo[1]
224            if (Guid, Version) in self.PkgsToBeDepend:
225                self.PkgsToBeDepend.remove((Guid, Version))
226        return self.CheckDpDepexSatisfied(DpObj)
227
228    ## Check whether a DP depex satisfied by current workspace.
229    #
230    # @param DpObj:  A distribution object
231    #
232    def CheckDpDepexSatisfied(self, DpObj):
233        for PkgKey in DpObj.PackageSurfaceArea.keys():
234            PkgObj = DpObj.PackageSurfaceArea[PkgKey]
235            if self.CheckPackageDepexSatisfied(PkgObj, DpObj):
236                continue
237            else:
238                return False
239
240        for ModKey in DpObj.ModuleSurfaceArea.keys():
241            ModObj = DpObj.ModuleSurfaceArea[ModKey]
242            if self.CheckModuleDepexSatisfied(ModObj, DpObj):
243                continue
244            else:
245                return False
246
247        return True
248
249    ## Check whether a DP could be removed from current workspace.
250    #
251    # @param DpGuid:  File's guid
252    # @param DpVersion: File's version
253    # @retval Removable: True if distribution could be removed, False Else
254    # @retval DependModuleList: the list of modules that make distribution can not be removed
255    #
256    def CheckDpDepexForRemove(self, DpGuid, DpVersion):
257        Removable = True
258        DependModuleList = []
259        WsModuleList = self.WsModuleList
260        #
261        # remove modules that included in current DP
262        # List of item (FilePath)
263        DpModuleList = self.IpiDb.GetDpModuleList(DpGuid, DpVersion)
264        for Module in DpModuleList:
265            if Module in WsModuleList:
266                WsModuleList.remove(Module)
267            else:
268                Logger.Warn("UPT\n",
269                            ST.ERR_MODULE_NOT_INSTALLED % Module)
270        #
271        # get packages in current Dp and find the install path
272        # List of item (PkgGuid, PkgVersion, InstallPath)
273        DpPackageList = self.IpiDb.GetPackageListFromDp(DpGuid, DpVersion)
274        DpPackagePathList = []
275        WorkSP = GlobalData.gWORKSPACE
276        for (PkgName, PkgGuid, PkgVersion, DecFile) in self.WsPkgList:
277            if PkgName:
278                pass
279            DecPath = dirname(DecFile)
280            if DecPath.find(WorkSP) > -1:
281                InstallPath = GetRelativePath(DecPath, WorkSP)
282                DecFileRelaPath = GetRelativePath(DecFile, WorkSP)
283            else:
284                InstallPath = DecPath
285                DecFileRelaPath = DecFile
286
287            if (PkgGuid, PkgVersion, InstallPath) in DpPackageList:
288                DpPackagePathList.append(DecFileRelaPath)
289                DpPackageList.remove((PkgGuid, PkgVersion, InstallPath))
290
291        #
292        # the left items in DpPackageList are the packages that installed but not found anymore
293        #
294        for (PkgGuid, PkgVersion, InstallPath) in DpPackageList:
295            Logger.Warn("UPT",
296                        ST.WARN_INSTALLED_PACKAGE_NOT_FOUND%(PkgGuid, PkgVersion, InstallPath))
297
298        #
299        # check modules to see if has dependency on package of current DP
300        #
301        for Module in WsModuleList:
302            if (not VerifyRemoveModuleDep(Module, DpPackagePathList)):
303                Removable = False
304                DependModuleList.append(Module)
305        return (Removable, DependModuleList)
306
307
308    ## Check whether a DP could be replaced by a distribution containing NewDpPkgList
309    # from current workspace.
310    #
311    # @param OrigDpGuid:  original Dp's Guid
312    # @param OrigDpVersion: original Dp's version
313    # @param NewDpPkgList: a list of package information (Guid, Version) in new Dp
314    # @retval Replaceable: True if distribution could be replaced, False Else
315    # @retval DependModuleList: the list of modules that make distribution can not be replaced
316    #
317    def CheckDpDepexForReplace(self, OrigDpGuid, OrigDpVersion, NewDpPkgList):
318        Replaceable = True
319        DependModuleList = []
320        WsModuleList = self.WsModuleList
321        #
322        # remove modules that included in current DP
323        # List of item (FilePath)
324        DpModuleList = self.IpiDb.GetDpModuleList(OrigDpGuid, OrigDpVersion)
325        for Module in DpModuleList:
326            if Module in WsModuleList:
327                WsModuleList.remove(Module)
328            else:
329                Logger.Warn("UPT\n",
330                            ST.ERR_MODULE_NOT_INSTALLED % Module)
331
332        OtherPkgList = NewDpPkgList
333        #
334        # get packages in current Dp and find the install path
335        # List of item (PkgGuid, PkgVersion, InstallPath)
336        DpPackageList = self.IpiDb.GetPackageListFromDp(OrigDpGuid, OrigDpVersion)
337        DpPackagePathList = []
338        WorkSP = GlobalData.gWORKSPACE
339        for (PkgName, PkgGuid, PkgVersion, DecFile) in self.WsPkgList:
340            if PkgName:
341                pass
342            DecPath = dirname(DecFile)
343            if DecPath.find(WorkSP) > -1:
344                InstallPath = GetRelativePath(DecPath, WorkSP)
345                DecFileRelaPath = GetRelativePath(DecFile, WorkSP)
346            else:
347                InstallPath = DecPath
348                DecFileRelaPath = DecFile
349
350            if (PkgGuid, PkgVersion, InstallPath) in DpPackageList:
351                DpPackagePathList.append(DecFileRelaPath)
352                DpPackageList.remove((PkgGuid, PkgVersion, InstallPath))
353            else:
354                OtherPkgList.append((PkgGuid, PkgVersion))
355
356        #
357        # the left items in DpPackageList are the packages that installed but not found anymore
358        #
359        for (PkgGuid, PkgVersion, InstallPath) in DpPackageList:
360            Logger.Warn("UPT",
361                        ST.WARN_INSTALLED_PACKAGE_NOT_FOUND%(PkgGuid, PkgVersion, InstallPath))
362
363        #
364        # check modules to see if it can be satisfied by package not belong to removed DP
365        #
366        for Module in WsModuleList:
367            if (not VerifyReplaceModuleDep(Module, DpPackagePathList, OtherPkgList)):
368                Replaceable = False
369                DependModuleList.append(Module)
370        return (Replaceable, DependModuleList)
371
372
373## check whether module depends on packages in DpPackagePathList, return True
374# if found, False else
375#
376# @param Path: a module path
377# @param DpPackagePathList: a list of Package Paths
378# @retval:  False: module depends on package in DpPackagePathList
379#           True:  module doesn't depend on package in DpPackagePathList
380#
381def VerifyRemoveModuleDep(Path, DpPackagePathList):
382    try:
383        for Item in GetPackagePath(Path):
384            if Item in DpPackagePathList:
385                DecPath = os.path.normpath(os.path.join(GlobalData.gWORKSPACE, Item))
386                Logger.Info(ST.MSG_MODULE_DEPEND_ON % (Path, DecPath))
387                return False
388        else:
389            return True
390    except FatalError as ErrCode:
391        if ErrCode.message == EDK1_INF_ERROR:
392            Logger.Warn("UPT",
393                        ST.WRN_EDK1_INF_FOUND%Path)
394            return True
395        else:
396            return True
397
398# # GetPackagePath
399#
400# Get Dependency package path from an Inf file path
401#
402def GetPackagePath(InfPath):
403    PackagePath = []
404    if os.path.exists(InfPath):
405        FindSection = False
406        for Line in open(InfPath).readlines():
407            Line = Line.strip()
408            if not Line:
409                continue
410            if Line.startswith('#'):
411                continue
412            if Line.startswith('[Packages') and Line.endswith(']'):
413                FindSection = True
414                continue
415            if Line.startswith('[') and Line.endswith(']') and FindSection:
416                break
417            if FindSection:
418                PackagePath.append(os.path.normpath(Line))
419
420    return PackagePath
421
422## check whether module depends on packages in DpPackagePathList and can not be satisfied by OtherPkgList
423#
424# @param Path: a module path
425# @param DpPackagePathList:  a list of Package Paths
426# @param OtherPkgList:       a list of Package Information (Guid, Version)
427# @retval:  False: module depends on package in DpPackagePathList and can not be satisfied by OtherPkgList
428#           True:  either module doesn't depend on DpPackagePathList or module depends on DpPackagePathList
429#                 but can be satisfied by OtherPkgList
430#
431def VerifyReplaceModuleDep(Path, DpPackagePathList, OtherPkgList):
432    try:
433        for Item in GetPackagePath(Path):
434            if Item in DpPackagePathList:
435                DecPath = os.path.normpath(os.path.join(GlobalData.gWORKSPACE, Item))
436                Name, Guid, Version = GetPkgInfoFromDec(DecPath)
437                if (Guid, Version) not in OtherPkgList:
438                    Logger.Info(ST.MSG_MODULE_DEPEND_ON % (Path, DecPath))
439                    return False
440        else:
441            return True
442    except FatalError as ErrCode:
443        if ErrCode.message == EDK1_INF_ERROR:
444            Logger.Warn("UPT",
445                        ST.WRN_EDK1_INF_FOUND%Path)
446            return True
447        else:
448            return True
449