blob: aa3a2bbcab20b0c54542cf4ff8bea2c3b6f86b55 [file] [log] [blame]
# @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