at master 5.0 kB view raw
1"""Format C code according to QMK's style. 2""" 3from shutil import which 4from subprocess import CalledProcessError, DEVNULL, Popen, PIPE 5 6from argcomplete.completers import FilesCompleter 7from milc import cli 8 9from qmk.path import normpath 10from qmk.c_parse import c_source_files 11 12c_file_suffixes = ('c', 'h', 'cpp', 'hpp') 13core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms', 'modules') 14ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards') 15 16 17def is_relative_to(file, other): 18 """Provide similar behavior to PurePath.is_relative_to in Python > 3.9 19 """ 20 return str(normpath(file).resolve()).startswith(str(normpath(other).resolve())) 21 22 23def find_clang_format(): 24 """Returns the path to clang-format. 25 """ 26 for clang_version in range(20, 6, -1): 27 binary = f'clang-format-{clang_version}' 28 29 if which(binary): 30 return binary 31 32 return 'clang-format' 33 34 35def find_diffs(files): 36 """Run clang-format and diff it against a file. 37 """ 38 found_diffs = False 39 40 for file in files: 41 cli.log.debug('Checking for changes in %s', file) 42 clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True) 43 diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True) 44 45 if diff.returncode != 0: 46 print(diff.stdout) 47 found_diffs = True 48 49 return found_diffs 50 51 52def cformat_run(files): 53 """Spawn clang-format subprocess with proper arguments 54 """ 55 # Determine which version of clang-format to use 56 clang_format = [find_clang_format(), '-i'] 57 58 try: 59 cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL) 60 cli.log.info('Successfully formatted the C code.') 61 return True 62 63 except CalledProcessError as e: 64 cli.log.error('Error formatting C code!') 65 cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode) 66 cli.log.debug('STDOUT:') 67 cli.log.debug(e.stdout) 68 cli.log.debug('STDERR:') 69 cli.log.debug(e.stderr) 70 return False 71 72 73def filter_files(files, core_only=False): 74 """Yield only files to be formatted and skip the rest 75 """ 76 files = list(map(normpath, filter(None, files))) 77 78 for file in files: 79 if core_only: 80 # The following statement checks each file to see if the file path is 81 # - in the core directories 82 # - not in the ignored directories 83 if not any(is_relative_to(file, i) for i in core_dirs) or any(is_relative_to(file, i) for i in ignored): 84 cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file) 85 continue 86 87 if file.suffix[1:] in c_file_suffixes: 88 yield file 89 else: 90 cli.log.debug('Skipping file %s', file) 91 92 93@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") 94@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') 95@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') 96@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.') 97@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.') 98@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) 99def format_c(cli): 100 """Format C code according to QMK's style. 101 """ 102 # Find the list of files to format 103 if cli.args.files: 104 files = list(filter_files(cli.args.files, cli.args.core_only)) 105 106 if not files: 107 cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files))) 108 exit(0) 109 110 if cli.args.all_files: 111 cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files))) 112 113 elif cli.args.all_files: 114 all_files = c_source_files(core_dirs) 115 files = list(filter_files(all_files, True)) 116 117 else: 118 git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs] 119 git_diff = cli.run(git_diff_cmd, stdin=DEVNULL) 120 121 if git_diff.returncode != 0: 122 cli.log.error("Error running %s", git_diff_cmd) 123 print(git_diff.stderr) 124 return git_diff.returncode 125 126 changed_files = git_diff.stdout.strip().split('\n') 127 files = list(filter_files(changed_files, True)) 128 129 # Sanity check 130 if not files: 131 cli.log.error('No changed files detected. Use "qmk format-c -a" to format all core files') 132 return False 133 134 # Run clang-format on the files we've found 135 if cli.args.dry_run: 136 return not find_diffs(files) 137 else: 138 return cformat_run(files)