def generate_dfu_header(cli): """Generates the Keyboard.h file. """ # Determine our keyboard(s) if not cli.config.generate_dfu_header.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.generate_dfu_header.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_dfu_header.keyboard) return False # Build the Keyboard.h file. kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard)) keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}') keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader') # Optional if 'qmk_lufa_bootloader.esc_output' in kb_info_json: keyboard_h_lines.append(f'#define QMK_ESC_OUTPUT {kb_info_json["qmk_lufa_bootloader.esc_output"]}') if 'qmk_lufa_bootloader.esc_input' in kb_info_json: keyboard_h_lines.append(f'#define QMK_ESC_INPUT {kb_info_json["qmk_lufa_bootloader.esc_input"]}') if 'qmk_lufa_bootloader.led' in kb_info_json: keyboard_h_lines.append(f'#define QMK_LED {kb_info_json["qmk_lufa_bootloader.led"]}') if 'qmk_lufa_bootloader.speaker' in kb_info_json: keyboard_h_lines.append(f'#define QMK_SPEAKER {kb_info_json["qmk_lufa_bootloader.speaker"]}') # Show the results dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
def keyboard_check(kb): """Perform the keyboard level checks. """ ok = True kb_info = info_json(kb) if not _handle_json_errors(kb, kb_info): ok = False # Additional checks rules_mk_assignment_errors = _rules_mk_assignment_only(kb) if rules_mk_assignment_errors: ok = False cli.log.error( '%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb) for assignment_error in rules_mk_assignment_errors: cli.log.error(assignment_error) invalid_files = git_get_ignored_files(f'keyboards/{kb}/') for file in invalid_files: if 'keymap' in file: continue cli.log.error(f'{kb}: The file "{file}" should not exist!') ok = False return ok
def info(cli): """Compile an info.json for a particular keyboard and pretty-print it. """ # Determine our keyboard(s) if not cli.config.info.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.info.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.info.keyboard) return False # Build the info.json file kb_info_json = info_json(cli.config.info.keyboard) # Output in the requested format if cli.args.format == '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': print_friendly_output(kb_info_json) else: cli.log.error('Unknown format: %s', cli.args.format) return False
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)
def generate_config_h(cli): """Generates the info_config.h file. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) # Build the info_config.h file. config_h_lines = [ GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once' ] generate_config_items(kb_info_json, config_h_lines) generate_matrix_size(kb_info_json, config_h_lines) if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) if 'split' in kb_info_json: generate_split_config(kb_info_json, config_h_lines) # Show the results dump_lines(cli.args.output, config_h_lines, cli.args.quiet)
def generate_api(cli): """Generates the QMK API data. """ api_data_dir = Path('api_data') v1_dir = api_data_dir / 'v1' keyboard_list = v1_dir / 'keyboard_list.json' keyboard_all = v1_dir / 'keyboards.json' usb_file = v1_dir / 'usb.json' if not api_data_dir.exists(): api_data_dir.mkdir() kb_all = {'last_updated': current_datetime(), 'keyboards': {}} usb_list = {'last_updated': current_datetime(), 'devices': {}} # Generate and write keyboard specific JSON files for keyboard_name in list_keyboards(): kb_all['keyboards'][keyboard_name] = info_json(keyboard_name) keyboard_dir = v1_dir / 'keyboards' / keyboard_name keyboard_info = keyboard_dir / 'info.json' keyboard_readme = keyboard_dir / 'readme.md' keyboard_readme_src = Path('keyboards') / keyboard_name / 'readme.md' keyboard_dir.mkdir(parents=True, exist_ok=True) keyboard_info.write_text( json.dumps({ 'last_updated': current_datetime(), 'keyboards': { keyboard_name: kb_all['keyboards'][keyboard_name] } })) if keyboard_readme_src.exists(): copyfile(keyboard_readme_src, keyboard_readme) if 'usb' in kb_all['keyboards'][keyboard_name]: usb = kb_all['keyboards'][keyboard_name]['usb'] 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']]: usb_list['devices'][usb['vid']][usb['pid']] = {} 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']) }, cls=InfoJSONEncoder)) keyboard_all.write_text(json.dumps(kb_all, cls=InfoJSONEncoder)) usb_file.write_text(json.dumps(usb_list, cls=InfoJSONEncoder))
def lint(cli): """Check keyboard and keymap for common mistakes. """ if not cli.config.lint.keyboard: cli.log.error('Missing required argument: --keyboard') cli.print_help() return False if not is_keyboard(cli.config.lint.keyboard): cli.log.error('No such keyboard: %s', cli.config.lint.keyboard) return False # Gather data about the keyboard. ok = True keyboard_path = keyboard(cli.config.lint.keyboard) keyboard_info = info_json(cli.config.lint.keyboard) readme_path = find_readme(cli.config.lint.keyboard) missing_readme_path = keyboard_path / 'readme.md' # Check for errors in the info.json if keyboard_info['parse_errors']: ok = False cli.log.error('Errors found when generating info.json.') if cli.config.lint.strict and keyboard_info['parse_warnings']: ok = False cli.log.error( 'Warnings found when generating info.json (Strict mode enabled.)') # Check for a readme.md and warn if it doesn't exist if not readme_path: ok = False cli.log.error('Missing %s', missing_readme_path) # Keymap specific checks if cli.config.lint.keymap: keymap_path = locate_keymap(cli.config.lint.keyboard, cli.config.lint.keymap) if not keymap_path: ok = False cli.log.error("Can't find %s keymap for %s keyboard.", cli.config.lint.keymap, cli.config.lint.keyboard) else: keymap_readme = keymap_path.parent / 'readme.md' if not keymap_readme.exists(): cli.log.warning('Missing %s', keymap_readme) if cli.config.lint.strict: ok = False # Check and report the overall status if ok: cli.log.info('Lint check passed!') return True cli.log.error('Lint check failed!') return False
def generate_dfu_header(cli): """Generates the Keyboard.h file. """ # Determine our keyboard(s) if not cli.config.generate_dfu_header.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.generate_dfu_header.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_dfu_header.keyboard) return False # Build the Keyboard.h file. kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard)) keyboard_h_lines = [ '/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.', ' */', '', '#pragma once' ] keyboard_h_lines.append( f'#define MANUFACTURER {kb_info_json["manufacturer"]}') keyboard_h_lines.append( f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader') # Optional if 'qmk_lufa_bootloader.esc_output' in kb_info_json: keyboard_h_lines.append( f'#define QMK_ESC_OUTPUT {kb_info_json["qmk_lufa_bootloader.esc_output"]}' ) if 'qmk_lufa_bootloader.esc_input' in kb_info_json: keyboard_h_lines.append( f'#define QMK_ESC_INPUT {kb_info_json["qmk_lufa_bootloader.esc_input"]}' ) if 'qmk_lufa_bootloader.led' in kb_info_json: keyboard_h_lines.append( f'#define QMK_LED {kb_info_json["qmk_lufa_bootloader.led"]}') if 'qmk_lufa_bootloader.speaker' in kb_info_json: keyboard_h_lines.append( f'#define QMK_SPEAKER {kb_info_json["qmk_lufa_bootloader.speaker"]}' ) # Show the results keyboard_h = '\n'.join(keyboard_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.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(keyboard_h) if not cli.args.quiet: cli.log.info('Wrote Keyboard.h to %s.', cli.args.output) else: print(keyboard_h)
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ if not cli.config.generate_rules_mk.keyboard: cli.log.error('Missing parameter: --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 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.', '' ] # 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) # 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: 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}') # 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.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(rules_mk) if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output) else: print(rules_mk)
def list_layouts(cli): """List the layouts for a specific keyboard """ if not cli.config.list_layouts.keyboard: cli.log.error('Missing required arguments: --keyboard') cli.subcommands['list-layouts'].print_help() return False info_data = info_json(cli.config.list_layouts.keyboard) for name in sorted(info_data.get('community_layouts', [])): print(name)
def info(cli): """Compile an info.json for a particular keyboard and pretty-print it. """ # Determine our keyboard(s) if not cli.config.info.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.info.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.info.keyboard) return False if bool(cli.args.rules_mk): print_parsed_rules_mk(cli.config.info.keyboard) return False # default keymap stored in config file should be ignored if cli.config_source.info.keymap == 'config_file': cli.config_source.info.keymap = None # Build the info.json file if cli.config.info.keymap: kb_info_json = keymap_json(cli.config.info.keyboard, cli.config.info.keymap) else: kb_info_json = info_json(cli.config.info.keyboard) # Output in the requested format if cli.args.format == 'json': print(json.dumps(kb_info_json, cls=InfoJSONEncoder)) return True elif cli.args.format == 'text': print_dotted_output(kb_info_json) title_caps = False elif cli.args.format == 'friendly': print_friendly_output(kb_info_json) title_caps = True else: cli.log.error('Unknown format: %s', cli.args.format) return False # Output requested extras if cli.config.info.layouts: show_layouts(kb_info_json, title_caps) if cli.config.info.matrix: show_matrix(kb_info_json, title_caps) if cli.config.info.keymap: show_keymap(kb_info_json, title_caps)
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) info_rules_map = json_load(Path('data/mappings/info_rules.json')) rules_mk_lines = [GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE] # 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) # Iterate through features to enable/disable them 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}') # Set SPLIT_TRANSPORT, if needed if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') # Set CUSTOM_MATRIX, if needed if kb_info_json.get('matrix_pins', {}).get('custom'): if kb_info_json.get('matrix_pins', {}).get('custom_lite'): rules_mk_lines.append('CUSTOM_MATRIX ?= lite') else: rules_mk_lines.append('CUSTOM_MATRIX ?= yes') # Show the results dump_lines(cli.args.output, rules_mk_lines) if cli.args.output: if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output)
def format_json(cli): """Format a json file. """ json_file = json_load(cli.args.json_file) if cli.args.format == 'auto': try: validate(json_file, 'qmk.keyboard.v1') json_encoder = InfoJSONEncoder except ValidationError as e: cli.log.warning('File %s did not validate as a keyboard:\n\t%s', cli.args.json_file, e) cli.log.info('Treating %s as a keymap file.', cli.args.json_file) json_encoder = KeymapJSONEncoder elif cli.args.format == 'keyboard': json_encoder = InfoJSONEncoder elif cli.args.format == 'keymap': json_encoder = KeymapJSONEncoder else: # This should be impossible cli.log.error('Unknown format: %s', cli.args.format) return False if json_encoder == KeymapJSONEncoder and 'layout' in json_file: # Attempt to format the keycodes. layout = json_file['layout'] info_data = info_json(json_file['keyboard']) if layout in info_data.get('layout_aliases', {}): layout = json_file['layout'] = info_data['layout_aliases'][layout] if layout in info_data.get('layouts'): for layer_num, layer in enumerate(json_file['layers']): current_layer = [] last_row = 0 for keymap_key, info_key in zip( layer, info_data['layouts'][layout]['layout']): if last_row != info_key['y']: current_layer.append('JSON_NEWLINE') last_row = info_key['y'] current_layer.append(keymap_key) json_file['layers'][layer_num] = current_layer # Display the results print(json.dumps(json_file, cls=json_encoder))
def generate_keyboard_c(cli): """Generates the keyboard.h file. """ kb_info_json = info_json(cli.args.keyboard) # Build the layouts.h file. keyboard_h_lines = [ GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '' ] keyboard_h_lines.extend(_gen_led_config(kb_info_json)) # Show the results dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
def would_populate_layout_h(keyboard): """Detect if a given keyboard is doing data driven layouts """ # Build the info.json file kb_info_json = info_json(keyboard) for layout_name in kb_info_json['layouts']: 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!', keyboard, layout_name) continue return True return False
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 parameter: --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 if cli.args.overwrite: output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve() if cli.args.output: cli.log.warning('Overwriting user supplied --output with %s', output_path) cli.args.output = output_path # Build the info.json file kb_info_json = info_json(cli.config.generate_info_json.keyboard) strip_info_json(kb_info_json) info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder) if cli.args.output: # Write to a file output_path = normpath(cli.args.output) if output_path.exists(): cli.log.warning('Overwriting output file %s', output_path) output_path.write_text(info_json_text + '\n') cli.log.info('Wrote info.json to %s.', output_path) else: # Display the results print(info_json_text)
def generate_config_h(cli): """Generates the info_config.h file. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.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' ] generate_config_items(kb_info_json, config_h_lines) generate_matrix_size(kb_info_json, config_h_lines) if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) if 'split' in kb_info_json: generate_split_config(kb_info_json, config_h_lines) # 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.parent / (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)
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 parameter: --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) strip_info_json(kb_info_json) # Display the results print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder))
def via2json(cli): """Convert a VIA backup json to keymap.json format. This command uses the `qmk.keymap` module to generate a keymap.json from a VIA backup json. The generated keymap is written to stdout, or to a file if -o is provided. """ # Find appropriate layout macro keymap_layout = cli.args.layout if cli.args.layout else _find_via_layout_macro( cli.args.keyboard) if not keymap_layout: cli.log.error( f"Couldn't find LAYOUT macro for keyboard {cli.args.keyboard}. Please specify it with the '-l' argument." ) exit(1) # Load the VIA backup json with cli.args.filename.open('r') as fd: via_backup = json.load(fd) # Generate keyboard metadata keyboard_data = info_json(cli.args.keyboard) # Get keycode array keymap_data = _via_to_keymap(via_backup, keyboard_data, keymap_layout) # Convert macros macro_data = list() if via_backup.get('macros'): macro_data = _convert_macros(via_backup['macros']) # Replace VIA macro keys with JSON keymap ones keymap_data = _fix_macro_keys(keymap_data) # Generate the keymap.json keymap_json = generate_json(cli.args.keymap, cli.args.keyboard, keymap_layout, keymap_data, macro_data) keymap_lines = [json.dumps(keymap_json, cls=KeymapJSONEncoder)] dump_lines(cli.args.output, keymap_lines, cli.args.quiet)
def info(cli): """Compile an info.json for a particular keyboard and pretty-print it. """ # Determine our keyboard(s) if not is_keyboard(cli.config.info.keyboard): cli.log.error('Invalid keyboard: %s!', cli.config.info.keyboard) exit(1) # Build the info.json file kb_info_json = info_json(cli.config.info.keyboard) # Output in the requested format if cli.args.format == 'json': print(json.dumps(kb_info_json)) exit() if cli.args.format == 'text': for key in sorted(kb_info_json): if key == 'layouts': cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) else: cli.echo('{fg_blue}%s{fg_reset}: %s', key, kb_info_json[key]) if cli.config.info.layouts: show_layouts(kb_info_json, False) if cli.config.info.matrix: show_matrix(kb_info_json, False) if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': show_keymap(kb_info_json, False) elif cli.args.format == 'friendly': cli.echo('{fg_blue}Keyboard Name{fg_reset}: %s', kb_info_json.get('keyboard_name', 'Unknown')) cli.echo('{fg_blue}Manufacturer{fg_reset}: %s', kb_info_json.get('manufacturer', 'Unknown')) if 'url' in kb_info_json: cli.echo('{fg_blue}Website{fg_reset}: %s', kb_info_json['url']) if kb_info_json.get('maintainer') == 'qmk': cli.echo('{fg_blue}Maintainer{fg_reset}: QMK Community') else: cli.echo('{fg_blue}Maintainer{fg_reset}: %s', kb_info_json.get('maintainer', 'qmk')) cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s', kb_info_json.get('keyboard_folder', 'Unknown')) cli.echo('{fg_blue}Layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) if 'width' in kb_info_json and 'height' in 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 cli.config.info.layouts: show_layouts(kb_info_json, True) if cli.config.info.matrix: show_matrix(kb_info_json, True) if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': show_keymap(kb_info_json, True) else: cli.log.error('Unknown format: %s', cli.args.format)
def generate_api(cli): """Generates the QMK API data. """ api_data_dir = Path('api_data') v1_dir = api_data_dir / 'v1' keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target if not api_data_dir.exists(): api_data_dir.mkdir() kb_all = {} usb_list = {} # Generate and write keyboard specific JSON files for keyboard_name in list_keyboards(): kb_all[keyboard_name] = info_json(keyboard_name) keyboard_dir = v1_dir / 'keyboards' / keyboard_name keyboard_info = keyboard_dir / 'info.json' keyboard_readme = keyboard_dir / 'readme.md' keyboard_readme_src = find_readme(keyboard_name) keyboard_dir.mkdir(parents=True, exist_ok=True) keyboard_json = json.dumps({'last_updated': current_datetime(), 'keyboards': {keyboard_name: kb_all[keyboard_name]}}) if not cli.args.dry_run: keyboard_info.write_text(keyboard_json) cli.log.debug('Wrote file %s', keyboard_info) if keyboard_readme_src: copyfile(keyboard_readme_src, keyboard_readme) cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme) if 'usb' in kb_all[keyboard_name]: usb = kb_all[keyboard_name]['usb'] if 'vid' in usb and usb['vid'] not in usb_list: usb_list[usb['vid']] = {} if 'pid' in usb and usb['pid'] not in usb_list[usb['vid']]: usb_list[usb['vid']][usb['pid']] = {} if 'vid' in usb and 'pid' in usb: usb_list[usb['vid']][usb['pid']][keyboard_name] = usb # Generate data for the global files keyboard_list = sorted(kb_all) keyboard_aliases = json_load(Path('data/mappings/keyboard_aliases.json')) keyboard_metadata = { 'last_updated': current_datetime(), 'keyboards': keyboard_list, 'keyboard_aliases': keyboard_aliases, 'usb': usb_list, } # Write the global JSON files keyboard_all_json = json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, cls=InfoJSONEncoder) usb_json = json.dumps({'last_updated': current_datetime(), 'usb': usb_list}, cls=InfoJSONEncoder) keyboard_list_json = json.dumps({'last_updated': current_datetime(), 'keyboards': keyboard_list}, cls=InfoJSONEncoder) keyboard_aliases_json = json.dumps({'last_updated': current_datetime(), 'keyboard_aliases': keyboard_aliases}, cls=InfoJSONEncoder) keyboard_metadata_json = json.dumps(keyboard_metadata, cls=InfoJSONEncoder) if not cli.args.dry_run: keyboard_all_file.write_text(keyboard_all_json) usb_file.write_text(usb_json) keyboard_list_file.write_text(keyboard_list_json) keyboard_aliases_file.write_text(keyboard_aliases_json) keyboard_metadata_file.write_text(keyboard_metadata_json)
def compile_json(keyboard_keymap_data, source_ip=None, send_metrics=True, public_firmware=False): """Compile a keymap. Arguments: keyboard_keymap_data A configurator export file that's been deserialized source_ip The IP that submitted the compile job """ start_time = time() base_metric = f'{gethostname()}.qmk_compiler.compile_json' result = { 'keyboard': 'unknown', 'returncode': -2, 'output': '', 'firmware': None, 'firmware_filename': '', 'source_ip': source_ip, 'output': 'Unknown error', 'public_firmware': public_firmware, } if DEBUG: print('Pointing graphite at', GRAPHITE_HOST) graphyte.init(GRAPHITE_HOST, GRAPHITE_PORT) try: for key in ('keyboard', 'layout', 'keymap'): result[key] = keyboard_keymap_data[key] # Gather information result['keymap_archive'] = '%s-%s.json' % (result['keyboard'].replace('/', '-'), result['keymap'].replace('/', '-')) result['keymap_json'] = json.dumps(keyboard_keymap_data) result['command'] = ['bin/qmk', 'compile', result['keymap_archive']] job = get_current_job() result['id'] = job.id branch = keyboard_keymap_data.get('branch', QMK_GIT_BRANCH) # Fetch the appropriate version of QMK git_start_time = time() checkout_qmk(branch=branch) git_time = time() - git_start_time chdir('qmk_firmware') # Sanity check if not path.exists('keyboards/' + result['keyboard']): print('Unknown keyboard: %s' % (result['keyboard'],)) return {'returncode': -1, 'command': '', 'output': 'Unknown keyboard!', 'firmware': None} # Pull in the modules from the QMK we just checked out if './lib/python' not in sys.path: sys.path.append('./lib/python') from qmk.info import info_json # If this keyboard needs a submodule check it out submodule_start_time = time() kb_info = info_json(result['keyboard']) if 'protocol' not in kb_info: kb_info['protocol'] = 'unknown' if kb_info['protocol'] in ['ChibiOS', 'LUFA']: checkout_lufa() if kb_info['protocol'] == 'ChibiOS': checkout_chibios() if kb_info['protocol'] == 'V-USB': checkout_vusb() submodule_time = time() - submodule_start_time # Write the keymap file with open(result['keymap_archive'], 'w') as fd: fd.write(result['keymap_json'] + '\n') # Compile the firmware compile_start_time = time() compile_keymap(job, result) compile_time = time() - compile_start_time # Store the source in S3 storage_start_time = time() store_firmware_binary(result) chdir('..') store_firmware_source(result) remove(result['source_archive']) storage_time = time() - storage_start_time # Send metrics about this build if send_metrics: graphyte.send(f'{base_metric}.{result["keyboard"]}.all_layouts', 1) graphyte.send(f'{base_metric}.{result["keyboard"]}.{result["layout"]}', 1) graphyte.send(f'{base_metric}.{result["keyboard"]}.git_time', git_time) graphyte.send(f'{base_metric}.all_keyboards.git_time', git_time) graphyte.send(f'{base_metric}.{result["keyboard"]}.submodule_time', submodule_time) graphyte.send(f'{base_metric}.all_keyboards.submodule_time', submodule_time) graphyte.send(f'{base_metric}.{result["keyboard"]}.compile_time', compile_time) graphyte.send(f'{base_metric}.all_keyboards.compile_time', compile_time) if result['returncode'] == 0: graphyte.send(f'{base_metric}.{result["keyboard"]}.compile_time', compile_time) graphyte.send(f'{base_metric}.all_keyboards.compile_time', compile_time) else: graphyte.send(f'{base_metric}.{result["keyboard"]}.errors', 1) if source_ip: ip_location = geolite2.lookup(source_ip) if ip_location: if ip_location.subdivisions: location_key = f'{ip_location.country}_{"_".join(ip_location.subdivisions)}' else: location_key = ip_location.country graphyte.send(f'{gethostname()}.qmk_compiler.geoip.{location_key}', 1) total_time = time() - start_time graphyte.send(f'{base_metric}.{result["keyboard"]}.storage_time', storage_time) graphyte.send(f'{base_metric}.all_keyboards.storage_time', storage_time) graphyte.send(f'{base_metric}.{result["keyboard"]}.total_time', total_time) graphyte.send(f'{base_metric}.all_keyboards.total_time', total_time) except Exception as e: result['returncode'] = -3 result['exception'] = e.__class__.__name__ result['stacktrace'] = format_exc() if send_metrics: graphyte.send(f'{base_metric}.{result["keyboard"]}.errors', 1) store_firmware_metadata(job, result) return result
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 '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']: 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)] 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)
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.', '' ] # 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]}') # 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') 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']: 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' 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)
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.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.', '' ] # 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) # 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: 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}') # Set SPLIT_TRANSPORT, if needed if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') # Set CUSTOM_MATRIX, if needed if kb_info_json.get('matrix_pins', {}).get('custom'): if kb_info_json.get('matrix_pins', {}).get('custom_lite'): rules_mk_lines.append('CUSTOM_MATRIX ?= lite') else: rules_mk_lines.append('CUSTOM_MATRIX ?= yes') # 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.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(rules_mk) if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output) else: print(rules_mk)
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 parameter: --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_config.h file. kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) info_config_map = json_load(Path('data/mappings/info_config.json')) config_h_lines = [ '/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once' ] # 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'])) # 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.parent / (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)
def lint(cli): """Check keyboard and keymap for common mistakes. """ failed = [] # Determine our keyboard list if cli.args.all_kb: if cli.args.keyboard: cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes presidence.') keyboard_list = list_keyboards() elif not cli.config.lint.keyboard: cli.log.error('Missing required arguments: --keyboard or --all-kb') cli.print_help() return False else: keyboard_list = cli.config.lint.keyboard.split(',') # Lint each keyboard for kb in keyboard_list: if not is_keyboard(kb): cli.log.error('No such keyboard: %s', kb) continue # Gather data about the keyboard. ok = True keyboard_path = keyboard(kb) keyboard_info = info_json(kb) # Check for errors in the info.json if keyboard_info['parse_errors']: ok = False cli.log.error('%s: Errors found when generating info.json.', kb) if cli.config.lint.strict and keyboard_info['parse_warnings']: ok = False cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb) # Check the rules.mk file(s) rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path) if rules_mk_assignment_errors: ok = False cli.log.error('%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb) for assignment_error in rules_mk_assignment_errors: cli.log.error(assignment_error) # Keymap specific checks if cli.config.lint.keymap: if not keymap_check(kb, cli.config.lint.keymap): ok = False # Report status if not ok: failed.append(kb) # Check and report the overall status if failed: cli.log.error('Lint check failed for: %s', ', '.join(failed)) return False cli.log.info('Lint check passed!') return True
def list_layouts(cli): """List the layouts for a specific keyboard """ info_data = info_json(cli.config.list_layouts.keyboard) for name in sorted(info_data.get('community_layouts', [])): print(name)