# @file dependency_check.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.inf_parser import InfParser from edk2toolext.environment.var_dict import VarDict class DependencyCheck(ICiBuildPlugin): """ A CiBuildPlugin that finds all modules (inf files) in a package and reviews the packages used to confirm they are acceptable. This is to help enforce layering and identify improper dependencies between packages. Configuration options: "DependencyCheck": { "AcceptableDependencies": [], # Package dec files that are allowed in all INFs. Example: MdePkg/MdePkg.dec "AcceptableDependencies-": [], # OPTIONAL Package dependencies for INFs that are HOST_APPLICATION "AcceptableDependencies-HOST_APPLICATION": [], # EXAMPLE Package dependencies for INFs that are HOST_APPLICATION "IgnoreInf": [] # Ignore INF if found in filesystem } """ 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 .. """ return ("Test Package Dependencies for modules in " + packagename, packagename + ".DependencyCheck") ## # 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 # Get current platform abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename) # Get INF Files INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path) INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in INFFiles] # make edk2relative path so can compare with Ignore List # Remove ignored INFs if "IgnoreInf" in pkgconfig: for a in pkgconfig["IgnoreInf"]: a = a.replace(os.sep, "/") ## convert path sep in case ignore list is bad. Can't change case try: INFFiles.remove(a) tc.LogStdOut("IgnoreInf {0}".format(a)) except: logging.info("DependencyConfig.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) tc.LogStdError("DependencyConfig.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) # Get the AccpetableDependencies list if "AcceptableDependencies" not in pkgconfig: logging.info("DependencyCheck Skipped. No Acceptable Dependencies defined.") tc.LogStdOut("DependencyCheck Skipped. No Acceptable Dependencies defined.") tc.SetSkipped() return -1 # Log dependencies for k in pkgconfig.keys(): if k.startswith("AcceptableDependencies"): pkgstring = "\n".join(pkgconfig[k]) if ("-" in k): _, _, mod_type = k.partition("-") tc.LogStdOut(f"Additional dependencies for MODULE_TYPE {mod_type}:\n {pkgstring}") else: tc.LogStdOut(f"Acceptable Dependencies:\n {pkgstring}") # For each INF file for file in INFFiles: ip = InfParser() logging.debug("Parsing " + file) ip.SetBaseAbsPath(Edk2pathObj.WorkspacePath).SetPackagePaths(Edk2pathObj.PackagePathList).ParseFile(file) if("MODULE_TYPE" not in ip.Dict): tc.LogStdOut("Ignoring INF. Missing key for MODULE_TYPE {0}".format(file)) continue mod_type = ip.Dict["MODULE_TYPE"].upper() for p in ip.PackagesUsed: if p not in pkgconfig["AcceptableDependencies"]: # If not in the main acceptable dependencies list then check module specific mod_specific_key = "AcceptableDependencies-" + mod_type if mod_specific_key in pkgconfig and p in pkgconfig[mod_specific_key]: continue logging.error("Dependency Check: Invalid Dependency INF: {0} depends on pkg {1}".format(file, p)) tc.LogStdError("Dependency Check: Invalid Dependency INF: {0} depends on pkg {1}".format(file, p)) overall_status += 1 # If XML object exists, add results if overall_status != 0: tc.SetFailed("Failed with {0} errors".format(overall_status), "DEPENDENCYCHECK_FAILED") else: tc.SetSuccess() return overall_status