1 # @file UncrustifyCheck.py
3 # An edk2-pytool based plugin wrapper for Uncrustify
5 # Copyright (c) Microsoft Corporation.
6 # SPDX-License-Identifier: BSD-2-Clause-Patent
16 from edk2toolext
.environment
import version_aggregator
17 from edk2toolext
.environment
.plugin_manager
import PluginManager
18 from edk2toolext
.environment
.plugintypes
.ci_build_plugin
import ICiBuildPlugin
19 from edk2toolext
.environment
.plugintypes
.uefi_helper_plugin
import HelperFunctions
20 from edk2toolext
.environment
.var_dict
import VarDict
21 from edk2toollib
.gitignore_parser
import parse_gitignore_lines
22 from edk2toollib
.log
.junit_report_format
import JunitReportTestCase
23 from edk2toollib
.uefi
.edk2
.path_utilities
import Edk2Path
24 from edk2toollib
.utility_functions
import RunCmd
25 from io
import StringIO
26 from typing
import Any
, Dict
, List
, Tuple
29 # Provide more user friendly messages for certain scenarios
31 class UncrustifyException(Exception):
32 def __init__(self
, message
, exit_code
):
33 super().__init
__(message
)
34 self
.exit_code
= exit_code
37 class UncrustifyAppEnvVarNotFoundException(UncrustifyException
):
38 def __init__(self
, message
):
39 super().__init
__(message
, -101)
42 class UncrustifyAppVersionErrorException(UncrustifyException
):
43 def __init__(self
, message
):
44 super().__init
__(message
, -102)
47 class UncrustifyAppExecutionException(UncrustifyException
):
48 def __init__(self
, message
):
49 super().__init
__(message
, -103)
52 class UncrustifyStalePluginFormattedFilesException(UncrustifyException
):
53 def __init__(self
, message
):
54 super().__init
__(message
, -120)
57 class UncrustifyInputFileCreationErrorException(UncrustifyException
):
58 def __init__(self
, message
):
59 super().__init
__(message
, -121)
61 class UncrustifyInvalidIgnoreStandardPathsException(UncrustifyException
):
62 def __init__(self
, message
):
63 super().__init
__(message
, -122)
65 class UncrustifyGitIgnoreFileException(UncrustifyException
):
66 def __init__(self
, message
):
67 super().__init
__(message
, -140)
70 class UncrustifyGitSubmoduleException(UncrustifyException
):
71 def __init__(self
, message
):
72 super().__init
__(message
, -141)
75 class UncrustifyCheck(ICiBuildPlugin
):
77 A CiBuildPlugin that uses Uncrustify to check the source files in the
78 package being tested for coding standard issues.
80 By default, the plugin runs against standard C source file extensions but
81 its configuration can be modified through its configuration file.
83 Configuration options:
85 "AdditionalIncludePaths": [], # Additional paths to check formatting (wildcards supported).
86 "AuditOnly": False, # Don't fail the build if there are errors. Just log them.
87 "ConfigFilePath": "", # Custom path to an Uncrustify config file.
88 "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignored.
89 "OutputFileDiffs": False, # Output chunks of formatting diffs in the test case log.
90 # This can significantly slow down the plugin on very large packages.
91 "SkipGitExclusions": False # Don't exclude git ignored files and files in git submodules.
96 # By default, use an "uncrustify.cfg" config file in the plugin directory
97 # A package can override this path via "ConfigFilePath"
99 # Note: Values specified via "ConfigFilePath" are relative to the package
101 DEFAULT_CONFIG_FILE_PATH
= os
.path
.join(
102 pathlib
.Path(__file__
).parent
.resolve(), "uncrustify.cfg")
105 # The extension used for formatted files produced by this plugin
107 FORMATTED_FILE_EXTENSION
= ".uncrustify_plugin"
110 # A package can add any additional paths with "AdditionalIncludePaths"
111 # A package can remove any of these paths with "IgnoreStandardPaths"
113 STANDARD_PLUGIN_DEFINED_PATHS
= ("*.c", "*.h")
116 # The Uncrustify application path should set in this environment variable
118 UNCRUSTIFY_PATH_ENV_KEY
= "UNCRUSTIFY_CI_PATH"
120 def GetTestName(self
, packagename
: str, environment
: VarDict
) -> Tuple
:
121 """ Provide the testcase name and classname for use in reporting
124 packagename: string containing name of package to build
125 environment: The VarDict for the test to run in
127 A tuple containing the testcase name and the classname
128 (testcasename, classname)
129 testclassname: a descriptive string for the testcase can include whitespace
130 classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
132 return ("Check file coding standard compliance in " + packagename
, packagename
+ ".UncrustifyCheck")
134 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:
136 External function of plugin. This function is used to perform the task of the CiBuild Plugin.
139 - package_rel_path: edk2 workspace relative path to the package
140 - edk2_path: Edk2Path object with workspace and packages paths
141 - package_config: Dictionary with the package configuration
142 - environment_config: Environment configuration
143 - plugin_manager: Plugin Manager Instance
144 - plugin_manager_helper: Plugin Manager Helper Instance
145 - tc: JUnit test case
146 - output_stream: The StringIO output stream from this plugin (logging)
149 >0 : Number of errors found
150 0 : Passed successfully
151 -1 : Skipped for missing prereq
154 # Initialize plugin and check pre-requisites.
155 self
._initialize
_environment
_info
(
156 package_rel_path
, edk2_path
, package_config
, tc
)
157 self
._initialize
_configuration
()
158 self
._check
_for
_preexisting
_formatted
_files
()
160 # Log important context information.
161 self
._log
_uncrustify
_app
_info
()
163 # Get template file contents if specified
164 self
._get
_template
_file
_contents
()
166 # Create meta input files & directories
167 self
._create
_temp
_working
_directory
()
168 self
._create
_uncrustify
_file
_list
_file
()
170 self
._run
_uncrustify
()
172 # Post-execution actions.
173 self
._process
_uncrustify
_results
()
175 except UncrustifyException
as e
:
176 self
._tc
.LogStdError(
177 f
"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
179 f
"Uncrustify error {e.exit_code}. Details:\n\n{str(e)}")
182 if self
._formatted
_file
_error
_count
> 0:
183 if self
._audit
_only
_mode
:
185 "Setting test as skipped since AuditOnly is enabled")
186 self
._tc
.SetSkipped()
190 f
"{self._plugin_name} failed due to {self._formatted_file_error_count} incorrectly formatted files.", "CHECK_FAILED")
192 self
._tc
.SetSuccess()
193 return self
._formatted
_file
_error
_count
195 self
._cleanup
_temporary
_formatted
_files
()
196 self
._cleanup
_temporary
_directory
()
198 def _initialize_configuration(self
) -> None:
200 Initializes plugin configuration.
202 self
._initialize
_app
_info
()
203 self
._initialize
_config
_file
_info
()
204 self
._initialize
_file
_to
_format
_info
()
205 self
._initialize
_test
_case
_output
_options
()
207 def _check_for_preexisting_formatted_files(self
) -> None:
209 Checks if any formatted files from prior execution are present.
211 Existence of such files is an unexpected condition. This might result
212 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.
214 pre_existing_formatted_file_count
= len(
215 [str(path
.resolve()) for path
in pathlib
.Path(self
._abs
_package
_path
).rglob(f
'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')])
217 if pre_existing_formatted_file_count
> 0:
218 raise UncrustifyStalePluginFormattedFilesException(
219 f
"{pre_existing_formatted_file_count} formatted files already exist. To prevent overwriting these files, please remove them before running this plugin.")
221 def _cleanup_temporary_directory(self
) -> None:
223 Cleans up the temporary directory used for this execution instance.
225 This removes the directory and all files created during this instance.
227 if hasattr(self
, '_working_dir'):
228 self
._remove
_tree
(self
._working
_dir
)
230 def _cleanup_temporary_formatted_files(self
) -> None:
232 Cleans up the temporary formmatted files produced by Uncrustify.
234 This will recursively remove all formatted files generated by Uncrustify
235 during this execution instance.
237 if hasattr(self
, '_abs_package_path'):
238 formatted_files
= [str(path
.resolve()) for path
in pathlib
.Path(
239 self
._abs
_package
_path
).rglob(f
'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
241 for formatted_file
in formatted_files
:
242 os
.remove(formatted_file
)
244 def _create_temp_working_directory(self
) -> None:
246 Creates the temporary directory used for this execution instance.
248 self
._working
_dir
= os
.path
.join(
249 self
._abs
_workspace
_path
, "Build", ".pytool", "Plugin", f
"{self._plugin_name}")
252 pathlib
.Path(self
._working
_dir
).mkdir(parents
=True, exist_ok
=True)
254 raise UncrustifyInputFileCreationErrorException(
255 f
"Error creating plugin directory {self._working_dir}.\n\n{repr(e)}.")
257 def _create_uncrustify_file_list_file(self
) -> None:
259 Creates the file with the list of source files for Uncrustify to process.
261 self
._app
_input
_file
_path
= os
.path
.join(
262 self
._working
_dir
, "uncrustify_file_list.txt")
264 with
open(self
._app
_input
_file
_path
, 'w', encoding
='utf8') as f
:
265 f
.writelines(f
"\n".join(self
._abs
_file
_paths
_to
_format
))
267 def _execute_uncrustify(self
) -> None:
269 Executes Uncrustify with the initialized configuration.
272 self
._app
_exit
_code
= RunCmd(
274 f
"-c {self._app_config_file} -F {self._app_input_file_path} --if-changed --suffix {UncrustifyCheck.FORMATTED_FILE_EXTENSION}", outstream
=output
)
275 self
._app
_output
= output
.getvalue().strip().splitlines()
277 def _get_files_ignored_in_config(self
):
279 Returns a function that returns true if a given file string path is ignored in the plugin configuration file and false otherwise.
282 if "IgnoreFiles" in self
._package
_config
:
283 ignored_files
= self
._package
_config
["IgnoreFiles"]
285 # Pass "Package configuration file" as the source file path since
286 # the actual configuration file name is unknown to this plugin and
287 # this provides a generic description of the file that provided
288 # the ignore file content.
290 # This information is only used for reporting (not used here) and
291 # the ignore lines are being passed directly as they are given to
293 return parse_gitignore_lines(ignored_files
, "Package configuration file", self
._abs
_package
_path
)
295 def _get_git_ignored_paths(self
) -> List
[str]:
297 Returns a list of file absolute path strings to all files ignored in this git repository.
299 If git is not found, an empty list will be returned.
301 if not shutil
.which("git"):
303 "Git is not found on this system. Git submodule paths will not be considered.")
306 outstream_buffer
= StringIO()
307 exit_code
= RunCmd("git", "ls-files --other",
308 workingdir
=self
._abs
_workspace
_path
, outstream
=outstream_buffer
, logging_level
=logging
.NOTSET
)
310 raise UncrustifyGitIgnoreFileException(
311 f
"An error occurred reading git ignore settings. This will prevent Uncrustify from running against the expected set of files.")
313 # Note: This will potentially be a large list, but at least sorted
314 rel_paths
= outstream_buffer
.getvalue().strip().splitlines()
316 for path
in rel_paths
:
318 os
.path
.normpath(os
.path
.join(self
._abs
_workspace
_path
, path
)))
321 def _get_git_submodule_paths(self
) -> List
[str]:
323 Returns a list of directory absolute path strings to the root of each submodule in the workspace repository.
325 If git is not found, an empty list will be returned.
327 if not shutil
.which("git"):
329 "Git is not found on this system. Git submodule paths will not be considered.")
332 if os
.path
.isfile(os
.path
.join(self
._abs
_workspace
_path
, ".gitmodules")):
334 f
".gitmodules file found. Excluding submodules in {self._package_name}.")
336 outstream_buffer
= StringIO()
337 exit_code
= RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir
=self
._abs
_workspace
_path
, outstream
=outstream_buffer
, logging_level
=logging
.NOTSET
)
339 raise UncrustifyGitSubmoduleException(
340 f
".gitmodule file detected but an error occurred reading the file. Cannot proceed with unknown submodule paths.")
343 for line
in outstream_buffer
.getvalue().strip().splitlines():
344 submodule_paths
.append(
345 os
.path
.normpath(os
.path
.join(self
._abs
_workspace
_path
, line
.split()[1])))
347 return submodule_paths
351 def _get_template_file_contents(self
) -> None:
353 Gets the contents of Uncrustify template files if they are specified
354 in the Uncrustify configuration file.
357 self
._file
_template
_contents
= None
358 self
._func
_template
_contents
= None
360 # Allow no value to allow "set" statements in the config file which do
361 # not specify value assignment
362 parser
= configparser
.ConfigParser(allow_no_value
=True)
363 with
open(self
._app
_config
_file
, 'r') as cf
:
364 parser
.read_string("[dummy_section]\n" + cf
.read())
367 file_template_name
= parser
["dummy_section"]["cmt_insert_file_header"]
369 file_template_path
= pathlib
.Path(file_template_name
)
371 if not file_template_path
.is_file():
372 file_template_path
= pathlib
.Path(os
.path
.join(self
._plugin
_path
, file_template_name
))
373 self
._file
_template
_contents
= file_template_path
.read_text()
375 logging
.warn("A file header template is not specified in the config file.")
376 except FileNotFoundError
:
377 logging
.warn("The specified file header template file was not found.")
379 func_template_name
= parser
["dummy_section"]["cmt_insert_func_header"]
381 func_template_path
= pathlib
.Path(func_template_name
)
383 if not func_template_path
.is_file():
384 func_template_path
= pathlib
.Path(os
.path
.join(self
._plugin
_path
, func_template_name
))
385 self
._func
_template
_contents
= func_template_path
.read_text()
387 logging
.warn("A function header template is not specified in the config file.")
388 except FileNotFoundError
:
389 logging
.warn("The specified function header template file was not found.")
391 def _initialize_app_info(self
) -> None:
393 Initialize Uncrustify application information.
395 This function will determine the application path and version.
397 # Verify Uncrustify is specified in the environment.
398 if UncrustifyCheck
.UNCRUSTIFY_PATH_ENV_KEY
not in os
.environ
:
399 raise UncrustifyAppEnvVarNotFoundException(
400 f
"Uncrustify environment variable {UncrustifyCheck.UNCRUSTIFY_PATH_ENV_KEY} is not present.")
402 self
._app
_path
= shutil
.which('uncrustify', path
=os
.environ
[UncrustifyCheck
.UNCRUSTIFY_PATH_ENV_KEY
])
404 if self
._app
_path
is None:
405 raise FileNotFoundError(
406 errno
.ENOENT
, os
.strerror(errno
.ENOENT
), self
._app
_path
)
408 self
._app
_path
= os
.path
.normcase(os
.path
.normpath(self
._app
_path
))
410 if not os
.path
.isfile(self
._app
_path
):
411 raise FileNotFoundError(
412 errno
.ENOENT
, os
.strerror(errno
.ENOENT
), self
._app
_path
)
414 # Verify Uncrustify is present at the expected path.
415 return_buffer
= StringIO()
416 ret
= RunCmd(self
._app
_path
, "--version", outstream
=return_buffer
)
418 raise UncrustifyAppVersionErrorException(
419 f
"Error occurred executing --version: {ret}.")
421 # Log Uncrustify version information.
422 self
._app
_version
= return_buffer
.getvalue().strip()
423 self
._tc
.LogStdOut(f
"Uncrustify version: {self._app_version}")
424 version_aggregator
.GetVersionAggregator().ReportVersion(
425 "Uncrustify", self
._app
_version
, version_aggregator
.VersionTypes
.INFO
)
427 def _initialize_config_file_info(self
) -> None:
429 Initialize Uncrustify configuration file info.
431 The config file path is relative to the package root.
433 self
._app
_config
_file
= UncrustifyCheck
.DEFAULT_CONFIG_FILE_PATH
434 if "ConfigFilePath" in self
._package
_config
:
435 self
._app
_config
_file
= self
._package
_config
["ConfigFilePath"].strip()
437 self
._app
_config
_file
= os
.path
.normpath(
438 os
.path
.join(self
._abs
_package
_path
, self
._app
_config
_file
))
440 if not os
.path
.isfile(self
._app
_config
_file
):
441 raise FileNotFoundError(
442 errno
.ENOENT
, os
.strerror(errno
.ENOENT
), self
._app
_config
_file
)
444 def _initialize_environment_info(self
, package_rel_path
: str, edk2_path
: Edk2Path
, package_config
: Dict
[str, List
[str]], tc
: JunitReportTestCase
) -> None:
446 Initializes plugin environment information.
448 self
._abs
_package
_path
= edk2_path
.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
450 self
._abs
_workspace
_path
= edk2_path
.WorkspacePath
451 self
._package
_config
= package_config
452 self
._package
_name
= os
.path
.basename(
453 os
.path
.normpath(package_rel_path
))
454 self
._plugin
_name
= self
.__class
__.__name
__
455 self
._plugin
_path
= os
.path
.dirname(os
.path
.realpath(__file__
))
456 self
._rel
_package
_path
= package_rel_path
459 def _initialize_file_to_format_info(self
) -> None:
461 Forms the list of source files for Uncrustify to process.
463 # Create a list of all the package relative file paths in the package to run against Uncrustify.
464 rel_file_paths_to_format
= list(
465 UncrustifyCheck
.STANDARD_PLUGIN_DEFINED_PATHS
)
467 # Allow the ci.yaml to remove any of the pre-defined standard paths
468 if "IgnoreStandardPaths" in self
._package
_config
:
469 for a
in self
._package
_config
["IgnoreStandardPaths"]:
470 if a
.strip() in rel_file_paths_to_format
:
472 f
"Ignoring standard path due to ci.yaml ignore: {a}")
473 rel_file_paths_to_format
.remove(a
.strip())
475 raise UncrustifyInvalidIgnoreStandardPathsException(f
"Invalid IgnoreStandardPaths value: {a}")
477 # Allow the ci.yaml to specify additional include paths for this package
478 if "AdditionalIncludePaths" in self
._package
_config
:
479 rel_file_paths_to_format
.extend(
480 self
._package
_config
["AdditionalIncludePaths"])
482 self
._abs
_file
_paths
_to
_format
= []
483 for path
in rel_file_paths_to_format
:
484 self
._abs
_file
_paths
_to
_format
.extend(
485 [str(path
.resolve()) for path
in pathlib
.Path(self
._abs
_package
_path
).rglob(path
)])
487 # Remove files ignore in the plugin configuration file
488 plugin_ignored_files
= list(filter(self
._get
_files
_ignored
_in
_config
(), self
._abs
_file
_paths
_to
_format
))
490 if plugin_ignored_files
:
492 f
"{self._package_name} file count before plugin ignore file exclusion: {len(self._abs_file_paths_to_format)}")
493 for path
in plugin_ignored_files
:
494 if path
in self
._abs
_file
_paths
_to
_format
:
495 logging
.info(f
" File ignored in plugin config file: {path}")
496 self
._abs
_file
_paths
_to
_format
.remove(path
)
498 f
"{self._package_name} file count after plugin ignore file exclusion: {len(self._abs_file_paths_to_format)}")
500 if not "SkipGitExclusions" in self
._package
_config
or not self
._package
_config
["SkipGitExclusions"]:
501 # Remove files ignored by git
503 f
"{self._package_name} file count before git ignore file exclusion: {len(self._abs_file_paths_to_format)}")
505 ignored_paths
= self
._get
_git
_ignored
_paths
()
506 self
._abs
_file
_paths
_to
_format
= list(
507 set(self
._abs
_file
_paths
_to
_format
).difference(ignored_paths
))
510 f
"{self._package_name} file count after git ignore file exclusion: {len(self._abs_file_paths_to_format)}")
512 # Remove files in submodules
514 f
"{self._package_name} file count before submodule exclusion: {len(self._abs_file_paths_to_format)}")
516 submodule_paths
= tuple(self
._get
_git
_submodule
_paths
())
517 for path
in submodule_paths
:
518 logging
.info(f
" submodule path: {path}")
520 self
._abs
_file
_paths
_to
_format
= [
521 f
for f
in self
._abs
_file
_paths
_to
_format
if not f
.startswith(submodule_paths
)]
524 f
"{self._package_name} file count after submodule exclusion: {len(self._abs_file_paths_to_format)}")
526 # Sort the files for more consistent results
527 self
._abs
_file
_paths
_to
_format
.sort()
529 def _initialize_test_case_output_options(self
) -> None:
531 Initializes options that influence test case output.
533 self
._audit
_only
_mode
= False
534 self
._output
_file
_diffs
= True
536 if "AuditOnly" in self
._package
_config
and self
._package
_config
["AuditOnly"]:
537 self
._audit
_only
_mode
= True
539 if "OutputFileDiffs" in self
._package
_config
and not self
._package
_config
["OutputFileDiffs"]:
540 self
._output
_file
_diffs
= False
542 def _log_uncrustify_app_info(self
) -> None:
544 Logs Uncrustify application information.
546 self
._tc
.LogStdOut(f
"Found Uncrustify at {self._app_path}")
547 self
._tc
.LogStdOut(f
"Uncrustify version: {self._app_version}")
548 self
._tc
.LogStdOut('\n')
549 logging
.info(f
"Found Uncrustify at {self._app_path}")
550 logging
.info(f
"Uncrustify version: {self._app_version}")
553 def _process_uncrustify_results(self
) -> None:
555 Process the results from Uncrustify.
557 Determines whether formatting errors are present and logs failures.
559 formatted_files
= [str(path
.resolve()) for path
in pathlib
.Path(
560 self
._abs
_package
_path
).rglob(f
'*{UncrustifyCheck.FORMATTED_FILE_EXTENSION}')]
562 self
._formatted
_file
_error
_count
= len(formatted_files
)
564 if self
._formatted
_file
_error
_count
> 0:
566 "Visit the following instructions to learn "
567 "how to find the detailed formatting errors in Azure "
569 "https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Code-Formatting#how-to-find-uncrustify-formatting-errors-in-continuous-integration-ci")
570 self
._tc
.LogStdError("Files with formatting errors:\n")
572 if self
._output
_file
_diffs
:
573 logging
.info("Calculating file diffs. This might take a while...")
575 for formatted_file
in formatted_files
:
576 pre_formatted_file
= formatted_file
[:-
577 len(UncrustifyCheck
.FORMATTED_FILE_EXTENSION
)]
578 logging
.error(pre_formatted_file
)
580 if (self
._output
_file
_diffs
or
581 self
._file
_template
_contents
is not None or
582 self
._func
_template
_contents
is not None):
583 self
._tc
.LogStdError(
584 f
"Formatting errors in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
586 with
open(formatted_file
) as ff
:
587 formatted_file_text
= ff
.read()
589 if (self
._file
_template
_contents
is not None and
590 self
._file
_template
_contents
in formatted_file_text
):
591 self
._tc
.LogStdError(f
"File header is missing in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
593 if (self
._func
_template
_contents
is not None and
594 self
._func
_template
_contents
in formatted_file_text
):
595 self
._tc
.LogStdError(f
"A function header is missing in {os.path.relpath(pre_formatted_file, self._abs_package_path)}\n")
597 if self
._output
_file
_diffs
:
598 with
open(pre_formatted_file
) as pf
:
599 pre_formatted_file_text
= pf
.read()
601 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):
602 self
._tc
.LogStdError(line
)
604 self
._tc
.LogStdError('\n')
606 self
._tc
.LogStdError(pre_formatted_file
)
608 def _remove_tree(self
, dir_path
: str, ignore_errors
: bool = False) -> None:
610 Helper for removing a directory. Over time there have been
611 many private implementations of this due to reliability issues in the
612 shutil implementations. To consolidate on a single function this helper is added.
614 On error try to change file attributes. Also add retry logic.
616 This function is temporarily borrowed from edk2toollib.utility_functions
617 since the version used in edk2 is not recent enough to include the
620 This function should be replaced by "RemoveTree" when it is available.
623 - dir_path: Path to directory to remove.
624 - ignore_errors: Whether to ignore errors during removal
627 def _remove_readonly(func
, path
, _
):
629 Private function to attempt to change permissions on file/folder being deleted.
631 os
.chmod(path
, os
.stat
.S_IWRITE
)
634 for _
in range(3): # retry up to 3 times
636 shutil
.rmtree(dir_path
, ignore_errors
=ignore_errors
, onerror
=_remove_readonly
)
637 except OSError as err
:
638 logging
.warning(f
"Failed to fully remove {dir_path}: {err}")
642 raise RuntimeError(f
"Failed to remove {dir_path}")
644 def _run_uncrustify(self
) -> None:
646 Runs Uncrustify for this instance of plugin execution.
648 logging
.info("Executing Uncrustify. This might take a while...")
649 start_time
= timeit
.default_timer()
650 self
._execute
_uncrustify
()
651 end_time
= timeit
.default_timer() - start_time
653 execution_summary
= f
"Uncrustify executed against {len(self._abs_file_paths_to_format)} files in {self._package_name} in {end_time:.2f} seconds.\n"
655 self
._tc
.LogStdOut(execution_summary
)
656 logging
.info(execution_summary
)
658 if self
._app
_exit
_code
!= 0 and self
._app
_exit
_code
!= 1:
659 raise UncrustifyAppExecutionException(
660 f
"Error {str(self._app_exit_code)} returned from Uncrustify:\n\n{str(self._app_output)}")