qmk-firmware/lib/python/qmk/cli/cformat.py
Zach White db1eacdaac
Align our subprocess usage with current best practices. (#12940)
* Align our subprocess usage with current best practices.

* remove unused import

* Apply suggestions from code review

Co-authored-by: Ryan <fauxpark@gmail.com>

* fix the cpp invocation for older python

* allow for unprompted installation

* make sure qmk new-keyboard works on windows

Co-authored-by: Ryan <fauxpark@gmail.com>
2021-05-19 15:24:46 -07:00

138 lines
4.8 KiB
Python

"""Format C code according to QMK's style.
"""
from os import path
from shutil import which
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
from argcomplete.completers import FilesCompleter
from milc import cli
from qmk.path import normpath
from qmk.c_parse import c_source_files
c_file_suffixes = ('c', 'h', 'cpp')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
def find_clang_format():
"""Returns the path to clang-format.
"""
for clang_version in range(20, 6, -1):
binary = f'clang-format-{clang_version}'
if which(binary):
return binary
return 'clang-format'
def find_diffs(files):
"""Run clang-format and diff it against a file.
"""
found_diffs = False
for file in files:
cli.log.debug('Checking for changes in %s', file)
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
if diff.returncode != 0:
print(diff.stdout)
found_diffs = True
return found_diffs
def cformat_run(files):
"""Spawn clang-format subprocess with proper arguments
"""
# Determine which version of clang-format to use
clang_format = [find_clang_format(), '-i']
try:
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
cli.log.info('Successfully formatted the C code.')
return True
except CalledProcessError as e:
cli.log.error('Error formatting C code!')
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
cli.log.debug('STDOUT:')
cli.log.debug(e.stdout)
cli.log.debug('STDERR:')
cli.log.debug(e.stderr)
return False
def filter_files(files, core_only=False):
"""Yield only files to be formatted and skip the rest
"""
if core_only:
# Filter non-core files
for index, file in enumerate(files):
# The following statement checks each file to see if the file path is
# - in the core directories
# - not in the ignored directories
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
files[index] = None
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
for file in files:
if file and file.name.split('.')[-1] in c_file_suffixes:
yield file
else:
cli.log.debug('Skipping file %s', file)
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def cformat(cli):
"""Format C code according to QMK's style.
"""
# Find the list of files to format
if cli.args.files:
files = list(filter_files(cli.args.files, cli.args.core_only))
if not files:
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
exit(0)
if cli.args.all_files:
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
elif cli.args.all_files:
all_files = c_source_files(core_dirs)
files = list(filter_files(all_files, True))
else:
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
if git_diff.returncode != 0:
cli.log.error("Error running %s", git_diff_cmd)
print(git_diff.stderr)
return git_diff.returncode
files = []
for file in git_diff.stdout.strip().split('\n'):
if not any([file.startswith(ignore) for ignore in ignored]):
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
files.append(file)
# Sanity check
if not files:
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
return False
# Run clang-format on the files we've found
if cli.args.dry_run:
return not find_diffs(files)
else:
return cformat_run(files)