hc
2024-08-12 233ab1bd4c5697f5cdec94e60206e8c6ac609b4c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# @file GuidCheck.py
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
import logging
from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
from edk2toollib.uefi.edk2.guid_list import GuidList
from edk2toolext.environment.var_dict import VarDict
 
 
class GuidCheck(ICiBuildPlugin):
    """
    A CiBuildPlugin that scans the code tree and looks for duplicate guids
    from the package being tested.
 
    Configuration options:
    "GuidCheck": {
        "IgnoreGuidName": [], # provide in format guidname=guidvalue or just guidname
        "IgnoreGuidValue": [],
        "IgnoreFoldersAndFiles": [],
        "IgnoreDuplicates": [] # Provide in format guidname=guidname=guidname...
    }
    """
 
    def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
        """ Provide the testcase name and classname for use in reporting
 
            Args:
              packagename: string containing name of package to build
              environment: The VarDict for the test to run in
            Returns:
                a tuple containing the testcase name and the classname
                (testcasename, classname)
                testclassname: a descriptive string for the testcase can include whitespace
                classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
        """
        return ("Confirm GUIDs are unique in " + packagename, packagename + ".GuidCheck")
 
    def _FindConflictingGuidValues(self, guidlist: list) -> list:
        """ Find all duplicate guids by guid value and report them as errors
        """
        # Sort the list by guid
        guidsorted = sorted(
            guidlist, key=lambda x: x.guid.upper(), reverse=True)
 
        previous = None  # Store previous entry for comparison
        error = None
        errors = []
        for index in range(len(guidsorted)):
            i = guidsorted[index]
            if(previous is not None):
                if i.guid == previous.guid:  # Error
                    if(error is None):
                        # Catch errors with more than 1 conflict
                        error = ErrorEntry("guid")
                        error.entries.append(previous)
                        errors.append(error)
                    error.entries.append(i)
                else:
                    # no match.  clear error
                    error = None
            previous = i
        return errors
 
    def _FindConflictingGuidNames(self, guidlist: list) -> list:
        """ Find all duplicate guids by name and if they are not all
        from inf files report them as errors.  It is ok to have
        BASE_NAME duplication.
 
        Is this useful?  It would catch two same named guids in dec file
        that resolve to different values.
        """
        # Sort the list by guid
        namesorted = sorted(guidlist, key=lambda x: x.name.upper())
 
        previous = None  # Store previous entry for comparison
        error = None
        errors = []
        for index in range(len(namesorted)):
            i = namesorted[index]
            if(previous is not None):
                # If name matches
                if i.name == previous.name:
                    if(error is None):
                        # Catch errors with more than 1 conflict
                        error = ErrorEntry("name")
                        error.entries.append(previous)
                        errors.append(error)
                    error.entries.append(i)
                else:
                    # no match.  clear error
                    error = None
            previous = i
 
            # Loop thru and remove any errors where all files are infs as it is ok if
            # they have the same inf base name.
            for e in errors[:]:
                if len( [en for en in e.entries if not en.absfilepath.lower().endswith(".inf")]) == 0:
                    errors.remove(e)
 
        return errors
 
    ##
    # External function of plugin.  This function is used to perform the task of the MuBuild Plugin
    #
    #   - package is the edk2 path to package.  This means workspace/packagepath relative.
    #   - edk2path object configured with workspace and packages path
    #   - PkgConfig Object (dict) for the pkg
    #   - EnvConfig Object
    #   - Plugin Manager Instance
    #   - Plugin Helper Obj Instance
    #   - Junit Logger
    #   - output_stream the StringIO output stream from this plugin via logging
 
    def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
        Errors = []
 
        abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
            packagename)
 
        if abs_pkg_path is None:
            tc.SetSkipped()
            tc.LogStdError("No package {0}".format(packagename))
            return -1
 
        All_Ignores = ["/Build", "/Conf"]
        # Parse the config for other ignores
        if "IgnoreFoldersAndFiles" in pkgconfig:
            All_Ignores.extend(pkgconfig["IgnoreFoldersAndFiles"])
 
        # Parse the workspace for all GUIDs
        gs = GuidList.guidlist_from_filesystem(
            Edk2pathObj.WorkspacePath, ignore_lines=All_Ignores)
 
        # Remove ignored guidvalue
        if "IgnoreGuidValue" in pkgconfig:
            for a in pkgconfig["IgnoreGuidValue"]:
                try:
                    tc.LogStdOut("Ignoring Guid {0}".format(a.upper()))
                    for b in gs[:]:
                        if b.guid == a.upper():
                            gs.remove(b)
                except:
                    tc.LogStdError("GuidCheck.IgnoreGuid -> {0} not found.  Invalid ignore guid".format(a.upper()))
                    logging.info("GuidCheck.IgnoreGuid -> {0} not found.  Invalid ignore guid".format(a.upper()))
 
        # Remove ignored guidname
        if "IgnoreGuidName" in pkgconfig:
            for a in pkgconfig["IgnoreGuidName"]:
                entry = a.split("=")
                if(len(entry) > 2):
                    tc.LogStdError("GuidCheck.IgnoreGuidName -> {0} Invalid Format.".format(a))
                    logging.info("GuidCheck.IgnoreGuidName -> {0} Invalid Format.".format(a))
                    continue
                try:
                    tc.LogStdOut("Ignoring Guid {0}".format(a))
                    for b in gs[:]:
                        if b.name == entry[0]:
                            if(len(entry) == 1):
                                gs.remove(b)
                            elif(len(entry) == 2 and b.guid.upper() == entry[1].upper()):
                                gs.remove(b)
                            else:
                                c.LogStdError("GuidCheck.IgnoreGuidName -> {0} incomplete match.  Invalid ignore guid".format(a))
 
                except:
                    tc.LogStdError("GuidCheck.IgnoreGuidName -> {0} not found.  Invalid ignore name".format(a))
                    logging.info("GuidCheck.IgnoreGuidName -> {0} not found.  Invalid ignore name".format(a))
 
        # Find conflicting Guid Values
        Errors.extend(self._FindConflictingGuidValues(gs))
 
        # Check if there are expected duplicates and remove it from the error list
        if "IgnoreDuplicates" in pkgconfig:
            for a in pkgconfig["IgnoreDuplicates"]:
                names = a.split("=")
                if len(names) < 2:
                    tc.LogStdError("GuidCheck.IgnoreDuplicates -> {0} invalid format".format(a))
                    logging.info("GuidCheck.IgnoreDuplicates -> {0} invalid format".format(a))
                    continue
 
                for b in Errors[:]:
                    if b.type != "guid":
                        continue
                    ## Make a list of the names that are not in the names list.  If there
                    ## are any in the list then this error should not be ignored.
                    t = [x for x in b.entries if x.name not in names]
                    if(len(t) == len(b.entries)):
                        ## did not apply to any entry
                        continue
                    elif(len(t) == 0):
                        ## full match - ignore duplicate
                        tc.LogStdOut("GuidCheck.IgnoreDuplicates -> {0}".format(a))
                        Errors.remove(b)
                    elif(len(t) < len(b.entries)):
                        ## partial match
                        tc.LogStdOut("GuidCheck.IgnoreDuplicates -> {0} incomplete match".format(a))
                        logging.info("GuidCheck.IgnoreDuplicates -> {0} incomplete match".format(a))
                    else:
                        tc.LogStdOut("GuidCheck.IgnoreDuplicates -> {0} unknown error.".format(a))
                        logging.info("GuidCheck.IgnoreDuplicates -> {0} unknown error".format(a))
 
 
 
        # Find conflicting Guid Names
        Errors.extend(self._FindConflictingGuidNames(gs))
 
        # Log errors for anything within the package under test
        for er in Errors[:]:
            InMyPackage = False
            for a in er.entries:
                if abs_pkg_path in a.absfilepath:
                    InMyPackage = True
                    break
            if(not InMyPackage):
                Errors.remove(er)
            else:
                logging.error(str(er))
                tc.LogStdError(str(er))
 
        # add result to test case
        overall_status = len(Errors)
        if overall_status != 0:
            tc.SetFailed("GuidCheck {0} Failed.  Errors {1}".format(
                packagename, overall_status), "CHECK_FAILED")
        else:
            tc.SetSuccess()
        return overall_status
 
 
class ErrorEntry():
    """ Custom/private class for reporting errors in the GuidList
    """
 
    def __init__(self, errortype):
        self.type = errortype  # 'guid' or 'name' depending on error type
        self.entries = []  # GuidListEntry that are in error condition
 
    def __str__(self):
        a = f"Error Duplicate {self.type}: "
        if(self.type == "guid"):
            a += f" {self.entries[0].guid}"
        elif(self.type == "name"):
            a += f" {self.entries[0].name}"
 
        a += f" ({len(self.entries)})\n"
 
        for e in self.entries:
            a += "\t" + str(e) + "\n"
        return a