2021-12-06 16:22:22 +01:00
# @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
2022-03-21 20:59:07 +01:00
from edk2toollib . gitignore_parser import parse_gitignore_lines
2021-12-06 16:22:22 +01:00
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 ( )
2022-03-21 20:59:07 +01:00
def _get_files_ignored_in_config ( self ) :
""" "
Returns a function that returns true if a given file string path is ignored in the plugin configuration file and false otherwise .
"""
ignored_files = [ ]
if " IgnoreFiles " in self . _package_config :
ignored_files = self . _package_config [ " IgnoreFiles " ]
# Pass "Package configuration file" as the source file path since
# the actual configuration file name is unknown to this plugin and
# this provides a generic description of the file that provided
# the ignore file content.
#
# This information is only used for reporting (not used here) and
# the ignore lines are being passed directly as they are given to
# this plugin.
2022-06-06 21:24:54 +02:00
return parse_gitignore_lines ( ignored_files , " Package configuration file " , self . _abs_package_path )
2022-03-21 20:59:07 +01:00
2021-12-06 16:22:22 +01:00
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
2022-03-16 18:52:20 +01:00
rel_paths = outstream_buffer . getvalue ( ) . strip ( ) . splitlines ( )
abs_paths = [ ]
for path in rel_paths :
abs_paths . append (
os . path . normpath ( os . path . join ( self . _abs_workspace_path , path ) ) )
return abs_paths
2021-12-06 16:22:22 +01:00
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 .
"""
2022-03-08 23:42:08 +01:00
self . _abs_package_path = edk2_path . GetAbsolutePathOnThisSystemFromEdk2RelativePath (
2021-12-06 16:22:22 +01:00
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 ) ] )
2022-03-21 20:59:07 +01:00
# Remove files ignore in the plugin configuration file
plugin_ignored_files = list ( filter ( self . _get_files_ignored_in_config ( ) , self . _abs_file_paths_to_format ) )
if plugin_ignored_files :
logging . info (
f " { self . _package_name } file count before plugin ignore file exclusion: { len ( self . _abs_file_paths_to_format ) } " )
for path in plugin_ignored_files :
if path in self . _abs_file_paths_to_format :
logging . info ( f " File ignored in plugin config file: { path } " )
self . _abs_file_paths_to_format . remove ( path )
logging . info (
f " { self . _package_name } file count after plugin ignore file exclusion: { len ( self . _abs_file_paths_to_format ) } " )
2021-12-06 16:22:22 +01:00
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
2022-02-10 16:58:03 +01:00
self . _output_file_diffs = True
2021-12-06 16:22:22 +01:00
if " AuditOnly " in self . _package_config and self . _package_config [ " AuditOnly " ] :
self . _audit_only_mode = True
2022-02-10 16:58:03 +01:00
if " OutputFileDiffs " in self . _package_config and not self . _package_config [ " OutputFileDiffs " ] :
self . _output_file_diffs = False
2021-12-06 16:22:22 +01:00
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 :
2022-04-20 03:13:18 +02:00
logging . error (
" Visit the following instructions to learn "
" how to find the detailed formatting errors in Azure "
" DevOps CI: "
" https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Code-Formatting#how-to-find-uncrustify-formatting-errors-in-continuous-integration-ci " )
2021-12-06 16:22:22 +01:00
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 ) ]
logging . error ( pre_formatted_file )
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 ) } " )