# @file LibraryClassCheck.py | |
# | |
# Copyright (c) Microsoft Corporation. | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
## | |
import logging | |
import os | |
from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin | |
from edk2toollib.uefi.edk2.parsers.dec_parser import DecParser | |
from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser | |
from edk2toolext.environment.var_dict import VarDict | |
class LibraryClassCheck(ICiBuildPlugin): | |
""" | |
A CiBuildPlugin that scans the code tree and library classes for undeclared | |
files | |
Configuration options: | |
"LibraryClassCheck": { | |
IgnoreHeaderFile: [], # Ignore a file found on disk | |
IgnoreLibraryClass: [] # Ignore a declaration found in dec file | |
} | |
""" | |
def GetTestName(self, packagename: str, environment: VarDict) -> tuple: | |
""" Provide the testcase name and classname for use in reporting | |
testclassname: a descriptive string for the testcase can include whitespace | |
classname: should be patterned <packagename>.<plugin>.<optionally any unique condition> | |
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) | |
""" | |
return ("Check library class declarations in " + packagename, packagename + ".LibraryClassCheck") | |
def __GetPkgDec(self, rootpath): | |
try: | |
allEntries = os.listdir(rootpath) | |
for entry in allEntries: | |
if entry.lower().endswith(".dec"): | |
return(os.path.join(rootpath, entry)) | |
except Exception: | |
logging.error("Unable to find DEC for package:{0}".format(rootpath)) | |
return None | |
## | |
# 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): | |
overall_status = 0 | |
LibraryClassIgnore = [] | |
abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(packagename) | |
abs_dec_path = self.__GetPkgDec(abs_pkg_path) | |
wsr_dec_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(abs_dec_path) | |
if abs_dec_path is None or wsr_dec_path == "" or not os.path.isfile(abs_dec_path): | |
tc.SetSkipped() | |
tc.LogStdError("No DEC file {0} in package {1}".format(abs_dec_path, abs_pkg_path)) | |
return -1 | |
# Get all include folders | |
dec = DecParser() | |
dec.SetBaseAbsPath(Edk2pathObj.WorkspacePath).SetPackagePaths(Edk2pathObj.PackagePathList) | |
dec.ParseFile(wsr_dec_path) | |
AllHeaderFiles = [] | |
for includepath in dec.IncludePaths: | |
## Get all header files in the library folder | |
AbsLibraryIncludePath = os.path.join(abs_pkg_path, includepath, "Library") | |
if(not os.path.isdir(AbsLibraryIncludePath)): | |
continue | |
hfiles = self.WalkDirectoryForExtension([".h"], AbsLibraryIncludePath) | |
hfiles = [os.path.relpath(x,abs_pkg_path) for x in hfiles] # make package root relative path | |
hfiles = [x.replace("\\", "/") for x in hfiles] # make package relative path | |
AllHeaderFiles.extend(hfiles) | |
if len(AllHeaderFiles) == 0: | |
tc.SetSkipped() | |
tc.LogStdError(f"No Library include folder in any Include path") | |
return -1 | |
# Remove ignored paths | |
if "IgnoreHeaderFile" in pkgconfig: | |
for a in pkgconfig["IgnoreHeaderFile"]: | |
try: | |
tc.LogStdOut("Ignoring Library Header File {0}".format(a)) | |
AllHeaderFiles.remove(a) | |
except: | |
tc.LogStdError("LibraryClassCheck.IgnoreHeaderFile -> {0} not found. Invalid Header File".format(a)) | |
logging.info("LibraryClassCheck.IgnoreHeaderFile -> {0} not found. Invalid Header File".format(a)) | |
if "IgnoreLibraryClass" in pkgconfig: | |
LibraryClassIgnore = pkgconfig["IgnoreLibraryClass"] | |
## Attempt to find library classes | |
for lcd in dec.LibraryClasses: | |
## Check for correct file path separator | |
if "\\" in lcd.path: | |
tc.LogStdError("LibraryClassCheck.DecFilePathSeparator -> {0} invalid.".format(lcd.path)) | |
logging.error("LibraryClassCheck.DecFilePathSeparator -> {0} invalid.".format(lcd.path)) | |
overall_status += 1 | |
continue | |
if lcd.name in LibraryClassIgnore: | |
tc.LogStdOut("Ignoring Library Class Name {0}".format(lcd.name)) | |
LibraryClassIgnore.remove(lcd.name) | |
continue | |
logging.debug(f"Looking for Library Class {lcd.path}") | |
try: | |
AllHeaderFiles.remove(lcd.path) | |
except ValueError: | |
tc.LogStdError(f"Library {lcd.name} with path {lcd.path} not found in package filesystem") | |
logging.error(f"Library {lcd.name} with path {lcd.path} not found in package filesystem") | |
overall_status += 1 | |
## any remaining AllHeaderFiles are not described in DEC | |
for h in AllHeaderFiles: | |
tc.LogStdError(f"Library Header File {h} not declared in package DEC {wsr_dec_path}") | |
logging.error(f"Library Header File {h} not declared in package DEC {wsr_dec_path}") | |
overall_status += 1 | |
## Warn about any invalid library class names in the ignore list | |
for r in LibraryClassIgnore: | |
tc.LogStdError("LibraryClassCheck.IgnoreLibraryClass -> {0} not found. Library Class not found".format(r)) | |
logging.info("LibraryClassCheck.IgnoreLibraryClass -> {0} not found. Library Class not found".format(r)) | |
# If XML object exists, add result | |
if overall_status != 0: | |
tc.SetFailed("LibraryClassCheck {0} Failed. Errors {1}".format(wsr_dec_path, overall_status), "CHECK_FAILED") | |
else: | |
tc.SetSuccess() | |
return overall_status |