ChimeraTK-DeviceAccess  03.18.00
fix-linter-for-all.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: LGPL-3.0-or-later
3 # SPDX-FileCopyRightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
4 
5 import argparse
6 import json
7 import os
8 import re
9 import shutil
10 import subprocess
11 import sys
12 import tempfile
13 
14 
15 def make_absolute(file: str, path: str):
16  if os.path.isabs(file):
17  return file
18  return os.path.normpath(os.path.join(path, file))
19 
20 
21 def main():
22  parser = argparse.ArgumentParser(description="Runs clang-tidy over a compilation database and applies the fixes")
23  parser.add_argument("compile_db", metavar='PATH')
24  parser.add_argument("--tidy", help="Path to clang-tidy", default="clang-tidy-14")
25  parser.add_argument("--apply-tool", help='Path to clang-apply-replacements', default="clang-apply-replacements-14")
26  parser.add_argument("--git", help="Path to git", default="git")
27  parser.add_argument("--source", help="Path to top-level source folder", default=".")
28  parser.add_argument("--exclude", help="Regex of files to exclude", default=None)
29 
30  args = parser.parse_args()
31 
32  apply_version = subprocess.run([args.apply_tool, "--version"], capture_output=True,
33  encoding="latin1").stdout.strip()
34 
35  version_regex = re.compile(r"ersion\s*(\d+)\.(\d+)\.(\d+)")
36  match = version_regex.search(apply_version)
37  has_ignore_insert_conflict = int(match.group(1)) > 14
38 
39  files = None
40 
41  try:
42  database = json.load(open(args.compile_db))
43  files = set([make_absolute(entry['file'], entry['directory']) for entry in database])
44  except FileNotFoundError:
45  print(f"Failed to open {args.compile_db}: Not found")
46  except json.decoder.JSONDecodeError:
47  print(f"Failed to open {args.compile_db}: Not valid json")
48 
49 
50  if not files:
51  sys.exit(1)
52 
53  tmpdir = tempfile.mkdtemp()
54  source_folder = os.path.abspath(args.source)
55 
56  exclude_re = re.compile(args.exclude) if args.exclude else None
57 
58  for file in files:
59  if exclude_re and exclude_re.search(file):
60  continue
61  tidy = [args.tidy, "-header-filter=.*", "-export-fixes"]
62  (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
63  os.close(handle)
64  tidy.append(name)
65  tidy.append("-p=" + os.path.dirname(os.path.normpath(args.compile_db)))
66  tidy.append(file)
67  try:
68  print(f"Linting {file}...")
69  subprocess.run(tidy, check=True)
70  except subprocess.CalledProcessError:
71  print("Linting failed. Usually that means the previously applied fix has introduced a compiler error. Aborting.")
72  print("Most likely it converted a conversion operator to explicit")
73  sys.exit(1)
74 
75  fixer = [args.apply_tool, "--format", "--style=file", "--remove-change-desc-files"]
76  if has_ignore_insert_conflict:
77  fixer.append('--ignore-insert-conflict')
78  fixer.append(tmpdir)
79  if subprocess.call(fixer) == 0:
80  git_update = [args.git, 'add', '-u']
81  subprocess.call(git_update)
82  git_commit = [args.git, 'commit', '-m', f'clang-tidy: {os.path.relpath(file, start=source_folder)}']
83  subprocess.call(git_commit)
84  else:
85  print("Error applying fixes, not committing")
86  shutil.rmtree(tmpdir)
87 
88 
89 if __name__ == "__main__":
90  main()
fix-linter-for-all.main
def main()
Definition: fix-linter-for-all.py:21
fix-linter-for-all.make_absolute
def make_absolute(str file, str path)
Definition: fix-linter-for-all.py:15