diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 08d23cf5ba9f..785b94045627 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -24,7 +24,7 @@ def _get_chunks(it, size): return iter(lambda: tuple(islice(it, size)), ()) -def _preprocess_c_file(file): +def preprocess_c_file(file): """Load file and strip comments """ file_contents = file.read_text(encoding='utf-8') @@ -66,7 +66,7 @@ def find_layouts(file): parsed_layouts = {} # Search the file for LAYOUT macros and aliases - file_contents = _preprocess_c_file(file) + file_contents = preprocess_c_file(file) for line in file_contents.split('\n'): if layout_macro_define_regex.match(line.lstrip()) and '(' in line and 'LAYOUT' in line: @@ -248,7 +248,7 @@ def _parse_led_config(file, matrix_cols, matrix_rows): current_row_index = 0 current_row = [] - for _type, value in lex(_preprocess_c_file(file), CLexer()): + for _type, value in lex(preprocess_c_file(file), CLexer()): if not found_g_led_config: # Check for type if value == 'led_config_t': diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index ba0c3f274cf7..3154cee5ae60 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -10,7 +10,7 @@ from qmk.keymap import locate_keymap, list_keymaps from qmk.path import keyboard from qmk.git import git_get_ignored_files -from qmk.c_parse import c_source_files +from qmk.c_parse import c_source_files, preprocess_c_file CHIBIOS_CONF_CHECKS = ['chconf.h', 'halconf.h', 'mcuconf.h', 'board.h'] INVALID_KB_FEATURES = set(['encoder_map', 'dip_switch_map', 'combo', 'tap_dance', 'via']) @@ -32,12 +32,42 @@ def _list_defaultish_keymaps(kb): return keymaps +def _get_build_files(kb, km=None): + """Return potential keyboard/keymap build files + """ + search_path = locate_keymap(kb, km).parent if km else keyboard(kb) + + build_files = [] + + if not km: + current_path = Path() + for path_part in search_path.parts: + current_path = current_path / path_part + build_files.extend(current_path.glob('*rules.mk')) + + for file in search_path.glob("**/*rules.mk"): + # Ignore keymaps when only globing keyboard files + if not km and 'keymaps' in file.parts: + continue + build_files.append(file) + + return set(build_files) + + def _get_code_files(kb, km=None): """Return potential keyboard/keymap code files """ search_path = locate_keymap(kb, km).parent if km else keyboard(kb) code_files = [] + + if not km: + current_path = Path() + for path_part in search_path.parts: + current_path = current_path / path_part + code_files.extend(current_path.glob('*.h')) + code_files.extend(current_path.glob('*.c')) + for file in c_source_files([search_path]): # Ignore keymaps when only globing keyboard files if not km and 'keymaps' in file.parts: @@ -47,6 +77,24 @@ def _get_code_files(kb, km=None): return code_files +def _is_empty_rules(file): + """Check if file contains any useful content + """ + for line in file.read_text(encoding='utf-8').split("\n"): + if len(line) > 0 and not line.isspace() and not line.startswith('#'): + return False + return True + + +def _is_empty_include(file): + """Check if file contains any useful content + """ + for line in preprocess_c_file(file).split("\n"): + if len(line) > 0 and not line.isspace() and not line.startswith('#pragma once'): + return False + return True + + def _has_license(file): """Check file has a license header """ @@ -90,37 +138,28 @@ def _chibios_conf_includenext_check(target): return None -def _rules_mk_assignment_only(kb): +def _rules_mk_assignment_only(rules_mk): """Check the keyboard-level rules.mk to ensure it only has assignments. """ - keyboard_path = keyboard(kb) - current_path = Path() errors = [] + continuation = None + for i, line in enumerate(rules_mk.open()): + line = line.strip() - for path_part in keyboard_path.parts: - current_path = current_path / path_part - rules_mk = current_path / 'rules.mk' + if '#' in line: + line = line[:line.index('#')] - if rules_mk.exists(): + if continuation: + line = continuation + line continuation = None - for i, line in enumerate(rules_mk.open()): - line = line.strip() - - if '#' in line: - line = line[:line.index('#')] - - if continuation: - line = continuation + line - continuation = None - - if line: - if line[-1] == '\\': - continuation = line[:-1] - continue + if line: + if line[-1] == '\\': + continuation = line[:-1] + continue - if line and '=' not in line: - errors.append(f'Non-assignment code on line +{i} {rules_mk}: {line}') + if line and '=' not in line: + errors.append(f'Non-assignment code on line +{i} {rules_mk}: {line}') return errors @@ -169,13 +208,6 @@ def keyboard_check(kb): if not _handle_invalid_features(kb, kb_info): ok = False - rules_mk_assignment_errors = _rules_mk_assignment_only(kb) - if rules_mk_assignment_errors: - ok = False - cli.log.error('%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb) - for assignment_error in rules_mk_assignment_errors: - cli.log.error(assignment_error) - invalid_files = git_get_ignored_files(f'keyboards/{kb}/') for file in invalid_files: if 'keymap' in file: @@ -183,11 +215,29 @@ def keyboard_check(kb): cli.log.error(f'{kb}: The file "{file}" should not exist!') ok = False + for file in _get_build_files(kb): + if _is_empty_rules(file): + cli.log.error(f'{kb}: The file "{file}" is effectively empty and should be removed!') + ok = False + + if file.suffix in ['rules.mk']: + rules_mk_assignment_errors = _rules_mk_assignment_only(file) + if rules_mk_assignment_errors: + ok = False + cli.log.error('%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb) + for assignment_error in rules_mk_assignment_errors: + cli.log.error(assignment_error) + for file in _get_code_files(kb): if not _has_license(file): cli.log.error(f'{kb}: The file "{file}" does not have a license header!') ok = False + if file.name in ['config.h']: + if _is_empty_include(file): + cli.log.error(f'{kb}: The file "{file}" is effectively empty and should be removed!') + ok = False + if file.name in CHIBIOS_CONF_CHECKS: check_error = _chibios_conf_includenext_check(file) if check_error is not None: