21 Check and apply the ns-3 coding style recursively to all files in the PATH arguments.
23 The coding style is defined with the clang-format tool, whose definitions are in
24 the ".clang-format" file. This script performs the following checks / fixes:
25 - Check / fix local #include headers with "ns3/" prefix. Respects clang-format guards.
26 - Check / apply clang-format. Respects clang-format guards.
27 - Check / trim trailing whitespace. Always checked.
28 - Check / replace tabs with spaces. Respects clang-format guards.
30 This script can be applied to all text files in a given path or to individual files.
32 NOTE: The formatting check requires clang-format (version >= 14) to be found on the path.
33 The remaining checks do not depend on clang-format and can be executed by disabling clang-format
34 checking with the "--no-formatting" option.
38 import concurrent.futures
45 from typing
import Callable, Dict, List, Tuple
50 CLANG_FORMAT_VERSIONS = [
57 CLANG_FORMAT_GUARD_ON =
"// clang-format on"
58 CLANG_FORMAT_GUARD_OFF =
"// clang-format off"
60 DIRECTORIES_TO_SKIP = [
75 FILE_EXTENSIONS_TO_CHECK_FORMATTING = [
81 FILE_EXTENSIONS_TO_CHECK_INCLUDE_PREFIXES = FILE_EXTENSIONS_TO_CHECK_FORMATTING
83 FILE_EXTENSIONS_TO_CHECK_WHITESPACE = [
113 FILES_TO_CHECK_WHITESPACE = [
118 FILE_EXTENSIONS_TO_CHECK_TABS = [
136 Check whether a directory should be analyzed.
138 @param dirpath Directory path.
139 @return Whether the directory should be analyzed.
142 _, directory = os.path.split(dirpath)
145 directory
in DIRECTORIES_TO_SKIP
or (directory.startswith(
".")
and directory !=
".")
151 files_to_check: List[str],
152 file_extensions_to_check: List[str],
155 Check whether a file should be analyzed.
157 @param path Path to the file.
158 @param files_to_check List of files that shall be checked.
159 @param file_extensions_to_check List of file extensions that shall be checked.
160 @return Whether the file should be analyzed.
163 filename = os.path.split(path)[1]
165 if filename
in FILES_TO_SKIP:
168 basename, extension = os.path.splitext(filename)
170 return basename
in files_to_check
or extension
in file_extensions_to_check
175 ) -> Tuple[List[str], List[str], List[str], List[str]]:
177 Find all files to be checked in a given list of paths.
179 @param paths List of paths to the files to check.
180 @return Tuple [List of files to check include prefixes,
181 List of files to check formatting,
182 List of files to check trailing whitespace,
183 List of files to check tabs].
186 files_to_check: List[str] = []
189 abs_path = os.path.abspath(os.path.expanduser(path))
191 if os.path.isfile(abs_path):
192 files_to_check.append(path)
194 elif os.path.isdir(abs_path):
195 for dirpath, dirnames, filenames
in os.walk(path, topdown=
True):
201 files_to_check.extend([os.path.join(dirpath, f)
for f
in filenames])
204 raise ValueError(f
"Error: {path} is not a file nor a directory")
206 files_to_check.sort()
208 files_to_check_include_prefixes: List[str] = []
209 files_to_check_formatting: List[str] = []
210 files_to_check_whitespace: List[str] = []
211 files_to_check_tabs: List[str] = []
213 for f
in files_to_check:
215 files_to_check_include_prefixes.append(f)
218 files_to_check_formatting.append(f)
221 files_to_check_whitespace.append(f)
224 files_to_check_tabs.append(f)
227 files_to_check_include_prefixes,
228 files_to_check_formatting,
229 files_to_check_whitespace,
236 Find the path to one of the supported versions of clang-format.
237 If no supported version of clang-format is found, raise an exception.
239 @return Path to clang-format.
243 for version
in CLANG_FORMAT_VERSIONS:
244 clang_format_path = shutil.which(f
"clang-format-{version}")
246 if clang_format_path:
247 return clang_format_path
250 clang_format_path = shutil.which(
"clang-format")
252 if clang_format_path:
253 process = subprocess.run(
254 [clang_format_path,
"--version"],
260 version = process.stdout.strip().split(
" ")[-1]
261 major_version = int(version.split(
".")[0])
263 if major_version
in CLANG_FORMAT_VERSIONS:
264 return clang_format_path
268 f
"Could not find any supported version of clang-format installed on this system. "
269 f
"List of supported versions: {CLANG_FORMAT_VERSIONS}."
278 enable_check_include_prefixes: bool,
279 enable_check_formatting: bool,
280 enable_check_whitespace: bool,
281 enable_check_tabs: bool,
287 Check / fix the coding style of a list of files.
289 @param paths List of paths to the files to check.
290 @param enable_check_include_prefixes Whether to enable checking #include headers from the same module with the "ns3/" prefix.
291 @param enable_check_formatting Whether to enable checking code formatting.
292 @param enable_check_whitespace Whether to enable checking trailing whitespace.
293 @param enable_check_tabs Whether to enable checking tabs.
294 @param fix Whether to fix (True) or just check (False) the file.
295 @param verbose Show the lines that are not compliant with the style.
296 @param n_jobs Number of parallel jobs.
297 @return Whether all files are compliant with all enabled style checks.
301 files_to_check_include_prefixes,
302 files_to_check_formatting,
303 files_to_check_whitespace,
307 check_include_prefixes_successful =
True
308 check_formatting_successful =
True
309 check_whitespace_successful =
True
310 check_tabs_successful =
True
312 if enable_check_include_prefixes:
314 '#include headers from the same module with the "ns3/" prefix',
316 files_to_check_include_prefixes,
320 respect_clang_format_guards=
True,
321 check_style_line_function=check_include_prefixes_line,
326 if enable_check_formatting:
328 "bad code formatting",
329 check_formatting_file,
330 files_to_check_formatting,
339 if enable_check_whitespace:
341 "trailing whitespace",
343 files_to_check_whitespace,
347 respect_clang_format_guards=
False,
348 check_style_line_function=check_whitespace_line,
353 if enable_check_tabs:
361 respect_clang_format_guards=
True,
362 check_style_line_function=check_tabs_line,
367 check_include_prefixes_successful,
368 check_formatting_successful,
369 check_whitespace_successful,
370 check_tabs_successful,
376 style_check_str: str,
377 check_style_file_function: Callable[..., Tuple[str, bool, List[str]]],
378 filenames: List[str],
385 Check / fix style of a list of files.
387 @param style_check_str Description of the check to be performed.
388 @param check_style_file_function Function used to check the file.
389 @param filename Name of the file to be checked.
390 @param fix Whether to fix (True) or just check (False) the file (True).
391 @param verbose Show the lines that are not compliant with the style.
392 @param n_jobs Number of parallel jobs.
393 @param kwargs Additional keyword arguments to the check_style_file_function.
394 @return Whether all files are compliant with the style.
398 non_compliant_files: List[str] = []
399 files_verbose_infos: Dict[str, List[str]] = {}
401 with concurrent.futures.ProcessPoolExecutor(n_jobs)
as executor:
402 non_compliant_files_results = executor.map(
403 check_style_file_function,
405 itertools.repeat(fix),
406 itertools.repeat(verbose),
407 *[arg
if isinstance(arg, list)
else itertools.repeat(arg)
for arg
in kwargs.values()],
410 for filename, is_file_compliant, verbose_infos
in non_compliant_files_results:
411 if not is_file_compliant:
412 non_compliant_files.append(filename)
415 files_verbose_infos[filename] = verbose_infos
418 if not non_compliant_files:
419 print(f
"- No files detected with {style_check_str}")
423 n_non_compliant_files = len(non_compliant_files)
426 print(f
"- Fixed {style_check_str} in the files ({n_non_compliant_files}):")
428 print(f
"- Detected {style_check_str} in the files ({n_non_compliant_files}):")
430 for f
in non_compliant_files:
432 print(*[f
" {l}" for l
in files_verbose_infos[f]], sep=
"\n")
447 clang_format_path: str,
448 ) -> Tuple[str, bool, List[str]]:
450 Check / fix the coding style of a file with clang-format.
452 @param filename Name of the file to be checked.
453 @param fix Whether to fix (True) or just check (False) the style of the file (True).
454 @param verbose Show the lines that are not compliant with the style.
455 @param clang_format_path Path to clang-format.
456 @return Tuple [Filename,
457 Whether the file is compliant with the style (before the check),
458 Verbose information].
461 verbose_infos: List[str] = []
464 process = subprocess.run(
472 f
"--ferror-limit={0 if verbose else 1}",
479 is_file_compliant = process.returncode == 0
482 verbose_infos = process.stderr.splitlines()
485 if fix
and not is_file_compliant:
486 process = subprocess.run(
494 stdout=subprocess.DEVNULL,
495 stderr=subprocess.DEVNULL,
498 return (filename, is_file_compliant, verbose_infos)
505 respect_clang_format_guards: bool,
506 check_style_line_function: Callable[[str, str, int], Tuple[bool, str, List[str]]],
507 ) -> Tuple[str, bool, List[str]]:
509 Check / fix a file manually using a function to check / fix each line.
511 @param filename Name of the file to be checked.
512 @param fix Whether to fix (True) or just check (False) the style of the file (True).
513 @param verbose Show the lines that are not compliant with the style.
514 @param respect_clang_format_guards Whether to respect clang-format guards.
515 @param check_style_line_function Function used to check each line.
516 @return Tuple [Filename,
517 Whether the file is compliant with the style (before the check),
518 Verbose information].
521 is_file_compliant =
True
522 verbose_infos: List[str] = []
523 clang_format_enabled =
True
525 with open(filename,
"r", encoding=
"utf-8")
as f:
526 file_lines = f.readlines()
528 for i, line
in enumerate(file_lines):
530 if respect_clang_format_guards:
531 line_stripped = line.strip()
533 if line_stripped == CLANG_FORMAT_GUARD_ON:
534 clang_format_enabled =
True
535 elif line_stripped == CLANG_FORMAT_GUARD_OFF:
536 clang_format_enabled =
False
538 if not clang_format_enabled
and line_stripped
not in (
539 CLANG_FORMAT_GUARD_ON,
540 CLANG_FORMAT_GUARD_OFF,
545 (is_line_compliant, line_fixed, line_verbose_infos) = check_style_line_function(
549 if not is_line_compliant:
550 is_file_compliant =
False
551 file_lines[i] = line_fixed
552 verbose_infos.extend(line_verbose_infos)
555 if not fix
and not verbose:
559 if fix
and not is_file_compliant:
560 with open(filename,
"w", encoding=
"utf-8")
as f:
561 f.writelines(file_lines)
563 return (filename, is_file_compliant, verbose_infos)
570 ) -> Tuple[bool, str, List[str]]:
572 Check / fix #include headers from the same module with the "ns3/" prefix in a line.
574 @param line The line to check.
575 @param filename Name of the file to be checked.
576 @param line_number The number of the line checked.
577 @return Tuple [Whether the line is compliant with the style (before the check),
579 Verbose information].
582 is_line_compliant =
True
584 verbose_infos: List[str] = []
587 line_stripped = line.strip()
588 header_file = re.findall(
r'^#include ["<]ns3/(.*\.h)[">]', line_stripped)
592 header_file = header_file[0]
593 parent_path = os.path.split(filename)[0]
595 if os.path.exists(os.path.join(parent_path, header_file)):
596 is_line_compliant =
False
598 line_stripped.replace(f
"ns3/{header_file}", header_file)
604 header_index = len(
'#include "')
606 verbose_infos.extend(
608 f
'{filename}:{line_number + 1}:{header_index + 1}: error: #include headers from the same module with the "ns3/" prefix detected',
610 f
' {"":{header_index}}^',
614 return (is_line_compliant, line_fixed, verbose_infos)
621 ) -> Tuple[bool, str, List[str]]:
623 Check / fix whitespace in a line.
625 @param line The line to check.
626 @param filename Name of the file to be checked.
627 @param line_number The number of the line checked.
628 @return Tuple [Whether the line is compliant with the style (before the check),
630 Verbose information].
633 is_line_compliant =
True
634 line_fixed = line.rstrip() +
"\n"
635 verbose_infos: List[str] = []
637 if line_fixed != line:
638 is_line_compliant =
False
639 line_fixed_stripped_expanded = line_fixed.rstrip().expandtabs(TAB_SIZE)
642 f
"{filename}:{line_number + 1}:{len(line_fixed_stripped_expanded) + 1}: error: Trailing whitespace detected",
643 f
" {line_fixed_stripped_expanded}",
644 f
' {"":{len(line_fixed_stripped_expanded)}}^',
647 return (is_line_compliant, line_fixed, verbose_infos)
654 ) -> Tuple[bool, str, List[str]]:
656 Check / fix tabs in a line.
658 @param line The line to check.
659 @param filename Name of the file to be checked.
660 @param line_number The number of the line checked.
661 @return Tuple [Whether the line is compliant with the style (before the check),
663 Verbose information].
666 is_line_compliant =
True
668 verbose_infos: List[str] = []
670 tab_index = line.find(
"\t")
673 is_line_compliant =
False
674 line_fixed = line.expandtabs(TAB_SIZE)
677 f
"{filename}:{line_number + 1}:{tab_index + 1}: error: Tab detected",
679 f
' {"":{tab_index}}^',
682 return (is_line_compliant, line_fixed, verbose_infos)
688 if __name__ ==
"__main__":
689 parser = argparse.ArgumentParser(
690 description=
"Check and apply the ns-3 coding style recursively to all files in the given PATHs. "
691 "The script checks the formatting of the file with clang-format. "
692 'Additionally, it checks #include headers from the same module with the "ns3/" prefix, '
693 "the presence of trailing whitespace and tabs. "
694 'Formatting, local #include "ns3/" prefixes and tabs checks respect clang-format guards. '
695 'When used in "check mode" (default), the script checks if all files are well '
696 "formatted and do not have trailing whitespace nor tabs. "
697 "If it detects non-formatted files, they will be printed and this process exits with a "
698 'non-zero code. When used in "fix mode", this script automatically fixes the files.'
706 help=
"List of paths to the files to check",
710 "--no-include-prefixes",
712 help=
'Do not check / fix #include headers from the same module with the "ns3/" prefix',
718 help=
"Do not check / fix code formatting",
724 help=
"Do not check / fix trailing whitespace",
730 help=
"Do not check / fix tabs",
736 help=
"Fix coding style issues detected in the files",
743 help=
"Show the lines that are not well-formatted",
750 default=
max(1, os.cpu_count() - 1),
751 help=
"Number of parallel jobs",
754 args = parser.parse_args()
759 enable_check_include_prefixes=(
not args.no_include_prefixes),
760 enable_check_formatting=(
not args.no_formatting),
761 enable_check_whitespace=(
not args.no_whitespace),
762 enable_check_tabs=(
not args.no_tabs),
764 verbose=args.verbose,
768 except Exception
as e:
772 if all_checks_successful: