[PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin


Michael Kubacki
 

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3D3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:
=20
1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.
=20
V3 changes:
=20
1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.
=20
V2 changes:
=20
1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 +++++++=
+++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++=
++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/Un=
crustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for=
coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent th=
e plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable =
that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This=
unique variable name is used to avoid confusion
+with other paths to Uncrustify which might not be the expected build for=
use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" loca=
ted in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `C=
onfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustif=
y/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.=
azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the =
following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git s=
ubmodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no chang=
es are made to the list of discovered files.
+
+To control the paths checked in a given package, review the configuratio=
n options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatti=
ng (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are =
errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify confi=
g file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that=
should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs =
in the test case log.
+ # This can significantly slow down t=
he plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files an=
d files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be incl=
uded with this option.
+
+At this time, it is recommended all files run against the plugin be writ=
ten in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all error=
s but instead of failing the build, it will set
+the test as skipped. This allows visibility into the failures without br=
eaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to th=
e Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package co=
nfiguration file can specify any of these paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. Th=
is is helpful to exactly understand what changes
+need to be made to the source code in order to fix a coding standard com=
pliance issue.
+
+Note that calculating the file diffs on a very large set of of results (=
e.g. >100 files) can significantly slow down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized =
git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and excl=
ude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrusti=
fy`
+ 2. For each source file with formatting errors, a sibling file with th=
e `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of th=
e plugin. All of these files are removed on
+exit of the plugin including successful or unsuccessful execution (such =
as a Python exception occurring). If for any
+reason, any files in the package exist prior to running the plugin with =
the `.uncrustify_plugin` extension, the plugin
+will inform the user to remove these files and exit before running Uncru=
stify. This is to ensure the accuracy of the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustif=
y and then invokes Uncrustify with that file
+list. For any files not compliant to the configuration file provided, Un=
crustify will generate a corresponding file
+with the `.uncrustify_plugin` extension. The plugin discovers all of the=
se files. If any such files are present, this
+indicates a formatting issue was found and the test is marked failed (un=
less `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format =
properly, allowing the user to run Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` conf=
iguration option is set to `True`, the plugin
+will output diff chunks for all code formatting issues in the test case =
log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/=
Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuild=
Plugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import Helpe=
rFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code =3D exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException)=
:
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in th=
e
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extension=
s but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check format=
ting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there ar=
e errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify con=
fig file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths th=
at should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diff=
s in the test case log.
+ # This can significantly slow down=
the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files =
and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin dire=
ctory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the pa=
ckage
+ #
+ DEFAULT_CONFIG_FILE_PATH =3D os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION =3D ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePath=
s"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS =3D ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment var=
iable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY =3D "UNCRUSTIFY_CI_PATH"
+
+ def GetTestName(self, packagename: str, environment: VarDict) -> Tup=
le:
+ """ 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>.<o=
ptionally any unique condition>
+ """
+ return ("Check file coding standard compliance in " + packagenam=
e, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path,=
package_config: Dict[str, List[str]], environment_config: Any, plugin_ma=
nager: PluginManager, plugin_manager_helper: HelperFunctions, tc: JunitRe=
portTestCase, output_stream=3DNone) -> int:
+ """
+ External function of plugin. This function is used to perform th=
e task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the packag=
e
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (=
logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enab=
led")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._forma=
tted_file_error_count} incorrectly formatted files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might r=
esult
+ from an error that occurred during a previous run or a premature=
exit from a debug scenario. In any case, the package should be clean bef=
ore starting a new run.
+ """
+ pre_existing_formatted_file_count =3D len(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_pack=
age_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files al=
ready exist. To prevent overwriting these files, please remove them befor=
e running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instan=
ce.
+
+ This removes the directory and all files created during this ins=
tance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Un=
crustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files =3D [str(path.resolve()) for path in pathlib=
.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMAT=
TED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance=
.
+ """
+ self._working_dir =3D os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{s=
elf._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=3DTrue, exist_=
ok=3DTrue)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n=
\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to=
process.
+ """
+ self._app_input_file_path =3D os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding=3D'utf8') as =
f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output =3D StringIO()
+ self._app_exit_code =3D RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} =
--if-changed --suffix {UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstr=
eam=3Doutput)
+ self._app_output =3D output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignore=
d in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths wi=
ll not be considered.")
+ return []
+
+ outstream_buffer =3D StringIO()
+ exit_code =3D RunCmd("git", "ls-files --other",
+ workingdir=3Dself._abs_workspace_path, outstr=
eam=3Doutstream_buffer, logging_level=3Dlogging.NOTSET)
+ if (exit_code !=3D 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This wi=
ll prevent Uncrustify from running against the expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sort=
ed
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of=
each submodule in the workspace repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths wi=
ll not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmo=
dules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self.=
_package_name}.")
+
+ outstream_buffer =3D StringIO()
+ exit_code =3D RunCmd("git", "config --file .gitmodules --get=
-regexp path", workingdir=3Dself._abs_workspace_path, outstream=3Doutstre=
am_buffer, logging_level=3Dlogging.NOTSET)
+ if (exit_code !=3D 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred rea=
ding the file. Cannot proceed with unknown submodule paths.")
+
+ submodule_paths =3D []
+ for line in outstream_buffer.getvalue().strip().splitlines()=
:
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_pa=
th, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are speci=
fied
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents =3D None
+ self._func_template_contents =3D None
+
+ # Allow no value to allow "set" statements in the config file wh=
ich do
+ # not specify value assignment
+ parser =3D configparser.ConfigParser(allow_no_value=3DTrue)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name =3D parser["dummy_section"]["cmt_insert_f=
ile_header"]
+
+ file_template_path =3D pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path =3D pathlib.Path(os.path.join(self._p=
lugin_path, file_template_name))
+ self._file_template_contents =3D file_template_path.read=
_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the=
config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was no=
t found.")
+ try:
+ func_template_name =3D parser["dummy_section"]["cmt_insert_f=
unc_header"]
+
+ func_template_path =3D pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path =3D pathlib.Path(os.path.join(self._p=
lugin_path, func_template_name))
+ self._func_template_contents =3D func_template_path.read=
_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in=
the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file wa=
s not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRU=
STIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path =3D shutil.which('uncrustify', path=3Dos.environ[=
UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path =3D os.path.normcase(os.path.normpath(self._app_p=
ath))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer =3D StringIO()
+ ret =3D RunCmd(self._app_path, "--version", outstream=3Dreturn_b=
uffer)
+ if (ret !=3D 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version =3D return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionT=
ypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file =3D UncrustifyCheck.DEFAULT_CONFIG_FILE_PA=
TH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file =3D self._package_config["ConfigFilePa=
th"].strip()
+
+ self._app_config_file =3D os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_fi=
le))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_confi=
g_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_p=
ath: Edk2Path, package_config: Dict[str, List[str]], tc: JunitReportTestC=
ase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path =3D edk2_path.GetAbsolutePathOnThisSytemF=
romEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path =3D edk2_path.WorkspacePath
+ self._package_config =3D package_config
+ self._package_name =3D os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name =3D self.__class__.__name__
+ self._plugin_path =3D os.path.dirname(os.path.realpath(__file__)=
)
+ self._rel_package_path =3D package_rel_path
+ self._tc =3D tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the pa=
ckage to run against Uncrustify.
+ rel_file_paths_to_format =3D list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard pa=
ths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: =
{a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(=
f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this=
package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format =3D []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_=
package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._=
package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file=
exclusion: {len(self._abs_file_paths_to_format)}")
+
+ ignored_paths =3D self._get_git_ignored_paths()
+ self._abs_file_paths_to_format =3D list(
+ set(self._abs_file_paths_to_format).difference(ignored_p=
aths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file =
exclusion: {len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclu=
sion: {len(self._abs_file_paths_to_format)}")
+
+ submodule_paths =3D tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format =3D [
+ f for f in self._abs_file_paths_to_format if not f.start=
swith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclus=
ion: {len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode =3D False
+ self._output_file_diffs =3D False
+
+ if "AuditOnly" in self._package_config and self._package_config[=
"AuditOnly"]:
+ self._audit_only_mode =3D True
+
+ if "OutputFileDiffs" in self._package_config and self._package_c=
onfig["OutputFileDiffs"]:
+ self._output_file_diffs =3D True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failur=
es.
+ """
+ formatted_files =3D [str(path.resolve()) for path in pathlib.Pat=
h(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_=
FILE_EXTENSION}')]
+
+ self._formatted_file_error_count =3D len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a =
while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file =3D formatted_file[:-
+ len(UncrustifyCheck.FORM=
ATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatte=
d_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text =3D ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_fi=
le_text):
+ self._tc.LogStdError(f"File header is missing in=
{os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_fi=
le_text):
+ self._tc.LogStdError(f"A function header is miss=
ing in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text =3D pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_f=
ile_text.split('\n'), formatted_file_text.split('\n'), fromfile=3Dpre_for=
matted_file, tofile=3Dformatted_file, n=3D3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool =3D False)=
-> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues i=
n the
+ shutil implementations. To consolidate on a single function this=
helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_f=
unctions
+ since the version used in edk2 is not recent enough to include t=
he
+ function.
+
+ This function should be replaced by "RemoveTree" when it is avai=
lable.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/fo=
lder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=3Dignore_errors, o=
nerror=3D_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {er=
r}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time =3D timeit.default_timer()
+ self._execute_uncrustify()
+ end_time =3D timeit.default_timer() - start_time
+
+ execution_summary =3D f"Uncrustify executed against {len(self._a=
bs_file_paths_to_format)} files in {self._package_name} in {end_time:.2f}=
seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code !=3D 0 and self._app_exit_code !=3D 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrust=
ify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt b/.py=
tool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt b=
/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requ=
ires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were ot=
her
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plug=
in/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standard=
s-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with =
the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs =3D false
+utf8_byte =3D false
+utf8_force =3D true
+
+# Code width / line splitting
+#code_width =3D120 # TODO: This causes non-dete=
rministic behaviour in some cases when code wraps
+ls_code_width =3Dfalse
+ls_for_split_full =3Dtrue
+ls_func_split_full =3Dtrue
+pos_comma =3Dtrail
+
+# 5.1.7 All files must end with CRLF
+newlines =3D crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces =3D true # Whether to convert all t=
abs to spaces in comments. If false, tabs in
+ # comments are left alone, u=
nless used for indenting.
+indent_columns =3D 2 # Number of spaces for ind=
entation
+indent_with_tabs =3D 0 # Do not use TAB character=
s
+string_replace_tab_chars =3D true # Replace TAB with SPACE
+ # Note: This will break .rob=
ot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends wi=
th ;)
+nl_multi_line_cond =3D true # Add a newline between ')=
' and '{' if the ')' is on a different line than
+ # the if/for/etc.
+nl_after_semicolon =3D true # Whether to add a newline=
after semicolons, except in 'for' statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenth=
esis ')' of simple predicate expressions
+mod_full_brace_do =3D add # Add or remove braces on =
a single-line 'do' statement.
+mod_full_brace_for =3D add
+mod_full_brace_function =3D add # Add or remove braces on =
a single-line function definition.
+mod_full_brace_if =3D add # Add or remove braces on =
a single-line 'if' statement. Braces will not be
+ # removed if the braced stat=
ement contains an 'else'.
+mod_full_brace_if_chain =3D false
+mod_full_brace_while =3D add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last lin=
e of the body
+eat_blanks_after_open_brace =3D true
+eat_blanks_before_close_brace =3D true # Whether to remove blank =
lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign =3D add # Add or remove space arou=
nd assignment operator '=3D', '+=3D', etc.
+sp_assign_default =3D add
+sp_bool =3D add # Add or remove space arou=
nd boolean operators '&&' and '||'.
+sp_compare =3D add # Add or remove space arou=
nd compare operator '<', '>', '=3D=3D', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr =3D remove # A or remove space after =
the '&' (address-of) unary operator.
+sp_incdec =3D remove # Add or remove space betw=
een '++' and '--' the word to which it is being
+ # applied, as in '(--x)' or =
'y++;'.
+sp_inv =3D remove # Add or remove space afte=
r the '~' (invert) unary operator.
+sp_not =3D remove # Add or remove space afte=
r the '!' (not) unary operator.
+sp_sign =3D remove # Add or remove space afte=
r '+' or '-', as in 'x =3D -5' or 'y =3D +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up t=
wo spaces from the beginning of the function
+# name
+nl_func_call_args_multi_line =3D true # Whether to add a newline=
after each ',' in a function call if '(' and ')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures =3D false
+
+# - Indent each argument 2 spaces from the start of the function name. I=
f a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the sta=
rt of the
+# member name.
+indent_func_call_edk2_style =3D true # Use EDK2 indentation sty=
le for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call =3D true # Whether to indent the op=
en parenthesis of a function call, if the
+ # parenthesis is on its own =
line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close =3D 0 # How to indent a close pa=
renthesis after a newline.
+ # (0: Body, 1: Openparenthes=
is, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate item=
s
+sp_after_comma =3D force # Add or remove space afte=
r ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma =3D remove # Add or remove space befo=
re ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen =3D add # Add or remove space afte=
r ')' of control statements.
+sp_attribute_paren =3D add # Add or remove space betw=
een '__attribute__' and '('.
+sp_before_sparen =3D force # Add or remove space befo=
re '(' of control statements
+ # ('if', 'for', 'switch', 'w=
hile', etc.).
+sp_defined_paren =3D force # Add or remove space betw=
een 'defined' and '(' in '#if defined (FOO)'.
+sp_func_call_paren =3D force # Add or remove space betw=
een function name and '(' on function calls.
+sp_func_call_paren_empty =3D force # Add or remove space betw=
een function name and '()' on function calls
+ # without parameters. If set=
to ignore (the default), sp_func_call_paren is
+ # used.
+sp_func_def_paren =3D add # Add or remove space betw=
een alias name and '(' of a non-pointer function
+ # type typedef.
+sp_func_proto_paren =3D add # Add or remove space betw=
een function name and '()' on function declaration
+sp_sizeof_paren =3D force # Add or remove space betw=
een 'sizeof' and '('.
+sp_type_func =3D add # Add or remove space betw=
een return type and function name. A minimum of 1
+ # is forced except for point=
er return types.
+
+# Not specified, but also good style to remove spaces inside parentheses=
(Optional)
+sp_cparen_oparen =3D remove # Add or remove space betw=
een back-to-back parentheses, i.e. ')(' vs. ') ('.
+sp_inside_fparen =3D remove # Add or remove space insi=
de function '(' and ')'.
+sp_inside_fparens =3D remove # Add or remove space insi=
de empty function '()'.
+sp_inside_paren =3D remove # Add or remove space insi=
de '(' and ')'.
+sp_inside_paren_cast =3D remove # Add or remove spaces ins=
ide cast parentheses. '(int)x'
+sp_inside_square =3D remove # Add or remove space insi=
de a non-empty '[' and ']'.
+sp_paren_paren =3D remove # Add or remove space betw=
een nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen =3D remove # Add or remove space betw=
een ']' and '(' when part of a function call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open =3D force # Add or remove space betw=
een 'do' and '{'.
+sp_paren_brace =3D force # Add or remove space betw=
een ')' and '{'.
+sp_sparen_brace =3D force # Add or remove space betw=
een ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operator=
s
+sp_after_byref =3D remove # Add or remove space afte=
r reference sign '&', if followed by a word.
+sp_before_byref =3D add # Add or remove space befo=
re a reference sign '&'.
+sp_deref =3D remove # Add or remove space afte=
r the '*' (dereference) unary operator. This does
+ # not affect the spacing aft=
er a '*' that is part of a type.
+sp_member =3D remove # Add or remove space arou=
nd the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square =3D remove # Add or remove space befo=
re '[' (except '[]').
+sp_before_squares =3D remove # Add or remove space befo=
re '[]'.
+sp_before_vardef_square =3D remove # Add or remove space befo=
re '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowl=
edge of the order of precedence of C
+mod_full_paren_if_bool =3D true # Whether to fully parenth=
esize Boolean expressions in 'while' and 'if'
+ # statement, as in 'if (a &&=
b > c)' =3D> 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it c=
ontinues.
+use_indent_continue_only_once =3D true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close =3D true # Whether to add a newline=
after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else =3D remove # Add or remove newline be=
tween '}' and 'else'.
+nl_brace_while =3D remove # Add or remove newline be=
tween '}' and 'while' of 'do' statement.
+nl_do_brace =3D remove # Add or remove newline be=
tween 'do' and '{'.
+nl_else_brace =3D remove # Add or remove newline be=
tween 'else' and '{'.
+nl_else_if =3D remove # Add or remove newline be=
tween 'else' and 'if'.
+nl_elseif_brace =3D remove # Add or remove newline be=
tween 'else if' and '{'.
+nl_enum_brace =3D remove # Add or remove newline be=
tween 'enum' and '{'.
+nl_fcall_brace =3D remove # Add or remove newline be=
tween a function call's ')' and '{',
+ # as in 'list_for_each(item,=
&list) { }'.
+nl_for_brace =3D remove # Add or remove newline be=
tween 'for' and '{'.
+nl_if_brace =3D remove # Add or remove newline be=
tween 'if' and '{'.
+nl_struct_brace =3D remove # Add or remove newline be=
tween 'struct and '{'.
+nl_switch_brace =3D remove # Add or remove newline be=
tween 'switch' and '{'.
+nl_union_brace =3D remove # Add or remove newline be=
tween 'union' and '{'.
+nl_while_brace =3D remove # Add or remove newline be=
tween 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star =3D remove # Add or remove space afte=
r pointer star '*', if followed by a word.
+ # Useful when paired with al=
ign_var_def_star_style=3D=3D2
+sp_after_ptr_star_func =3D remove # Add or remove space afte=
r a pointer star '*', if followed by a function
+ # prototype or function defi=
nition.
+sp_after_semi =3D remove # Add or remove space afte=
r ';', except when followed by a comment.
+sp_before_case_colon =3D remove # Add or remove space befo=
re case ':'.
+sp_before_ptr_star =3D add # Add or remove space befo=
re pointer star '*'.
+sp_before_ptr_star_func =3D add # Add or remove space befo=
re a pointer star '*', if followed by a function
+ # prototype or function defi=
nition.
+sp_before_semi =3D remove # Add or remove space befo=
re ';'
+sp_before_semi_for =3D remove # Add or remove space befo=
re ';' in non-empty 'for' statements.
+sp_before_semi_for_empty =3D add # Add or remove space befo=
re a semicolon of an empty part of a for statement
+sp_between_ptr_star =3D remove # Add or remove space betw=
een pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while =3D force # Add or remove space betw=
een '}' and 'while'.
+
+sp_after_cast =3D remove
+sp_after_type =3D add
+sp_balance_nested_parens =3D false
+sp_before_nl_cont =3D add
+sp_before_square_asm_block =3D ignore
+sp_before_unnamed_byref =3D add
+sp_brace_brace =3D ignore
+sp_brace_else =3D force
+sp_brace_typedef =3D add
+sp_case_label =3D force
+sp_cmt_cpp_doxygen =3D true
+sp_cond_colon =3D add
+sp_cond_question =3D add
+sp_cpp_cast_paren =3D force
+sp_else_brace =3D force
+sp_endif_cmt =3D force
+sp_enum_assign =3D add
+sp_inside_braces =3D force
+sp_inside_braces_empty =3D force
+sp_inside_braces_enum =3D force
+sp_inside_braces_struct =3D force
+sp_pp_concat =3D add
+sp_pp_stringify =3D add
+sp_return_paren =3D add
+sp_special_semi =3D force
+sp_while_paren_open =3D force
+
+# Additional Indentation Rules
+indent_access_spec =3D 1
+indent_access_spec_body =3D false
+indent_align_assign =3D true
+indent_align_string =3D true
+indent_bool_paren =3D true
+indent_brace_parent =3D false
+indent_braces =3D false
+indent_braces_no_class =3D false
+indent_braces_no_func =3D true
+indent_braces_no_struct =3D false
+indent_class =3D false
+indent_class_colon =3D false
+indent_cmt_with_tabs =3D false # Whether to indent co=
mments that are not at a brace level with tabs on
+ # a tabstop. Requires in=
dent_with_tabs=3D2. If false, will use spaces.
+indent_col1_comment =3D true
+indent_col1_multi_string_literal=3D true
+indent_comma_paren =3D true
+indent_else_if =3D true
+indent_extern =3D false
+indent_first_bool_expr =3D true
+
+indent_func_def_param_paren_pos_threshold =3D 0
+indent_func_param_double =3D false
+indent_func_proto_param =3D true
+indent_ignore_asm_block =3D true
+indent_label =3D 1
+indent_member =3D 2
+indent_namespace =3D false
+indent_param =3D 2
+indent_paren_nl =3D false
+indent_paren_open_brace =3D false
+indent_preserve_sql =3D false
+indent_relative_single_line_comments =3D false
+indent_sing_line_comments =3D 0
+indent_single_newlines =3D false
+indent_square_nl =3D false
+indent_switch_case =3D 2
+indent_template_param =3D true
+indent_var_def_blk =3D 0
+indent_var_def_cont =3D false
+
+# Tidy-up rules (Optional)
+mod_move_case_break =3D true # Whether to move a 'break=
' that appears after a fully braced 'case'
+ # before the close brace, as=
in 'case X: { ... } break;' =3D>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon =3D false
+mod_remove_empty_return =3D false # Whether to remove a void=
'return;' that appears as the last statement
+ # in a function.
+mod_remove_extra_semicolon =3D true
+mod_sort_import =3D false
+mod_sort_include =3D false
+mod_sort_using =3D false
+nl_after_case =3D false # Whether to add a newline=
after a 'case' statement.
+nl_end_of_file =3D force # Add or remove newline at=
the end of the file.
+nl_end_of_file_min =3D 1 # The minimum number of ne=
wlines at the end of the file
+nl_max =3D 2 # The maximum number of co=
nsecutive newlines (3 =3D 2 blank lines).
+nl_start_of_file =3D remove # Add or remove newlines a=
t the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon =3D false
+align_assign_span =3D 1 # The span for aligning on=
'=3D' in assignments.
+align_assign_thresh =3D 4
+align_edk2_style =3D true # Whether to apply edk2-sp=
ecific alignment formatting
+align_enum_equ_span =3D 1 # The span for aligning on=
'=3D' in enums.
+align_func_params =3D true # Whether to align variabl=
e definitions in prototypes and functions.
+align_func_params_gap =3D 2
+align_func_params_span =3D 2 # The span for aligning pa=
rameter definitions in function on parameter name.
+align_func_params_thresh =3D 0
+align_func_proto_span =3D 0
+align_keep_tabs =3D false
+align_left_shift =3D false
+align_mix_var_proto =3D false
+align_nl_cont =3D false
+align_oc_decl_colon =3D false
+align_on_operator =3D false
+align_on_tabstop =3D false
+align_pp_define_gap =3D 2
+align_pp_define_span =3D 1
+align_right_cmt_at_col =3D 0 # Align trailing comment a=
t or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=3Di=
gnore)
+align_right_cmt_gap =3D 0 # If a trailing comment is=
more than this number of columns away from the
+ # text it follows,
+ # it will qualify for being =
aligned. This has to be > 0 to do anything.
+align_right_cmt_mix =3D false # If aligning comments, mi=
x with comments after '}' and #endif with less
+ # than 3 spaces before the c=
omment
+align_right_cmt_same_level =3D true # Whether to only align tr=
ailing comments that are at the same brace level.
+align_right_cmt_span =3D 2 # The span for aligning co=
mments that end lines.
+align_same_func_call_params =3D false
+align_single_line_brace =3D true
+align_single_line_func =3D true
+align_struct_init_span =3D 1 # The span for aligning st=
ruct initializer values.
+align_typedef_amp_style =3D 1
+align_typedef_func =3D 1 # How to align typedef'd f=
unctions with other typedefs.
+ # (0: No align, 1: Align ope=
n paranthesis, 2: Align function type name)
+align_typedef_gap =3D 2
+align_typedef_span =3D 1 # The span for aligning si=
ngle-line typedefs.
+align_typedef_star_style =3D 1
+align_var_def_amp_style =3D 1
+align_var_def_attribute =3D true
+align_var_def_colon =3D true # Whether to align the col=
on in struct bit fields.
+align_var_def_gap =3D 2 # The gap (minimum spacing=
for aligned items) for variable definitions.
+align_var_def_inline =3D false
+align_var_def_span =3D 1 # The span (lines needed t=
o align) for aligning variable definitions.
+align_var_def_star_style =3D 1 # How to consider (or trea=
t) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'v=
oid * foo;' (default)
+ # 1: Part of the variable 'v=
oid *foo;'
+ # 2: Dangling 'v=
oid *foo;'
+ # (Note - should also set sp=
_after_ptr_star=3Dremove)
+align_var_struct_gap =3D 4
+align_var_struct_span =3D 8 # The span for aligning st=
ruct/union member definitions.
+align_var_struct_thresh =3D 0
+align_with_tabs =3D false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags =3D true # Whether to align doxygen=
javadoc-style tags ('@param', '@return', etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group =3D false
+cmt_c_nl_end =3D true # Whether to add a newline=
before the closing '*/' of the combined c-comment.
+cmt_c_nl_start =3D true
+cmt_cpp_group =3D false
+cmt_cpp_nl_end =3D true
+cmt_cpp_nl_start =3D true
+cmt_cpp_to_c =3D false
+cmt_indent_multi =3D false # Whether to apply changes=
to multi-line comments, including cmt_width,
+ # keyword substitution and l=
eading chars.
+cmt_insert_before_preproc =3D false
+cmt_insert_file_header =3D default_file_header.txt
+cmt_insert_func_header =3D default_function_header.txt
+cmt_multi_check_last =3D false
+cmt_multi_first_len_minimum =3D 2
+cmt_reflow_mode =3D 1 # How to reflow comments.
+ # (0:No reflow, 1:No touchin=
g at all, 2: Full reflow)
+cmt_sp_after_star_cont =3D 0 # The number of spaces to =
insert after the star on subsequent comment lines.
+cmt_sp_before_star_cont =3D 0 # The number of spaces to =
insert at the start of subsequent comment lines.
+cmt_star_cont =3D false # Whether to put a star on=
subsequent comment lines.
+cmt_width =3D 120 # Try to wrap comments at =
N columns.
+sp_cmt_cpp_start =3D add # Add or remove space afte=
r the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Brea=
ks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param =3D false # Whether to indent contin=
ued function call parameters one indent level,
+ # rather than aligning param=
eters under the open parenthesis.
+indent_func_class_param =3D false # Whether to indent contin=
ued function call declaration one indent level,
+ # rather than aligning param=
eters under the open parenthesis.
+indent_func_ctor_var_param =3D false # Whether to indent contin=
ued class variable constructors one indent level,
+ # rather than aligning param=
eters under the open parenthesis.
+indent_func_def_param =3D true # Whether to indent contin=
ued function definition parameters one indent
+ # level, rather than alignin=
g parameters under the open parenthesis.
+nl_fdef_brace =3D add # Add or remove newline be=
tween function signature and '{'.
+nl_func_call_end_multi_line =3D true # Whether to add a newline=
before ')' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_call_paren =3D remove # Add or remove newline be=
tween a function name and the opening '(' in the
+ # call.
+nl_func_call_start_multi_line =3D true # Whether to add a newline=
after '(' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_decl_args =3D force # Add or remove newline af=
ter each ',' in a function declaration.
+nl_func_decl_empty =3D add # Add or remove newline be=
tween '()' in a function declaration.
+nl_func_def_args =3D force # Add or remove newline af=
ter each ',' in a function definition.
+nl_func_def_empty =3D add # Add or remove newline be=
tween '()' in a function definition.
+nl_func_def_paren =3D remove # Add or remove newline be=
tween a function name and the opening '('
+ # in the definition.
+nl_func_paren =3D remove # Add or remove newline be=
tween a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name =3D add # Add or remove newline be=
tween return type and function name in a function
+ # definition.
+sp_fparen_brace =3D force # Add or remove space betw=
een ')' and '{' of function.
+use_indent_func_call_param =3D true # indent_func_call_param w=
ill be used
+
+# Additional Newline Rules
+nl_after_brace_open =3D true # Whether to a=
dd a newline after '{'. This also adds a newline
+ # before the mat=
ching '}'.
+nl_after_brace_open_cmt =3D true # Whether to a=
dd a newline between the open brace and a
+ # trailing singl=
e-line comment.
+ # Requires nl_af=
ter_brace_open =3D true.
+nl_after_do =3D add # Add or remov=
e blank line after 'do/while' statement.
+nl_after_for =3D add # Add or remov=
e blank line after 'for' statement.
+nl_after_func_body =3D 2 # The number o=
f newlines after '}' of a multi-line function body
+nl_after_func_body_one_liner =3D 2
+nl_after_func_proto =3D 2
+nl_after_func_proto_group =3D 2
+nl_after_if =3D add
+nl_after_multiline_comment =3D false
+nl_after_return =3D false
+nl_after_struct =3D 2
+nl_after_switch =3D add
+nl_after_vbrace_close =3D true
+nl_after_vbrace_open =3D true
+nl_after_vbrace_open_empty =3D true
+nl_after_while =3D add
+nl_assign_leave_one_liners =3D true
+nl_before_block_comment =3D 2
+nl_before_case =3D false
+nl_before_do =3D ignore
+nl_before_for =3D ignore
+nl_before_if =3D ignore
+nl_before_switch =3D ignore
+nl_before_while =3D ignore
+nl_before_whole_file_ifdef =3D 2
+nl_brace_brace =3D force
+nl_brace_struct_var =3D remove
+nl_case_colon_brace =3D add
+nl_class_leave_one_liners =3D false
+nl_collapse_empty_body =3D false
+nl_comment_func_def =3D 1
+nl_create_for_one_liner =3D false
+nl_create_if_one_liner =3D false
+nl_create_while_one_liner =3D false
+nl_define_macro =3D false
+nl_ds_struct_enum_close_brace =3D true
+nl_ds_struct_enum_cmt =3D false
+nl_enum_leave_one_liners =3D false
+nl_func_decl_end =3D add
+nl_func_decl_start =3D add
+nl_func_def_end =3D add
+nl_func_def_start =3D add
+nl_func_leave_one_liners =3D false
+nl_func_proto_type_name =3D add
+nl_func_var_def_blk =3D 1
+nl_getset_leave_one_liners =3D false
+nl_if_leave_one_liners =3D false
+nl_multi_line_define =3D false
+nl_squeeze_ifdef =3D false
+nl_var_def_blk_end =3D 0
+nl_var_def_blk_start =3D 0
+
+# Preprocessor Rules
+pp_define_at_level =3D true
+pp_if_indent_code =3D false
+pp_indent_func_def =3D false
+pp_indent_extern =3D false
+pp_ignore_define_body =3D true # Workaround: Turn off p=
rocessing for #define body
+ # (current rules do not wo=
rk for some defines)
+pp_indent =3D add
+pp_indent_at_level =3D true
+pp_indent_count =3D 2
+pp_indent_if =3D 2
+pp_indent_region =3D 2
+pp_region_indent_code =3D false
+pp_space =3D remove
+
+#
+# The tokens below are assigned specific types so they are always recogn=
ized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml b/.py=
tool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/=
mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml b/.py=
tool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style=
C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTo=
ols
package. It checks that the code complies to the EDKII coding standard.
=20
+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance =
issues.
+
## PyTool Scopes
=20
Scopes are how the PyTool ext_dep, path_env, and plugins are activated. =
Meaning
--=20
2.28.0.windows.1


Michael D Kinney
 

Hi Michael,

I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?


-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
- # in different lines.


-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it will set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present, this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc: JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case, the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for' statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will not be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '(' and ')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default), sp_func_call_paren is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum of 1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and 'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with tabs on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return', etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line function body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1


Michael Kubacki
 

Yes. Those were redundant and detected by the Python configparser module when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,
I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?
-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
- # in different lines.
-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false
Thanks,
Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it will set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present, this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc: JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case, the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for' statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will not be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '(' and ')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default), sp_func_call_paren is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum of 1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and 'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with tabs on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return', etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line function body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1


Michael D Kinney
 

Thanks. That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?


-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
- # in different lines.


-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it will
set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present, this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case, the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer,
logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code
wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for' statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will not be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '(' and
')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default), sp_func_call_paren
is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function
declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum of
1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ')
('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This
does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and 'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with tabs
on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter
name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace
level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return', etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and ')'
are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')' are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a
function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line function
body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1







Michael D Kinney
 

Hi Michael,

Unfortunately, enabling the templates in the uncrustify.cfg files modifies 340 files with missing
file/function headers that will now fail UncrustifyCheck.

cmt_insert_file_header = default_file_header.txt
cmt_insert_func_header = default_function_header.txt

Perhaps we should leave this feature off to get the uncrustify format changes committed and
enable this feature immediately after so patch reviews after that will slowly fix these missing
file/function headers.

Or I can comment these two lines out when I generate the final version of the patch series so
we don’t have to do an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 3:06 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Thanks. That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?


-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
- # in different lines.


-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it will
set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the
plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present,
this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run
Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the
plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case,
the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer,
logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against
the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion: {len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code
wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line
than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for' statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will not
be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is
being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '(' and
')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default), sp_func_call_paren
is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer
function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function
declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum
of
1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ')
('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This
does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and 'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with tabs
on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter
name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from
the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace
level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return',
etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment
lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and ')'
are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in
the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a
function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a
newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line function
body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1







Michael D Kinney
 

The 2nd option I listed won't work. That will fail UncrustifyCheck as well because EDK II CI
will insert missing file/function headers and the files will not match.

We will need to comment out these 2 lines and submit an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 4:08 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Hi Michael,

Unfortunately, enabling the templates in the uncrustify.cfg files modifies 340 files with missing
file/function headers that will now fail UncrustifyCheck.

cmt_insert_file_header = default_file_header.txt
cmt_insert_func_header = default_function_header.txt

Perhaps we should leave this feature off to get the uncrustify format changes committed and
enable this feature immediately after so patch reviews after that will slowly fix these missing
file/function headers.

Or I can comment these two lines out when I generate the final version of the patch series so
we don’t have to do an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 3:06 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Thanks. That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?


-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
- # in different lines.


-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered
files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it
will
set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow
down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed
on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for
any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the
plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of
the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that
file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding
file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present,
this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run
Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the
plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly
formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case,
the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer,
logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against
the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace
repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in
{os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code
wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line
than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for'
statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will
not
be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is
being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the
function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '('
and
')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined
(FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default),
sp_func_call_paren
is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer
function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function
declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum
of
1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs.
')
('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function
call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This
does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and
'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with
tabs
on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use
spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last
statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter
name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from
the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do
anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with
less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace
level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type
name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable
definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return',
etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including
cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment
lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one
indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and
')'
are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in
the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a
function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a
newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line
function
body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1







Michael Kubacki
 

Hi Mike,

How about I quickly send a v5 with the lines commented out and follow up with a patch to uncomment them that we can review but merge when ready?

Thanks,
Michael

On 11/24/2021 7:16 PM, Michael D Kinney wrote:
The 2nd option I listed won't work. That will fail UncrustifyCheck as well because EDK II CI
will insert missing file/function headers and the files will not match.
We will need to comment out these 2 lines and submit an extra commit to re-enable.
Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 4:08 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Hi Michael,

Unfortunately, enabling the templates in the uncrustify.cfg files modifies 340 files with missing
file/function headers that will now fail UncrustifyCheck.

cmt_insert_file_header = default_file_header.txt
cmt_insert_func_header = default_function_header.txt

Perhaps we should leave this feature off to get the uncrustify format changes committed and
enable this feature immediately after so patch reviews after that will slowly fix these missing
file/function headers.

Or I can comment these two lines out when I generate the final version of the patch series so
we don’t have to do an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 3:06 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Thanks. That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg. Was that in purpose? Does it change the format?


-nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
- # in different lines.


-nl_func_call_args_multi_line = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
V4 changes:

1. Updated commit subject to use the new plugin name.
2. Enabled file and function header templates to be placed in
files missing a file or function header (comment block).
3. Added support to detect whether a file header or function
header is missing in a file by searching files modified by
Uncrustify for the template file text.
4. Updated the plugin name displayed in some log output messages.

V3 changes:

1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
to follow similar naming conventions used in other plugins
that check files
2. Added a clarifying paragraph to the beginning of the Uncrustify
CI plugin Readme.md file that the plugin is enabled by default
and test results can be ignored by enabling "AuditOnly" mode.
3. Added instructions in the commit message on how to map the
version reported by the Uncrustify application (--version) to
the version specified in the CI plugin external dependency
YAML file.

V2 changes:

1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
2. Added TCBZ REF in patch commit message

.pytool/Plugin/UncrustifyCheck/Readme.md | 120 ++++
.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py | 617 ++++++++++++++++++++
.pytool/Plugin/UncrustifyCheck/default_file_header.txt | 9 +
.pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
.pytool/Plugin/UncrustifyCheck/uncrustify.cfg | 462 +++++++++++++++
.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml | 16 +
.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml | 11 +
.pytool/Readme.md | 4 +
8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered
files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it
will
set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow
down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+ 1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+ 2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed
on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for
any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the
plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of
the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that
file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding
file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present,
this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run
Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the
plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+ def __init__(self, message, exit_code):
+ super().__init__(message)
+ self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+ def __init__(self, message):
+ super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+ """
+ A CiBuildPlugin that uses Uncrustify to check the source files in the
+ package being tested for coding standard issues.
+
+ By default, the plugin runs against standard C source file extensions but
+ its configuration can be modified through its configuration file.
+
+ Configuration options:
+ "UncrustifyCheck": {
+ "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+ "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
+ "ConfigFilePath": "", # Custom path to an Uncrustify config file.
+ "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
+ "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
+ # This can significantly slow down the plugin on very large packages.
+ "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
+ }
+ """
+
+ #
+ # By default, use an "uncrustify.cfg" config file in the plugin directory
+ # A package can override this path via "ConfigFilePath"
+ #
+ # Note: Values specified via "ConfigFilePath" are relative to the package
+ #
+ DEFAULT_CONFIG_FILE_PATH = os.path.join(
+ pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+ #
+ # The extension used for formatted files produced by this plugin
+ #
+ FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+ #
+ # A package can add any additional paths with "AdditionalIncludePaths"
+ # A package can remove any of these paths with "IgnoreStandardPaths"
+ #
+ STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+ #
+ # The Uncrustify application path should set in this environment variable
+ #
+ UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+ 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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+ def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+ """
+ External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+ Args:
+ - package_rel_path: edk2 workspace relative path to the package
+ - edk2_path: Edk2Path object with workspace and packages paths
+ - package_config: Dictionary with the package configuration
+ - environment_config: Environment configuration
+ - plugin_manager: Plugin Manager Instance
+ - plugin_manager_helper: Plugin Manager Helper Instance
+ - tc: JUnit test case
+ - output_stream: The StringIO output stream from this plugin (logging)
+
+ Returns
+ >0 : Number of errors found
+ 0 : Passed successfully
+ -1 : Skipped for missing prereq
+ """
+ try:
+ # Initialize plugin and check pre-requisites.
+ self._initialize_environment_info(
+ package_rel_path, edk2_path, package_config, tc)
+ self._initialize_configuration()
+ self._check_for_preexisting_formatted_files()
+
+ # Log important context information.
+ self._log_uncrustify_app_info()
+
+ # Get template file contents if specified
+ self._get_template_file_contents()
+
+ # Create meta input files & directories
+ self._create_temp_working_directory()
+ self._create_uncrustify_file_list_file()
+
+ self._run_uncrustify()
+
+ # Post-execution actions.
+ self._process_uncrustify_results()
+
+ except UncrustifyException as e:
+ self._tc.LogStdError(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ logging.warning(
+ f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+ return -1
+ else:
+ if self._formatted_file_error_count > 0:
+ if self._audit_only_mode:
+ logging.info(
+ "Setting test as skipped since AuditOnly is enabled")
+ self._tc.SetSkipped()
+ return -1
+ else:
+ self._tc.SetFailed(
+ f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly
formatted
files.", "CHECK_FAILED")
+ else:
+ self._tc.SetSuccess()
+ return self._formatted_file_error_count
+ finally:
+ self._cleanup_temporary_formatted_files()
+ self._cleanup_temporary_directory()
+
+ def _initialize_configuration(self) -> None:
+ """
+ Initializes plugin configuration.
+ """
+ self._initialize_app_info()
+ self._initialize_config_file_info()
+ self._initialize_file_to_format_info()
+ self._initialize_test_case_output_options()
+
+ def _check_for_preexisting_formatted_files(self) -> None:
+ """
+ Checks if any formatted files from prior execution are present.
+
+ Existence of such files is an unexpected condition. This might result
+ from an error that occurred during a previous run or a premature exit from a debug scenario. In any case,
the
package should be clean before starting a new run.
+ """
+ pre_existing_formatted_file_count = len(
+ [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+ if pre_existing_formatted_file_count > 0:
+ raise UncrustifyStalePluginFormattedFilesException(
+ f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+ def _cleanup_temporary_directory(self) -> None:
+ """
+ Cleans up the temporary directory used for this execution instance.
+
+ This removes the directory and all files created during this instance.
+ """
+ if hasattr(self, '_working_dir'):
+ self._remove_tree(self._working_dir)
+
+ def _cleanup_temporary_formatted_files(self) -> None:
+ """
+ Cleans up the temporary formmatted files produced by Uncrustify.
+
+ This will recursively remove all formatted files generated by Uncrustify
+ during this execution instance.
+ """
+ if hasattr(self, '_abs_package_path'):
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ for formatted_file in formatted_files:
+ os.remove(formatted_file)
+
+ def _create_temp_working_directory(self) -> None:
+ """
+ Creates the temporary directory used for this execution instance.
+ """
+ self._working_dir = os.path.join(
+ self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+ try:
+ pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+ except OSError as e:
+ raise UncrustifyInputFileCreationErrorException(
+ f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+ def _create_uncrustify_file_list_file(self) -> None:
+ """
+ Creates the file with the list of source files for Uncrustify to process.
+ """
+ self._app_input_file_path = os.path.join(
+ self._working_dir, "uncrustify_file_list.txt")
+
+ with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+ def _execute_uncrustify(self) -> None:
+ """
+ Executes Uncrustify with the initialized configuration.
+ """
+ output = StringIO()
+ self._app_exit_code = RunCmd(
+ self._app_path,
+ f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+ self._app_output = output.getvalue().strip().splitlines()
+
+ def _get_git_ignored_paths(self) -> List[str]:
+ """"
+ Returns a list of file absolute path strings to all files ignored in this git repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "ls-files --other",
+ workingdir=self._abs_workspace_path, outstream=outstream_buffer,
logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitIgnoreFileException(
+ f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against
the
expected set of files.")
+
+ # Note: This will potentially be a large list, but at least sorted
+ return outstream_buffer.getvalue().strip().splitlines()
+
+ def _get_git_submodule_paths(self) -> List[str]:
+ """
+ Returns a list of directory absolute path strings to the root of each submodule in the workspace
repository.
+
+ If git is not found, an empty list will be returned.
+ """
+ if not shutil.which("git"):
+ logging.warn(
+ "Git is not found on this system. Git submodule paths will not be considered.")
+ return []
+
+ if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+ logging.info(
+ f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+ outstream_buffer = StringIO()
+ exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+ if (exit_code != 0):
+ raise UncrustifyGitSubmoduleException(
+ f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+ submodule_paths = []
+ for line in outstream_buffer.getvalue().strip().splitlines():
+ submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+ return submodule_paths
+ else:
+ return []
+
+ def _get_template_file_contents(self) -> None:
+ """
+ Gets the contents of Uncrustify template files if they are specified
+ in the Uncrustify configuration file.
+ """
+
+ self._file_template_contents = None
+ self._func_template_contents = None
+
+ # Allow no value to allow "set" statements in the config file which do
+ # not specify value assignment
+ parser = configparser.ConfigParser(allow_no_value=True)
+ with open(self._app_config_file, 'r') as cf:
+ parser.read_string("[dummy_section]\n" + cf.read())
+
+ try:
+ file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+ file_template_path = pathlib.Path(file_template_name)
+
+ if not file_template_path.is_file():
+ file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+ self._file_template_contents = file_template_path.read_text()
+ except KeyError:
+ logging.warn("A file header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified file header template file was not found.")
+ try:
+ func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+ func_template_path = pathlib.Path(func_template_name)
+
+ if not func_template_path.is_file():
+ func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+ self._func_template_contents = func_template_path.read_text()
+ except KeyError:
+ logging.warn("A function header template is not specified in the config file.")
+ except FileNotFoundError:
+ logging.warn("The specified function header template file was not found.")
+
+ def _initialize_app_info(self) -> None:
+ """
+ Initialize Uncrustify application information.
+
+ This function will determine the application path and version.
+ """
+ # Verify Uncrustify is specified in the environment.
+ if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+ raise UncrustifyAppEnvVarNotFoundException(
+ f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+ self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+ if self._app_path is None:
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+ if not os.path.isfile(self._app_path):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+ # Verify Uncrustify is present at the expected path.
+ return_buffer = StringIO()
+ ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+ if (ret != 0):
+ raise UncrustifyAppVersionErrorException(
+ f"Error occurred executing --version: {ret}.")
+
+ # Log Uncrustify version information.
+ self._app_version = return_buffer.getvalue().strip()
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ version_aggregator.GetVersionAggregator().ReportVersion(
+ "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+ def _initialize_config_file_info(self) -> None:
+ """
+ Initialize Uncrustify configuration file info.
+
+ The config file path is relative to the package root.
+ """
+ self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+ if "ConfigFilePath" in self._package_config:
+ self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+ self._app_config_file = os.path.normpath(
+ os.path.join(self._abs_package_path, self._app_config_file))
+
+ if not os.path.isfile(self._app_config_file):
+ raise FileNotFoundError(
+ errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+ def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+ """
+ Initializes plugin environment information.
+ """
+ self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+ package_rel_path)
+ self._abs_workspace_path = edk2_path.WorkspacePath
+ self._package_config = package_config
+ self._package_name = os.path.basename(
+ os.path.normpath(package_rel_path))
+ self._plugin_name = self.__class__.__name__
+ self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+ self._rel_package_path = package_rel_path
+ self._tc = tc
+
+ def _initialize_file_to_format_info(self) -> None:
+ """
+ Forms the list of source files for Uncrustify to process.
+ """
+ # Create a list of all the package relative file paths in the package to run against Uncrustify.
+ rel_file_paths_to_format = list(
+ UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+ # Allow the ci.yaml to remove any of the pre-defined standard paths
+ if "IgnoreStandardPaths" in self._package_config:
+ for a in self._package_config["IgnoreStandardPaths"]:
+ if a.strip() in rel_file_paths_to_format:
+ self._tc.LogStdOut(
+ f"Ignoring standard path due to ci.yaml ignore: {a}")
+ rel_file_paths_to_format.remove(a.strip())
+ else:
+ raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+ # Allow the ci.yaml to specify additional include paths for this package
+ if "AdditionalIncludePaths" in self._package_config:
+ rel_file_paths_to_format.extend(
+ self._package_config["AdditionalIncludePaths"])
+
+ self._abs_file_paths_to_format = []
+ for path in rel_file_paths_to_format:
+ self._abs_file_paths_to_format.extend(
+ [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+ if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+ # Remove files ignored by git
+ logging.info(
+ f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ ignored_paths = self._get_git_ignored_paths()
+ self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+ logging.info(
+ f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Remove files in submodules
+ logging.info(
+ f"{self._package_name} file count before submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ submodule_paths = tuple(self._get_git_submodule_paths())
+ for path in submodule_paths:
+ logging.info(f" submodule path: {path}")
+
+ self._abs_file_paths_to_format = [
+ f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+ logging.info(
+ f"{self._package_name} file count after submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+ # Sort the files for more consistent results
+ self._abs_file_paths_to_format.sort()
+
+ def _initialize_test_case_output_options(self) -> None:
+ """
+ Initializes options that influence test case output.
+ """
+ self._audit_only_mode = False
+ self._output_file_diffs = False
+
+ if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+ self._audit_only_mode = True
+
+ if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+ self._output_file_diffs = True
+
+ def _log_uncrustify_app_info(self) -> None:
+ """
+ Logs Uncrustify application information.
+ """
+ self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+ self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+ self._tc.LogStdOut('\n')
+ logging.info(f"Found Uncrustify at {self._app_path}")
+ logging.info(f"Uncrustify version: {self._app_version}")
+ logging.info('\n')
+
+ def _process_uncrustify_results(self) -> None:
+ """
+ Process the results from Uncrustify.
+
+ Determines whether formatting errors are present and logs failures.
+ """
+ formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+ self._formatted_file_error_count = len(formatted_files)
+
+ if self._formatted_file_error_count > 0:
+ self._tc.LogStdError("Files with formatting errors:\n")
+
+ if self._output_file_diffs:
+ logging.info("Calculating file diffs. This might take a while...")
+
+ for formatted_file in formatted_files:
+ pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+ if (self._output_file_diffs or
+ self._file_template_contents is not None or
+ self._func_template_contents is not None):
+ self._tc.LogStdError(
+ f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+ with open(formatted_file) as ff:
+ formatted_file_text = ff.read()
+
+ if (self._file_template_contents is not None and
+ self._file_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if (self._func_template_contents is not None and
+ self._func_template_contents in formatted_file_text):
+ self._tc.LogStdError(f"A function header is missing in
{os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+ if self._output_file_diffs:
+ with open(pre_formatted_file) as pf:
+ pre_formatted_file_text = pf.read()
+
+ for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+ self._tc.LogStdError(line)
+
+ self._tc.LogStdError('\n')
+ else:
+ self._tc.LogStdError(pre_formatted_file)
+
+ def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+ """
+ Helper for removing a directory. Over time there have been
+ many private implementations of this due to reliability issues in the
+ shutil implementations. To consolidate on a single function this helper is added.
+
+ On error try to change file attributes. Also add retry logic.
+
+ This function is temporarily borrowed from edk2toollib.utility_functions
+ since the version used in edk2 is not recent enough to include the
+ function.
+
+ This function should be replaced by "RemoveTree" when it is available.
+
+ Args:
+ - dir_path: Path to directory to remove.
+ - ignore_errors: Whether to ignore errors during removal
+ """
+
+ def _remove_readonly(func, path, _):
+ """
+ Private function to attempt to change permissions on file/folder being deleted.
+ """
+ os.chmod(path, os.stat.S_IWRITE)
+ func(path)
+
+ for _ in range(3): # retry up to 3 times
+ try:
+ shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+ except OSError as err:
+ logging.warning(f"Failed to fully remove {dir_path}: {err}")
+ else:
+ break
+ else:
+ raise RuntimeError(f"Failed to remove {dir_path}")
+
+ def _run_uncrustify(self) -> None:
+ """
+ Runs Uncrustify for this instance of plugin execution.
+ """
+ logging.info("Executing Uncrustify. This might take a while...")
+ start_time = timeit.default_timer()
+ self._execute_uncrustify()
+ end_time = timeit.default_timer() - start_time
+
+ execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+ self._tc.LogStdOut(execution_summary)
+ logging.info(execution_summary)
+
+ if self._app_exit_code != 0 and self._app_exit_code != 1:
+ raise UncrustifyAppExecutionException(
+ f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+ Brief description of the file's purpose.
+
+ Detailed description of the file's contents and other useful
+ information for a person viewing the file for the first time.
+
+ <<Copyright>>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+ Brief description of this function's purpose.
+
+ Follow it immediately with the detailed description.
+
+ @param[in] Arg1 Description of Arg1.
+ @param[in] Arg2 Description of Arg2 This is complicated and requires
+ multiple lines to describe.
+ @param[out] Arg3 Description of Arg3.
+ @param[in, out] Arg4 Description of Arg4.
+
+ @retval VAL_ONE Description of what VAL_ONE signifies.
+ @retval OTHER This is the only other return value. If there were other
+ return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs = false
+utf8_byte = false
+utf8_force = true
+
+# Code width / line splitting
+#code_width =120 # TODO: This causes non-deterministic behaviour in some cases when code
wraps
+ls_code_width =false
+ls_for_split_full =true
+ls_func_split_full =true
+pos_comma =trail
+
+# 5.1.7 All files must end with CRLF
+newlines = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces = true # Whether to convert all tabs to spaces in comments. If false, tabs in
+ # comments are left alone, unless used for indenting.
+indent_columns = 2 # Number of spaces for indentation
+indent_with_tabs = 0 # Do not use TAB characters
+string_replace_tab_chars = true # Replace TAB with SPACE
+ # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond = true # Add a newline between ')' and '{' if the ')' is on a different line
than
+ # the if/for/etc.
+nl_after_semicolon = true # Whether to add a newline after semicolons, except in 'for'
statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do = add # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for = add
+mod_full_brace_function = add # Add or remove braces on a single-line function definition.
+mod_full_brace_if = add # Add or remove braces on a single-line 'if' statement. Braces will
not
be
+ # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain = false
+mod_full_brace_while = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace = true
+eat_blanks_before_close_brace = true # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign = add # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default = add
+sp_bool = add # Add or remove space around boolean operators '&&' and '||'.
+sp_compare = add # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr = remove # A or remove space after the '&' (address-of) unary operator.
+sp_incdec = remove # Add or remove space between '++' and '--' the word to which it is
being
+ # applied, as in '(--x)' or 'y++;'.
+sp_inv = remove # Add or remove space after the '~' (invert) unary operator.
+sp_not = remove # Add or remove space after the '!' (not) unary operator.
+sp_sign = remove # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the
function
+# name
+nl_func_call_args_multi_line = true # Whether to add a newline after each ',' in a function call if '('
and
')'
+ # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+# function is called through a structure or union member, of type
+# pointer-to-function, then indent each argument 2 spaces from the start of the
+# member name.
+indent_func_call_edk2_style = true # Use EDK2 indentation style for function calls (**CUSTOM SETTING**)
+indent_paren_after_func_call = true # Whether to indent the open parenthesis of a function call, if the
+ # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close = 0 # How to indent a close parenthesis after a newline.
+ # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma = force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma = remove # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen = add # Add or remove space after ')' of control statements.
+sp_attribute_paren = add # Add or remove space between '__attribute__' and '('.
+sp_before_sparen = force # Add or remove space before '(' of control statements
+ # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren = force # Add or remove space between 'defined' and '(' in '#if defined
(FOO)'.
+sp_func_call_paren = force # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty = force # Add or remove space between function name and '()' on function calls
+ # without parameters. If set to ignore (the default),
sp_func_call_paren
is
+ # used.
+sp_func_def_paren = add # Add or remove space between alias name and '(' of a non-pointer
function
+ # type typedef.
+sp_func_proto_paren = add # Add or remove space between function name and '()' on function
declaration
+sp_sizeof_paren = force # Add or remove space between 'sizeof' and '('.
+sp_type_func = add # Add or remove space between return type and function name. A minimum
of
1
+ # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen = remove # Add or remove space between back-to-back parentheses, i.e. ')(' vs.
')
('.
+sp_inside_fparen = remove # Add or remove space inside function '(' and ')'.
+sp_inside_fparens = remove # Add or remove space inside empty function '()'.
+sp_inside_paren = remove # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast = remove # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square = remove # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren = remove # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen = remove # Add or remove space between ']' and '(' when part of a function
call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open = force # Add or remove space between 'do' and '{'.
+sp_paren_brace = force # Add or remove space between ')' and '{'.
+sp_sparen_brace = force # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref = remove # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref = add # Add or remove space before a reference sign '&'.
+sp_deref = remove # Add or remove space after the '*' (dereference) unary operator. This
does
+ # not affect the spacing after a '*' that is part of a type.
+sp_member = remove # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square = remove # Add or remove space before '[' (except '[]').
+sp_before_squares = remove # Add or remove space before '[]'.
+sp_before_vardef_square = remove # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool = true # Whether to fully parenthesize Boolean expressions in 'while' and
'if'
+ # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close = true # Whether to add a newline after '}'. Does not apply if followed by a
+ # necessary ';'.
+nl_brace_else = remove # Add or remove newline between '}' and 'else'.
+nl_brace_while = remove # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace = remove # Add or remove newline between 'do' and '{'.
+nl_else_brace = remove # Add or remove newline between 'else' and '{'.
+nl_else_if = remove # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace = remove # Add or remove newline between 'else if' and '{'.
+nl_enum_brace = remove # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace = remove # Add or remove newline between a function call's ')' and '{',
+ # as in 'list_for_each(item, &list) { }'.
+nl_for_brace = remove # Add or remove newline between 'for' and '{'.
+nl_if_brace = remove # Add or remove newline between 'if' and '{'.
+nl_struct_brace = remove # Add or remove newline between 'struct and '{'.
+nl_switch_brace = remove # Add or remove newline between 'switch' and '{'.
+nl_union_brace = remove # Add or remove newline between 'union' and '{'.
+nl_while_brace = remove # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star = remove # Add or remove space after pointer star '*', if followed by a word.
+ # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func = remove # Add or remove space after a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_after_semi = remove # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon = remove # Add or remove space before case ':'.
+sp_before_ptr_star = add # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func = add # Add or remove space before a pointer star '*', if followed by a
function
+ # prototype or function definition.
+sp_before_semi = remove # Add or remove space before ';'
+sp_before_semi_for = remove # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty = add # Add or remove space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star = remove # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while = force # Add or remove space between '}' and 'while'.
+
+sp_after_cast = remove
+sp_after_type = add
+sp_balance_nested_parens = false
+sp_before_nl_cont = add
+sp_before_square_asm_block = ignore
+sp_before_unnamed_byref = add
+sp_brace_brace = ignore
+sp_brace_else = force
+sp_brace_typedef = add
+sp_case_label = force
+sp_cmt_cpp_doxygen = true
+sp_cond_colon = add
+sp_cond_question = add
+sp_cpp_cast_paren = force
+sp_else_brace = force
+sp_endif_cmt = force
+sp_enum_assign = add
+sp_inside_braces = force
+sp_inside_braces_empty = force
+sp_inside_braces_enum = force
+sp_inside_braces_struct = force
+sp_pp_concat = add
+sp_pp_stringify = add
+sp_return_paren = add
+sp_special_semi = force
+sp_while_paren_open = force
+
+# Additional Indentation Rules
+indent_access_spec = 1
+indent_access_spec_body = false
+indent_align_assign = true
+indent_align_string = true
+indent_bool_paren = true
+indent_brace_parent = false
+indent_braces = false
+indent_braces_no_class = false
+indent_braces_no_func = true
+indent_braces_no_struct = false
+indent_class = false
+indent_class_colon = false
+indent_cmt_with_tabs = false # Whether to indent comments that are not at a brace level with
tabs
on
+ # a tabstop. Requires indent_with_tabs=2. If false, will use
spaces.
+indent_col1_comment = true
+indent_col1_multi_string_literal= true
+indent_comma_paren = true
+indent_else_if = true
+indent_extern = false
+indent_first_bool_expr = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double = false
+indent_func_proto_param = true
+indent_ignore_asm_block = true
+indent_label = 1
+indent_member = 2
+indent_namespace = false
+indent_param = 2
+indent_paren_nl = false
+indent_paren_open_brace = false
+indent_preserve_sql = false
+indent_relative_single_line_comments = false
+indent_sing_line_comments = 0
+indent_single_newlines = false
+indent_square_nl = false
+indent_switch_case = 2
+indent_template_param = true
+indent_var_def_blk = 0
+indent_var_def_cont = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break = true # Whether to move a 'break' that appears after a fully braced 'case'
+ # before the close brace, as in 'case X: { ... } break;' =>
+ # 'case X: { ... break; }'.
+mod_pawn_semicolon = false
+mod_remove_empty_return = false # Whether to remove a void 'return;' that appears as the last
statement
+ # in a function.
+mod_remove_extra_semicolon = true
+mod_sort_import = false
+mod_sort_include = false
+mod_sort_using = false
+nl_after_case = false # Whether to add a newline after a 'case' statement.
+nl_end_of_file = force # Add or remove newline at the end of the file.
+nl_end_of_file_min = 1 # The minimum number of newlines at the end of the file
+nl_max = 2 # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file = remove # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon = false
+align_assign_span = 1 # The span for aligning on '=' in assignments.
+align_assign_thresh = 4
+align_edk2_style = true # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span = 1 # The span for aligning on '=' in enums.
+align_func_params = true # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap = 2
+align_func_params_span = 2 # The span for aligning parameter definitions in function on parameter
name.
+align_func_params_thresh = 0
+align_func_proto_span = 0
+align_keep_tabs = false
+align_left_shift = false
+align_mix_var_proto = false
+align_nl_cont = false
+align_oc_decl_colon = false
+align_on_operator = false
+align_on_tabstop = false
+align_pp_define_gap = 2
+align_pp_define_span = 1
+align_right_cmt_at_col = 0 # Align trailing comment at or beyond column N; 'pulls in' comments as
+ # a bonus side effect (0=ignore)
+align_right_cmt_gap = 0 # If a trailing comment is more than this number of columns away from
the
+ # text it follows,
+ # it will qualify for being aligned. This has to be > 0 to do
anything.
+align_right_cmt_mix = false # If aligning comments, mix with comments after '}' and #endif with
less
+ # than 3 spaces before the comment
+align_right_cmt_same_level = true # Whether to only align trailing comments that are at the same brace
level.
+align_right_cmt_span = 2 # The span for aligning comments that end lines.
+align_same_func_call_params = false
+align_single_line_brace = true
+align_single_line_func = true
+align_struct_init_span = 1 # The span for aligning struct initializer values.
+align_typedef_amp_style = 1
+align_typedef_func = 1 # How to align typedef'd functions with other typedefs.
+ # (0: No align, 1: Align open paranthesis, 2: Align function type
name)
+align_typedef_gap = 2
+align_typedef_span = 1 # The span for aligning single-line typedefs.
+align_typedef_star_style = 1
+align_var_def_amp_style = 1
+align_var_def_attribute = true
+align_var_def_colon = true # Whether to align the colon in struct bit fields.
+align_var_def_gap = 2 # The gap (minimum spacing for aligned items) for variable
definitions.
+align_var_def_inline = false
+align_var_def_span = 1 # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style = 1 # How to consider (or treat) the '*' in the alignment of variable
+ # definitions.
+ # 0: Part of the type 'void * foo;' (default)
+ # 1: Part of the variable 'void *foo;'
+ # 2: Dangling 'void *foo;'
+ # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap = 4
+align_var_struct_span = 8 # The span for aligning struct/union member definitions.
+align_var_struct_thresh = 0
+align_with_tabs = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags = true # Whether to align doxygen javadoc-style tags ('@param', '@return',
etc.)
+ # TODO: Eats '[' in '[in]'
+cmt_c_group = false
+cmt_c_nl_end = true # Whether to add a newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start = true
+cmt_cpp_group = false
+cmt_cpp_nl_end = true
+cmt_cpp_nl_start = true
+cmt_cpp_to_c = false
+cmt_indent_multi = false # Whether to apply changes to multi-line comments, including
cmt_width,
+ # keyword substitution and leading chars.
+cmt_insert_before_preproc = false
+cmt_insert_file_header = default_file_header.txt
+cmt_insert_func_header = default_function_header.txt
+cmt_multi_check_last = false
+cmt_multi_first_len_minimum = 2
+cmt_reflow_mode = 1 # How to reflow comments.
+ # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont = 0 # The number of spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont = 0 # The number of spaces to insert at the start of subsequent comment
lines.
+cmt_star_cont = false # Whether to put a star on subsequent comment lines.
+cmt_width = 120 # Try to wrap comments at N columns.
+sp_cmt_cpp_start = add # Add or remove space after the opening of a C++ comment, as in
+ # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param = false # Whether to indent continued function call parameters one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_class_param = false # Whether to indent continued function call declaration one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param = false # Whether to indent continued class variable constructors one indent
level,
+ # rather than aligning parameters under the open parenthesis.
+indent_func_def_param = true # Whether to indent continued function definition parameters one
indent
+ # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace = add # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line = true # Whether to add a newline before ')' in a function call if '(' and
')'
are
+ # in different lines.
+nl_func_call_paren = remove # Add or remove newline between a function name and the opening '(' in
the
+ # call.
+nl_func_call_start_multi_line = true # Whether to add a newline after '(' in a function call if '(' and ')'
are
+ # in different lines.
+nl_func_decl_args = force # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty = add # Add or remove newline between '()' in a function declaration.
+nl_func_def_args = force # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty = add # Add or remove newline between '()' in a function definition.
+nl_func_def_paren = remove # Add or remove newline between a function name and the opening '('
+ # in the definition.
+nl_func_paren = remove # Add or remove newline between a function name and the opening '(' in
+ # the declaration.
+nl_func_type_name = add # Add or remove newline between return type and function name in a
function
+ # definition.
+sp_fparen_brace = force # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param = true # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open = true # Whether to add a newline after '{'. This also adds a
newline
+ # before the matching '}'.
+nl_after_brace_open_cmt = true # Whether to add a newline between the open brace and a
+ # trailing single-line comment.
+ # Requires nl_after_brace_open = true.
+nl_after_do = add # Add or remove blank line after 'do/while' statement.
+nl_after_for = add # Add or remove blank line after 'for' statement.
+nl_after_func_body = 2 # The number of newlines after '}' of a multi-line
function
body
+nl_after_func_body_one_liner = 2
+nl_after_func_proto = 2
+nl_after_func_proto_group = 2
+nl_after_if = add
+nl_after_multiline_comment = false
+nl_after_return = false
+nl_after_struct = 2
+nl_after_switch = add
+nl_after_vbrace_close = true
+nl_after_vbrace_open = true
+nl_after_vbrace_open_empty = true
+nl_after_while = add
+nl_assign_leave_one_liners = true
+nl_before_block_comment = 2
+nl_before_case = false
+nl_before_do = ignore
+nl_before_for = ignore
+nl_before_if = ignore
+nl_before_switch = ignore
+nl_before_while = ignore
+nl_before_whole_file_ifdef = 2
+nl_brace_brace = force
+nl_brace_struct_var = remove
+nl_case_colon_brace = add
+nl_class_leave_one_liners = false
+nl_collapse_empty_body = false
+nl_comment_func_def = 1
+nl_create_for_one_liner = false
+nl_create_if_one_liner = false
+nl_create_while_one_liner = false
+nl_define_macro = false
+nl_ds_struct_enum_close_brace = true
+nl_ds_struct_enum_cmt = false
+nl_enum_leave_one_liners = false
+nl_func_decl_end = add
+nl_func_decl_start = add
+nl_func_def_end = add
+nl_func_def_start = add
+nl_func_leave_one_liners = false
+nl_func_proto_type_name = add
+nl_func_var_def_blk = 1
+nl_getset_leave_one_liners = false
+nl_if_leave_one_liners = false
+nl_multi_line_define = false
+nl_squeeze_ifdef = false
+nl_var_def_blk_end = 0
+nl_var_def_blk_start = 0
+
+# Preprocessor Rules
+pp_define_at_level = true
+pp_if_indent_code = false
+pp_indent_func_def = false
+pp_indent_extern = false
+pp_ignore_define_body = true # Workaround: Turn off processing for #define body
+ # (current rules do not work for some defines)
+pp_indent = add
+pp_indent_at_level = true
+pp_indent_count = 2
+pp_indent_if = 2
+pp_indent_region = 2
+pp_region_indent_code = false
+pp_space = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "id": "uncrustify-ci-1",
+ "scope": "cibuild",
+ "type": "nuget",
+ "name": "mu-uncrustify-release",
+ "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+ "version": "73.0.3",
+ "flags": ["set_shell_var", "host_specific"],
+ "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+ "scope": "cibuild",
+ "name": "Uncrustify Coding Standard Test",
+ "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
## PyTool Scopes

Scopes are how the PyTool ext_dep, path_env, and plugins are activated. Meaning
--
2.28.0.windows.1







Michael Kubacki
 

I sent the v5 patch with the templates disabled:
https://edk2.groups.io/g/devel/message/84068

I also posted that patch on this branch:
https://github.com/makubacki/edk2/tree/add_uncrustify_ci_plugin_v5

I posted another branch that also has the commit to enable the templates here:
https://github.com/makubacki/edk2/commits/add_uncrustify_ci_plugin_v5_w_template_enable_commit

I will be out of office until Mon Nov. 29th for the US holiday.

Thanks,
Michael

On 11/24/2021 7:24 PM, Michael Kubacki wrote:
Hi Mike,
How about I quickly send a v5 with the lines commented out and follow up with a patch to uncomment them that we can review but merge when ready?
Thanks,
Michael
On 11/24/2021 7:16 PM, Michael D Kinney wrote:
The 2nd option I listed won't work.  That will fail UncrustifyCheck as well because EDK II CI
will insert missing file/function headers and the files will not match.

We will need to comment out these 2 lines and submit an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 4:08 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Hi Michael,

Unfortunately, enabling the templates in the uncrustify.cfg files modifies 340 files with missing
file/function headers that will now fail UncrustifyCheck.

     cmt_insert_file_header          = default_file_header.txt
     cmt_insert_func_header          = default_function_header.txt

Perhaps we should leave this feature off to get the uncrustify format changes committed and
enable this feature immediately after so patch reviews after that will slowly fix these missing
file/function headers.

Or I can comment these two lines out when I generate the final version of the patch series so
we don’t have to do an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 3:06 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Thanks.  That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg.  Was that in purpose?  Does it change the format?


-nl_func_call_start_multi_line   = true      # Whether to add a newline after '(' in a function call if '(' and ')'
are
-                                            # in different lines.


-nl_func_call_args_multi_line                 = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
      V4 changes:

      1. Updated commit subject to use the new plugin name.
      2. Enabled file and function header templates to be placed in
         files missing a file or function header (comment block).
      3. Added support to detect whether a file header or function
         header is missing in a file by searching files modified by
         Uncrustify for the template file text.
      4. Updated the plugin name displayed in some log output messages.

      V3 changes:

      1. Renamed the CI plugin to "UncrustifyCheck" from "Uncrustify"
         to follow similar naming conventions used in other plugins
         that check files
      2. Added a clarifying paragraph to the beginning of the Uncrustify
         CI plugin Readme.md file that the plugin is enabled by default
         and test results can be ignored by enabling "AuditOnly" mode.
      3. Added instructions in the commit message on how to map the
         version reported by the Uncrustify application (--version) to
         the version specified in the CI plugin external dependency
         YAML file.

      V2 changes:

      1. Changed plugin temp directory to Build/.pytool/Plugin/Uncrustify
      2. Added TCBZ REF in patch commit message

   .pytool/Plugin/UncrustifyCheck/Readme.md                   | 120 ++++
   .pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py          | 617 ++++++++++++++++++++
   .pytool/Plugin/UncrustifyCheck/default_file_header.txt     | 9 +
   .pytool/Plugin/UncrustifyCheck/default_function_header.txt | 15 +
   .pytool/Plugin/UncrustifyCheck/uncrustify.cfg              | 462 +++++++++++++++
   .pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml     | 16 +
   .pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml     | 11 +
   .pytool/Readme.md                                          | 4 +
   8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify executable that should be used for this plugin to
+be specified in an environment variable named `UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected build for use by this plugin.
+
+By default, an Uncrustify configuration file named "uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation: https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation: https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found no changes are made to the list of discovered
files.
+
+To control the paths checked in a given package, review the configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration options.
+
+``` yaml
+  "UncrustifyCheck": {
+      "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+      "AuditOnly": False,           # Don't fail the build if there are errors.  Just log them.
+      "ConfigFilePath": "",         # Custom path to an Uncrustify config file.
+      "IgnoreStandardPaths": [],    # Standard Plugin defined paths that should be ignored.
+      "OutputFileDiffs": False,     # Output chunks of formatting diffs in the test case log.
+                                    # This can significantly slow down the plugin on very large packages.
+      "SkipGitExclusions": False    # Don't exclude git ignored files and files in git submodules.
+  }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to be included with this option.
+
+At this time, it is recommended all files run against the plugin be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log all errors but instead of failing the build, it
will
set
+the test as skipped. This allows visibility into the failures without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of results (e.g. >100 files) can significantly slow
down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+  1. A working directory in the directory `Build/.pytool/Plugin/Uncrustify`
+  2. For each source file with formatting errors, a sibling file with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to operation of the plugin. All of these files are removed
on
+exit of the plugin including successful or unsuccessful execution (such as a Python exception occurring). If for
any
+reason, any files in the package exist prior to running the plugin with the `.uncrustify_plugin` extension, the
plugin
+will inform the user to remove these files and exit before running Uncrustify. This is to ensure the accuracy of
the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with Uncrustify and then invokes Uncrustify with that
file
+list. For any files not compliant to the configuration file provided, Uncrustify will generate a corresponding
file
+with the `.uncrustify_plugin` extension. The plugin discovers all of these files. If any such files are present,
this
+indicates a formatting issue was found and the test is marked failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to format properly, allowing the user to run
Uncrustify
+against the file locally to fix the issue. If the `OutputFileDiffs` configuration option is set to `True`, the
plugin
+will output diff chunks for all code formatting issues in the test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import  RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+    def __init__(self, message, exit_code):
+        super().__init__(message)
+        self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -103)
+
+
+class UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -120)
+
+
+class UncrustifyInputFileCreationErrorException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -121)
+
+class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+    """
+    A CiBuildPlugin that uses Uncrustify to check the source files in the
+    package being tested for coding standard issues.
+
+    By default, the plugin runs against standard C source file extensions but
+    its configuration can be modified through its configuration file.
+
+    Configuration options:
+    "UncrustifyCheck": {
+        "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
+        "AuditOnly": False,           # Don't fail the build if there are errors.  Just log them.
+        "ConfigFilePath": "",         # Custom path to an Uncrustify config file.
+        "IgnoreStandardPaths": [],    # Standard Plugin defined paths that should be ignored.
+        "OutputFileDiffs": False,     # Output chunks of formatting diffs in the test case log.
+                                      # This can significantly slow down the plugin on very large packages.
+        "SkipGitExclusions": False    # Don't exclude git ignored files and files in git submodules.
+    }
+    """
+
+    #
+    # By default, use an "uncrustify.cfg" config file in the plugin directory
+    # A package can override this path via "ConfigFilePath"
+    #
+    # Note: Values specified via "ConfigFilePath" are relative to the package
+    #
+    DEFAULT_CONFIG_FILE_PATH = os.path.join(
+        pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+    #
+    # The extension used for formatted files produced by this plugin
+    #
+    FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+    #
+    # A package can add any additional paths with "AdditionalIncludePaths"
+    # A package can remove any of these paths with "IgnoreStandardPaths"
+    #
+    STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+    #
+    # The Uncrustify application path should set in this environment variable
+    #
+    UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+    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 file coding standard compliance in " + packagename, packagename + ".UncrustifyCheck")
+
+    def RunBuildPlugin(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager, plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+        """
+        External function of plugin. This function is used to perform the task of the CiBuild Plugin.
+
+        Args:
+          - package_rel_path: edk2 workspace relative path to the package
+          - edk2_path: Edk2Path object with workspace and packages paths
+          - package_config: Dictionary with the package configuration
+          - environment_config: Environment configuration
+          - plugin_manager: Plugin Manager Instance
+          - plugin_manager_helper: Plugin Manager Helper Instance
+          - tc: JUnit test case
+          - output_stream: The StringIO output stream from this plugin (logging)
+
+        Returns
+          >0 : Number of errors found
+          0  : Passed successfully
+          -1 : Skipped for missing prereq
+        """
+        try:
+            # Initialize plugin and check pre-requisites.
+            self._initialize_environment_info(
+                package_rel_path, edk2_path, package_config, tc)
+            self._initialize_configuration()
+            self._check_for_preexisting_formatted_files()
+
+            # Log important context information.
+            self._log_uncrustify_app_info()
+
+            # Get template file contents if specified
+            self._get_template_file_contents()
+
+            # Create meta input files & directories
+            self._create_temp_working_directory()
+            self._create_uncrustify_file_list_file()
+
+            self._run_uncrustify()
+
+            # Post-execution actions.
+            self._process_uncrustify_results()
+
+        except UncrustifyException as e:
+            self._tc.LogStdError(
+                f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+            logging.warning(
+                f"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
+            return -1
+        else:
+            if self._formatted_file_error_count > 0:
+                if self._audit_only_mode:
+                    logging.info(
+                        "Setting test as skipped since AuditOnly is enabled")
+                    self._tc.SetSkipped()
+                    return -1
+                else:
+                    self._tc.SetFailed(
+                        f"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly
formatted
files.", "CHECK_FAILED")
+            else:
+                self._tc.SetSuccess()
+            return self._formatted_file_error_count
+        finally:
+            self._cleanup_temporary_formatted_files()
+            self._cleanup_temporary_directory()
+
+    def _initialize_configuration(self) -> None:
+        """
+        Initializes plugin configuration.
+        """
+        self._initialize_app_info()
+        self._initialize_config_file_info()
+        self._initialize_file_to_format_info()
+        self._initialize_test_case_output_options()
+
+    def _check_for_preexisting_formatted_files(self) -> None:
+        """
+        Checks if any formatted files from prior execution are present.
+
+        Existence of such files is an unexpected condition. This might result
+        from an error that occurred during a previous run or a premature exit from a debug scenario. In any case,
the
package should be clean before starting a new run.
+        """
+        pre_existing_formatted_file_count = len(
+            [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
+
+        if pre_existing_formatted_file_count > 0:
+            raise UncrustifyStalePluginFormattedFilesException(
+                f"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+    def _cleanup_temporary_directory(self) -> None:
+        """
+        Cleans up the temporary directory used for this execution instance.
+
+        This removes the directory and all files created during this instance.
+        """
+        if hasattr(self, '_working_dir'):
+            self._remove_tree(self._working_dir)
+
+    def _cleanup_temporary_formatted_files(self) -> None:
+        """
+        Cleans up the temporary formmatted files produced by Uncrustify.
+
+        This will recursively remove all formatted files generated by Uncrustify
+        during this execution instance.
+        """
+        if hasattr(self, '_abs_package_path'):
+            formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+            for formatted_file in formatted_files:
+                os.remove(formatted_file)
+
+    def _create_temp_working_directory(self) -> None:
+        """
+        Creates the temporary directory used for this execution instance.
+        """
+        self._working_dir = os.path.join(
+            self._abs_workspace_path, "Build", ".pytool", "Plugin", f"{self._plugin_name}")
+
+        try:
+            pathlib.Path(self._working_dir).mkdir(parents=True, exist_ok=True)
+        except OSError as e:
+            raise UncrustifyInputFileCreationErrorException(
+                f"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
+
+    def _create_uncrustify_file_list_file(self) -> None:
+        """
+        Creates the file with the list of source files for Uncrustify to process.
+        """
+        self._app_input_file_path = os.path.join(
+            self._working_dir, "uncrustify_file_list.txt")
+
+        with open(self._app_input_file_path, 'w', encoding='utf8') as f:
+ f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+    def _execute_uncrustify(self) -> None:
+        """
+        Executes Uncrustify with the initialized configuration.
+        """
+        output = StringIO()
+        self._app_exit_code = RunCmd(
+            self._app_path,
+            f"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+        self._app_output = output.getvalue().strip().splitlines()
+
+    def _get_git_ignored_paths(self) -> List[str]:
+        """"
+        Returns a list of file absolute path strings to all files ignored in this git repository.
+
+        If git is not found, an empty list will be returned.
+        """
+        if not shutil.which("git"):
+            logging.warn(
+                "Git is not found on this system. Git submodule paths will not be considered.")
+            return []
+
+        outstream_buffer = StringIO()
+        exit_code = RunCmd("git", "ls-files --other",
+                           workingdir=self._abs_workspace_path, outstream=outstream_buffer,
logging_level=logging.NOTSET)
+        if (exit_code != 0):
+            raise UncrustifyGitIgnoreFileException(
+                f"An error occurred reading git ignore settings. This will prevent Uncrustify from running against
the
expected set of files.")
+
+        # Note: This will potentially be a large list, but at least sorted
+        return outstream_buffer.getvalue().strip().splitlines()
+
+    def _get_git_submodule_paths(self) -> List[str]:
+        """
+        Returns a list of directory absolute path strings to the root of each submodule in the workspace
repository.
+
+        If git is not found, an empty list will be returned.
+        """
+        if not shutil.which("git"):
+            logging.warn(
+                "Git is not found on this system. Git submodule paths will not be considered.")
+            return []
+
+        if os.path.isfile(os.path.join(self._abs_workspace_path, ".gitmodules")):
+            logging.info(
+                f".gitmodules file found. Excluding submodules in {self._package_name}.")
+
+            outstream_buffer = StringIO()
+            exit_code = RunCmd("git", "config --file .gitmodules --get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+            if (exit_code != 0):
+                raise UncrustifyGitSubmoduleException(
+                    f".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+            submodule_paths = []
+            for line in outstream_buffer.getvalue().strip().splitlines():
+                submodule_paths.append(
+ os.path.normpath(os.path.join(self._abs_workspace_path, line.split()[1])))
+
+            return submodule_paths
+        else:
+            return []
+
+    def _get_template_file_contents(self) -> None:
+        """
+        Gets the contents of Uncrustify template files if they are specified
+        in the Uncrustify configuration file.
+        """
+
+        self._file_template_contents = None
+        self._func_template_contents = None
+
+        # Allow no value to allow "set" statements in the config file which do
+        # not specify value assignment
+        parser = configparser.ConfigParser(allow_no_value=True)
+        with open(self._app_config_file, 'r') as cf:
+            parser.read_string("[dummy_section]\n" + cf.read())
+
+        try:
+            file_template_name = parser["dummy_section"]["cmt_insert_file_header"]
+
+            file_template_path = pathlib.Path(file_template_name)
+
+            if not file_template_path.is_file():
+                file_template_path = pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+                self._file_template_contents = file_template_path.read_text()
+        except KeyError:
+            logging.warn("A file header template is not specified in the config file.")
+        except FileNotFoundError:
+            logging.warn("The specified file header template file was not found.")
+        try:
+            func_template_name = parser["dummy_section"]["cmt_insert_func_header"]
+
+            func_template_path = pathlib.Path(func_template_name)
+
+            if not func_template_path.is_file():
+                func_template_path = pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+                self._func_template_contents = func_template_path.read_text()
+        except KeyError:
+            logging.warn("A function header template is not specified in the config file.")
+        except FileNotFoundError:
+            logging.warn("The specified function header template file was not found.")
+
+    def _initialize_app_info(self) -> None:
+        """
+        Initialize Uncrustify application information.
+
+        This function will determine the application path and version.
+        """
+        # Verify Uncrustify is specified in the environment.
+        if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in os.environ:
+            raise UncrustifyAppEnvVarNotFoundException(
+                f"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+        self._app_path = shutil.which('uncrustify', path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+        if self._app_path is None:
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+        self._app_path = os.path.normcase(os.path.normpath(self._app_path))
+
+        if not os.path.isfile(self._app_path):
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT), self._app_path)
+
+        # Verify Uncrustify is present at the expected path.
+        return_buffer = StringIO()
+        ret = RunCmd(self._app_path, "--version", outstream=return_buffer)
+        if (ret != 0):
+            raise UncrustifyAppVersionErrorException(
+                f"Error occurred executing --version: {ret}.")
+
+        # Log Uncrustify version information.
+        self._app_version = return_buffer.getvalue().strip()
+        self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+        version_aggregator.GetVersionAggregator().ReportVersion(
+            "Uncrustify", self._app_version, version_aggregator.VersionTypes.INFO)
+
+    def _initialize_config_file_info(self) -> None:
+        """
+        Initialize Uncrustify configuration file info.
+
+        The config file path is relative to the package root.
+        """
+        self._app_config_file = UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+        if "ConfigFilePath" in self._package_config:
+            self._app_config_file = self._package_config["ConfigFilePath"].strip()
+
+            self._app_config_file = os.path.normpath(
+                os.path.join(self._abs_package_path, self._app_config_file))
+
+        if not os.path.isfile(self._app_config_file):
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT), self._app_config_file)
+
+    def _initialize_environment_info(self, package_rel_path: str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+        """
+        Initializes plugin environment information.
+        """
+        self._abs_package_path = edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+            package_rel_path)
+        self._abs_workspace_path = edk2_path.WorkspacePath
+        self._package_config = package_config
+        self._package_name = os.path.basename(
+            os.path.normpath(package_rel_path))
+        self._plugin_name = self.__class__.__name__
+        self._plugin_path = os.path.dirname(os.path.realpath(__file__))
+        self._rel_package_path = package_rel_path
+        self._tc = tc
+
+    def _initialize_file_to_format_info(self) -> None:
+        """
+        Forms the list of source files for Uncrustify to process.
+        """
+        # Create a list of all the package relative file paths in the package to run against Uncrustify.
+        rel_file_paths_to_format = list(
+            UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+        # Allow the ci.yaml to remove any of the pre-defined standard paths
+        if "IgnoreStandardPaths" in self._package_config:
+            for a in self._package_config["IgnoreStandardPaths"]:
+                if a.strip() in rel_file_paths_to_format:
+                    self._tc.LogStdOut(
+                        f"Ignoring standard path due to ci.yaml ignore: {a}")
+                    rel_file_paths_to_format.remove(a.strip())
+                else:
+                    raise UncrustifyInvalidIgnoreStandardPathsException(f"Invalid IgnoreStandardPaths value: {a}")
+
+        # Allow the ci.yaml to specify additional include paths for this package
+        if "AdditionalIncludePaths" in self._package_config:
+            rel_file_paths_to_format.extend(
+                self._package_config["AdditionalIncludePaths"])
+
+        self._abs_file_paths_to_format = []
+        for path in rel_file_paths_to_format:
+            self._abs_file_paths_to_format.extend(
+                [str(path.resolve()) for path in pathlib.Path(self._abs_package_path).rglob(path)])
+
+        if not "SkipGitExclusions" in self._package_config or not self._package_config["SkipGitExclusions"]:
+            # Remove files ignored by git
+            logging.info(
+                f"{self._package_name} file count before git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            ignored_paths = self._get_git_ignored_paths()
+            self._abs_file_paths_to_format = list(
+ set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+            logging.info(
+                f"{self._package_name} file count after git ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            # Remove files in submodules
+            logging.info(
+                f"{self._package_name} file count before submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            submodule_paths = tuple(self._get_git_submodule_paths())
+            for path in submodule_paths:
+                logging.info(f"  submodule path: {path}")
+
+            self._abs_file_paths_to_format = [
+                f for f in self._abs_file_paths_to_format if not f.startswith(submodule_paths)]
+
+            logging.info(
+                f"{self._package_name} file count after submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+        # Sort the files for more consistent results
+        self._abs_file_paths_to_format.sort()
+
+    def _initialize_test_case_output_options(self) -> None:
+        """
+        Initializes options that influence test case output.
+        """
+        self._audit_only_mode = False
+        self._output_file_diffs = False
+
+        if "AuditOnly" in self._package_config and self._package_config["AuditOnly"]:
+            self._audit_only_mode = True
+
+        if "OutputFileDiffs" in self._package_config and self._package_config["OutputFileDiffs"]:
+            self._output_file_diffs = True
+
+    def _log_uncrustify_app_info(self) -> None:
+        """
+        Logs Uncrustify application information.
+        """
+        self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+        self._tc.LogStdOut(f"Uncrustify version: {self._app_version}")
+        self._tc.LogStdOut('\n')
+        logging.info(f"Found Uncrustify at {self._app_path}")
+        logging.info(f"Uncrustify version: {self._app_version}")
+        logging.info('\n')
+
+    def _process_uncrustify_results(self) -> None:
+        """
+        Process the results from Uncrustify.
+
+        Determines whether formatting errors are present and logs failures.
+        """
+        formatted_files = [str(path.resolve()) for path in pathlib.Path(
+ self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
+
+        self._formatted_file_error_count = len(formatted_files)
+
+        if self._formatted_file_error_count > 0:
+            self._tc.LogStdError("Files with formatting errors:\n")
+
+            if self._output_file_diffs:
+                logging.info("Calculating file diffs. This might take a while...")
+
+        for formatted_file in formatted_files:
+            pre_formatted_file = formatted_file[:-
+ len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+            if (self._output_file_diffs or
+                    self._file_template_contents is not None or
+                    self._func_template_contents is not None):
+                self._tc.LogStdError(
+                    f"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+                with open(formatted_file) as ff:
+                    formatted_file_text = ff.read()
+
+                    if (self._file_template_contents is not None and
+                            self._file_template_contents in formatted_file_text):
+                        self._tc.LogStdError(f"File header is missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+                    if (self._func_template_contents is not None and
+                            self._func_template_contents in formatted_file_text):
+                        self._tc.LogStdError(f"A function header is missing in
{os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+                    if self._output_file_diffs:
+                        with open(pre_formatted_file) as pf:
+                            pre_formatted_file_text = pf.read()
+
+                        for line in difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file, tofile=formatted_file, n=3):
+                            self._tc.LogStdError(line)
+
+                        self._tc.LogStdError('\n')
+            else:
+                self._tc.LogStdError(pre_formatted_file)
+
+    def _remove_tree(self, dir_path: str, ignore_errors: bool = False) -> None:
+        """
+        Helper for removing a directory. Over time there have been
+        many private implementations of this due to reliability issues in the
+        shutil implementations. To consolidate on a single function this helper is added.
+
+        On error try to change file attributes. Also add retry logic.
+
+        This function is temporarily borrowed from edk2toollib.utility_functions
+        since the version used in edk2 is not recent enough to include the
+        function.
+
+        This function should be replaced by "RemoveTree" when it is available.
+
+        Args:
+          - dir_path: Path to directory to remove.
+          - ignore_errors: Whether to ignore errors during removal
+        """
+
+        def _remove_readonly(func, path, _):
+            """
+            Private function to attempt to change permissions on file/folder being deleted.
+            """
+            os.chmod(path, os.stat.S_IWRITE)
+            func(path)
+
+        for _ in range(3):  # retry up to 3 times
+            try:
+                shutil.rmtree(dir_path, ignore_errors=ignore_errors, onerror=_remove_readonly)
+            except OSError as err:
+                logging.warning(f"Failed to fully remove {dir_path}: {err}")
+            else:
+                break
+        else:
+            raise RuntimeError(f"Failed to remove {dir_path}")
+
+    def _run_uncrustify(self) -> None:
+        """
+        Runs Uncrustify for this instance of plugin execution.
+        """
+        logging.info("Executing Uncrustify. This might take a while...")
+        start_time = timeit.default_timer()
+        self._execute_uncrustify()
+        end_time = timeit.default_timer() - start_time
+
+        execution_summary = f"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+        self._tc.LogStdOut(execution_summary)
+        logging.info(execution_summary)
+
+        if self._app_exit_code != 0 and self._app_exit_code != 1:
+            raise UncrustifyAppExecutionException(
+                f"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+  Brief description of the file's purpose.
+
+  Detailed description of the file's contents and other useful
+  information for a person viewing the file for the first time.
+
+  <<Copyright>>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+  Brief description of this function's purpose.
+
+  Follow it immediately with the detailed description.
+
+  @param[in]      Arg1  Description of Arg1.
+  @param[in]      Arg2  Description of Arg2 This is complicated and requires
+                        multiple lines to describe.
+  @param[out]     Arg3  Description of Arg3.
+  @param[in, out] Arg4  Description of Arg4.
+
+  @retval VAL_ONE  Description of what VAL_ONE signifies.
+  @retval OTHER    This is the only other return value. If there were other
+                   return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard: https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/
+#
+# This configuration file is meant to be a "best attempt" to align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs                 = false
+utf8_byte                       = false
+utf8_force                      = true
+
+# Code width / line splitting
+#code_width                      =120     # TODO: This causes non-deterministic behaviour in some cases when code
wraps
+ls_code_width                   =false
+ls_for_split_full               =true
+ls_func_split_full              =true
+pos_comma                       =trail
+
+# 5.1.7  All files must end with CRLF
+newlines                        = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces       = true      # Whether to convert all tabs to spaces in comments. If false, tabs in
+                                            # comments are left alone, unless used for indenting.
+indent_columns                  = 2         # Number of spaces for indentation
+indent_with_tabs                = 0         # Do not use TAB characters
+string_replace_tab_chars        = true      # Replace TAB with SPACE
+                                            # Note: This will break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement ends with ;)
+nl_multi_line_cond              = true      # Add a newline between ')' and '{' if the ')' is on a different line
than
+                                            # the if/for/etc.
+nl_after_semicolon              = true      # Whether to add a newline after semicolons, except in 'for'
statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing parenthesis ')' of simple predicate expressions
+mod_full_brace_do               = add       # Add or remove braces on a single-line 'do' statement.
+mod_full_brace_for              = add
+mod_full_brace_function         = add       # Add or remove braces on a single-line function definition.
+mod_full_brace_if               = add       # Add or remove braces on a single-line 'if' statement. Braces will
not
be
+                                            # removed if the braced statement contains an 'else'.
+mod_full_brace_if_chain         = false
+mod_full_brace_while            = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the last line of the body
+eat_blanks_after_open_brace     = true
+eat_blanks_before_close_brace   = true      # Whether to remove blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign                       = add       # Add or remove space around assignment operator '=', '+=', etc.
+sp_assign_default               = add
+sp_bool                         = add       # Add or remove space around boolean operators '&&' and '||'.
+sp_compare                      = add       # Add or remove space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr                         = remove    # A or remove space after the '&' (address-of) unary operator.
+sp_incdec                       = remove    # Add or remove space between '++' and '--' the word to which it is
being
+                                            # applied, as in '(--x)' or 'y++;'.
+sp_inv                          = remove    # Add or remove space after the '~' (invert) unary operator.
+sp_not                          = remove    # Add or remove space after the '!' (not) unary operator.
+sp_sign                         = remove    # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should line up two spaces from the beginning of the
function
+#         name
+nl_func_call_args_multi_line    = true      # Whether to add a newline after each ',' in a function call if '('
and
')'
+                                            # are in different lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function name. If a
+#   function is called through a structure or union member, of type
+#   pointer-to-function, then indent each argument 2 spaces from the start of the
+#   member name.
+indent_func_call_edk2_style     = true      # Use EDK2 indentation style for function calls  (**CUSTOM SETTING**)
+indent_paren_after_func_call    = true      # Whether to indent the open parenthesis of a function call, if the
+                                            # parenthesis is on its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close              = 0         # How to indent a close parenthesis after a newline.
+                                            # (0: Body, 1: Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that separate items
+sp_after_comma                  = force     # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma                 = remove    # Add or remove space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen                 = add       # Add or remove space after ')' of control statements.
+sp_attribute_paren              = add       # Add or remove space between '__attribute__' and '('.
+sp_before_sparen                = force     # Add or remove space before '(' of control statements
+                                            # ('if', 'for', 'switch', 'while', etc.).
+sp_defined_paren                = force     # Add or remove space between 'defined' and '(' in '#if defined
(FOO)'.
+sp_func_call_paren              = force     # Add or remove space between function name and '(' on function calls.
+sp_func_call_paren_empty        = force     # Add or remove space between function name and '()' on function calls
+                                            # without parameters. If set to ignore (the default),
sp_func_call_paren
is
+                                            # used.
+sp_func_def_paren               = add       # Add or remove space between alias name and '(' of a non-pointer
function
+                                            # type typedef.
+sp_func_proto_paren             = add       # Add or remove space between function name and '()' on function
declaration
+sp_sizeof_paren                 = force     # Add or remove space between 'sizeof' and '('.
+sp_type_func                    = add       # Add or remove space between return type and function name. A minimum
of
1
+                                            # is forced except for pointer return types.
+
+# Not specified, but also good style to remove spaces inside parentheses (Optional)
+sp_cparen_oparen                = remove    # Add or remove space between back-to-back parentheses, i.e. ')(' vs.
')
('.
+sp_inside_fparen                = remove    # Add or remove space inside function '(' and ')'.
+sp_inside_fparens               = remove    # Add or remove space inside empty function '()'.
+sp_inside_paren                 = remove    # Add or remove space inside '(' and ')'.
+sp_inside_paren_cast            = remove    # Add or remove spaces inside cast parentheses. '(int)x'
+sp_inside_square                = remove    # Add or remove space inside a non-empty '[' and ']'.
+sp_paren_paren                  = remove    # Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen                = remove    # Add or remove space between ']' and '(' when part of a function
call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its own line
+sp_do_brace_open                = force     # Add or remove space between 'do' and '{'.
+sp_paren_brace                  = force     # Add or remove space between ')' and '{'.
+sp_sparen_brace                 = force     # Add or remove space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer operators
+sp_after_byref                  = remove    # Add or remove space after reference sign '&', if followed by a word.
+sp_before_byref                 = add       # Add or remove space before a reference sign '&'.
+sp_deref                        = remove    # Add or remove space after the '*' (dereference) unary operator. This
does
+                                            # not affect the spacing after a '*' that is part of a type.
+sp_member                       = remove    # Add or remove space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array subscripts
+sp_before_square                = remove    # Add or remove space before '[' (except '[]').
+sp_before_squares               = remove    # Add or remove space before '[]'.
+sp_before_vardef_square         = remove    # Add or remove space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool          = true      # Whether to fully parenthesize Boolean expressions in 'while' and
'if'
+                                            # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line that it continues.
+use_indent_continue_only_once   = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some flexibility.
+nl_after_brace_close            = true      # Whether to add a newline after '}'. Does not apply if followed by a
+                                            # necessary ';'.
+nl_brace_else                   = remove    # Add or remove newline between '}' and 'else'.
+nl_brace_while                  = remove    # Add or remove newline between '}' and 'while' of 'do' statement.
+nl_do_brace                     = remove    # Add or remove newline between 'do' and '{'.
+nl_else_brace                   = remove    # Add or remove newline between 'else' and '{'.
+nl_else_if                      = remove    # Add or remove newline between 'else' and 'if'.
+nl_elseif_brace                 = remove    # Add or remove newline between 'else if' and '{'.
+nl_enum_brace                   = remove    # Add or remove newline between 'enum' and '{'.
+nl_fcall_brace                  = remove    # Add or remove newline between a function call's ')' and '{',
+                                            # as in 'list_for_each(item, &list) { }'.
+nl_for_brace                    = remove    # Add or remove newline between 'for' and '{'.
+nl_if_brace                     = remove    # Add or remove newline between 'if' and '{'.
+nl_struct_brace                 = remove    # Add or remove newline between 'struct and '{'.
+nl_switch_brace                 = remove    # Add or remove newline between 'switch' and '{'.
+nl_union_brace                  = remove    # Add or remove newline between 'union' and '{'.
+nl_while_brace                  = remove    # Add or remove newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star               = remove    # Add or remove space after pointer star '*', if followed by a word.
+                                            # Useful when paired with align_var_def_star_style==2
+sp_after_ptr_star_func          = remove    # Add or remove space after a pointer star '*', if followed by a
function
+                                            # prototype or function definition.
+sp_after_semi                   = remove    # Add or remove space after ';', except when followed by a comment.
+sp_before_case_colon            = remove    # Add or remove space before case ':'.
+sp_before_ptr_star              = add       # Add or remove space before pointer star '*'.
+sp_before_ptr_star_func         = add       # Add or remove space before a pointer star '*', if followed by a
function
+                                            # prototype or function definition.
+sp_before_semi                  = remove    # Add or remove space before ';'
+sp_before_semi_for              = remove    # Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty        = add       # Add or remove space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star             = remove    # Add or remove space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while            = force     # Add or remove space between '}' and 'while'.
+
+sp_after_cast                   = remove
+sp_after_type                   = add
+sp_balance_nested_parens        = false
+sp_before_nl_cont               = add
+sp_before_square_asm_block      = ignore
+sp_before_unnamed_byref         = add
+sp_brace_brace                  = ignore
+sp_brace_else                   = force
+sp_brace_typedef                = add
+sp_case_label                   = force
+sp_cmt_cpp_doxygen              = true
+sp_cond_colon                   = add
+sp_cond_question                = add
+sp_cpp_cast_paren               = force
+sp_else_brace                   = force
+sp_endif_cmt                    = force
+sp_enum_assign                  = add
+sp_inside_braces                = force
+sp_inside_braces_empty          = force
+sp_inside_braces_enum           = force
+sp_inside_braces_struct         = force
+sp_pp_concat                    = add
+sp_pp_stringify                 = add
+sp_return_paren                 = add
+sp_special_semi                 = force
+sp_while_paren_open             = force
+
+# Additional Indentation Rules
+indent_access_spec              = 1
+indent_access_spec_body         = false
+indent_align_assign             = true
+indent_align_string             = true
+indent_bool_paren               = true
+indent_brace_parent             = false
+indent_braces                   = false
+indent_braces_no_class          = false
+indent_braces_no_func           = true
+indent_braces_no_struct         = false
+indent_class                    = false
+indent_class_colon              = false
+indent_cmt_with_tabs            = false         # Whether to indent comments that are not at a brace level with
tabs
on
+                                                # a tabstop. Requires indent_with_tabs=2. If false, will use
spaces.
+indent_col1_comment             = true
+indent_col1_multi_string_literal= true
+indent_comma_paren              = true
+indent_else_if                  = true
+indent_extern                   = false
+indent_first_bool_expr          = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double                  = false
+indent_func_proto_param                   = true
+indent_ignore_asm_block                   = true
+indent_label                              = 1
+indent_member                             = 2
+indent_namespace                          = false
+indent_param                              = 2
+indent_paren_nl                           = false
+indent_paren_open_brace                   = false
+indent_preserve_sql                       = false
+indent_relative_single_line_comments      = false
+indent_sing_line_comments                 = 0
+indent_single_newlines                    = false
+indent_square_nl                          = false
+indent_switch_case                        = 2
+indent_template_param                     = true
+indent_var_def_blk                        = 0
+indent_var_def_cont                       = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break             = true      # Whether to move a 'break' that appears after a fully braced 'case'
+                                            # before the close brace, as in 'case X: { ... } break;' =>
+                                            # 'case X: { ... break; }'.
+mod_pawn_semicolon              = false
+mod_remove_empty_return         = false     # Whether to remove a void 'return;' that appears as the last
statement
+                                            # in a function.
+mod_remove_extra_semicolon      = true
+mod_sort_import                 = false
+mod_sort_include                = false
+mod_sort_using                  = false
+nl_after_case                   = false     # Whether to add a newline after a 'case' statement.
+nl_end_of_file                  = force     # Add or remove newline at the end of the file.
+nl_end_of_file_min              = 1         # The minimum number of newlines at the end of the file
+nl_max                          = 2         # The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file                = remove    # Add or remove newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon                 = false
+align_assign_span               = 1         # The span for aligning on '=' in assignments.
+align_assign_thresh             = 4
+align_edk2_style                = true      # Whether to apply edk2-specific alignment formatting
+align_enum_equ_span             = 1         # The span for aligning on '=' in enums.
+align_func_params               = true      # Whether to align variable definitions in prototypes and functions.
+align_func_params_gap           = 2
+align_func_params_span          = 2         # The span for aligning parameter definitions in function on parameter
name.
+align_func_params_thresh        = 0
+align_func_proto_span           = 0
+align_keep_tabs                 = false
+align_left_shift                = false
+align_mix_var_proto             = false
+align_nl_cont                   = false
+align_oc_decl_colon             = false
+align_on_operator               = false
+align_on_tabstop                = false
+align_pp_define_gap             = 2
+align_pp_define_span            = 1
+align_right_cmt_at_col          = 0         # Align trailing comment at or beyond column N; 'pulls in' comments as
+                                            # a bonus side effect (0=ignore)
+align_right_cmt_gap             = 0         # If a trailing comment is more than this number of columns away from
the
+                                            # text it follows,
+                                            # it will qualify for being aligned. This has to be > 0 to do
anything.
+align_right_cmt_mix             = false     # If aligning comments, mix with comments after '}' and #endif with
less
+                                            # than 3 spaces before the comment
+align_right_cmt_same_level      = true      # Whether to only align trailing comments that are at the same brace
level.
+align_right_cmt_span            = 2         # The span for aligning comments that end lines.
+align_same_func_call_params     = false
+align_single_line_brace         = true
+align_single_line_func          = true
+align_struct_init_span          = 1         # The span for aligning struct initializer values.
+align_typedef_amp_style         = 1
+align_typedef_func              = 1         # How to align typedef'd functions with other typedefs.
+                                            # (0: No align, 1: Align open paranthesis, 2: Align function type
name)
+align_typedef_gap               = 2
+align_typedef_span              = 1         # The span for aligning single-line typedefs.
+align_typedef_star_style        = 1
+align_var_def_amp_style         = 1
+align_var_def_attribute         = true
+align_var_def_colon             = true      # Whether to align the colon in struct bit fields.
+align_var_def_gap               = 2         # The gap (minimum spacing for aligned items) for variable
definitions.
+align_var_def_inline            = false
+align_var_def_span              = 1         # The span (lines needed to align) for aligning variable definitions.
+align_var_def_star_style        = 1         # How to consider (or treat) the '*' in the alignment of variable
+                                            # definitions.
+                                            # 0: Part of the type     'void *   foo;' (default)
+                                            # 1: Part of the variable 'void     *foo;'
+                                            # 2: Dangling             'void    *foo;'
+                                            # (Note - should also set sp_after_ptr_star=remove)
+align_var_struct_gap            = 4
+align_var_struct_span           = 8         # The span for aligning struct/union member definitions.
+align_var_struct_thresh         = 0
+align_with_tabs                 = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags  = true      # Whether to align doxygen javadoc-style tags ('@param', '@return',
etc.)
+                                            # TODO: Eats '[' in '[in]'
+cmt_c_group                     = false
+cmt_c_nl_end                    = true      # Whether to add a newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start                  = true
+cmt_cpp_group                   = false
+cmt_cpp_nl_end                  = true
+cmt_cpp_nl_start                = true
+cmt_cpp_to_c                    = false
+cmt_indent_multi                = false     # Whether to apply changes to multi-line comments, including
cmt_width,
+                                            # keyword substitution and leading chars.
+cmt_insert_before_preproc       = false
+cmt_insert_file_header          = default_file_header.txt
+cmt_insert_func_header          = default_function_header.txt
+cmt_multi_check_last            = false
+cmt_multi_first_len_minimum     = 2
+cmt_reflow_mode                 = 1         # How to reflow comments.
+                                            # (0:No reflow, 1:No touching at all, 2: Full reflow)
+cmt_sp_after_star_cont          = 0         # The number of spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont         = 0         # The number of spaces to insert at the start of subsequent comment
lines.
+cmt_star_cont                   = false     # Whether to put a star on subsequent comment lines.
+cmt_width                       = 120       # Try to wrap comments at N columns.
+sp_cmt_cpp_start                = add       # Add or remove space after the opening of a C++ comment, as in
+                                            # '// <here> A'. NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param          = false     # Whether to indent continued function call parameters one indent
level,
+                                            # rather than aligning parameters under the open parenthesis.
+indent_func_class_param         = false     # Whether to indent continued function call declaration one indent
level,
+                                            # rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param      = false     # Whether to indent continued class variable constructors one indent
level,
+                                            # rather than aligning parameters under the open parenthesis.
+indent_func_def_param           = true      # Whether to indent continued function definition parameters one
indent
+                                            # level, rather than aligning parameters under the open parenthesis.
+nl_fdef_brace                   = add       # Add or remove newline between function signature and '{'.
+nl_func_call_end_multi_line     = true      # Whether to add a newline before ')' in a function call if '(' and
')'
are
+                                            # in different lines.
+nl_func_call_paren              = remove    # Add or remove newline between a function name and the opening '(' in
the
+                                            # call.
+nl_func_call_start_multi_line   = true      # Whether to add a newline after '(' in a function call if '(' and ')'
are
+                                            # in different lines.
+nl_func_decl_args               = force     # Add or remove newline after each ',' in a function declaration.
+nl_func_decl_empty              = add       # Add or remove newline between '()' in a function declaration.
+nl_func_def_args                = force     # Add or remove newline after each ',' in a function definition.
+nl_func_def_empty               = add       # Add or remove newline between '()' in a function definition.
+nl_func_def_paren               = remove    # Add or remove newline between a function name and the opening '('
+                                            # in the definition.
+nl_func_paren                   = remove    # Add or remove newline between a function name and the opening '(' in
+                                            # the declaration.
+nl_func_type_name               = add       # Add or remove newline between return type and function name in a
function
+                                            # definition.
+sp_fparen_brace                 = force     # Add or remove space between ')' and '{' of function.
+use_indent_func_call_param      = true      # indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open                          = true     # Whether to add a newline after '{'. This also adds a
newline
+                                                        # before the matching '}'.
+nl_after_brace_open_cmt                      = true     # Whether to add a newline between the open brace and a
+                                                        # trailing single-line comment.
+                                                        # Requires nl_after_brace_open = true.
+nl_after_do                                  = add      # Add or remove blank line after 'do/while' statement.
+nl_after_for                                 = add      # Add or remove blank line after 'for' statement.
+nl_after_func_body                           = 2        # The number of newlines after '}' of a multi-line
function
body
+nl_after_func_body_one_liner                 = 2
+nl_after_func_proto                          = 2
+nl_after_func_proto_group                    = 2
+nl_after_if                                  = add
+nl_after_multiline_comment                   = false
+nl_after_return                              = false
+nl_after_struct                              = 2
+nl_after_switch                              = add
+nl_after_vbrace_close                        = true
+nl_after_vbrace_open                         = true
+nl_after_vbrace_open_empty                   = true
+nl_after_while                               = add
+nl_assign_leave_one_liners                   = true
+nl_before_block_comment                      = 2
+nl_before_case                               = false
+nl_before_do                                 = ignore
+nl_before_for                                = ignore
+nl_before_if                                 = ignore
+nl_before_switch                             = ignore
+nl_before_while                              = ignore
+nl_before_whole_file_ifdef                   = 2
+nl_brace_brace                               = force
+nl_brace_struct_var                          = remove
+nl_case_colon_brace                          = add
+nl_class_leave_one_liners                    = false
+nl_collapse_empty_body                       = false
+nl_comment_func_def                          = 1
+nl_create_for_one_liner                      = false
+nl_create_if_one_liner                       = false
+nl_create_while_one_liner                    = false
+nl_define_macro                              = false
+nl_ds_struct_enum_close_brace                = true
+nl_ds_struct_enum_cmt                        = false
+nl_enum_leave_one_liners                     = false
+nl_func_decl_end                             = add
+nl_func_decl_start                           = add
+nl_func_def_end                              = add
+nl_func_def_start                            = add
+nl_func_leave_one_liners                     = false
+nl_func_proto_type_name                      = add
+nl_func_var_def_blk                          = 1
+nl_getset_leave_one_liners                   = false
+nl_if_leave_one_liners                       = false
+nl_multi_line_define                         = false
+nl_squeeze_ifdef                             = false
+nl_var_def_blk_end                           = 0
+nl_var_def_blk_start                         = 0
+
+# Preprocessor Rules
+pp_define_at_level      = true
+pp_if_indent_code       = false
+pp_indent_func_def      = false
+pp_indent_extern        = false
+pp_ignore_define_body   = true                # Workaround: Turn off processing for #define body
+                                              # (current rules do not work for some defines)
+pp_indent               = add
+pp_indent_at_level      = true
+pp_indent_count         = 2
+pp_indent_if            = 2
+pp_indent_region        = 2
+pp_region_indent_code   = false
+pp_space                = remove
+
+#
+# The tokens below are assigned specific types so they are always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+  "id": "uncrustify-ci-1",
+  "scope": "cibuild",
+  "type": "nuget",
+  "name": "mu-uncrustify-release",
+  "source": "https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",
+  "version": "73.0.3",
+  "flags": ["set_shell_var", "host_specific"],
+  "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+  "scope": "cibuild",
+  "name": "Uncrustify Coding Standard Test",
+  "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
   Run the Ecc tool on the package. The Ecc tool is available in the BaseTools
   package. It checks that the code complies to the EDKII coding standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard compliance issues.
+
   ## PyTool Scopes

   Scopes are how the PyTool ext_dep, path_env, and plugins are activated.  Meaning
--
2.28.0.windows.1











Michael D Kinney
 

Hi Michael,

I agree with these updates.

Thank you for posting the branches.

Mike

-----Original Message-----
From: Michael Kubacki <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 5:25 PM
To: devel@edk2.groups.io; Kinney, Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan <sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

I sent the v5 patch with the templates disabled:
https://edk2.groups.io/g/devel/message/84068

I also posted that patch on this branch:
https://github.com/makubacki/edk2/tree/add_uncrustify_ci_plugin_v5

I posted another branch that also has the commit to enable the templates
here:
https://github.com/makubacki/edk2/commits/add_uncrustify_ci_plugin_v5_w_template_enable_commit

I will be out of office until Mon Nov. 29th for the US holiday.

Thanks,
Michael

On 11/24/2021 7:24 PM, Michael Kubacki wrote:
Hi Mike,

How about I quickly send a v5 with the lines commented out and follow up
with a patch to uncomment them that we can review but merge when ready?

Thanks,
Michael

On 11/24/2021 7:16 PM, Michael D Kinney wrote:
The 2nd option I listed won't work.  That will fail UncrustifyCheck as
well because EDK II CI
will insert missing file/function headers and the files will not match.

We will need to comment out these 2 lines and submit an extra commit
to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 4:08 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney,
Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1]
.pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Hi Michael,

Unfortunately, enabling the templates in the uncrustify.cfg files
modifies 340 files with missing
file/function headers that will now fail UncrustifyCheck.

     cmt_insert_file_header          = default_file_header.txt
     cmt_insert_func_header          = default_function_header.txt

Perhaps we should leave this feature off to get the uncrustify format
changes committed and
enable this feature immediately after so patch reviews after that
will slowly fix these missing
file/function headers.

Or I can comment these two lines out when I generate the final
version of the patch series so
we don’t have to do an extra commit to re-enable.

Mike

-----Original Message-----
From: Kinney, Michael D <michael.d.kinney@intel.com>
Sent: Wednesday, November 24, 2021 3:06 PM
To: devel@edk2.groups.io; mikuback@linux.microsoft.com; Kinney,
Michael D <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: RE: [edk2-devel] [PATCH v4 1/1]
.pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Thanks.  That makes sense.

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of
Michael Kubacki
Sent: Wednesday, November 24, 2021 2:55 PM
To: devel@edk2.groups.io; Kinney, Michael D
<michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: Re: [edk2-devel] [PATCH v4 1/1]
.pytool/Plugin/UncrustifyCheck: Add Uncrustify CI plugin

Yes. Those were redundant and detected by the Python configparser
module
when I used it to parse the config file.

Thanks,
Michael

On 11/24/2021 5:45 PM, Michael D Kinney wrote:
Hi Michael,

I see a couple settings deleted from the uncrustify.cfg.  Was that
in purpose?  Does it change the format?


-nl_func_call_start_multi_line   = true      # Whether to add a
newline after '(' in a function call if '(' and ')'
are
-                                            # in different lines.


-nl_func_call_args_multi_line                 = true
-nl_func_call_args_multi_line_ignore_closures = false


Thanks,

Mike

-----Original Message-----
From: mikuback@linux.microsoft.com <mikuback@linux.microsoft.com>
Sent: Wednesday, November 24, 2021 1:42 PM
To: devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@intel.com>; Liming Gao
<gaoliming@byosoft.com.cn>; Sean Brogan
<sean.brogan@microsoft.com>; Bret Barkelew
<Bret.Barkelew@microsoft.com>
Subject: [PATCH v4 1/1] .pytool/Plugin/UncrustifyCheck: Add
Uncrustify CI plugin

From: Michael Kubacki <michael.kubacki@microsoft.com>

REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3748

Adds a new CI plugin for Uncrustify. This is used to check
coding standard compliance of source code to the EDK II C Coding
Standards Specification.

An external dependency is added in the plugin directory to retrieve
the Uncrustify executable. Currently, the executable is from an edk2
fork of the application host in this repository:

https://dev.azure.com/projectmu/Uncrustify/

Note that the Uncrustify application outputs the commit ID that the
application was built from when given the --version parameter.

This ID can be mapped to the version specified in the Uncrustify CI
plugin external dependency file (uncrustify_ext_dep.yaml) such as
73.0.3 by visiting the Uncrustify edk2 fork release pipeline page
which associates the NuGet package version with the commit ID it was
built from:

https://dev.azure.com/projectmu/Uncrustify/_build

The default Uncrustify configuration files are added in the plugin
directory. Additional details are in the Readme.md file added in
the Uncrustify plugin directory.

Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
---

Notes:
      V4 changes:

      1. Updated commit subject to use the new plugin name.
      2. Enabled file and function header templates to be placed in
         files missing a file or function header (comment block).
      3. Added support to detect whether a file header or function
         header is missing in a file by searching files modified by
         Uncrustify for the template file text.
      4. Updated the plugin name displayed in some log output
messages.

      V3 changes:

      1. Renamed the CI plugin to "UncrustifyCheck" from
"Uncrustify"
         to follow similar naming conventions used in other plugins
         that check files
      2. Added a clarifying paragraph to the beginning of the
Uncrustify
         CI plugin Readme.md file that the plugin is enabled by
default
         and test results can be ignored by enabling "AuditOnly"
mode.
      3. Added instructions in the commit message on how to map the
         version reported by the Uncrustify application
(--version) to
         the version specified in the CI plugin external dependency
         YAML file.

      V2 changes:

      1. Changed plugin temp directory to
Build/.pytool/Plugin/Uncrustify
      2. Added TCBZ REF in patch commit message

   .pytool/Plugin/UncrustifyCheck/Readme.md                   |
120 ++++
   .pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py          |
617 ++++++++++++++++++++
   .pytool/Plugin/UncrustifyCheck/default_file_header.txt     |
9 +
   .pytool/Plugin/UncrustifyCheck/default_function_header.txt |
15 +
   .pytool/Plugin/UncrustifyCheck/uncrustify.cfg              |
462 +++++++++++++++
   .pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml     |
16 +
   .pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml     |
11 +
   .pytool/Readme.md                                          |
4 +
   8 files changed, 1254 insertions(+)

diff --git a/.pytool/Plugin/UncrustifyCheck/Readme.md
b/.pytool/Plugin/UncrustifyCheck/Readme.md
new file mode 100644
index 000000000000..bb263bcc87d7
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/Readme.md
@@ -0,0 +1,120 @@
+# UncrustifyCheck Plugin
+
+This CiBuildPlugin scans all the files in a given package and
checks for coding standard compliance issues.
+
+This plugin is enabled by default. If a package would like to
prevent the plugin from reporting errors, it can do
+so by enabling [`AuditOnly`](#auditonly) mode.
+
+This plugin requires the directory containing the Uncrustify
executable that should be used for this plugin to
+be specified in an environment variable named
`UNCRUSTIFY_CI_PATH`. This unique variable name is used to avoid
confusion
+with other paths to Uncrustify which might not be the expected
build for use by this plugin.
+
+By default, an Uncrustify configuration file named
"uncrustify.cfg" located in the same directory as the plugin is
+used. The value can be overridden to a package-specific path
with the `ConfigFilePath` configuration file option.
+
+* Uncrustify source code and documentation:
https://github.com/uncrustify/uncrustify
+* Project Mu Uncrustify fork source code and documentation:
https://dev.azure.com/projectmu/Uncrustify
+
+## Files Checked in a Package
+
+By default, this plugin will discover all files in the package
with the following default paths:
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+From this list of files, any files ignored by Git or residing in
a Git submodule will be removed. If Git is not
+found, submodules are not found, or ignored files are not found
no changes are made to the list of discovered
files.
+
+To control the paths checked in a given package, review the
configuration options described in this file.
+
+## Configuration
+
+The plugin can be configured with a few optional configuration
options.
+
+``` yaml
+  "UncrustifyCheck": {
+      "AdditionalIncludePaths": [], # Additional paths to check
formatting (wildcards supported).
+      "AuditOnly": False,           # Don't fail the build if
there are errors.  Just log them.
+      "ConfigFilePath": "",         # Custom path to an
Uncrustify config file.
+      "IgnoreStandardPaths": [],    # Standard Plugin defined
paths that should be ignored.
+      "OutputFileDiffs": False,     # Output chunks of
formatting diffs in the test case log.
+                                    # This can significantly
slow down the plugin on very large packages.
+      "SkipGitExclusions": False    # Don't exclude git ignored
files and files in git submodules.
+  }
+```
+
+### `AdditionalIncludePaths`
+
+A package configuration file can specify any additional paths to
be included with this option.
+
+At this time, it is recommended all files run against the plugin
be written in the C or C++ language.
+
+### `AuditOnly`
+
+`Boolean` - Default is `False`.
+
+If `True`, run the test in an "audit only mode" which will log
all errors but instead of failing the build, it
will
set
+the test as skipped. This allows visibility into the failures
without breaking the build.
+
+### `ConfigFilePath`
+
+`String` - Default is `"uncrustify.cfg"`
+
+When specified in the config file, this is a package relative
path to the Uncrustify configuration file.
+
+### `IgnoreStandardPaths`
+
+This plugin by default will check the below standard paths. A
package configuration file can specify any of these
paths
+to be ignored.
+
+```python
+[
+# C source
+"*.c",
+"*.h"
+]
+```
+
+### `OutputFileDiffs`
+
+`Boolean` - Default is `False`.
+
+If `True`, output diffs of formatting changes into the test case
log. This is helpful to exactly understand what
changes
+need to be made to the source code in order to fix a coding
standard compliance issue.
+
+Note that calculating the file diffs on a very large set of of
results (e.g. >100 files) can significantly slow
down
+plugin execution.
+
+### `SkipGitExclusions`
+
+`Boolean` - Default is `False`.
+
+By default, files in paths matched in a .gitignore file or a
recognized git submodule are excluded. If this option
+is `True`, the plugin will not attempt to recognize these files
and exclude them.
+
+## High-Level Plugin Operation
+
+This plugin generates two main sets of temporary files:
+
+  1. A working directory in the directory
`Build/.pytool/Plugin/Uncrustify`
+  2. For each source file with formatting errors, a sibling file
with the `.uncrustify_plugin` extension
+
+The working directory contains temporary files unique to
operation of the plugin. All of these files are removed
on
+exit of the plugin including successful or unsuccessful
execution (such as a Python exception occurring). If for
any
+reason, any files in the package exist prior to running the
plugin with the `.uncrustify_plugin` extension, the
plugin
+will inform the user to remove these files and exit before
running Uncrustify. This is to ensure the accuracy of
the
+results reported from each execution instance of the plugin.
+
+The plugin determines the list of relevant files to check with
Uncrustify and then invokes Uncrustify with that
file
+list. For any files not compliant to the configuration file
provided, Uncrustify will generate a corresponding
file
+with the `.uncrustify_plugin` extension. The plugin discovers
all of these files. If any such files are present,
this
+indicates a formatting issue was found and the test is marked
failed (unless `AuditOnly` mode is enabled).
+
+The test case log will contain a report of which files failed to
format properly, allowing the user to run
Uncrustify
+against the file locally to fix the issue. If the
`OutputFileDiffs` configuration option is set to `True`, the
plugin
+will output diff chunks for all code formatting issues in the
test case log.
diff --git a/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
new file mode 100644
index 000000000000..59534469a2d3
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/UncrustifyCheck.py
@@ -0,0 +1,617 @@
+# @file UncrustifyCheck.py
+#
+# An edk2-pytool based plugin wrapper for Uncrustify
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import configparser
+import difflib
+import errno
+import logging
+import os
+import pathlib
+import shutil
+import timeit
+from edk2toolext.environment import version_aggregator
+from edk2toolext.environment.plugin_manager import PluginManager
+from edk2toolext.environment.plugintypes.ci_build_plugin import
ICiBuildPlugin
+from edk2toolext.environment.plugintypes.uefi_helper_plugin
import HelperFunctions
+from edk2toolext.environment.var_dict import VarDict
+from edk2toollib.log.junit_report_format import JunitReportTestCase
+from edk2toollib.uefi.edk2.path_utilities import Edk2Path
+from edk2toollib.utility_functions import  RunCmd
+from io import StringIO
+from typing import Any, Dict, List, Tuple
+
+#
+# Provide more user friendly messages for certain scenarios
+#
+class UncrustifyException(Exception):
+    def __init__(self, message, exit_code):
+        super().__init__(message)
+        self.exit_code = exit_code
+
+
+class UncrustifyAppEnvVarNotFoundException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -101)
+
+
+class UncrustifyAppVersionErrorException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -102)
+
+
+class UncrustifyAppExecutionException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -103)
+
+
+class
UncrustifyStalePluginFormattedFilesException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -120)
+
+
+class
UncrustifyInputFileCreationErrorException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -121)
+
+class
UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -122)
+
+class UncrustifyGitIgnoreFileException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -140)
+
+
+class UncrustifyGitSubmoduleException(UncrustifyException):
+    def __init__(self, message):
+        super().__init__(message, -141)
+
+
+class UncrustifyCheck(ICiBuildPlugin):
+    """
+    A CiBuildPlugin that uses Uncrustify to check the source
files in the
+    package being tested for coding standard issues.
+
+    By default, the plugin runs against standard C source file
extensions but
+    its configuration can be modified through its configuration
file.
+
+    Configuration options:
+    "UncrustifyCheck": {
+        "AdditionalIncludePaths": [], # Additional paths to
check formatting (wildcards supported).
+        "AuditOnly": False,           # Don't fail the build if
there are errors.  Just log them.
+        "ConfigFilePath": "",         # Custom path to an
Uncrustify config file.
+        "IgnoreStandardPaths": [],    # Standard Plugin defined
paths that should be ignored.
+        "OutputFileDiffs": False,     # Output chunks of
formatting diffs in the test case log.
+                                      # This can significantly
slow down the plugin on very large packages.
+        "SkipGitExclusions": False    # Don't exclude git
ignored files and files in git submodules.
+    }
+    """
+
+    #
+    # By default, use an "uncrustify.cfg" config file in the
plugin directory
+    # A package can override this path via "ConfigFilePath"
+    #
+    # Note: Values specified via "ConfigFilePath" are relative
to the package
+    #
+    DEFAULT_CONFIG_FILE_PATH = os.path.join(
+        pathlib.Path(__file__).parent.resolve(), "uncrustify.cfg")
+
+    #
+    # The extension used for formatted files produced by this
plugin
+    #
+    FORMATTED_FILE_EXTENSION = ".uncrustify_plugin"
+
+    #
+    # A package can add any additional paths with
"AdditionalIncludePaths"
+    # A package can remove any of these paths with
"IgnoreStandardPaths"
+    #
+    STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h")
+
+    #
+    # The Uncrustify application path should set in this
environment variable
+    #
+    UNCRUSTIFY_PATH_ENV_KEY = "UNCRUSTIFY_CI_PATH"
+
+    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 file coding standard compliance in " +
packagename, packagename + ".UncrustifyCheck")
+
+    def RunBuildPlugin(self, package_rel_path: str, edk2_path:
Edk2Path, package_config: Dict[str, List[str]],
environment_config: Any, plugin_manager: PluginManager,
plugin_manager_helper: HelperFunctions, tc:
JunitReportTestCase,
output_stream=None) -> int:
+        """
+        External function of plugin. This function is used to
perform the task of the CiBuild Plugin.
+
+        Args:
+          - package_rel_path: edk2 workspace relative path to
the package
+          - edk2_path: Edk2Path object with workspace and
packages paths
+          - package_config: Dictionary with the package
configuration
+          - environment_config: Environment configuration
+          - plugin_manager: Plugin Manager Instance
+          - plugin_manager_helper: Plugin Manager Helper Instance
+          - tc: JUnit test case
+          - output_stream: The StringIO output stream from this
plugin (logging)
+
+        Returns
+          >0 : Number of errors found
+          0  : Passed successfully
+          -1 : Skipped for missing prereq
+        """
+        try:
+            # Initialize plugin and check pre-requisites.
+            self._initialize_environment_info(
+                package_rel_path, edk2_path, package_config, tc)
+            self._initialize_configuration()
+            self._check_for_preexisting_formatted_files()
+
+            # Log important context information.
+            self._log_uncrustify_app_info()
+
+            # Get template file contents if specified
+            self._get_template_file_contents()
+
+            # Create meta input files & directories
+            self._create_temp_working_directory()
+            self._create_uncrustify_file_list_file()
+
+            self._run_uncrustify()
+
+            # Post-execution actions.
+            self._process_uncrustify_results()
+
+        except UncrustifyException as e:
+            self._tc.LogStdError(
+                f"Uncrustify error {e.exit_code}.
Details:\n\n{str(e)}")
+            logging.warning(
+                f"Uncrustify error {e.exit_code}.
Details:\n\n{str(e)}")
+            return -1
+        else:
+            if self._formatted_file_error_count > 0:
+                if self._audit_only_mode:
+                    logging.info(
+                        "Setting test as skipped since AuditOnly
is enabled")
+                    self._tc.SetSkipped()
+                    return -1
+                else:
+                    self._tc.SetFailed(
+                        f"{self._plugin_name} failed due to
{self._formatted_file_error_count} incorrectly
formatted
files.", "CHECK_FAILED")
+            else:
+                self._tc.SetSuccess()
+            return self._formatted_file_error_count
+        finally:
+            self._cleanup_temporary_formatted_files()
+            self._cleanup_temporary_directory()
+
+    def _initialize_configuration(self) -> None:
+        """
+        Initializes plugin configuration.
+        """
+        self._initialize_app_info()
+        self._initialize_config_file_info()
+        self._initialize_file_to_format_info()
+        self._initialize_test_case_output_options()
+
+    def _check_for_preexisting_formatted_files(self) -> None:
+        """
+        Checks if any formatted files from prior execution are
present.
+
+        Existence of such files is an unexpected condition. This
might result
+        from an error that occurred during a previous run or a
premature exit from a debug scenario. In any case,
the
package should be clean before starting a new run.
+        """
+        pre_existing_formatted_file_count = len(
+            [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])

+
+        if pre_existing_formatted_file_count > 0:
+            raise UncrustifyStalePluginFormattedFilesException(
+                f"{pre_existing_formatted_file_count} formatted
files already exist. To prevent overwriting these
files,
please remove them before running this plugin.")
+
+    def _cleanup_temporary_directory(self) -> None:
+        """
+        Cleans up the temporary directory used for this
execution instance.
+
+        This removes the directory and all files created during
this instance.
+        """
+        if hasattr(self, '_working_dir'):
+            self._remove_tree(self._working_dir)
+
+    def _cleanup_temporary_formatted_files(self) -> None:
+        """
+        Cleans up the temporary formmatted files produced by
Uncrustify.
+
+        This will recursively remove all formatted files
generated by Uncrustify
+        during this execution instance.
+        """
+        if hasattr(self, '_abs_package_path'):
+            formatted_files = [str(path.resolve()) for path in
pathlib.Path(
+
self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]

+
+            for formatted_file in formatted_files:
+                os.remove(formatted_file)
+
+    def _create_temp_working_directory(self) -> None:
+        """
+        Creates the temporary directory used for this execution
instance.
+        """
+        self._working_dir = os.path.join(
+            self._abs_workspace_path, "Build", ".pytool",
"Plugin", f"{self._plugin_name}")
+
+        try:
+            pathlib.Path(self._working_dir).mkdir(parents=True,
exist_ok=True)
+        except OSError as e:
+            raise UncrustifyInputFileCreationErrorException(
+                f"Error creating plugin directory
{self._working_dir}.\n\n{repr(e)}.")
+
+    def _create_uncrustify_file_list_file(self) -> None:
+        """
+        Creates the file with the list of source files for
Uncrustify to process.
+        """
+        self._app_input_file_path = os.path.join(
+            self._working_dir, "uncrustify_file_list.txt")
+
+        with open(self._app_input_file_path, 'w',
encoding='utf8') as f:
+
f.writelines(f"\n".join(self._abs_file_paths_to_format))
+
+    def _execute_uncrustify(self) -> None:
+        """
+        Executes Uncrustify with the initialized configuration.
+        """
+        output = StringIO()
+        self._app_exit_code = RunCmd(
+            self._app_path,
+            f"-c {self._app_config_file} -F
{self._app_input_file_path} --if-changed --suffix
{UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream=output)
+        self._app_output = output.getvalue().strip().splitlines()
+
+    def _get_git_ignored_paths(self) -> List[str]:
+        """"
+        Returns a list of file absolute path strings to all
files ignored in this git repository.
+
+        If git is not found, an empty list will be returned.
+        """
+        if not shutil.which("git"):
+            logging.warn(
+                "Git is not found on this system. Git submodule
paths will not be considered.")
+            return []
+
+        outstream_buffer = StringIO()
+        exit_code = RunCmd("git", "ls-files --other",
+                           workingdir=self._abs_workspace_path,
outstream=outstream_buffer,
logging_level=logging.NOTSET)
+        if (exit_code != 0):
+            raise UncrustifyGitIgnoreFileException(
+                f"An error occurred reading git ignore settings.
This will prevent Uncrustify from running against
the
expected set of files.")
+
+        # Note: This will potentially be a large list, but at
least sorted
+        return outstream_buffer.getvalue().strip().splitlines()
+
+    def _get_git_submodule_paths(self) -> List[str]:
+        """
+        Returns a list of directory absolute path strings to the
root of each submodule in the workspace
repository.
+
+        If git is not found, an empty list will be returned.
+        """
+        if not shutil.which("git"):
+            logging.warn(
+                "Git is not found on this system. Git submodule
paths will not be considered.")
+            return []
+
+        if os.path.isfile(os.path.join(self._abs_workspace_path,
".gitmodules")):
+            logging.info(
+                f".gitmodules file found. Excluding submodules
in {self._package_name}.")
+
+            outstream_buffer = StringIO()
+            exit_code = RunCmd("git", "config --file .gitmodules
--get-regexp path",
workingdir=self._abs_workspace_path,
outstream=outstream_buffer, logging_level=logging.NOTSET)
+            if (exit_code != 0):
+                raise UncrustifyGitSubmoduleException(
+                    f".gitmodule file detected but an error
occurred reading the file. Cannot proceed with unknown
submodule paths.")
+
+            submodule_paths = []
+            for line in
outstream_buffer.getvalue().strip().splitlines():
+                submodule_paths.append(
+
os.path.normpath(os.path.join(self._abs_workspace_path,
line.split()[1])))
+
+            return submodule_paths
+        else:
+            return []
+
+    def _get_template_file_contents(self) -> None:
+        """
+        Gets the contents of Uncrustify template files if they
are specified
+        in the Uncrustify configuration file.
+        """
+
+        self._file_template_contents = None
+        self._func_template_contents = None
+
+        # Allow no value to allow "set" statements in the config
file which do
+        # not specify value assignment
+        parser = configparser.ConfigParser(allow_no_value=True)
+        with open(self._app_config_file, 'r') as cf:
+            parser.read_string("[dummy_section]\n" + cf.read())
+
+        try:
+            file_template_name =
parser["dummy_section"]["cmt_insert_file_header"]
+
+            file_template_path = pathlib.Path(file_template_name)
+
+            if not file_template_path.is_file():
+                file_template_path =
pathlib.Path(os.path.join(self._plugin_path, file_template_name))
+                self._file_template_contents =
file_template_path.read_text()
+        except KeyError:
+            logging.warn("A file header template is not
specified in the config file.")
+        except FileNotFoundError:
+            logging.warn("The specified file header template
file was not found.")
+        try:
+            func_template_name =
parser["dummy_section"]["cmt_insert_func_header"]
+
+            func_template_path = pathlib.Path(func_template_name)
+
+            if not func_template_path.is_file():
+                func_template_path =
pathlib.Path(os.path.join(self._plugin_path, func_template_name))
+                self._func_template_contents =
func_template_path.read_text()
+        except KeyError:
+            logging.warn("A function header template is not
specified in the config file.")
+        except FileNotFoundError:
+            logging.warn("The specified function header template
file was not found.")
+
+    def _initialize_app_info(self) -> None:
+        """
+        Initialize Uncrustify application information.
+
+        This function will determine the application path and
version.
+        """
+        # Verify Uncrustify is specified in the environment.
+        if UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY not in
os.environ:
+            raise UncrustifyAppEnvVarNotFoundException(
+                f"Uncrustify environment variable
{UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
+
+        self._app_path = shutil.which('uncrustify',
path=os.environ[UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY])
+
+        if self._app_path is None:
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT),
self._app_path)
+
+        self._app_path =
os.path.normcase(os.path.normpath(self._app_path))
+
+        if not os.path.isfile(self._app_path):
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT),
self._app_path)
+
+        # Verify Uncrustify is present at the expected path.
+        return_buffer = StringIO()
+        ret = RunCmd(self._app_path, "--version",
outstream=return_buffer)
+        if (ret != 0):
+            raise UncrustifyAppVersionErrorException(
+                f"Error occurred executing --version: {ret}.")
+
+        # Log Uncrustify version information.
+        self._app_version = return_buffer.getvalue().strip()
+        self._tc.LogStdOut(f"Uncrustify version:
{self._app_version}")
+        version_aggregator.GetVersionAggregator().ReportVersion(
+            "Uncrustify", self._app_version,
version_aggregator.VersionTypes.INFO)
+
+    def _initialize_config_file_info(self) -> None:
+        """
+        Initialize Uncrustify configuration file info.
+
+        The config file path is relative to the package root.
+        """
+        self._app_config_file =
UncrustifyCheck.DEFAULT_CONFIG_FILE_PATH
+        if "ConfigFilePath" in self._package_config:
+            self._app_config_file =
self._package_config["ConfigFilePath"].strip()
+
+            self._app_config_file = os.path.normpath(
+                os.path.join(self._abs_package_path,
self._app_config_file))
+
+        if not os.path.isfile(self._app_config_file):
+            raise FileNotFoundError(
+                errno.ENOENT, os.strerror(errno.ENOENT),
self._app_config_file)
+
+    def _initialize_environment_info(self, package_rel_path:
str, edk2_path: Edk2Path, package_config: Dict[str,
List[str]], tc: JunitReportTestCase) -> None:
+        """
+        Initializes plugin environment information.
+        """
+        self._abs_package_path =
edk2_path.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
+            package_rel_path)
+        self._abs_workspace_path = edk2_path.WorkspacePath
+        self._package_config = package_config
+        self._package_name = os.path.basename(
+            os.path.normpath(package_rel_path))
+        self._plugin_name = self.__class__.__name__
+        self._plugin_path =
os.path.dirname(os.path.realpath(__file__))
+        self._rel_package_path = package_rel_path
+        self._tc = tc
+
+    def _initialize_file_to_format_info(self) -> None:
+        """
+        Forms the list of source files for Uncrustify to process.
+        """
+        # Create a list of all the package relative file paths
in the package to run against Uncrustify.
+        rel_file_paths_to_format = list(
+            UncrustifyCheck.STANDARD_PLUGIN_DEFINED_PATHS)
+
+        # Allow the ci.yaml to remove any of the pre-defined
standard paths
+        if "IgnoreStandardPaths" in self._package_config:
+            for a in self._package_config["IgnoreStandardPaths"]:
+                if a.strip() in rel_file_paths_to_format:
+                    self._tc.LogStdOut(
+                        f"Ignoring standard path due to ci.yaml
ignore: {a}")
+                    rel_file_paths_to_format.remove(a.strip())
+                else:
+                    raise
UncrustifyInvalidIgnoreStandardPathsException(f"Invalid
IgnoreStandardPaths value: {a}")
+
+        # Allow the ci.yaml to specify additional include paths
for this package
+        if "AdditionalIncludePaths" in self._package_config:
+            rel_file_paths_to_format.extend(
+                self._package_config["AdditionalIncludePaths"])
+
+        self._abs_file_paths_to_format = []
+        for path in rel_file_paths_to_format:
+            self._abs_file_paths_to_format.extend(
+                [str(path.resolve()) for path in
pathlib.Path(self._abs_package_path).rglob(path)])
+
+        if not "SkipGitExclusions" in self._package_config or
not self._package_config["SkipGitExclusions"]:
+            # Remove files ignored by git
+            logging.info(
+                f"{self._package_name} file count before git
ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            ignored_paths = self._get_git_ignored_paths()
+            self._abs_file_paths_to_format = list(
+
set(self._abs_file_paths_to_format).difference(ignored_paths))
+
+            logging.info(
+                f"{self._package_name} file count after git
ignore file exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            # Remove files in submodules
+            logging.info(
+                f"{self._package_name} file count before
submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+            submodule_paths =
tuple(self._get_git_submodule_paths())
+            for path in submodule_paths:
+                logging.info(f"  submodule path: {path}")
+
+            self._abs_file_paths_to_format = [
+                f for f in self._abs_file_paths_to_format if not
f.startswith(submodule_paths)]
+
+            logging.info(
+                f"{self._package_name} file count after
submodule exclusion:
{len(self._abs_file_paths_to_format)}")
+
+        # Sort the files for more consistent results
+        self._abs_file_paths_to_format.sort()
+
+    def _initialize_test_case_output_options(self) -> None:
+        """
+        Initializes options that influence test case output.
+        """
+        self._audit_only_mode = False
+        self._output_file_diffs = False
+
+        if "AuditOnly" in self._package_config and
self._package_config["AuditOnly"]:
+            self._audit_only_mode = True
+
+        if "OutputFileDiffs" in self._package_config and
self._package_config["OutputFileDiffs"]:
+            self._output_file_diffs = True
+
+    def _log_uncrustify_app_info(self) -> None:
+        """
+        Logs Uncrustify application information.
+        """
+        self._tc.LogStdOut(f"Found Uncrustify at {self._app_path}")
+        self._tc.LogStdOut(f"Uncrustify version:
{self._app_version}")
+        self._tc.LogStdOut('\n')
+        logging.info(f"Found Uncrustify at {self._app_path}")
+        logging.info(f"Uncrustify version: {self._app_version}")
+        logging.info('\n')
+
+    def _process_uncrustify_results(self) -> None:
+        """
+        Process the results from Uncrustify.
+
+        Determines whether formatting errors are present and
logs failures.
+        """
+        formatted_files = [str(path.resolve()) for path in
pathlib.Path(
+
self._abs_package_path).rglob(f'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]

+
+        self._formatted_file_error_count = len(formatted_files)
+
+        if self._formatted_file_error_count > 0:
+            self._tc.LogStdError("Files with formatting errors:\n")
+
+            if self._output_file_diffs:
+                logging.info("Calculating file diffs. This might
take a while...")
+
+        for formatted_file in formatted_files:
+            pre_formatted_file = formatted_file[:-
+
len(UncrustifyCheck.FORMATTED_FILE_EXTENSION)]
+
+            if (self._output_file_diffs or
+                    self._file_template_contents is not None or
+                    self._func_template_contents is not None):
+                self._tc.LogStdError(
+                    f"Formatting errors in
{os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
+
+                with open(formatted_file) as ff:
+                    formatted_file_text = ff.read()
+
+                    if (self._file_template_contents is not None
and
+                            self._file_template_contents in
formatted_file_text):
+                        self._tc.LogStdError(f"File header is
missing in {os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+                    if (self._func_template_contents is not None
and
+                            self._func_template_contents in
formatted_file_text):
+                        self._tc.LogStdError(f"A function header
is missing in
{os.path.relpath(pre_formatted_file,
self._abs_package_path)}\n")
+
+                    if self._output_file_diffs:
+                        with open(pre_formatted_file) as pf:
+                            pre_formatted_file_text = pf.read()
+
+                        for line in
difflib.unified_diff(pre_formatted_file_text.split('\n'),
formatted_file_text.split('\n'), fromfile=pre_formatted_file,
tofile=formatted_file, n=3):
+                            self._tc.LogStdError(line)
+
+                        self._tc.LogStdError('\n')
+            else:
+                self._tc.LogStdError(pre_formatted_file)
+
+    def _remove_tree(self, dir_path: str, ignore_errors: bool =
False) -> None:
+        """
+        Helper for removing a directory. Over time there have been
+        many private implementations of this due to reliability
issues in the
+        shutil implementations. To consolidate on a single
function this helper is added.
+
+        On error try to change file attributes. Also add retry
logic.
+
+        This function is temporarily borrowed from
edk2toollib.utility_functions
+        since the version used in edk2 is not recent enough to
include the
+        function.
+
+        This function should be replaced by "RemoveTree" when it
is available.
+
+        Args:
+          - dir_path: Path to directory to remove.
+          - ignore_errors: Whether to ignore errors during removal
+        """
+
+        def _remove_readonly(func, path, _):
+            """
+            Private function to attempt to change permissions on
file/folder being deleted.
+            """
+            os.chmod(path, os.stat.S_IWRITE)
+            func(path)
+
+        for _ in range(3):  # retry up to 3 times
+            try:
+                shutil.rmtree(dir_path,
ignore_errors=ignore_errors, onerror=_remove_readonly)
+            except OSError as err:
+                logging.warning(f"Failed to fully remove
{dir_path}: {err}")
+            else:
+                break
+        else:
+            raise RuntimeError(f"Failed to remove {dir_path}")
+
+    def _run_uncrustify(self) -> None:
+        """
+        Runs Uncrustify for this instance of plugin execution.
+        """
+        logging.info("Executing Uncrustify. This might take a
while...")
+        start_time = timeit.default_timer()
+        self._execute_uncrustify()
+        end_time = timeit.default_timer() - start_time
+
+        execution_summary = f"Uncrustify executed against
{len(self._abs_file_paths_to_format)} files in
{self._package_name} in {end_time:.2f} seconds.\n"
+
+        self._tc.LogStdOut(execution_summary)
+        logging.info(execution_summary)
+
+        if self._app_exit_code != 0 and self._app_exit_code != 1:
+            raise UncrustifyAppExecutionException(
+                f"Error {str(self._app_exit_code)} returned from
Uncrustify:\n\n{str(self._app_output)}")
diff --git a/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
new file mode 100644
index 000000000000..2955a734dfe1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_file_header.txt
@@ -0,0 +1,9 @@
+/** @file
+  Brief description of the file's purpose.
+
+  Detailed description of the file's contents and other useful
+  information for a person viewing the file for the first time.
+
+  <<Copyright>>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
diff --git
a/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
new file mode 100644
index 000000000000..66edc72e6731
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/default_function_header.txt
@@ -0,0 +1,15 @@
+/**
+  Brief description of this function's purpose.
+
+  Follow it immediately with the detailed description.
+
+  @param[in]      Arg1  Description of Arg1.
+  @param[in]      Arg2  Description of Arg2 This is complicated
and requires
+                        multiple lines to describe.
+  @param[out]     Arg3  Description of Arg3.
+  @param[in, out] Arg4  Description of Arg4.
+
+  @retval VAL_ONE  Description of what VAL_ONE signifies.
+  @retval OTHER    This is the only other return value. If there
were other
+                   return values, they would be listed.
+**/
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
new file mode 100644
index 000000000000..1a87295ec9f9
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify.cfg
@@ -0,0 +1,462 @@
+## @file
+# Uncrustify Configuration File for EDK II C Code
+#
+# Coding Standard:
https://edk2-docs.gitbook.io/edk-ii-c-coding-standards-specification/

+#
+# This configuration file is meant to be a "best attempt" to
align with the
+# definitions in the EDK II C Coding Standards Specification.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+# Force UTF-8 encoding (no UTF-16)
+enable_digraphs                 = false
+utf8_byte                       = false
+utf8_force                      = true
+
+# Code width / line splitting
+#code_width                      =120     # TODO: This causes
non-deterministic behaviour in some cases when code
wraps
+ls_code_width                   =false
+ls_for_split_full               =true
+ls_func_split_full              =true
+pos_comma                       =trail
+
+# 5.1.7  All files must end with CRLF
+newlines                        = crlf
+
+# 5.1.2 Do not use tab characters
+
+cmt_convert_tab_to_spaces       = true      # Whether to convert
all tabs to spaces in comments. If false, tabs in
+                                            # comments are left
alone, unless used for indenting.
+indent_columns                  = 2         # Number of spaces
for indentation
+indent_with_tabs                = 0         # Do not use TAB
characters
+string_replace_tab_chars        = true      # Replace TAB with
SPACE
+                                            # Note: This will
break .robot files but is needed for edk2 style
+
+# 5.2.1.1 There shall be only one statement on a line (statement
ends with ;)
+nl_multi_line_cond              = true      # Add a newline
between ')' and '{' if the ')' is on a different line
than
+                                            # the if/for/etc.
+nl_after_semicolon              = true      # Whether to add a
newline after semicolons, except in 'for'
statements.
+
+# 5.2.1.3 An open brace '{' goes on the same line as the closing
parenthesis ')' of simple predicate expressions
+mod_full_brace_do               = add       # Add or remove
braces on a single-line 'do' statement.
+mod_full_brace_for              = add
+mod_full_brace_function         = add       # Add or remove
braces on a single-line function definition.
+mod_full_brace_if               = add       # Add or remove
braces on a single-line 'if' statement. Braces will
not
be
+                                            # removed if the
braced statement contains an 'else'.
+mod_full_brace_if_chain         = false
+mod_full_brace_while            = add
+
+# 5.2.1.4 A close brace '}' always goes at the beginning of the
last line of the body
+eat_blanks_after_open_brace     = true
+eat_blanks_before_close_brace   = true      # Whether to remove
blank lines before '}'.
+
+# 5.2.2.2 Always put space before and after binary operators.
+sp_assign                       = add       # Add or remove
space around assignment operator '=', '+=', etc.
+sp_assign_default               = add
+sp_bool                         = add       # Add or remove
space around boolean operators '&&' and '||'.
+sp_compare                      = add       # Add or remove
space around compare operator '<', '>', '==', etc.
+
+# 5.2.2.3 Do not put space between unary operators and their object
+sp_addr                         = remove    # A or remove space
after the '&' (address-of) unary operator.
+sp_incdec                       = remove    # Add or remove
space between '++' and '--' the word to which it is
being
+                                            # applied, as in
'(--x)' or 'y++;'.
+sp_inv                          = remove    # Add or remove
space after the '~' (invert) unary operator.
+sp_not                          = remove    # Add or remove
space after the '!' (not) unary operator.
+sp_sign                         = remove    # Add or remove
space after '+' or '-', as in 'x = -5' or 'y = +7'.
+
+# 5.2.2.4 Subsequent lines of multi-line function calls should
line up two spaces from the beginning of the
function
+#         name
+nl_func_call_args_multi_line    = true      # Whether to add a
newline after each ',' in a function call if '('
and
')'
+                                            # are in different
lines.
+nl_func_call_args_multi_line_ignore_closures = false
+
+# - Indent each argument 2 spaces from the start of the function
name. If a
+#   function is called through a structure or union member, of type
+#   pointer-to-function, then indent each argument 2 spaces from
the start of the
+#   member name.
+indent_func_call_edk2_style     = true      # Use EDK2
indentation style for function calls  (**CUSTOM SETTING**)
+indent_paren_after_func_call    = true      # Whether to indent
the open parenthesis of a function call, if the
+                                            # parenthesis is on
its own line.
+
+# - Align the close parenthesis with the start of the last argument
+indent_paren_close              = 0         # How to indent a
close parenthesis after a newline.
+                                            # (0: Body, 1:
Openparenthesis, 2: Brace level)
+
+
+# 5.2.2.5 Always put space after commas or semicolons that
separate items
+sp_after_comma                  = force     # Add or remove
space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_before_comma                 = remove    # Add or remove
space before ','.
+
+# 5.2.2.6 Always put space before an open parenthesis
+sp_after_sparen                 = add       # Add or remove
space after ')' of control statements.
+sp_attribute_paren              = add       # Add or remove
space between '__attribute__' and '('.
+sp_before_sparen                = force     # Add or remove
space before '(' of control statements
+                                            # ('if', 'for',
'switch', 'while', etc.).
+sp_defined_paren                = force     # Add or remove
space between 'defined' and '(' in '#if defined
(FOO)'.
+sp_func_call_paren              = force     # Add or remove
space between function name and '(' on function calls.
+sp_func_call_paren_empty        = force     # Add or remove
space between function name and '()' on function calls
+                                            # without
parameters. If set to ignore (the default),
sp_func_call_paren
is
+                                            # used.
+sp_func_def_paren               = add       # Add or remove
space between alias name and '(' of a non-pointer
function
+                                            # type typedef.
+sp_func_proto_paren             = add       # Add or remove
space between function name and '()' on function
declaration
+sp_sizeof_paren                 = force     # Add or remove
space between 'sizeof' and '('.
+sp_type_func                    = add       # Add or remove
space between return type and function name. A minimum
of
1
+                                            # is forced except
for pointer return types.
+
+# Not specified, but also good style to remove spaces inside
parentheses (Optional)
+sp_cparen_oparen                = remove    # Add or remove
space between back-to-back parentheses, i.e. ')(' vs.
')
('.
+sp_inside_fparen                = remove    # Add or remove
space inside function '(' and ')'.
+sp_inside_fparens               = remove    # Add or remove
space inside empty function '()'.
+sp_inside_paren                 = remove    # Add or remove
space inside '(' and ')'.
+sp_inside_paren_cast            = remove    # Add or remove
spaces inside cast parentheses. '(int)x'
+sp_inside_square                = remove    # Add or remove
space inside a non-empty '[' and ']'.
+sp_paren_paren                  = remove    # Add or remove
space between nested parentheses, i.e. '((' vs. ') )'.
+sp_square_fparen                = remove    # Add or remove
space between ']' and '(' when part of a function
call.
+
+# 5.2.2.7 Put a space before an open brace if it is not on its
own line
+sp_do_brace_open                = force     # Add or remove
space between 'do' and '{'.
+sp_paren_brace                  = force     # Add or remove
space between ')' and '{'.
+sp_sparen_brace                 = force     # Add or remove
space between ')' and '{' of of control statements.
+
+# 5.2.2.8 Do not put spaces around structure member and pointer
operators
+sp_after_byref                  = remove    # Add or remove
space after reference sign '&', if followed by a word.
+sp_before_byref                 = add       # Add or remove
space before a reference sign '&'.
+sp_deref                        = remove    # Add or remove
space after the '*' (dereference) unary operator. This
does
+                                            # not affect the
spacing after a '*' that is part of a type.
+sp_member                       = remove    # Add or remove
space around the '.' or '->' operators.
+
+# 5.2.2.9 Do not put spaces before open brackets of array
subscripts
+sp_before_square                = remove    # Add or remove
space before '[' (except '[]').
+sp_before_squares               = remove    # Add or remove
space before '[]'.
+sp_before_vardef_square         = remove    # Add or remove
space before '[' for a variable definition.
+
+# 5.2.2.10 Use extra parentheses rather than depending on
in-depth knowledge of the order of precedence of C
+mod_full_paren_if_bool          = true      # Whether to fully
parenthesize Boolean expressions in 'while' and
'if'
+                                            # statement, as in
'if (a && b > c)' => 'if (a && (b > c))'.
+
+# 5.2.2.11 Align a continuation line with the part of the line
that it continues.
+use_indent_continue_only_once   = true
+
+# Additional '{}' bracing rules (Optional)
+# NOTE - The style guide specifies two different styles for braces,
+# so these are ignored for now to allow developers some
flexibility.
+nl_after_brace_close            = true      # Whether to add a
newline after '}'. Does not apply if followed by a
+                                            # necessary ';'.
+nl_brace_else                   = remove    # Add or remove
newline between '}' and 'else'.
+nl_brace_while                  = remove    # Add or remove
newline between '}' and 'while' of 'do' statement.
+nl_do_brace                     = remove    # Add or remove
newline between 'do' and '{'.
+nl_else_brace                   = remove    # Add or remove
newline between 'else' and '{'.
+nl_else_if                      = remove    # Add or remove
newline between 'else' and 'if'.
+nl_elseif_brace                 = remove    # Add or remove
newline between 'else if' and '{'.
+nl_enum_brace                   = remove    # Add or remove
newline between 'enum' and '{'.
+nl_fcall_brace                  = remove    # Add or remove
newline between a function call's ')' and '{',
+                                            # as in
'list_for_each(item, &list) { }'.
+nl_for_brace                    = remove    # Add or remove
newline between 'for' and '{'.
+nl_if_brace                     = remove    # Add or remove
newline between 'if' and '{'.
+nl_struct_brace                 = remove    # Add or remove
newline between 'struct and '{'.
+nl_switch_brace                 = remove    # Add or remove
newline between 'switch' and '{'.
+nl_union_brace                  = remove    # Add or remove
newline between 'union' and '{'.
+nl_while_brace                  = remove    # Add or remove
newline between 'while' and '{'.
+
+# Additional whitespace rules (Optional)
+sp_after_ptr_star               = remove    # Add or remove
space after pointer star '*', if followed by a word.
+                                            # Useful when paired
with align_var_def_star_style==2
+sp_after_ptr_star_func          = remove    # Add or remove
space after a pointer star '*', if followed by a
function
+                                            # prototype or
function definition.
+sp_after_semi                   = remove    # Add or remove
space after ';', except when followed by a comment.
+sp_before_case_colon            = remove    # Add or remove
space before case ':'.
+sp_before_ptr_star              = add       # Add or remove
space before pointer star '*'.
+sp_before_ptr_star_func         = add       # Add or remove
space before a pointer star '*', if followed by a
function
+                                            # prototype or
function definition.
+sp_before_semi                  = remove    # Add or remove
space before ';'
+sp_before_semi_for              = remove    # Add or remove
space before ';' in non-empty 'for' statements.
+sp_before_semi_for_empty        = add       # Add or remove
space before a semicolon of an empty part of a for
statement
+sp_between_ptr_star             = remove    # Add or remove
space between pointer stars '*'. (ie, 'VOID **')
+sp_brace_close_while            = force     # Add or remove
space between '}' and 'while'.
+
+sp_after_cast                   = remove
+sp_after_type                   = add
+sp_balance_nested_parens        = false
+sp_before_nl_cont               = add
+sp_before_square_asm_block      = ignore
+sp_before_unnamed_byref         = add
+sp_brace_brace                  = ignore
+sp_brace_else                   = force
+sp_brace_typedef                = add
+sp_case_label                   = force
+sp_cmt_cpp_doxygen              = true
+sp_cond_colon                   = add
+sp_cond_question                = add
+sp_cpp_cast_paren               = force
+sp_else_brace                   = force
+sp_endif_cmt                    = force
+sp_enum_assign                  = add
+sp_inside_braces                = force
+sp_inside_braces_empty          = force
+sp_inside_braces_enum           = force
+sp_inside_braces_struct         = force
+sp_pp_concat                    = add
+sp_pp_stringify                 = add
+sp_return_paren                 = add
+sp_special_semi                 = force
+sp_while_paren_open             = force
+
+# Additional Indentation Rules
+indent_access_spec              = 1
+indent_access_spec_body         = false
+indent_align_assign             = true
+indent_align_string             = true
+indent_bool_paren               = true
+indent_brace_parent             = false
+indent_braces                   = false
+indent_braces_no_class          = false
+indent_braces_no_func           = true
+indent_braces_no_struct         = false
+indent_class                    = false
+indent_class_colon              = false
+indent_cmt_with_tabs            = false         # Whether to
indent comments that are not at a brace level with
tabs
on
+                                                # a tabstop.
Requires indent_with_tabs=2. If false, will use
spaces.
+indent_col1_comment             = true
+indent_col1_multi_string_literal= true
+indent_comma_paren              = true
+indent_else_if                  = true
+indent_extern                   = false
+indent_first_bool_expr          = true
+
+indent_func_def_param_paren_pos_threshold = 0
+indent_func_param_double                  = false
+indent_func_proto_param                   = true
+indent_ignore_asm_block                   = true
+indent_label                              = 1
+indent_member                             = 2
+indent_namespace                          = false
+indent_param                              = 2
+indent_paren_nl                           = false
+indent_paren_open_brace                   = false
+indent_preserve_sql                       = false
+indent_relative_single_line_comments      = false
+indent_sing_line_comments                 = 0
+indent_single_newlines                    = false
+indent_square_nl                          = false
+indent_switch_case                        = 2
+indent_template_param                     = true
+indent_var_def_blk                        = 0
+indent_var_def_cont                       = false
+
+# Tidy-up rules (Optional)
+mod_move_case_break             = true      # Whether to move a
'break' that appears after a fully braced 'case'
+                                            # before the close
brace, as in 'case X: { ... } break;' =>
+                                            # 'case X: { ...
break; }'.
+mod_pawn_semicolon              = false
+mod_remove_empty_return         = false     # Whether to remove
a void 'return;' that appears as the last
statement
+                                            # in a function.
+mod_remove_extra_semicolon      = true
+mod_sort_import                 = false
+mod_sort_include                = false
+mod_sort_using                  = false
+nl_after_case                   = false     # Whether to add a
newline after a 'case' statement.
+nl_end_of_file                  = force     # Add or remove
newline at the end of the file.
+nl_end_of_file_min              = 1         # The minimum number
of newlines at the end of the file
+nl_max                          = 2         # The maximum number
of consecutive newlines (3 = 2 blank lines).
+nl_start_of_file                = remove    # Add or remove
newlines at the start of the file.
+
+# Code alignment rules (Optional)
+align_asm_colon                 = false
+align_assign_span               = 1         # The span for
aligning on '=' in assignments.
+align_assign_thresh             = 4
+align_edk2_style                = true      # Whether to apply
edk2-specific alignment formatting
+align_enum_equ_span             = 1         # The span for
aligning on '=' in enums.
+align_func_params               = true      # Whether to align
variable definitions in prototypes and functions.
+align_func_params_gap           = 2
+align_func_params_span          = 2         # The span for
aligning parameter definitions in function on parameter
name.
+align_func_params_thresh        = 0
+align_func_proto_span           = 0
+align_keep_tabs                 = false
+align_left_shift                = false
+align_mix_var_proto             = false
+align_nl_cont                   = false
+align_oc_decl_colon             = false
+align_on_operator               = false
+align_on_tabstop                = false
+align_pp_define_gap             = 2
+align_pp_define_span            = 1
+align_right_cmt_at_col          = 0         # Align trailing
comment at or beyond column N; 'pulls in' comments as
+                                            # a bonus side
effect (0=ignore)
+align_right_cmt_gap             = 0         # If a trailing
comment is more than this number of columns away from
the
+                                            # text it follows,
+                                            # it will qualify
for being aligned. This has to be > 0 to do
anything.
+align_right_cmt_mix             = false     # If aligning
comments, mix with comments after '}' and #endif with
less
+                                            # than 3 spaces
before the comment
+align_right_cmt_same_level      = true      # Whether to only
align trailing comments that are at the same brace
level.
+align_right_cmt_span            = 2         # The span for
aligning comments that end lines.
+align_same_func_call_params     = false
+align_single_line_brace         = true
+align_single_line_func          = true
+align_struct_init_span          = 1         # The span for
aligning struct initializer values.
+align_typedef_amp_style         = 1
+align_typedef_func              = 1         # How to align
typedef'd functions with other typedefs.
+                                            # (0: No align, 1:
Align open paranthesis, 2: Align function type
name)
+align_typedef_gap               = 2
+align_typedef_span              = 1         # The span for
aligning single-line typedefs.
+align_typedef_star_style        = 1
+align_var_def_amp_style         = 1
+align_var_def_attribute         = true
+align_var_def_colon             = true      # Whether to align
the colon in struct bit fields.
+align_var_def_gap               = 2         # The gap (minimum
spacing for aligned items) for variable
definitions.
+align_var_def_inline            = false
+align_var_def_span              = 1         # The span (lines
needed to align) for aligning variable definitions.
+align_var_def_star_style        = 1         # How to consider
(or treat) the '*' in the alignment of variable
+                                            # definitions.
+                                            # 0: Part of the
type     'void *   foo;' (default)
+                                            # 1: Part of the
variable 'void     *foo;'
+                                            # 2:
Dangling             'void    *foo;'
+                                            # (Note - should
also set sp_after_ptr_star=remove)
+align_var_struct_gap            = 4
+align_var_struct_span           = 8         # The span for
aligning struct/union member definitions.
+align_var_struct_thresh         = 0
+align_with_tabs                 = false
+
+# Comment formatting
+cmt_align_doxygen_javadoc_tags  = true      # Whether to align
doxygen javadoc-style tags ('@param', '@return',
etc.)
+                                            # TODO: Eats '[' in
'[in]'
+cmt_c_group                     = false
+cmt_c_nl_end                    = true      # Whether to add a
newline before the closing '*/' of the combined c-
comment.
+cmt_c_nl_start                  = true
+cmt_cpp_group                   = false
+cmt_cpp_nl_end                  = true
+cmt_cpp_nl_start                = true
+cmt_cpp_to_c                    = false
+cmt_indent_multi                = false     # Whether to apply
changes to multi-line comments, including
cmt_width,
+                                            # keyword
substitution and leading chars.
+cmt_insert_before_preproc       = false
+cmt_insert_file_header          = default_file_header.txt
+cmt_insert_func_header          = default_function_header.txt
+cmt_multi_check_last            = false
+cmt_multi_first_len_minimum     = 2
+cmt_reflow_mode                 = 1         # How to reflow
comments.
+                                            # (0:No reflow, 1:No
touching at all, 2: Full reflow)
+cmt_sp_after_star_cont          = 0         # The number of
spaces to insert after the star on subsequent comment
lines.
+cmt_sp_before_star_cont         = 0         # The number of
spaces to insert at the start of subsequent comment
lines.
+cmt_star_cont                   = false     # Whether to put a
star on subsequent comment lines.
+cmt_width                       = 120       # Try to wrap
comments at N columns.
+sp_cmt_cpp_start                = add       # Add or remove
space after the opening of a C++ comment, as in
+                                            # '// <here> A'.
NOTE: Breaks indentation within comments.
+
+# Function definitions / declarations
+indent_func_call_param          = false     # Whether to indent
continued function call parameters one indent
level,
+                                            # rather than
aligning parameters under the open parenthesis.
+indent_func_class_param         = false     # Whether to indent
continued function call declaration one indent
level,
+                                            # rather than
aligning parameters under the open parenthesis.
+indent_func_ctor_var_param      = false     # Whether to indent
continued class variable constructors one indent
level,
+                                            # rather than
aligning parameters under the open parenthesis.
+indent_func_def_param           = true      # Whether to indent
continued function definition parameters one
indent
+                                            # level, rather than
aligning parameters under the open parenthesis.
+nl_fdef_brace                   = add       # Add or remove
newline between function signature and '{'.
+nl_func_call_end_multi_line     = true      # Whether to add a
newline before ')' in a function call if '(' and
')'
are
+                                            # in different lines.
+nl_func_call_paren              = remove    # Add or remove
newline between a function name and the opening '(' in
the
+                                            # call.
+nl_func_call_start_multi_line   = true      # Whether to add a
newline after '(' in a function call if '(' and ')'
are
+                                            # in different lines.
+nl_func_decl_args               = force     # Add or remove
newline after each ',' in a function declaration.
+nl_func_decl_empty              = add       # Add or remove
newline between '()' in a function declaration.
+nl_func_def_args                = force     # Add or remove
newline after each ',' in a function definition.
+nl_func_def_empty               = add       # Add or remove
newline between '()' in a function definition.
+nl_func_def_paren               = remove    # Add or remove
newline between a function name and the opening '('
+                                            # in the definition.
+nl_func_paren                   = remove    # Add or remove
newline between a function name and the opening '(' in
+                                            # the declaration.
+nl_func_type_name               = add       # Add or remove
newline between return type and function name in a
function
+                                            # definition.
+sp_fparen_brace                 = force     # Add or remove
space between ')' and '{' of function.
+use_indent_func_call_param      = true      #
indent_func_call_param will be used
+
+# Additional Newline Rules
+nl_after_brace_open                          = true     #
Whether to add a newline after '{'. This also adds a
newline
+                                                        # before
the matching '}'.
+nl_after_brace_open_cmt                      = true     #
Whether to add a newline between the open brace and a
+                                                        #
trailing single-line comment.
+                                                        #
Requires nl_after_brace_open = true.
+nl_after_do                                  = add      # Add or
remove blank line after 'do/while' statement.
+nl_after_for                                 = add      # Add or
remove blank line after 'for' statement.
+nl_after_func_body                           = 2        # The
number of newlines after '}' of a multi-line
function
body
+nl_after_func_body_one_liner                 = 2
+nl_after_func_proto                          = 2
+nl_after_func_proto_group                    = 2
+nl_after_if                                  = add
+nl_after_multiline_comment                   = false
+nl_after_return                              = false
+nl_after_struct                              = 2
+nl_after_switch                              = add
+nl_after_vbrace_close                        = true
+nl_after_vbrace_open                         = true
+nl_after_vbrace_open_empty                   = true
+nl_after_while                               = add
+nl_assign_leave_one_liners                   = true
+nl_before_block_comment                      = 2
+nl_before_case                               = false
+nl_before_do                                 = ignore
+nl_before_for                                = ignore
+nl_before_if                                 = ignore
+nl_before_switch                             = ignore
+nl_before_while                              = ignore
+nl_before_whole_file_ifdef                   = 2
+nl_brace_brace                               = force
+nl_brace_struct_var                          = remove
+nl_case_colon_brace                          = add
+nl_class_leave_one_liners                    = false
+nl_collapse_empty_body                       = false
+nl_comment_func_def                          = 1
+nl_create_for_one_liner                      = false
+nl_create_if_one_liner                       = false
+nl_create_while_one_liner                    = false
+nl_define_macro                              = false
+nl_ds_struct_enum_close_brace                = true
+nl_ds_struct_enum_cmt                        = false
+nl_enum_leave_one_liners                     = false
+nl_func_decl_end                             = add
+nl_func_decl_start                           = add
+nl_func_def_end                              = add
+nl_func_def_start                            = add
+nl_func_leave_one_liners                     = false
+nl_func_proto_type_name                      = add
+nl_func_var_def_blk                          = 1
+nl_getset_leave_one_liners                   = false
+nl_if_leave_one_liners                       = false
+nl_multi_line_define                         = false
+nl_squeeze_ifdef                             = false
+nl_var_def_blk_end                           = 0
+nl_var_def_blk_start                         = 0
+
+# Preprocessor Rules
+pp_define_at_level      = true
+pp_if_indent_code       = false
+pp_indent_func_def      = false
+pp_indent_extern        = false
+pp_ignore_define_body   = true                # Workaround: Turn
off processing for #define body
+                                              # (current rules
do not work for some defines)
+pp_indent               = add
+pp_indent_at_level      = true
+pp_indent_count         = 2
+pp_indent_if            = 2
+pp_indent_region        = 2
+pp_region_indent_code   = false
+pp_space                = remove
+
+#
+# The tokens below are assigned specific types so they are
always recognized properly.
+#
+
+# Explicitly define EDK II qualifiers
+set QUALIFIER CONST
+set QUALIFIER EFIAPI
+set QUALIFIER IN
+set QUALIFIER OPTIONAL
+set QUALIFIER OUT
+
+# Explicitly define EDK II types
+set TYPE EFI_STATUS
+set TYPE VOID
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
new file mode 100644
index 000000000000..d8c22403b4b1
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_ext_dep.yaml
@@ -0,0 +1,16 @@
+## @file
+# Downloads the Uncrustify application from a Project Mu NuGet
package.
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+  "id": "uncrustify-ci-1",
+  "scope": "cibuild",
+  "type": "nuget",
+  "name": "mu-uncrustify-release",
+  "source":
"https://pkgs.dev.azure.com/projectmu/Uncrustify/_packaging/mu_uncrustify/nuget/v3/index.json",

+  "version": "73.0.3",
+  "flags": ["set_shell_var", "host_specific"],
+  "var_name": "UNCRUSTIFY_CI_PATH"
+}
diff --git a/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
new file mode 100644
index 000000000000..06c76af02759
--- /dev/null
+++ b/.pytool/Plugin/UncrustifyCheck/uncrustify_plug_in.yaml
@@ -0,0 +1,11 @@
+## @file
+# CiBuildPlugin used to check coding standard compliance of EDK
II style C source code
+#
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+{
+  "scope": "cibuild",
+  "name": "Uncrustify Coding Standard Test",
+  "module": "UncrustifyCheck"
+}
diff --git a/.pytool/Readme.md b/.pytool/Readme.md
index f6505507966a..e0d07f317049 100644
--- a/.pytool/Readme.md
+++ b/.pytool/Readme.md
@@ -264,6 +264,10 @@ BSD-2-Clause-Patent.
   Run the Ecc tool on the package. The Ecc tool is available in
the BaseTools
   package. It checks that the code complies to the EDKII coding
standard.

+### Coding Standard Compliance - UncrustifyCheck
+
+Runs the Uncrustify application to check for coding standard
compliance issues.
+
   ## PyTool Scopes

   Scopes are how the PyTool ext_dep, path_env, and plugins are
activated.  Meaning
--
2.28.0.windows.1