First stab at codegen.

This commit is contained in:
Nick Brassel 2025-01-22 00:50:55 +11:00
commit e3fe9fac60
No known key found for this signature in database
8 changed files with 123 additions and 27 deletions

View file

@ -224,6 +224,20 @@ $(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON)
generated-files: $(INTERMEDIATE_OUTPUT)/src/config.h $(INTERMEDIATE_OUTPUT)/src/keymap.c $(INTERMEDIATE_OUTPUT)/src/keymap.h
$(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-h --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/community_modules.c: $(KEYMAP_JSON)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-c --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(KEYMAP_JSON))
@$(BUILD_CMD)
SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c
generated-files: $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c
endif
include $(BUILDDEFS_PATH)/converters.mk

View file

@ -71,6 +71,12 @@
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string"
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View file

@ -49,6 +49,7 @@ subcommands = [
'qmk.cli.generate.api',
'qmk.cli.generate.autocorrect_data',
'qmk.cli.generate.compilation_database',
'qmk.cli.generate.community_modules',
'qmk.cli.generate.config_h',
'qmk.cli.generate.develop_pr_list',
'qmk.cli.generate.dfu_header',

View file

@ -0,0 +1,75 @@
from argcomplete.completers import FilesCompleter
from milc import cli
from milc.attrdict import AttrDict
import qmk.path
from qmk.commands import dump_lines, parse_configurator_json
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
class ModuleAPI(AttrDict):
def __init__(self, **kwargs):
super().__init__()
for key, value in kwargs.items():
self[key] = value
MODULE_API_LIST = [
ModuleAPI(ret_type='void', name='keyboard_pre_init', args='void', call_params='', ret_val=None),
ModuleAPI(ret_type='void', name='keyboard_post_init', args='void', call_params='', ret_val=None),
ModuleAPI(ret_type='void', name='housekeeping_task', args='void', call_params='', ret_val=None),
ModuleAPI(ret_type='bool', name='process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', ret_val='true'),
]
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.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('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
@cli.subcommand('Creates a community_modules.h from a keymap.json file.')
def generate_community_modules_h(cli):
"""Creates a community_modules.h from a keymap.json file
"""
if cli.args.output and cli.args.output.name == '-':
cli.args.output = None
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
keymap_json = parse_configurator_json(cli.args.filename)
if keymap_json and 'modules' in keymap_json:
for module in keymap_json['modules']:
for api in MODULE_API_LIST:
lines.append(f'{api.ret_type} {api.name}_{module}({api.args});')
dump_lines(cli.args.output, lines, cli.args.quiet)
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.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('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
@cli.subcommand('Creates a community_modules.c from a keymap.json file.')
def generate_community_modules_c(cli):
"""Creates a community_modules.c from a keymap.json file
"""
if cli.args.output and cli.args.output.name == '-':
cli.args.output = None
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '#include "community_modules.h"']
keymap_json = parse_configurator_json(cli.args.filename)
if keymap_json and 'modules' in keymap_json:
for module in keymap_json['modules']:
for api in MODULE_API_LIST:
lines.append('')
lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module}({api.args}) {{ ')
if api.ret_val:
lines.append(f' return {api.ret_val};')
lines.append('}')
for api in MODULE_API_LIST:
lines.append('')
lines.append(f'{api.ret_type} {api.name}_modules({api.args}) {{')
if api.ret_type == 'bool':
lines.append(' return true')
for module in keymap_json['modules']:
if api.ret_type == 'bool':
lines.append(f' && {api.name}_{module}({api.call_params})')
else:
lines.append(f' {api.name}_{module}({api.call_params});')
if api.ret_type == 'bool':
lines.append(' ;')
lines.append('}')
dump_lines(cli.args.output, lines, cli.args.quiet)

View file

@ -55,6 +55,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
def generate_rules_mk(cli):
"""Generates a rules.mk file from info.json.
"""
user_keymap = None
converter = None
# Determine our keyboard/keymap
if cli.args.filename:
@ -99,6 +100,14 @@ def generate_rules_mk(cli):
if converter:
rules_mk_lines.append(generate_rule('CONVERT_TO', converter))
if user_keymap and 'modules' in user_keymap:
for module in user_keymap['modules']:
rules_mk_lines.append('')
rules_mk_lines.append(f'COMMUNITY_MODULES += {module}')
rules_mk_lines.append(f'OPT_DEFS += -DMODULE_{module.upper()}=TRUE')
rules_mk_lines.append(f'VPATH += modules/{module}')
rules_mk_lines.append(f'include modules/{module}/rules.mk')
# Show the results
dump_lines(cli.args.output, rules_mk_lines)

View file

@ -334,33 +334,6 @@ def write_json(keyboard, keymap, layout, layers, macros=None):
return write_file(keymap_file, keymap_content)
def write(keymap_json):
"""Generate the `keymap.c` and write it to disk.
Returns the filename written to.
`keymap_json` should be a dict with the following keys:
keyboard
The name of the keyboard
keymap
The name of the keymap
layout
The LAYOUT macro this keymap uses.
layers
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
macros
A list of macros for this keymap.
"""
keymap_content = generate_c(keymap_json)
keymap_file = qmk.path.keymaps(keymap_json['keyboard'])[0] / keymap_json['keymap'] / 'keymap.c'
return write_file(keymap_file, keymap_content)
def locate_keymap(keyboard, keymap, force_layout=None):
"""Returns the path to a keymap for a specific keyboard.
"""

View file

@ -0,0 +1,17 @@
// Copyright 2025 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
static uint32_t init_timestamp = 0;
void keyboard_post_init_hello_world(void) {
init_timestamp = timer_read32();
}
void housekeeping_task_hello_world(void) {
static bool welcomed = false;
if(!welcomed && timer_elapsed32(init_timestamp) > 1000) {
welcomed = true;
print("Hello, world! I'm a QMK based keyboard!\n");
}
}

View file

@ -0,0 +1 @@
SRC += hello_world.c