1# @file dependency_check.py 2# 3# Copyright (c) Microsoft Corporation. 4# SPDX-License-Identifier: BSD-2-Clause-Patent 5## 6 7import logging 8import os 9from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin 10from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser 11from edk2toolext.environment.var_dict import VarDict 12 13 14class DependencyCheck(ICiBuildPlugin): 15 """ 16 A CiBuildPlugin that finds all modules (inf files) in a package and reviews the packages used 17 to confirm they are acceptable. This is to help enforce layering and identify improper 18 dependencies between packages. 19 20 Configuration options: 21 "DependencyCheck": { 22 "AcceptableDependencies": [], # Package dec files that are allowed in all INFs. Example: MdePkg/MdePkg.dec 23 "AcceptableDependencies-<MODULE_TYPE>": [], # OPTIONAL Package dependencies for INFs that are HOST_APPLICATION 24 "AcceptableDependencies-HOST_APPLICATION": [], # EXAMPLE Package dependencies for INFs that are HOST_APPLICATION 25 "IgnoreInf": [] # Ignore INF if found in filesystem 26 } 27 """ 28 29 def GetTestName(self, packagename: str, environment: VarDict) -> tuple: 30 """ Provide the testcase name and classname for use in reporting 31 32 Args: 33 packagename: string containing name of package to build 34 environment: The VarDict for the test to run in 35 Returns: 36 a tuple containing the testcase name and the classname 37 (testcasename, classname) 38 testclassname: a descriptive string for the testcase can include whitespace 39 classname: should be patterned <packagename>.<plugin>.<optionally any unique condition> 40 """ 41 return ("Test Package Dependencies for modules in " + packagename, packagename + ".DependencyCheck") 42 43 ## 44 # External function of plugin. This function is used to perform the task of the MuBuild Plugin 45 # 46 # - package is the edk2 path to package. This means workspace/packagepath relative. 47 # - edk2path object configured with workspace and packages path 48 # - PkgConfig Object (dict) for the pkg 49 # - EnvConfig Object 50 # - Plugin Manager Instance 51 # - Plugin Helper Obj Instance 52 # - Junit Logger 53 # - output_stream the StringIO output stream from this plugin via logging 54 def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None): 55 overall_status = 0 56 57 # Get current platform 58 abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename) 59 60 # Get INF Files 61 INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path) 62 INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in INFFiles] # make edk2relative path so can compare with Ignore List 63 64 # Remove ignored INFs 65 if "IgnoreInf" in pkgconfig: 66 for a in pkgconfig["IgnoreInf"]: 67 a = a.replace(os.sep, "/") ## convert path sep in case ignore list is bad. Can't change case 68 try: 69 INFFiles.remove(a) 70 tc.LogStdOut("IgnoreInf {0}".format(a)) 71 except: 72 logging.info("DependencyConfig.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) 73 tc.LogStdError("DependencyConfig.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) 74 75 76 # Get the AccpetableDependencies list 77 if "AcceptableDependencies" not in pkgconfig: 78 logging.info("DependencyCheck Skipped. No Acceptable Dependencies defined.") 79 tc.LogStdOut("DependencyCheck Skipped. No Acceptable Dependencies defined.") 80 tc.SetSkipped() 81 return -1 82 83 # Log dependencies 84 for k in pkgconfig.keys(): 85 if k.startswith("AcceptableDependencies"): 86 pkgstring = "\n".join(pkgconfig[k]) 87 if ("-" in k): 88 _, _, mod_type = k.partition("-") 89 tc.LogStdOut(f"Additional dependencies for MODULE_TYPE {mod_type}:\n {pkgstring}") 90 else: 91 tc.LogStdOut(f"Acceptable Dependencies:\n {pkgstring}") 92 93 # For each INF file 94 for file in INFFiles: 95 ip = InfParser() 96 logging.debug("Parsing " + file) 97 ip.SetBaseAbsPath(Edk2pathObj.WorkspacePath).SetPackagePaths(Edk2pathObj.PackagePathList).ParseFile(file) 98 99 if("MODULE_TYPE" not in ip.Dict): 100 tc.LogStdOut("Ignoring INF. Missing key for MODULE_TYPE {0}".format(file)) 101 continue 102 103 mod_type = ip.Dict["MODULE_TYPE"].upper() 104 for p in ip.PackagesUsed: 105 if p not in pkgconfig["AcceptableDependencies"]: 106 # If not in the main acceptable dependencies list then check module specific 107 mod_specific_key = "AcceptableDependencies-" + mod_type 108 if mod_specific_key in pkgconfig and p in pkgconfig[mod_specific_key]: 109 continue 110 111 logging.error("Dependency Check: Invalid Dependency INF: {0} depends on pkg {1}".format(file, p)) 112 tc.LogStdError("Dependency Check: Invalid Dependency INF: {0} depends on pkg {1}".format(file, p)) 113 overall_status += 1 114 115 # If XML object exists, add results 116 if overall_status != 0: 117 tc.SetFailed("Failed with {0} errors".format(overall_status), "DEPENDENCYCHECK_FAILED") 118 else: 119 tc.SetSuccess() 120 return overall_status 121