Skip to content

Commit

Permalink
BaseTools/Plugin: Add DebugMacroCheck
Browse files Browse the repository at this point in the history
Adds a plugin that finds debug macro formatting issues. These errors
often creep into debug prints in error conditions not frequently
executed and make debug more difficult when they are encountered.

The code can be as a standalone script which is useful to find
problems in a large codebase that has not been checked before or as
a build plugin that notifies a developer of an error right away.

The script was already used to find numerous issues in edk2 in the
past so there's not many code fixes in this change. More details
are available in the readme file:

.pytool\Plugin\DebugMacroCheck\Readme.md

Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Rebecca Cran <rebecca@bsdio.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Bob Feng <bob.c.feng@intel.com>
Cc: Yuwei Chen <yuwei.chen@intel.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
Reviewed-by: Michael D Kinney <michael.d.kinney@intel.com>
  • Loading branch information
makubacki authored and mergify[bot] committed Sep 19, 2023
1 parent 97d367f commit cbcf042
Show file tree
Hide file tree
Showing 8 changed files with 2,256 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# @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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## @file
# Build plugin used to check that debug macros are formatted properly.
#
# Copyright (c) Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
{
"scope": "global",
"name": "Debug Macro Check Plugin",
"module": "DebugMacroCheckBuildPlugin"
}
Loading

0 comments on commit cbcf042

Please sign in to comment.