.pytool/Plugin: Add CI plugins
https://bugzilla.tianocore.org/show_bug.cgi?id=2315 Add .pytool directory to the edk2 repository with the following plugins. These plugins are in a top level directory because that can be used with all packages and platforms. * CharEncodingCheck * CompilerPlugin * DependencyCheck * DscCompleteCheck * GuidCheck * LibraryClassCheck * SpellCheck Cc: Sean Brogan <sean.brogan@microsoft.com> Cc: Bret Barkelew <Bret.Barkelew@microsoft.com> Cc: Liming Gao <liming.gao@intel.com> Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com> Reviewed-by: Liming Gao <liming.gao@intel.com>
This commit is contained in:
committed by
Michael D Kinney
parent
de4ce46d6e
commit
9da7846c88
251
.pytool/Plugin/GuidCheck/GuidCheck.py
Normal file
251
.pytool/Plugin/GuidCheck/GuidCheck.py
Normal file
@ -0,0 +1,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 is not 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
|
Reference in New Issue
Block a user