Skip to content

Commit 364281c

Browse files
authored
Create a system to map between info.json and config.h/rules.mk (qmk#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
1 parent 29158be commit 364281c

File tree

11 files changed

+329
-460
lines changed

11 files changed

+329
-460
lines changed

bin/qmk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def _check_modules(requirements):
2727
line = line.split('#')[0]
2828

2929
module = dict()
30-
module['name'] = module['import'] = line.split('=')[0] if '=' in line else line
30+
module['name'] = line.split('=')[0] if '=' in line else line
31+
module['import'] = module['name'].replace('-', '_')
3132

3233
# Not every module is importable by its own name.
3334
if module['name'] == "pep8-naming":

data/mappings/info_config.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This file maps keys between `config.h` and `info.json`. It is used by QMK
2+
# to correctly and consistently map back and forth between the two systems.
3+
{
4+
# Format:
5+
# <config.h key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
6+
# value_type: one of "array", "array.int", "int", "hex", "list", "mapping"
7+
# to_json: Default `true`. Set to `false` to exclude this mapping from info.json
8+
# to_c: Default `true`. Set to `false` to exclude this mapping from config.h
9+
# warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
10+
"DEBOUNCE": {"info_key": "debounce", "value_type": "int"}
11+
"DEVICE_VER": {"info_key": "usb.device_ver", "value_type": "hex"},
12+
"DESCRIPTION": {"info_key": "keyboard_folder", "to_json": false},
13+
"DIODE_DIRECTION": {"info_key": "diode_direction"},
14+
"LAYOUTS": {"info_key": "layout_aliases", "value_type": "mapping"},
15+
"LED_CAPS_LOCK_PIN": {"info_key": "indicators.caps_lock"},
16+
"LED_NUM_LOCK_PIN": {"info_key": "indicators.num_lock"},
17+
"LED_SCROLL_LOCK_PIN": {"info_key": "indicators.scroll_lock"},
18+
"MANUFACTURER": {"info_key": "manufacturer"},
19+
"RGB_DI_PIN": {"info_key": "rgblight.pin"},
20+
"RGBLED_NUM": {"info_key": "rgblight.led_count", "value_type": "int"},
21+
"RGBLED_SPLIT": {"info_key": "rgblight.split_count", "value_type": "array.int"},
22+
"RGBLIGHT_ANIMATIONS": {"info_key": "rgblight.animations.all", "value_type": "bool"},
23+
"RGBLIGHT_EFFECT_ALTERNATING": {"info_key": "rgblight.animations.alternating", "value_type": "bool"},
24+
"RGBLIGHT_EFFECT_BREATHING": {"info_key": "rgblight.animations.breathing", "value_type": "bool"},
25+
"RGBLIGHT_EFFECT_CHRISTMAS": {"info_key": "rgblight.animations.christmas", "value_type": "bool"},
26+
"RGBLIGHT_EFFECT_KNIGHT": {"info_key": "rgblight.animations.knight", "value_type": "bool"},
27+
"RGBLIGHT_EFFECT_RAINBOW_MOOD": {"info_key": "rgblight.animations.rainbow_mood", "value_type": "bool"},
28+
"RGBLIGHT_EFFECT_RAINBOW_SWIRL": {"info_key": "rgblight.animations.rainbow_swirl", "value_type": "bool"},
29+
"RGBLIGHT_EFFECT_RGB_TEST": {"info_key": "rgblight.animations.rgb_test", "value_type": "bool"},
30+
"RGBLIGHT_EFFECT_SNAKE": {"info_key": "rgblight.animations.snake", "value_type": "bool"},
31+
"RGBLIGHT_EFFECT_STATIC_GRADIENT": {"info_key": "rgblight.animations.static_gradient", "value_type": "bool"},
32+
"RGBLIGHT_EFFECT_TWINKLE": {"info_key": "rgblight.animations.twinkle"},
33+
"RGBLIGHT_LIMIT_VAL": {"info_key": "rgblight.max_brightness", "value_type": "int"},
34+
"RGBLIGHT_HUE_STEP": {"info_key": "rgblight.hue_steps", "value_type": "int"},
35+
"RGBLIGHT_SAT_STEP": {"info_key": "rgblight.saturation_steps", "value_type": "int"},
36+
"RGBLIGHT_VAL_STEP": {"info_key": "rgblight.brightness_steps", "value_type": "int"},
37+
"RGBLIGHT_SLEEP": {"info_key": "rgblight.sleep", "value_type": "bool"},
38+
"RGBLIGHT_SPLIT": {"info_key": "rgblight.split", "value_type": "bool"},
39+
"PRODUCT": {"info_key": "keyboard_folder", "to_json": false},
40+
"PRODUCT_ID": {"info_key": "usb.pid", "value_type": "hex"},
41+
"VENDOR_ID": {"info_key": "usb.vid", "value_type": "hex"}
42+
}

data/mappings/info_rules.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This file maps keys between `rules.mk` and `info.json`. It is used by QMK
2+
# to correctly and consistently map back and forth between the two systems.
3+
{
4+
# Format:
5+
# <rules.mk key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
6+
# value_type: one of "array", "array.int", "int", "list", "hex", "mapping"
7+
# to_json: Default `true`. Set to `false` to exclude this mapping from info.json
8+
# to_c: Default `true`. Set to `false` to exclude this mapping from rules.mk
9+
# warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
10+
"BOARD": {"info_key": "board"},
11+
"BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},
12+
"LAYOUTS": {"info_key": "community_layouts", "value_type": "list"},
13+
"LED_MATRIX_DRIVER": {"info_key": "led_matrix.driver"},
14+
"MCU": {"info_key": "processor", "warn_duplicate": false},
15+
}

docs/data_driven_config.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ Now we have support for generating `rules.mk` and `config.h` values from `info.j
1212

1313
## Overview
1414

15-
On the C side of things nothing really changes. When you need to create a new rule or define you follow the same process:
15+
On the C side of things nothing changes. When you need to create a new rule or define you follow the same process:
1616

1717
1. Add it to `docs/config_options.md`
1818
1. Set a default in the appropriate core file
19-
1. Add your `ifdef` and/or `#ifdef` statements as needed
19+
1. Add your ifdef statements as needed
2020

2121
You will then need to add support for your new configuration to `info.json`. The basic process is:
2222

2323
1. Add it to the schema in `data/schemas/keyboards.jsonschema`
24-
1. Add code to extract it from `config.h`/`rules.mk` to `lib/python/qmk/info.py`
25-
1. Add code to generate it to one of:
24+
1. Add a mapping in `data/maps`
25+
1. (optional and discoraged) Add code to extract/generate it to:
26+
* `lib/python/qmk/info.py`
2627
* `lib/python/qmk/cli/generate/config_h.py`
2728
* `lib/python/qmk/cli/generate/rules_mk.py`
2829

@@ -32,12 +33,43 @@ This section describes adding support for a `config.h`/`rules.mk` value to info.
3233

3334
### Add it to the schema
3435

35-
QMK maintains schema files in `data/schemas`. The values that go into keyboard-specific `info.json` files are kept in `keyboard.jsonschema`. Any value you want to make available to end users to edit must go in here.
36+
QMK maintains [jsonschema](https://json-schema.org/) files in `data/schemas`. The values that go into keyboard-specific `info.json` files are kept in `keyboard.jsonschema`. Any value you want to make available to end users to edit must go in here.
3637

37-
In some cases you can simply add a new top-level key. Some examples to follow are `keyboard_name`, `maintainer`, `processor`, and `url`. This is appropriate when your option is self-contained and not directly related to other options. In other cases you should group like options together in an `object`. This is particularly true when adding support for a feature. Some examples to follow for this are `indicators`, `matrix_pins`, and `rgblight`. If you are not sure how to integrate your new option(s) [open an issue](https://github.com/qmk/qmk_firmware/issues/new?assignees=&labels=cli%2C+python&template=other_issues.md&title=) or [join #cli on Discord](https://discord.gg/heQPAgy) and start a conversation there.
38+
In some cases you can simply add a new top-level key. Some examples to follow are `keyboard_name`, `maintainer`, `processor`, and `url`. This is appropriate when your option is self-contained and not directly related to other options.
39+
40+
In other cases you should group like options together in an `object`. This is particularly true when adding support for a feature. Some examples to follow for this are `indicators`, `matrix_pins`, and `rgblight`. If you are not sure how to integrate your new option(s) [open an issue](https://github.com/qmk/qmk_firmware/issues/new?assignees=&labels=cli%2C+python&template=other_issues.md&title=) or [join #cli on Discord](https://discord.gg/heQPAgy) and start a conversation there.
41+
42+
### Add a mapping
43+
44+
In most cases you can add a simple mapping. These are maintained as JSON files in `data/mappings/info_config.json` and `data/mappings/info_rules.json`, and control mapping for `config.h` and `rules.mk`, respectively. Each mapping is keyed by the `config.h` or `rules.mk` variable, and the value is a hash with the following keys:
45+
46+
* `info_key`: (required) The location within `info.json` for this value. See below.
47+
* `value_type`: (optional) Default `str`. The format for this variable's value. See below.
48+
* `to_json`: (optional) Default `true`. Set to `false` to exclude this mapping from info.json
49+
* `to_c`: (optional) Default `true`. Set to `false` to exclude this mapping from config.h
50+
* `warn_duplicate`: (optional) Default `true`. Set to `false` to turn off warning when a value exists in both places
51+
52+
#### Info Key
53+
54+
We use JSON dot notation to address variables within info.json. For example, to access `info_json["rgblight"]["split_count"]` I would specify `rgblight.split_count`. This allows you to address deeply nested keys with a simple string.
55+
56+
Under the hood we use [Dotty Dict](https://dotty-dict.readthedocs.io/en/latest/), you can refer to that documentation for how these strings are converted to object access.
57+
58+
#### Value Types
59+
60+
By default we treat all values as simple strings. If your value is more complex you can use one of these types to intelligently parse the data:
61+
62+
* `array`: A comma separated array of strings
63+
* `array.int`: A comma separated array of integers
64+
* `int`: An integer
65+
* `hex`: A number formatted as hex
66+
* `list`: A space separate array of strings
67+
* `mapping`: A hash of key/value pairs
3868

3969
### Add code to extract it
4070

71+
Most use cases can be solved by the mapping files described above. If yours can't you can instead write code to extract your config values.
72+
4173
Whenever QMK generates a complete `info.json` it extracts information from `config.h` and `rules.mk`. You will need to add code for your new config value to `lib/python/qmk/info.py`. Typically this means adding a new `_extract_<feature>()` function and then calling your function in either `_extract_config_h()` or `_extract_rules_mk()`.
4274

4375
If you are not sure how to edit this file or are not comfortable with Python [open an issue](https://github.com/qmk/qmk_firmware/issues/new?assignees=&labels=cli%2C+python&template=other_issues.md&title=) or [join #cli on Discord](https://discord.gg/heQPAgy) and someone can help you with this part.

lib/python/qmk/cli/doctor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ def doctor(cli):
107107
submodules.update()
108108
sub_ok = check_submodules()
109109

110-
if CheckStatus.ERROR in sub_ok:
110+
if sub_ok == CheckStatus.ERROR:
111111
status = CheckStatus.ERROR
112-
elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK:
112+
elif sub_ok == CheckStatus.WARNING and status == CheckStatus.OK:
113113
status = CheckStatus.WARNING
114114

115115
# Report a summary of our findings to the user

0 commit comments

Comments
 (0)