| # @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 | |
| edk2 = builder.edk2path | |
| package = edk2.GetContainingPackage( | |
| builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath( | |
| 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 |