1 | # @file LibraryClassCheck.py
|
---|
2 | #
|
---|
3 | # Copyright (c) Microsoft Corporation.
|
---|
4 | # SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
5 | ##
|
---|
6 | import logging
|
---|
7 | import os
|
---|
8 | from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
|
---|
9 | from edk2toollib.uefi.edk2.parsers.dec_parser import DecParser
|
---|
10 | from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser
|
---|
11 | from edk2toolext.environment.var_dict import VarDict
|
---|
12 |
|
---|
13 |
|
---|
14 | class LibraryClassCheck(ICiBuildPlugin):
|
---|
15 | """
|
---|
16 | A CiBuildPlugin that scans the code tree and library classes for undeclared
|
---|
17 | files
|
---|
18 |
|
---|
19 | Configuration options:
|
---|
20 | "LibraryClassCheck": {
|
---|
21 | IgnoreHeaderFile: [], # Ignore a file found on disk
|
---|
22 | IgnoreLibraryClass: [] # Ignore a declaration found in dec file
|
---|
23 | }
|
---|
24 | """
|
---|
25 |
|
---|
26 | def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
|
---|
27 | """ Provide the testcase name and classname for use in reporting
|
---|
28 | testclassname: a descriptive string for the testcase can include whitespace
|
---|
29 | classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
|
---|
30 |
|
---|
31 | Args:
|
---|
32 | packagename: string containing name of package to build
|
---|
33 | environment: The VarDict for the test to run in
|
---|
34 | Returns:
|
---|
35 | a tuple containing the testcase name and the classname
|
---|
36 | (testcasename, classname)
|
---|
37 | """
|
---|
38 | return ("Check library class declarations in " + packagename, packagename + ".LibraryClassCheck")
|
---|
39 |
|
---|
40 | def __GetPkgDec(self, rootpath):
|
---|
41 | try:
|
---|
42 | allEntries = os.listdir(rootpath)
|
---|
43 | for entry in allEntries:
|
---|
44 | if entry.lower().endswith(".dec"):
|
---|
45 | return(os.path.join(rootpath, entry))
|
---|
46 | except Exception:
|
---|
47 | logging.error("Unable to find DEC for package:{0}".format(rootpath))
|
---|
48 |
|
---|
49 | return None
|
---|
50 |
|
---|
51 | ##
|
---|
52 | # External function of plugin. This function is used to perform the task of the MuBuild Plugin
|
---|
53 | #
|
---|
54 | # - package is the edk2 path to package. This means workspace/packagepath relative.
|
---|
55 | # - edk2path object configured with workspace and packages path
|
---|
56 | # - PkgConfig Object (dict) for the pkg
|
---|
57 | # - EnvConfig Object
|
---|
58 | # - Plugin Manager Instance
|
---|
59 | # - Plugin Helper Obj Instance
|
---|
60 | # - Junit Logger
|
---|
61 | # - output_stream the StringIO output stream from this plugin via logging
|
---|
62 | def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
|
---|
63 | overall_status = 0
|
---|
64 | LibraryClassIgnore = []
|
---|
65 |
|
---|
66 | abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename)
|
---|
67 | abs_dec_path = self.__GetPkgDec(abs_pkg_path)
|
---|
68 | wsr_dec_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(abs_dec_path)
|
---|
69 |
|
---|
70 | if abs_dec_path is None or wsr_dec_path == "" or not os.path.isfile(abs_dec_path):
|
---|
71 | tc.SetSkipped()
|
---|
72 | tc.LogStdError("No DEC file {0} in package {1}".format(abs_dec_path, abs_pkg_path))
|
---|
73 | return -1
|
---|
74 |
|
---|
75 | # Get all include folders
|
---|
76 | dec = DecParser()
|
---|
77 | dec.SetBaseAbsPath(Edk2pathObj.WorkspacePath).SetPackagePaths(Edk2pathObj.PackagePathList)
|
---|
78 | dec.ParseFile(wsr_dec_path)
|
---|
79 |
|
---|
80 | AllHeaderFiles = []
|
---|
81 |
|
---|
82 | for includepath in dec.IncludePaths:
|
---|
83 | ## Get all header files in the library folder
|
---|
84 | AbsLibraryIncludePath = os.path.join(abs_pkg_path, includepath, "Library")
|
---|
85 | if(not os.path.isdir(AbsLibraryIncludePath)):
|
---|
86 | continue
|
---|
87 |
|
---|
88 | hfiles = self.WalkDirectoryForExtension([".h"], AbsLibraryIncludePath)
|
---|
89 | hfiles = [os.path.relpath(x,abs_pkg_path) for x in hfiles] # make package root relative path
|
---|
90 | hfiles = [x.replace("\\", "/") for x in hfiles] # make package relative path
|
---|
91 |
|
---|
92 | AllHeaderFiles.extend(hfiles)
|
---|
93 |
|
---|
94 | if len(AllHeaderFiles) == 0:
|
---|
95 | tc.SetSkipped()
|
---|
96 | tc.LogStdError(f"No Library include folder in any Include path")
|
---|
97 | return -1
|
---|
98 |
|
---|
99 | # Remove ignored paths
|
---|
100 | if "IgnoreHeaderFile" in pkgconfig:
|
---|
101 | for a in pkgconfig["IgnoreHeaderFile"]:
|
---|
102 | try:
|
---|
103 | tc.LogStdOut("Ignoring Library Header File {0}".format(a))
|
---|
104 | AllHeaderFiles.remove(a)
|
---|
105 | except:
|
---|
106 | tc.LogStdError("LibraryClassCheck.IgnoreHeaderFile -> {0} not found. Invalid Header File".format(a))
|
---|
107 | logging.info("LibraryClassCheck.IgnoreHeaderFile -> {0} not found. Invalid Header File".format(a))
|
---|
108 |
|
---|
109 | if "IgnoreLibraryClass" in pkgconfig:
|
---|
110 | LibraryClassIgnore = pkgconfig["IgnoreLibraryClass"]
|
---|
111 |
|
---|
112 |
|
---|
113 | ## Attempt to find library classes
|
---|
114 | for lcd in dec.LibraryClasses:
|
---|
115 | ## Check for correct file path separator
|
---|
116 | if "\\" in lcd.path:
|
---|
117 | tc.LogStdError("LibraryClassCheck.DecFilePathSeparator -> {0} invalid.".format(lcd.path))
|
---|
118 | logging.error("LibraryClassCheck.DecFilePathSeparator -> {0} invalid.".format(lcd.path))
|
---|
119 | overall_status += 1
|
---|
120 | continue
|
---|
121 |
|
---|
122 | if lcd.name in LibraryClassIgnore:
|
---|
123 | tc.LogStdOut("Ignoring Library Class Name {0}".format(lcd.name))
|
---|
124 | LibraryClassIgnore.remove(lcd.name)
|
---|
125 | continue
|
---|
126 |
|
---|
127 | logging.debug(f"Looking for Library Class {lcd.path}")
|
---|
128 | try:
|
---|
129 | AllHeaderFiles.remove(lcd.path)
|
---|
130 |
|
---|
131 | except ValueError:
|
---|
132 | tc.LogStdError(f"Library {lcd.name} with path {lcd.path} not found in package filesystem")
|
---|
133 | logging.error(f"Library {lcd.name} with path {lcd.path} not found in package filesystem")
|
---|
134 | overall_status += 1
|
---|
135 |
|
---|
136 | ## any remaining AllHeaderFiles are not described in DEC
|
---|
137 | for h in AllHeaderFiles:
|
---|
138 | tc.LogStdError(f"Library Header File {h} not declared in package DEC {wsr_dec_path}")
|
---|
139 | logging.error(f"Library Header File {h} not declared in package DEC {wsr_dec_path}")
|
---|
140 | overall_status += 1
|
---|
141 |
|
---|
142 | ## Warn about any invalid library class names in the ignore list
|
---|
143 | for r in LibraryClassIgnore:
|
---|
144 | tc.LogStdError("LibraryClassCheck.IgnoreLibraryClass -> {0} not found. Library Class not found".format(r))
|
---|
145 | logging.info("LibraryClassCheck.IgnoreLibraryClass -> {0} not found. Library Class not found".format(r))
|
---|
146 |
|
---|
147 |
|
---|
148 | # If XML object exists, add result
|
---|
149 | if overall_status != 0:
|
---|
150 | tc.SetFailed("LibraryClassCheck {0} Failed. Errors {1}".format(wsr_dec_path, overall_status), "CHECK_FAILED")
|
---|
151 | else:
|
---|
152 | tc.SetSuccess()
|
---|
153 | return overall_status
|
---|