1 | # @file dependency_check.py
|
---|
2 | #
|
---|
3 | # Copyright (c) Microsoft Corporation.
|
---|
4 | # SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
5 | ##
|
---|
6 |
|
---|
7 | import logging
|
---|
8 | import os
|
---|
9 | from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
|
---|
10 | from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser
|
---|
11 | from edk2toolext.environment.var_dict import VarDict
|
---|
12 |
|
---|
13 |
|
---|
14 | class 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
|
---|