audk/.pytool/Plugin/DscCompleteCheck/DscCompleteCheck.py
Joey Vagedes 592725d229 DscCompleteCheck: Allow git ignore syntax
Allows ignore lines in the CI YAML file to use git ignore syntax.

This is especially useful for ignore files recursively in directories
like those that may exist in an external dependency folder.

Co-authored-by: Michael Kubacki <michael.kubacki@microsoft.com>
Signed-off-by: Joey Vagedes <joey.vagedes@gmail.com>
2024-07-04 07:40:58 +00:00

157 lines
6.6 KiB
Python

# @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