aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk/info.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk/info.py')
-rw-r--r--lib/python/qmk/info.py574
1 files changed, 499 insertions, 75 deletions
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index f476dc666..28c281a4b 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -4,15 +4,60 @@ import json
4from glob import glob 4from glob import glob
5from pathlib import Path 5from pathlib import Path
6 6
7import jsonschema
7from milc import cli 8from milc import cli
8 9
9from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS 10from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS
10from qmk.c_parse import find_layouts 11from qmk.c_parse import find_layouts
11from qmk.keyboard import config_h, rules_mk 12from qmk.keyboard import config_h, rules_mk
12from qmk.keymap import list_keymaps 13from qmk.keymap import list_keymaps
13from qmk.makefile import parse_rules_mk_file 14from qmk.makefile import parse_rules_mk_file
14from qmk.math import compute 15from qmk.math import compute
15 16
17led_matrix_properties = {
18 'driver_count': 'LED_DRIVER_COUNT',
19 'driver_addr1': 'LED_DRIVER_ADDR_1',
20 'driver_addr2': 'LED_DRIVER_ADDR_2',
21 'driver_addr3': 'LED_DRIVER_ADDR_3',
22 'driver_addr4': 'LED_DRIVER_ADDR_4',
23 'led_count': 'LED_DRIVER_LED_COUNT',
24 'timeout': 'ISSI_TIMEOUT',
25 'persistence': 'ISSI_PERSISTENCE'
26}
27
28rgblight_properties = {
29 'led_count': ('RGBLED_NUM', int),
30 'pin': ('RGB_DI_PIN', str),
31 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int),
32 'hue_steps': ('RGBLIGHT_HUE_STEP', int),
33 'saturation_steps': ('RGBLIGHT_SAT_STEP', int),
34 'brightness_steps': ('RGBLIGHT_VAL_STEP', int)
35}
36
37rgblight_toggles = {
38 'sleep': 'RGBLIGHT_SLEEP',
39 'split': 'RGBLIGHT_SPLIT',
40}
41
42rgblight_animations = {
43 'all': 'RGBLIGHT_ANIMATIONS',
44 'alternating': 'RGBLIGHT_EFFECT_ALTERNATING',
45 'breathing': 'RGBLIGHT_EFFECT_BREATHING',
46 'christmas': 'RGBLIGHT_EFFECT_CHRISTMAS',
47 'knight': 'RGBLIGHT_EFFECT_KNIGHT',
48 'rainbow_mood': 'RGBLIGHT_EFFECT_RAINBOW_MOOD',
49 'rainbow_swirl': 'RGBLIGHT_EFFECT_RAINBOW_SWIRL',
50 'rgb_test': 'RGBLIGHT_EFFECT_RGB_TEST',
51 'snake': 'RGBLIGHT_EFFECT_SNAKE',
52 'static_gradient': 'RGBLIGHT_EFFECT_STATIC_GRADIENT',
53 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE'
54}
55
56usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
57
58true_values = ['1', 'on', 'yes']
59false_values = ['0', 'off', 'no']
60
16 61
17def info_json(keyboard): 62def info_json(keyboard):
18 """Generate the info.json data for a specific keyboard. 63 """Generate the info.json data for a specific keyboard.
@@ -38,8 +83,9 @@ def info_json(keyboard):
38 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} 83 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'}
39 84
40 # Populate layout data 85 # Populate layout data
41 for layout_name, layout_json in _find_all_layouts(info_data, keyboard, rules).items(): 86 for layout_name, layout_json in _find_all_layouts(info_data, keyboard).items():
42 if not layout_name.startswith('LAYOUT_kc'): 87 if not layout_name.startswith('LAYOUT_kc'):
88 layout_json['c_macro'] = True
43 info_data['layouts'][layout_name] = layout_json 89 info_data['layouts'][layout_name] = layout_json
44 90
45 # Merge in the data from info.json, config.h, and rules.mk 91 # Merge in the data from info.json, config.h, and rules.mk
@@ -47,54 +93,364 @@ def info_json(keyboard):
47 info_data = _extract_config_h(info_data) 93 info_data = _extract_config_h(info_data)
48 info_data = _extract_rules_mk(info_data) 94 info_data = _extract_rules_mk(info_data)
49 95
96 # Validate against the jsonschema
97 try:
98 keyboard_api_validate(info_data)
99
100 except jsonschema.ValidationError as e:
101 json_path = '.'.join([str(p) for p in e.absolute_path])
102 cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message)
103 print(dir(e))
104 exit()
105
106 # Make sure we have at least one layout
107 if not info_data.get('layouts'):
108 _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.')
109
110 # Make sure we supply layout macros for the community layouts we claim to support
111 # FIXME(skullydazed): This should be populated into info.json and read from there instead
112 if 'LAYOUTS' in rules and info_data.get('layouts'):
113 # Match these up against the supplied layouts
114 supported_layouts = rules['LAYOUTS'].strip().split()
115 for layout_name in sorted(info_data['layouts']):
116 layout_name = layout_name[7:]
117
118 if layout_name in supported_layouts:
119 supported_layouts.remove(layout_name)
120
121 if supported_layouts:
122 for supported_layout in supported_layouts:
123 _log_error(info_data, 'Claims to support community layout %s but no LAYOUT_%s() macro found' % (supported_layout, supported_layout))
124
50 return info_data 125 return info_data
51 126
52 127
53def _extract_config_h(info_data): 128def _json_load(json_file):
54 """Pull some keyboard information from existing rules.mk files 129 """Load a json file from disk.
130
131 Note: file must be a Path object.
132 """
133 try:
134 return json.load(json_file.open())
135
136 except json.decoder.JSONDecodeError as e:
137 cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
138 exit(1)
139
140
141def _jsonschema(schema_name):
142 """Read a jsonschema file from disk.
143 """
144 schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
145
146 if not schema_path.exists():
147 schema_path = Path('data/schemas/false.jsonschema')
148
149 return _json_load(schema_path)
150
151
152def keyboard_validate(data):
153 """Validates data against the keyboard jsonschema.
154 """
155 schema = _jsonschema('keyboard')
156 validator = jsonschema.Draft7Validator(schema).validate
157
158 return validator(data)
159
160
161def keyboard_api_validate(data):
162 """Validates data against the api_keyboard jsonschema.
163 """
164 base = _jsonschema('keyboard')
165 relative = _jsonschema('api_keyboard')
166 resolver = jsonschema.RefResolver.from_schema(base)
167 validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate
168
169 return validator(data)
170
171
172def _extract_debounce(info_data, config_c):
173 """Handle debounce.
174 """
175 if 'debounce' in info_data and 'DEBOUNCE' in config_c:
176 _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.')
177
178 if 'DEBOUNCE' in config_c:
179 info_data['debounce'] = int(config_c['DEBOUNCE'])
180
181 return info_data
182
183
184def _extract_diode_direction(info_data, config_c):
185 """Handle the diode direction.
186 """
187 if 'diode_direction' in info_data and 'DIODE_DIRECTION' in config_c:
188 _log_warning(info_data, 'Diode direction is specified in both info.json and config.h, the config.h value wins.')
189
190 if 'DIODE_DIRECTION' in config_c:
191 info_data['diode_direction'] = config_c.get('DIODE_DIRECTION')
192
193 return info_data
194
195
196def _extract_indicators(info_data, config_c):
197 """Find the LED indicator information.
198 """
199 for json_key, config_key in LED_INDICATORS.items():
200 if json_key in info_data.get('indicators', []) and config_key in config_c:
201 _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.')
202
203 if 'indicators' not in info_data:
204 info_data['indicators'] = {}
205
206 if config_key in config_c:
207 if 'indicators' not in info_data:
208 info_data['indicators'] = {}
209
210 info_data['indicators'][json_key] = config_c.get(config_key)
211
212 return info_data
213
214
215def _extract_community_layouts(info_data, rules):
216 """Find the community layouts in rules.mk.
217 """
218 community_layouts = rules['LAYOUTS'].split() if 'LAYOUTS' in rules else []
219
220 if 'community_layouts' in info_data:
221 for layout in community_layouts:
222 if layout not in info_data['community_layouts']:
223 community_layouts.append(layout)
224
225 else:
226 info_data['community_layouts'] = community_layouts
227
228 return info_data
229
230
231def _extract_features(info_data, rules):
232 """Find all the features enabled in rules.mk.
233 """
234 # Special handling for bootmagic which also supports a "lite" mode.
235 if rules.get('BOOTMAGIC_ENABLE') == 'lite':
236 rules['BOOTMAGIC_LITE_ENABLE'] = 'on'
237 del rules['BOOTMAGIC_ENABLE']
238 if rules.get('BOOTMAGIC_ENABLE') == 'full':
239 rules['BOOTMAGIC_ENABLE'] = 'on'
240
241 # Skip non-boolean features we haven't implemented special handling for
242 for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE':
243 if rules.get(feature):
244 del rules[feature]
245
246 # Process the rest of the rules as booleans
247 for key, value in rules.items():
248 if key.endswith('_ENABLE'):
249 key = '_'.join(key.split('_')[:-1]).lower()
250 value = True if value.lower() in true_values else False if value.lower() in false_values else value
251
252 if 'config_h_features' not in info_data:
253 info_data['config_h_features'] = {}
254
255 if 'features' not in info_data:
256 info_data['features'] = {}
257
258 if key in info_data['features']:
259 _log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,))
260
261 info_data['features'][key] = value
262 info_data['config_h_features'][key] = value
263
264 return info_data
265
266
267def _extract_led_drivers(info_data, rules):
268 """Find all the LED drivers set in rules.mk.
269 """
270 if 'LED_MATRIX_DRIVER' in rules:
271 if 'led_matrix' not in info_data:
272 info_data['led_matrix'] = {}
273
274 if info_data['led_matrix'].get('driver'):
275 _log_warning(info_data, 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.')
276
277 info_data['led_matrix']['driver'] = rules['LED_MATRIX_DRIVER']
278
279 return info_data
280
281
282def _extract_led_matrix(info_data, config_c):
283 """Handle the led_matrix configuration.
284 """
285 led_matrix = info_data.get('led_matrix', {})
286
287 for json_key, config_key in led_matrix_properties.items():
288 if config_key in config_c:
289 if json_key in led_matrix:
290 _log_warning(info_data, 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
291
292 led_matrix[json_key] = config_c[config_key]
293
294
295def _extract_rgblight(info_data, config_c):
296 """Handle the rgblight configuration.
297 """
298 rgblight = info_data.get('rgblight', {})
299 animations = rgblight.get('animations', {})
300
301 if 'RGBLED_SPLIT' in config_c:
302 raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip()
303 rgblight['split_count'] = [int(i) for i in raw_split.split(',')]
304
305 for json_key, config_key_type in rgblight_properties.items():
306 config_key, config_type = config_key_type
307
308 if config_key in config_c:
309 if json_key in rgblight:
310 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
311
312 try:
313 rgblight[json_key] = config_type(config_c[config_key])
314 except ValueError as e:
315 cli.log.error('%s: config.h: Could not convert "%s" to %s: %s', info_data['keyboard_folder'], config_c[config_key], config_type.__name__, e)
316
317 for json_key, config_key in rgblight_toggles.items():
318 if config_key in config_c and json_key in rgblight:
319 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key)
320
321 rgblight[json_key] = config_key in config_c
322
323 for json_key, config_key in rgblight_animations.items():
324 if config_key in config_c:
325 if json_key in animations:
326 _log_warning(info_data, 'RGB Light: animations: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
327
328 animations[json_key] = config_c[config_key]
329
330 if animations:
331 rgblight['animations'] = animations
332
333 if rgblight:
334 info_data['rgblight'] = rgblight
335
336 return info_data
337
338
339def _pin_name(pin):
340 """Returns the proper representation for a pin.
341 """
342 pin = pin.strip()
343
344 if not pin:
345 return None
346
347 elif pin.isdigit():
348 return int(pin)
349
350 elif pin == 'NO_PIN':
351 return None
352
353 elif pin[0] in 'ABCDEFGHIJK' and pin[1].isdigit():
354 return pin
355
356 raise ValueError(f'Invalid pin: {pin}')
357
358
359def _extract_pins(pins):
360 """Returns a list of pins from a comma separated string of pins.
361 """
362 return [_pin_name(pin) for pin in pins.split(',')]
363
364
365def _extract_direct_matrix(info_data, direct_pins):
366 """
367 """
368 info_data['matrix_pins'] = {}
369 direct_pin_array = []
370
371 while direct_pins[-1] != '}':
372 direct_pins = direct_pins[:-1]
373
374 for row in direct_pins.split('},{'):
375 if row.startswith('{'):
376 row = row[1:]
377
378 if row.endswith('}'):
379 row = row[:-1]
380
381 direct_pin_array.append([])
382
383 for pin in row.split(','):
384 if pin == 'NO_PIN':
385 pin = None
386
387 direct_pin_array[-1].append(pin)
388
389 return direct_pin_array
390
391
392def _extract_matrix_info(info_data, config_c):
393 """Populate the matrix information.
55 """ 394 """
56 config_c = config_h(info_data['keyboard_folder'])
57 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() 395 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
58 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() 396 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
59 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] 397 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
60 398
61 info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') 399 if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
62 info_data['matrix_size'] = { 400 if 'matrix_size' in info_data:
63 'rows': compute(config_c.get('MATRIX_ROWS', '0')), 401 _log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.')
64 'cols': compute(config_c.get('MATRIX_COLS', '0')),
65 }
66 info_data['matrix_pins'] = {}
67 402
68 if row_pins: 403 info_data['matrix_size'] = {
69 info_data['matrix_pins']['rows'] = row_pins.split(',') 404 'cols': compute(config_c.get('MATRIX_COLS', '0')),
70 if col_pins: 405 'rows': compute(config_c.get('MATRIX_ROWS', '0')),
71 info_data['matrix_pins']['cols'] = col_pins.split(',') 406 }
407
408 if row_pins and col_pins:
409 if 'matrix_pins' in info_data:
410 _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
411
412 info_data['matrix_pins'] = {
413 'cols': _extract_pins(col_pins),
414 'rows': _extract_pins(row_pins),
415 }
72 416
73 if direct_pins: 417 if direct_pins:
74 direct_pin_array = [] 418 if 'matrix_pins' in info_data:
75 for row in direct_pins.split('},{'): 419 _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
76 if row.startswith('{'):
77 row = row[1:]
78 if row.endswith('}'):
79 row = row[:-1]
80 420
81 direct_pin_array.append([]) 421 info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins)
82 422
83 for pin in row.split(','): 423 return info_data
84 if pin == 'NO_PIN':
85 pin = None
86 424
87 direct_pin_array[-1].append(pin)
88 425
89 info_data['matrix_pins']['direct'] = direct_pin_array 426def _extract_usb_info(info_data, config_c):
427 """Populate the USB information.
428 """
429 if 'usb' not in info_data:
430 info_data['usb'] = {}
90 431
91 info_data['usb'] = { 432 for info_name, config_name in usb_properties.items():
92 'vid': config_c.get('VENDOR_ID'), 433 if config_name in config_c:
93 'pid': config_c.get('PRODUCT_ID'), 434 if info_name in info_data['usb']:
94 'device_ver': config_c.get('DEVICE_VER'), 435 _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name))
95 'manufacturer': config_c.get('MANUFACTURER'), 436
96 'product': config_c.get('PRODUCT'), 437 info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper()
97 } 438
439 return info_data
440
441
442def _extract_config_h(info_data):
443 """Pull some keyboard information from existing config.h files
444 """
445 config_c = config_h(info_data['keyboard_folder'])
446
447 _extract_debounce(info_data, config_c)
448 _extract_diode_direction(info_data, config_c)
449 _extract_indicators(info_data, config_c)
450 _extract_matrix_info(info_data, config_c)
451 _extract_usb_info(info_data, config_c)
452 _extract_led_matrix(info_data, config_c)
453 _extract_rgblight(info_data, config_c)
98 454
99 return info_data 455 return info_data
100 456
@@ -106,16 +462,53 @@ def _extract_rules_mk(info_data):
106 mcu = rules.get('MCU') 462 mcu = rules.get('MCU')
107 463
108 if mcu in CHIBIOS_PROCESSORS: 464 if mcu in CHIBIOS_PROCESSORS:
109 return arm_processor_rules(info_data, rules) 465 arm_processor_rules(info_data, rules)
110 466
111 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: 467 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS:
112 return avr_processor_rules(info_data, rules) 468 avr_processor_rules(info_data, rules)
113 469
114 msg = "Unknown MCU: " + str(mcu) 470 else:
471 cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu))
472 unknown_processor_rules(info_data, rules)
115 473
116 _log_warning(info_data, msg) 474 _extract_community_layouts(info_data, rules)
475 _extract_features(info_data, rules)
476 _extract_led_drivers(info_data, rules)
117 477
118 return unknown_processor_rules(info_data, rules) 478 return info_data
479
480
481def _merge_layouts(info_data, new_info_data):
482 """Merge new_info_data into info_data in an intelligent way.
483 """
484 for layout_name, layout_json in new_info_data['layouts'].items():
485 if layout_name in info_data['layouts']:
486 # Pull in layouts we have a macro for
487 if len(info_data['layouts'][layout_name]['layout']) != len(layout_json['layout']):
488 msg = '%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s'
489 _log_error(info_data, msg % (info_data['keyboard_folder'], layout_name, len(layout_json['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
490 else:
491 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
492 key.update(layout_json['layout'][i])
493 else:
494 # Pull in layouts that have matrix data
495 missing_matrix = False
496 for key in layout_json.get('layout', {}):
497 if 'matrix' not in key:
498 missing_matrix = True
499
500 if not missing_matrix:
501 if layout_name in info_data['layouts']:
502 # Update an existing layout with new data
503 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
504 key.update(layout_json['layout'][i])
505
506 else:
507 # Copy in the new layout wholesale
508 layout_json['c_macro'] = False
509 info_data['layouts'][layout_name] = layout_json
510
511 return info_data
119 512
120 513
121def _search_keyboard_h(path): 514def _search_keyboard_h(path):
@@ -131,34 +524,21 @@ def _search_keyboard_h(path):
131 return layouts 524 return layouts
132 525
133 526
134def _find_all_layouts(info_data, keyboard, rules): 527def _find_all_layouts(info_data, keyboard):
135 """Looks for layout macros associated with this keyboard. 528 """Looks for layout macros associated with this keyboard.
136 """ 529 """
137 layouts = _search_keyboard_h(Path(keyboard)) 530 layouts = _search_keyboard_h(Path(keyboard))
138 531
139 if not layouts: 532 if not layouts:
140 # If we didn't find any layouts above we widen our search. This is error 533 # 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.
141 # prone which is why we want to encourage people to follow the standard above. 534 info_data['parse_warnings'].append('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
142 _log_warning(info_data, 'Falling back to searching for KEYMAP/LAYOUT macros.') 535
143 for file in glob('keyboards/%s/*.h' % keyboard): 536 for file in glob('keyboards/%s/*.h' % keyboard):
144 if file.endswith('.h'): 537 if file.endswith('.h'):
145 these_layouts = find_layouts(file) 538 these_layouts = find_layouts(file)
146 if these_layouts: 539 if these_layouts:
147 layouts.update(these_layouts) 540 layouts.update(these_layouts)
148 541
149 if 'LAYOUTS' in rules:
150 # Match these up against the supplied layouts
151 supported_layouts = rules['LAYOUTS'].strip().split()
152 for layout_name in sorted(layouts):
153 if not layout_name.startswith('LAYOUT_'):
154 continue
155 layout_name = layout_name[7:]
156 if layout_name in supported_layouts:
157 supported_layouts.remove(layout_name)
158
159 if supported_layouts:
160 _log_error(info_data, 'Missing LAYOUT() macro for %s' % (', '.join(supported_layouts)))
161
162 return layouts 542 return layouts
163 543
164 544
@@ -180,13 +560,29 @@ def arm_processor_rules(info_data, rules):
180 """Setup the default info for an ARM board. 560 """Setup the default info for an ARM board.
181 """ 561 """
182 info_data['processor_type'] = 'arm' 562 info_data['processor_type'] = 'arm'
183 info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'unknown'
184 info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown'
185 info_data['protocol'] = 'ChibiOS' 563 info_data['protocol'] = 'ChibiOS'
186 564
187 if info_data['bootloader'] == 'unknown': 565 if 'MCU' in rules:
566 if 'processor' in info_data:
567 _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.')
568
569 info_data['processor'] = rules['MCU']
570
571 elif 'processor' not in info_data:
572 info_data['processor'] = 'unknown'
573
574 if 'BOOTLOADER' in rules:
575 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
576 # if 'bootloader' in info_data:
577 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
578
579 info_data['bootloader'] = rules['BOOTLOADER']
580
581 else:
188 if 'STM32' in info_data['processor']: 582 if 'STM32' in info_data['processor']:
189 info_data['bootloader'] = 'stm32-dfu' 583 info_data['bootloader'] = 'stm32-dfu'
584 else:
585 info_data['bootloader'] = 'unknown'
190 586
191 if 'STM32' in info_data['processor']: 587 if 'STM32' in info_data['processor']:
192 info_data['platform'] = 'STM32' 588 info_data['platform'] = 'STM32'
@@ -204,9 +600,26 @@ def avr_processor_rules(info_data, rules):
204 info_data['processor_type'] = 'avr' 600 info_data['processor_type'] = 'avr'
205 info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu' 601 info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu'
206 info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' 602 info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown'
207 info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown'
208 info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' 603 info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA'
209 604
605 if 'MCU' in rules:
606 if 'processor' in info_data:
607 _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.')
608
609 info_data['processor'] = rules['MCU']
610
611 elif 'processor' not in info_data:
612 info_data['processor'] = 'unknown'
613
614 if 'BOOTLOADER' in rules:
615 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
616 # if 'bootloader' in info_data:
617 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
618
619 info_data['bootloader'] = rules['BOOTLOADER']
620 else:
621 info_data['bootloader'] = 'atmel-dfu'
622
210 # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: 623 # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk:
211 # info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA' 624 # info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA'
212 625
@@ -231,10 +644,12 @@ def merge_info_jsons(keyboard, info_data):
231 for info_file in find_info_json(keyboard): 644 for info_file in find_info_json(keyboard):
232 # Load and validate the JSON data 645 # Load and validate the JSON data
233 try: 646 try:
234 with info_file.open('r') as info_fd: 647 new_info_data = _json_load(info_file)
235 new_info_data = json.load(info_fd) 648 keyboard_validate(new_info_data)
236 except Exception as e: 649
237 _log_error(info_data, "Invalid JSON in file %s: %s: %s" % (str(info_file), e.__class__.__name__, e)) 650 except jsonschema.ValidationError as e:
651 json_path = '.'.join([str(p) for p in e.absolute_path])
652 cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message)
238 continue 653 continue
239 654
240 if not isinstance(new_info_data, dict): 655 if not isinstance(new_info_data, dict):
@@ -242,21 +657,30 @@ def merge_info_jsons(keyboard, info_data):
242 continue 657 continue
243 658
244 # Copy whitelisted keys into `info_data` 659 # Copy whitelisted keys into `info_data`
245 for key in ('keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'): 660 for key in ('debounce', 'diode_direction', 'indicators', 'keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'):
246 if key in new_info_data: 661 if key in new_info_data:
247 info_data[key] = new_info_data[key] 662 info_data[key] = new_info_data[key]
248 663
249 # Merge the layouts in 664 # Deep merge certain keys
665 # FIXME(skullydazed/anyone): this should be generalized more so that we can inteligently merge more than one level deep. It would be nice if we could filter on valid keys too. That may have to wait for a future where we use openapi or something.
666 for key in ('features', 'layout_aliases', 'led_matrix', 'matrix_pins', 'rgblight', 'usb'):
667 if key in new_info_data:
668 if key not in info_data:
669 info_data[key] = {}
670
671 info_data[key].update(new_info_data[key])
672
673 # Merge the layouts
674 if 'community_layouts' in new_info_data:
675 if 'community_layouts' in info_data:
676 for layout in new_info_data['community_layouts']:
677 if layout not in info_data['community_layouts']:
678 info_data['community_layouts'].append(layout)
679 else:
680 info_data['community_layouts'] = new_info_data['community_layouts']
681
250 if 'layouts' in new_info_data: 682 if 'layouts' in new_info_data:
251 for layout_name, json_layout in new_info_data['layouts'].items(): 683 _merge_layouts(info_data, new_info_data)
252 # Only pull in layouts we have a macro for
253 if layout_name in info_data['layouts']:
254 if info_data['layouts'][layout_name]['key_count'] != len(json_layout['layout']):
255 msg = '%s: Number of elements in info.json does not match! info.json:%s != %s:%s'
256 _log_error(info_data, msg % (layout_name, len(json_layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
257 else:
258 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
259 key.update(json_layout['layout'][i])
260 684
261 return info_data 685 return info_data
262 686