# @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": "" "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 .. """ 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