From 2de70e6f2d5c7217ec0a0f318e999a0644d9db0d Mon Sep 17 00:00:00 2001 From: Dasky <32983009+daskygit@users.noreply.github.com> Date: Mon, 30 May 2022 23:04:50 +0100 Subject: Add uf2-split-* make targets. (#17257) --- lib/python/qmk/cli/flash.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index 28e48a4101..3ff4a318df 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -33,6 +33,8 @@ def print_bootloader_help(): cli.echo('\tdfu-split-right') cli.echo('\tdfu-util-split-left') cli.echo('\tdfu-util-split-right') + cli.echo('\tuf2-split-left') + cli.echo('\tuf2-split-right') cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') -- cgit v1.2.3 From 2879573688e347fd448ac32a1621ba3bec97f5c5 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 30 May 2022 23:43:36 +0100 Subject: Fix various lint errors (#17255) * Fix various lint errors * reduce complexity --- lib/python/qmk/c_parse.py | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 359aaccbbc..4b49b8d4e9 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -24,6 +24,14 @@ def _get_chunks(it, size): return iter(lambda: tuple(islice(it, size)), ()) +def _preprocess_c_file(file): + """Load file and strip comments + """ + file_contents = file.read_text(encoding='utf-8') + file_contents = comment_remover(file_contents) + return file_contents.replace('\\\n', '') + + def strip_line_comment(string): """Removes comments from a single line string. """ @@ -58,9 +66,7 @@ def find_layouts(file): parsed_layouts = {} # Search the file for LAYOUT macros and aliases - file_contents = file.read_text(encoding='utf-8') - file_contents = comment_remover(file_contents) - file_contents = file_contents.replace('\\\n', '') + 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: @@ -205,13 +211,23 @@ def _coerce_led_token(_type, value): return value_map[value] +def _validate_led_config(matrix, matrix_rows, matrix_indexes, position, position_raw, flags): + # TODO: Improve crude parsing/validation + if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2): + raise ValueError("Unable to parse g_led_config matrix data") + if len(position) != len(flags): + raise ValueError("Unable to parse g_led_config position data") + if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)): + raise ValueError("OOB within g_led_config matrix data") + if not all(isinstance(n, int) for n in matrix_indexes): + raise ValueError("matrix indexes are not all ints") + if (len(position_raw) % 2) != 0: + raise ValueError("Malformed g_led_config position data") + + def _parse_led_config(file, matrix_cols, matrix_rows): """Return any 'raw' led/rgb matrix config """ - file_contents = file.read_text(encoding='utf-8') - file_contents = comment_remover(file_contents) - file_contents = file_contents.replace('\\\n', '') - matrix_raw = [] position_raw = [] flags = [] @@ -219,7 +235,7 @@ def _parse_led_config(file, matrix_cols, matrix_rows): found_led_config = False bracket_count = 0 section = 0 - for _type, value in lex(file_contents, CLexer()): + for _type, value in lex(_preprocess_c_file(file), CLexer()): # Assume g_led_config..stuff..; if value == 'g_led_config': found_led_config = True @@ -248,17 +264,12 @@ def _parse_led_config(file, matrix_cols, matrix_rows): position = list(_get_chunks(position_raw, 2)) matrix_indexes = list(filter(lambda x: x is not None, matrix_raw)) - # If we have not found anything - bail + # If we have not found anything - bail with no error if not section: return None - # TODO: Improve crude parsing/validation - if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2): - raise ValueError("Unable to parse g_led_config matrix data") - if len(position) != len(flags): - raise ValueError("Unable to parse g_led_config position data") - if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)): - raise ValueError("OOB within g_led_config matrix data") + # Throw any validation errors + _validate_led_config(matrix, matrix_rows, matrix_indexes, position, position_raw, flags) return (matrix, position, flags) -- cgit v1.2.3 From a599550adbd6b1291509d7cdc7ea61c92550a60c Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 9 Jun 2022 21:02:16 +0100 Subject: Add support for linting deprecated and removed functionality (#17063) * Add support for more lint warnings/errors * Develop currently needs extra deps installed * Lint a few more scenarios * fix tests --- lib/python/qmk/info.py | 120 +++++++++++++----------------- lib/python/qmk/tests/test_cli_commands.py | 1 - 2 files changed, 53 insertions(+), 68 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 0763433b3d..6ff9cba45b 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -440,6 +440,47 @@ def _extract_device_version(info_data): info_data['usb']['device_version'] = f'{major}.{minor}.{revision}' +def _config_to_json(key_type, config_value): + """Convert config value using spec + """ + if key_type.startswith('array'): + if '.' in key_type: + key_type, array_type = key_type.split('.', 1) + else: + array_type = None + + config_value = config_value.replace('{', '').replace('}', '').strip() + + if array_type == 'int': + return list(map(int, config_value.split(','))) + else: + return config_value.split(',') + + elif key_type == 'bool': + return config_value in true_values + + elif key_type == 'hex': + return '0x' + config_value[2:].upper() + + elif key_type == 'list': + return config_value.split() + + elif key_type == 'int': + return int(config_value) + + elif key_type == 'str': + return config_value.strip('"') + + elif key_type == 'bcd_version': + major = int(config_value[2:4]) + minor = int(config_value[4]) + revision = int(config_value[5]) + + return f'{major}.{minor}.{revision}' + + return config_value + + def _extract_config_h(info_data, config_c): """Pull some keyboard information from existing config.h files """ @@ -452,47 +493,16 @@ def _extract_config_h(info_data, config_c): key_type = info_dict.get('value_type', 'raw') try: + if config_key in config_c and info_dict.get('invalid', False): + _log_error(info_data, '%s in config.h is no longer a valid option' % config_key) + elif config_key in config_c and info_dict.get('deprecated', False): + _log_warning(info_data, '%s in config.h is deprecated and will be removed at a later date' % config_key) + if config_key in config_c and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key)) - if key_type.startswith('array'): - if '.' in key_type: - key_type, array_type = key_type.split('.', 1) - else: - array_type = None - - config_value = config_c[config_key].replace('{', '').replace('}', '').strip() - - if array_type == 'int': - dotty_info[info_key] = list(map(int, config_value.split(','))) - else: - dotty_info[info_key] = config_value.split(',') - - elif key_type == 'bool': - dotty_info[info_key] = config_c[config_key] in true_values - - elif key_type == 'hex': - dotty_info[info_key] = '0x' + config_c[config_key][2:].upper() - - elif key_type == 'list': - dotty_info[info_key] = config_c[config_key].split() - - elif key_type == 'int': - dotty_info[info_key] = int(config_c[config_key]) - - elif key_type == 'str': - dotty_info[info_key] = config_c[config_key].strip('"') - - elif key_type == 'bcd_version': - major = int(config_c[config_key][2:4]) - minor = int(config_c[config_key][4]) - revision = int(config_c[config_key][5]) - - dotty_info[info_key] = f'{major}.{minor}.{revision}' - - else: - dotty_info[info_key] = config_c[config_key] + dotty_info[info_key] = _config_to_json(key_type, config_c[config_key]) except Exception as e: _log_warning(info_data, f'{config_key}->{info_key}: {e}') @@ -547,40 +557,16 @@ def _extract_rules_mk(info_data, rules): key_type = info_dict.get('value_type', 'raw') try: + if rules_key in rules and info_dict.get('invalid', False): + _log_error(info_data, '%s in rules.mk is no longer a valid option' % rules_key) + elif rules_key in rules and info_dict.get('deprecated', False): + _log_warning(info_data, '%s in rules.mk is deprecated and will be removed at a later date' % rules_key) + if rules_key in rules and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) - if key_type.startswith('array'): - if '.' in key_type: - key_type, array_type = key_type.split('.', 1) - else: - array_type = None - - rules_value = rules[rules_key].replace('{', '').replace('}', '').strip() - - if array_type == 'int': - dotty_info[info_key] = list(map(int, rules_value.split(','))) - else: - dotty_info[info_key] = rules_value.split(',') - - elif key_type == 'list': - dotty_info[info_key] = rules[rules_key].split() - - elif key_type == 'bool': - dotty_info[info_key] = rules[rules_key] in true_values - - elif key_type == 'hex': - dotty_info[info_key] = '0x' + rules[rules_key][2:].upper() - - elif key_type == 'int': - dotty_info[info_key] = int(rules[rules_key]) - - elif key_type == 'str': - dotty_info[info_key] = rules[rules_key].strip('"') - - else: - dotty_info[info_key] = rules[rules_key] + dotty_info[info_key] = _config_to_json(key_type, rules[rules_key]) except Exception as e: _log_warning(info_data, f'{rules_key}->{info_key}: {e}') diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index d40d4bf573..2463543ef1 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -259,7 +259,6 @@ def test_generate_config_h(): result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic') check_returncode(result) assert '# define DEVICE_VER 0x0001' in result.stdout - assert '# define DESCRIPTION "handwired/pytest/basic"' in result.stdout assert '# define DIODE_DIRECTION COL2ROW' in result.stdout assert '# define MANUFACTURER none' in result.stdout assert '# define PRODUCT pytest' in result.stdout -- cgit v1.2.3 From 7baf9b3f357abe314fcd02e01c814f9516d66e89 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 10 Jun 2022 11:44:23 +0100 Subject: Promote label with newlines to lint error (#17347) --- lib/python/qmk/info.py | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 6ff9cba45b..dd753f328c 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -26,13 +26,6 @@ def _valid_community_layout(layout): return (Path('layouts/default') / layout).exists() -def _remove_newlines_from_labels(layouts): - for layout_name, layout_json in layouts.items(): - for key in layout_json['layout']: - if '\n' in key['label']: - key['label'] = key['label'].split('\n')[0] - - def info_json(keyboard): """Generate the info.json data for a specific keyboard. """ @@ -111,9 +104,6 @@ def info_json(keyboard): # Check that the reported matrix size is consistent with the actual matrix size _check_matrix(info_data) - # Remove newline characters from layout labels - _remove_newlines_from_labels(layouts) - return info_data -- cgit v1.2.3 From 92665aef33148fef051ad07687028c88f0da64fc Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 10 Jun 2022 11:44:48 +0100 Subject: Promote 'layouts require matrix data' to api error (#17349) --- lib/python/qmk/cli/lint.py | 7 ------- lib/python/qmk/info.py | 7 +++++-- 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index af057b4110..96593ed69b 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -116,13 +116,6 @@ def lint(cli): if not keymap_check(kb, cli.config.lint.keymap): ok = False - # Check if all non-data driven macros exist in - for layout, data in keyboard_info['layouts'].items(): - # Matrix data should be a list with exactly two integers: [0, 1] - if not data['c_macro'] and not all('matrix' in key_data.keys() or len(key_data) == 2 or all(isinstance(n, int) for n in key_data) for key_data in data['layout']): - cli.log.error(f'{kb}: "{layout}" has no "matrix" definition in either "info.json" or ".h"!') - ok = False - # Report status if not ok: failed.append(kb) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index dd753f328c..23761d71b7 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -797,8 +797,11 @@ def merge_info_jsons(keyboard, info_data): for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']): existing_key.update(new_key) else: - layout['c_macro'] = False - info_data['layouts'][layout_name] = layout + if not all('matrix' in key_data.keys() for key_data in layout['layout']): + _log_error(info_data, f'Layout "{layout_name}" has no "matrix" definition in either "info.json" or ".h"!') + else: + layout['c_macro'] = False + info_data['layouts'][layout_name] = layout # Update info_data with the new data if 'layouts' in new_info_data: -- cgit v1.2.3 From af6435d44d6fb1a6343d26a9783d3be5572c7ccc Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 12 Jun 2022 04:10:09 +1000 Subject: `qmk doctor`: show arch for macOS (#17356) --- lib/python/qmk/cli/doctor/macos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/doctor/macos.py b/lib/python/qmk/cli/doctor/macos.py index 00fb272858..5d088c9492 100644 --- a/lib/python/qmk/cli/doctor/macos.py +++ b/lib/python/qmk/cli/doctor/macos.py @@ -8,6 +8,6 @@ from .check import CheckStatus def os_test_macos(): """Run the Mac specific tests. """ - cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0]) + cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel') return CheckStatus.OK -- cgit v1.2.3 From f37de9a212e79e9c4c05c4298ba82ffe527d7132 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 14 Jun 2022 14:54:46 +0100 Subject: Perform stricter lint checks (#17348) --- lib/python/qmk/cli/lint.py | 117 ++++++++++++++++++++++++++++++++++----------- lib/python/qmk/git.py | 9 ++++ 2 files changed, 97 insertions(+), 29 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index 96593ed69b..38b6457c43 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -7,26 +7,43 @@ from milc import cli from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.keyboard import keyboard_completer, list_keyboards -from qmk.keymap import locate_keymap +from qmk.keymap import locate_keymap, list_keymaps from qmk.path import is_keyboard, keyboard +from qmk.git import git_get_ignored_files -def keymap_check(kb, km): - """Perform the keymap level checks. +def _list_defaultish_keymaps(kb): + """Return default like keymaps for a given keyboard """ - ok = True - keymap_path = locate_keymap(kb, km) + defaultish = ['ansi', 'iso', 'via'] - if not keymap_path: + keymaps = set() + for x in list_keymaps(kb): + if x in defaultish or x.startswith('default'): + keymaps.add(x) + + return keymaps + + +def _handle_json_errors(kb, info): + """Convert any json errors into lint errors + """ + ok = True + # Check for errors in the json + if info['parse_errors']: ok = False - cli.log.error("%s: Can't find %s keymap.", kb, km) + cli.log.error(f'{kb}: Errors found when generating info.json.') + if cli.config.lint.strict and info['parse_warnings']: + ok = False + cli.log.error(f'{kb}: Warnings found when generating info.json (Strict mode enabled.)') return ok -def rules_mk_assignment_only(keyboard_path): +def _rules_mk_assignment_only(kb): """Check the keyboard-level rules.mk to ensure it only has assignments. """ + keyboard_path = keyboard(kb) current_path = Path() errors = [] @@ -58,10 +75,58 @@ def rules_mk_assignment_only(keyboard_path): return errors +def keymap_check(kb, km): + """Perform the keymap level checks. + """ + ok = True + keymap_path = locate_keymap(kb, km) + + if not keymap_path: + ok = False + cli.log.error("%s: Can't find %s keymap.", kb, km) + return ok + + # Additional checks + invalid_files = git_get_ignored_files(keymap_path.parent) + for file in invalid_files: + cli.log.error(f'{kb}/{km}: The file "{file}" should not exist!') + ok = False + + return ok + + +def keyboard_check(kb): + """Perform the keyboard level checks. + """ + ok = True + kb_info = info_json(kb) + + if not _handle_json_errors(kb, kb_info): + ok = False + + # Additional checks + 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: + continue + cli.log.error(f'{kb}: The file "{file}" should not exist!') + ok = False + + return ok + + @cli.argument('--strict', action='store_true', help='Treat warnings as errors') @cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Comma separated list of keyboards to check') @cli.argument('-km', '--keymap', help='The keymap to check') @cli.argument('--all-kb', action='store_true', arg_only=True, help='Check all keyboards') +@cli.argument('--all-km', action='store_true', arg_only=True, help='Check all keymaps') @cli.subcommand('Check keyboard and keymap for common mistakes.') @automagic_keyboard @automagic_keymap @@ -73,7 +138,7 @@ def lint(cli): # Determine our keyboard list if cli.args.all_kb: if cli.args.keyboard: - cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes presidence.') + cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes precedence.') keyboard_list = list_keyboards() elif not cli.config.lint.keyboard: @@ -89,31 +154,25 @@ def lint(cli): cli.log.error('No such keyboard: %s', kb) continue - # Gather data about the keyboard. - ok = True - keyboard_path = keyboard(kb) - keyboard_info = info_json(kb) - - # Check for errors in the info.json - if keyboard_info['parse_errors']: - ok = False - cli.log.error('%s: Errors found when generating info.json.', kb) + # Determine keymaps to also check + if cli.args.all_km: + keymaps = list_keymaps(kb) + elif cli.config.lint.keymap: + keymaps = {cli.config.lint.keymap} + else: + keymaps = _list_defaultish_keymaps(kb) + # Ensure that at least a 'default' keymap always exists + keymaps.add('default') - if cli.config.lint.strict and keyboard_info['parse_warnings']: - ok = False - cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb) + ok = True - # Check the rules.mk file(s) - rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path) - if rules_mk_assignment_errors: + # keyboard level checks + if not keyboard_check(kb): 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) # Keymap specific checks - if cli.config.lint.keymap: - if not keymap_check(kb, cli.config.lint.keymap): + for keymap in keymaps: + if not keymap_check(kb, keymap): ok = False # Report status diff --git a/lib/python/qmk/git.py b/lib/python/qmk/git.py index beeb689144..f493628492 100644 --- a/lib/python/qmk/git.py +++ b/lib/python/qmk/git.py @@ -108,3 +108,12 @@ def git_check_deviation(active_branch): cli.run(['git', 'fetch', 'upstream', active_branch]) deviations = cli.run(['git', '--no-pager', 'log', f'upstream/{active_branch}...{active_branch}']) return bool(deviations.returncode) + + +def git_get_ignored_files(check_dir='.'): + """Return a list of files that would be captured by the current .gitingore + """ + invalid = cli.run(['git', 'ls-files', '-c', '-o', '-i', '--exclude-standard', check_dir]) + if invalid.returncode != 0: + return [] + return invalid.stdout.strip().splitlines() -- cgit v1.2.3 From d3dfa83b4069507df5e8e36def33826a269cd720 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 15 Jun 2022 22:43:54 +0100 Subject: Fix clean arg handling (#17392) --- lib/python/qmk/cli/compile.py | 5 +++-- lib/python/qmk/cli/flash.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index acbd778649..95118e6687 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -32,8 +32,9 @@ def compile(cli): If a keyboard and keymap are provided this command will build a firmware based on that. """ if cli.args.clean and not cli.args.filename and not cli.args.dry_run: - command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, 'clean') - cli.run(command, capture_output=False, stdin=DEVNULL) + if cli.config.compile.keyboard and cli.config.compile.keymap: + command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, 'clean') + cli.run(command, capture_output=False, stdin=DEVNULL) # Build the environment vars envs = {} diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index 28e48a4101..216896b974 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -59,8 +59,9 @@ def flash(cli): If bootloader is omitted the make system will use the configured bootloader for that keyboard. """ if cli.args.clean and not cli.args.filename and not cli.args.dry_run: - command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean') - cli.run(command, capture_output=False, stdin=DEVNULL) + if cli.config.flash.keyboard and cli.config.flash.keymap: + command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean') + cli.run(command, capture_output=False, stdin=DEVNULL) # Build the environment vars envs = {} -- cgit v1.2.3 From 0b1bed1d41951fa039227a6737be470628df8466 Mon Sep 17 00:00:00 2001 From: precondition <57645186+precondition@users.noreply.github.com> Date: Thu, 16 Jun 2022 20:20:12 +0200 Subject: Use --exclude-from=.gitignore in place of --exclude-standard (#17399) --- lib/python/qmk/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/git.py b/lib/python/qmk/git.py index f493628492..960184a0a2 100644 --- a/lib/python/qmk/git.py +++ b/lib/python/qmk/git.py @@ -111,9 +111,9 @@ def git_check_deviation(active_branch): def git_get_ignored_files(check_dir='.'): - """Return a list of files that would be captured by the current .gitingore + """Return a list of files that would be captured by the current .gitignore """ - invalid = cli.run(['git', 'ls-files', '-c', '-o', '-i', '--exclude-standard', check_dir]) + invalid = cli.run(['git', 'ls-files', '-c', '-o', '-i', '--exclude-from=.gitignore', check_dir]) if invalid.returncode != 0: return [] return invalid.stdout.strip().splitlines() -- cgit v1.2.3 From 17ec1650fd4fd27b3bf409e3493faf11c8d421e8 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 18 Jun 2022 06:30:46 +0100 Subject: Additional schema fixes (#17414) --- lib/python/qmk/cli/generate/info_json.py | 8 ++++---- lib/python/qmk/json_schema.py | 6 +----- 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 284d1a8510..0dc80f10cc 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py @@ -5,7 +5,7 @@ Compile an info.json for a particular keyboard and pretty-print it. import json from argcomplete.completers import FilesCompleter -from jsonschema import Draft7Validator, RefResolver, validators +from jsonschema import Draft202012Validator, RefResolver, validators from milc import cli from pathlib import Path @@ -18,7 +18,7 @@ from qmk.path import is_keyboard, normpath def pruning_validator(validator_class): - """Extends Draft7Validator to remove properties that aren't specified in the schema. + """Extends Draft202012Validator to remove properties that aren't specified in the schema. """ validate_properties = validator_class.VALIDATORS["properties"] @@ -37,10 +37,10 @@ def strip_info_json(kb_info_json): """Remove the API-only properties from the info.json. """ schema_store = compile_schema_store() - pruning_draft_7_validator = pruning_validator(Draft7Validator) + pruning_draft_validator = pruning_validator(Draft202012Validator) schema = schema_store['qmk.keyboard.v1'] resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) - validator = pruning_draft_7_validator(schema, resolver=resolver).validate + validator = pruning_draft_validator(schema, resolver=resolver).validate return validator(kb_info_json) diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index 682346113e..01175146b5 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py @@ -68,11 +68,7 @@ def create_validator(schema): schema_store = compile_schema_store() resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store) - # TODO: Remove this after the jsonschema>=4 requirement had time to reach users - try: - return jsonschema.Draft202012Validator(schema_store[schema], resolver=resolver).validate - except AttributeError: - return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate + return jsonschema.Draft202012Validator(schema_store[schema], resolver=resolver).validate def validate(data, schema): -- cgit v1.2.3 From 1a400d8644a1f0763c68626863b897cb83c6c939 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 21 Jun 2022 04:15:06 +0100 Subject: Allow encoder config from info.json (#17295) --- lib/python/qmk/cli/generate/config_h.py | 29 +++++++++++++++++ lib/python/qmk/info.py | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 893892c479..9d50368aba 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -134,6 +134,29 @@ def generate_config_items(kb_info_json, config_h_lines): config_h_lines.append(f'#endif // {config_key}') +def generate_encoder_config(encoder_json, config_h_lines, postfix=''): + """Generate the config.h lines for encoders.""" + a_pads = [] + b_pads = [] + resolutions = [] + for encoder in encoder_json.get("rotary", []): + a_pads.append(encoder["pin_a"]) + b_pads.append(encoder["pin_b"]) + resolutions.append(str(encoder.get("resolution", 4))) + + config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}') + config_h_lines.append(f'# define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}') + config_h_lines.append(f'#endif // ENCODERS_PAD_A{postfix}') + + config_h_lines.append(f'#ifndef ENCODERS_PAD_B{postfix}') + config_h_lines.append(f'# define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}') + config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}') + + config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') + config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') + config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') + + def generate_split_config(kb_info_json, config_h_lines): """Generate the config.h lines for split boards.""" if 'primary' in kb_info_json['split']: @@ -173,6 +196,9 @@ def generate_split_config(kb_info_json, config_h_lines): if 'right' in kb_info_json['split'].get('matrix_pins', {}): config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT')) + if 'right' in kb_info_json['split'].get('encoder', {}): + generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_RIGHT') + @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @@ -198,6 +224,9 @@ def generate_config_h(cli): if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) + if 'encoder' in kb_info_json: + generate_encoder_config(kb_info_json['encoder'], config_h_lines) + if 'split' in kb_info_json: generate_split_config(kb_info_json, config_h_lines) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 23761d71b7..d308de9db8 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -218,6 +218,62 @@ def _extract_audio(info_data, config_c): info_data['audio'] = {'pins': audio_pins} +def _extract_encoders_values(config_c, postfix=''): + """Common encoder extraction logic + """ + a_pad = config_c.get(f'ENCODERS_PAD_A{postfix}', '').replace(' ', '')[1:-1] + b_pad = config_c.get(f'ENCODERS_PAD_B{postfix}', '').replace(' ', '')[1:-1] + resolutions = config_c.get(f'ENCODER_RESOLUTIONS{postfix}', '').replace(' ', '')[1:-1] + + default_resolution = config_c.get('ENCODER_RESOLUTION', '4') + + if a_pad and b_pad: + a_pad = list(filter(None, a_pad.split(','))) + b_pad = list(filter(None, b_pad.split(','))) + resolutions = list(filter(None, resolutions.split(','))) + resolutions += [default_resolution] * (len(a_pad) - len(resolutions)) + + encoders = [] + for index in range(len(a_pad)): + encoders.append({'pin_a': a_pad[index], 'pin_b': b_pad[index], "resolution": int(resolutions[index])}) + + return encoders + + +def _extract_encoders(info_data, config_c): + """Populate data about encoder pins + """ + encoders = _extract_encoders_values(config_c) + if encoders: + if 'encoder' not in info_data: + info_data['encoder'] = {} + + if 'rotary' in info_data['encoder']: + _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['encoder']['rotary']) + + info_data['encoder']['rotary'] = encoders + + +def _extract_split_encoders(info_data, config_c): + """Populate data about split encoder pins + """ + encoders = _extract_encoders_values(config_c, '_RIGHT') + if encoders: + if 'split' not in info_data: + info_data['split'] = {} + + if 'encoder' not in info_data['split']: + info_data['split']['encoder'] = {} + + if 'right' not in info_data['split']['encoder']: + info_data['split']['encoder']['right'] = {} + + if 'rotary' in info_data['split']['encoder']['right']: + _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['split']['encoder']['right']['rotary']) + + info_data['split']['encoder']['right']['rotary'] = encoders + + def _extract_secure_unlock(info_data, config_c): """Populate data about the secure unlock sequence """ @@ -506,6 +562,8 @@ def _extract_config_h(info_data, config_c): _extract_split_main(info_data, config_c) _extract_split_transport(info_data, config_c) _extract_split_right_pins(info_data, config_c) + _extract_encoders(info_data, config_c) + _extract_split_encoders(info_data, config_c) _extract_device_version(info_data) return info_data -- cgit v1.2.3 From e13bb58c40c2c4473be3fefe144b5b7144ebbb5d Mon Sep 17 00:00:00 2001 From: precondition <57645186+precondition@users.noreply.github.com> Date: Sat, 25 Jun 2022 21:40:49 +0200 Subject: Only omit paths containing "/keymaps/" (#17468) This allows keyboard names to contain the word "keymaps" --- lib/python/qmk/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py index e69f63aebe..686d4fc403 100644 --- a/lib/python/qmk/keyboard.py +++ b/lib/python/qmk/keyboard.py @@ -103,7 +103,7 @@ def list_keyboards(): """ # We avoid pathlib here because this is performance critical code. kb_wildcard = os.path.join(base_path, "**", "rules.mk") - paths = [path for path in glob(kb_wildcard, recursive=True) if 'keymaps' not in path] + paths = [path for path in glob(kb_wildcard, recursive=True) if os.path.sep + 'keymaps' + os.path.sep not in path] return sorted(set(map(resolve_keyboard, map(_find_name, paths)))) -- cgit v1.2.3 From fa3dd373b4925734d9843ae6014349069ffec353 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sun, 26 Jun 2022 23:58:31 +0100 Subject: Fix error message in generated code (#17484) --- lib/python/qmk/cli/generate/keyboard_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/keyboard_h.py b/lib/python/qmk/cli/generate/keyboard_h.py index 2058865cbf..54ddb4cffd 100755 --- a/lib/python/qmk/cli/generate/keyboard_h.py +++ b/lib/python/qmk/cli/generate/keyboard_h.py @@ -41,7 +41,7 @@ def generate_keyboard_h(cli): keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "quantum.h"'] if not has_layout_h: - keyboard_h_lines.append('#pragma error(".h is only optional for data driven keyboards - kb.h == bad times")') + keyboard_h_lines.append('#error(".h is only optional for data driven keyboards - kb.h == bad times")') # Show the results dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) -- cgit v1.2.3 From 7326a0051b1acdd8fbce741c16000866530773a2 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 30 Jun 2022 00:33:59 +0100 Subject: Allow module check to error out when piped to /dev/null (#17505) --- lib/python/qmk/cli/__init__.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index d7192631a3..02c6d1cbf4 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -156,6 +156,18 @@ def _broken_module_imports(requirements): return False +def _yesno(*args): + """Wrapper to only prompt if interactive + """ + return sys.stdout.isatty() and yesno(*args) + + +def _eprint(errmsg): + """Wrapper to print to stderr + """ + print(errmsg, file=sys.stderr) + + # Make sure our python is new enough # # Supported version information @@ -177,7 +189,7 @@ def _broken_module_imports(requirements): # void: 3.9 if sys.version_info[0] != 3 or sys.version_info[1] < 7: - print('Error: Your Python is too old! Please upgrade to Python 3.7 or later.') + _eprint('Error: Your Python is too old! Please upgrade to Python 3.7 or later.') exit(127) milc_version = __VERSION__.split('.') @@ -185,7 +197,7 @@ milc_version = __VERSION__.split('.') if int(milc_version[0]) < 2 and int(milc_version[1]) < 4: requirements = Path('requirements.txt').resolve() - print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') + _eprint(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') exit(127) # Make sure we can run binaries in the same directory as our Python interpreter @@ -195,7 +207,7 @@ if python_dir not in os.environ['PATH'].split(':'): os.environ['PATH'] = ":".join((python_dir, os.environ['PATH'])) # Check to make sure we have all our dependencies -msg_install = f'Please run `{sys.executable} -m pip install -r %s` to install required python dependencies.' +msg_install = f'\nPlease run `{sys.executable} -m pip install -r %s` to install required python dependencies.' args = sys.argv[1:] while args and args[0][0] == '-': del args[0] @@ -204,24 +216,20 @@ safe_command = args and args[0] in safe_commands if not safe_command: if _broken_module_imports('requirements.txt'): - if yesno('Would you like to install the required Python modules?'): + if _yesno('Would you like to install the required Python modules?'): _install_deps('requirements.txt') else: - print() - print(msg_install % (str(Path('requirements.txt').resolve()),)) - print() + _eprint(msg_install % (str(Path('requirements.txt').resolve()),)) exit(1) if cli.config.user.developer and _broken_module_imports('requirements-dev.txt'): - if yesno('Would you like to install the required developer Python modules?'): + if _yesno('Would you like to install the required developer Python modules?'): _install_deps('requirements-dev.txt') - elif yesno('Would you like to disable developer mode?'): + elif _yesno('Would you like to disable developer mode?'): _run_cmd(sys.argv[0], 'config', 'user.developer=None') else: - print() - print(msg_install % (str(Path('requirements-dev.txt').resolve()),)) - print('You can also turn off developer mode: qmk config user.developer=None') - print() + _eprint(msg_install % (str(Path('requirements-dev.txt').resolve()),)) + _eprint('You can also turn off developer mode: qmk config user.developer=None') exit(1) # Import our subcommands @@ -231,6 +239,6 @@ for subcommand in subcommands: except (ImportError, ModuleNotFoundError) as e: if safe_command: - print(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}') + _eprint(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}') else: raise -- cgit v1.2.3 From d7173967087e022d20d1f9c812b1b668e9d3f71b Mon Sep 17 00:00:00 2001 From: Stefan Kerkmann Date: Thu, 30 Jun 2022 13:19:27 +0200 Subject: [Core] Add Raspberry Pi RP2040 support (#14877) * Disable RESET keycode because of naming conflicts * Add Pico SDK as submodule * Add RP2040 build support to QMK * Adjust USB endpoint structs for RP2040 * Add RP2040 bootloader and double-tap reset routine * Add generic and pro micro RP2040 boards * Add RP2040 onekey keyboard * Add WS2812 PIO DMA enabled driver and documentation Supports regular and open-drain output configuration. RP2040 GPIOs are sadly not 5V tolerant, so this is a bit use-less or needs extra hardware or you take the risk to fry your hardware. * Adjust SIO Driver for RP2040 * Adjust I2C Driver for RP2040 * Adjust SPI Driver for RP2040 * Add PIO serial driver and documentation * Add general RP2040 documentation * Apply suggestions from code review Co-authored-by: Nick Brassel Co-authored-by: Nick Brassel --- lib/python/qmk/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index a54d9058bc..be85a1dbff 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -14,7 +14,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware' MAX_KEYBOARD_SUBFOLDERS = 5 # Supported processor types -CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' +CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' -- cgit v1.2.3 From 9d70162d53a1e42733562bdab511f649ed0980c2 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 1 Jul 2022 22:20:32 +0100 Subject: Allow locate_keymap to handle DEFAULT_FOLDER (#17529) --- lib/python/qmk/keymap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index ca5be0959b..f317f4d11e 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -12,7 +12,7 @@ from pygments.token import Token from pygments import lex import qmk.path -from qmk.keyboard import find_keyboard_from_dir, rules_mk +from qmk.keyboard import find_keyboard_from_dir, rules_mk, keyboard_folder from qmk.errors import CppError # The `keymap.c` template to use when a keyboard doesn't have its own @@ -357,7 +357,7 @@ def locate_keymap(keyboard, keymap): checked_dirs = '' keymap_path = '' - for dir in keyboard.split('/'): + for dir in keyboard_folder(keyboard).split('/'): if checked_dirs: checked_dirs = '/'.join((checked_dirs, dir)) else: -- cgit v1.2.3 From 59e28b8958db4940f026c4bbd81dee7b9b5e49b0 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 2 Jul 2022 12:50:09 +0100 Subject: Add cli command to import keyboard|keymap|kbfirmware (#16668) --- lib/python/qmk/cli/__init__.py | 3 + lib/python/qmk/cli/import/__init__.py | 0 lib/python/qmk/cli/import/kbfirmware.py | 25 ++++++ lib/python/qmk/cli/import/keyboard.py | 23 +++++ lib/python/qmk/cli/import/keymap.py | 23 +++++ lib/python/qmk/importers.py | 148 ++++++++++++++++++++++++++++++++ 6 files changed, 222 insertions(+) create mode 100644 lib/python/qmk/cli/import/__init__.py create mode 100644 lib/python/qmk/cli/import/kbfirmware.py create mode 100644 lib/python/qmk/cli/import/keyboard.py create mode 100644 lib/python/qmk/cli/import/keymap.py create mode 100644 lib/python/qmk/importers.py (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 02c6d1cbf4..8a507677ef 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -59,6 +59,9 @@ subcommands = [ 'qmk.cli.generate.rules_mk', 'qmk.cli.generate.version_h', 'qmk.cli.hello', + 'qmk.cli.import.kbfirmware', + 'qmk.cli.import.keyboard', + 'qmk.cli.import.keymap', 'qmk.cli.info', 'qmk.cli.json2c', 'qmk.cli.lint', diff --git a/lib/python/qmk/cli/import/__init__.py b/lib/python/qmk/cli/import/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/cli/import/kbfirmware.py b/lib/python/qmk/cli/import/kbfirmware.py new file mode 100644 index 0000000000..9c03737378 --- /dev/null +++ b/lib/python/qmk/cli/import/kbfirmware.py @@ -0,0 +1,25 @@ +from milc import cli + +from qmk.importers import import_kbfirmware as _import_kbfirmware +from qmk.path import FileType +from qmk.json_schema import json_load + + +@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') +@cli.subcommand('Import kbfirmware json export') +def import_kbfirmware(cli): + filename = cli.args.filename[0] + + data = json_load(filename) + + cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') + cli.echo('') + + cli.log.warn("Support here is basic - Consider using 'qmk new-keyboard' instead") + + kb_name = _import_kbfirmware(data) + + cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}') + cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},') + cli.log.info('or open the directory in your preferred text editor.') + cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.") diff --git a/lib/python/qmk/cli/import/keyboard.py b/lib/python/qmk/cli/import/keyboard.py new file mode 100644 index 0000000000..3a5ed37dee --- /dev/null +++ b/lib/python/qmk/cli/import/keyboard.py @@ -0,0 +1,23 @@ +from milc import cli + +from qmk.importers import import_keyboard as _import_keyboard +from qmk.path import FileType +from qmk.json_schema import json_load + + +@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') +@cli.subcommand('Import data-driven keyboard') +def import_keyboard(cli): + filename = cli.args.filename[0] + + data = json_load(filename) + + cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') + cli.echo('') + + kb_name = _import_keyboard(data) + + cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}') + cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},') + cli.log.info('or open the directory in your preferred text editor.') + cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.") diff --git a/lib/python/qmk/cli/import/keymap.py b/lib/python/qmk/cli/import/keymap.py new file mode 100644 index 0000000000..a499c93480 --- /dev/null +++ b/lib/python/qmk/cli/import/keymap.py @@ -0,0 +1,23 @@ +from milc import cli + +from qmk.importers import import_keymap as _import_keymap +from qmk.path import FileType +from qmk.json_schema import json_load + + +@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') +@cli.subcommand('Import data-driven keymap') +def import_keymap(cli): + filename = cli.args.filename[0] + + data = json_load(filename) + + cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') + cli.echo('') + + kb_name, km_name = _import_keymap(data) + + cli.log.info(f'{{fg_green}}Imported a new keymap named {{fg_cyan}}{km_name}{{fg_green}}.{{fg_reset}}') + cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}/keymaps/{km_name}{{fg_reset}},') + cli.log.info('or open the directory in your preferred text editor.') + cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km {km_name}{{fg_reset}}.") diff --git a/lib/python/qmk/importers.py b/lib/python/qmk/importers.py new file mode 100644 index 0000000000..f9ecac02ae --- /dev/null +++ b/lib/python/qmk/importers.py @@ -0,0 +1,148 @@ +from dotty_dict import dotty +import json + +from qmk.json_schema import validate +from qmk.path import keyboard, keymap +from qmk.constants import MCU2BOOTLOADER +from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder + + +def _gen_dummy_keymap(name, info_data): + # Pick the first layout macro and just dump in KC_NOs or something? + (layout_name, layout_data), *_ = info_data["layouts"].items() + layout_length = len(layout_data["layout"]) + + keymap_data = { + "keyboard": name, + "layout": layout_name, + "layers": [["KC_NO" for _ in range(0, layout_length)]], + } + + return json.dumps(keymap_data, cls=KeymapJSONEncoder) + + +def import_keymap(keymap_data): + # Validate to ensure we don't have to deal with bad data - handles stdin/file + validate(keymap_data, 'qmk.keymap.v1') + + kb_name = keymap_data['keyboard'] + km_name = keymap_data['keymap'] + + km_folder = keymap(kb_name) / km_name + keyboard_keymap = km_folder / 'keymap.json' + + # This is the deepest folder in the expected tree + keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + + # Dump out all those lovely files + keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder)) + + return (kb_name, km_name) + + +def import_keyboard(info_data): + # Validate to ensure we don't have to deal with bad data - handles stdin/file + validate(info_data, 'qmk.api.keyboard.v1') + + # And validate some more as everything is optional + if not all(key in info_data for key in ['keyboard_name', 'layouts']): + raise ValueError('invalid info.json') + + kb_name = info_data['keyboard_name'] + + # bail + kb_folder = keyboard(kb_name) + if kb_folder.exists(): + raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') + + keyboard_info = kb_folder / 'info.json' + keyboard_rules = kb_folder / 'rules.mk' + keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json' + + # This is the deepest folder in the expected tree + keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + + # Dump out all those lovely files + keyboard_info.write_text(json.dumps(info_data, cls=InfoJSONEncoder)) + keyboard_rules.write_text("# This file intentionally left blank") + keyboard_keymap.write_text(_gen_dummy_keymap(kb_name, info_data)) + + return kb_name + + +def import_kbfirmware(kbfirmware_data): + kbf_data = dotty(kbfirmware_data) + + diode_direction = ["COL2ROW", "ROW2COL"][kbf_data['keyboard.settings.diodeDirection']] + mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']] + bootloader = MCU2BOOTLOADER.get(mcu, "custom") + + layout = [] + for key in kbf_data['keyboard.keys']: + layout.append({ + "matrix": [key["row"], key["col"]], + "x": key["state"]["x"], + "y": key["state"]["y"], + "w": key["state"]["w"], + "h": key["state"]["h"], + }) + + # convert to d/d info.json + info_data = { + "keyboard_name": kbf_data['keyboard.settings.name'].lower(), + "manufacturer": "TODO", + "maintainer": "TODO", + "processor": mcu, + "bootloader": bootloader, + "diode_direction": diode_direction, + "matrix_pins": { + "cols": kbf_data['keyboard.pins.col'], + "rows": kbf_data['keyboard.pins.row'], + }, + "usb": { + "vid": "0xFEED", + "pid": "0x0000", + "device_version": "0.0.1", + }, + "features": { + "bootmagic": True, + "command": False, + "console": False, + "extrakey": True, + "mousekey": True, + "nkro": True, + }, + "layouts": { + "LAYOUT": { + "layout": layout, + } + } + } + + if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']: + indicators = {} + if kbf_data['keyboard.pins.num']: + indicators['num_lock'] = kbf_data['keyboard.pins.num'] + if kbf_data['keyboard.pins.caps']: + indicators['caps_lock'] = kbf_data['keyboard.pins.caps'] + if kbf_data['keyboard.pins.scroll']: + indicators['scroll_lock'] = kbf_data['keyboard.pins.scroll'] + info_data['indicators'] = indicators + + if kbf_data['keyboard.pins.rgb']: + info_data['rgblight'] = { + 'animations': { + 'all': True + }, + 'led_count': kbf_data['keyboard.settings.rgbNum'], + 'pin': kbf_data['keyboard.pins.rgb'], + } + + if kbf_data['keyboard.pins.led']: + info_data['backlight'] = { + 'levels': kbf_data['keyboard.settings.backlightLevels'], + 'pin': kbf_data['keyboard.pins.led'], + } + + # delegate as if it were a regular keyboard import + return import_keyboard(info_data) -- cgit v1.2.3 From ac5e6b6a3bfad12ab7d9786a18fcc90f8bf4caf7 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 3 Jul 2022 00:12:45 +1000 Subject: Tentative Teensy 3.5 support (#14420) * Tentative Teensy 3.5 support * Set firmware format to .hex for ARM Teensys * Got to "device descriptor failed" by comparing with Teensy 3.6 code * Drop down to 96MHz... * Bump back up to 120MHz --- lib/python/qmk/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index be85a1dbff..95fe9a61d0 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -14,7 +14,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware' MAX_KEYBOARD_SUBFOLDERS = 5 # Supported processor types -CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' +CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' -- cgit v1.2.3 From 81d317aa8768fe53a6cca040297231278b06af64 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 8 Jul 2022 22:48:48 +0100 Subject: Fix rgbkb/sol/rev2 build issues (#17601) --- lib/python/qmk/c_parse.py | 3 +++ lib/python/qmk/cli/generate/config_h.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 4b49b8d4e9..c14eb490fa 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -258,6 +258,9 @@ def _parse_led_config(file, matrix_cols, matrix_rows): position_raw.append(_coerce_led_token(_type, value)) if section == 3 and bracket_count == 2: flags.append(_coerce_led_token(_type, value)) + elif _type in [Token.Comment.Preproc]: + # TODO: Promote to error + return None # Slightly better intrim format matrix = list(_get_chunks(matrix_raw, matrix_cols)) diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 9d50368aba..b53f4ff335 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -152,9 +152,14 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''): config_h_lines.append(f'# define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}') config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}') - config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') - config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') - config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') + if len(set(resolutions)) == 1: + config_h_lines.append(f'#ifndef ENCODER_RESOLUTION{postfix}') + config_h_lines.append(f'# define ENCODER_RESOLUTION{postfix} { resolutions[0] }') + config_h_lines.append(f'#endif // ENCODER_RESOLUTION{postfix}') + else: + config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') + config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') + config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') def generate_split_config(kb_info_json, config_h_lines): -- cgit v1.2.3 From 06310e81e9ddebcfc5e25b826bfe4914375de14e Mon Sep 17 00:00:00 2001 From: Dasky <32983009+daskygit@users.noreply.github.com> Date: Fri, 8 Jul 2022 22:49:55 +0100 Subject: Allow only col or row pins for *_right extraction. (#17590) --- lib/python/qmk/info.py | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 23761d71b7..340969f415 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -318,7 +318,7 @@ def _extract_split_right_pins(info_data, config_c): unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1] - if row_pins and col_pins: + if row_pins or col_pins or direct_pins or unused_pins: if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data: _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') @@ -331,37 +331,17 @@ def _extract_split_right_pins(info_data, config_c): if 'right' not in info_data['split']['matrix_pins']: info_data['split']['matrix_pins']['right'] = {} - info_data['split']['matrix_pins']['right'] = { - 'cols': _extract_pins(col_pins), - 'rows': _extract_pins(row_pins), - } - - if direct_pins: - if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}): - _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') - - if 'split' not in info_data: - info_data['split'] = {} + if col_pins: + info_data['split']['matrix_pins']['right']['cols'] = _extract_pins(col_pins) - if 'matrix_pins' not in info_data['split']: - info_data['split']['matrix_pins'] = {} + if row_pins: + info_data['split']['matrix_pins']['right']['rows'] = _extract_pins(row_pins) - if 'right' not in info_data['split']['matrix_pins']: - info_data['split']['matrix_pins']['right'] = {} - - info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins) - - if unused_pins: - if 'split' not in info_data: - info_data['split'] = {} - - if 'matrix_pins' not in info_data['split']: - info_data['split']['matrix_pins'] = {} - - if 'right' not in info_data['split']['matrix_pins']: - info_data['split']['matrix_pins']['right'] = {} + if direct_pins: + info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins) - info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins) + if unused_pins: + info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins) def _extract_matrix_info(info_data, config_c): -- cgit v1.2.3 From 561c5e1d7adfee1e318dd5b732738c35778d575e Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 9 Jul 2022 22:32:43 +1000 Subject: Add line to multibuild output log showing the actual build target. (#17606) --- lib/python/qmk/cli/multibuild.py | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/multibuild.py b/lib/python/qmk/cli/multibuild.py index dff8c88422..a1db669ca8 100755 --- a/lib/python/qmk/cli/multibuild.py +++ b/lib/python/qmk/cli/multibuild.py @@ -69,6 +69,7 @@ def multibuild(cli): all: {keyboard_safe}_binary {keyboard_safe}_binary: @rm -f "{QMK_FIRMWARE}/.build/failed.log.{keyboard_safe}" || true + @echo "Compiling QMK Firmware for target: '{keyboard_name}:{cli.args.keymap}'..." >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/builddefs/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false {' '.join(cli.args.env)} \\ >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" 2>&1 \\ || cp "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" "{QMK_FIRMWARE}/.build/failed.log.{os.getpid()}.{keyboard_safe}" -- cgit v1.2.3 From 35d78aa8a4587ce5286a362471380a9d4f000f3c Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 11 Jul 2022 10:51:39 +0100 Subject: More DD encoder fixes (#17615) --- lib/python/qmk/cli/generate/config_h.py | 8 +++++--- lib/python/qmk/info.py | 10 +++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index b53f4ff335..a26dcdf7d7 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -142,7 +142,7 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''): for encoder in encoder_json.get("rotary", []): a_pads.append(encoder["pin_a"]) b_pads.append(encoder["pin_b"]) - resolutions.append(str(encoder.get("resolution", 4))) + resolutions.append(encoder.get("resolution", None)) config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}') config_h_lines.append(f'# define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}') @@ -152,13 +152,15 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''): config_h_lines.append(f'# define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}') config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}') - if len(set(resolutions)) == 1: + if None in resolutions: + cli.log.debug("Unable to generate ENCODER_RESOLUTION configuration") + elif len(set(resolutions)) == 1: config_h_lines.append(f'#ifndef ENCODER_RESOLUTION{postfix}') config_h_lines.append(f'# define ENCODER_RESOLUTION{postfix} { resolutions[0] }') config_h_lines.append(f'#endif // ENCODER_RESOLUTION{postfix}') else: config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') - config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') + config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(map(str,resolutions)) } }}') config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index dac2fd6e9e..72424f390e 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -225,17 +225,21 @@ def _extract_encoders_values(config_c, postfix=''): b_pad = config_c.get(f'ENCODERS_PAD_B{postfix}', '').replace(' ', '')[1:-1] resolutions = config_c.get(f'ENCODER_RESOLUTIONS{postfix}', '').replace(' ', '')[1:-1] - default_resolution = config_c.get('ENCODER_RESOLUTION', '4') + default_resolution = config_c.get('ENCODER_RESOLUTION', None) if a_pad and b_pad: a_pad = list(filter(None, a_pad.split(','))) b_pad = list(filter(None, b_pad.split(','))) resolutions = list(filter(None, resolutions.split(','))) - resolutions += [default_resolution] * (len(a_pad) - len(resolutions)) + if default_resolution: + resolutions += [default_resolution] * (len(a_pad) - len(resolutions)) encoders = [] for index in range(len(a_pad)): - encoders.append({'pin_a': a_pad[index], 'pin_b': b_pad[index], "resolution": int(resolutions[index])}) + encoder = {'pin_a': a_pad[index], 'pin_b': b_pad[index]} + if index < len(resolutions): + encoder['resolution'] = int(resolutions[index]) + encoders.append(encoder) return encoders -- cgit v1.2.3 From 1f42a8ccdd10a1f1ac1ed1a0ab62d2c9e5dc3ffa Mon Sep 17 00:00:00 2001 From: Niko Wenselowski Date: Sat, 23 Jul 2022 18:42:19 +0200 Subject: Fix test logic to check for both keymaps (#17761) Python will evaluate first the left and then the right side of the and operator. The left side would previously return True based on the truthiness logic that treats any non-emptry string as true. It would not check if the desired keymap exists. If the left side is true it will evaluate the right side which will check for the existance of a specific keymap. With this change the check for existance of two keymaps is implemented. --- lib/python/qmk/tests/test_cli_commands.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 2463543ef1..fde8b079a3 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -97,13 +97,15 @@ def test_list_keyboards(): def test_list_keymaps(): result = check_subcommand('list-keymaps', '-kb', 'handwired/pytest/basic') check_returncode(result) - assert 'default' and 'default_json' in result.stdout + assert 'default' in result.stdout + assert 'default_json' in result.stdout def test_list_keymaps_long(): result = check_subcommand('list-keymaps', '--keyboard', 'handwired/pytest/basic') check_returncode(result) - assert 'default' and 'default_json' in result.stdout + assert 'default' in result.stdout + assert 'default_json' in result.stdout def test_list_keymaps_community(): @@ -115,19 +117,22 @@ def test_list_keymaps_community(): def test_list_keymaps_kb_only(): result = check_subcommand('list-keymaps', '-kb', 'contra') check_returncode(result) - assert 'default' and 'via' in result.stdout + assert 'default' in result.stdout + assert 'via' in result.stdout def test_list_keymaps_vendor_kb(): result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar') check_returncode(result) - assert 'default' and 'via' in result.stdout + assert 'default' in result.stdout + assert 'via' in result.stdout def test_list_keymaps_vendor_kb_rev(): result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2') check_returncode(result) - assert 'default' and 'via' in result.stdout + assert 'default' in result.stdout + assert 'via' in result.stdout def test_list_keymaps_no_keyboard_found(): -- cgit v1.2.3 From d1434b6d75865d91b6b6626a06b67e24bd81e145 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 27 Jul 2022 02:37:28 +1000 Subject: Make `qmk doctor` print out the last log entry for upstream/{master,develop}, including dates (#17713) --- lib/python/qmk/cli/doctor/main.py | 26 +++++++++++++++++++++++++- lib/python/qmk/git.py | 19 +++++++++++++++++++ lib/python/qmk/submodules.py | 26 +++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/doctor/main.py b/lib/python/qmk/cli/doctor/main.py index 2898a9894c..1600ab8dd4 100755 --- a/lib/python/qmk/cli/doctor/main.py +++ b/lib/python/qmk/cli/doctor/main.py @@ -11,7 +11,7 @@ from milc.questions import yesno from qmk import submodules from qmk.constants import QMK_FIRMWARE, QMK_FIRMWARE_UPSTREAM from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules -from qmk.git import git_check_repo, git_get_branch, git_get_tag, git_is_dirty, git_get_remotes, git_check_deviation +from qmk.git import git_check_repo, git_get_branch, git_get_tag, git_get_last_log_entry, git_get_common_ancestor, git_is_dirty, git_get_remotes, git_check_deviation from qmk.commands import in_virtualenv @@ -66,10 +66,32 @@ def git_tests(): if git_branch in ['master', 'develop'] and git_deviation: cli.log.warning('{fg_yellow}The local "%s" branch contains commits not found in the upstream branch.', git_branch) status = CheckStatus.WARNING + for branch in [git_branch, 'upstream/master', 'upstream/develop']: + cli.log.info('- Latest %s: %s', branch, git_get_last_log_entry(branch)) + for branch in ['upstream/master', 'upstream/develop']: + cli.log.info('- Common ancestor with %s: %s', branch, git_get_common_ancestor(branch, 'HEAD')) return status +def output_submodule_status(): + """Prints out information related to the submodule status. + """ + cli.log.info('Submodule status:') + sub_status = submodules.status() + for s in sub_status.keys(): + sub_info = sub_status[s] + if 'name' in sub_info: + sub_name = sub_info['name'] + sub_shorthash = sub_info['shorthash'] if 'shorthash' in sub_info else '' + sub_describe = sub_info['describe'] if 'describe' in sub_info else '' + sub_last_log_timestamp = sub_info['last_log_timestamp'] if 'last_log_timestamp' in sub_info else '' + if sub_last_log_timestamp != '': + cli.log.info(f'- {sub_name}: {sub_last_log_timestamp} -- {sub_describe} ({sub_shorthash})') + else: + cli.log.error(f'- {sub_name}: <<< missing or unknown >>>') + + @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.') @cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.') @cli.subcommand('Basic QMK environment checks') @@ -129,6 +151,8 @@ def doctor(cli): elif sub_ok == CheckStatus.WARNING and status == CheckStatus.OK: status = CheckStatus.WARNING + output_submodule_status() + # Report a summary of our findings to the user if status == CheckStatus.OK: cli.log.info('{fg_green}QMK is ready to go') diff --git a/lib/python/qmk/git.py b/lib/python/qmk/git.py index f493628492..5d09d816df 100644 --- a/lib/python/qmk/git.py +++ b/lib/python/qmk/git.py @@ -62,6 +62,25 @@ def git_get_tag(): return git_tag.stdout.strip() +def git_get_last_log_entry(branch_name): + """Retrieves the last log entry for the branch being worked on. + """ + git_lastlog = cli.run(['git', '--no-pager', 'log', '--pretty=format:%ad (%h) -- %s', '--date=iso', '-n1', branch_name]) + + if git_lastlog.returncode == 0 and git_lastlog.stdout: + return git_lastlog.stdout.strip() + + +def git_get_common_ancestor(branch_a, branch_b): + """Retrieves the common ancestor between for the two supplied branches. + """ + git_merge_base = cli.run(['git', 'merge-base', branch_a, branch_b]) + git_branchpoint_log = cli.run(['git', '--no-pager', 'log', '--pretty=format:%ad (%h) -- %s', '--date=iso', '-n1', git_merge_base.stdout.strip()]) + + if git_branchpoint_log.returncode == 0 and git_branchpoint_log.stdout: + return git_branchpoint_log.stdout.strip() + + def git_get_remotes(): """Returns the current remotes for a repo. """ diff --git a/lib/python/qmk/submodules.py b/lib/python/qmk/submodules.py index 6a272dae50..3cb232a7b5 100644 --- a/lib/python/qmk/submodules.py +++ b/lib/python/qmk/submodules.py @@ -11,7 +11,11 @@ def status(): { 'name': 'submodule_name', 'status': None/False/True, - 'githash': ' + 'githash': '' + 'shorthash': '' + 'describe': '' + 'last_log_message': 'log message' + 'last_log_timestamp': 'timestamp' } status is None when the submodule doesn't exist, False when it's out of date, and True when it's current @@ -36,6 +40,26 @@ def status(): else: raise ValueError('Unknown `git submodule status` sha-1 prefix character: "%s"' % status) + submodule_logs = cli.run(['git', 'submodule', '-q', 'foreach', 'git --no-pager log --pretty=format:"$sm_path%x01%h%x01%ad%x01%s%x0A" --date=iso -n1']) + for log_line in submodule_logs.stdout.split('\n'): + if not log_line: + continue + + r = log_line.split('\x01') + submodule = r[0] + submodules[submodule]['shorthash'] = r[1] if len(r) > 1 else '' + submodules[submodule]['last_log_timestamp'] = r[2] if len(r) > 2 else '' + submodules[submodule]['last_log_message'] = r[3] if len(r) > 3 else '' + + submodule_tags = cli.run(['git', 'submodule', '-q', 'foreach', 'echo -n "$sm_path "; git describe --tags']) + for log_line in submodule_tags.stdout.split('\n'): + if not log_line: + continue + + r = log_line.split() + submodule = r[0] + submodules[submodule]['describe'] = r[1] if len(r) > 1 else '' + return submodules -- cgit v1.2.3 From 3285659690c79807f96021002e0dc336c99c73f2 Mon Sep 17 00:00:00 2001 From: Albert Y <76888457+filterpaper@users.noreply.github.com> Date: Wed, 27 Jul 2022 17:29:10 +0800 Subject: CLI compatibility for MacOS (#17811) --- lib/python/qmk/submodules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/submodules.py b/lib/python/qmk/submodules.py index 3cb232a7b5..52efa602a0 100644 --- a/lib/python/qmk/submodules.py +++ b/lib/python/qmk/submodules.py @@ -51,7 +51,7 @@ def status(): submodules[submodule]['last_log_timestamp'] = r[2] if len(r) > 2 else '' submodules[submodule]['last_log_message'] = r[3] if len(r) > 3 else '' - submodule_tags = cli.run(['git', 'submodule', '-q', 'foreach', 'echo -n "$sm_path "; git describe --tags']) + submodule_tags = cli.run(['git', 'submodule', '-q', 'foreach', '\'echo $sm_path `git describe --tags`\'']) for log_line in submodule_tags.stdout.split('\n'): if not log_line: continue -- cgit v1.2.3 From 897403c4a71080469b8c11fc87e0e145ffe3837a Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 6 Aug 2022 07:14:29 +0100 Subject: Publish data as part of API generation (#17020) --- lib/python/qmk/cli/generate/api.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 0596b3f22b..8d8ca3cd41 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -12,21 +12,30 @@ from qmk.json_encoders import InfoJSONEncoder from qmk.json_schema import json_load from qmk.keyboard import find_readme, list_keyboards -TEMPLATE_PATH = Path('data/templates/api/') +DATA_PATH = Path('data') +TEMPLATE_PATH = DATA_PATH / 'templates/api/' BUILD_API_PATH = Path('.build/api_data/') +def _filtered_keyboard_list(): + """Perform basic filtering of list_keyboards + """ + keyboard_list = list_keyboards() + if cli.args.filter: + kb_list = [] + for keyboard_name in keyboard_list: + if any(i in keyboard_name for i in cli.args.filter): + kb_list.append(keyboard_name) + keyboard_list = kb_list + return keyboard_list + + @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.") @cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.") -@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True) +@cli.subcommand('Generate QMK API data', hidden=False if cli.config.user.developer else True) def generate_api(cli): """Generates the QMK API data. """ - if BUILD_API_PATH.exists(): - shutil.rmtree(BUILD_API_PATH) - - shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH) - v1_dir = BUILD_API_PATH / 'v1' keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets @@ -34,14 +43,14 @@ def generate_api(cli): keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target + if BUILD_API_PATH.exists(): + shutil.rmtree(BUILD_API_PATH) + + shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH) + shutil.copytree(DATA_PATH, v1_dir) + # Filter down when required - keyboard_list = list_keyboards() - if cli.args.filter: - kb_list = [] - for keyboard_name in keyboard_list: - if any(i in keyboard_name for i in cli.args.filter): - kb_list.append(keyboard_name) - keyboard_list = kb_list + keyboard_list = _filtered_keyboard_list() kb_all = {} usb_list = {} -- cgit v1.2.3 From 37345e2ace56e1ea17e0ab6b4df3af81f437ae79 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 6 Aug 2022 22:37:40 +1000 Subject: Provide users with replacements for deprecated/invalid functionality where applicable (#17604) --- lib/python/qmk/info.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 340969f415..ccec46ce21 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -463,10 +463,17 @@ def _extract_config_h(info_data, config_c): key_type = info_dict.get('value_type', 'raw') try: + replace_with = info_dict.get('replace_with') if config_key in config_c and info_dict.get('invalid', False): - _log_error(info_data, '%s in config.h is no longer a valid option' % config_key) + if replace_with: + _log_error(info_data, '%s in config.h is no longer a valid option and should be replaced with %s' % (config_key, replace_with)) + else: + _log_error(info_data, '%s in config.h is no longer a valid option and should be removed' % config_key) elif config_key in config_c and info_dict.get('deprecated', False): - _log_warning(info_data, '%s in config.h is deprecated and will be removed at a later date' % config_key) + if replace_with: + _log_warning(info_data, '%s in config.h is deprecated in favor of %s and will be removed at a later date' % (config_key, replace_with)) + else: + _log_warning(info_data, '%s in config.h is deprecated and will be removed at a later date' % config_key) if config_key in config_c and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): @@ -527,10 +534,17 @@ def _extract_rules_mk(info_data, rules): key_type = info_dict.get('value_type', 'raw') try: + replace_with = info_dict.get('replace_with') if rules_key in rules and info_dict.get('invalid', False): - _log_error(info_data, '%s in rules.mk is no longer a valid option' % rules_key) + if replace_with: + _log_error(info_data, '%s in rules.mk is no longer a valid option and should be replaced with %s' % (rules_key, replace_with)) + else: + _log_error(info_data, '%s in rules.mk is no longer a valid option and should be removed' % rules_key) elif rules_key in rules and info_dict.get('deprecated', False): - _log_warning(info_data, '%s in rules.mk is deprecated and will be removed at a later date' % rules_key) + if replace_with: + _log_warning(info_data, '%s in rules.mk is deprecated in favor of %s and will be removed at a later date' % (rules_key, replace_with)) + else: + _log_warning(info_data, '%s in rules.mk is deprecated and will be removed at a later date' % rules_key) if rules_key in rules and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): -- cgit v1.2.3 From 154d35ac146422bef938ed9756f6e0012baa83a2 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 6 Aug 2022 23:23:35 +1000 Subject: Remove `UNUSED_PINS` (#17931) --- lib/python/qmk/info.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index bc07f68d7b..7ed636d0f9 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -374,11 +374,9 @@ def _extract_split_right_pins(info_data, config_c): # Figure out the right half matrix pins row_pins = config_c.get('MATRIX_ROW_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() col_pins = config_c.get('MATRIX_COL_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() - unused_pin_text = config_c.get('UNUSED_PINS_RIGHT') - unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1] - if row_pins or col_pins or direct_pins or unused_pins: + if row_pins or col_pins or direct_pins: if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data: _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') @@ -400,17 +398,12 @@ def _extract_split_right_pins(info_data, config_c): if direct_pins: info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins) - if unused_pins: - info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins) - def _extract_matrix_info(info_data, config_c): """Populate the matrix information. """ row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() - unused_pin_text = config_c.get('UNUSED_PINS') - unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] info_snippet = {} @@ -436,12 +429,6 @@ def _extract_matrix_info(info_data, config_c): info_snippet['direct'] = _extract_direct_matrix(direct_pins) - if unused_pins: - if 'matrix_pins' not in info_data: - info_data['matrix_pins'] = {} - - info_snippet['unused'] = _extract_pins(unused_pins) - if config_c.get('CUSTOM_MATRIX', 'no') != 'no': if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']: _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.') -- cgit v1.2.3 From fc7e9efd215fee7ec8acd3e2740a6ab9d4633818 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 13 Aug 2022 14:39:56 +0100 Subject: Improve importer workflow (#17707) --- lib/python/qmk/constants.py | 5 ++ lib/python/qmk/importers.py | 147 +++++++++++++++++++++++++++++--------------- 2 files changed, 101 insertions(+), 51 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 95fe9a61d0..5fe8326daf 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -58,6 +58,11 @@ MCU2BOOTLOADER = { "atmega328": "usbasploader", } +# Map of legacy keycodes that can be automatically updated +LEGACY_KEYCODES = { # Comment here is to force multiline formatting + 'RESET': 'QK_BOOT' +} + # Common format strings DATE_FORMAT = '%Y-%m-%d' DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z' diff --git a/lib/python/qmk/importers.py b/lib/python/qmk/importers.py index f9ecac02ae..307c66ee3c 100644 --- a/lib/python/qmk/importers.py +++ b/lib/python/qmk/importers.py @@ -1,10 +1,26 @@ from dotty_dict import dotty +from datetime import date +from pathlib import Path import json +from qmk.git import git_get_username from qmk.json_schema import validate from qmk.path import keyboard, keymap -from qmk.constants import MCU2BOOTLOADER +from qmk.constants import MCU2BOOTLOADER, LEGACY_KEYCODES from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder +from qmk.json_schema import deep_update, json_load + +TEMPLATE = Path('data/templates/keyboard/') + + +def replace_placeholders(src, dest, tokens): + """Replaces the given placeholders in each template file. + """ + content = src.read_text() + for key, value in tokens.items(): + content = content.replace(f'%{key}%', value) + + dest.write_text(content) def _gen_dummy_keymap(name, info_data): @@ -18,7 +34,47 @@ def _gen_dummy_keymap(name, info_data): "layers": [["KC_NO" for _ in range(0, layout_length)]], } - return json.dumps(keymap_data, cls=KeymapJSONEncoder) + return keymap_data + + +def _extract_kbfirmware_layout(kbf_data): + layout = [] + for key in kbf_data['keyboard.keys']: + item = { + 'matrix': [key['row'], key['col']], + 'x': key['state']['x'], + 'y': key['state']['y'], + } + if key['state']['w'] != 1: + item['w'] = key['state']['w'] + if key['state']['h'] != 1: + item['h'] = key['state']['h'] + layout.append(item) + + return layout + + +def _extract_kbfirmware_keymap(kbf_data): + keymap_data = { + 'keyboard': kbf_data['keyboard.settings.name'].lower(), + 'layout': 'LAYOUT', + 'layers': [], + } + + for i in range(15): + layer = [] + for key in kbf_data['keyboard.keys']: + keycode = key['keycodes'][i]['id'] + keycode = LEGACY_KEYCODES.get(keycode, keycode) + if '()' in keycode: + fields = key['keycodes'][i]['fields'] + keycode = f'{keycode.split(")")[0]}{",".join(map(str, fields))})' + layer.append(keycode) + if set(layer) == {'KC_TRNS'}: + break + keymap_data['layers'].append(layer) + + return keymap_data def import_keymap(keymap_data): @@ -40,7 +96,7 @@ def import_keymap(keymap_data): return (kb_name, km_name) -def import_keyboard(info_data): +def import_keyboard(info_data, keymap_data=None): # Validate to ensure we don't have to deal with bad data - handles stdin/file validate(info_data, 'qmk.api.keyboard.v1') @@ -55,17 +111,36 @@ def import_keyboard(info_data): if kb_folder.exists(): raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') + if not keymap_data: + # TODO: if supports community then grab that instead + keymap_data = _gen_dummy_keymap(kb_name, info_data) + keyboard_info = kb_folder / 'info.json' - keyboard_rules = kb_folder / 'rules.mk' keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json' - # This is the deepest folder in the expected tree + # begin with making the deepest folder in the tree keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + user_name = git_get_username() + if not user_name: + user_name = 'TODO' + + tokens = { # Comment here is to force multiline formatting + 'YEAR': str(date.today().year), + 'KEYBOARD': kb_name, + 'USER_NAME': user_name, + 'REAL_NAME': user_name, + } + # Dump out all those lovely files - keyboard_info.write_text(json.dumps(info_data, cls=InfoJSONEncoder)) - keyboard_rules.write_text("# This file intentionally left blank") - keyboard_keymap.write_text(_gen_dummy_keymap(kb_name, info_data)) + for file in list(TEMPLATE.iterdir()): + replace_placeholders(file, kb_folder / file.name, tokens) + + temp = json_load(keyboard_info) + deep_update(temp, info_data) + + keyboard_info.write_text(json.dumps(temp, cls=InfoJSONEncoder)) + keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder)) return kb_name @@ -77,21 +152,12 @@ def import_kbfirmware(kbfirmware_data): mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']] bootloader = MCU2BOOTLOADER.get(mcu, "custom") - layout = [] - for key in kbf_data['keyboard.keys']: - layout.append({ - "matrix": [key["row"], key["col"]], - "x": key["state"]["x"], - "y": key["state"]["y"], - "w": key["state"]["w"], - "h": key["state"]["h"], - }) + layout = _extract_kbfirmware_layout(kbf_data) + keymap_data = _extract_kbfirmware_keymap(kbf_data) # convert to d/d info.json - info_data = { + info_data = dotty({ "keyboard_name": kbf_data['keyboard.settings.name'].lower(), - "manufacturer": "TODO", - "maintainer": "TODO", "processor": mcu, "bootloader": bootloader, "diode_direction": diode_direction, @@ -99,50 +165,29 @@ def import_kbfirmware(kbfirmware_data): "cols": kbf_data['keyboard.pins.col'], "rows": kbf_data['keyboard.pins.row'], }, - "usb": { - "vid": "0xFEED", - "pid": "0x0000", - "device_version": "0.0.1", - }, - "features": { - "bootmagic": True, - "command": False, - "console": False, - "extrakey": True, - "mousekey": True, - "nkro": True, - }, "layouts": { "LAYOUT": { "layout": layout, } } - } + }) if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']: - indicators = {} if kbf_data['keyboard.pins.num']: - indicators['num_lock'] = kbf_data['keyboard.pins.num'] + info_data['indicators.num_lock'] = kbf_data['keyboard.pins.num'] if kbf_data['keyboard.pins.caps']: - indicators['caps_lock'] = kbf_data['keyboard.pins.caps'] + info_data['indicators.caps_lock'] = kbf_data['keyboard.pins.caps'] if kbf_data['keyboard.pins.scroll']: - indicators['scroll_lock'] = kbf_data['keyboard.pins.scroll'] - info_data['indicators'] = indicators + info_data['indicators.scroll_lock'] = kbf_data['keyboard.pins.scroll'] if kbf_data['keyboard.pins.rgb']: - info_data['rgblight'] = { - 'animations': { - 'all': True - }, - 'led_count': kbf_data['keyboard.settings.rgbNum'], - 'pin': kbf_data['keyboard.pins.rgb'], - } + info_data['rgblight.animations.all'] = True + info_data['rgblight.led_count'] = kbf_data['keyboard.settings.rgbNum'] + info_data['rgblight.pin'] = kbf_data['keyboard.pins.rgb'] if kbf_data['keyboard.pins.led']: - info_data['backlight'] = { - 'levels': kbf_data['keyboard.settings.backlightLevels'], - 'pin': kbf_data['keyboard.pins.led'], - } + info_data['backlight.levels'] = kbf_data['keyboard.settings.backlightLevels'] + info_data['backlight.pin'] = kbf_data['keyboard.pins.led'] # delegate as if it were a regular keyboard import - return import_keyboard(info_data) + return import_keyboard(info_data.to_dict(), keymap_data) -- cgit v1.2.3 From 9550cc464cd0579071cf490b6f3c9d151d336bd9 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 19 Aug 2022 01:48:33 +0100 Subject: Fix new-keyboard default for RP2040 bootloader (#18100) --- lib/python/qmk/constants.py | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 5fe8326daf..7da9df1d8a 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -20,6 +20,7 @@ VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' # Bootloaders of the supported processors MCU2BOOTLOADER = { + "RP2040": "rp2040", "MKL26Z64": "halfkay", "MK20DX128": "halfkay", "MK20DX256": "halfkay", -- cgit v1.2.3 From 3c745caf6113cfe8778cf9c11edbc0217c34e236 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 19 Aug 2022 01:56:48 +0100 Subject: Remove legacy bootmagic cli parsing (#18099) --- lib/python/qmk/info.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 7ed636d0f9..c95b55916c 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -110,14 +110,7 @@ def info_json(keyboard): def _extract_features(info_data, rules): """Find all the features enabled in rules.mk. """ - # Special handling for bootmagic which also supports a "lite" mode. - if rules.get('BOOTMAGIC_ENABLE') == 'lite': - rules['BOOTMAGIC_LITE_ENABLE'] = 'on' - del rules['BOOTMAGIC_ENABLE'] - if rules.get('BOOTMAGIC_ENABLE') == 'full': - rules['BOOTMAGIC_ENABLE'] = 'on' - - # Process the rest of the rules as booleans + # Process booleans rules for key, value in rules.items(): if key.endswith('_ENABLE'): key = '_'.join(key.split('_')[:-1]).lower() -- cgit v1.2.3 From 5e2ffe7d8f4187109514147469d7db93e075f6f0 Mon Sep 17 00:00:00 2001 From: Erovia Date: Sat, 20 Aug 2022 06:39:19 +0100 Subject: CLI: Teaching the CLI to flash binaries (#16584) Co-authored-by: Ryan Co-authored-by: Sergey Vlasov Co-authored-by: Joel Challis Co-authored-by: Nick Brassel --- lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/doctor/linux.py | 57 +++-------- lib/python/qmk/cli/flash.py | 120 ++++++++++++---------- lib/python/qmk/constants.py | 48 +++++++++ lib/python/qmk/flashers.py | 203 +++++++++++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 93 deletions(-) create mode 100644 lib/python/qmk/flashers.py (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 8a507677ef..f05b2a746e 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -15,6 +15,7 @@ from milc.questions import yesno import_names = { # A mapping of package name to importable name 'pep8-naming': 'pep8ext_naming', + 'pyserial': 'serial', 'pyusb': 'usb.core', 'qmk-dotty-dict': 'dotty_dict', 'pillow': 'PIL' diff --git a/lib/python/qmk/cli/doctor/linux.py b/lib/python/qmk/cli/doctor/linux.py index 94683d3307..a803305c0d 100644 --- a/lib/python/qmk/cli/doctor/linux.py +++ b/lib/python/qmk/cli/doctor/linux.py @@ -6,7 +6,7 @@ from pathlib import Path from milc import cli -from qmk.constants import QMK_FIRMWARE +from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS from .check import CheckStatus @@ -26,6 +26,18 @@ def _udev_rule(vid, pid=None, *args): return rule +def _generate_desired_rules(bootloader_vids_pids): + rules = dict() + for bl in bootloader_vids_pids.keys(): + rules[bl] = set() + for vid_pid in bootloader_vids_pids[bl]: + if bl == 'caterina' or bl == 'md-boot': + rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"')) + else: + rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1])) + return rules + + def _deprecated_udev_rule(vid, pid=None): """ Helper function that return udev rules @@ -47,47 +59,8 @@ def check_udev_rules(): Path("/run/udev/rules.d/"), Path("/etc/udev/rules.d/"), ] - desired_rules = { - 'atmel-dfu': { - _udev_rule("03eb", "2fef"), # ATmega16U2 - _udev_rule("03eb", "2ff0"), # ATmega32U2 - _udev_rule("03eb", "2ff3"), # ATmega16U4 - _udev_rule("03eb", "2ff4"), # ATmega32U4 - _udev_rule("03eb", "2ff9"), # AT90USB64 - _udev_rule("03eb", "2ffa"), # AT90USB162 - _udev_rule("03eb", "2ffb") # AT90USB128 - }, - 'kiibohd': {_udev_rule("1c11", "b007")}, - 'stm32': { - _udev_rule("1eaf", "0003"), # STM32duino - _udev_rule("0483", "df11") # STM32 DFU - }, - 'bootloadhid': {_udev_rule("16c0", "05df")}, - 'usbasploader': {_udev_rule("16c0", "05dc")}, - 'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')}, - 'caterina': { - # Spark Fun Electronics - _udev_rule("1b4f", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz - _udev_rule("1b4f", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz - _udev_rule("1b4f", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones) - # Pololu Electronics - _udev_rule("1ffb", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4 - # Arduino SA - _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo - _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro - # Adafruit Industries LLC - _udev_rule("239a", "000c", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4 - _udev_rule("239a", "000d", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz - _udev_rule("239a", "000e", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz - # dog hunter AG - _udev_rule("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo - _udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro - }, - 'hid-bootloader': { - _udev_rule("03eb", "2067"), # QMK HID - _udev_rule("16c0", "0478") # PJRC halfkay - } - } + + desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS) # These rules are no longer recommended, only use them to check for their presence. deprecated_rules = { diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index ebe739c50e..c39f4b36d4 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -4,6 +4,7 @@ You can compile a keymap already in the repo or using a QMK Configurator export. A bootloader must be specified. """ from subprocess import DEVNULL +import sys from argcomplete.completers import FilesCompleter from milc import cli @@ -12,6 +13,7 @@ import qmk.path from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json from qmk.keyboard import keyboard_completer, keyboard_folder +from qmk.flashers import flasher def print_bootloader_help(): @@ -38,9 +40,10 @@ def print_bootloader_help(): cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') -@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.') +@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.') @cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.') @cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') +@cli.argument('-m', '--mcu', help='The MCU name. Required for HalfKay, HID, USBAspLoader and ISP flashing.') @cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") @@ -53,6 +56,8 @@ def print_bootloader_help(): def flash(cli): """Compile and or flash QMK Firmware or keyboard/layout + If a binary firmware is supplied, try to flash that. + If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments will be ignored. @@ -60,56 +65,69 @@ def flash(cli): If bootloader is omitted the make system will use the configured bootloader for that keyboard. """ - if cli.args.clean and not cli.args.filename and not cli.args.dry_run: - if cli.config.flash.keyboard and cli.config.flash.keymap: - command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean') - cli.run(command, capture_output=False, stdin=DEVNULL) - - # Build the environment vars - envs = {} - for env in cli.args.env: - if '=' in env: - key, value = env.split('=', 1) - envs[key] = value - else: - cli.log.warning('Invalid environment variable: %s', env) - - # Determine the compile command - command = '' - - if cli.args.bootloaders: - # Provide usage and list bootloaders - cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') - print_bootloader_help() - return False - - if cli.args.filename: - # Handle compiling a configurator JSON - user_keymap = parse_configurator_json(cli.args.filename) - keymap_path = qmk.path.keymap(user_keymap['keyboard']) - command = compile_configurator_json(user_keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs) - - cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) + if cli.args.filename and cli.args.filename.suffix in ['.bin', '.hex']: + # Try to flash binary firmware + cli.echo('Flashing binary firmware...\nPlease reset your keyboard into bootloader mode now!\nPress Ctrl-C to exit.\n') + try: + err, msg = flasher(cli.args.mcu, cli.args.filename) + if err: + cli.log.error(msg) + return False + except KeyboardInterrupt: + cli.log.info('Ctrl-C was pressed, exiting...') + sys.exit(0) else: - if cli.config.flash.keyboard and cli.config.flash.keymap: - # Generate the make command for a specific keyboard/keymap. - command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs) - - elif not cli.config.flash.keyboard: - cli.log.error('Could not determine keyboard!') - elif not cli.config.flash.keymap: - cli.log.error('Could not determine keymap!') - - # Compile the firmware, if we're able to - if command: - cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command)) - if not cli.args.dry_run: - cli.echo('\n') - compile = cli.run(command, capture_output=False, stdin=DEVNULL) - return compile.returncode + if cli.args.clean and not cli.args.filename and not cli.args.dry_run: + if cli.config.flash.keyboard and cli.config.flash.keymap: + command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean') + cli.run(command, capture_output=False, stdin=DEVNULL) + + # Build the environment vars + envs = {} + for env in cli.args.env: + if '=' in env: + key, value = env.split('=', 1) + envs[key] = value + else: + cli.log.warning('Invalid environment variable: %s', env) + + # Determine the compile command + command = '' + + if cli.args.bootloaders: + # Provide usage and list bootloaders + cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') + print_bootloader_help() + return False + + if cli.args.filename: + # Handle compiling a configurator JSON + user_keymap = parse_configurator_json(cli.args.filename) + keymap_path = qmk.path.keymap(user_keymap['keyboard']) + command = compile_configurator_json(user_keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs) + + cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) - else: - cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.') - cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') - return False + else: + if cli.config.flash.keyboard and cli.config.flash.keymap: + # Generate the make command for a specific keyboard/keymap. + command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs) + + elif not cli.config.flash.keyboard: + cli.log.error('Could not determine keyboard!') + elif not cli.config.flash.keymap: + cli.log.error('Could not determine keymap!') + + # Compile the firmware, if we're able to + if command: + cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command)) + if not cli.args.dry_run: + cli.echo('\n') + compile = cli.run(command, capture_output=False, stdin=DEVNULL) + return compile.returncode + + else: + cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.') + cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') + return False diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 7da9df1d8a..10da5e7e8e 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -64,6 +64,54 @@ LEGACY_KEYCODES = { # Comment here is to force multiline formatting 'RESET': 'QK_BOOT' } +# Map VID:PID values to bootloaders +BOOTLOADER_VIDS_PIDS = { + 'atmel-dfu': { + ("03eb", "2fef"), # ATmega16U2 + ("03eb", "2ff0"), # ATmega32U2 + ("03eb", "2ff3"), # ATmega16U4 + ("03eb", "2ff4"), # ATmega32U4 + ("03eb", "2ff9"), # AT90USB64 + ("03eb", "2ffa"), # AT90USB162 + ("03eb", "2ffb") # AT90USB128 + }, + 'kiibohd': {("1c11", "b007")}, + 'stm32-dfu': { + ("1eaf", "0003"), # STM32duino + ("0483", "df11") # STM32 DFU + }, + 'apm32-dfu': {("314b", "0106")}, + 'gd32v-dfu': {("28e9", "0189")}, + 'bootloadhid': {("16c0", "05df")}, + 'usbasploader': {("16c0", "05dc")}, + 'usbtinyisp': {("1782", "0c9f")}, + 'md-boot': {("03eb", "6124")}, + 'caterina': { + # pid.codes shared PID + ("1209", "9203"), # Keyboardio Atreus 2 Bootloader + # Spark Fun Electronics + ("1b4f", "9203"), # Pro Micro 3V3/8MHz + ("1b4f", "9205"), # Pro Micro 5V/16MHz + ("1b4f", "9207"), # LilyPad 3V3/8MHz (and some Pro Micro clones) + # Pololu Electronics + ("1ffb", "0101"), # A-Star 32U4 + # Arduino SA + ("2341", "0036"), # Leonardo + ("2341", "0037"), # Micro + # Adafruit Industries LLC + ("239a", "000c"), # Feather 32U4 + ("239a", "000d"), # ItsyBitsy 32U4 3V3/8MHz + ("239a", "000e"), # ItsyBitsy 32U4 5V/16MHz + # dog hunter AG + ("2a03", "0036"), # Leonardo + ("2a03", "0037") # Micro + }, + 'hid-bootloader': { + ("03eb", "2067"), # QMK HID + ("16c0", "0478") # PJRC halfkay + } +} + # Common format strings DATE_FORMAT = '%Y-%m-%d' DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z' diff --git a/lib/python/qmk/flashers.py b/lib/python/qmk/flashers.py new file mode 100644 index 0000000000..a9cf726b44 --- /dev/null +++ b/lib/python/qmk/flashers.py @@ -0,0 +1,203 @@ +import shutil +import time +import os +import signal + +import usb.core + +from qmk.constants import BOOTLOADER_VIDS_PIDS +from milc import cli + +# yapf: disable +_PID_TO_MCU = { + '2fef': 'atmega16u2', + '2ff0': 'atmega32u2', + '2ff3': 'atmega16u4', + '2ff4': 'atmega32u4', + '2ff9': 'at90usb64', + '2ffa': 'at90usb162', + '2ffb': 'at90usb128' +} + +AVRDUDE_MCU = { + 'atmega32a': 'm32', + 'atmega328p': 'm328p', + 'atmega328': 'm328', +} +# yapf: enable + + +class DelayedKeyboardInterrupt: + # Custom interrupt handler to delay the processing of Ctrl-C + # https://stackoverflow.com/a/21919644 + def __enter__(self): + self.signal_received = False + self.old_handler = signal.signal(signal.SIGINT, self.handler) + + def handler(self, sig, frame): + self.signal_received = (sig, frame) + + def __exit__(self, type, value, traceback): + signal.signal(signal.SIGINT, self.old_handler) + if self.signal_received: + self.old_handler(*self.signal_received) + + +# TODO: Make this more generic, so cli/doctor/check.py and flashers.py can share the code +def _check_dfu_programmer_version(): + # Return True if version is higher than 0.7.0: supports '--force' + check = cli.run(['dfu-programmer', '--version'], combined_output=True, timeout=5) + first_line = check.stdout.split('\n')[0] + version_number = first_line.split()[1] + maj, min_, bug = version_number.split('.') + if int(maj) >= 0 and int(min_) >= 7: + return True + else: + return False + + +def _find_bootloader(): + # To avoid running forever in the background, only look for bootloaders for 10min + start_time = time.time() + while time.time() - start_time < 600: + for bl in BOOTLOADER_VIDS_PIDS: + for vid, pid in BOOTLOADER_VIDS_PIDS[bl]: + vid_hex = int(f'0x{vid}', 0) + pid_hex = int(f'0x{pid}', 0) + with DelayedKeyboardInterrupt(): + # PyUSB does not like to be interrupted by Ctrl-C + # therefore we catch the interrupt with a custom handler + # and only process it once pyusb finished + dev = usb.core.find(idVendor=vid_hex, idProduct=pid_hex) + if dev: + if bl == 'atmel-dfu': + details = _PID_TO_MCU[pid] + elif bl == 'caterina': + details = (vid_hex, pid_hex) + elif bl == 'hid-bootloader': + if vid == '16c0' and pid == '0478': + details = 'halfkay' + else: + details = 'qmk-hid' + elif bl == 'stm32-dfu' or bl == 'apm32-dfu' or bl == 'gd32v-dfu' or bl == 'kiibohd': + details = (vid, pid) + else: + details = None + return (bl, details) + time.sleep(0.1) + return (None, None) + + +def _find_serial_port(vid, pid): + if 'windows' in cli.platform.lower(): + from serial.tools.list_ports_windows import comports + platform = 'windows' + else: + from serial.tools.list_ports_posix import comports + platform = 'posix' + + start_time = time.time() + # Caterina times out after 8 seconds + while time.time() - start_time < 8: + for port in comports(): + port, desc, hwid = port + if f'{vid:04x}:{pid:04x}' in hwid.casefold(): + if platform == 'windows': + time.sleep(1) + return port + else: + start_time = time.time() + # Wait until the port becomes writable before returning + while time.time() - start_time < 8: + if os.access(port, os.W_OK): + return port + else: + time.sleep(0.5) + return None + return None + + +def _flash_caterina(details, file): + port = _find_serial_port(details[0], details[1]) + if port: + cli.run(['avrdude', '-p', 'atmega32u4', '-c', 'avr109', '-U', f'flash:w:{file}:i', '-P', port], capture_output=False) + return False + else: + return True + + +def _flash_atmel_dfu(mcu, file): + force = '--force' if _check_dfu_programmer_version() else '' + cli.run(['dfu-programmer', mcu, 'erase', force], capture_output=False) + cli.run(['dfu-programmer', mcu, 'flash', force, file], capture_output=False) + cli.run(['dfu-programmer', mcu, 'reset'], capture_output=False) + + +def _flash_hid_bootloader(mcu, details, file): + if details == 'halfkay': + if shutil.which('teensy-loader-cli'): + cmd = 'teensy-loader-cli' + elif shutil.which('teensy_loader_cli'): + cmd = 'teensy_loader_cli' + + # Use 'hid_bootloader_cli' for QMK HID and as a fallback for HalfKay + if not cmd: + if shutil.which('hid_bootloader_cli'): + cmd = 'hid_bootloader_cli' + else: + return True + + cli.run([cmd, f'-mmcu={mcu}', '-w', '-v', file], capture_output=False) + + +def _flash_dfu_util(details, file): + # STM32duino + if details[0] == '1eaf' and details[1] == '0003': + cli.run(['dfu-util', '-a', '2', '-d', f'{details[0]}:{details[1]}', '-R', '-D', file], capture_output=False) + # kiibohd + elif details[0] == '1c11' and details[1] == 'b007': + cli.run(['dfu-util', '-a', '0', '-d', f'{details[0]}:{details[1]}', '-D', file], capture_output=False) + # STM32, APM32, or GD32V DFU + else: + cli.run(['dfu-util', '-a', '0', '-d', f'{details[0]}:{details[1]}', '-s', '0x08000000:leave', '-D', file], capture_output=False) + + +def _flash_isp(mcu, programmer, file): + programmer = 'usbasp' if programmer == 'usbasploader' else 'usbtiny' + # Check if the provide mcu has an avrdude-specific name, otherwise pass on what the user provided + mcu = AVRDUDE_MCU.get(mcu, mcu) + cli.run(['avrdude', '-p', mcu, '-c', programmer, '-U', f'flash:w:{file}:i'], capture_output=False) + + +def _flash_mdloader(file): + cli.run(['mdloader', '--first', '--download', file, '--restart'], capture_output=False) + + +def flasher(mcu, file): + bl, details = _find_bootloader() + # Add a small sleep to avoid race conditions + time.sleep(1) + if bl == 'atmel-dfu': + _flash_atmel_dfu(details, file.name) + elif bl == 'caterina': + if _flash_caterina(details, file.name): + return (True, "The Caterina bootloader was found but is not writable. Check 'qmk doctor' output for advice.") + elif bl == 'hid-bootloader': + if mcu: + if _flash_hid_bootloader(mcu, details, file.name): + return (True, "Please make sure 'teensy_loader_cli' or 'hid_bootloader_cli' is available on your system.") + else: + return (True, "Specifying the MCU with '-m' is necessary for HalfKay/HID bootloaders!") + elif bl == 'stm32-dfu' or bl == 'apm32-dfu' or bl == 'gd32v-dfu' or bl == 'kiibohd': + _flash_dfu_util(details, file.name) + elif bl == 'usbasploader' or bl == 'usbtinyisp': + if mcu: + _flash_isp(mcu, bl, file.name) + else: + return (True, "Specifying the MCU with '-m' is necessary for ISP flashing!") + elif bl == 'md-boot': + _flash_mdloader(file.name) + else: + return (True, "Known bootloader found but flashing not currently supported!") + + return (False, None) -- cgit v1.2.3 From 7ee55b17540f1ec1dfae95e870cf29d74dcea8ad Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Sat, 20 Aug 2022 17:59:17 +0300 Subject: Fix PID value for the Keyboardio Atreus 2 bootloader (#18116) Copy the correct PID from `util/udev/50-qmk.rules`. --- lib/python/qmk/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 10da5e7e8e..622199e46e 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -88,7 +88,7 @@ BOOTLOADER_VIDS_PIDS = { 'md-boot': {("03eb", "6124")}, 'caterina': { # pid.codes shared PID - ("1209", "9203"), # Keyboardio Atreus 2 Bootloader + ("1209", "2302"), # Keyboardio Atreus 2 Bootloader # Spark Fun Electronics ("1b4f", "9203"), # Pro Micro 3V3/8MHz ("1b4f", "9205"), # Pro Micro 5V/16MHz -- cgit v1.2.3 From 3d8c6246980cc52a150fb568503d491edcb8c696 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 24 Aug 2022 20:25:16 +0100 Subject: Update invalid pip install flag (#18146) --- lib/python/qmk/cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 02c6d1cbf4..1e15c28758 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -91,7 +91,7 @@ def _install_deps(requirements): elif not os.access(sys.prefix, os.W_OK): # We can't write to sys.prefix, attempt to install locally - command.append('--local') + command.append('--user') return _run_cmd(*command, '-r', requirements) -- cgit v1.2.3 From 24720400a854e5954c75baef67161469bc6a3e8e Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 26 Aug 2022 12:19:34 +1000 Subject: Update LUFA submodule (#18168) --- lib/python/qmk/cli/generate/dfu_header.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py index e873117387..aa0252ca86 100644 --- a/lib/python/qmk/cli/generate/dfu_header.py +++ b/lib/python/qmk/cli/generate/dfu_header.py @@ -33,8 +33,8 @@ def generate_dfu_header(cli): kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard)) keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] - keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}') - keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader') + keyboard_h_lines.append(f'#define MANUFACTURER "{kb_info_json["manufacturer"]}"') + keyboard_h_lines.append(f'#define PRODUCT "{kb_info_json["keyboard_name"]} Bootloader"') # Optional if 'qmk_lufa_bootloader.esc_output' in kb_info_json: -- cgit v1.2.3 From 6f804f76b4b1ce13245ea061df4bcb081b322f43 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 31 Aug 2022 15:20:00 +1000 Subject: `qmk lint`: fix TypeError (#18226) --- lib/python/qmk/cli/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index 38b6457c43..6b077c250f 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -87,7 +87,7 @@ def keymap_check(kb, km): return ok # Additional checks - invalid_files = git_get_ignored_files(keymap_path.parent) + invalid_files = git_get_ignored_files(keymap_path.parent.as_posix()) for file in invalid_files: cli.log.error(f'{kb}/{km}: The file "{file}" should not exist!') ok = False -- cgit v1.2.3 From 725df1278bcb7d4b2c2744fd23a850c186f964b9 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 9 Sep 2022 21:22:55 +0100 Subject: Fix '_RIGHT' matrix pins lint error (#18320) --- lib/python/qmk/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index c95b55916c..7460d84ad3 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -370,7 +370,7 @@ def _extract_split_right_pins(info_data, config_c): direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1] if row_pins or col_pins or direct_pins: - if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data: + if info_data.get('split', {}).get('matrix_pins', {}).get('right', None): _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') if 'split' not in info_data: -- cgit v1.2.3 From cf88d956130ef847677cf4defd1f21d4e72c677f Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Fri, 16 Sep 2022 18:19:10 +1000 Subject: Add ability to remove temporary files during multibuild. (#18381) --- lib/python/qmk/cli/multibuild.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/cli/multibuild.py b/lib/python/qmk/cli/multibuild.py index a1db669ca8..5e0f0b5188 100755 --- a/lib/python/qmk/cli/multibuild.py +++ b/lib/python/qmk/cli/multibuild.py @@ -28,6 +28,7 @@ def _is_split(keyboard_name): return True if 'SPLIT_KEYBOARD' in rules_mk and rules_mk['SPLIT_KEYBOARD'].lower() == 'yes' else False +@cli.argument('-t', '--no-temp', arg_only=True, action='store_true', help="Remove temporary files during build.") @cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.") @cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.") @cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on the supplied value in rules.mk. Supported format is 'SPLIT_KEYBOARD=yes'. May be passed multiple times.") @@ -77,11 +78,26 @@ all: {keyboard_safe}_binary || {{ grep '\[WARNINGS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" >/dev/null 2>&1 && printf "Build %-64s \e[1;33m[WARNINGS]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}" ; }} \\ || printf "Build %-64s \e[1;32m[OK]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}" @rm -f "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" || true - """# noqa ) # yapf: enable + if cli.args.no_temp: + # yapf: disable + f.write( + f"""\ + @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.elf" 2>/dev/null || true + @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.map" 2>/dev/null || true + @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.hex" 2>/dev/null || true + @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.bin" 2>/dev/null || true + @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.uf2" 2>/dev/null || true + @rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}" || true + @rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}_{cli.args.keymap}" || true +"""# noqa + ) + # yapf: enable + f.write('\n') + cli.run([make_cmd, *get_make_parallel_args(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL) # Check for failures -- cgit v1.2.3 From 4dec07741b4bfb1b9858f5d862589754b28dec5c Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 12 Oct 2022 22:40:53 +0100 Subject: Remove unused LED_INDICATORS constant (#18686) --- lib/python/qmk/constants.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'lib/python/qmk') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 622199e46e..8a13029a8a 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -121,13 +121,6 @@ TIME_FORMAT = '%H:%M:%S' COL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijilmnopqrstuvwxyz' ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop' -# Mapping between info.json and config.h keys -LED_INDICATORS = { - 'caps_lock': 'LED_CAPS_LOCK_PIN', - 'num_lock': 'LED_NUM_LOCK_PIN', - 'scroll_lock': 'LED_SCROLL_LOCK_PIN', -} - # Constants that should match their counterparts in make BUILD_DIR = environ.get('BUILD_DIR', '.build') KEYBOARD_OUTPUT_PREFIX = f'{BUILD_DIR}/obj_' -- cgit v1.2.3