1# @file DscCompleteCheck.py
2#
3# Copyright (c) Microsoft Corporation.
4# SPDX-License-Identifier: BSD-2-Clause-Patent
5##
6import logging
7import os
8from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
9from edk2toollib.uefi.edk2.parsers.dsc_parser import DscParser
10from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser
11from edk2toolext.environment.var_dict import VarDict
12
13
14class DscCompleteCheck(ICiBuildPlugin):
15    """
16    A CiBuildPlugin that scans the package dsc file and confirms all modules (inf files) are
17    listed in the components sections.
18
19    Configuration options:
20    "DscCompleteCheck": {
21        "DscPath": "<path to dsc from root of pkg>"
22        "IgnoreInf": []  # Ignore INF if found in filesystem by not dsc
23    }
24    """
25
26    def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
27        """ Provide the testcase name and classname for use in reporting
28
29            Args:
30              packagename: string containing name of package to build
31              environment: The VarDict for the test to run in
32            Returns:
33                a tuple containing the testcase name and the classname
34                (testcasename, classname)
35                testclassname: a descriptive string for the testcase can include whitespace
36                classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
37        """
38        return ("Check the " + packagename + " DSC for a being complete", packagename + ".DscCompleteCheck")
39
40    ##
41    # External function of plugin.  This function is used to perform the task of the MuBuild Plugin
42    #
43    #   - package is the edk2 path to package.  This means workspace/packagepath relative.
44    #   - edk2path object configured with workspace and packages path
45    #   - PkgConfig Object (dict) for the pkg
46    #   - VarDict containing the shell environment Build Vars
47    #   - Plugin Manager Instance
48    #   - Plugin Helper Obj Instance
49    #   - Junit Logger
50    #   - output_stream the StringIO output stream from this plugin via logging
51    def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
52        overall_status = 0
53
54        # Parse the config for required DscPath element
55        if "DscPath" not in pkgconfig:
56            tc.SetSkipped()
57            tc.LogStdError(
58                "DscPath not found in config file.  Nothing to check.")
59            return -1
60
61        abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
62            packagename)
63        abs_dsc_path = os.path.join(abs_pkg_path, pkgconfig["DscPath"].strip())
64        wsr_dsc_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
65            abs_dsc_path)
66
67        if abs_dsc_path is None or wsr_dsc_path == "" or not os.path.isfile(abs_dsc_path):
68            tc.SetSkipped()
69            tc.LogStdError("Package Dsc not found")
70            return 0
71
72        # Get INF Files
73        INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path)
74        INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
75            x) for x in INFFiles]  # make edk2relative path so can compare with DSC
76
77        # remove ignores
78
79        if "IgnoreInf" in pkgconfig:
80            for a in pkgconfig["IgnoreInf"]:
81                a = a.replace(os.sep, "/")
82                try:
83                    tc.LogStdOut("Ignoring INF {0}".format(a))
84                    INFFiles.remove(a)
85                except:
86                    tc.LogStdError(
87                        "DscCompleteCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))
88                    logging.info(
89                        "DscCompleteCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))
90
91        # DSC Parser
92        dp = DscParser()
93        dp.SetBaseAbsPath(Edk2pathObj.WorkspacePath)
94        dp.SetPackagePaths(Edk2pathObj.PackagePathList)
95        dp.SetInputVars(environment.GetAllBuildKeyValues())
96        dp.ParseFile(wsr_dsc_path)
97
98        # Check if INF in component section
99        for INF in INFFiles:
100            if not any(INF.strip() in x for x in dp.ThreeMods) and \
101               not any(INF.strip() in x for x in dp.SixMods) and \
102               not any(INF.strip() in x for x in dp.OtherMods):
103
104                infp = InfParser().SetBaseAbsPath(Edk2pathObj.WorkspacePath)
105                infp.SetPackagePaths(Edk2pathObj.PackagePathList)
106                infp.ParseFile(INF)
107                if("MODULE_TYPE" not in infp.Dict):
108                    tc.LogStdOut(
109                        "Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF))
110                    continue
111
112                if(infp.Dict["MODULE_TYPE"] == "HOST_APPLICATION"):
113                    tc.LogStdOut(
114                        "Ignoring INF.  Module type is HOST_APPLICATION {0}".format(INF))
115                    continue
116
117                if len(infp.SupportedPhases) == 1 and \
118                   "HOST_APPLICATION" in infp.SupportedPhases:
119                    tc.LogStdOut(
120                        "Ignoring Library INF due to only supporting type HOST_APPLICATION {0}".format(INF))
121                    continue
122
123                logging.critical(INF + " not in " + wsr_dsc_path)
124                tc.LogStdError("{0} not in {1}".format(INF, wsr_dsc_path))
125                overall_status = overall_status + 1
126
127        # If XML object exists, add result
128        if overall_status != 0:
129            tc.SetFailed("DscCompleteCheck {0} Failed.  Errors {1}".format(
130                wsr_dsc_path, overall_status), "CHECK_FAILED")
131        else:
132            tc.SetSuccess()
133        return overall_status
134