summaryrefslogtreecommitdiffstats
path: root/lib/python/qmk/info.py
diff options
context:
space:
mode:
authorlokher <lokher@gmail.com>2022-12-06 17:04:10 +0800
committerlokher <lokher@gmail.com>2022-12-06 17:04:10 +0800
commit27fc28fd2ff52e079a5bc58d6aaea4c752420615 (patch)
tree7ac943fb1ba4f430a7220efd18f66f6a77205c30 /lib/python/qmk/info.py
parente736133392fe6427cfb995da0787337189828272 (diff)
parent2709b6ed616f8012ff4cfd3ee69a822a8d188351 (diff)
Merge upstream master
Diffstat (limited to 'lib/python/qmk/info.py')
-rw-r--r--lib/python/qmk/info.py170
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'