From 47b9b110097a864d6ab76516b2213afd59948527 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 30 Dec 2020 10:27:37 -0800 Subject: Configure keyboard matrix from info.json (#10817) * Make parameters from info.json available to the build system * move all clueboard settings to info.json * code formatting * make flake8 happy * make flake8 happy * make qmk lint happy * Add support for specifying led indicators in json * move led indicators to the clueboard info.json * Apply suggestions from code review Co-authored-by: Erovia <Erovia@users.noreply.github.com> * add missing docstring Co-authored-by: Erovia <Erovia@users.noreply.github.com> --- lib/python/qmk/cli/c2json.py | 3 +- lib/python/qmk/cli/chibios/confmigrate.py | 8 +- lib/python/qmk/cli/generate/__init__.py | 4 + lib/python/qmk/cli/generate/api.py | 14 +- lib/python/qmk/cli/generate/config_h.py | 277 +++++++++++++++++++++++ lib/python/qmk/cli/generate/info_json.py | 49 +++++ lib/python/qmk/cli/generate/layouts.py | 93 ++++++++ lib/python/qmk/cli/generate/rules_mk.py | 59 +++++ lib/python/qmk/cli/info.py | 7 +- lib/python/qmk/cli/kle2json.py | 51 ++--- lib/python/qmk/constants.py | 11 + lib/python/qmk/info.py | 355 +++++++++++++++++++++++++----- lib/python/qmk/info_json_encoder.py | 96 ++++++++ 13 files changed, 920 insertions(+), 107 deletions(-) create mode 100755 lib/python/qmk/cli/generate/config_h.py create mode 100755 lib/python/qmk/cli/generate/info_json.py create mode 100755 lib/python/qmk/cli/generate/layouts.py create mode 100755 lib/python/qmk/cli/generate/rules_mk.py create mode 100755 lib/python/qmk/info_json_encoder.py (limited to 'lib') diff --git a/lib/python/qmk/cli/c2json.py b/lib/python/qmk/cli/c2json.py index 2b3bb774f7..8f9d8dc383 100644 --- a/lib/python/qmk/cli/c2json.py +++ b/lib/python/qmk/cli/c2json.py @@ -6,6 +6,7 @@ from milc import cli import qmk.keymap import qmk.path +from qmk.info_json_encoder import InfoJSONEncoder @cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c') @@ -47,7 +48,7 @@ def c2json(cli): cli.args.output.parent.mkdir(parents=True, exist_ok=True) if cli.args.output.exists(): cli.args.output.replace(cli.args.output.name + '.bak') - cli.args.output.write_text(json.dumps(keymap_json)) + cli.args.output.write_text(json.dumps(keymap_json, cls=InfoJSONEncoder)) if not cli.args.quiet: cli.log.info('Wrote keymap to %s.', cli.args.output) diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index eae294a0c6..b9cfda9614 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -13,7 +13,7 @@ def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -fileHeader = """\ +file_header = """\ /* Copyright 2020 QMK * * This program is free software: you can redistribute it and/or modify @@ -77,7 +77,7 @@ def check_diffs(input_defs, reference_defs): def migrate_chconf_h(to_override, outfile): - print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) + print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) for override in to_override: print("#define %s %s" % (override[0], override[1]), file=outfile) @@ -87,7 +87,7 @@ def migrate_chconf_h(to_override, outfile): def migrate_halconf_h(to_override, outfile): - print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) + print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) for override in to_override: print("#define %s %s" % (override[0], override[1]), file=outfile) @@ -97,7 +97,7 @@ def migrate_halconf_h(to_override, outfile): def migrate_mcuconf_h(to_override, outfile): - print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) + print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) print("#include_next <mcuconf.h>\n", file=outfile) diff --git a/lib/python/qmk/cli/generate/__init__.py b/lib/python/qmk/cli/generate/__init__.py index f9585bfb5c..bd75b044c5 100644 --- a/lib/python/qmk/cli/generate/__init__.py +++ b/lib/python/qmk/cli/generate/__init__.py @@ -1,3 +1,7 @@ from . import api +from . import config_h from . import docs +from . import info_json +from . import layouts from . import rgb_breathe_table +from . import rules_mk diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 66db37cb52..6d111f244c 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -8,6 +8,7 @@ from milc import cli from qmk.datetime import current_datetime from qmk.info import info_json +from qmk.info_json_encoder import InfoJSONEncoder from qmk.keyboard import list_keyboards @@ -44,15 +45,16 @@ def generate_api(cli): if 'usb' in kb_all['keyboards'][keyboard_name]: usb = kb_all['keyboards'][keyboard_name]['usb'] - if usb['vid'] not in usb_list['devices']: + if 'vid' in usb and usb['vid'] not in usb_list['devices']: usb_list['devices'][usb['vid']] = {} - if usb['pid'] not in usb_list['devices'][usb['vid']]: + if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: usb_list['devices'][usb['vid']][usb['pid']] = {} - usb_list['devices'][usb['vid']][usb['pid']][keyboard_name] = usb + if 'vid' in usb and 'pid' in usb: + usb_list['devices'][usb['vid']][usb['pid']][keyboard_name] = usb # Write the global JSON files - keyboard_list.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': sorted(kb_all['keyboards'])})) - keyboard_all.write_text(json.dumps(kb_all)) - usb_file.write_text(json.dumps(usb_list)) + keyboard_list.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': sorted(kb_all['keyboards'])}, cls=InfoJSONEncoder)) + keyboard_all.write_text(json.dumps(kb_all, cls=InfoJSONEncoder)) + usb_file.write_text(json.dumps(usb_list, cls=InfoJSONEncoder)) diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py new file mode 100755 index 0000000000..4d734017a1 --- /dev/null +++ b/lib/python/qmk/cli/generate/config_h.py @@ -0,0 +1,277 @@ +"""Used by the make system to generate info_config.h from info.json. +""" +from milc import cli + +from qmk.constants import LED_INDICATORS +from qmk.decorators import automagic_keyboard, automagic_keymap +from qmk.info import info_json, rgblight_animations, rgblight_properties, rgblight_toggles +from qmk.path import is_keyboard, normpath + +usb_properties = { + 'vid': 'VENDOR_ID', + 'pid': 'PRODUCT_ID', + 'device_ver': 'DEVICE_VER', +} + + +def debounce(debounce): + """Return the config.h lines that set debounce + """ + return """ +#ifndef DEBOUNCE +# define DEBOUNCE %s +#endif // DEBOUNCE +""" % debounce + + +def diode_direction(diode_direction): + """Return the config.h lines that set diode direction + """ + return """ +#ifndef DIODE_DIRECTION +# define DIODE_DIRECTION %s +#endif // DIODE_DIRECTION +""" % diode_direction + + +def keyboard_name(keyboard_name): + """Return the config.h lines that set the keyboard's name. + """ + return """ +#ifndef DESCRIPTION +# define DESCRIPTION %s +#endif // DESCRIPTION + +#ifndef PRODUCT +# define PRODUCT %s +#endif // PRODUCT +""" % (keyboard_name, keyboard_name) + + +def manufacturer(manufacturer): + """Return the config.h lines that set the manufacturer. + """ + return """ +#ifndef MANUFACTURER +# define MANUFACTURER %s +#endif // MANUFACTURER +""" % (manufacturer) + + +def direct_pins(direct_pins): + """Return the config.h lines that set the direct pins. + """ + rows = [] + + for row in direct_pins: + cols = ','.join([col or 'NO_PIN' for col in row]) + rows.append('{' + cols + '}') + + col_count = len(direct_pins[0]) + row_count = len(direct_pins) + + return """ +#ifndef MATRIX_COLS +# define MATRIX_COLS %s +#endif // MATRIX_COLS + +#ifndef MATRIX_ROWS +# define MATRIX_ROWS %s +#endif // MATRIX_ROWS + +#ifndef DIRECT_PINS +# define DIRECT_PINS {%s} +#endif // DIRECT_PINS +""" % (col_count, row_count, ','.join(rows)) + + +def col_pins(col_pins): + """Return the config.h lines that set the column pins. + """ + cols = ','.join(col_pins) + col_num = len(col_pins) + + return """ +#ifndef MATRIX_COLS +# define MATRIX_COLS %s +#endif // MATRIX_COLS + +#ifndef MATRIX_COL_PINS +# define MATRIX_COL_PINS {%s} +#endif // MATRIX_COL_PINS +""" % (col_num, cols) + + +def row_pins(row_pins): + """Return the config.h lines that set the row pins. + """ + rows = ','.join(row_pins) + row_num = len(row_pins) + + return """ +#ifndef MATRIX_ROWS +# define MATRIX_ROWS %s +#endif // MATRIX_ROWS + +#ifndef MATRIX_ROW_PINS +# define MATRIX_ROW_PINS {%s} +#endif // MATRIX_ROW_PINS +""" % (row_num, rows) + + +def indicators(config): + """Return the config.h lines that setup LED indicators. + """ + defines = [] + + for led, define in LED_INDICATORS.items(): + if led in config: + defines.append('') + defines.append('#ifndef %s' % (define,)) + defines.append('# define %s %s' % (define, config[led])) + defines.append('#endif // %s' % (define,)) + + return '\n'.join(defines) + + +def layout_aliases(layout_aliases): + """Return the config.h lines that setup layout aliases. + """ + defines = [] + + for alias, layout in layout_aliases.items(): + defines.append('') + defines.append('#ifndef %s' % (alias,)) + defines.append('# define %s %s' % (alias, layout)) + defines.append('#endif // %s' % (alias,)) + + return '\n'.join(defines) + + +def matrix_pins(matrix_pins): + """Add the matrix config to the config.h. + """ + pins = [] + + if 'direct' in matrix_pins: + pins.append(direct_pins(matrix_pins['direct'])) + + if 'cols' in matrix_pins: + pins.append(col_pins(matrix_pins['cols'])) + + if 'rows' in matrix_pins: + pins.append(row_pins(matrix_pins['rows'])) + + return '\n'.join(pins) + + +def rgblight(config): + """Return the config.h lines that setup rgblight. + """ + rgblight_config = [] + + for json_key, config_key in rgblight_properties.items(): + if json_key in config: + rgblight_config.append('') + rgblight_config.append('#ifndef %s' % (config_key,)) + rgblight_config.append('# define %s %s' % (config_key, config[json_key])) + rgblight_config.append('#endif // %s' % (config_key,)) + + for json_key, config_key in rgblight_toggles.items(): + if config.get(json_key): + rgblight_config.append('') + rgblight_config.append('#ifndef %s' % (config_key,)) + rgblight_config.append('# define %s' % (config_key,)) + rgblight_config.append('#endif // %s' % (config_key,)) + + for json_key, config_key in rgblight_animations.items(): + if 'animations' in config and config['animations'].get(json_key): + rgblight_config.append('') + rgblight_config.append('#ifndef %s' % (config_key,)) + rgblight_config.append('# define %s' % (config_key,)) + rgblight_config.append('#endif // %s' % (config_key,)) + + return '\n'.join(rgblight_config) + + +def usb_properties(usb_props): + """Return the config.h lines that setup USB params. + """ + usb_lines = [] + + for info_name, config_name in usb_props.items(): + if info_name in usb_props: + usb_lines.append('') + usb_lines.append('#ifndef ' + config_name) + usb_lines.append('# define %s %s' % (config_name, usb_props[info_name])) + usb_lines.append('#endif // ' + config_name) + + return '\n'.join(usb_lines) + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') +@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) +@automagic_keyboard +@automagic_keymap +def generate_config_h(cli): + """Generates the info_config.h file. + """ + # Determine our keyboard(s) + if not cli.config.generate_config_h.keyboard: + cli.log.error('Missing paramater: --keyboard') + cli.subcommands['info'].print_help() + return False + + if not is_keyboard(cli.config.generate_config_h.keyboard): + cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) + return False + + # Build the info.json file + kb_info_json = info_json(cli.config.generate_config_h.keyboard) + + # Build the info_config.h file. + config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] + + if 'debounce' in kb_info_json: + config_h_lines.append(debounce(kb_info_json['debounce'])) + + if 'diode_direction' in kb_info_json: + config_h_lines.append(diode_direction(kb_info_json['diode_direction'])) + + if 'indicators' in kb_info_json: + config_h_lines.append(indicators(kb_info_json['indicators'])) + + if 'keyboard_name' in kb_info_json: + config_h_lines.append(keyboard_name(kb_info_json['keyboard_name'])) + + if 'layout_aliases' in kb_info_json: + config_h_lines.append(layout_aliases(kb_info_json['layout_aliases'])) + + if 'manufacturer' in kb_info_json: + config_h_lines.append(manufacturer(kb_info_json['manufacturer'])) + + if 'rgblight' in kb_info_json: + config_h_lines.append(rgblight(kb_info_json['rgblight'])) + + if 'matrix_pins' in kb_info_json: + config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) + + if 'usb' in kb_info_json: + config_h_lines.append(usb_properties(kb_info_json['usb'])) + + # Show the results + config_h = '\n'.join(config_h_lines) + + if cli.args.output: + cli.args.output.parent.mkdir(parents=True, exist_ok=True) + if cli.args.output.exists(): + cli.args.output.replace(cli.args.output.name + '.bak') + cli.args.output.write_text(config_h) + + if not cli.args.quiet: + cli.log.info('Wrote info_config.h to %s.', cli.args.output) + + else: + print(config_h) diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py new file mode 100755 index 0000000000..7e6654e45d --- /dev/null +++ b/lib/python/qmk/cli/generate/info_json.py @@ -0,0 +1,49 @@ +"""Keyboard information script. + +Compile an info.json for a particular keyboard and pretty-print it. +""" +import json + +from milc import cli + +from qmk.info_json_encoder import InfoJSONEncoder +from qmk.decorators import automagic_keyboard, automagic_keymap +from qmk.info import info_json +from qmk.path import is_keyboard + + +@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.') +@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') +@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) +@automagic_keyboard +@automagic_keymap +def generate_info_json(cli): + """Generate an info.json file for a keyboard + """ + # Determine our keyboard(s) + if not cli.config.generate_info_json.keyboard: + cli.log.error('Missing paramater: --keyboard') + cli.subcommands['info'].print_help() + return False + + if not is_keyboard(cli.config.generate_info_json.keyboard): + cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard) + return False + + # Build the info.json file + kb_info_json = info_json(cli.config.generate_info_json.keyboard) + pared_down_json = {} + + for key in ('manufacturer', 'maintainer', 'usb', 'keyboard_name', 'width', 'height', 'debounce', 'diode_direction', 'features', 'community_layouts', 'layout_aliases', 'matrix_pins', 'rgblight', 'url'): + if key in kb_info_json: + pared_down_json[key] = kb_info_json[key] + + pared_down_json['layouts'] = {} + if 'layouts' in pared_down_json: + for layout_name, layout in kb_info_json['layouts'].items(): + pared_down_json['layouts'][layout_name] = {} + pared_down_json['layouts'][layout_name]['key_count'] = layout.get('key_count', len(layout['layout'])) + pared_down_json['layouts'][layout_name]['layout'] = layout['layout'] + + # Display the results + print(json.dumps(pared_down_json, indent=2, cls=InfoJSONEncoder)) diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py new file mode 100755 index 0000000000..809f0ef7ec --- /dev/null +++ b/lib/python/qmk/cli/generate/layouts.py @@ -0,0 +1,93 @@ +"""Used by the make system to generate layouts.h from info.json. +""" +from milc import cli + +from qmk.constants import COL_LETTERS, ROW_LETTERS +from qmk.decorators import automagic_keyboard, automagic_keymap +from qmk.info import info_json +from qmk.path import is_keyboard, normpath + +usb_properties = { + 'vid': 'VENDOR_ID', + 'pid': 'PRODUCT_ID', + 'device_ver': 'DEVICE_VER', +} + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') +@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True) +@automagic_keyboard +@automagic_keymap +def generate_layouts(cli): + """Generates the layouts.h file. + """ + # Determine our keyboard(s) + if not cli.config.generate_layouts.keyboard: + cli.log.error('Missing paramater: --keyboard') + cli.subcommands['info'].print_help() + return False + + if not is_keyboard(cli.config.generate_layouts.keyboard): + cli.log.error('Invalid keyboard: "%s"', cli.config.generate_layouts.keyboard) + return False + + # Build the info.json file + kb_info_json = info_json(cli.config.generate_layouts.keyboard) + + # Build the layouts.h file. + layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.' ' */', '', '#pragma once'] + + if 'direct' in kb_info_json['matrix_pins']: + col_num = len(kb_info_json['matrix_pins']['direct'][0]) + row_num = len(kb_info_json['matrix_pins']['direct']) + elif 'cols' in kb_info_json['matrix_pins'] and 'rows' in kb_info_json['matrix_pins']: + col_num = len(kb_info_json['matrix_pins']['cols']) + row_num = len(kb_info_json['matrix_pins']['rows']) + else: + cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard) + return False + + for layout_name in kb_info_json['layouts']: + if kb_info_json['layouts'][layout_name]['c_macro']: + continue + + layout_keys = [] + layout_matrix = [['KC_NO' for i in range(col_num)] for i in range(row_num)] + + for i, key in enumerate(kb_info_json['layouts'][layout_name]['layout']): + row = key['matrix'][0] + col = key['matrix'][1] + identifier = 'k%s%s' % (ROW_LETTERS[row], COL_LETTERS[col]) + + try: + layout_matrix[row][col] = identifier + layout_keys.append(identifier) + except IndexError: + key_name = key.get('label', identifier) + cli.log.error('Matrix data out of bounds for layout %s at index %s (%s): %s, %s', layout_name, i, key_name, row, col) + return False + + layouts_h_lines.append('') + layouts_h_lines.append('#define %s(%s) {\\' % (layout_name, ', '.join(layout_keys))) + + rows = ', \\\n'.join(['\t {' + ', '.join(row) + '}' for row in layout_matrix]) + rows += ' \\' + layouts_h_lines.append(rows) + layouts_h_lines.append('}') + + # Show the results + layouts_h = '\n'.join(layouts_h_lines) + '\n' + + if cli.args.output: + cli.args.output.parent.mkdir(parents=True, exist_ok=True) + if cli.args.output.exists(): + cli.args.output.replace(cli.args.output.name + '.bak') + cli.args.output.write_text(layouts_h) + + if not cli.args.quiet: + cli.log.info('Wrote info_config.h to %s.', cli.args.output) + + else: + print(layouts_h) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py new file mode 100755 index 0000000000..4268ae047b --- /dev/null +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -0,0 +1,59 @@ +"""Used by the make system to generate a rules.mk +""" +from milc import cli + +from qmk.decorators import automagic_keyboard, automagic_keymap +from qmk.info import info_json +from qmk.path import is_keyboard, normpath + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') +@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) +@automagic_keyboard +@automagic_keymap +def generate_rules_mk(cli): + """Generates a rules.mk file from info.json. + """ + # Determine our keyboard(s) + if not cli.config.generate_rules_mk.keyboard: + cli.log.error('Missing paramater: --keyboard') + cli.subcommands['info'].print_help() + return False + + if not is_keyboard(cli.config.generate_rules_mk.keyboard): + cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard) + return False + + # Build the info.json file + kb_info_json = info_json(cli.config.generate_rules_mk.keyboard) + rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] + + # Find features that should be enabled + if 'features' in kb_info_json: + for feature, enabled in kb_info_json['features'].items(): + feature = feature.upper() + enabled = 'yes' if enabled else 'no' + rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') + + # Add community layouts + if 'community_layouts' in kb_info_json: + rules_mk_lines.append(f'LAYOUTS = {" ".join(kb_info_json["community_layouts"])}') + + # Show the results + rules_mk = '\n'.join(rules_mk_lines) + '\n' + + if cli.args.output: + cli.args.output.parent.mkdir(parents=True, exist_ok=True) + if cli.args.output.exists(): + cli.args.output.replace(cli.args.output.name + '.bak') + cli.args.output.write_text(rules_mk) + + if cli.args.quiet: + print(cli.args.output) + else: + cli.log.info('Wrote info_config.h to %s.', cli.args.output) + + else: + print(rules_mk) diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index 9ab299a21e..87d7253d4b 100755 --- a/lib/python/qmk/cli/info.py +++ b/lib/python/qmk/cli/info.py @@ -7,6 +7,8 @@ import platform from milc import cli +from qmk.info_json_encoder import InfoJSONEncoder +from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.keyboard import render_layouts, render_layout from qmk.keymap import locate_keymap @@ -15,9 +17,6 @@ from qmk.path import is_keyboard platform_id = platform.platform().lower() -ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop' -COL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijilmnopqrstuvwxyz' - def show_keymap(kb_info_json, title_caps=True): """Render the keymap in ascii art. @@ -149,7 +148,7 @@ def info(cli): # Output in the requested format if cli.args.format == 'json': - print(json.dumps(kb_info_json)) + print(json.dumps(kb_info_json, cls=InfoJSONEncoder)) elif cli.args.format == 'text': print_text_output(kb_info_json) elif cli.args.format == 'friendly': diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 3d1bb8c43c..66d504bfc2 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -3,25 +3,12 @@ import json import os from pathlib import Path -from decimal import Decimal -from collections import OrderedDict from milc import cli from kle2xy import KLE2xy from qmk.converter import kle2qmk - - -class CustomJSONEncoder(json.JSONEncoder): - def default(self, obj): - try: - if isinstance(obj, Decimal): - if obj % 2 in (Decimal(0), Decimal(1)): - return int(obj) - return float(obj) - except TypeError: - pass - return json.JSONEncoder.default(self, obj) +from qmk.info_json_encoder import InfoJSONEncoder @cli.argument('filename', help='The KLE raw txt to convert') @@ -52,24 +39,22 @@ def kle2json(cli): cli.log.error('Could not parse KLE raw data: %s', raw_code) cli.log.exception(e) return False - keyboard = OrderedDict( - keyboard_name=kle.name, - url='', - maintainer='qmk', - width=kle.columns, - height=kle.rows, - layouts={'LAYOUT': { - 'layout': 'LAYOUT_JSON_HERE' - }}, - ) - # Initialize keyboard with json encoded from ordered dict - keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=CustomJSONEncoder) - # Initialize layout with kle2qmk from converter module - layout = json.dumps(kle2qmk(kle), separators=(', ', ':'), cls=CustomJSONEncoder) - # Replace layout in keyboard json - keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout) + keyboard = { + 'keyboard_name': kle.name, + 'url': '', + 'maintainer': 'qmk', + 'width': kle.columns, + 'height': kle.rows, + 'layouts': { + 'LAYOUT': { + 'layout': kle2qmk(kle) + } + }, + } + # Write our info.json - file = open(out_path / "info.json", "w") - file.write(keyboard) - file.close() + keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) + info_json_file = out_path / 'info.json' + + info_json_file.write_text(keyboard) cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path) diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 94ab68e5e4..675832c506 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -17,3 +17,14 @@ VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' DATE_FORMAT = '%Y-%m-%d' DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z' TIME_FORMAT = '%H:%M:%S' + +# Used when generating matrix locations +COL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijilmnopqrstuvwxyz' +ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop' + +# Mapping between info.json and config.h keys +LED_INDICATORS = { + 'caps_lock': 'LED_CAPS_LOCK_PIN', + 'num_lock': 'LED_NUM_LOCK_PIN', + 'scrol_lock': 'LED_SCROLL_LOCK_PIN' +} diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index f476dc666d..d7b128aa66 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -6,13 +6,45 @@ from pathlib import Path from milc import cli -from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS +from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS from qmk.c_parse import find_layouts from qmk.keyboard import config_h, rules_mk from qmk.keymap import list_keymaps from qmk.makefile import parse_rules_mk_file from qmk.math import compute +rgblight_properties = { + 'led_count': 'RGBLED_NUM', + 'pin': 'RGB_DI_PIN', + 'split_count': 'RGBLED_SPLIT', + 'max_brightness': 'RGBLIGHT_LIMIT_VAL', + 'hue_steps': 'RGBLIGHT_HUE_STEP', + 'saturation_steps': 'RGBLIGHT_SAT_STEP', + 'brightness_steps': 'RGBLIGHT_VAL_STEP' +} + +rgblight_toggles = { + 'sleep': 'RGBLIGHT_SLEEP', + 'split': 'RGBLIGHT_SPLIT', +} + +rgblight_animations = { + 'all': 'RGBLIGHT_ANIMATIONS', + 'alternating': 'RGBLIGHT_EFFECT_ALTERNATING', + 'breathing': 'RGBLIGHT_EFFECT_BREATHING', + 'christmas': 'RGBLIGHT_EFFECT_CHRISTMAS', + 'knight': 'RGBLIGHT_EFFECT_KNIGHT', + 'rainbow_mood': 'RGBLIGHT_EFFECT_RAINBOW_MOOD', + 'rainbow_swirl': 'RGBLIGHT_EFFECT_RAINBOW_SWIRL', + 'rgb_test': 'RGBLIGHT_EFFECT_RGB_TEST', + 'snake': 'RGBLIGHT_EFFECT_SNAKE', + 'static_gradient': 'RGBLIGHT_EFFECT_STATIC_GRADIENT', + 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' +} + +true_values = ['1', 'on', 'yes'] +false_values = ['0', 'off', 'no'] + def info_json(keyboard): """Generate the info.json data for a specific keyboard. @@ -38,8 +70,9 @@ def info_json(keyboard): info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} # Populate layout data - for layout_name, layout_json in _find_all_layouts(info_data, keyboard, rules).items(): + for layout_name, layout_json in _find_all_layouts(info_data, keyboard).items(): if not layout_name.startswith('LAYOUT_kc'): + layout_json['c_macro'] = True info_data['layouts'][layout_name] = layout_json # Merge in the data from info.json, config.h, and rules.mk @@ -47,34 +80,179 @@ def info_json(keyboard): info_data = _extract_config_h(info_data) info_data = _extract_rules_mk(info_data) + # Make sure we have at least one layout + 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.') + + # Make sure we supply layout macros for the community layouts we claim to support + # FIXME(skullydazed): This should be populated into info.json and read from there instead + if 'LAYOUTS' in rules and info_data.get('layouts'): + # Match these up against the supplied layouts + supported_layouts = rules['LAYOUTS'].strip().split() + for layout_name in sorted(info_data['layouts']): + layout_name = layout_name[7:] + + if layout_name in supported_layouts: + supported_layouts.remove(layout_name) + + if supported_layouts: + for supported_layout in supported_layouts: + _log_error(info_data, 'Claims to support community layout %s but no LAYOUT_%s() macro found' % (supported_layout, supported_layout)) + return info_data -def _extract_config_h(info_data): - """Pull some keyboard information from existing rules.mk files +def _extract_debounce(info_data, config_c): + """Handle debounce. + """ + if 'debounce' in info_data and 'DEBOUNCE' in config_c: + _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.') + + if 'DEBOUNCE' in config_c: + info_data['debounce'] = config_c.get('DEBOUNCE') + + return info_data + + +def _extract_diode_direction(info_data, config_c): + """Handle the diode direction. + """ + if 'diode_direction' in info_data and 'DIODE_DIRECTION' in config_c: + _log_warning(info_data, 'Diode direction is specified in both info.json and config.h, the config.h value wins.') + + if 'DIODE_DIRECTION' in config_c: + info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') + + return info_data + + +def _extract_indicators(info_data, config_c): + """Find the LED indicator information. + """ + for json_key, config_key in LED_INDICATORS.items(): + if json_key in info_data.get('indicators', []) and config_key in config_c: + _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') + + if config_key in config_c: + info_data['indicators'][json_key] = config_c.get(config_key) + + return info_data + + +def _extract_community_layouts(info_data, rules): + """Find the community layouts in rules.mk. + """ + community_layouts = rules['LAYOUTS'].split() if 'LAYOUTS' in rules else [] + + if 'community_layouts' in info_data: + for layout in community_layouts: + if layout not in info_data['community_layouts']: + community_layouts.append(layout) + + else: + info_data['community_layouts'] = community_layouts + + return info_data + + +def _extract_features(info_data, rules): + """Find all the features enabled in rules.mk. + """ + for key, value in rules.items(): + if key.endswith('_ENABLE'): + key = '_'.join(key.split('_')[:-1]).lower() + value = True if value in true_values else False if value in false_values else value + + if 'config_h_features' not in info_data: + info_data['config_h_features'] = {} + + if 'features' not in info_data: + info_data['features'] = {} + + if key in info_data['features']: + _log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,)) + + info_data['features'][key] = value + info_data['config_h_features'][key] = value + + return info_data + + +def _extract_rgblight(info_data, config_c): + """Handle the rgblight configuration + """ + rgblight = info_data.get('rgblight', {}) + animations = rgblight.get('animations', {}) + + for json_key, config_key in rgblight_properties.items(): + if config_key in config_c: + if json_key in rgblight: + _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) + + rgblight[json_key] = config_c[config_key] + + for json_key, config_key in rgblight_toggles.items(): + if config_key in config_c: + if json_key in rgblight: + _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key) + + rgblight[json_key] = config_c[config_key] + + for json_key, config_key in rgblight_animations.items(): + if config_key in config_c: + if json_key in animations: + _log_warning(info_data, 'RGB Light: animations: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) + + animations[json_key] = config_c[config_key] + + if animations: + rgblight['animations'] = animations + + if rgblight: + info_data['rgblight'] = rgblight + + return info_data + + +def _extract_matrix_info(info_data, config_c): + """Populate the matrix information. """ - config_c = config_h(info_data['keyboard_folder']) row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] - info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') - info_data['matrix_size'] = { - 'rows': compute(config_c.get('MATRIX_ROWS', '0')), - 'cols': compute(config_c.get('MATRIX_COLS', '0')), - } - info_data['matrix_pins'] = {} + if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c: + if 'matrix_size' in info_data: + _log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.') - if row_pins: - info_data['matrix_pins']['rows'] = row_pins.split(',') - if col_pins: - info_data['matrix_pins']['cols'] = col_pins.split(',') + info_data['matrix_size'] = { + 'rows': compute(config_c.get('MATRIX_ROWS', '0')), + 'cols': compute(config_c.get('MATRIX_COLS', '0')), + } + + if row_pins and col_pins: + if 'matrix_pins' in info_data: + _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') + + info_data['matrix_pins'] = {} + + if row_pins: + info_data['matrix_pins']['rows'] = row_pins.split(',') + + if col_pins: + info_data['matrix_pins']['cols'] = col_pins.split(',') if direct_pins: + if 'matrix_pins' in info_data: + _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') + + info_data['matrix_pins'] = {} direct_pin_array = [] + for row in direct_pins.split('},{'): if row.startswith('{'): row = row[1:] + if row.endswith('}'): row = row[:-1] @@ -86,15 +264,43 @@ def _extract_config_h(info_data): direct_pin_array[-1].append(pin) - info_data['matrix_pins']['direct'] = direct_pin_array + info_data['matrix_pins']['direct'] = direct_pin_array - info_data['usb'] = { - 'vid': config_c.get('VENDOR_ID'), - 'pid': config_c.get('PRODUCT_ID'), - 'device_ver': config_c.get('DEVICE_VER'), - 'manufacturer': config_c.get('MANUFACTURER'), - 'product': config_c.get('PRODUCT'), - } + return info_data + + +def _extract_usb_info(info_data, config_c): + """Populate the USB information. + """ + usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} + + if 'usb' not in info_data: + info_data['usb'] = {} + + for info_name, config_name in usb_properties.items(): + if config_name in config_c: + if info_name in info_data['usb']: + _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) + + info_data['usb'][info_name] = config_c[config_name] + + elif info_name not in info_data['usb']: + _log_error(info_data, '%s not specified in config.h, and %s not specified in info.json. One is required.' % (config_name, info_name)) + + return info_data + + +def _extract_config_h(info_data): + """Pull some keyboard information from existing config.h files + """ + config_c = config_h(info_data['keyboard_folder']) + + _extract_debounce(info_data, config_c) + _extract_diode_direction(info_data, config_c) + _extract_indicators(info_data, config_c) + _extract_matrix_info(info_data, config_c) + _extract_usb_info(info_data, config_c) + _extract_rgblight(info_data, config_c) return info_data @@ -106,16 +312,52 @@ def _extract_rules_mk(info_data): mcu = rules.get('MCU') if mcu in CHIBIOS_PROCESSORS: - return arm_processor_rules(info_data, rules) + arm_processor_rules(info_data, rules) elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: - return avr_processor_rules(info_data, rules) + avr_processor_rules(info_data, rules) + + else: + cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) + unknown_processor_rules(info_data, rules) + + _extract_community_layouts(info_data, rules) + _extract_features(info_data, rules) + + return info_data - msg = "Unknown MCU: " + str(mcu) - _log_warning(info_data, msg) +def _merge_layouts(info_data, new_info_data): + """Merge new_info_data into info_data in an intelligent way. + """ + for layout_name, layout_json in new_info_data['layouts'].items(): + if layout_name in info_data['layouts']: + # Pull in layouts we have a macro for + if len(info_data['layouts'][layout_name]['layout']) != len(layout_json['layout']): + msg = '%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s' + _log_error(info_data, msg % (info_data['keyboard_folder'], layout_name, len(layout_json['layout']), layout_name, len(info_data['layouts'][layout_name]['layout']))) + else: + for i, key in enumerate(info_data['layouts'][layout_name]['layout']): + key.update(layout_json['layout'][i]) + else: + # Pull in layouts that have matrix data + missing_matrix = False + for key in layout_json['layout']: + if 'matrix' not in key: + missing_matrix = True + + if not missing_matrix: + if layout_name in info_data['layouts']: + # Update an existing layout with new data + for i, key in enumerate(info_data['layouts'][layout_name]['layout']): + key.update(layout_json['layout'][i]) - return unknown_processor_rules(info_data, rules) + else: + # Copy in the new layout wholesale + layout_json['c_macro'] = False + info_data['layouts'][layout_name] = layout_json + + return info_data def _search_keyboard_h(path): @@ -131,34 +373,21 @@ def _search_keyboard_h(path): return layouts -def _find_all_layouts(info_data, keyboard, rules): +def _find_all_layouts(info_data, keyboard): """Looks for layout macros associated with this keyboard. """ layouts = _search_keyboard_h(Path(keyboard)) if not layouts: - # If we didn't find any layouts above 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, 'Falling back to searching for KEYMAP/LAYOUT macros.') + # 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. + info_data['parse_warnings'].append('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard)) + for file in glob('keyboards/%s/*.h' % keyboard): if file.endswith('.h'): these_layouts = find_layouts(file) if these_layouts: layouts.update(these_layouts) - if 'LAYOUTS' in rules: - # Match these up against the supplied layouts - supported_layouts = rules['LAYOUTS'].strip().split() - for layout_name in sorted(layouts): - if not layout_name.startswith('LAYOUT_'): - continue - layout_name = layout_name[7:] - if layout_name in supported_layouts: - supported_layouts.remove(layout_name) - - if supported_layouts: - _log_error(info_data, 'Missing LAYOUT() macro for %s' % (', '.join(supported_layouts))) - return layouts @@ -231,32 +460,40 @@ def merge_info_jsons(keyboard, info_data): for info_file in find_info_json(keyboard): # Load and validate the JSON data try: - with info_file.open('r') as info_fd: - new_info_data = json.load(info_fd) + new_info_data = json.load(info_file.open('r')) except Exception as e: _log_error(info_data, "Invalid JSON in file %s: %s: %s" % (str(info_file), e.__class__.__name__, e)) - continue + new_info_data = {} if not isinstance(new_info_data, dict): _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) continue # Copy whitelisted keys into `info_data` - for key in ('keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'): + for key in ('debounce', 'diode_direction', 'indicators', 'keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'): if key in new_info_data: info_data[key] = new_info_data[key] - # Merge the layouts in + # Deep merge certain keys + # 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. + for key in ('features', 'layout_aliases', 'matrix_pins', 'rgblight', 'usb'): + if key in new_info_data: + if key not in info_data: + info_data[key] = {} + + info_data[key].update(new_info_data[key]) + + # Merge the layouts + if 'community_layouts' in new_info_data: + if 'community_layouts' in info_data: + for layout in new_info_data['community_layouts']: + if layout not in info_data['community_layouts']: + info_data['community_layouts'].append(layout) + else: + info_data['community_layouts'] = new_info_data['community_layouts'] + if 'layouts' in new_info_data: - for layout_name, json_layout in new_info_data['layouts'].items(): - # Only pull in layouts we have a macro for - if layout_name in info_data['layouts']: - if info_data['layouts'][layout_name]['key_count'] != len(json_layout['layout']): - msg = '%s: Number of elements in info.json does not match! info.json:%s != %s:%s' - _log_error(info_data, msg % (layout_name, len(json_layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout']))) - else: - for i, key in enumerate(info_data['layouts'][layout_name]['layout']): - key.update(json_layout['layout'][i]) + _merge_layouts(info_data, new_info_data) return info_data diff --git a/lib/python/qmk/info_json_encoder.py b/lib/python/qmk/info_json_encoder.py new file mode 100755 index 0000000000..60dae7247f --- /dev/null +++ b/lib/python/qmk/info_json_encoder.py @@ -0,0 +1,96 @@ +"""Class that pretty-prints QMK info.json files. +""" +import json +from decimal import Decimal + + +class InfoJSONEncoder(json.JSONEncoder): + """Custom encoder to make info.json's a little nicer to work with. + """ + container_types = (list, tuple, dict) + indentation_char = " " + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.indentation_level = 0 + + if not self.indent: + self.indent = 4 + + def encode(self, obj): + """Encode JSON objects for QMK. + """ + if isinstance(obj, Decimal): + if obj == int(obj): # I can't believe Decimal objects don't have .is_integer() + return int(obj) + return float(obj) + + elif isinstance(obj, (list, tuple)): + if self._primitives_only(obj): + return "[" + ", ".join(self.encode(element) for element in obj) + "]" + + else: + self.indentation_level += 1 + output = [self.indent_str + self.encode(element) for element in obj] + self.indentation_level -= 1 + return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]" + + elif isinstance(obj, dict): + if obj: + if self.indentation_level == 4: + # These are part of a layout, put them on a single line. + return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }" + + else: + self.indentation_level += 1 + output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_root_dict)] + self.indentation_level -= 1 + return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}" + else: + return "{}" + else: + return super().encode(obj) + + def _primitives_only(self, obj): + """Returns true if the object doesn't have any container type objects (list, tuple, dict). + """ + if isinstance(obj, dict): + obj = obj.values() + + return not any(isinstance(element, self.container_types) for element in obj) + + def sort_root_dict(self, key): + """Forces layout to the back of the sort order. + """ + key = key[0] + + if self.indentation_level == 1: + if key == 'manufacturer': + return '10keyboard_name' + + elif key == 'keyboard_name': + return '11keyboard_name' + + elif key == 'maintainer': + return '12maintainer' + + elif key in ('height', 'width'): + return '40' + str(key) + + elif key == 'community_layouts': + return '97community_layouts' + + elif key == 'layout_aliases': + return '98layout_aliases' + + elif key == 'layouts': + return '99layouts' + + else: + return '50' + str(key) + + return key + + @property + def indent_str(self): + return self.indentation_char * (self.indentation_level * self.indent) -- cgit v1.2.3 From f27d8d94489342d4ce7ba9955cd062c725350db9 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 2 Jan 2021 12:05:56 -0800 Subject: Fix compiling on develop (#11409) --- lib/python/qmk/cli/generate/config_h.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 4d734017a1..e160ffea83 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -7,7 +7,7 @@ from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json, rgblight_animations, rgblight_properties, rgblight_toggles from qmk.path import is_keyboard, normpath -usb_properties = { +usb_prop_map = { 'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER', @@ -199,7 +199,7 @@ def usb_properties(usb_props): """ usb_lines = [] - for info_name, config_name in usb_props.items(): + for info_name, config_name in usb_prop_map.items(): if info_name in usb_props: usb_lines.append('') usb_lines.append('#ifndef ' + config_name) -- cgit v1.2.3 From 11bd98f684148ed9577b263189121e52027d66d9 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 2 Jan 2021 18:08:17 -0800 Subject: Fix broken keyboards (#11412) * Fix a couple errors * add a dependency for the generated headers --- lib/python/qmk/cli/generate/layouts.py | 19 ++++++++++--------- lib/python/qmk/info.py | 3 +++ 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index 809f0ef7ec..273870e15c 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -39,15 +39,16 @@ def generate_layouts(cli): # Build the layouts.h file. layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.' ' */', '', '#pragma once'] - if 'direct' in kb_info_json['matrix_pins']: - col_num = len(kb_info_json['matrix_pins']['direct'][0]) - row_num = len(kb_info_json['matrix_pins']['direct']) - elif 'cols' in kb_info_json['matrix_pins'] and 'rows' in kb_info_json['matrix_pins']: - col_num = len(kb_info_json['matrix_pins']['cols']) - row_num = len(kb_info_json['matrix_pins']['rows']) - else: - cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard) - return False + if 'matrix_pins' in kb_info_json: + if 'direct' in kb_info_json['matrix_pins']: + col_num = len(kb_info_json['matrix_pins']['direct'][0]) + row_num = len(kb_info_json['matrix_pins']['direct']) + elif 'cols' in kb_info_json['matrix_pins'] and 'rows' in kb_info_json['matrix_pins']: + col_num = len(kb_info_json['matrix_pins']['cols']) + row_num = len(kb_info_json['matrix_pins']['rows']) + else: + cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard) + return False for layout_name in kb_info_json['layouts']: if kb_info_json['layouts'][layout_name]['c_macro']: diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index d7b128aa66..2954a17e09 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -134,6 +134,9 @@ def _extract_indicators(info_data, config_c): _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') if config_key in config_c: + if 'indicators' not in info_data: + info_data['indicators'] = {} + info_data['indicators'][json_key] = config_c.get(config_key) return info_data -- cgit v1.2.3 From 6daa204363dfaa7f31d935a9913d6b35ec22e971 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sun, 3 Jan 2021 15:45:06 -0800 Subject: fix keyboards with apostrophes in their names --- lib/python/qmk/cli/generate/config_h.py | 4 ++-- lib/python/qmk/info.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index e160ffea83..09aa0041f4 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -45,7 +45,7 @@ def keyboard_name(keyboard_name): #ifndef PRODUCT # define PRODUCT %s #endif // PRODUCT -""" % (keyboard_name, keyboard_name) +""" % (keyboard_name.replace("'", ""), keyboard_name.replace("'", "")) def manufacturer(manufacturer): @@ -55,7 +55,7 @@ def manufacturer(manufacturer): #ifndef MANUFACTURER # define MANUFACTURER %s #endif // MANUFACTURER -""" % (manufacturer) +""" % (manufacturer.replace("'", "")) def direct_pins(direct_pins): diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 2954a17e09..4611874e85 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -345,7 +345,7 @@ def _merge_layouts(info_data, new_info_data): else: # Pull in layouts that have matrix data missing_matrix = False - for key in layout_json['layout']: + for key in layout_json.get('layout', {}): if 'matrix' not in key: missing_matrix = True -- cgit v1.2.3 From ededff8556daff544633cb143cb6d939afd09014 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Tue, 1 Dec 2020 12:52:02 -0800 Subject: validate keyboard data with jsonschema --- lib/python/qmk/cli/generate/info_json.py | 2 +- lib/python/qmk/cli/generate/rules_mk.py | 13 +++ lib/python/qmk/info.py | 151 ++++++++++++++++++++++++++++--- 3 files changed, 154 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 7e6654e45d..fba4b1c014 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py @@ -39,7 +39,7 @@ def generate_info_json(cli): pared_down_json[key] = kb_info_json[key] pared_down_json['layouts'] = {} - if 'layouts' in pared_down_json: + if 'layouts' in kb_info_json: for layout_name, layout in kb_info_json['layouts'].items(): pared_down_json['layouts'][layout_name] = {} pared_down_json['layouts'][layout_name]['key_count'] = layout.get('key_count', len(layout['layout'])) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 4268ae047b..72ed3c45fa 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -6,6 +6,10 @@ from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.path import is_keyboard, normpath +info_to_rules = { + 'bootloader': 'BOOTLOADER', + 'processor': 'MCU' +} @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @@ -30,6 +34,10 @@ def generate_rules_mk(cli): kb_info_json = info_json(cli.config.generate_rules_mk.keyboard) rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] + # Bring in settings + for info_key, rule_key in info_to_rules.items(): + rules_mk_lines.append(f'{rule_key} := {kb_info_json[info_key]}') + # Find features that should be enabled if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): @@ -37,6 +45,11 @@ def generate_rules_mk(cli): enabled = 'yes' if enabled else 'no' rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') + # Set the LED driver + if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: + driver = kb_info_json['led_matrix']['driver'] + rules_mk_lines.append(f'LED_MATRIX_DRIVER = {driver}') + # Add community layouts if 'community_layouts' in kb_info_json: rules_mk_lines.append(f'LAYOUTS = {" ".join(kb_info_json["community_layouts"])}') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 4611874e85..1cf12190d6 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -4,6 +4,7 @@ import json from glob import glob from pathlib import Path +import jsonschema from milc import cli from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS @@ -13,6 +14,17 @@ from qmk.keymap import list_keymaps from qmk.makefile import parse_rules_mk_file from qmk.math import compute +led_matrix_properties = { + 'driver_count': 'LED_DRIVER_COUNT', + 'driver_addr1': 'LED_DRIVER_ADDR_1', + 'driver_addr2': 'LED_DRIVER_ADDR_2', + 'driver_addr3': 'LED_DRIVER_ADDR_3', + 'driver_addr4': 'LED_DRIVER_ADDR_4', + 'led_count': 'LED_DRIVER_LED_COUNT', + 'timeout': 'ISSI_TIMEOUT', + 'persistence': 'ISSI_PERSISTENCE' +} + rgblight_properties = { 'led_count': 'RGBLED_NUM', 'pin': 'RGB_DI_PIN', @@ -80,6 +92,15 @@ def info_json(keyboard): info_data = _extract_config_h(info_data) info_data = _extract_rules_mk(info_data) + # Validate against the jsonschema + try: + keyboard_api_validate(info_data) + + except jsonschema.ValidationError as e: + cli.log.error('Invalid info.json data: %s', e.message) + print(dir(e)) + exit() + # Make sure we have at least one layout 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.') @@ -102,6 +123,50 @@ def info_json(keyboard): return info_data +def _json_load(json_file): + """Load a json file from disk. + + Note: file must be a Path object. + """ + try: + return json.load(json_file.open()) + + except json.decoder.JSONDecodeError as e: + cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) + exit(1) + + +def _jsonschema(schema_name): + """Read a jsonschema file from disk. + """ + schema_path = Path(f'data/schemas/{schema_name}.jsonschema') + + if not schema_path.exists(): + schema_path = Path('data/schemas/false.jsonschema') + + return _json_load(schema_path) + + +def keyboard_validate(data): + """Validates data against the keyboard jsonschema. + """ + schema = _jsonschema('keyboard') + validator = jsonschema.Draft7Validator(schema).validate + + return validator(data) + + +def keyboard_api_validate(data): + """Validates data against the api_keyboard jsonschema. + """ + base = _jsonschema('keyboard') + relative = _jsonschema('api_keyboard') + resolver = jsonschema.RefResolver.from_schema(base) + validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate + + return validator(data) + + def _extract_debounce(info_data, config_c): """Handle debounce. """ @@ -109,7 +174,7 @@ def _extract_debounce(info_data, config_c): _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.') if 'DEBOUNCE' in config_c: - info_data['debounce'] = config_c.get('DEBOUNCE') + info_data['debounce'] = int(config_c['DEBOUNCE']) return info_data @@ -181,8 +246,36 @@ def _extract_features(info_data, rules): return info_data +def _extract_led_drivers(info_data, rules): + """Find all the LED drivers set in rules.mk. + """ + if 'LED_MATRIX_DRIVER' in rules: + if 'led_matrix' not in info_data: + info_data['led_matrix'] = {} + + if info_data['led_matrix'].get('driver'): + _log_warning(info_data, 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['led_matrix']['driver'] = rules['LED_MATRIX_DRIVER'] + + return info_data + + +def _extract_led_matrix(info_data, config_c): + """Handle the led_matrix configuration. + """ + led_matrix = info_data.get('led_matrix', {}) + + for json_key, config_key in led_matrix_properties.items(): + if config_key in config_c: + if json_key in led_matrix: + _log_warning(info_data, 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) + + led_matrix[json_key] = config_c[config_key] + + def _extract_rgblight(info_data, config_c): - """Handle the rgblight configuration + """Handle the rgblight configuration. """ rgblight = info_data.get('rgblight', {}) animations = rgblight.get('animations', {}) @@ -303,6 +396,7 @@ def _extract_config_h(info_data): _extract_indicators(info_data, config_c) _extract_matrix_info(info_data, config_c) _extract_usb_info(info_data, config_c) + _extract_led_matrix(info_data, config_c) _extract_rgblight(info_data, config_c) return info_data @@ -326,6 +420,7 @@ def _extract_rules_mk(info_data): _extract_community_layouts(info_data, rules) _extract_features(info_data, rules) + _extract_led_drivers(info_data, rules) return info_data @@ -412,13 +507,28 @@ def arm_processor_rules(info_data, rules): """Setup the default info for an ARM board. """ info_data['processor_type'] = 'arm' - info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'unknown' - info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown' info_data['protocol'] = 'ChibiOS' - if info_data['bootloader'] == 'unknown': + if 'MCU' in rules: + if 'processor' in info_data: + _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['processor'] = rules['MCU'] + + elif 'processor' not in info_data: + info_data['processor'] = 'unknown' + + if 'BOOTLOADER' in rules: + if 'bootloader' in info_data: + _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['bootloader'] = rules['BOOTLOADER'] + + else: if 'STM32' in info_data['processor']: info_data['bootloader'] = 'stm32-dfu' + else: + info_data['bootloader'] = 'unknown' if 'STM32' in info_data['processor']: info_data['platform'] = 'STM32' @@ -436,9 +546,25 @@ def avr_processor_rules(info_data, rules): info_data['processor_type'] = 'avr' info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu' info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' - info_data['processor'] = rules['MCU'] if 'MCU' in rules else 'unknown' info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' + if 'MCU' in rules: + if 'processor' in info_data: + _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['processor'] = rules['MCU'] + + elif 'processor' not in info_data: + info_data['processor'] = 'unknown' + + if 'BOOTLOADER' in rules: + if 'bootloader' in info_data: + _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['bootloader'] = rules['BOOTLOADER'] + else: + info_data['bootloader'] = 'atmel-dfu' + # 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' @@ -463,10 +589,13 @@ def merge_info_jsons(keyboard, info_data): for info_file in find_info_json(keyboard): # Load and validate the JSON data try: - new_info_data = json.load(info_file.open('r')) - except Exception as e: - _log_error(info_data, "Invalid JSON in file %s: %s: %s" % (str(info_file), e.__class__.__name__, e)) - new_info_data = {} + new_info_data = _json_load(info_file) + keyboard_validate(new_info_data) + + except jsonschema.ValidationError as e: + cli.log.error('Invalid info.json data: %s', e.message) + cli.log.error('Not including file %s', info_file) + continue if not isinstance(new_info_data, dict): _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) @@ -479,7 +608,7 @@ def merge_info_jsons(keyboard, info_data): # Deep merge certain keys # 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. - for key in ('features', 'layout_aliases', 'matrix_pins', 'rgblight', 'usb'): + for key in ('features', 'layout_aliases', 'led_matrix', 'matrix_pins', 'rgblight', 'usb'): if key in new_info_data: if key not in info_data: info_data[key] = {} -- cgit v1.2.3 From b2c26f7cdd4b268e80f98cae7f444956559436ec Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Tue, 1 Dec 2020 16:04:22 -0800 Subject: get qmk generate-api into a good state --- lib/python/qmk/c_parse.py | 26 ++++++++--- lib/python/qmk/cli/generate/api.py | 2 +- lib/python/qmk/cli/generate/rules_mk.py | 9 ++-- lib/python/qmk/info.py | 82 +++++++++++++++++++++++---------- 4 files changed, 84 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index e41e271a43..67e196f0ea 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -1,12 +1,27 @@ """Functions for working with config.h files. """ from pathlib import Path +import re from milc import cli from qmk.comment_remover import comment_remover default_key_entry = {'x': -1, 'y': 0, 'w': 1} +single_comment_regex = re.compile(r' */[/*].*$') +multi_comment_regex = re.compile(r'/\*(.|\n)*\*/', re.MULTILINE) + + +def strip_line_comment(string): + """Removes comments from a single line string. + """ + return single_comment_regex.sub('', string) + + +def strip_multiline_comment(string): + """Removes comments from a single line string. + """ + return multi_comment_regex.sub('', string) def c_source_files(dir_names): @@ -53,7 +68,8 @@ def find_layouts(file): parsed_layout = [_default_key(key) for key in layout.split(',')] for key in parsed_layout: - key['matrix'] = matrix_locations.get(key['label']) + if key['label'] in matrix_locations: + key['matrix'] = matrix_locations[key['label']] parsed_layouts[macro_name] = { 'key_count': len(parsed_layout), @@ -88,12 +104,10 @@ def parse_config_h_file(config_h_file, config_h=None): if config_h_file.exists(): config_h_text = config_h_file.read_text() config_h_text = config_h_text.replace('\\\n', '') + config_h_text = strip_multiline_comment(config_h_text) for linenum, line in enumerate(config_h_text.split('\n')): - line = line.strip() - - if '//' in line: - line = line[:line.index('//')].strip() + line = strip_line_comment(line).strip() if not line: continue @@ -156,6 +170,6 @@ def _parse_matrix_locations(matrix, file, macro_name): row = row.replace('{', '').replace('}', '') for col_num, identifier in enumerate(row.split(',')): if identifier != 'KC_NO': - matrix_locations[identifier] = (row_num, col_num) + matrix_locations[identifier] = [row_num, col_num] return matrix_locations diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 6d111f244c..2fda38fc9a 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -48,7 +48,7 @@ def generate_api(cli): if 'vid' in usb and usb['vid'] not in usb_list['devices']: usb_list['devices'][usb['vid']] = {} - if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: + if 'vid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: usb_list['devices'][usb['vid']][usb['pid']] = {} if 'vid' in usb and 'pid' in usb: diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 72ed3c45fa..570ef5a0d6 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -41,9 +41,12 @@ def generate_rules_mk(cli): # Find features that should be enabled if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): - feature = feature.upper() - enabled = 'yes' if enabled else 'no' - rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') + if feature == 'bootmagic_lite' and enabled: + rules_mk_lines.append(f'BOOTMAGIC_ENABLE := lite') + else: + feature = feature.upper() + enabled = 'yes' if enabled else 'no' + rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') # Set the LED driver if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 1cf12190d6..39af88f790 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -26,13 +26,12 @@ led_matrix_properties = { } rgblight_properties = { - 'led_count': 'RGBLED_NUM', - 'pin': 'RGB_DI_PIN', - 'split_count': 'RGBLED_SPLIT', - 'max_brightness': 'RGBLIGHT_LIMIT_VAL', - 'hue_steps': 'RGBLIGHT_HUE_STEP', - 'saturation_steps': 'RGBLIGHT_SAT_STEP', - 'brightness_steps': 'RGBLIGHT_VAL_STEP' + 'led_count': ('RGBLED_NUM', int), + 'pin': ('RGB_DI_PIN', str), + 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int), + 'hue_steps': ('RGBLIGHT_HUE_STEP', int), + 'saturation_steps': ('RGBLIGHT_SAT_STEP', int), + 'brightness_steps': ('RGBLIGHT_VAL_STEP', int) } rgblight_toggles = { @@ -54,6 +53,8 @@ rgblight_animations = { 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' } +usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} + true_values = ['1', 'on', 'yes'] false_values = ['0', 'off', 'no'] @@ -97,7 +98,8 @@ def info_json(keyboard): keyboard_api_validate(info_data) except jsonschema.ValidationError as e: - cli.log.error('Invalid info.json data: %s', e.message) + 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) print(dir(e)) exit() @@ -198,6 +200,9 @@ def _extract_indicators(info_data, config_c): if json_key in info_data.get('indicators', []) and config_key in config_c: _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') + if 'indicators' not in info_data: + info_data['indicators'] = {} + if config_key in config_c: if 'indicators' not in info_data: info_data['indicators'] = {} @@ -226,10 +231,23 @@ def _extract_community_layouts(info_data, rules): def _extract_features(info_data, rules): """Find all the features enabled in rules.mk. """ + # Special handling for bootmagic which also supports a "lite" mode. + if rules.get('BOOTMAGIC_ENABLE') == 'lite': + rules['BOOTMAGIC_LITE_ENABLE'] = 'on' + del(rules['BOOTMAGIC_ENABLE']) + if rules.get('BOOTMAGIC_ENABLE') == 'full': + rules['BOOTMAGIC_ENABLE'] = 'on' + + # Skip non-boolean features we haven't implemented special handling for + for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE': + if rules.get(feature): + del(rules[feature]) + + # Process the rest of the rules as booleans for key, value in rules.items(): if key.endswith('_ENABLE'): key = '_'.join(key.split('_')[:-1]).lower() - value = True if value in true_values else False if value in false_values else value + value = True if value.lower() in true_values else False if value.lower() in false_values else value if 'config_h_features' not in info_data: info_data['config_h_features'] = {} @@ -280,12 +298,21 @@ def _extract_rgblight(info_data, config_c): rgblight = info_data.get('rgblight', {}) animations = rgblight.get('animations', {}) - for json_key, config_key in rgblight_properties.items(): + if 'RGBLED_SPLIT' in config_c: + raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip() + rgblight['split_count'] = [int(i) for i in raw_split.split(',')] + + for json_key, config_key_type in rgblight_properties.items(): + config_key, config_type = config_key_type + if config_key in config_c: if json_key in rgblight: _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) - rgblight[json_key] = config_c[config_key] + try: + rgblight[json_key] = config_type(config_c[config_key]) + except ValueError as e: + 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) for json_key, config_key in rgblight_toggles.items(): if config_key in config_c: @@ -332,11 +359,16 @@ def _extract_matrix_info(info_data, config_c): info_data['matrix_pins'] = {} + # FIXME(skullydazed/anyone): Should really check every pin, not just the first if row_pins: - info_data['matrix_pins']['rows'] = row_pins.split(',') + row_pins = [pin.strip() for pin in row_pins.split(',') if pin] + if row_pins[0][0] in 'ABCDEFGHIJK' and row_pins[0][1].isdigit(): + info_data['matrix_pins']['rows'] = row_pins if col_pins: - info_data['matrix_pins']['cols'] = col_pins.split(',') + col_pins = [pin.strip() for pin in col_pins.split(',') if pin] + if col_pins[0][0] in 'ABCDEFGHIJK' and col_pins[0][1].isdigit(): + info_data['matrix_pins']['cols'] = col_pins if direct_pins: if 'matrix_pins' in info_data: @@ -345,6 +377,9 @@ def _extract_matrix_info(info_data, config_c): info_data['matrix_pins'] = {} direct_pin_array = [] + while direct_pins[-1] != '}': + direct_pins = direct_pins[:-1] + for row in direct_pins.split('},{'): if row.startswith('{'): row = row[1:] @@ -368,8 +403,6 @@ def _extract_matrix_info(info_data, config_c): def _extract_usb_info(info_data, config_c): """Populate the USB information. """ - usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} - if 'usb' not in info_data: info_data['usb'] = {} @@ -378,10 +411,7 @@ def _extract_usb_info(info_data, config_c): if info_name in info_data['usb']: _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) - info_data['usb'][info_name] = config_c[config_name] - - elif info_name not in info_data['usb']: - _log_error(info_data, '%s not specified in config.h, and %s not specified in info.json. One is required.' % (config_name, info_name)) + info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper() return info_data @@ -519,8 +549,9 @@ def arm_processor_rules(info_data, rules): info_data['processor'] = 'unknown' if 'BOOTLOADER' in rules: - if 'bootloader' in info_data: - _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') + # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first + # if 'bootloader' in info_data: + # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') info_data['bootloader'] = rules['BOOTLOADER'] @@ -558,8 +589,9 @@ def avr_processor_rules(info_data, rules): info_data['processor'] = 'unknown' if 'BOOTLOADER' in rules: - if 'bootloader' in info_data: - _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') + # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first + # if 'bootloader' in info_data: + # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') info_data['bootloader'] = rules['BOOTLOADER'] else: @@ -593,8 +625,8 @@ def merge_info_jsons(keyboard, info_data): keyboard_validate(new_info_data) except jsonschema.ValidationError as e: - cli.log.error('Invalid info.json data: %s', e.message) - cli.log.error('Not including file %s', info_file) + json_path = '.'.join([str(p) for p in e.absolute_path]) + cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message) continue if not isinstance(new_info_data, dict): -- cgit v1.2.3 From 56ef80216ae4c67e2a70857c61d1e62eec1ab380 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 30 Dec 2020 11:21:18 -0800 Subject: make flake8 happy --- lib/python/qmk/cli/generate/rules_mk.py | 5 +- lib/python/qmk/info.py | 84 ++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 39 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 570ef5a0d6..2a7e918569 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -8,9 +8,10 @@ from qmk.path import is_keyboard, normpath info_to_rules = { 'bootloader': 'BOOTLOADER', - 'processor': 'MCU' + 'processor': 'MCU', } + @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') @@ -42,7 +43,7 @@ def generate_rules_mk(cli): if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): if feature == 'bootmagic_lite' and enabled: - rules_mk_lines.append(f'BOOTMAGIC_ENABLE := lite') + rules_mk_lines.append('BOOTMAGIC_ENABLE := lite') else: feature = feature.upper() enabled = 'yes' if enabled else 'no' diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 39af88f790..efd339115b 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -234,14 +234,14 @@ def _extract_features(info_data, rules): # Special handling for bootmagic which also supports a "lite" mode. if rules.get('BOOTMAGIC_ENABLE') == 'lite': rules['BOOTMAGIC_LITE_ENABLE'] = 'on' - del(rules['BOOTMAGIC_ENABLE']) + del rules['BOOTMAGIC_ENABLE'] if rules.get('BOOTMAGIC_ENABLE') == 'full': rules['BOOTMAGIC_ENABLE'] = 'on' # Skip non-boolean features we haven't implemented special handling for for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE': if rules.get(feature): - del(rules[feature]) + del rules[feature] # Process the rest of the rules as booleans for key, value in rules.items(): @@ -337,6 +337,45 @@ def _extract_rgblight(info_data, config_c): return info_data +def _extract_pins(pins): + """Returns a list of pins from a comma separated string of pins. + """ + pins = [pin.strip() for pin in pins.split(',') if pin] + + for pin in pins: + if pin[0] not in 'ABCDEFGHIJK' or not pin[1].isdigit(): + raise ValueError(f'Invalid pin: {pin}') + + return pins + + +def _extract_direct_matrix(info_data, direct_pins): + """ + """ + info_data['matrix_pins'] = {} + direct_pin_array = [] + + while direct_pins[-1] != '}': + direct_pins = direct_pins[:-1] + + for row in direct_pins.split('},{'): + if row.startswith('{'): + row = row[1:] + + if row.endswith('}'): + row = row[:-1] + + direct_pin_array.append([]) + + for pin in row.split(','): + if pin == 'NO_PIN': + pin = None + + direct_pin_array[-1].append(pin) + + return direct_pin_array + + def _extract_matrix_info(info_data, config_c): """Populate the matrix information. """ @@ -349,53 +388,24 @@ def _extract_matrix_info(info_data, config_c): _log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.') info_data['matrix_size'] = { - 'rows': compute(config_c.get('MATRIX_ROWS', '0')), 'cols': compute(config_c.get('MATRIX_COLS', '0')), + 'rows': compute(config_c.get('MATRIX_ROWS', '0')), } if row_pins and col_pins: if 'matrix_pins' in info_data: _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') - info_data['matrix_pins'] = {} - - # FIXME(skullydazed/anyone): Should really check every pin, not just the first - if row_pins: - row_pins = [pin.strip() for pin in row_pins.split(',') if pin] - if row_pins[0][0] in 'ABCDEFGHIJK' and row_pins[0][1].isdigit(): - info_data['matrix_pins']['rows'] = row_pins - - if col_pins: - col_pins = [pin.strip() for pin in col_pins.split(',') if pin] - if col_pins[0][0] in 'ABCDEFGHIJK' and col_pins[0][1].isdigit(): - info_data['matrix_pins']['cols'] = col_pins + info_data['matrix_pins'] = { + 'cols': _extract_pins(col_pins), + 'rows': _extract_pins(row_pins), + } if direct_pins: if 'matrix_pins' in info_data: _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') - info_data['matrix_pins'] = {} - direct_pin_array = [] - - while direct_pins[-1] != '}': - direct_pins = direct_pins[:-1] - - for row in direct_pins.split('},{'): - if row.startswith('{'): - row = row[1:] - - if row.endswith('}'): - row = row[:-1] - - direct_pin_array.append([]) - - for pin in row.split(','): - if pin == 'NO_PIN': - pin = None - - direct_pin_array[-1].append(pin) - - info_data['matrix_pins']['direct'] = direct_pin_array + info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins) return info_data -- cgit v1.2.3 From 9a178f6826a30007f84983daf1c87ded1525c9cd Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 6 Jan 2021 10:13:25 -0800 Subject: fix rgblight properties --- lib/python/qmk/cli/generate/config_h.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 09aa0041f4..15d4fbf2dd 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -173,9 +173,9 @@ def rgblight(config): for json_key, config_key in rgblight_properties.items(): if json_key in config: rgblight_config.append('') - rgblight_config.append('#ifndef %s' % (config_key,)) - rgblight_config.append('# define %s %s' % (config_key, config[json_key])) - rgblight_config.append('#endif // %s' % (config_key,)) + rgblight_config.append('#ifndef %s' % (config_key[0],)) + rgblight_config.append('# define %s %s' % (config_key[0], config[json_key])) + rgblight_config.append('#endif // %s' % (config_key[0],)) for json_key, config_key in rgblight_toggles.items(): if config.get(json_key): -- cgit v1.2.3 From b44532aa3dbdce702d662e5b1cf043e461846d8b Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 6 Jan 2021 10:28:58 -0800 Subject: change invalid pin from error to warning --- lib/python/qmk/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index efd339115b..89c08809ba 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -344,7 +344,7 @@ def _extract_pins(pins): for pin in pins: if pin[0] not in 'ABCDEFGHIJK' or not pin[1].isdigit(): - raise ValueError(f'Invalid pin: {pin}') + cli.log.warning(f'Nonstandard pin format: {pin}') return pins -- cgit v1.2.3 From e2e793c1c3efeb96108072c2c8a57555c0fa76a0 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 6 Jan 2021 10:37:31 -0800 Subject: Revert "change invalid pin from error to warning" This reverts commit 68da9cd19658071f0c2f22e97956303281703176. --- lib/python/qmk/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 89c08809ba..efd339115b 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -344,7 +344,7 @@ def _extract_pins(pins): for pin in pins: if pin[0] not in 'ABCDEFGHIJK' or not pin[1].isdigit(): - cli.log.warning(f'Nonstandard pin format: {pin}') + raise ValueError(f'Invalid pin: {pin}') return pins -- cgit v1.2.3 From 30331b383f9ef4620e47aa07e4f9af7fae9d30b3 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Fri, 8 Jan 2021 00:00:15 -0800 Subject: fix bugs triggered by certain boards --- lib/python/qmk/c_parse.py | 4 ++-- lib/python/qmk/cli/generate/config_h.py | 6 +++--- lib/python/qmk/info.py | 35 ++++++++++++++++++++++----------- 3 files changed, 29 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 67e196f0ea..0338484ec7 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -9,7 +9,7 @@ from qmk.comment_remover import comment_remover default_key_entry = {'x': -1, 'y': 0, 'w': 1} single_comment_regex = re.compile(r' */[/*].*$') -multi_comment_regex = re.compile(r'/\*(.|\n)*\*/', re.MULTILINE) +multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE) def strip_line_comment(string): @@ -103,7 +103,7 @@ def parse_config_h_file(config_h_file, config_h=None): if config_h_file.exists(): config_h_text = config_h_file.read_text() - config_h_text = config_h_text.replace('\\\n', '') + config_h_text = config_h_text.replace('\\\n', '') # Why are you here? config_h_text = strip_multiline_comment(config_h_text) for linenum, line in enumerate(config_h_text.split('\n')): diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 15d4fbf2dd..1de84de7a9 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -64,7 +64,7 @@ def direct_pins(direct_pins): rows = [] for row in direct_pins: - cols = ','.join([col or 'NO_PIN' for col in row]) + cols = ','.join(map(str, [col or 'NO_PIN' for col in row])) rows.append('{' + cols + '}') col_count = len(direct_pins[0]) @@ -88,7 +88,7 @@ def direct_pins(direct_pins): def col_pins(col_pins): """Return the config.h lines that set the column pins. """ - cols = ','.join(col_pins) + cols = ','.join(map(str, [pin or 'NO_PIN' for pin in col_pins])) col_num = len(col_pins) return """ @@ -105,7 +105,7 @@ def col_pins(col_pins): def row_pins(row_pins): """Return the config.h lines that set the row pins. """ - rows = ','.join(row_pins) + rows = ','.join(map(str, [pin or 'NO_PIN' for pin in row_pins])) row_num = len(row_pins) return """ diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index efd339115b..28c281a4bc 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -315,11 +315,10 @@ def _extract_rgblight(info_data, config_c): 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) for json_key, config_key in rgblight_toggles.items(): - if config_key in config_c: - if json_key in rgblight: - _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key) + if config_key in config_c and json_key in rgblight: + _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key) - rgblight[json_key] = config_c[config_key] + rgblight[json_key] = config_key in config_c for json_key, config_key in rgblight_animations.items(): if config_key in config_c: @@ -337,16 +336,30 @@ def _extract_rgblight(info_data, config_c): return info_data -def _extract_pins(pins): - """Returns a list of pins from a comma separated string of pins. +def _pin_name(pin): + """Returns the proper representation for a pin. """ - pins = [pin.strip() for pin in pins.split(',') if pin] + pin = pin.strip() + + if not pin: + return None + + elif pin.isdigit(): + return int(pin) - for pin in pins: - if pin[0] not in 'ABCDEFGHIJK' or not pin[1].isdigit(): - raise ValueError(f'Invalid pin: {pin}') + elif pin == 'NO_PIN': + return None - return pins + elif pin[0] in 'ABCDEFGHIJK' and pin[1].isdigit(): + return pin + + raise ValueError(f'Invalid pin: {pin}') + + +def _extract_pins(pins): + """Returns a list of pins from a comma separated string of pins. + """ + return [_pin_name(pin) for pin in pins.split(',')] def _extract_direct_matrix(info_data, direct_pins): -- cgit v1.2.3 From 58fcdf8c07e5c1363b6b3eaf23883853dfd12f53 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Fri, 8 Jan 2021 00:21:51 -0800 Subject: remove extraneous comment --- lib/python/qmk/c_parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 0338484ec7..ade3e38059 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -103,7 +103,7 @@ def parse_config_h_file(config_h_file, config_h=None): if config_h_file.exists(): config_h_text = config_h_file.read_text() - config_h_text = config_h_text.replace('\\\n', '') # Why are you here? + config_h_text = config_h_text.replace('\\\n', '') config_h_text = strip_multiline_comment(config_h_text) for linenum, line in enumerate(config_h_text.split('\n')): -- cgit v1.2.3 From e2c7f893470465f986bd6849c18606be0f2b7842 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Fri, 8 Jan 2021 08:46:51 -0800 Subject: typo fix --- lib/python/qmk/cli/generate/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 2fda38fc9a..6d111f244c 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -48,7 +48,7 @@ def generate_api(cli): if 'vid' in usb and usb['vid'] not in usb_list['devices']: usb_list['devices'][usb['vid']] = {} - if 'vid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: + if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: usb_list['devices'][usb['vid']][usb['pid']] = {} if 'vid' in usb and 'pid' in usb: -- cgit v1.2.3 From 962bc8d9dd413690dbeadeaac971a5389697210f Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 9 Jan 2021 13:34:14 -0800 Subject: Use the schema to eliminate custom code (#11108) * use the schema to eliminate custom code * Update docs/reference_info_json.md Co-authored-by: Ryan <fauxpark@gmail.com> * make flake8 happy * bugfix * do not overwrite make vars from json Co-authored-by: Ryan <fauxpark@gmail.com> --- lib/python/qmk/cli/generate/info_json.py | 48 ++++++++++++++++--------- lib/python/qmk/cli/generate/layouts.py | 4 +++ lib/python/qmk/cli/generate/rules_mk.py | 10 +++--- lib/python/qmk/constants.py | 2 +- lib/python/qmk/info.py | 60 +++++++++++++++----------------- 5 files changed, 71 insertions(+), 53 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index fba4b1c014..f3fc54ddcf 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py @@ -4,14 +4,41 @@ Compile an info.json for a particular keyboard and pretty-print it. """ import json +from jsonschema import Draft7Validator, validators from milc import cli -from qmk.info_json_encoder import InfoJSONEncoder from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.info import info_json +from qmk.info import info_json, _jsonschema +from qmk.info_json_encoder import InfoJSONEncoder from qmk.path import is_keyboard +def pruning_validator(validator_class): + """Extends Draft7Validator to remove properties that aren't specified in the schema. + """ + validate_properties = validator_class.VALIDATORS["properties"] + + def remove_additional_properties(validator, properties, instance, schema): + for prop in list(instance.keys()): + if prop not in properties: + del instance[prop] + + for error in validate_properties(validator, properties, instance, schema): + yield error + + return validators.extend(validator_class, {"properties": remove_additional_properties}) + + +def strip_info_json(kb_info_json): + """Remove the API-only properties from the info.json. + """ + pruning_draft_7_validator = pruning_validator(Draft7Validator) + schema = _jsonschema('keyboard') + validator = pruning_draft_7_validator(schema).validate + + return validator(kb_info_json) + + @cli.argument('-kb', '--keyboard', help='Keyboard to show info for.') @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) @@ -22,7 +49,7 @@ def generate_info_json(cli): """ # Determine our keyboard(s) if not cli.config.generate_info_json.keyboard: - cli.log.error('Missing paramater: --keyboard') + cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False @@ -32,18 +59,7 @@ def generate_info_json(cli): # Build the info.json file kb_info_json = info_json(cli.config.generate_info_json.keyboard) - pared_down_json = {} - - for key in ('manufacturer', 'maintainer', 'usb', 'keyboard_name', 'width', 'height', 'debounce', 'diode_direction', 'features', 'community_layouts', 'layout_aliases', 'matrix_pins', 'rgblight', 'url'): - if key in kb_info_json: - pared_down_json[key] = kb_info_json[key] - - pared_down_json['layouts'] = {} - if 'layouts' in kb_info_json: - for layout_name, layout in kb_info_json['layouts'].items(): - pared_down_json['layouts'][layout_name] = {} - pared_down_json['layouts'][layout_name]['key_count'] = layout.get('key_count', len(layout['layout'])) - pared_down_json['layouts'][layout_name]['layout'] = layout['layout'] + strip_info_json(kb_info_json) # Display the results - print(json.dumps(pared_down_json, indent=2, cls=InfoJSONEncoder)) + print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder)) diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index 273870e15c..b7baae0651 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -54,6 +54,10 @@ def generate_layouts(cli): if kb_info_json['layouts'][layout_name]['c_macro']: continue + if 'matrix' not in kb_info_json['layouts'][layout_name]['layout'][0]: + cli.log.debug('%s/%s: No matrix data!', cli.config.generate_layouts.keyboard, layout_name) + continue + layout_keys = [] layout_matrix = [['KC_NO' for i in range(col_num)] for i in range(row_num)] diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 2a7e918569..0fdccb4048 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -37,26 +37,26 @@ def generate_rules_mk(cli): # Bring in settings for info_key, rule_key in info_to_rules.items(): - rules_mk_lines.append(f'{rule_key} := {kb_info_json[info_key]}') + rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}') # Find features that should be enabled if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): if feature == 'bootmagic_lite' and enabled: - rules_mk_lines.append('BOOTMAGIC_ENABLE := lite') + rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite') else: feature = feature.upper() enabled = 'yes' if enabled else 'no' - rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') + rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') # Set the LED driver if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: driver = kb_info_json['led_matrix']['driver'] - rules_mk_lines.append(f'LED_MATRIX_DRIVER = {driver}') + rules_mk_lines.append(f'LED_MATRIX_DRIVER ?= {driver}') # Add community layouts if 'community_layouts' in kb_info_json: - rules_mk_lines.append(f'LAYOUTS = {" ".join(kb_info_json["community_layouts"])}') + rules_mk_lines.append(f'LAYOUTS ?= {" ".join(kb_info_json["community_layouts"])}') # Show the results rules_mk = '\n'.join(rules_mk_lines) + '\n' diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 675832c506..6a643070fd 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -26,5 +26,5 @@ ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop' LED_INDICATORS = { 'caps_lock': 'LED_CAPS_LOCK_PIN', 'num_lock': 'LED_NUM_LOCK_PIN', - 'scrol_lock': 'LED_SCROLL_LOCK_PIN' + 'scrol_lock': 'LED_SCROLL_LOCK_PIN', } diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 28c281a4bc..0ea5136a97 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -1,6 +1,7 @@ """Functions that help us generate and use info.json files. """ import json +from collections.abc import Mapping from glob import glob from pathlib import Path @@ -140,6 +141,8 @@ def _json_load(json_file): def _jsonschema(schema_name): """Read a jsonschema file from disk. + + FIXME(skullydazed/anyone): Refactor to make this a public function. """ schema_path = Path(f'data/schemas/{schema_name}.jsonschema') @@ -638,49 +641,44 @@ def unknown_processor_rules(info_data, rules): return info_data +def deep_update(origdict, newdict): + """Update a dictionary in place, recursing to do a deep copy. + """ + for key, value in newdict.items(): + if isinstance(value, Mapping): + origdict[key] = deep_update(origdict.get(key, {}), value) + + else: + origdict[key] = value + + return origdict + + def merge_info_jsons(keyboard, info_data): """Return a merged copy of all the info.json files for a keyboard. """ for info_file in find_info_json(keyboard): # Load and validate the JSON data + new_info_data = _json_load(info_file) + + if not isinstance(new_info_data, dict): + _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) + continue + try: - new_info_data = _json_load(info_file) keyboard_validate(new_info_data) - except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) - cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message) + cli.log.error('Not including data from file: %s', info_file) + cli.log.error('\t%s: %s', json_path, e.message) continue - if not isinstance(new_info_data, dict): - _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) - continue - - # Copy whitelisted keys into `info_data` - for key in ('debounce', 'diode_direction', 'indicators', 'keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'): - if key in new_info_data: - info_data[key] = new_info_data[key] - - # Deep merge certain keys - # 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. - for key in ('features', 'layout_aliases', 'led_matrix', 'matrix_pins', 'rgblight', 'usb'): - if key in new_info_data: - if key not in info_data: - info_data[key] = {} - - info_data[key].update(new_info_data[key]) - - # Merge the layouts - if 'community_layouts' in new_info_data: - if 'community_layouts' in info_data: - for layout in new_info_data['community_layouts']: - if layout not in info_data['community_layouts']: - info_data['community_layouts'].append(layout) - else: - info_data['community_layouts'] = new_info_data['community_layouts'] + # Mark the layouts as coming from json + for layout in new_info_data.get('layouts', {}).values(): + layout['c_macro'] = False - if 'layouts' in new_info_data: - _merge_layouts(info_data, new_info_data) + # Update info_data with the new data + deep_update(info_data, new_info_data) return info_data -- cgit v1.2.3 From eaa9106ec74591593e638ac015a5c90d17b30612 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 9 Jan 2021 20:18:47 -0800 Subject: Add support for specifying BOARD in info.json (#11492) * add support for specifying BOARD in info.json * move BOARD from rules.mk to info.json for clueboard * fix keyboards that do not require board * remove out of compliance values --- lib/python/qmk/cli/generate/rules_mk.py | 4 +++- lib/python/qmk/info.py | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 0fdccb4048..b262e3c666 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -7,6 +7,7 @@ from qmk.info import info_json from qmk.path import is_keyboard, normpath info_to_rules = { + 'board': 'BOARD', 'bootloader': 'BOOTLOADER', 'processor': 'MCU', } @@ -37,7 +38,8 @@ def generate_rules_mk(cli): # Bring in settings for info_key, rule_key in info_to_rules.items(): - rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}') + if info_key in kb_info_json: + rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}') # Find features that should be enabled if 'features' in kb_info_json: diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 0ea5136a97..cc81f7a086 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -462,7 +462,7 @@ def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) - mcu = rules.get('MCU') + mcu = rules.get('MCU', info_data.get('processor')) if mcu in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) @@ -594,6 +594,12 @@ def arm_processor_rules(info_data, rules): elif 'ARM_ATSAM' in rules: info_data['platform'] = 'ARM_ATSAM' + if 'BOARD' in rules: + if 'board' in info_data: + _log_warning(info_data, 'Board is specified in both info.json and rules.mk, the rules.mk value wins.') + + info_data['board'] = rules['BOARD'] + return info_data -- cgit v1.2.3 From a7c45daeb8022097a0dbb3859bfcd56b73d65d14 Mon Sep 17 00:00:00 2001 From: Nick Brassel <nick@tzarc.org> Date: Wed, 20 Jan 2021 00:59:12 +1100 Subject: ChibiOS 20.3.2 (#11495) * Update ChibiOS submodule * Update ChibiOS-Contrib submodule * Update ChibiOS config files --- lib/chibios | 2 +- lib/chibios-contrib | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/chibios b/lib/chibios index 313416b8fd..ffe54d63cb 160000 --- a/lib/chibios +++ b/lib/chibios @@ -1 +1 @@ -Subproject commit 313416b8fda90d9973a749a0a35970956852c286 +Subproject commit ffe54d63cb10a355add318f8e922e39f1c3d4bfd diff --git a/lib/chibios-contrib b/lib/chibios-contrib index e3a3a24047..61baa6b036 160000 --- a/lib/chibios-contrib +++ b/lib/chibios-contrib @@ -1 +1 @@ -Subproject commit e3a3a24047717af33b098266e11c2e4e5102206a +Subproject commit 61baa6b036138c155f7cfc5646d833d9423f3243 -- cgit v1.2.3 From 78fe155b7241900988f26479b8e417623b137320 Mon Sep 17 00:00:00 2001 From: Nick Brassel <nick@tzarc.org> Date: Wed, 20 Jan 2021 02:19:36 +1100 Subject: Fix up comments showing how to execute config migration. (#11621) --- lib/python/qmk/cli/chibios/confmigrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index b9cfda9614..9d25488586 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -32,7 +32,7 @@ file_header = """\ /* * This file was auto-generated by: - * `qmk chibios-confupdate -i {0} -r {1}` + * `qmk chibios-confmigrate -i {0} -r {1}` */ #pragma once -- cgit v1.2.3 From bdb757e189bf72c0ef382a924328642a629e06d5 Mon Sep 17 00:00:00 2001 From: Nick Brassel <nick@tzarc.org> Date: Thu, 21 Jan 2021 17:00:53 +1100 Subject: ChibiOS conf migrations... take 11 (#11646) * Allow forcing re-migration. * ChibiOS conf upgrade for acheron/arctic acheron/arctic - 2aedbe9103fff6c37e596c33c9ed337957647368 * ChibiOS conf upgrade for acheron/austin acheron/austin - c2f4e3b7fc9f1c3d64f47d139bc58367afeca1b7 * ChibiOS conf upgrade for acheron/keebspcb acheron/keebspcb - 1ba976e409732bfa5c0487dd33e20bec06852ce4 * ChibiOS conf upgrade for acheron/lasgweloth acheron/lasgweloth - 01a2e70d0d86de0ff05d0b898a6e3b4428ee1581 * ChibiOS conf upgrade for aeboards/ext65/rev2 aeboards/ext65/rev2 - 3c9a45cafb4ed6d9672aaff4548b105193633a87 * ChibiOS conf upgrade for ai03/vega ai03/vega - 1bd0dfccb99baa69bacd2d55f2bfa72019b8bf80 * ChibiOS conf upgrade for akegata_denki/device_one akegata_denki/device_one - a013823188660f5fca37c5763f160f8646aed7a7 * ChibiOS conf upgrade for at_at/660m at_at/660m - 9999583e8bec2772046132a22818482d24e18c84 * ChibiOS conf upgrade for box75 box75 - 1126206109a942237eea96f3a9608e3c9ed55f8e * ChibiOS conf upgrade for bt66tech/bt66tech60 bt66tech/bt66tech60 - b69120638a8b2c86c008fd0592be918383d8a454 * ChibiOS conf upgrade for cannonkeys/an_c cannonkeys/an_c - c755f6caaccbe2b30d95661a3d441b836534c29f * ChibiOS conf upgrade for cannonkeys/atlas cannonkeys/atlas - 1a18c55492a834b82715516ea4cd4d3d94508743 * ChibiOS conf upgrade for cannonkeys/chimera65 cannonkeys/chimera65 - 6bf226ef353da9106c381a8ac95a9b90529327e5 * ChibiOS conf upgrade for cannonkeys/db60 cannonkeys/db60 - 07e3246f8e3adc5b6918809e6a5aa1ad064b9a09 * ChibiOS conf upgrade for cannonkeys/devastatingtkl cannonkeys/devastatingtkl - 907d258e29eb7e35b1f868c1ea6148bfd1e3a3f2 * ChibiOS conf upgrade for cannonkeys/instant60 cannonkeys/instant60 - ac876ff6746b08839a3936dd4126b52683b763f4 * ChibiOS conf upgrade for cannonkeys/instant65 cannonkeys/instant65 - 3b8c89ec5fecbdb35cccb00c45e64a798528dbda * ChibiOS conf upgrade for cannonkeys/iron165 cannonkeys/iron165 - 0814ec4067d9857b0134e38384f272cf7172fb03 * ChibiOS conf upgrade for cannonkeys/obliterated75 cannonkeys/obliterated75 - 0f376e5d9a232c62e9c60f8fdc81d12732352ddc * ChibiOS conf upgrade for cannonkeys/onyx cannonkeys/onyx - d35134ebe09046b91ab176035d3742d80b23ed8e * ChibiOS conf upgrade for cannonkeys/ortho48 cannonkeys/ortho48 - 828dbfbb013ff4fefe75044b3c9fd31fec5e95a1 * ChibiOS conf upgrade for cannonkeys/ortho60 cannonkeys/ortho60 - 2aca4dd4234e0dac828f5fa15ae5b3bc3c0605dc * ChibiOS conf upgrade for cannonkeys/ortho75 cannonkeys/ortho75 - 4af8aaaded0ff53e9c01473f5968cc169f268647 * ChibiOS conf upgrade for cannonkeys/practice60 cannonkeys/practice60 - e01df551d9483eeb3f825fcc453317fe65f674b5 * ChibiOS conf upgrade for cannonkeys/practice65 cannonkeys/practice65 - 156163789d4c96cdd4787895788c1d02a2367f5c * ChibiOS conf upgrade for cannonkeys/rekt1800 cannonkeys/rekt1800 - 15882afb6373dea3e5f7ed13c19744ee2463f3bf * ChibiOS conf upgrade for cannonkeys/sagittarius cannonkeys/sagittarius - f49fee611ffcc02cb3dc7f249fc8c83f136907b9 * ChibiOS conf upgrade for cannonkeys/satisfaction75 cannonkeys/satisfaction75 - 2c9fbbea3a38820774f6ff436ef51017b545475a * ChibiOS conf upgrade for cannonkeys/savage65 cannonkeys/savage65 - ddd764be363c46f3e9b1a16151bd02d8f66b2dd4 * ChibiOS conf upgrade for cannonkeys/tmov2 cannonkeys/tmov2 - 7af36cd2a42015838b77697624e90008ffa72778 * ChibiOS conf upgrade for cannonkeys/tsukuyomi cannonkeys/tsukuyomi - a120bea5dacb2cd6f143458cece46d2860ea196d * ChibiOS conf upgrade for cheshire/curiosity cheshire/curiosity - 93d8fe3b7a8e028d4b015941ee71b50afe86bc8a * ChibiOS conf upgrade for coarse/cordillera coarse/cordillera - 7d986d3f534786f302cbf74a5e6b7e51ffe60093 * ChibiOS conf upgrade for converter/siemens_tastatur converter/siemens_tastatur - 3f79f1467bbc56f284aceafd76ab5c6a1cbb68f4 * ChibiOS conf upgrade for cutie_club/borsdorf cutie_club/borsdorf - f8c6015b7aacfd5edef4f22f719eea30660005e7 * ChibiOS conf upgrade for dekunukem/duckypad dekunukem/duckypad - d9c162d0867d3925cca2e653a3a3870eac2d23d2 * ChibiOS conf upgrade for ebastler/isometria_75/rev1 ebastler/isometria_75/rev1 - 2ecdd00b8c6cc794a7014747185b88374d5766c0 * ChibiOS conf upgrade for ergodox_infinity ergodox_infinity - 76736c701db22e890764481c25bd38badf32a1cf * ChibiOS conf upgrade for ergodox_stm32 ergodox_stm32 - a7bdb0b7822617ca0f9d316a082874ac0fef5964 * ChibiOS conf upgrade for function96 function96 - 1b9e394a86dab24b85d160afe9281b5e7652afe2 * ChibiOS conf upgrade for geekboards/macropad_v2 geekboards/macropad_v2 - de261e46a42cc7b7ff793e26200669f94b1dbb18 * ChibiOS conf upgrade for generic_panda/panda65_01 generic_panda/panda65_01 - 592d2443033875e0309506aa8cb5212389054458 * ChibiOS conf upgrade for handwired/ck4x4 handwired/ck4x4 - 9d7d4af7f2d787afd638e976334cd37c490040d7 * ChibiOS conf upgrade for handwired/onekey/blackpill_f401 handwired/onekey/blackpill_f401 - c9257a30203b3a0e5aa18b35e07281fcd043ab5d * ChibiOS conf upgrade for handwired/onekey/blackpill_f411 handwired/onekey/blackpill_f411 - dfccc2c6b245cfb561faba60c9f3fc7af8c3db30 * ChibiOS conf upgrade for handwired/onekey/bluepill handwired/onekey/bluepill - 393f4b94e9a0fbc9cd00de1acde346ad72250b4a * ChibiOS conf upgrade for handwired/onekey/stm32f0_disco handwired/onekey/stm32f0_disco - d827881f9bed09fd41c7c4e9d066e96b6643a099 * ChibiOS conf upgrade for handwired/onekey/teensy_32 handwired/onekey/teensy_32 - dd90f6c823da80ba43afa41249a2bdbbab98b04c * ChibiOS conf upgrade for handwired/onekey/teensy_lc handwired/onekey/teensy_lc - 34596f2eeb62403cce532d20cf893ebd77fe2d05 * ChibiOS conf upgrade for handwired/pill60/blackpill_f401 handwired/pill60/blackpill_f401 - 20bbccb5058085076357f4fa956eede2c0af86e8 * ChibiOS conf upgrade for handwired/pill60/blackpill_f411 handwired/pill60/blackpill_f411 - e03a4c3ec3b9ba65b8067126a039a8eb2cd10e51 * ChibiOS conf upgrade for handwired/pill60/bluepill handwired/pill60/bluepill - 2fb721fb4376cde2067eed1c0c8acbf3bf5851c3 * ChibiOS conf upgrade for handwired/riblee_f401 handwired/riblee_f401 - 2823cd98d64c91b146dae343a32176d51772952b * ChibiOS conf upgrade for handwired/riblee_f411 handwired/riblee_f411 - 5cf7589c9a11c4ed14e7d28676ac836fbb07d013 * ChibiOS conf upgrade for handwired/sono1 handwired/sono1 - d972acee03efb4917fc42f7d72c0a416b67ebd4a * ChibiOS conf upgrade for handwired/t111 handwired/t111 - f947c1d59025fe04e7d7b999d80e20277be4366c * ChibiOS conf upgrade for handwired/twadlee/tp69 handwired/twadlee/tp69 - 48ab62464ba1f2651f4468cbec7c6058e3b8c158 * ChibiOS conf upgrade for handwired/z150 handwired/z150 - 35e85696845e965d7f2dcdb372ed20c17d42c2bb * ChibiOS conf upgrade for hub20 hub20 - f094036a182169b5a73f89f09f2ccaa3e5fc9e7e * ChibiOS conf upgrade for iron180 iron180 - 5efb6f21c848b3c964795d9debddceac7628933d * ChibiOS conf upgrade for jm60 jm60 - dbbdbe8b27b3c1d0a25981b2f75728d163d5d299 * ChibiOS conf upgrade for keebio/bdn9/rev2 keebio/bdn9/rev2 - 0031632523dddfc8f7b3a02cf9c5990ebbb3856d * ChibiOS conf upgrade for keebio/choconum keebio/choconum - 4484ef6936a497610ca5ae4d378bc07b2bc1b1c7 * ChibiOS conf upgrade for matrix/m20add matrix/m20add - 6068aeff5b14c26de8838180f4397800abe9f1aa * ChibiOS conf upgrade for matrix/noah matrix/noah - fcde175fb1d3f5bf0716ac5a880c2cc9ab4bbf7f * ChibiOS conf upgrade for misterknife/knife66 misterknife/knife66 - e7f3ceffb94fb8e680da2af27fa7cb1a0a52b699 * ChibiOS conf upgrade for misterknife/knife66_iso misterknife/knife66_iso - ee027db939c098f908f70f02ffa9cc3952139f70 * ChibiOS conf upgrade for nebula12 nebula12 - 2f3a25b6675dcd0fabe75e7bb63038d3cac19610 * ChibiOS conf upgrade for nemui nemui - 4030faa372512f766fd747876cb28176302453f9 * ChibiOS conf upgrade for nibiria/stream15 nibiria/stream15 - fd7b09affa208403099ef9bff1b756fac0d4f918 * ChibiOS conf upgrade for nightingale_studios/hailey nightingale_studios/hailey - 496a44b2f789cc59a6110c7c1c41d57e59c722be * ChibiOS conf upgrade for planck/rev6 planck/rev6 - 6fb8f0bc21b4ab774ce3d339fdf6f25d96059f10 * ChibiOS conf upgrade for primekb/meridian primekb/meridian - 8021ed99e8bdf92faab806780186cc924dd59e50 * ChibiOS conf upgrade for projectkb/alice projectkb/alice - ad7678d475a14c54f28b62d1c1b15f5b4c4448bc * ChibiOS conf upgrade for projectkb/signature87 projectkb/signature87 - de61338b0a5c477f39c000df8117dd3017c2643d * ChibiOS conf upgrade for ramonimbao/squishy65 ramonimbao/squishy65 - 2ea1a8b18c39d0e193bbbf5836191874d3aafc53 * ChibiOS conf upgrade for ramonimbao/wete ramonimbao/wete - a80b3b309f06566adcdf20234d81c1d5aa599b00 * ChibiOS conf upgrade for rart/rartlice rart/rartlice - c8c80c302428d62bb8b08e3185509a233a94f5f0 * ChibiOS conf upgrade for retro_75 retro_75 - 7434e266bba43ded8ca1ff75d77faa506473ca91 * ChibiOS conf upgrade for rocketboard_16 rocketboard_16 - 1182a8730a84b58eba5a470286b6db6134d36f54 * ChibiOS conf upgrade for satt/vision satt/vision - c85a28a8d7e321511a17a7e7084d0a7876d1779a * ChibiOS conf upgrade for technika technika - 7c59de348f0ec5f6ac9e260806a87aaf16fbafa2 * ChibiOS conf upgrade for tkc/godspeed75 tkc/godspeed75 - e447b39dca393736fbf289049699d5b4748bb8c6 * ChibiOS conf upgrade for tkw/stoutgat/v2/f411 tkw/stoutgat/v2/f411 - 58e9b909379ad107affbff0508e3923264b75426 * ChibiOS conf upgrade for viktus/styrka viktus/styrka - 36bf0921be79a8b037fdc25343584574a312be60 * ChibiOS conf upgrade for westfoxtrot/prophet westfoxtrot/prophet - 80036c8f986c8d3261d6b0748d2057eccab6430b * ChibiOS conf upgrade for whitefox whitefox - c9eccac0196c3e2e2ea6c90d3036283a3eabff2c * ChibiOS conf upgrade for wolfmarkclub/wm1 wolfmarkclub/wm1 - 0d22a426255b829a7805bd6a3ff927198b1e31b4 * ChibiOS conf upgrade for xelus/kangaroo xelus/kangaroo - fdb1ffb66f4841f662e968929f058d4fd403c97e * ChibiOS conf upgrade for xelus/trinityxttkl xelus/trinityxttkl - a9641e5b39b7bb38351b9109f0f381837b2e8e79 * ChibiOS conf upgrade for xelus/valor_frl_tkl xelus/valor_frl_tkl - 6c3b577852736a68727ec319c30712b3088e65cd * ChibiOS conf upgrade for xiaomi/mk02 xiaomi/mk02 - 4c1406ebfee73551a844ef7ab29fc5788d7e875c * ChibiOS conf upgrade for zoo/wampus zoo/wampus - 930c903e85478e220a235f45593c03512c66bc2a * ChibiOS conf upgrade for zvecr/split_blackpill zvecr/split_blackpill - c0e3e1583262e4cb0ebfe2efa6356ed6c5c957ca --- lib/python/qmk/cli/chibios/confmigrate.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index 9d25488586..3e348b2b07 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -111,6 +111,7 @@ def migrate_mcuconf_h(to_override, outfile): @cli.argument('-r', '--reference', type=normpath, arg_only=True, help='Specify the reference file to compare against') @cli.argument('-o', '--overwrite', arg_only=True, action='store_true', help='Overwrites the input file during migration.') @cli.argument('-d', '--delete', arg_only=True, action='store_true', help='If the file has no overrides, migration will delete the input file.') +@cli.argument('-f', '--force', arg_only=True, action='store_true', help='Re-migrates an already migrated file, even if it doesn\'t detect a full ChibiOS config.') @cli.subcommand('Generates a migrated ChibiOS configuration file, as a result of comparing the input against a reference') def chibios_confmigrate(cli): """Generates a usable ChibiOS replacement configuration file, based on a fully-defined conf and a reference config. @@ -142,19 +143,19 @@ def chibios_confmigrate(cli): eprint('--------------------------------------') - if "CHCONF_H" in input_defs["dict"] or "_CHCONF_H_" in input_defs["dict"]: + if cli.args.input.name == "chconf.h" and ("CHCONF_H" in input_defs["dict"] or "_CHCONF_H_" in input_defs["dict"] or cli.args.force): migrate_chconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: with open(cli.args.input, "w") as out_file: migrate_chconf_h(to_override, outfile=out_file) - elif "HALCONF_H" in input_defs["dict"] or "_HALCONF_H_" in input_defs["dict"]: + elif cli.args.input.name == "halconf.h" and ("HALCONF_H" in input_defs["dict"] or "_HALCONF_H_" in input_defs["dict"] or cli.args.force): migrate_halconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: with open(cli.args.input, "w") as out_file: migrate_halconf_h(to_override, outfile=out_file) - elif "MCUCONF_H" in input_defs["dict"] or "_MCUCONF_H_" in input_defs["dict"]: + elif cli.args.input.name == "mcuconf.h" and ("MCUCONF_H" in input_defs["dict"] or "_MCUCONF_H_" in input_defs["dict"] or cli.args.force): migrate_mcuconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: with open(cli.args.input, "w") as out_file: -- cgit v1.2.3 From d92ffd1157e3ecc4ae2dbf8548c45c8b0269f664 Mon Sep 17 00:00:00 2001 From: Dasky <32983009+daskygit@users.noreply.github.com> Date: Sat, 30 Jan 2021 03:53:56 +0000 Subject: Adds AT90USB162 support (#11570) * at90usb162 support * fix missing bracket * Apply suggestions from code review Co-authored-by: Ryan <fauxpark@gmail.com> Co-authored-by: Ryan <fauxpark@gmail.com> --- lib/python/qmk/constants.py | 2 +- lib/python/qmk/os_helpers/linux/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 404a58a7e5..cb94613562 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -11,7 +11,7 @@ MAX_KEYBOARD_SUBFOLDERS = 5 # Supported processor types CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F411' -LUFA_PROCESSORS = 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None +LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' # Common format strings diff --git a/lib/python/qmk/os_helpers/linux/__init__.py b/lib/python/qmk/os_helpers/linux/__init__.py index 86850bf284..a04ac4f8a9 100644 --- a/lib/python/qmk/os_helpers/linux/__init__.py +++ b/lib/python/qmk/os_helpers/linux/__init__.py @@ -48,6 +48,7 @@ def check_udev_rules(): _udev_rule("03eb", "2ff3"), # ATmega16U4 _udev_rule("03eb", "2ff4"), # ATmega32U4 _udev_rule("03eb", "2ff9"), # AT90USB64 + _udev_rule("03eb", "2ffa"), # AT90USB162 _udev_rule("03eb", "2ffb") # AT90USB128 }, 'kiibohd': {_udev_rule("1c11", "b007")}, -- cgit v1.2.3 From ef6329af7c7be77b537fbfc5a5cc7105acc679f7 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sun, 31 Jan 2021 12:46:00 -0800 Subject: Create a system to map between info.json and config.h/rules.mk (#11548) * generate rules.mk from a json mapping * generate rules.mk from a json mapping * support for config.h from json maps * improve the mapping system * document the mapping system * move data/maps to data/mappings * fix flake8 errors * fixup LED_MATRIX_DRIVER * remove product and description from the vision_division keymap level * reduce the complexity of generate-rules-mk * add tests for the generate commands * fix qmk doctor when submodules are not clean --- lib/python/qmk/cli/doctor.py | 4 +- lib/python/qmk/cli/generate/config_h.py | 245 ++++++--------------- lib/python/qmk/cli/generate/rules_mk.py | 61 +++--- lib/python/qmk/constants.py | 2 +- lib/python/qmk/info.py | 343 +++++++++--------------------- lib/python/qmk/tests/test_cli_commands.py | 29 +++ 6 files changed, 231 insertions(+), 453 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py index 70f32911a4..28d480707c 100755 --- a/lib/python/qmk/cli/doctor.py +++ b/lib/python/qmk/cli/doctor.py @@ -107,9 +107,9 @@ def doctor(cli): submodules.update() sub_ok = check_submodules() - if CheckStatus.ERROR in sub_ok: + if sub_ok == CheckStatus.ERROR: status = CheckStatus.ERROR - elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK: + elif sub_ok == CheckStatus.WARNING and status == CheckStatus.OK: status = CheckStatus.WARNING # Report a summary of our findings to the user diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 1de84de7a9..7ddad745d1 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -1,62 +1,14 @@ """Used by the make system to generate info_config.h from info.json. """ +from pathlib import Path + +from dotty_dict import dotty from milc import cli -from qmk.constants import LED_INDICATORS from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.info import info_json, rgblight_animations, rgblight_properties, rgblight_toggles +from qmk.info import _json_load, info_json from qmk.path import is_keyboard, normpath -usb_prop_map = { - 'vid': 'VENDOR_ID', - 'pid': 'PRODUCT_ID', - 'device_ver': 'DEVICE_VER', -} - - -def debounce(debounce): - """Return the config.h lines that set debounce - """ - return """ -#ifndef DEBOUNCE -# define DEBOUNCE %s -#endif // DEBOUNCE -""" % debounce - - -def diode_direction(diode_direction): - """Return the config.h lines that set diode direction - """ - return """ -#ifndef DIODE_DIRECTION -# define DIODE_DIRECTION %s -#endif // DIODE_DIRECTION -""" % diode_direction - - -def keyboard_name(keyboard_name): - """Return the config.h lines that set the keyboard's name. - """ - return """ -#ifndef DESCRIPTION -# define DESCRIPTION %s -#endif // DESCRIPTION - -#ifndef PRODUCT -# define PRODUCT %s -#endif // PRODUCT -""" % (keyboard_name.replace("'", ""), keyboard_name.replace("'", "")) - - -def manufacturer(manufacturer): - """Return the config.h lines that set the manufacturer. - """ - return """ -#ifndef MANUFACTURER -# define MANUFACTURER %s -#endif // MANUFACTURER -""" % (manufacturer.replace("'", "")) - def direct_pins(direct_pins): """Return the config.h lines that set the direct pins. @@ -72,80 +24,34 @@ def direct_pins(direct_pins): return """ #ifndef MATRIX_COLS -# define MATRIX_COLS %s +# define MATRIX_COLS %s #endif // MATRIX_COLS #ifndef MATRIX_ROWS -# define MATRIX_ROWS %s +# define MATRIX_ROWS %s #endif // MATRIX_ROWS #ifndef DIRECT_PINS -# define DIRECT_PINS {%s} +# define DIRECT_PINS {%s} #endif // DIRECT_PINS """ % (col_count, row_count, ','.join(rows)) -def col_pins(col_pins): - """Return the config.h lines that set the column pins. - """ - cols = ','.join(map(str, [pin or 'NO_PIN' for pin in col_pins])) - col_num = len(col_pins) - - return """ -#ifndef MATRIX_COLS -# define MATRIX_COLS %s -#endif // MATRIX_COLS - -#ifndef MATRIX_COL_PINS -# define MATRIX_COL_PINS {%s} -#endif // MATRIX_COL_PINS -""" % (col_num, cols) - - -def row_pins(row_pins): - """Return the config.h lines that set the row pins. - """ - rows = ','.join(map(str, [pin or 'NO_PIN' for pin in row_pins])) - row_num = len(row_pins) - - return """ -#ifndef MATRIX_ROWS -# define MATRIX_ROWS %s -#endif // MATRIX_ROWS - -#ifndef MATRIX_ROW_PINS -# define MATRIX_ROW_PINS {%s} -#endif // MATRIX_ROW_PINS -""" % (row_num, rows) - - -def indicators(config): - """Return the config.h lines that setup LED indicators. +def pin_array(define, pins): + """Return the config.h lines that set a pin array. """ - defines = [] + pin_num = len(pins) + pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins])) - for led, define in LED_INDICATORS.items(): - if led in config: - defines.append('') - defines.append('#ifndef %s' % (define,)) - defines.append('# define %s %s' % (define, config[led])) - defines.append('#endif // %s' % (define,)) + return f""" +#ifndef {define}S +# define {define}S {pin_num} +#endif // {define}S - return '\n'.join(defines) - - -def layout_aliases(layout_aliases): - """Return the config.h lines that setup layout aliases. - """ - defines = [] - - for alias, layout in layout_aliases.items(): - defines.append('') - defines.append('#ifndef %s' % (alias,)) - defines.append('# define %s %s' % (alias, layout)) - defines.append('#endif // %s' % (alias,)) - - return '\n'.join(defines) +#ifndef {define}_PINS +# define {define}_PINS {{ {pin_array} }} +#endif // {define}_PINS +""" def matrix_pins(matrix_pins): @@ -157,58 +63,14 @@ def matrix_pins(matrix_pins): pins.append(direct_pins(matrix_pins['direct'])) if 'cols' in matrix_pins: - pins.append(col_pins(matrix_pins['cols'])) + pins.append(pin_array('MATRIX_COL', matrix_pins['cols'])) if 'rows' in matrix_pins: - pins.append(row_pins(matrix_pins['rows'])) + pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'])) return '\n'.join(pins) -def rgblight(config): - """Return the config.h lines that setup rgblight. - """ - rgblight_config = [] - - for json_key, config_key in rgblight_properties.items(): - if json_key in config: - rgblight_config.append('') - rgblight_config.append('#ifndef %s' % (config_key[0],)) - rgblight_config.append('# define %s %s' % (config_key[0], config[json_key])) - rgblight_config.append('#endif // %s' % (config_key[0],)) - - for json_key, config_key in rgblight_toggles.items(): - if config.get(json_key): - rgblight_config.append('') - rgblight_config.append('#ifndef %s' % (config_key,)) - rgblight_config.append('# define %s' % (config_key,)) - rgblight_config.append('#endif // %s' % (config_key,)) - - for json_key, config_key in rgblight_animations.items(): - if 'animations' in config and config['animations'].get(json_key): - rgblight_config.append('') - rgblight_config.append('#ifndef %s' % (config_key,)) - rgblight_config.append('# define %s' % (config_key,)) - rgblight_config.append('#endif // %s' % (config_key,)) - - return '\n'.join(rgblight_config) - - -def usb_properties(usb_props): - """Return the config.h lines that setup USB params. - """ - usb_lines = [] - - for info_name, config_name in usb_prop_map.items(): - if info_name in usb_props: - usb_lines.append('') - usb_lines.append('#ifndef ' + config_name) - usb_lines.append('# define %s %s' % (config_name, usb_props[info_name])) - usb_lines.append('#endif // ' + config_name) - - return '\n'.join(usb_lines) - - @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') @@ -228,39 +90,52 @@ def generate_config_h(cli): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) return False - # Build the info.json file - kb_info_json = info_json(cli.config.generate_config_h.keyboard) - # Build the info_config.h file. - config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] + kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) + info_config_map = _json_load(Path('data/mappings/info_config.json')) - if 'debounce' in kb_info_json: - config_h_lines.append(debounce(kb_info_json['debounce'])) - - if 'diode_direction' in kb_info_json: - config_h_lines.append(diode_direction(kb_info_json['diode_direction'])) - - if 'indicators' in kb_info_json: - config_h_lines.append(indicators(kb_info_json['indicators'])) - - if 'keyboard_name' in kb_info_json: - config_h_lines.append(keyboard_name(kb_info_json['keyboard_name'])) - - if 'layout_aliases' in kb_info_json: - config_h_lines.append(layout_aliases(kb_info_json['layout_aliases'])) - - if 'manufacturer' in kb_info_json: - config_h_lines.append(manufacturer(kb_info_json['manufacturer'])) + config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] - if 'rgblight' in kb_info_json: - config_h_lines.append(rgblight(kb_info_json['rgblight'])) + # Iterate through the info_config map to generate basic things + for config_key, info_dict in info_config_map.items(): + info_key = info_dict['info_key'] + key_type = info_dict.get('value_type', 'str') + to_config = info_dict.get('to_config', True) + + if not to_config: + continue + + try: + config_value = kb_info_json[info_key] + except KeyError: + continue + + if key_type.startswith('array'): + config_h_lines.append('') + config_h_lines.append(f'#ifndef {config_key}') + config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}') + config_h_lines.append(f'#endif // {config_key}') + elif key_type == 'bool': + if config_value: + config_h_lines.append('') + config_h_lines.append(f'#ifndef {config_key}') + config_h_lines.append(f'# define {config_key}') + config_h_lines.append(f'#endif // {config_key}') + elif key_type == 'mapping': + for key, value in config_value.items(): + config_h_lines.append('') + config_h_lines.append(f'#ifndef {key}') + config_h_lines.append(f'# define {key} {value}') + config_h_lines.append(f'#endif // {key}') + else: + config_h_lines.append('') + config_h_lines.append(f'#ifndef {config_key}') + config_h_lines.append(f'# define {config_key} {config_value}') + config_h_lines.append(f'#endif // {config_key}') if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) - if 'usb' in kb_info_json: - config_h_lines.append(usb_properties(kb_info_json['usb'])) - # Show the results config_h = '\n'.join(config_h_lines) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index b262e3c666..af740f341d 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -1,16 +1,37 @@ """Used by the make system to generate a rules.mk """ +from pathlib import Path + +from dotty_dict import dotty from milc import cli from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.info import info_json +from qmk.info import _json_load, info_json from qmk.path import is_keyboard, normpath -info_to_rules = { - 'board': 'BOARD', - 'bootloader': 'BOOTLOADER', - 'processor': 'MCU', -} + +def process_mapping_rule(kb_info_json, rules_key, info_dict): + """Return the rules.mk line(s) for a mapping rule. + """ + if not info_dict.get('to_c', True): + return None + + info_key = info_dict['info_key'] + key_type = info_dict.get('value_type', 'str') + + try: + rules_value = kb_info_json[info_key] + except KeyError: + return None + + if key_type == 'array': + return f'{rules_key} ?= {" ".join(rules_value)}' + elif key_type == 'bool': + return f'{rules_key} ?= {"on" if rules_value else "off"}' + elif key_type == 'mapping': + return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()]) + + return f'{rules_key} ?= {rules_value}' @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @@ -22,7 +43,6 @@ info_to_rules = { def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ - # Determine our keyboard(s) if not cli.config.generate_rules_mk.keyboard: cli.log.error('Missing paramater: --keyboard') cli.subcommands['info'].print_help() @@ -32,16 +52,18 @@ def generate_rules_mk(cli): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard) return False - # Build the info.json file - kb_info_json = info_json(cli.config.generate_rules_mk.keyboard) + kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard)) + info_rules_map = _json_load(Path('data/mappings/info_rules.json')) rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] - # Bring in settings - for info_key, rule_key in info_to_rules.items(): - if info_key in kb_info_json: - rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}') + # Iterate through the info_rules map to generate basic rules + for rules_key, info_dict in info_rules_map.items(): + new_entry = process_mapping_rule(kb_info_json, rules_key, info_dict) + + if new_entry: + rules_mk_lines.append(new_entry) - # Find features that should be enabled + # Iterate through features to enable/disable them if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): if feature == 'bootmagic_lite' and enabled: @@ -51,15 +73,6 @@ def generate_rules_mk(cli): enabled = 'yes' if enabled else 'no' rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') - # Set the LED driver - if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: - driver = kb_info_json['led_matrix']['driver'] - rules_mk_lines.append(f'LED_MATRIX_DRIVER ?= {driver}') - - # Add community layouts - if 'community_layouts' in kb_info_json: - rules_mk_lines.append(f'LAYOUTS ?= {" ".join(kb_info_json["community_layouts"])}') - # Show the results rules_mk = '\n'.join(rules_mk_lines) + '\n' @@ -72,7 +85,7 @@ def generate_rules_mk(cli): if cli.args.quiet: print(cli.args.output) else: - cli.log.info('Wrote info_config.h to %s.', cli.args.output) + cli.log.info('Wrote rules.mk to %s.', cli.args.output) else: print(rules_mk) diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index cb94613562..8a177eb6b5 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -27,7 +27,7 @@ ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop' LED_INDICATORS = { 'caps_lock': 'LED_CAPS_LOCK_PIN', 'num_lock': 'LED_NUM_LOCK_PIN', - 'scrol_lock': 'LED_SCROLL_LOCK_PIN', + 'scroll_lock': 'LED_SCROLL_LOCK_PIN', } # Constants that should match their counterparts in make diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index cc81f7a086..2accaba9e4 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -5,57 +5,18 @@ from collections.abc import Mapping from glob import glob from pathlib import Path +import hjson import jsonschema +from dotty_dict import dotty from milc import cli -from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS +from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS from qmk.c_parse import find_layouts from qmk.keyboard import config_h, rules_mk from qmk.keymap import list_keymaps from qmk.makefile import parse_rules_mk_file from qmk.math import compute -led_matrix_properties = { - 'driver_count': 'LED_DRIVER_COUNT', - 'driver_addr1': 'LED_DRIVER_ADDR_1', - 'driver_addr2': 'LED_DRIVER_ADDR_2', - 'driver_addr3': 'LED_DRIVER_ADDR_3', - 'driver_addr4': 'LED_DRIVER_ADDR_4', - 'led_count': 'LED_DRIVER_LED_COUNT', - 'timeout': 'ISSI_TIMEOUT', - 'persistence': 'ISSI_PERSISTENCE' -} - -rgblight_properties = { - 'led_count': ('RGBLED_NUM', int), - 'pin': ('RGB_DI_PIN', str), - 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int), - 'hue_steps': ('RGBLIGHT_HUE_STEP', int), - 'saturation_steps': ('RGBLIGHT_SAT_STEP', int), - 'brightness_steps': ('RGBLIGHT_VAL_STEP', int) -} - -rgblight_toggles = { - 'sleep': 'RGBLIGHT_SLEEP', - 'split': 'RGBLIGHT_SPLIT', -} - -rgblight_animations = { - 'all': 'RGBLIGHT_ANIMATIONS', - 'alternating': 'RGBLIGHT_EFFECT_ALTERNATING', - 'breathing': 'RGBLIGHT_EFFECT_BREATHING', - 'christmas': 'RGBLIGHT_EFFECT_CHRISTMAS', - 'knight': 'RGBLIGHT_EFFECT_KNIGHT', - 'rainbow_mood': 'RGBLIGHT_EFFECT_RAINBOW_MOOD', - 'rainbow_swirl': 'RGBLIGHT_EFFECT_RAINBOW_SWIRL', - 'rgb_test': 'RGBLIGHT_EFFECT_RGB_TEST', - 'snake': 'RGBLIGHT_EFFECT_SNAKE', - 'static_gradient': 'RGBLIGHT_EFFECT_STATIC_GRADIENT', - 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' -} - -usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} - true_values = ['1', 'on', 'yes'] false_values = ['0', 'off', 'no'] @@ -101,7 +62,6 @@ def info_json(keyboard): 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) - print(dir(e)) exit() # Make sure we have at least one layout @@ -132,7 +92,7 @@ def _json_load(json_file): Note: file must be a Path object. """ try: - return json.load(json_file.open()) + return hjson.load(json_file.open()) except json.decoder.JSONDecodeError as e: cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) @@ -172,65 +132,6 @@ def keyboard_api_validate(data): return validator(data) -def _extract_debounce(info_data, config_c): - """Handle debounce. - """ - if 'debounce' in info_data and 'DEBOUNCE' in config_c: - _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.') - - if 'DEBOUNCE' in config_c: - info_data['debounce'] = int(config_c['DEBOUNCE']) - - return info_data - - -def _extract_diode_direction(info_data, config_c): - """Handle the diode direction. - """ - if 'diode_direction' in info_data and 'DIODE_DIRECTION' in config_c: - _log_warning(info_data, 'Diode direction is specified in both info.json and config.h, the config.h value wins.') - - if 'DIODE_DIRECTION' in config_c: - info_data['diode_direction'] = config_c.get('DIODE_DIRECTION') - - return info_data - - -def _extract_indicators(info_data, config_c): - """Find the LED indicator information. - """ - for json_key, config_key in LED_INDICATORS.items(): - if json_key in info_data.get('indicators', []) and config_key in config_c: - _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') - - if 'indicators' not in info_data: - info_data['indicators'] = {} - - if config_key in config_c: - if 'indicators' not in info_data: - info_data['indicators'] = {} - - info_data['indicators'][json_key] = config_c.get(config_key) - - return info_data - - -def _extract_community_layouts(info_data, rules): - """Find the community layouts in rules.mk. - """ - community_layouts = rules['LAYOUTS'].split() if 'LAYOUTS' in rules else [] - - if 'community_layouts' in info_data: - for layout in community_layouts: - if layout not in info_data['community_layouts']: - community_layouts.append(layout) - - else: - info_data['community_layouts'] = community_layouts - - return info_data - - def _extract_features(info_data, rules): """Find all the features enabled in rules.mk. """ @@ -267,78 +168,6 @@ def _extract_features(info_data, rules): return info_data -def _extract_led_drivers(info_data, rules): - """Find all the LED drivers set in rules.mk. - """ - if 'LED_MATRIX_DRIVER' in rules: - if 'led_matrix' not in info_data: - info_data['led_matrix'] = {} - - if info_data['led_matrix'].get('driver'): - _log_warning(info_data, 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['led_matrix']['driver'] = rules['LED_MATRIX_DRIVER'] - - return info_data - - -def _extract_led_matrix(info_data, config_c): - """Handle the led_matrix configuration. - """ - led_matrix = info_data.get('led_matrix', {}) - - for json_key, config_key in led_matrix_properties.items(): - if config_key in config_c: - if json_key in led_matrix: - _log_warning(info_data, 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) - - led_matrix[json_key] = config_c[config_key] - - -def _extract_rgblight(info_data, config_c): - """Handle the rgblight configuration. - """ - rgblight = info_data.get('rgblight', {}) - animations = rgblight.get('animations', {}) - - if 'RGBLED_SPLIT' in config_c: - raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip() - rgblight['split_count'] = [int(i) for i in raw_split.split(',')] - - for json_key, config_key_type in rgblight_properties.items(): - config_key, config_type = config_key_type - - if config_key in config_c: - if json_key in rgblight: - _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) - - try: - rgblight[json_key] = config_type(config_c[config_key]) - except ValueError as e: - 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) - - for json_key, config_key in rgblight_toggles.items(): - if config_key in config_c and json_key in rgblight: - _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key) - - rgblight[json_key] = config_key in config_c - - for json_key, config_key in rgblight_animations.items(): - if config_key in config_c: - if json_key in animations: - _log_warning(info_data, 'RGB Light: animations: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) - - animations[json_key] = config_c[config_key] - - if animations: - rgblight['animations'] = animations - - if rgblight: - info_data['rgblight'] = rgblight - - return info_data - - def _pin_name(pin): """Returns the proper representation for a pin. """ @@ -426,34 +255,59 @@ def _extract_matrix_info(info_data, config_c): return info_data -def _extract_usb_info(info_data, config_c): - """Populate the USB information. +def _extract_config_h(info_data): + """Pull some keyboard information from existing config.h files """ - if 'usb' not in info_data: - info_data['usb'] = {} + config_c = config_h(info_data['keyboard_folder']) - for info_name, config_name in usb_properties.items(): - if config_name in config_c: - if info_name in info_data['usb']: - _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) + # Pull in data from the json map + dotty_info = dotty(info_data) + info_config_map = _json_load(Path('data/mappings/info_config.json')) - info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper() + for config_key, info_dict in info_config_map.items(): + info_key = info_dict['info_key'] + key_type = info_dict.get('value_type', 'str') - return info_data + try: + if config_key in config_c and info_dict.get('to_json', True): + if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): + _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key)) + if key_type.startswith('array'): + if '.' in key_type: + key_type, array_type = key_type.split('.', 1) + else: + array_type = None -def _extract_config_h(info_data): - """Pull some keyboard information from existing config.h files - """ - config_c = config_h(info_data['keyboard_folder']) + config_value = config_c[config_key].replace('{', '').replace('}', '').strip() + + if array_type == 'int': + dotty_info[info_key] = list(map(int, config_value.split(','))) + else: + dotty_info[info_key] = config_value.split(',') + + elif key_type == 'bool': + dotty_info[info_key] = config_c[config_key] in true_values + + elif key_type == 'hex': + dotty_info[info_key] = '0x' + config_c[config_key][2:].upper() - _extract_debounce(info_data, config_c) - _extract_diode_direction(info_data, config_c) - _extract_indicators(info_data, config_c) + elif key_type == 'list': + dotty_info[info_key] = config_c[config_key].split() + + elif key_type == 'int': + dotty_info[info_key] = int(config_c[config_key]) + + else: + dotty_info[info_key] = config_c[config_key] + + except Exception as e: + _log_warning(info_data, f'{config_key}->{info_key}: {e}') + + info_data.update(dotty_info) + + # Pull data that easily can't be mapped in json _extract_matrix_info(info_data, config_c) - _extract_usb_info(info_data, config_c) - _extract_led_matrix(info_data, config_c) - _extract_rgblight(info_data, config_c) return info_data @@ -462,21 +316,66 @@ def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) - mcu = rules.get('MCU', info_data.get('processor')) + info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) - if mcu in CHIBIOS_PROCESSORS: + if info_data['processor'] in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) - elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: + elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS: avr_processor_rules(info_data, rules) else: - cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) + cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor'])) unknown_processor_rules(info_data, rules) - _extract_community_layouts(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')) + + for rules_key, info_dict in info_rules_map.items(): + info_key = info_dict['info_key'] + key_type = info_dict.get('value_type', 'str') + + try: + if rules_key in rules and info_dict.get('to_json', True): + if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): + _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) + + if key_type.startswith('array'): + if '.' in key_type: + key_type, array_type = key_type.split('.', 1) + else: + array_type = None + + rules_value = rules[rules_key].replace('{', '').replace('}', '').strip() + + if array_type == 'int': + dotty_info[info_key] = list(map(int, rules_value.split(','))) + else: + dotty_info[info_key] = rules_value.split(',') + + elif key_type == 'list': + dotty_info[info_key] = rules[rules_key].split() + + elif key_type == 'bool': + dotty_info[info_key] = rules[rules_key] in true_values + + elif key_type == 'hex': + dotty_info[info_key] = '0x' + rules[rules_key][2:].upper() + + elif key_type == 'int': + dotty_info[info_key] = int(rules[rules_key]) + + else: + dotty_info[info_key] = rules[rules_key] + + except Exception as e: + _log_warning(info_data, f'{rules_key}->{info_key}: {e}') + + info_data.update(dotty_info) + + # Merge in config values that can't be easily mapped _extract_features(info_data, rules) - _extract_led_drivers(info_data, rules) return info_data @@ -565,23 +464,7 @@ def arm_processor_rules(info_data, rules): info_data['processor_type'] = 'arm' info_data['protocol'] = 'ChibiOS' - if 'MCU' in rules: - if 'processor' in info_data: - _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['processor'] = rules['MCU'] - - elif 'processor' not in info_data: - info_data['processor'] = 'unknown' - - if 'BOOTLOADER' in rules: - # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first - # if 'bootloader' in info_data: - # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['bootloader'] = rules['BOOTLOADER'] - - else: + if 'bootloader' not in info_data: if 'STM32' in info_data['processor']: info_data['bootloader'] = 'stm32-dfu' else: @@ -594,12 +477,6 @@ def arm_processor_rules(info_data, rules): elif 'ARM_ATSAM' in rules: info_data['platform'] = 'ARM_ATSAM' - if 'BOARD' in rules: - if 'board' in info_data: - _log_warning(info_data, 'Board is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['board'] = rules['BOARD'] - return info_data @@ -607,26 +484,10 @@ def avr_processor_rules(info_data, rules): """Setup the default info for an AVR board. """ info_data['processor_type'] = 'avr' - info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu' 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 'MCU' in rules: - if 'processor' in info_data: - _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['processor'] = rules['MCU'] - - elif 'processor' not in info_data: - info_data['processor'] = 'unknown' - - if 'BOOTLOADER' in rules: - # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first - # if 'bootloader' in info_data: - # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') - - info_data['bootloader'] = rules['BOOTLOADER'] - else: + if 'bootloader' not in info_data: info_data['bootloader'] = 'atmel-dfu' # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index f889833d0b..3efeddb85e 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -230,3 +230,32 @@ def test_generate_rgb_breathe_table(): check_returncode(result) assert 'Breathing center: 1.2' in result.stdout assert 'Breathing max: 127' in result.stdout + + +def test_generate_config_h(): + result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic') + check_returncode(result) + assert '# define DEVICE_VER 0x0001' in result.stdout + assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout + assert '# define DIODE_DIRECTION COL2ROW' in result.stdout + assert '# define MANUFACTURER none' in result.stdout + assert '# define PRODUCT handwired/pytest/basic' in result.stdout + assert '# define PRODUCT_ID 0x6465' in result.stdout + assert '# define VENDOR_ID 0xFEED' in result.stdout + assert '# define MATRIX_COLS 1' in result.stdout + assert '# define MATRIX_COL_PINS { F4 }' in result.stdout + assert '# define MATRIX_ROWS 1' in result.stdout + assert '# define MATRIX_ROW_PINS { F5 }' in result.stdout + + +def test_generate_rules_mk(): + result = check_subcommand('generate-rules-mk', '-kb', 'handwired/pytest/basic') + check_returncode(result) + assert 'BOOTLOADER ?= atmel-dfu' in result.stdout + assert 'MCU ?= atmega32u4' in result.stdout + + +def test_generate_layouts(): + result = check_subcommand('generate-layouts', '-kb', 'handwired/pytest/basic') + check_returncode(result) + assert '#define LAYOUT_custom(k0A) {' in result.stdout -- cgit v1.2.3 From 620a946d01477b64ee2f719141aa35400c0188c6 Mon Sep 17 00:00:00 2001 From: Nick Brassel <nick@tzarc.org> Date: Sat, 6 Feb 2021 11:27:46 +1100 Subject: Add STM32G431 and STM32G474 board definitions. (#11793) * Add STM32G431 and STM32G474 board definitions. * Add docs. --- lib/python/qmk/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 8a177eb6b5..3ed69f3bf9 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -10,7 +10,7 @@ QMK_FIRMWARE = Path.cwd() MAX_KEYBOARD_SUBFOLDERS = 5 # Supported processor types -CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F411' +CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F411', 'STM32G431', 'STM32G474' LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' -- cgit v1.2.3 From 448cc1acd5f107f9679a48c605861654523432e7 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Mon, 8 Feb 2021 03:03:08 -0800 Subject: remove deprecated qmk json-keymap (#11823) --- lib/python/qmk/cli/__init__.py | 1 - lib/python/qmk/cli/json/__init__.py | 5 ----- lib/python/qmk/cli/json/keymap.py | 16 ---------------- 3 files changed, 22 deletions(-) delete mode 100644 lib/python/qmk/cli/json/__init__.py delete mode 100755 lib/python/qmk/cli/json/keymap.py (limited to 'lib') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 372c40921a..1732287da0 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -19,7 +19,6 @@ from . import flash from . import generate from . import hello from . import info -from . import json from . import json2c from . import lint from . import list diff --git a/lib/python/qmk/cli/json/__init__.py b/lib/python/qmk/cli/json/__init__.py deleted file mode 100644 index f4ebfc45b4..0000000000 --- a/lib/python/qmk/cli/json/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""QMK CLI JSON Subcommands - -We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. -""" -from . import keymap diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py deleted file mode 100755 index 2af9faaa72..0000000000 --- a/lib/python/qmk/cli/json/keymap.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Generate a keymap.c from a configurator export. -""" -from pathlib import Path - -from milc import cli - - -@cli.argument('-o', '--output', arg_only=True, type=Path, help='File to write to') -@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") -@cli.argument('filename', arg_only=True, help='Configurator JSON file') -@cli.subcommand('Creates a keymap.c from a QMK Configurator export.', hidden=True) -def json_keymap(cli): - """Renamed to `qmk json2c`. - """ - cli.log.error('This command has been renamed to `qmk json2c`.') - return False -- cgit v1.2.3 From cd336b2b545798269405e8ffef0fe8958d4d27d4 Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 13 Feb 2021 10:26:45 -0800 Subject: bump to python 3.7 (#11408) --- lib/python/qmk/cli/__init__.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 1732287da0..a5f1f47679 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -27,6 +27,24 @@ from . import new from . import pyformat from . import pytest -if sys.version_info[0] != 3 or sys.version_info[1] < 6: - cli.log.error('Your Python is too old! Please upgrade to Python 3.6 or later.') +# Supported version information +# +# Based on the OSes we support these are the minimum python version available by default. +# Last update: 2021 Jan 02 +# +# Arch: 3.9 +# Debian: 3.7 +# Fedora 31: 3.7 +# Fedora 32: 3.8 +# Fedora 33: 3.9 +# FreeBSD: 3.7 +# Gentoo: 3.7 +# macOS: 3.9 (from homebrew) +# msys2: 3.8 +# Slackware: 3.7 +# solus: 3.7 +# void: 3.9 + +if sys.version_info[0] != 3 or sys.version_info[1] < 7: + cli.log.error('Your Python is too old! Please upgrade to Python 3.7 or later.') exit(127) -- cgit v1.2.3 From 23ef327e118307d276677d30e3fda064ace6713b Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Wed, 24 Feb 2021 10:35:08 -0800 Subject: make LAYOUT parsing more robust --- lib/python/qmk/c_parse.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index ade3e38059..89dd278b7e 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -67,8 +67,10 @@ def find_layouts(file): layout = layout.strip() parsed_layout = [_default_key(key) for key in layout.split(',')] - for key in parsed_layout: - if key['label'] in matrix_locations: + for i, key in enumerate(parsed_layout): + if 'label' not in key: + cli.log.error('Invalid LAYOUT macro in %s: Empty parameter name in macro %s at pos %s.', file, macro_name, i) + elif key['label'] in matrix_locations: key['matrix'] = matrix_locations[key['label']] parsed_layouts[macro_name] = { -- cgit v1.2.3 From 1581ea48dcd48d0d3f42cc09b388c468aedec45d Mon Sep 17 00:00:00 2001 From: Zach White <skullydazed@gmail.com> Date: Sat, 27 Feb 2021 12:00:50 -0800 Subject: Fix develop (#12039) Fixes file encoding errors on Windows, and layouts not correctly merging into info.json. * force utf8 encoding * correctly merge layouts and layout aliases * show what aliases point to --- lib/python/qmk/c_parse.py | 11 ++------ lib/python/qmk/cli/chibios/confmigrate.py | 8 +++--- lib/python/qmk/cli/generate/layouts.py | 4 +++ lib/python/qmk/cli/info.py | 7 +++-- lib/python/qmk/cli/kle2json.py | 2 +- lib/python/qmk/info.py | 44 ++++++++++++++++++++++------- lib/python/qmk/keymap.py | 6 ++-- lib/python/qmk/os_helpers/linux/__init__.py | 2 +- lib/python/qmk/tests/test_cli_commands.py | 2 +- 9 files changed, 56 insertions(+), 30 deletions(-) (limited to 'lib') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 89dd278b7e..d4f39c8839 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -46,7 +46,7 @@ def find_layouts(file): parsed_layouts = {} # Search the file for LAYOUT macros and aliases - file_contents = file.read_text() + file_contents = file.read_text(encoding='utf-8') file_contents = comment_remover(file_contents) file_contents = file_contents.replace('\\\n', '') @@ -87,12 +87,7 @@ def find_layouts(file): except ValueError: continue - # Populate our aliases - for alias, text in aliases.items(): - if text in parsed_layouts and 'KEYMAP' not in alias: - parsed_layouts[alias] = parsed_layouts[text] - - return parsed_layouts + return parsed_layouts, aliases def parse_config_h_file(config_h_file, config_h=None): @@ -104,7 +99,7 @@ def parse_config_h_file(config_h_file, config_h=None): config_h_file = Path(config_h_file) if config_h_file.exists(): - config_h_text = config_h_file.read_text() + config_h_text = config_h_file.read_text(encoding='utf-8') config_h_text = config_h_text.replace('\\\n', '') config_h_text = strip_multiline_comment(config_h_text) diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index 3e348b2b07..89995931a4 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -40,7 +40,7 @@ file_header = """\ def collect_defines(filepath): - with open(filepath, 'r') as f: + with open(filepath, 'r', encoding='utf-8') as f: content = f.read() define_search = re.compile(r'(?m)^#\s*define\s+(?:.*\\\r?\n)*.*$', re.MULTILINE) value_search = re.compile(r'^#\s*define\s+(?P<name>[a-zA-Z0-9_]+(\([^\)]*\))?)\s*(?P<value>.*)', re.DOTALL) @@ -146,17 +146,17 @@ def chibios_confmigrate(cli): if cli.args.input.name == "chconf.h" and ("CHCONF_H" in input_defs["dict"] or "_CHCONF_H_" in input_defs["dict"] or cli.args.force): migrate_chconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", encoding='utf-8') as out_file: migrate_chconf_h(to_override, outfile=out_file) elif cli.args.input.name == "halconf.h" and ("HALCONF_H" in input_defs["dict"] or "_HALCONF_H_" in input_defs["dict"] or cli.args.force): migrate_halconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", encoding='utf-8') as out_file: migrate_halconf_h(to_override, outfile=out_file) elif cli.args.input.name == "mcuconf.h" and ("MCUCONF_H" in input_defs["dict"] or "_MCUCONF_H_" in input_defs["dict"] or cli.args.force): migrate_mcuconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", encoding='utf-8') as out_file: migrate_mcuconf_h(to_override, outfile=out_file) diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index b7baae0651..15b289522e 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -82,6 +82,10 @@ def generate_layouts(cli): layouts_h_lines.append(rows) layouts_h_lines.append('}') + for alias, target in kb_info_json.get('layout_aliases', {}).items(): + layouts_h_lines.append('') + layouts_h_lines.append('#define %s %s' % (alias, target)) + # Show the results layouts_h = '\n'.join(layouts_h_lines) + '\n' diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index 87d7253d4b..a7ce8abf03 100755 --- a/lib/python/qmk/cli/info.py +++ b/lib/python/qmk/cli/info.py @@ -29,7 +29,7 @@ def show_keymap(kb_info_json, title_caps=True): else: cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap) - keymap_data = json.load(keymap_path.open()) + keymap_data = json.load(keymap_path.open(encoding='utf-8')) layout_name = keymap_data['layout'] for layer_num, layer in enumerate(keymap_data['layers']): @@ -57,7 +57,7 @@ def show_matrix(kb_info_json, title_caps=True): # Build our label list labels = [] for key in layout['layout']: - if key['matrix']: + if 'matrix' in key: row = ROW_LETTERS[key['matrix'][0]] col = COL_LETTERS[key['matrix'][1]] @@ -91,6 +91,9 @@ def print_friendly_output(kb_info_json): cli.echo('{fg_blue}Size{fg_reset}: %s x %s' % (kb_info_json['width'], kb_info_json['height'])) cli.echo('{fg_blue}Processor{fg_reset}: %s', kb_info_json.get('processor', 'Unknown')) cli.echo('{fg_blue}Bootloader{fg_reset}: %s', kb_info_json.get('bootloader', 'Unknown')) + if 'layout_aliases' in kb_info_json: + aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()] + cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),)) if cli.config.info.layouts: show_layouts(kb_info_json, True) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 66d504bfc2..3bb7443582 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -27,7 +27,7 @@ def kle2json(cli): cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) return False out_path = file_path.parent - raw_code = file_path.open().read() + raw_code = file_path.read_text(encoding='utf-8') # Check if info.json exists, allow overwrite with force if Path(out_path, "info.json").exists() and not cli.args.force: cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 2accaba9e4..cf5dc6640b 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -45,7 +45,12 @@ def info_json(keyboard): info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} # Populate layout data - for layout_name, layout_json in _find_all_layouts(info_data, keyboard).items(): + layouts, aliases = _find_all_layouts(info_data, keyboard) + + if aliases: + info_data['layout_aliases'] = aliases + + for layout_name, layout_json in layouts.items(): if not layout_name.startswith('LAYOUT_kc'): layout_json['c_macro'] = True info_data['layouts'][layout_name] = layout_json @@ -92,7 +97,7 @@ def _json_load(json_file): Note: file must be a Path object. """ try: - return hjson.load(json_file.open()) + return hjson.load(json_file.open(encoding='utf-8')) except json.decoder.JSONDecodeError as e: cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) @@ -415,21 +420,28 @@ def _merge_layouts(info_data, new_info_data): def _search_keyboard_h(path): current_path = Path('keyboards/') + aliases = {} layouts = {} + for directory in path.parts: current_path = current_path / directory keyboard_h = '%s.h' % (directory,) keyboard_h_path = current_path / keyboard_h if keyboard_h_path.exists(): - layouts.update(find_layouts(keyboard_h_path)) + new_layouts, new_aliases = find_layouts(keyboard_h_path) + layouts.update(new_layouts) + + for alias, alias_text in new_aliases.items(): + if alias_text in layouts: + aliases[alias] = alias_text - return layouts + return layouts, aliases def _find_all_layouts(info_data, keyboard): """Looks for layout macros associated with this keyboard. """ - layouts = _search_keyboard_h(Path(keyboard)) + layouts, aliases = _search_keyboard_h(Path(keyboard)) if not layouts: # 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. @@ -437,11 +449,15 @@ def _find_all_layouts(info_data, keyboard): for file in glob('keyboards/%s/*.h' % keyboard): if file.endswith('.h'): - these_layouts = find_layouts(file) + these_layouts, these_aliases = find_layouts(file) + if these_layouts: layouts.update(these_layouts) - return layouts + if these_aliases: + aliases.update(these_aliases) + + return layouts, aliases def _log_error(info_data, message): @@ -540,11 +556,19 @@ def merge_info_jsons(keyboard, info_data): cli.log.error('\t%s: %s', json_path, e.message) continue - # Mark the layouts as coming from json - for layout in new_info_data.get('layouts', {}).values(): - layout['c_macro'] = False + # Merge layout data in + for layout_name, layout in new_info_data.get('layouts', {}).items(): + if layout_name in info_data['layouts']: + for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']): + existing_key.update(new_key) + else: + layout['c_macro'] = False + info_data['layouts'][layout_name] = layout # Update info_data with the new data + if 'layouts' in new_info_data: + del (new_info_data['layouts']) + deep_update(info_data, new_info_data) return info_data diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 266532f503..d8495c38bc 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -42,7 +42,7 @@ def template_json(keyboard): template_file = Path('keyboards/%s/templates/keymap.json' % keyboard) template = {'keyboard': keyboard} if template_file.exists(): - template.update(json.loads(template_file.read_text())) + template.update(json.load(template_file.open(encoding='utf-8'))) return template @@ -58,7 +58,7 @@ def template_c(keyboard): """ template_file = Path('keyboards/%s/templates/keymap.c' % keyboard) if template_file.exists(): - template = template_file.read_text() + template = template_file.read_text(encoding='utf-8') else: template = DEFAULT_KEYMAP_C @@ -469,7 +469,7 @@ def parse_keymap_c(keymap_file, use_cpp=True): if use_cpp: keymap_file = _c_preprocess(keymap_file) else: - keymap_file = keymap_file.read_text() + keymap_file = keymap_file.read_text(encoding='utf-8') keymap = dict() keymap['layers'] = _get_layers(keymap_file) diff --git a/lib/python/qmk/os_helpers/linux/__init__.py b/lib/python/qmk/os_helpers/linux/__init__.py index a04ac4f8a9..9e73964e47 100644 --- a/lib/python/qmk/os_helpers/linux/__init__.py +++ b/lib/python/qmk/os_helpers/linux/__init__.py @@ -95,7 +95,7 @@ def check_udev_rules(): # Collect all rules from the config files for rule_file in udev_rules: - for line in rule_file.read_text().split('\n'): + for line in rule_file.read_text(encoding='utf-8').split('\n'): line = line.strip() if not line.startswith("#") and len(line): current_rules.add(line) diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 3efeddb85e..82c42a20e8 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -16,7 +16,7 @@ def check_subcommand(command, *args): def check_subcommand_stdin(file_to_read, command, *args): """Pipe content of a file to a command and return output. """ - with open(file_to_read) as my_file: + with open(file_to_read, encoding='utf-8') as my_file: cmd = ['bin/qmk', command, *args] result = run(cmd, stdin=my_file, stdout=PIPE, stderr=STDOUT, universal_newlines=True) return result -- cgit v1.2.3