summaryrefslogtreecommitdiff
path: root/.pytool/Plugin/LibraryClassCheck/LibraryClassCheck.py
blob: a62a7e912b15546e5de39f76b405ab66bfb9a143 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# @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.GetAbsolutePathOnThisSytemFromEdk2RelativePath(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 is not 0:
            tc.SetFailed("LibraryClassCheck {0} Failed.  Errors {1}".format(wsr_dec_path, overall_status), "CHECK_FAILED")
        else:
            tc.SetSuccess()
        return overall_status