10Check and apply the ns-3 coding style recursively to all files in the PATH arguments.
12The coding style is defined with the clang-format tool, whose definitions are in
13the ".clang-format" file. This script performs the following checks / fixes:
14- Check / apply clang-format. Respects clang-format guards.
15- Check / fix local #include headers with "ns3/" prefix. Respects clang-format guards.
16- Check / trim trailing whitespace. Always checked.
17- Check / replace tabs with spaces. Respects clang-format guards.
18- Check / fix SPDX licenses rather than GPL text. Respects clang-format guards.
20This script can be applied to all text files in a given path or to individual files.
22NOTE: The formatting check requires clang-format (version >= 14) to be found on the path.
23The remaining checks do not depend on clang-format and can be executed by disabling clang-format
24checking with the "--no-formatting" option.
28import concurrent.futures
35from typing
import Callable, Dict, List, Tuple
40CLANG_FORMAT_VERSIONS = [
54 "// clang-format off",
55 "# cmake-format: off",
59DIRECTORIES_TO_SKIP = [
86FILES_TO_CHECK: Dict[str, List[str]] = {c: []
for c
in CHECKS}
88FILES_TO_CHECK[
"tabs"] = [
93 "codespell-ignored-lines",
94 "codespell-ignored-words",
98FILES_TO_CHECK[
"whitespace"] = FILES_TO_CHECK[
"tabs"] + [
103FILE_EXTENSIONS_TO_CHECK: Dict[str, List[str]] = {c: []
for c
in CHECKS}
105FILE_EXTENSIONS_TO_CHECK[
"formatting"] = [
111FILE_EXTENSIONS_TO_CHECK[
"include_prefixes"] = FILE_EXTENSIONS_TO_CHECK[
"formatting"]
113FILE_EXTENSIONS_TO_CHECK[
"tabs"] = [
132FILE_EXTENSIONS_TO_CHECK[
"whitespace"] = FILE_EXTENSIONS_TO_CHECK[
"tabs"] + [
148FILE_EXTENSIONS_TO_CHECK[
"license"] = [
165 Check whether a directory should be analyzed.
167 @param dirpath Directory path.
168 @return Whether the directory should be analyzed.
171 _, directory = os.path.split(dirpath)
173 return directory
not in DIRECTORIES_TO_SKIP
178 files_to_check: List[str],
179 file_extensions_to_check: List[str],
182 Check whether a file should be analyzed.
184 @param path Path to the file.
185 @param files_to_check List of files that shall be checked.
186 @param file_extensions_to_check List of file extensions that shall be checked.
187 @return Whether the file should be analyzed.
190 filename = os.path.split(path)[1]
192 if filename
in FILES_TO_SKIP:
195 extension = os.path.splitext(filename)[1]
197 return filename
in files_to_check
or extension
in file_extensions_to_check
202) -> Dict[str, List[str]]:
204 Find all files to be checked in a given list of paths.
206 @param paths List of paths to the files to check.
207 @return Dictionary of checks and corresponding list of files to check.
209 "formatting": list_of_files_to_check_formatting,
215 files_found: List[str] = []
218 abs_path = os.path.abspath(os.path.expanduser(path))
220 if os.path.isfile(abs_path):
221 files_found.append(path)
223 elif os.path.isdir(abs_path):
224 for dirpath, dirnames, filenames
in os.walk(path, topdown=
True):
230 files_found.extend([os.path.join(dirpath, f)
for f
in filenames])
233 raise ValueError(f
"{path} is not a valid file nor a directory")
238 files_to_check: Dict[str, List[str]] = {c: []
for c
in CHECKS}
240 for f
in files_found:
243 files_to_check[check].append(f)
245 return files_to_check
250 Find the path to one of the supported versions of clang-format.
251 If no supported version of clang-format is found, raise an exception.
253 @return Path to clang-format.
257 for version
in CLANG_FORMAT_VERSIONS:
258 clang_format_path = shutil.which(f
"clang-format-{version}")
260 if clang_format_path:
261 return clang_format_path
264 clang_format_path = shutil.which(
"clang-format")
266 if clang_format_path:
267 process = subprocess.run(
268 [clang_format_path,
"--version"],
274 clang_format_version = process.stdout.strip()
275 version_regex = re.findall(
r"\b(\d+)(\.\d+){0,2}\b", clang_format_version)
278 major_version = int(version_regex[0][0])
280 if major_version
in CLANG_FORMAT_VERSIONS:
281 return clang_format_path
285 f
"Could not find any supported version of clang-format installed on this system. "
286 f
"List of supported versions: {CLANG_FORMAT_VERSIONS}. "
287 + (f
"Found clang-format {major_version}." if version_regex
else "")
296 checks_enabled: Dict[str, bool],
302 Check / fix the coding style of a list of files.
304 @param paths List of paths to the files to check.
305 @param checks_enabled Dictionary of checks indicating whether to enable each of them.
306 @param fix Whether to fix (True) or just check (False) the file.
307 @param verbose Show the lines that are not compliant with the style.
308 @param n_jobs Number of parallel jobs.
309 @return Whether all files are compliant with all enabled style checks.
313 checks_successful = {c:
True for c
in CHECKS}
316 "include_prefixes":
'#include headers from the same module with the "ns3/" prefix',
317 "whitespace":
"trailing whitespace",
319 "license":
"GPL license text instead of SPDX license",
320 "formatting":
"bad code formatting",
323 check_style_file_functions_kwargs = {
324 "include_prefixes": {
325 "function": check_manually_file,
327 "respect_clang_format_guards":
True,
328 "check_style_line_function": check_include_prefixes_line,
332 "function": check_manually_file,
334 "respect_clang_format_guards":
False,
335 "check_style_line_function": check_whitespace_line,
339 "function": check_manually_file,
341 "respect_clang_format_guards":
True,
342 "check_style_line_function": check_tabs_line,
346 "function": check_manually_file,
348 "respect_clang_format_guards":
True,
349 "check_style_line_function": check_licenses_line,
353 "function": check_formatting_file,
358 if checks_enabled[
"formatting"]:
359 check_style_file_functions_kwargs[
"formatting"][
"kwargs"] = {
363 n_checks_enabled = sum(checks_enabled.values())
367 if checks_enabled[check]:
369 style_check_strs[check],
370 check_style_file_functions_kwargs[check][
"function"],
371 files_to_check[check],
375 **check_style_file_functions_kwargs[check][
"kwargs"],
380 if n_check < n_checks_enabled:
383 return all(checks_successful.values())
387 style_check_str: str,
388 check_style_file_function: Callable[..., Tuple[str, bool, List[str]]],
389 filenames: List[str],
396 Check / fix style of a list of files.
398 @param style_check_str Description of the check to be performed.
399 @param check_style_file_function Function used to check the file.
400 @param filename Name of the file to be checked.
401 @param fix Whether to fix (True) or just check (False) the file (True).
402 @param verbose Show the lines that are not compliant with the style.
403 @param n_jobs Number of parallel jobs.
404 @param kwargs Additional keyword arguments to the check_style_file_function.
405 @return Whether all files are compliant with the style.
409 non_compliant_files: List[str] = []
410 files_verbose_infos: Dict[str, List[str]] = {}
412 with concurrent.futures.ProcessPoolExecutor(n_jobs)
as executor:
413 non_compliant_files_results = executor.map(
414 check_style_file_function,
416 itertools.repeat(fix),
417 itertools.repeat(verbose),
418 *[arg
if isinstance(arg, list)
else itertools.repeat(arg)
for arg
in kwargs.values()],
421 for filename, is_file_compliant, verbose_infos
in non_compliant_files_results:
422 if not is_file_compliant:
423 non_compliant_files.append(filename)
426 files_verbose_infos[filename] = verbose_infos
429 if not non_compliant_files:
430 print(f
"- No files detected with {style_check_str}")
434 n_non_compliant_files = len(non_compliant_files)
437 print(f
"- Fixed {style_check_str} in the files ({n_non_compliant_files}):")
439 print(f
"- Detected {style_check_str} in the files ({n_non_compliant_files}):")
441 for f
in non_compliant_files:
443 print(*[f
" {l}" for l
in files_verbose_infos[f]], sep=
"\n")
458 clang_format_path: str,
459) -> Tuple[str, bool, List[str]]:
461 Check / fix the coding style of a file with clang-format.
463 @param filename Name of the file to be checked.
464 @param fix Whether to fix (True) or just check (False) the style of the file.
465 @param verbose Show the lines that are not compliant with the style.
466 @param clang_format_path Path to clang-format.
467 @return Tuple [Filename,
468 Whether the file is compliant with the style (before the check),
469 Verbose information].
472 verbose_infos: List[str] = []
475 process = subprocess.run(
483 f
"--ferror-limit={0 if verbose else 1}",
490 is_file_compliant = process.returncode == 0
493 verbose_infos = process.stderr.splitlines()
496 if fix
and not is_file_compliant:
497 process = subprocess.run(
505 stdout=subprocess.DEVNULL,
506 stderr=subprocess.DEVNULL,
509 return (filename, is_file_compliant, verbose_infos)
516 respect_clang_format_guards: bool,
517 check_style_line_function: Callable[[str, str, int], Tuple[bool, str, List[str]]],
518) -> Tuple[str, bool, List[str]]:
520 Check / fix a file manually using a function to check / fix each line.
522 @param filename Name of the file to be checked.
523 @param fix Whether to fix (True) or just check (False) the style of the file.
524 @param verbose Show the lines that are not compliant with the style.
525 @param respect_clang_format_guards Whether to respect clang-format guards.
526 @param check_style_line_function Function used to check each line.
527 @return Tuple [Filename,
528 Whether the file is compliant with the style (before the check),
529 Verbose information].
532 is_file_compliant =
True
533 verbose_infos: List[str] = []
534 clang_format_enabled =
True
536 with open(filename,
"r", encoding=
"utf-8")
as f:
537 file_lines = f.readlines()
539 for i, line
in enumerate(file_lines):
541 if respect_clang_format_guards:
542 line_stripped = line.strip()
544 if line_stripped
in FORMAT_GUARD_ON:
545 clang_format_enabled =
True
546 elif line_stripped
in FORMAT_GUARD_OFF:
547 clang_format_enabled =
False
549 if not clang_format_enabled
and line_stripped
not in (
550 FORMAT_GUARD_ON + FORMAT_GUARD_OFF
555 (is_line_compliant, line_fixed, line_verbose_infos) = check_style_line_function(
559 if not is_line_compliant:
560 is_file_compliant =
False
561 file_lines[i] = line_fixed
562 verbose_infos.extend(line_verbose_infos)
565 if not fix
and not verbose:
569 if fix
and not is_file_compliant:
570 with open(filename,
"w", encoding=
"utf-8")
as f:
571 f.writelines(file_lines)
573 return (filename, is_file_compliant, verbose_infos)
580) -> Tuple[bool, str, List[str]]:
582 Check / fix #include headers from the same module with the "ns3/" prefix in a line.
584 @param line The line to check.
585 @param filename Name of the file to be checked.
586 @param line_number The number of the line checked.
587 @return Tuple [Whether the line is compliant with the style (before the check),
589 Verbose information].
592 is_line_compliant =
True
594 verbose_infos: List[str] = []
597 line_stripped = line.strip()
598 header_file = re.findall(
r'^#include ["<]ns3/(.*\.h)[">]', line_stripped)
602 header_file = header_file[0]
603 parent_path = os.path.split(filename)[0]
605 if os.path.exists(os.path.join(parent_path, header_file)):
606 is_line_compliant =
False
608 line_stripped.replace(f
"ns3/{header_file}", header_file)
614 header_index = len(
'#include "')
616 verbose_infos.extend(
618 f
'{filename}:{line_number + 1}:{header_index + 1}: error: #include headers from the same module with the "ns3/" prefix detected',
620 f
' {"":{header_index}}^',
624 return (is_line_compliant, line_fixed, verbose_infos)
631) -> Tuple[bool, str, List[str]]:
633 Check / fix whitespace in a line.
635 @param line The line to check.
636 @param filename Name of the file to be checked.
637 @param line_number The number of the line checked.
638 @return Tuple [Whether the line is compliant with the style (before the check),
640 Verbose information].
643 is_line_compliant =
True
644 line_fixed = line.rstrip() +
"\n"
645 verbose_infos: List[str] = []
647 if line_fixed != line:
648 is_line_compliant =
False
649 line_fixed_stripped_expanded = line_fixed.rstrip().expandtabs(TAB_SIZE)
652 f
"{filename}:{line_number + 1}:{len(line_fixed_stripped_expanded) + 1}: error: Trailing whitespace detected",
653 f
" {line_fixed_stripped_expanded}",
654 f
' {"":{len(line_fixed_stripped_expanded)}}^',
657 return (is_line_compliant, line_fixed, verbose_infos)
664) -> Tuple[bool, str, List[str]]:
666 Check / fix tabs in a line.
668 @param line The line to check.
669 @param filename Name of the file to be checked.
670 @param line_number The number of the line checked.
671 @return Tuple [Whether the line is compliant with the style (before the check),
673 Verbose information].
676 is_line_compliant =
True
678 verbose_infos: List[str] = []
680 tab_index = line.find(
"\t")
683 is_line_compliant =
False
684 line_fixed = line.expandtabs(TAB_SIZE)
687 f
"{filename}:{line_number + 1}:{tab_index + 1}: error: Tab detected",
689 f
' {"":{tab_index}}^',
692 return (is_line_compliant, line_fixed, verbose_infos)
699) -> Tuple[bool, str, List[str]]:
701 Check / fix SPDX licenses rather than GPL text in a line.
703 @param line The line to check.
704 @param filename Name of the file to be checked.
705 @param line_number The number of the line checked.
706 @return Tuple [Whether the line is compliant with the style (before the check),
708 Verbose information].
712 GPL_LICENSE_LINES = [
713 "This program is free software; you can redistribute it and/or modify",
714 "it under the terms of the GNU General Public License version 2 as",
715 "published by the Free Software Foundation;",
716 "This program is distributed in the hope that it will be useful,",
717 "but WITHOUT ANY WARRANTY; without even the implied warranty of",
718 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
719 "GNU General Public License for more details.",
720 "You should have received a copy of the GNU General Public License",
721 "along with this program; if not, write to the Free Software",
722 "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA",
726 SPDX_LICENSE =
"SPDX-License-Identifier: GPL-2.0-only"
728 is_line_compliant =
True
730 verbose_infos: List[str] = []
733 line_stripped = line.strip()
734 line_stripped_no_leading_comments = line_stripped.strip(
"*#/").strip()
736 if line_stripped_no_leading_comments
in GPL_LICENSE_LINES:
737 is_line_compliant =
False
743 if line_stripped_no_leading_comments == GPL_LICENSE_LINES[0]:
744 line_fixed = line.replace(line_stripped_no_leading_comments, SPDX_LICENSE)
748 verbose_infos.extend(
750 f
"{filename}:{line_number + 1}:{col_index}: error: GPL license text detected instead of SPDX license",
752 f
' {"":{col_index}}^',
756 return (is_line_compliant, line_fixed, verbose_infos)
762if __name__ ==
"__main__":
763 parser = argparse.ArgumentParser(
764 description=
"Check and apply the ns-3 coding style recursively to all files in the given PATHs. "
765 "The script checks the formatting of the files using clang-format and"
766 " other coding style rules manually (see script arguments). "
767 "All checks respect clang-format guards, except trailing whitespace, which is always checked. "
768 'When used in "check mode" (default), the script runs all checks in all files. '
769 "If it detects non-formatted files, they will be printed and this process exits with a non-zero code. "
770 'When used in "fix mode", this script automatically fixes the files and exits with 0 code.'
778 help=
"List of paths to the files to check",
782 "--no-include-prefixes",
784 help=
'Do not check / fix #include headers from the same module with the "ns3/" prefix (respects clang-format guards)',
790 help=
"Do not check / fix trailing whitespace",
796 help=
"Do not check / fix tabs (respects clang-format guards)",
802 help=
"Do not check / fix SPDX licenses rather than GPL text (respects clang-format guards)",
808 help=
"Do not check / fix code formatting (respects clang-format guards)",
814 help=
"Fix coding style issues detected in the files",
821 help=
"Show the lines that are not well-formatted",
828 default=max(1, os.cpu_count() - 1),
829 help=
"Number of parallel jobs",
832 args = parser.parse_args()
838 "include_prefixes":
not args.no_include_prefixes,
839 "whitespace":
not args.no_whitespace,
840 "tabs":
not args.no_tabs,
841 "license":
not args.no_licenses,
842 "formatting":
not args.no_formatting,
845 verbose=args.verbose,
849 except Exception
as e:
853 if not all_checks_successful:
857 "Notes to fix the above formatting issues:",
858 ' - To fix the formatting of specific files, run this script with the flag "--fix":',
859 " $ ./utils/check-style-clang-format.py --fix path [path ...]",
860 " - To fix the formatting of all files modified by this branch, run this script in the following way:",
861 " $ git diff --name-only master | xargs ./utils/check-style-clang-format.py --fix",