diff options
Diffstat (limited to 'lib/python/qmk/info.py')
-rw-r--r-- | lib/python/qmk/info.py | 170 |
1 files changed, 79 insertions, 91 deletions
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 7460d84ad3..7e588b5182 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -1,6 +1,5 @@ """Functions that help us generate and use info.json files. """ -from glob import glob from pathlib import Path import jsonschema @@ -19,6 +18,16 @@ from qmk.math import compute true_values = ['1', 'on', 'yes'] false_values = ['0', 'off', 'no'] +# TODO: reduce this list down +SAFE_LAYOUT_TOKENS = { + 'ansi', + 'iso', + 'wkl', + 'tkl', + 'preonic', + 'planck', +} + def _valid_community_layout(layout): """Validate that a declared community list exists @@ -26,6 +35,51 @@ def _valid_community_layout(layout): return (Path('layouts/default') / layout).exists() +def _validate(keyboard, info_data): + """Perform various validation on the provided info.json data + """ + # First validate against the jsonschema + try: + validate(info_data, 'qmk.api.keyboard.v1') + + except jsonschema.ValidationError as e: + json_path = '.'.join([str(p) for p in e.absolute_path]) + cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) + exit(1) + + layouts = info_data.get('layouts', {}) + layout_aliases = info_data.get('layout_aliases', {}) + community_layouts = info_data.get('community_layouts', []) + community_layouts_names = list(map(lambda layout: f'LAYOUT_{layout}', community_layouts)) + + # Make sure we have at least one layout + if len(layouts) == 0: + _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in info.json.') + + # Providing only LAYOUT_all "because I define my layouts in a 3rd party tool" + if len(layouts) == 1 and 'LAYOUT_all' in layouts: + _log_warning(info_data, '"LAYOUT_all" should be "LAYOUT" unless additional layouts are provided.') + + # Extended layout name checks - ignoring community_layouts and "safe" values + name_fragments = set(keyboard.split('/')) - SAFE_LAYOUT_TOKENS + potential_layouts = set(layouts.keys()) - set(community_layouts_names) + for layout in potential_layouts: + if any(fragment in layout for fragment in name_fragments): + _log_warning(info_data, f'Layout "{layout}" should not contain name of keyboard.') + + # Filter out any non-existing community layouts + for layout in community_layouts: + if not _valid_community_layout(layout): + # Ignore layout from future checks + info_data['community_layouts'].remove(layout) + _log_error(info_data, 'Claims to support a community layout that does not exist: %s' % (layout)) + + # Make sure we supply layout macros for the community layouts we claim to support + for layout_name in community_layouts_names: + 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 info_json(keyboard): """Generate the info.json data for a specific keyboard. """ @@ -72,34 +126,8 @@ def info_json(keyboard): # Merge in data from <keyboard.c> info_data = _extract_led_config(info_data, str(keyboard)) - # Validate against the jsonschema - try: - validate(info_data, 'qmk.api.keyboard.v1') - - except jsonschema.ValidationError as e: - json_path = '.'.join([str(p) for p in e.absolute_path]) - cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) - exit(1) - - # Make sure we have at least one layout - if not info_data.get('layouts'): - _find_missing_layouts(info_data, keyboard) - - if not info_data.get('layouts'): - _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.') - - # Filter out any non-existing community layouts - for layout in info_data.get('community_layouts', []): - if not _valid_community_layout(layout): - # Ignore layout from future checks - info_data['community_layouts'].remove(layout) - _log_error(info_data, 'Claims to support a community layout that does not exist: %s' % (layout)) - - # Make sure we supply layout macros for the community layouts we claim to support - for layout in info_data.get('community_layouts', []): - layout_name = 'LAYOUT_' + layout - if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}): - _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) + # Validate + _validate(keyboard, info_data) # Check that the reported matrix size is consistent with the actual matrix size _check_matrix(info_data) @@ -437,19 +465,6 @@ def _extract_matrix_info(info_data, config_c): return info_data -# TODO: kill off usb.device_ver in favor of usb.device_version -def _extract_device_version(info_data): - if info_data.get('usb'): - if info_data['usb'].get('device_version') and not info_data['usb'].get('device_ver'): - (major, minor, revision) = info_data['usb']['device_version'].split('.', 3) - info_data['usb']['device_ver'] = f'0x{major.zfill(2)}{minor}{revision}' - if not info_data['usb'].get('device_version') and info_data['usb'].get('device_ver'): - major = int(info_data['usb']['device_ver'][2:4]) - minor = int(info_data['usb']['device_ver'][4]) - revision = int(info_data['usb']['device_ver'][5]) - info_data['usb']['device_version'] = f'{major}.{minor}.{revision}' - - def _config_to_json(key_type, config_value): """Convert config value using spec """ @@ -464,7 +479,7 @@ def _config_to_json(key_type, config_value): if array_type == 'int': return list(map(int, config_value.split(','))) else: - return config_value.split(',') + return list(map(str.strip, config_value.split(','))) elif key_type == 'bool': return config_value in true_values @@ -479,7 +494,7 @@ def _config_to_json(key_type, config_value): return int(config_value) elif key_type == 'str': - return config_value.strip('"') + return config_value.strip('"').replace('\\"', '"').replace('\\\\', '\\') elif key_type == 'bcd_version': major = int(config_value[2:4]) @@ -496,7 +511,7 @@ def _extract_config_h(info_data, config_c): """ # Pull in data from the json map dotty_info = dotty(info_data) - info_config_map = json_load(Path('data/mappings/info_config.json')) + info_config_map = json_load(Path('data/mappings/info_config.hjson')) for config_key, info_dict in info_config_map.items(): info_key = info_dict['info_key'] @@ -535,7 +550,6 @@ def _extract_config_h(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 @@ -543,7 +557,7 @@ def _extract_config_h(info_data, config_c): def _process_defaults(info_data): """Process any additional defaults based on currently discovered information """ - defaults_map = json_load(Path('data/mappings/defaults.json')) + defaults_map = json_load(Path('data/mappings/defaults.hjson')) for default_type in defaults_map.keys(): thing_map = defaults_map[default_type] if default_type in info_data: @@ -569,7 +583,7 @@ def _extract_rules_mk(info_data, rules): # Pull in data from the json map dotty_info = dotty(info_data) - info_rules_map = json_load(Path('data/mappings/info_rules.json')) + info_rules_map = json_load(Path('data/mappings/info_rules.hjson')) for rules_key, info_dict in info_rules_map.items(): info_key = info_dict['info_key'] @@ -627,20 +641,24 @@ def _extract_led_config(info_data, keyboard): cols = info_data['matrix_size']['cols'] rows = info_data['matrix_size']['rows'] - # Assume what feature owns g_led_config - feature = "rgb_matrix" - if info_data.get("features", {}).get("led_matrix", False): + # 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" - # 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 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}') return info_data @@ -711,30 +729,6 @@ def _search_keyboard_h(keyboard): return layouts, aliases -def _find_missing_layouts(info_data, keyboard): - """Looks for layout macros when they aren't found other places. - - If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above. - """ - _log_warning(info_data, '%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard)) - - for file in glob('keyboards/%s/*.h' % keyboard): - these_layouts, these_aliases = find_layouts(file) - - if these_layouts: - for layout_name, layout_json in these_layouts.items(): - if not layout_name.startswith('LAYOUT_kc'): - layout_json['c_macro'] = True - info_data['layouts'][layout_name] = layout_json - - for alias, alias_text in these_aliases.items(): - if alias_text in these_layouts: - if 'layout_aliases' not in info_data: - info_data['layout_aliases'] = {} - - info_data['layout_aliases'][alias] = alias_text - - def _log_error(info_data, message): """Send an error message to both JSON and the log. """ @@ -755,9 +749,6 @@ def arm_processor_rules(info_data, rules): info_data['processor_type'] = 'arm' info_data['protocol'] = 'ChibiOS' - if 'bootloader' not in info_data: - info_data['bootloader'] = 'unknown' - if 'STM32' in info_data['processor']: info_data['platform'] = 'STM32' elif 'MCU_SERIES' in rules: @@ -773,10 +764,7 @@ def avr_processor_rules(info_data, rules): """ info_data['processor_type'] = 'avr' info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' - info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' - - if 'bootloader' not in info_data: - info_data['bootloader'] = 'atmel-dfu' + info_data['protocol'] = 'V-USB' if info_data['processor'] in VUSB_PROCESSORS else 'LUFA' # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: # info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA' |