# @file DebugMacroCheckBuildPlugin.py | |
# | |
# A build plugin that checks if DEBUG macros are formatted properly. | |
# | |
# In particular, that print format specifiers are defined | |
# with the expected number of arguments in the variable | |
# argument list. | |
# | |
# Copyright (c) Microsoft Corporation. All rights reserved. | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
## | |
import logging | |
import os | |
import pathlib | |
import sys | |
import yaml | |
# Import the build plugin | |
plugin_file = pathlib.Path(__file__) | |
sys.path.append(str(plugin_file.parent.parent)) | |
# flake8 (E402): Ignore flake8 module level import not at top of file | |
import DebugMacroCheck # noqa: E402 | |
from edk2toolext import edk2_logging # noqa: E402 | |
from edk2toolext.environment.plugintypes.uefi_build_plugin import \ | |
IUefiBuildPlugin # noqa: E402 | |
from edk2toolext.environment.uefi_build import UefiBuilder # noqa: E402 | |
from edk2toollib.uefi.edk2.path_utilities import Edk2Path # noqa: E402 | |
from pathlib import Path # noqa: E402 | |
class DebugMacroCheckBuildPlugin(IUefiBuildPlugin): | |
def do_pre_build(self, builder: UefiBuilder) -> int: | |
"""Debug Macro Check pre-build functionality. | |
The plugin is invoked in pre-build since it can operate independently | |
of build tools and to notify the user of any errors earlier in the | |
build process to reduce feedback time. | |
Args: | |
builder (UefiBuilder): A UEFI builder object for this build. | |
Returns: | |
int: The number of debug macro errors found. Zero indicates the | |
check either did not run or no errors were found. | |
""" | |
# Check if disabled in the environment | |
env_disable = builder.env.GetValue("DISABLE_DEBUG_MACRO_CHECK") | |
if env_disable: | |
return 0 | |
# Only run on targets with compilation | |
build_target = builder.env.GetValue("TARGET").lower() | |
if "no-target" in build_target: | |
return 0 | |
pp = builder.pp.split(os.pathsep) | |
edk2 = Edk2Path(builder.ws, pp) | |
package = edk2.GetContainingPackage( | |
builder.mws.join(builder.ws, | |
builder.env.GetValue( | |
"ACTIVE_PLATFORM"))) | |
package_path = Path( | |
edk2.GetAbsolutePathOnThisSystemFromEdk2RelativePath( | |
package)) | |
# Every debug macro is printed at DEBUG logging level. | |
# Ensure the level is above DEBUG while executing the macro check | |
# plugin to avoid flooding the log handler. | |
handler_level_context = [] | |
for h in logging.getLogger().handlers: | |
if h.level < logging.INFO: | |
handler_level_context.append((h, h.level)) | |
h.setLevel(logging.INFO) | |
edk2_logging.log_progress("Checking DEBUG Macros") | |
# There are two ways to specify macro substitution data for this | |
# plugin. If multiple options are present, data is appended from | |
# each option. | |
# | |
# 1. Specify the substitution data in the package CI YAML file. | |
# 2. Specify a standalone substitution data YAML file. | |
## | |
sub_data = {} | |
# 1. Allow substitution data to be specified in a "DebugMacroCheck" of | |
# the package CI YAML file. This is used to provide a familiar per- | |
# package customization flow for a package maintainer. | |
package_config_file = Path( | |
os.path.join( | |
package_path, package + ".ci.yaml")) | |
if package_config_file.is_file(): | |
with open(package_config_file, 'r') as cf: | |
package_config_file_data = yaml.safe_load(cf) | |
if "DebugMacroCheck" in package_config_file_data and \ | |
"StringSubstitutions" in \ | |
package_config_file_data["DebugMacroCheck"]: | |
logging.info(f"Loading substitution data in " | |
f"{str(package_config_file)}") | |
sub_data |= package_config_file_data["DebugMacroCheck"]["StringSubstitutions"] # noqa | |
# 2. Allow a substitution file to be specified as an environment | |
# variable. This is used to provide flexibility in how to specify a | |
# substitution file. The value can be set anywhere prior to this plugin | |
# getting called such as pre-existing build script. | |
sub_file = builder.env.GetValue("DEBUG_MACRO_CHECK_SUB_FILE") | |
if sub_file: | |
logging.info(f"Loading substitution file {sub_file}") | |
with open(sub_file, 'r') as sf: | |
sub_data |= yaml.safe_load(sf) | |
try: | |
error_count = DebugMacroCheck.check_macros_in_directory( | |
package_path, | |
ignore_git_submodules=False, | |
show_progress_bar=False, | |
**sub_data) | |
finally: | |
for h, l in handler_level_context: | |
h.setLevel(l) | |
return error_count |