| # @file DscCompleteCheck.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.path_utilities import Edk2Path | |
| from edk2toollib.uefi.edk2.parsers.dsc_parser import DscParser | |
| from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser | |
| from edk2toolext.environment.var_dict import VarDict | |
| from edk2toollib.gitignore_parser import parse_gitignore_lines | |
| from pathlib import Path | |
| class DscCompleteCheck(ICiBuildPlugin): | |
| """ | |
| A CiBuildPlugin that scans the package dsc file and confirms all modules (inf files) are | |
| listed in the components sections. | |
| Configuration options: | |
| "DscCompleteCheck": { | |
| "DscPath": "<path to dsc from root of pkg>" | |
| "IgnoreInf": [] # Ignore INF if found in filesystem by not dsc | |
| } | |
| """ | |
| def GetTestName(self, packagename: str, environment: VarDict) -> tuple: | |
| """ Provide the testcase name and classname for use in reporting | |
| 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) | |
| testclassname: a descriptive string for the testcase can include whitespace | |
| classname: should be patterned <packagename>.<plugin>.<optionally any unique condition> | |
| """ | |
| return ("Check the " + packagename + " DSC for a being complete", packagename + ".DscCompleteCheck") | |
| ## | |
| # 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 | |
| # - VarDict containing the shell environment Build Vars | |
| # - 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 | |
| # Parse the config for required DscPath element | |
| if "DscPath" not in pkgconfig: | |
| tc.SetSkipped() | |
| tc.LogStdError( | |
| "DscPath not found in config file. Nothing to check.") | |
| return -1 | |
| abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath( | |
| packagename) | |
| abs_dsc_path = os.path.join(abs_pkg_path, pkgconfig["DscPath"].strip()) | |
| wsr_dsc_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath( | |
| abs_dsc_path) | |
| if abs_dsc_path is None or wsr_dsc_path == "" or not os.path.isfile(abs_dsc_path): | |
| tc.SetSkipped() | |
| tc.LogStdError("Package Dsc not found") | |
| return 0 | |
| # Get INF Files | |
| INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path) | |
| # remove ignores | |
| ignored_paths = [] | |
| if "IgnoreInf" in pkgconfig: | |
| ignore_filter = parse_gitignore_lines( | |
| pkgconfig["IgnoreInf"], | |
| "DSC Complete Check Config", | |
| os.path.dirname(abs_pkg_path)) | |
| # INFFiles must be a list of absolute paths | |
| ignored_paths = list(filter(ignore_filter, INFFiles)) | |
| for a in ignored_paths: | |
| try: | |
| tc.LogStdOut("Ignoring INF {0}".format(a)) | |
| INFFiles.remove(a) | |
| except Exception: | |
| tc.LogStdError( | |
| "DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) | |
| logging.info( | |
| "DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a)) | |
| # make edk2relative path so can compare with DSC | |
| INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in INFFiles] | |
| # DSC Parser | |
| dp = DscParser().SetEdk2Path(Edk2pathObj) | |
| dp.SetInputVars(environment.GetAllBuildKeyValues()) | |
| dp.ParseFile(wsr_dsc_path) | |
| # Check if INF in component section | |
| for INF in INFFiles: | |
| if not DscCompleteCheck._module_in_dsc(INF, dp, Edk2pathObj): | |
| infp = InfParser().SetEdk2Path(Edk2pathObj) | |
| infp.ParseFile(INF) | |
| if("MODULE_TYPE" not in infp.Dict): | |
| tc.LogStdOut( | |
| "Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF)) | |
| continue | |
| if(infp.Dict["MODULE_TYPE"] == "HOST_APPLICATION"): | |
| tc.LogStdOut( | |
| "Ignoring INF. Module type is HOST_APPLICATION {0}".format(INF)) | |
| continue | |
| if len(infp.SupportedPhases) == 1 and \ | |
| "HOST_APPLICATION" in infp.SupportedPhases: | |
| tc.LogStdOut( | |
| "Ignoring Library INF due to only supporting type HOST_APPLICATION {0}".format(INF)) | |
| continue | |
| logging.critical(INF + " not in " + wsr_dsc_path) | |
| tc.LogStdError("{0} not in {1}".format(INF, wsr_dsc_path)) | |
| overall_status = overall_status + 1 | |
| # If XML object exists, add result | |
| if overall_status != 0: | |
| tc.SetFailed("DscCompleteCheck {0} Failed. Errors {1}".format( | |
| wsr_dsc_path, overall_status), "CHECK_FAILED") | |
| else: | |
| tc.SetSuccess() | |
| return overall_status | |
| @staticmethod | |
| def _module_in_dsc(inf: str, dsc: DscParser, Edk2pathObj: Edk2Path) -> bool: | |
| """Checks if the given module (inf) is in the given dsc. | |
| Args: | |
| inf (str): The inf file to check for | |
| dsc (DscParser): The parsed dsc file. | |
| Edk2pathObj (Edk2Path): The path object capturing the workspace and package paths. | |
| Returns: | |
| bool: if the module is in the dsc. | |
| """ | |
| for module_type in (dsc.ThreeMods, dsc.SixMods, dsc.OtherMods): | |
| for module in module_type: | |
| if Path(module).is_absolute(): | |
| module = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(module) | |
| if inf in module: | |
| return True | |
| return False |