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.py457
1 files changed, 373 insertions, 84 deletions
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index f476dc666..2accaba9e 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -1,9 +1,13 @@
1"""Functions that help us generate and use info.json files. 1"""Functions that help us generate and use info.json files.
2""" 2"""
3import json 3import json
4from collections.abc import Mapping
4from glob import glob 5from glob import glob
5from pathlib import Path 6from pathlib import Path
6 7
8import hjson
9import jsonschema
10from dotty_dict import dotty
7from milc import cli 11from milc import cli
8 12
9from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS 13from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
@@ -13,6 +17,9 @@ from qmk.keymap import list_keymaps
13from qmk.makefile import parse_rules_mk_file 17from qmk.makefile import parse_rules_mk_file
14from qmk.math import compute 18from qmk.math import compute
15 19
20true_values = ['1', 'on', 'yes']
21false_values = ['0', 'off', 'no']
22
16 23
17def info_json(keyboard): 24def info_json(keyboard):
18 """Generate the info.json data for a specific keyboard. 25 """Generate the info.json data for a specific keyboard.
@@ -38,8 +45,9 @@ def info_json(keyboard):
38 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} 45 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'}
39 46
40 # Populate layout data 47 # Populate layout data
41 for layout_name, layout_json in _find_all_layouts(info_data, keyboard, rules).items(): 48 for layout_name, layout_json in _find_all_layouts(info_data, keyboard).items():
42 if not layout_name.startswith('LAYOUT_kc'): 49 if not layout_name.startswith('LAYOUT_kc'):
50 layout_json['c_macro'] = True
43 info_data['layouts'][layout_name] = layout_json 51 info_data['layouts'][layout_name] = layout_json
44 52
45 # Merge in the data from info.json, config.h, and rules.mk 53 # Merge in the data from info.json, config.h, and rules.mk
@@ -47,54 +55,259 @@ def info_json(keyboard):
47 info_data = _extract_config_h(info_data) 55 info_data = _extract_config_h(info_data)
48 info_data = _extract_rules_mk(info_data) 56 info_data = _extract_rules_mk(info_data)
49 57
58 # Validate against the jsonschema
59 try:
60 keyboard_api_validate(info_data)
61
62 except jsonschema.ValidationError as e:
63 json_path = '.'.join([str(p) for p in e.absolute_path])
64 cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message)
65 exit()
66
67 # Make sure we have at least one layout
68 if not info_data.get('layouts'):
69 _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.')
70
71 # Make sure we supply layout macros for the community layouts we claim to support
72 # FIXME(skullydazed): This should be populated into info.json and read from there instead
73 if 'LAYOUTS' in rules and info_data.get('layouts'):
74 # Match these up against the supplied layouts
75 supported_layouts = rules['LAYOUTS'].strip().split()
76 for layout_name in sorted(info_data['layouts']):
77 layout_name = layout_name[7:]
78
79 if layout_name in supported_layouts:
80 supported_layouts.remove(layout_name)
81
82 if supported_layouts:
83 for supported_layout in supported_layouts:
84 _log_error(info_data, 'Claims to support community layout %s but no LAYOUT_%s() macro found' % (supported_layout, supported_layout))
85
50 return info_data 86 return info_data
51 87
52 88
53def _extract_config_h(info_data): 89def _json_load(json_file):
54 """Pull some keyboard information from existing rules.mk files 90 """Load a json file from disk.
91
92 Note: file must be a Path object.
93 """
94 try:
95 return hjson.load(json_file.open())
96
97 except json.decoder.JSONDecodeError as e:
98 cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
99 exit(1)
100
101
102def _jsonschema(schema_name):
103 """Read a jsonschema file from disk.
104
105 FIXME(skullydazed/anyone): Refactor to make this a public function.
106 """
107 schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
108
109 if not schema_path.exists():
110 schema_path = Path('data/schemas/false.jsonschema')
111
112 return _json_load(schema_path)
113
114
115def keyboard_validate(data):
116 """Validates data against the keyboard jsonschema.
117 """
118 schema = _jsonschema('keyboard')
119 validator = jsonschema.Draft7Validator(schema).validate
120
121 return validator(data)
122
123
124def keyboard_api_validate(data):
125 """Validates data against the api_keyboard jsonschema.
126 """
127 base = _jsonschema('keyboard')
128 relative = _jsonschema('api_keyboard')
129 resolver = jsonschema.RefResolver.from_schema(base)
130 validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate
131
132 return validator(data)
133
134
135def _extract_features(info_data, rules):
136 """Find all the features enabled in rules.mk.
137 """
138 # Special handling for bootmagic which also supports a "lite" mode.
139 if rules.get('BOOTMAGIC_ENABLE') == 'lite':
140 rules['BOOTMAGIC_LITE_ENABLE'] = 'on'
141 del rules['BOOTMAGIC_ENABLE']
142 if rules.get('BOOTMAGIC_ENABLE') == 'full':
143 rules['BOOTMAGIC_ENABLE'] = 'on'
144
145 # Skip non-boolean features we haven't implemented special handling for
146 for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE':
147 if rules.get(feature):
148 del rules[feature]
149
150 # Process the rest of the rules as booleans
151 for key, value in rules.items():
152 if key.endswith('_ENABLE'):
153 key = '_'.join(key.split('_')[:-1]).lower()
154 value = True if value.lower() in true_values else False if value.lower() in false_values else value
155
156 if 'config_h_features' not in info_data:
157 info_data['config_h_features'] = {}
158
159 if 'features' not in info_data:
160 info_data['features'] = {}
161
162 if key in info_data['features']:
163 _log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,))
164
165 info_data['features'][key] = value
166 info_data['config_h_features'][key] = value
167
168 return info_data
169
170
171def _pin_name(pin):
172 """Returns the proper representation for a pin.
173 """
174 pin = pin.strip()
175
176 if not pin:
177 return None
178
179 elif pin.isdigit():
180 return int(pin)
181
182 elif pin == 'NO_PIN':
183 return None
184
185 elif pin[0] in 'ABCDEFGHIJK' and pin[1].isdigit():
186 return pin
187
188 raise ValueError(f'Invalid pin: {pin}')
189
190
191def _extract_pins(pins):
192 """Returns a list of pins from a comma separated string of pins.
193 """
194 return [_pin_name(pin) for pin in pins.split(',')]
195
196
197def _extract_direct_matrix(info_data, direct_pins):
198 """
199 """
200 info_data['matrix_pins'] = {}
201 direct_pin_array = []
202
203 while direct_pins[-1] != '}':
204 direct_pins = direct_pins[:-1]
205
206 for row in direct_pins.split('},{'):
207 if row.startswith('{'):
208 row = row[1:]
209
210 if row.endswith('}'):
211 row = row[:-1]
212
213 direct_pin_array.append([])
214
215 for pin in row.split(','):
216 if pin == 'NO_PIN':
217 pin = None
218
219 direct_pin_array[-1].append(pin)
220
221 return direct_pin_array
222
223
224def _extract_matrix_info(info_data, config_c):
225 """Populate the matrix information.
55 """ 226 """
56 config_c = config_h(info_data['keyboard_folder'])
57 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() 227 row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
58 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() 228 col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
59 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] 229 direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
60 230
61 info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') 231 if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
62 info_data['matrix_size'] = { 232 if 'matrix_size' in info_data:
63 'rows': compute(config_c.get('MATRIX_ROWS', '0')), 233 _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')), 234
65 } 235 info_data['matrix_size'] = {
66 info_data['matrix_pins'] = {} 236 'cols': compute(config_c.get('MATRIX_COLS', '0')),
237 'rows': compute(config_c.get('MATRIX_ROWS', '0')),
238 }
67 239
68 if row_pins: 240 if row_pins and col_pins:
69 info_data['matrix_pins']['rows'] = row_pins.split(',') 241 if 'matrix_pins' in info_data:
70 if col_pins: 242 _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
71 info_data['matrix_pins']['cols'] = col_pins.split(',') 243
244 info_data['matrix_pins'] = {
245 'cols': _extract_pins(col_pins),
246 'rows': _extract_pins(row_pins),
247 }
72 248
73 if direct_pins: 249 if direct_pins:
74 direct_pin_array = [] 250 if 'matrix_pins' in info_data:
75 for row in direct_pins.split('},{'): 251 _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 252
81 direct_pin_array.append([]) 253 info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins)
82 254
83 for pin in row.split(','): 255 return info_data
84 if pin == 'NO_PIN':
85 pin = None
86 256
87 direct_pin_array[-1].append(pin)
88 257
89 info_data['matrix_pins']['direct'] = direct_pin_array 258def _extract_config_h(info_data):
259 """Pull some keyboard information from existing config.h files
260 """
261 config_c = config_h(info_data['keyboard_folder'])
90 262
91 info_data['usb'] = { 263 # Pull in data from the json map
92 'vid': config_c.get('VENDOR_ID'), 264 dotty_info = dotty(info_data)
93 'pid': config_c.get('PRODUCT_ID'), 265 info_config_map = _json_load(Path('data/mappings/info_config.json'))
94 'device_ver': config_c.get('DEVICE_VER'), 266
95 'manufacturer': config_c.get('MANUFACTURER'), 267 for config_key, info_dict in info_config_map.items():
96 'product': config_c.get('PRODUCT'), 268 info_key = info_dict['info_key']
97 } 269 key_type = info_dict.get('value_type', 'str')
270
271 try:
272 if config_key in config_c and info_dict.get('to_json', True):
273 if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
274 _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key))
275
276 if key_type.startswith('array'):
277 if '.' in key_type:
278 key_type, array_type = key_type.split('.', 1)
279 else:
280 array_type = None
281
282 config_value = config_c[config_key].replace('{', '').replace('}', '').strip()
283
284 if array_type == 'int':
285 dotty_info[info_key] = list(map(int, config_value.split(',')))
286 else:
287 dotty_info[info_key] = config_value.split(',')
288
289 elif key_type == 'bool':
290 dotty_info[info_key] = config_c[config_key] in true_values
291
292 elif key_type == 'hex':
293 dotty_info[info_key] = '0x' + config_c[config_key][2:].upper()
294
295 elif key_type == 'list':
296 dotty_info[info_key] = config_c[config_key].split()
297
298 elif key_type == 'int':
299 dotty_info[info_key] = int(config_c[config_key])
300
301 else:
302 dotty_info[info_key] = config_c[config_key]
303
304 except Exception as e:
305 _log_warning(info_data, f'{config_key}->{info_key}: {e}')
306
307 info_data.update(dotty_info)
308
309 # Pull data that easily can't be mapped in json
310 _extract_matrix_info(info_data, config_c)
98 311
99 return info_data 312 return info_data
100 313
@@ -103,19 +316,101 @@ def _extract_rules_mk(info_data):
103 """Pull some keyboard information from existing rules.mk files 316 """Pull some keyboard information from existing rules.mk files
104 """ 317 """
105 rules = rules_mk(info_data['keyboard_folder']) 318 rules = rules_mk(info_data['keyboard_folder'])
106 mcu = rules.get('MCU') 319 info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4'))
320
321 if info_data['processor'] in CHIBIOS_PROCESSORS:
322 arm_processor_rules(info_data, rules)
323
324 elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS:
325 avr_processor_rules(info_data, rules)
326
327 else:
328 cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor']))
329 unknown_processor_rules(info_data, rules)
330
331 # Pull in data from the json map
332 dotty_info = dotty(info_data)
333 info_rules_map = _json_load(Path('data/mappings/info_rules.json'))
107 334
108 if mcu in CHIBIOS_PROCESSORS: 335 for rules_key, info_dict in info_rules_map.items():
109 return arm_processor_rules(info_data, rules) 336 info_key = info_dict['info_key']
337 key_type = info_dict.get('value_type', 'str')
110 338
111 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: 339 try:
112 return avr_processor_rules(info_data, rules) 340 if rules_key in rules and info_dict.get('to_json', True):
341 if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
342 _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key))
343
344 if key_type.startswith('array'):
345 if '.' in key_type:
346 key_type, array_type = key_type.split('.', 1)
347 else:
348 array_type = None
113 349
114 msg = "Unknown MCU: " + str(mcu) 350 rules_value = rules[rules_key].replace('{', '').replace('}', '').strip()
351
352 if array_type == 'int':
353 dotty_info[info_key] = list(map(int, rules_value.split(',')))
354 else:
355 dotty_info[info_key] = rules_value.split(',')
115 356
116 _log_warning(info_data, msg) 357 elif key_type == 'list':
358 dotty_info[info_key] = rules[rules_key].split()
117 359
118 return unknown_processor_rules(info_data, rules) 360 elif key_type == 'bool':
361 dotty_info[info_key] = rules[rules_key] in true_values
362
363 elif key_type == 'hex':
364 dotty_info[info_key] = '0x' + rules[rules_key][2:].upper()
365
366 elif key_type == 'int':
367 dotty_info[info_key] = int(rules[rules_key])
368
369 else:
370 dotty_info[info_key] = rules[rules_key]
371
372 except Exception as e:
373 _log_warning(info_data, f'{rules_key}->{info_key}: {e}')
374
375 info_data.update(dotty_info)
376
377 # Merge in config values that can't be easily mapped
378 _extract_features(info_data, rules)
379
380 return info_data
381
382
383def _merge_layouts(info_data, new_info_data):
384 """Merge new_info_data into info_data in an intelligent way.
385 """
386 for layout_name, layout_json in new_info_data['layouts'].items():
387 if layout_name in info_data['layouts']:
388 # Pull in layouts we have a macro for
389 if len(info_data['layouts'][layout_name]['layout']) != len(layout_json['layout']):
390 msg = '%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s'
391 _log_error(info_data, msg % (info_data['keyboard_folder'], layout_name, len(layout_json['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
392 else:
393 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
394 key.update(layout_json['layout'][i])
395 else:
396 # Pull in layouts that have matrix data
397 missing_matrix = False
398 for key in layout_json.get('layout', {}):
399 if 'matrix' not in key:
400 missing_matrix = True
401
402 if not missing_matrix:
403 if layout_name in info_data['layouts']:
404 # Update an existing layout with new data
405 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
406 key.update(layout_json['layout'][i])
407
408 else:
409 # Copy in the new layout wholesale
410 layout_json['c_macro'] = False
411 info_data['layouts'][layout_name] = layout_json
412
413 return info_data
119 414
120 415
121def _search_keyboard_h(path): 416def _search_keyboard_h(path):
@@ -131,34 +426,21 @@ def _search_keyboard_h(path):
131 return layouts 426 return layouts
132 427
133 428
134def _find_all_layouts(info_data, keyboard, rules): 429def _find_all_layouts(info_data, keyboard):
135 """Looks for layout macros associated with this keyboard. 430 """Looks for layout macros associated with this keyboard.
136 """ 431 """
137 layouts = _search_keyboard_h(Path(keyboard)) 432 layouts = _search_keyboard_h(Path(keyboard))
138 433
139 if not layouts: 434 if not layouts:
140 # If we didn't find any layouts above we widen our search. This is error 435 # 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. 436 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.') 437
143 for file in glob('keyboards/%s/*.h' % keyboard): 438 for file in glob('keyboards/%s/*.h' % keyboard):
144 if file.endswith('.h'): 439 if file.endswith('.h'):
145 these_layouts = find_layouts(file) 440 these_layouts = find_layouts(file)
146 if these_layouts: 441 if these_layouts:
147 layouts.update(these_layouts) 442 layouts.update(these_layouts)
148 443
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 444 return layouts
163 445
164 446
@@ -180,13 +462,13 @@ def arm_processor_rules(info_data, rules):
180 """Setup the default info for an ARM board. 462 """Setup the default info for an ARM board.
181 """ 463 """
182 info_data['processor_type'] = 'arm' 464 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' 465 info_data['protocol'] = 'ChibiOS'
186 466
187 if info_data['bootloader'] == 'unknown': 467 if 'bootloader' not in info_data:
188 if 'STM32' in info_data['processor']: 468 if 'STM32' in info_data['processor']:
189 info_data['bootloader'] = 'stm32-dfu' 469 info_data['bootloader'] = 'stm32-dfu'
470 else:
471 info_data['bootloader'] = 'unknown'
190 472
191 if 'STM32' in info_data['processor']: 473 if 'STM32' in info_data['processor']:
192 info_data['platform'] = 'STM32' 474 info_data['platform'] = 'STM32'
@@ -202,11 +484,12 @@ def avr_processor_rules(info_data, rules):
202 """Setup the default info for an AVR board. 484 """Setup the default info for an AVR board.
203 """ 485 """
204 info_data['processor_type'] = 'avr' 486 info_data['processor_type'] = 'avr'
205 info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu'
206 info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' 487 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' 488 info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA'
209 489
490 if 'bootloader' not in info_data:
491 info_data['bootloader'] = 'atmel-dfu'
492
210 # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: 493 # 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' 494 # info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA'
212 495
@@ -225,38 +508,44 @@ def unknown_processor_rules(info_data, rules):
225 return info_data 508 return info_data
226 509
227 510
511def deep_update(origdict, newdict):
512 """Update a dictionary in place, recursing to do a deep copy.
513 """
514 for key, value in newdict.items():
515 if isinstance(value, Mapping):
516 origdict[key] = deep_update(origdict.get(key, {}), value)
517
518 else:
519 origdict[key] = value
520
521 return origdict
522
523
228def merge_info_jsons(keyboard, info_data): 524def merge_info_jsons(keyboard, info_data):
229 """Return a merged copy of all the info.json files for a keyboard. 525 """Return a merged copy of all the info.json files for a keyboard.
230 """ 526 """
231 for info_file in find_info_json(keyboard): 527 for info_file in find_info_json(keyboard):
232 # Load and validate the JSON data 528 # Load and validate the JSON data
233 try: 529 new_info_data = _json_load(info_file)
234 with info_file.open('r') as info_fd:
235 new_info_data = json.load(info_fd)
236 except Exception as e:
237 _log_error(info_data, "Invalid JSON in file %s: %s: %s" % (str(info_file), e.__class__.__name__, e))
238 continue
239 530
240 if not isinstance(new_info_data, dict): 531 if not isinstance(new_info_data, dict):
241 _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) 532 _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
242 continue 533 continue
243 534
244 # Copy whitelisted keys into `info_data` 535 try:
245 for key in ('keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'): 536 keyboard_validate(new_info_data)
246 if key in new_info_data: 537 except jsonschema.ValidationError as e:
247 info_data[key] = new_info_data[key] 538 json_path = '.'.join([str(p) for p in e.absolute_path])
539 cli.log.error('Not including data from file: %s', info_file)
540 cli.log.error('\t%s: %s', json_path, e.message)
541 continue
542
543 # Mark the layouts as coming from json
544 for layout in new_info_data.get('layouts', {}).values():
545 layout['c_macro'] = False
248 546
249 # Merge the layouts in 547 # Update info_data with the new data
250 if 'layouts' in new_info_data: 548 deep_update(info_data, new_info_data)
251 for layout_name, json_layout in new_info_data['layouts'].items():
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 549
261 return info_data 550 return info_data
262 551