From 5267329caa6ce08a5497d774bae0c8f386220a16 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 4 Jan 2024 05:47:52 +0000 Subject: Ensure LED config is extracted when feature is disabled (#22809) * Ensure LED config is extracted when feature is disabled * Only attempt LED search if dd led config is missing --- lib/python/qmk/info.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index d51741ecb6..e960bf20ee 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -684,26 +684,26 @@ def _extract_led_config(info_data, keyboard): rows = info_data['matrix_size']['rows'] # Determine what feature owns g_led_config - features = info_data.get("features", {}) feature = None - if features.get("rgb_matrix", False): - feature = "rgb_matrix" - elif features.get("led_matrix", False): - feature = "led_matrix" + for feat in ['rgb_matrix', 'led_matrix']: + if info_data.get('features', {}).get(feat, False) or feat in info_data: + feature = feat if feature: - # Process - for file in find_keyboard_c(keyboard): - try: - ret = find_led_config(file, cols, rows) - if ret: - info_data[feature] = info_data.get(feature, {}) - info_data[feature]["layout"] = ret - except Exception as e: - _log_warning(info_data, f'led_config: {file.name}: {e}') - - if info_data[feature].get("layout", None) and not info_data[feature].get("led_count", None): - info_data[feature]["led_count"] = len(info_data[feature]["layout"]) + # Only attempt search if dd led config is missing + if 'layout' not in info_data.get(feature, {}): + # Process + for file in find_keyboard_c(keyboard): + try: + ret = find_led_config(file, cols, rows) + if ret: + info_data[feature] = info_data.get(feature, {}) + info_data[feature]['layout'] = ret + except Exception as e: + _log_warning(info_data, f'led_config: {file.name}: {e}') + + if info_data[feature].get('layout', None) and not info_data[feature].get('led_count', None): + info_data[feature]['led_count'] = len(info_data[feature]['layout']) return info_data -- cgit v1.2.3 From 71257e21e68d350c1c789a79440be8266be19788 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 6 Jan 2024 13:16:23 +0000 Subject: Generate true/false for _DEFAULT_ON options (#22829) --- lib/python/qmk/cli/generate/config_h.py | 2 ++ lib/python/qmk/info.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 00fb1d9585..fc681300a3 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -108,6 +108,8 @@ def generate_config_items(kb_info_json, config_h_lines): elif key_type.startswith('array'): config_h_lines.append(generate_define(config_key, f'{{ {", ".join(map(str, config_value))} }}')) elif key_type == 'bool': + config_h_lines.append(generate_define(config_key, 'true' if config_value else 'false')) + elif key_type == 'flag': if config_value: config_h_lines.append(generate_define(config_key)) elif key_type == 'mapping': diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index e960bf20ee..5500ecdd19 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -513,7 +513,7 @@ def _config_to_json(key_type, config_value): else: return list(map(str.strip, config_value.split(','))) - elif key_type == 'bool': + elif key_type in ['bool', 'flag']: if isinstance(config_value, bool): return config_value return config_value in true_values -- cgit v1.2.3 From 455cd65e80563469712fe56de15666e06b945636 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 9 Jan 2024 09:59:36 +0000 Subject: Prevent `qmk migrate` processing unparsed info.json values (#22374) --- lib/python/qmk/cli/migrate.py | 5 ++++- lib/python/qmk/info.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/migrate.py b/lib/python/qmk/cli/migrate.py index c1b1ad1ea9..0bab5c1949 100644 --- a/lib/python/qmk/cli/migrate.py +++ b/lib/python/qmk/cli/migrate.py @@ -47,9 +47,12 @@ def migrate(cli): files = _candidate_files(cli.args.keyboard) # Filter down keys if requested - keys = info_map.keys() + keys = list(filter(lambda key: info_map[key].get("to_json", True), info_map.keys())) if cli.args.filter: keys = list(set(keys) & set(cli.args.filter)) + rejected = set(cli.args.filter) - set(keys) + for key in rejected: + cli.log.info(f'{{fg_yellow}}Skipping {key} as migration not possible...') cli.log.info(f'{{fg_green}}Migrating keyboard {{fg_cyan}}{cli.args.keyboard}{{fg_green}}.{{fg_reset}}') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 5500ecdd19..b018ba96fd 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -501,6 +501,9 @@ def _config_to_json(key_type, config_value): """Convert config value using spec """ if key_type.startswith('array'): + if key_type.count('.') > 1: + raise Exception(f"Conversion of {key_type} not possible") + if '.' in key_type: key_type, array_type = key_type.split('.', 1) else: -- cgit v1.2.3 From 1bebaa310abb637cbca146b6d7859f3efe503cfd Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 14 Jan 2024 21:33:58 +1100 Subject: CLI: Allow generation of both LED and RGB Matrix config (#22896) --- lib/python/qmk/cli/generate/keyboard_c.py | 25 ++++++++++++-------- lib/python/qmk/info.py | 38 ++++++++++++++----------------- 2 files changed, 32 insertions(+), 31 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/generate/keyboard_c.py b/lib/python/qmk/cli/generate/keyboard_c.py index f010341613..5a6c967486 100755 --- a/lib/python/qmk/cli/generate/keyboard_c.py +++ b/lib/python/qmk/cli/generate/keyboard_c.py @@ -9,21 +9,25 @@ from qmk.path import normpath from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE -def _gen_led_config(info_data): +def _gen_led_configs(info_data): + lines = [] + + if 'layout' in info_data.get('rgb_matrix', {}): + lines.extend(_gen_led_config(info_data, 'rgb_matrix')) + + if 'layout' in info_data.get('led_matrix', {}): + lines.extend(_gen_led_config(info_data, 'led_matrix')) + + return lines + + +def _gen_led_config(info_data, config_type): """Convert info.json content to g_led_config """ cols = info_data['matrix_size']['cols'] rows = info_data['matrix_size']['rows'] - config_type = None - if 'layout' in info_data.get('rgb_matrix', {}): - config_type = 'rgb_matrix' - elif 'layout' in info_data.get('led_matrix', {}): - config_type = 'led_matrix' - lines = [] - if not config_type: - return lines matrix = [['NO_LED'] * cols for _ in range(rows)] pos = [] @@ -53,6 +57,7 @@ def _gen_led_config(info_data): lines.append(f' {{ {", ".join(flags)} }},') lines.append('};') lines.append('#endif') + lines.append('') return lines @@ -98,7 +103,7 @@ def generate_keyboard_c(cli): # Build the layouts.h file. keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', ''] - keyboard_h_lines.extend(_gen_led_config(kb_info_json)) + keyboard_h_lines.extend(_gen_led_configs(kb_info_json)) keyboard_h_lines.extend(_gen_matrix_mask(kb_info_json)) # Show the results diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index b018ba96fd..efe4dac34a 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -686,27 +686,23 @@ def _extract_led_config(info_data, keyboard): cols = info_data['matrix_size']['cols'] rows = info_data['matrix_size']['rows'] - # Determine what feature owns g_led_config - feature = None - for feat in ['rgb_matrix', 'led_matrix']: - if info_data.get('features', {}).get(feat, False) or feat in info_data: - feature = feat - - if feature: - # Only attempt search if dd led config is missing - if 'layout' not in info_data.get(feature, {}): - # Process - for file in find_keyboard_c(keyboard): - try: - ret = find_led_config(file, cols, rows) - if ret: - info_data[feature] = info_data.get(feature, {}) - info_data[feature]['layout'] = ret - except Exception as e: - _log_warning(info_data, f'led_config: {file.name}: {e}') - - if info_data[feature].get('layout', None) and not info_data[feature].get('led_count', None): - info_data[feature]['led_count'] = len(info_data[feature]['layout']) + for feature in ['rgb_matrix', 'led_matrix']: + if info_data.get('features', {}).get(feature, False) or feature in info_data: + + # Only attempt search if dd led config is missing + if 'layout' not in info_data.get(feature, {}): + # Process + for file in find_keyboard_c(keyboard): + try: + ret = find_led_config(file, cols, rows) + if ret: + info_data[feature] = info_data.get(feature, {}) + info_data[feature]['layout'] = ret + except Exception as e: + _log_warning(info_data, f'led_config: {file.name}: {e}') + + if info_data[feature].get('layout', None) and not info_data[feature].get('led_count', None): + info_data[feature]['led_count'] = len(info_data[feature]['layout']) return info_data -- cgit v1.2.3 From 2b0965944d9065daa65cd25540cf2dd007f23eda Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 16 Jan 2024 17:13:58 +1100 Subject: `qmk format-json`: Force Unix line endings and ensure LF at EOF (#22901) --- lib/python/qmk/cli/format/json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/format/json.py b/lib/python/qmk/cli/format/json.py index 283513254c..87a3837d10 100755 --- a/lib/python/qmk/cli/format/json.py +++ b/lib/python/qmk/cli/format/json.py @@ -92,8 +92,8 @@ def format_json(cli): output = json.dumps(json_data, cls=json_encoder, sort_keys=True) if cli.args.inplace: - with open(cli.args.json_file, 'w+', encoding='utf-8') as outfile: - outfile.write(output) + with open(cli.args.json_file, 'w+', encoding='utf-8', newline='\n') as outfile: + outfile.write(output + '\n') # Display the results if print was set # We don't operate in-place by default, so also display to stdout -- cgit v1.2.3 From 62d19fc2ac42b39a4f32683c7e8c5ad95b566b24 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 20 Jan 2024 08:11:17 +1100 Subject: Copy `compile_commands.json` to userspace, if in use. (#22925) --- lib/python/qmk/build_targets.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/build_targets.py b/lib/python/qmk/build_targets.py index 1ab489cec3..80f587bcc0 100644 --- a/lib/python/qmk/build_targets.py +++ b/lib/python/qmk/build_targets.py @@ -6,7 +6,7 @@ from typing import List, Union from pathlib import Path from dotty_dict import dotty, Dotty from milc import cli -from qmk.constants import QMK_FIRMWARE, INTERMEDIATE_OUTPUT_PREFIX +from qmk.constants import QMK_FIRMWARE, INTERMEDIATE_OUTPUT_PREFIX, HAS_QMK_USERSPACE, QMK_USERSPACE from qmk.commands import find_make, get_make_parallel_args, parse_configurator_json from qmk.keyboard import keyboard_folder from qmk.info import keymap_json @@ -118,7 +118,10 @@ class BuildTarget: self.prepare_build(build_target=build_target, **env_vars) command = self.compile_command(build_target=build_target, dry_run=True, **env_vars) from qmk.cli.generate.compilation_database import write_compilation_database # Lazy load due to circular references - write_compilation_database(command=command, output_path=QMK_FIRMWARE / 'compile_commands.json', skip_clean=skip_clean, **env_vars) + output_path = QMK_FIRMWARE / 'compile_commands.json' + write_compilation_database(command=command, output_path=output_path, skip_clean=skip_clean, **env_vars) + if output_path.exists() and HAS_QMK_USERSPACE: + shutil.copy(str(output_path), str(QMK_USERSPACE / 'compile_commands.json')) def compile(self, build_target: str = None, dry_run: bool = False, **env_vars) -> None: if self._clean or self._compiledb: -- cgit v1.2.3 From 58721a433b81790368386cf117347e5bd31fa961 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 22 Jan 2024 11:35:51 +0000 Subject: Move layout macro OOB checks to lint (#22610) --- lib/python/qmk/cli/generate/keyboard_h.py | 11 ++--------- lib/python/qmk/info.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 12 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/generate/keyboard_h.py b/lib/python/qmk/cli/generate/keyboard_h.py index b9e89032b9..5863a0983a 100755 --- a/lib/python/qmk/cli/generate/keyboard_h.py +++ b/lib/python/qmk/cli/generate/keyboard_h.py @@ -33,18 +33,11 @@ def _generate_layouts(keyboard, kb_info_json): layout_keys = [] layout_matrix = [['KC_NO'] * col_num for _ in range(row_num)] - for index, key_data in enumerate(layout_data['layout']): + for key_data in layout_data['layout']: row, col = key_data['matrix'] identifier = f'k{ROW_LETTERS[row]}{COL_LETTERS[col]}' - if row >= row_num or col >= col_num: - key_name = key_data.get('label', identifier) - if row >= row_num: - cli.log.error(f'{keyboard}/{layout_name}: Matrix row for key {index} ({key_name}) is {row} but must be less than {row_num}') - - if col >= col_num: - cli.log.error(f'{keyboard}/{layout_name}: Matrix column for key {index} ({key_name}) is {col} but must be less than {col_num}') - + cli.log.error(f'Skipping layouts due to {layout_name} containing invalid matrix values') return [] layout_matrix[row][col] = identifier diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index d51741ecb6..d42ba5c660 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -7,7 +7,7 @@ from dotty_dict import dotty from milc import cli -from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS +from qmk.constants import COL_LETTERS, ROW_LETTERS, CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config from qmk.json_schema import deep_update, json_load, validate from qmk.keyboard import config_h, rules_mk @@ -78,9 +78,11 @@ def _find_invalid_encoder_index(info_data): return ret -def _additional_validation(keyboard, info_data): +def _validate_layouts(keyboard, info_data): """Non schema checks """ + col_num = info_data.get('matrix_size', {}).get('cols', 0) + row_num = info_data.get('matrix_size', {}).get('rows', 0) layouts = info_data.get('layouts', {}) layout_aliases = info_data.get('layout_aliases', {}) community_layouts = info_data.get('community_layouts', []) @@ -90,6 +92,16 @@ def _additional_validation(keyboard, info_data): if len(layouts) == 0 or all(not layout.get('json_layout', False) for layout in layouts.values()): _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in info.json.') + # Make sure all matrix values are in bounds + for layout_name, layout_data in layouts.items(): + for index, key_data in enumerate(layout_data['layout']): + row, col = key_data['matrix'] + key_name = key_data.get('label', f'k{ROW_LETTERS[row]}{COL_LETTERS[col]}') + if row >= row_num: + _log_error(info_data, f'{layout_name}: Matrix row for key {index} ({key_name}) is {row} but must be less than {row_num}') + if col >= col_num: + _log_error(info_data, f'{layout_name}: Matrix column for key {index} ({key_name}) is {col} but must be less than {col_num}') + # Warn if physical positions are offset (at least one key should be at x=0, and at least one key at y=0) for layout_name, layout_data in layouts.items(): offset_x = min([_get_key_left_position(k) for k in layout_data['layout']]) @@ -122,12 +134,20 @@ def _additional_validation(keyboard, info_data): if layout_name not in layouts and layout_name not in layout_aliases: _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) + +def _validate_keycodes(keyboard, info_data): + """Non schema checks + """ # keycodes with length > 7 must have short forms for visualisation purposes for decl in info_data.get('keycodes', []): if len(decl["key"]) > 7: if not decl.get("aliases", []): _log_error(info_data, f'Keycode {decl["key"]} has no short form alias') + +def _validate_encoders(keyboard, info_data): + """Non schema checks + """ # encoder IDs in layouts must be in range and not duplicated found = _find_invalid_encoder_index(info_data) for layout_name, encoder_index, reason in found: @@ -141,7 +161,10 @@ def _validate(keyboard, info_data): try: validate(info_data, 'qmk.api.keyboard.v1') - _additional_validation(keyboard, info_data) + # Additional validation + _validate_layouts(keyboard, info_data) + _validate_keycodes(keyboard, info_data) + _validate_encoders(keyboard, info_data) except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) -- cgit v1.2.3 From 3a13c2120a9e3d897e05923c68efa542f3b3efc7 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 1 Feb 2024 15:45:58 +0000 Subject: Ensure LTO is enabled as a `info.json` build config option (#22932) * feature.lto -> build.lto * keymaps too --- lib/python/qmk/info.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/python') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 4ef12bea71..13588abdb8 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -233,6 +233,9 @@ def _extract_features(info_data, rules): key = '_'.join(key.split('_')[:-1]).lower() value = True if value.lower() in true_values else False if value.lower() in false_values else value + if key in ['lto']: + continue + if 'config_h_features' not in info_data: info_data['config_h_features'] = {} -- cgit v1.2.3 From e7b84e1cf6d864b1df41e7a81c576baa9d7ca30a Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 8 Feb 2024 06:34:48 +0000 Subject: Flag invalid keyboard features during lint (#22832) --- lib/python/qmk/cli/lint.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index a7c85b5643..7ebb0cf9c4 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -13,6 +13,7 @@ from qmk.git import git_get_ignored_files from qmk.c_parse import c_source_files CHIBIOS_CONF_CHECKS = ['chconf.h', 'halconf.h', 'mcuconf.h', 'board.h'] +INVALID_KB_FEATURES = set(['encoder_map', 'dip_switch_map', 'combo', 'tap_dance', 'via']) def _list_defaultish_keymaps(kb): @@ -69,6 +70,17 @@ def _handle_json_errors(kb, info): return ok +def _handle_invalid_features(kb, info): + """Check for features that should never be enabled at the keyboard level + """ + ok = True + features = set(info.get('features', [])) + for found in features & INVALID_KB_FEATURES: + ok = False + cli.log.error(f'{kb}: Invalid keyboard level feature detected - {found}') + return ok + + def _chibios_conf_includenext_check(target): """Check the ChibiOS conf.h for the correct inclusion of the next conf.h """ @@ -154,6 +166,9 @@ def keyboard_check(kb): ok = False # Additional checks + if not _handle_invalid_features(kb, kb_info): + ok = False + rules_mk_assignment_errors = _rules_mk_assignment_only(kb) if rules_mk_assignment_errors: ok = False -- cgit v1.2.3 From 98a68b68a400f7b3821db1d77375a592b34cc8d6 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 12 Feb 2024 10:02:44 +0000 Subject: Fix git-submodule running in wrong location (#23059) --- lib/python/qmk/cli/git/submodule.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/git/submodule.py b/lib/python/qmk/cli/git/submodule.py index ef116ea124..1cbfd74e88 100644 --- a/lib/python/qmk/cli/git/submodule.py +++ b/lib/python/qmk/cli/git/submodule.py @@ -1,8 +1,8 @@ import shutil +from pathlib import Path from milc import cli -from qmk.path import normpath from qmk import submodules REMOVE_DIRS = [ @@ -40,12 +40,12 @@ def git_submodule(cli): remove_dirs = REMOVE_DIRS if cli.config.git_submodule.force: # Also trash everything that isnt marked as "safe" - for path in normpath('lib').iterdir(): + for path in Path('lib').iterdir(): if not any(ignore in path.as_posix() for ignore in IGNORE_DIRS): remove_dirs.append(path) - for folder in map(normpath, remove_dirs): - if normpath(folder).is_dir(): + for folder in map(Path, remove_dirs): + if folder.is_dir(): print(f"Removing '{folder}'") shutil.rmtree(folder) -- cgit v1.2.3 From cf162f90fe64790961afafb6a1de21833b0fa6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez?= <58857054+elpekenin@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:40:21 +0100 Subject: [Refactor] Break `QGFImageFile`'s `_save` function into smaller pieces (#21124) --- lib/python/qmk/painter_qgf.py | 304 +++++++++++++++++++++++------------------- 1 file changed, 168 insertions(+), 136 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/painter_qgf.py b/lib/python/qmk/painter_qgf.py index 2b8edfb04d..cc4697f1c6 100644 --- a/lib/python/qmk/painter_qgf.py +++ b/lib/python/qmk/painter_qgf.py @@ -1,9 +1,11 @@ # Copyright 2021 Nick Brassel (@tzarc) +# Copyright 2023 Pablo Martinez (@elpekenin) # SPDX-License-Identifier: GPL-2.0-or-later # Quantum Graphics File "QGF" Image File Format. # See https://docs.qmk.fm/#/quantum_painter_qgf for more information. +import functools from colorsys import rgb_to_hsv from types import FunctionType from PIL import Image, ImageFile, ImageChops @@ -15,6 +17,12 @@ def o24(i): return o16(i & 0xFFFF) + o8((i & 0xFF0000) >> 16) +# Helper to convert from RGB888 to the QMK "dialect" of HSV888 +def rgb888_to_qmk_hsv888(e): + hsv = rgb_to_hsv(e[0] / 255.0, e[1] / 255.0, e[2] / 255.0) + return (int(hsv[0] * 255.0), int(hsv[1] * 255.0), int(hsv[2] * 255.0)) + + ######################################################################################################################## @@ -60,6 +68,14 @@ class QGFGraphicsDescriptor: + o16(self.frame_count) # frame count ) + @property + def image_size(self): + return self.image_width, self.image_height + + @image_size.setter + def image_size(self, size): + self.image_width, self.image_height = size + ######################################################################################################################## @@ -180,6 +196,14 @@ class QGFFrameDeltaDescriptorV1: + o16(self.bottom) # bottom ) + @property + def bbox(self): + return self.left, self.top, self.right, self.bottom + + @bbox.setter + def bbox(self, bbox): + self.left, self.top, self.right, self.bottom = bbox + ######################################################################################################################## @@ -221,42 +245,159 @@ def _accept(prefix): return False -def _save(im, fp, filename): +def _for_all_frames(x: FunctionType, /, images): + frame_num = 0 + last_frame = None + for frame in images: + # Get number of of frames in this image + nfr = getattr(frame, "n_frames", 1) + for idx in range(nfr): + frame.seek(idx) + frame.load() + copy = frame.copy().convert("RGB") + x(frame_num, copy, last_frame) + last_frame = copy + frame_num += 1 + + +def _compress_image(frame, last_frame, *, use_rle, use_deltas, format_, **_kwargs): + # Convert the original frame so we can do comparisons + converted = qmk.painter.convert_requested_format(frame, format_) + graphic_data = qmk.painter.convert_image_bytes(converted, format_) + + # Convert the raw data to RLE-encoded if requested + raw_data = graphic_data[1] + if use_rle: + rle_data = qmk.painter.compress_bytes_qmk_rle(graphic_data[1]) + use_raw_this_frame = not use_rle or len(raw_data) <= len(rle_data) + image_data = raw_data if use_raw_this_frame else rle_data + + # Work out if a delta frame is smaller than injecting it directly + use_delta_this_frame = False + bbox = None + if use_deltas and last_frame is not None: + # If we want to use deltas, then find the difference + diff = ImageChops.difference(frame, last_frame) + + # Get the bounding box of those differences + bbox = diff.getbbox() + + # If we have a valid bounding box... + if bbox: + # ...create the delta frame by cropping the original. + delta_frame = frame.crop(bbox) + + # Convert the delta frame to the requested format + delta_converted = qmk.painter.convert_requested_format(delta_frame, format_) + delta_graphic_data = qmk.painter.convert_image_bytes(delta_converted, format_) + + # Work out how large the delta frame is going to be with compression etc. + delta_raw_data = delta_graphic_data[1] + if use_rle: + delta_rle_data = qmk.painter.compress_bytes_qmk_rle(delta_graphic_data[1]) + delta_use_raw_this_frame = not use_rle or len(delta_raw_data) <= len(delta_rle_data) + delta_image_data = delta_raw_data if delta_use_raw_this_frame else delta_rle_data + + # If the size of the delta frame (plus delta descriptor) is smaller than the original, use that instead + # This ensures that if a non-delta is overall smaller in size, we use that in preference due to flash + # sizing constraints. + if (len(delta_image_data) + QGFFrameDeltaDescriptorV1.length) < len(image_data): + # Copy across all the delta equivalents so that the rest of the processing acts on those + graphic_data = delta_graphic_data + raw_data = delta_raw_data + rle_data = delta_rle_data + use_raw_this_frame = delta_use_raw_this_frame + image_data = delta_image_data + use_delta_this_frame = True + + # Default to whole image + bbox = bbox or [0, 0, *frame.size] + # Fix sze (as per #20296), we need to cast first as tuples are inmutable + bbox = list(bbox) + bbox[2] -= 1 + bbox[3] -= 1 + + return { + "bbox": bbox, + "graphic_data": graphic_data, + "image_data": image_data, + "use_delta_this_frame": use_delta_this_frame, + "use_raw_this_frame": use_raw_this_frame, + } + + +# Helper function to save each frame to the output file +def _write_frame(idx, frame, last_frame, *, fp, frame_offsets, **kwargs): + # Not an argument of the function as it would consume from **kwargs + format_ = kwargs["format_"] + + # (potentially) Apply RLE and/or delta, and work out output image's information + outputs = _compress_image(frame, last_frame, **kwargs) + bbox = outputs["bbox"] + graphic_data = outputs["graphic_data"] + image_data = outputs["image_data"] + use_delta_this_frame = outputs["use_delta_this_frame"] + use_raw_this_frame = outputs["use_raw_this_frame"] + + # Write out the frame descriptor + frame_offsets.frame_offsets[idx] = fp.tell() + vprint(f'{f"Frame {idx:3d} base":26s} {fp.tell():5d}d / {fp.tell():04X}h') + frame_descriptor = QGFFrameDescriptorV1() + frame_descriptor.is_delta = use_delta_this_frame + frame_descriptor.is_transparent = False + frame_descriptor.format = format_['image_format_byte'] + frame_descriptor.compression = 0x00 if use_raw_this_frame else 0x01 # See qp.h, painter_compression_t + frame_descriptor.delay = frame.info.get('duration', 1000) # If we're not an animation, just pretend we're delaying for 1000ms + frame_descriptor.write(fp) + + # Write out the palette if required + if format_['has_palette']: + palette = graphic_data[0] + palette_descriptor = QGFFramePaletteDescriptorV1() + + # Convert all palette entries to HSV888 and write to the output + palette_descriptor.palette_entries = list(map(rgb888_to_qmk_hsv888, palette)) + vprint(f'{f"Frame {idx:3d} palette":26s} {fp.tell():5d}d / {fp.tell():04X}h') + palette_descriptor.write(fp) + + # Write out the delta info if required + if use_delta_this_frame: + # Set up the rendering location of where the delta frame should be situated + delta_descriptor = QGFFrameDeltaDescriptorV1() + delta_descriptor.bbox = bbox + + # Write the delta frame to the output + vprint(f'{f"Frame {idx:3d} delta":26s} {fp.tell():5d}d / {fp.tell():04X}h') + delta_descriptor.write(fp) + + # Write out the data for this frame to the output + data_descriptor = QGFFrameDataDescriptorV1() + data_descriptor.data = image_data + vprint(f'{f"Frame {idx:3d} data":26s} {fp.tell():5d}d / {fp.tell():04X}h') + data_descriptor.write(fp) + + +def _save(im, fp, _filename): """Helper method used by PIL to write to an output file. """ # Work out from the parameters if we need to do anything special encoderinfo = im.encoderinfo.copy() - append_images = list(encoderinfo.get("append_images", [])) - verbose = encoderinfo.get("verbose", False) - use_deltas = encoderinfo.get("use_deltas", True) - use_rle = encoderinfo.get("use_rle", True) - # Helper for inline verbose prints - def vprint(s): - if verbose: - print(s) + # Helper for prints, noop taking any args if not verbose + global vprint + verbose = encoderinfo.get("verbose", False) + vprint = print if verbose else lambda *_args, **_kwargs: None # Helper to iterate through all frames in the input image - def _for_all_frames(x: FunctionType): - frame_num = 0 - last_frame = None - for frame in [im] + append_images: - # Get number of of frames in this image - nfr = getattr(frame, "n_frames", 1) - for idx in range(nfr): - frame.seek(idx) - frame.load() - copy = frame.copy().convert("RGB") - x(frame_num, copy, last_frame) - last_frame = copy - frame_num += 1 + append_images = list(encoderinfo.get("append_images", [])) + for_all_frames = functools.partial(_for_all_frames, images=[im, *append_images]) # Collect all the frame sizes frame_sizes = [] - _for_all_frames(lambda idx, frame, last_frame: frame_sizes.append(frame.size)) + for_all_frames(lambda _idx, frame, _last_frame: frame_sizes.append(frame.size)) # Make sure all frames are the same size - if len(list(set(frame_sizes))) != 1: + if len(set(frame_sizes)) != 1: raise ValueError("Mismatching sizes on frames") # Write out the initial graphics descriptor (and write a dummy value), so that we can come back and fill in the @@ -264,8 +405,7 @@ def _save(im, fp, filename): graphics_descriptor_location = fp.tell() graphics_descriptor = QGFGraphicsDescriptor() graphics_descriptor.frame_count = len(frame_sizes) - graphics_descriptor.image_width = frame_sizes[0][0] - graphics_descriptor.image_height = frame_sizes[0][1] + graphics_descriptor.image_size = frame_sizes[0] vprint(f'{"Graphics descriptor block":26s} {fp.tell():5d}d / {fp.tell():04X}h') graphics_descriptor.write(fp) @@ -276,117 +416,9 @@ def _save(im, fp, filename): vprint(f'{"Frame offsets block":26s} {fp.tell():5d}d / {fp.tell():04X}h') frame_offsets.write(fp) - # Helper function to save each frame to the output file - def _write_frame(idx, frame, last_frame): - # If we replace the frame we're going to output with a delta, we can override it here - this_frame = frame - location = (0, 0) - size = frame.size - - # Work out the format we're going to use - format = encoderinfo["qmk_format"] - - # Convert the original frame so we can do comparisons - converted = qmk.painter.convert_requested_format(this_frame, format) - graphic_data = qmk.painter.convert_image_bytes(converted, format) - - # Convert the raw data to RLE-encoded if requested - raw_data = graphic_data[1] - if use_rle: - rle_data = qmk.painter.compress_bytes_qmk_rle(graphic_data[1]) - use_raw_this_frame = not use_rle or len(raw_data) <= len(rle_data) - image_data = raw_data if use_raw_this_frame else rle_data - - # Work out if a delta frame is smaller than injecting it directly - use_delta_this_frame = False - if use_deltas and last_frame is not None: - # If we want to use deltas, then find the difference - diff = ImageChops.difference(frame, last_frame) - - # Get the bounding box of those differences - bbox = diff.getbbox() - - # If we have a valid bounding box... - if bbox: - # ...create the delta frame by cropping the original. - delta_frame = frame.crop(bbox) - delta_location = (bbox[0], bbox[1]) - delta_size = (bbox[2] - bbox[0], bbox[3] - bbox[1]) - - # Convert the delta frame to the requested format - delta_converted = qmk.painter.convert_requested_format(delta_frame, format) - delta_graphic_data = qmk.painter.convert_image_bytes(delta_converted, format) - - # Work out how large the delta frame is going to be with compression etc. - delta_raw_data = delta_graphic_data[1] - if use_rle: - delta_rle_data = qmk.painter.compress_bytes_qmk_rle(delta_graphic_data[1]) - delta_use_raw_this_frame = not use_rle or len(delta_raw_data) <= len(delta_rle_data) - delta_image_data = delta_raw_data if delta_use_raw_this_frame else delta_rle_data - - # If the size of the delta frame (plus delta descriptor) is smaller than the original, use that instead - # This ensures that if a non-delta is overall smaller in size, we use that in preference due to flash - # sizing constraints. - if (len(delta_image_data) + QGFFrameDeltaDescriptorV1.length) < len(image_data): - # Copy across all the delta equivalents so that the rest of the processing acts on those - this_frame = delta_frame - location = delta_location - size = delta_size - converted = delta_converted - graphic_data = delta_graphic_data - raw_data = delta_raw_data - rle_data = delta_rle_data - use_raw_this_frame = delta_use_raw_this_frame - image_data = delta_image_data - use_delta_this_frame = True - - # Write out the frame descriptor - frame_offsets.frame_offsets[idx] = fp.tell() - vprint(f'{f"Frame {idx:3d} base":26s} {fp.tell():5d}d / {fp.tell():04X}h') - frame_descriptor = QGFFrameDescriptorV1() - frame_descriptor.is_delta = use_delta_this_frame - frame_descriptor.is_transparent = False - frame_descriptor.format = format['image_format_byte'] - frame_descriptor.compression = 0x00 if use_raw_this_frame else 0x01 # See qp.h, painter_compression_t - frame_descriptor.delay = frame.info['duration'] if 'duration' in frame.info else 1000 # If we're not an animation, just pretend we're delaying for 1000ms - frame_descriptor.write(fp) - - # Write out the palette if required - if format['has_palette']: - palette = graphic_data[0] - palette_descriptor = QGFFramePaletteDescriptorV1() - - # Helper to convert from RGB888 to the QMK "dialect" of HSV888 - def rgb888_to_qmk_hsv888(e): - hsv = rgb_to_hsv(e[0] / 255.0, e[1] / 255.0, e[2] / 255.0) - return (int(hsv[0] * 255.0), int(hsv[1] * 255.0), int(hsv[2] * 255.0)) - - # Convert all palette entries to HSV888 and write to the output - palette_descriptor.palette_entries = list(map(rgb888_to_qmk_hsv888, palette)) - vprint(f'{f"Frame {idx:3d} palette":26s} {fp.tell():5d}d / {fp.tell():04X}h') - palette_descriptor.write(fp) - - # Write out the delta info if required - if use_delta_this_frame: - # Set up the rendering location of where the delta frame should be situated - delta_descriptor = QGFFrameDeltaDescriptorV1() - delta_descriptor.left = location[0] - delta_descriptor.top = location[1] - delta_descriptor.right = location[0] + size[0] - 1 - delta_descriptor.bottom = location[1] + size[1] - 1 - - # Write the delta frame to the output - vprint(f'{f"Frame {idx:3d} delta":26s} {fp.tell():5d}d / {fp.tell():04X}h') - delta_descriptor.write(fp) - - # Write out the data for this frame to the output - data_descriptor = QGFFrameDataDescriptorV1() - data_descriptor.data = image_data - vprint(f'{f"Frame {idx:3d} data":26s} {fp.tell():5d}d / {fp.tell():04X}h') - data_descriptor.write(fp) - # Iterate over each if the input frames, writing it to the output in the process - _for_all_frames(_write_frame) + write_frame = functools.partial(_write_frame, format_=encoderinfo["qmk_format"], fp=fp, use_deltas=encoderinfo.get("use_deltas", True), use_rle=encoderinfo.get("use_rle", True), frame_offsets=frame_offsets) + for_all_frames(write_frame) # Go back and update the graphics descriptor now that we can determine the final file size graphics_descriptor.total_file_size = fp.tell() -- cgit v1.2.3 From 9b0b3d7b25a845bd1af2152ab1259a6522d5590f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez?= <58857054+elpekenin@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:34:43 +0100 Subject: [Enhancement] Prepare for `SyntaxWarning` (#22562) --- lib/python/qmk/cli/bux.py | 4 ++-- lib/python/qmk/cli/mass_compile.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/bux.py b/lib/python/qmk/cli/bux.py index 8c7f172779..669521d08e 100755 --- a/lib/python/qmk/cli/bux.py +++ b/lib/python/qmk/cli/bux.py @@ -19,7 +19,7 @@ def bux(cli): config.set_config('user', 'bux', bux + 1) cli.save_config() - buck = """ + buck = r""" @@BBBBBBBBBBBBBBBBBBBBK `vP8#####BE2~ x###g_ `S###q n##} -j#Bl. vBBBBBBBBBBBBBBBBBBBB@@ @B `:!: ^#@#]- `!t@@&. 7@@B@#^ _Q@Q@@R y@@l:P@#1' `!!_ B@ @B r@@@B g@@| ` N@@u 7@@iv@@u *#@z"@@R y@@&@@Q- l@@@D B@ @@ -34,7 +34,7 @@ def bux(cli): @B _y ]# ,c vUWNWWPsfsssN9WyccnckAfUfWb0DR0&R5RRRddq2_ `@D`jr@2U@#c3@1@Qc- B@ @B !7! .r]` }AE0RdRqNd9dNR9fUIzzosPqqAddNNdER9EE9dPy! BQ!zy@iU@.Q@@y@8x- B@ @B :****>. '7adddDdR&gRNdRbd&dNNbbRdNdd5NdRRD0RSf}- .k0&EW`xR .8Q=NRRx B@ -@B =**-rx*r}r~}" ;n2jkzsf3N3zsKsP5dddRddddRddNNqPzy\\" '~****" B@ +@B =**-rx*r}r~}" ;n2jkzsf3N3zsKsP5dddRddddRddNNqPzy\" '~****" B@ @B :!!~!;=~r>:*_ `:^vxikylulKfHkyjzzozoIoklix|^!-` B@ @B ```'-_""::::!:_-.`` B@ @B `- .` B@ diff --git a/lib/python/qmk/cli/mass_compile.py b/lib/python/qmk/cli/mass_compile.py index 69b9103fdc..7db704d6c2 100755 --- a/lib/python/qmk/cli/mass_compile.py +++ b/lib/python/qmk/cli/mass_compile.py @@ -52,9 +52,9 @@ all: {keyboard_safe}_{keymap_name}_binary {' '.join(command)} \\ >>"{build_log}" 2>&1 \\ || cp "{build_log}" "{failed_log}" - @{{ grep '\[ERRORS\]' "{build_log}" >/dev/null 2>&1 && printf "Build %-64s \e[1;31m[ERRORS]\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\ - || {{ grep '\[WARNINGS\]' "{build_log}" >/dev/null 2>&1 && printf "Build %-64s \e[1;33m[WARNINGS]\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\ - || printf "Build %-64s \e[1;32m[OK]\e[0m\\n" "{keyboard_name}:{keymap_name}" + @{{ grep '\\[ERRORS\\]' "{build_log}" >/dev/null 2>&1 && printf "Build %-64s \\e[1;31m[ERRORS]\\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\ + || {{ grep '\\[WARNINGS\\]' "{build_log}" >/dev/null 2>&1 && printf "Build %-64s \\e[1;33m[WARNINGS]\\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\ + || printf "Build %-64s \\e[1;32m[OK]\\e[0m\\n" "{keyboard_name}:{keymap_name}" @rm -f "{build_log}" || true """# noqa ) -- cgit v1.2.3 From 6810aaf0130113e267e20fb506d874cc858f5f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez?= <58857054+elpekenin@users.noreply.github.com> Date: Sat, 17 Feb 2024 13:28:40 +0100 Subject: [Refactor] `qmk find` (#21096) --- lib/python/qmk/cli/find.py | 4 +- lib/python/qmk/search.py | 111 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 90 insertions(+), 25 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/find.py b/lib/python/qmk/cli/find.py index 55a0530092..8f3a29c90c 100644 --- a/lib/python/qmk/cli/find.py +++ b/lib/python/qmk/cli/find.py @@ -1,7 +1,7 @@ """Command to search through all keyboards and keymaps for a given search criteria. """ from milc import cli -from qmk.search import search_keymap_targets +from qmk.search import filter_help, search_keymap_targets @cli.argument( @@ -11,7 +11,7 @@ from qmk.search import search_keymap_targets action='append', default=[], help= # noqa: `format-python` and `pytest` don't agree here. - "Filter the list of keyboards based on their info.json data. Accepts the formats key=value, function(key), or function(key,value), eg. 'features.rgblight=true'. Valid functions are 'absent', 'contains', 'exists' and 'length'. May be passed multiple times; all filters need to match. Value may include wildcards such as '*' and '?'." # noqa: `format-python` and `pytest` don't agree here. + f"Filter the list of keyboards based on their info.json data. Accepts the formats key=value, function(key), or function(key,value), eg. 'features.rgblight=true'. Valid functions are {filter_help()}. May be passed multiple times; all filters need to match. Value may include wildcards such as '*' and '?'." # noqa: `format-python` and `pytest` don't agree here. ) @cli.argument('-p', '--print', arg_only=True, action='append', default=[], help="For each matched target, print the value of the supplied info.json key. May be passed multiple times.") @cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.") diff --git a/lib/python/qmk/search.py b/lib/python/qmk/search.py index 84cf6cbe32..33550a3db2 100644 --- a/lib/python/qmk/search.py +++ b/lib/python/qmk/search.py @@ -5,7 +5,7 @@ import functools import fnmatch import logging import re -from typing import List, Tuple +from typing import Callable, List, Optional, Tuple from dotty_dict import dotty, Dotty from milc import cli @@ -15,6 +15,82 @@ from qmk.keyboard import list_keyboards, keyboard_folder from qmk.keymap import list_keymaps, locate_keymap from qmk.build_targets import KeyboardKeymapBuildTarget, BuildTarget +TargetInfo = Tuple[str, str, dict] + + +# by using a class for filters, we dont need to worry about capturing values +# see details +class FilterFunction: + """Base class for filters. + It provides: + - __init__: capture key and value + + Each subclass should provide: + - func_name: how it will be specified on CLI + >>> qmk find -f ... + - apply: function that actually applies the filter + ie: return whether the input kb/km satisfies the condition + """ + + key: str + value: Optional[str] + + func_name: str + apply: Callable[[TargetInfo], bool] + + def __init__(self, key, value): + self.key = key + self.value = value + + +class Exists(FilterFunction): + func_name = "exists" + + def apply(self, target_info: TargetInfo) -> bool: + _kb, _km, info = target_info + return self.key in info + + +class Absent(FilterFunction): + func_name = "absent" + + def apply(self, target_info: TargetInfo) -> bool: + _kb, _km, info = target_info + return self.key not in info + + +class Length(FilterFunction): + func_name = "length" + + def apply(self, target_info: TargetInfo) -> bool: + _kb, _km, info = target_info + return (self.key in info and len(info[self.key]) == int(self.value)) + + +class Contains(FilterFunction): + func_name = "contains" + + def apply(self, target_info: TargetInfo) -> bool: + _kb, _km, info = target_info + return (self.key in info and self.value in info[self.key]) + + +def _get_filter_class(func_name: str, key: str, value: str) -> Optional[FilterFunction]: + """Initialize a filter subclass based on regex findings and return it. + None if no there's no filter with the name queried. + """ + + for subclass in FilterFunction.__subclasses__(): + if func_name == subclass.func_name: + return subclass(key, value) + + return None + + +def filter_help() -> str: + names = [f"'{f.func_name}'" for f in FilterFunction.__subclasses__()] + return ", ".join(names[:-1]) + f" and {names[-1]}" + def _set_log_level(level): cli.acquire_lock() @@ -48,11 +124,12 @@ def _keymap_exists(keyboard, keymap): return keyboard if locate_keymap(keyboard, keymap) is not None else None -def _load_keymap_info(kb_km): +def _load_keymap_info(target: Tuple[str, str]) -> TargetInfo: """Returns a tuple of (keyboard, keymap, info.json) for the given keyboard/keymap combination. """ + kb, km = target with ignore_logging(): - return (kb_km[0], kb_km[1], keymap_json(kb_km[0], kb_km[1])) + return (kb, km, keymap_json(kb, km)) def expand_make_targets(targets: List[str]) -> List[Tuple[str, str]]: @@ -139,26 +216,14 @@ def _filter_keymap_targets(target_list: List[Tuple[str, str]], filters: List[str key = function_match.group('key') value = function_match.group('value') - if value is not None: - if func_name == 'length': - valid_keymaps = filter(lambda e, key=key, value=value: key in e[2] and len(e[2].get(key)) == int(value), valid_keymaps) - elif func_name == 'contains': - valid_keymaps = filter(lambda e, key=key, value=value: key in e[2] and value in e[2].get(key), valid_keymaps) - else: - cli.log.warning(f'Unrecognized filter expression: {function_match.group(0)}') - continue - - cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}}, {{fg_cyan}}{value}{{fg_reset}})...') - else: - if func_name == 'exists': - valid_keymaps = filter(lambda e, key=key: key in e[2], valid_keymaps) - elif func_name == 'absent': - valid_keymaps = filter(lambda e, key=key: key not in e[2], valid_keymaps) - else: - cli.log.warning(f'Unrecognized filter expression: {function_match.group(0)}') - continue - - cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}})...') + filter_class = _get_filter_class(func_name, key, value) + if filter_class is None: + cli.log.warning(f'Unrecognized filter expression: {function_match.group(0)}') + continue + valid_keymaps = filter(filter_class.apply, valid_keymaps) + + value_str = f", {{fg_cyan}}{value}{{fg_reset}})" if value is not None else "" + cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}}{value_str}...') elif equals_match is not None: key = equals_match.group('key') -- cgit v1.2.3 From 56802f506cee22730e004b0695d87e9e9c5983af Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Thu, 22 Feb 2024 23:47:42 +1100 Subject: Ensure `qmk generate-compilation-database` copies to userspace as well. (#23129) --- lib/python/qmk/build_targets.py | 5 +++-- lib/python/qmk/cli/generate/compilation_database.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/build_targets.py b/lib/python/qmk/build_targets.py index 80f587bcc0..d974d04020 100644 --- a/lib/python/qmk/build_targets.py +++ b/lib/python/qmk/build_targets.py @@ -119,9 +119,10 @@ class BuildTarget: command = self.compile_command(build_target=build_target, dry_run=True, **env_vars) from qmk.cli.generate.compilation_database import write_compilation_database # Lazy load due to circular references output_path = QMK_FIRMWARE / 'compile_commands.json' - write_compilation_database(command=command, output_path=output_path, skip_clean=skip_clean, **env_vars) - if output_path.exists() and HAS_QMK_USERSPACE: + ret = write_compilation_database(command=command, output_path=output_path, skip_clean=skip_clean, **env_vars) + if ret and output_path.exists() and HAS_QMK_USERSPACE: shutil.copy(str(output_path), str(QMK_USERSPACE / 'compile_commands.json')) + return ret def compile(self, build_target: str = None, dry_run: bool = False, **env_vars) -> None: if self._clean or self._compiledb: diff --git a/lib/python/qmk/cli/generate/compilation_database.py b/lib/python/qmk/cli/generate/compilation_database.py index 5100d2b6d2..a2190fee66 100755 --- a/lib/python/qmk/cli/generate/compilation_database.py +++ b/lib/python/qmk/cli/generate/compilation_database.py @@ -17,6 +17,7 @@ from qmk.constants import QMK_FIRMWARE from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.keymap import keymap_completer +from qmk.build_targets import KeyboardKeymapBuildTarget @lru_cache(maxsize=10) @@ -138,4 +139,5 @@ def generate_compilation_database(cli: MILC) -> Union[bool, int]: elif not current_keymap: cli.log.error('Could not determine keymap!') - return write_compilation_database(current_keyboard, current_keymap, QMK_FIRMWARE / 'compile_commands.json') + target = KeyboardKeymapBuildTarget(current_keyboard, current_keymap) + return target.generate_compilation_database() -- cgit v1.2.3 From 1875659df025ec83201bb8e1e4515100e5152a87 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 2 Mar 2024 08:45:01 +1100 Subject: CLI Speed improvements. (#23189) --- lib/python/qmk/json_schema.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index b00df749cc..1d5f863807 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py @@ -7,6 +7,7 @@ from collections.abc import Mapping from functools import lru_cache from typing import OrderedDict from pathlib import Path +from copy import deepcopy from milc import cli @@ -22,7 +23,8 @@ def _dict_raise_on_duplicates(ordered_pairs): return d -def json_load(json_file, strict=True): +@lru_cache(maxsize=20) +def _json_load_impl(json_file, strict=True): """Load a json file from disk. Note: file must be a Path object. @@ -42,7 +44,11 @@ def json_load(json_file, strict=True): exit(1) -@lru_cache(maxsize=0) +def json_load(json_file, strict=True): + return deepcopy(_json_load_impl(json_file=json_file, strict=strict)) + + +@lru_cache(maxsize=20) def load_jsonschema(schema_name): """Read a jsonschema file from disk. """ @@ -57,7 +63,7 @@ def load_jsonschema(schema_name): return json_load(schema_path) -@lru_cache(maxsize=0) +@lru_cache(maxsize=1) def compile_schema_store(): """Compile all our schemas into a schema store. """ @@ -73,7 +79,7 @@ def compile_schema_store(): return schema_store -@lru_cache(maxsize=0) +@lru_cache(maxsize=20) def create_validator(schema): """Creates a validator for the given schema id. """ -- cgit v1.2.3 From c06087669290fe72d5fb85c7b37d20cf5ebe149d Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 2 Mar 2024 12:23:25 +0000 Subject: Remove cd suggestion from new-keyboard (#23194) --- lib/python/qmk/cli/new/keyboard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/new/keyboard.py b/lib/python/qmk/cli/new/keyboard.py index ce956d0ce1..cb50acf8bb 100644 --- a/lib/python/qmk/cli/new/keyboard.py +++ b/lib/python/qmk/cli/new/keyboard.py @@ -15,7 +15,7 @@ from qmk.json_schema import load_jsonschema from qmk.path import keyboard from qmk.json_encoders import InfoJSONEncoder from qmk.json_schema import deep_update, json_load -from qmk.constants import MCU2BOOTLOADER +from qmk.constants import MCU2BOOTLOADER, QMK_FIRMWARE COMMUNITY = Path('layouts/default/') TEMPLATE = Path('data/templates/keyboard/') @@ -254,6 +254,6 @@ def new_keyboard(cli): augment_community_info(community_info, keyboard(kb_name) / community_info.name) cli.log.info(f'{{fg_green}}Created a new keyboard called {{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}}.") + cli.log.info(f"Build Command: {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.") + cli.log.info(f'Project Location: {{fg_cyan}}{QMK_FIRMWARE}/{keyboard(kb_name)}{{fg_reset}},') + cli.log.info("{{fg_yellow}}Now update the config files to match the hardware!{{fg_reset}}") -- cgit v1.2.3 From ae38bdd5dc720e307266ba4cedd92933069024d8 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 12 Mar 2024 04:28:02 +0000 Subject: Flag LAYOUT macros still defined in .h files (#23260) --- lib/python/qmk/info.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/python') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 13588abdb8..815b851474 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -78,7 +78,7 @@ def _find_invalid_encoder_index(info_data): return ret -def _validate_layouts(keyboard, info_data): +def _validate_layouts(keyboard, info_data): # noqa C901 """Non schema checks """ col_num = info_data.get('matrix_size', {}).get('cols', 0) @@ -92,6 +92,11 @@ def _validate_layouts(keyboard, info_data): if len(layouts) == 0 or all(not layout.get('json_layout', False) for layout in layouts.values()): _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in info.json.') + # Make sure all layouts are DD + for layout_name, layout_data in layouts.items(): + if layout_data.get('c_macro', False): + _log_error(info_data, f'{layout_name}: Layout macro should not be defined within ".h" files.') + # Make sure all matrix values are in bounds for layout_name, layout_data in layouts.items(): for index, key_data in enumerate(layout_data['layout']): -- cgit v1.2.3 From fb11330eab2b459d5e510c66a9bdf3961c8e639a Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 13 Mar 2024 00:28:08 +0000 Subject: Absolute paths for -kb argument should error consistently (#23262) --- lib/python/qmk/path.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py index 74364ee04b..bb588d2e1c 100644 --- a/lib/python/qmk/path.py +++ b/lib/python/qmk/path.py @@ -12,11 +12,19 @@ from qmk.errors import NoSuchKeyboardError def is_keyboard(keyboard_name): """Returns True if `keyboard_name` is a keyboard we can compile. """ - if keyboard_name: - keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name - rules_mk = keyboard_path / 'rules.mk' + if not keyboard_name: + return False + + # keyboard_name values of 'c:/something' or '/something' trigger append issues + # due to "If the argument is an absolute path, the previous path is ignored" + # however it should always be a folder located under qmk_firmware/keyboards + if Path(keyboard_name).is_absolute(): + return False + + keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name + rules_mk = keyboard_path / 'rules.mk' - return rules_mk.exists() + return rules_mk.exists() def under_qmk_firmware(path=Path(os.environ['ORIG_CWD'])): -- cgit v1.2.3 From 319d9aa7b94daba050a7d207caf2dd607b04298c Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sun, 31 Mar 2024 12:23:40 +0100 Subject: Fix 'qmk compile' mass_compile execution (#23296) --- lib/python/qmk/cli/compile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/python') diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index 4c36dec3e7..8d1195bc8f 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -41,17 +41,17 @@ def compile(cli): cli.args.filter = [] cli.config.mass_compile.keymap = cli.config.compile.keymap cli.config.mass_compile.parallel = cli.config.compile.parallel - cli.config.mass_compile.no_temp = False + cli.args.no_temp = False return mass_compile(cli) # If we've received `-km all`, reroute it to mass-compile. if cli.args.keymap == 'all': from .mass_compile import mass_compile - cli.args.builds = [f'{cli.args.keyboard}:all'] + cli.args.builds = [f'{cli.config.compile.keyboard}:all'] cli.args.filter = [] cli.config.mass_compile.keymap = None cli.config.mass_compile.parallel = cli.config.compile.parallel - cli.config.mass_compile.no_temp = False + cli.args.no_temp = False return mass_compile(cli) # Build the environment vars -- cgit v1.2.3