aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/qmk3
-rw-r--r--data/mappings/info_config.json42
-rw-r--r--data/mappings/info_rules.json15
-rw-r--r--docs/data_driven_config.md44
-rw-r--r--keyboards/handwired/pytest/basic/info.json10
-rw-r--r--keyboards/vision_division/keymaps/default/config.h1
-rwxr-xr-xlib/python/qmk/cli/doctor.py4
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py245
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py61
-rw-r--r--lib/python/qmk/constants.py2
-rw-r--r--lib/python/qmk/info.py343
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py29
-rw-r--r--requirements.txt1
13 files changed, 339 insertions, 461 deletions
diff --git a/bin/qmk b/bin/qmk
index 801852d4e..a3c1be328 100755
--- a/bin/qmk
+++ b/bin/qmk
@@ -27,7 +27,8 @@ def _check_modules(requirements):
27 line = line.split('#')[0] 27 line = line.split('#')[0]
28 28
29 module = dict() 29 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('-', '_')
31 32
32 # Not every module is importable by its own name. 33 # Not every module is importable by its own name.
33 if module['name'] == "pep8-naming": 34 if module['name'] == "pep8-naming":
diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json
new file mode 100644
index 000000000..885e6d025
--- /dev/null
+++ b/data/mappings/info_config.json
@@ -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}
diff --git a/data/mappings/info_rules.json b/data/mappings/info_rules.json
new file mode 100644
index 000000000..97f772c4d
--- /dev/null
+++ b/data/mappings/info_rules.json
@@ -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}
diff --git a/docs/data_driven_config.md b/docs/data_driven_config.md
index 7e4f23284..c2ad4fed8 100644
--- a/docs/data_driven_config.md
+++ b/docs/data_driven_config.md
@@ -12,17 +12,18 @@ Now we have support for generating `rules.mk` and `config.h` values from `info.j
12 12
13## Overview 13## Overview
14 14
15On the C side of things nothing really changes. When you need to create a new rule or define you follow the same process: 15On the C side of things nothing changes. When you need to create a new rule or define you follow the same process:
16 16
171. Add it to `docs/config_options.md` 171. Add it to `docs/config_options.md`
181. Set a default in the appropriate core file 181. Set a default in the appropriate core file
191. Add your `ifdef` and/or `#ifdef` statements as needed 191. Add your ifdef statements as needed
20 20
21You will then need to add support for your new configuration to `info.json`. The basic process is: 21You will then need to add support for your new configuration to `info.json`. The basic process is:
22 22
231. Add it to the schema in `data/schemas/keyboards.jsonschema` 231. Add it to the schema in `data/schemas/keyboards.jsonschema`
241. Add code to extract it from `config.h`/`rules.mk` to `lib/python/qmk/info.py` 241. Add a mapping in `data/maps`
251. Add code to generate it to one of: 251. (optional and discoraged) Add code to extract/generate it to:
26 * `lib/python/qmk/info.py`
26 * `lib/python/qmk/cli/generate/config_h.py` 27 * `lib/python/qmk/cli/generate/config_h.py`
27 * `lib/python/qmk/cli/generate/rules_mk.py` 28 * `lib/python/qmk/cli/generate/rules_mk.py`
28 29
@@ -32,12 +33,43 @@ This section describes adding support for a `config.h`/`rules.mk` value to info.
32 33
33### Add it to the schema 34### Add it to the schema
34 35
35QMK 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. 36QMK 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.
36 37
37In 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. 38In 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
40In 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
44In 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
54We 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
56Under 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
60By 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
38 68
39### Add code to extract it 69### Add code to extract it
40 70
71Most 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
41Whenever 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()`. 73Whenever 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()`.
42 74
43If 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. 75If 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.
diff --git a/keyboards/handwired/pytest/basic/info.json b/keyboards/handwired/pytest/basic/info.json
new file mode 100644
index 000000000..ed052a14a
--- /dev/null
+++ b/keyboards/handwired/pytest/basic/info.json
@@ -0,0 +1,10 @@
1{
2 "maintainer": "qmk",
3 "layouts": {
4 "LAYOUT_custom": {
5 "layout": [
6 { "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 }
7 ]
8 }
9 }
10}
diff --git a/keyboards/vision_division/keymaps/default/config.h b/keyboards/vision_division/keymaps/default/config.h
index b8f22a3b5..aa8fc62aa 100644
--- a/keyboards/vision_division/keymaps/default/config.h
+++ b/keyboards/vision_division/keymaps/default/config.h
@@ -8,7 +8,6 @@
8#define VENDOR_ID 0xFEED 8#define VENDOR_ID 0xFEED
9#define DEVICE_VER 0x0001 9#define DEVICE_VER 0x0001
10#define MANUFACTURER IBNobody 10#define MANUFACTURER IBNobody
11#define PRODUCT Vision Division
12 11
13#define MATRIX_ROWS 6 12#define MATRIX_ROWS 6
14#define MATRIX_ROW_PINS { C2, C3, F4, F5, F6, F7 } 13#define MATRIX_ROW_PINS { C2, C3, F4, F5, F6, F7 }
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
index 70f32911a..28d480707 100755
--- a/lib/python/qmk/cli/doctor.py
+++ b/lib/python/qmk/cli/doctor.py
@@ -107,9 +107,9 @@ def doctor(cli):
107 submodules.update() 107 submodules.update()
108 sub_ok = check_submodules() 108 sub_ok = check_submodules()
109 109
110 if CheckStatus.ERROR in sub_ok: 110 if sub_ok == CheckStatus.ERROR:
111 status = CheckStatus.ERROR 111 status = CheckStatus.ERROR
112 elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK: 112 elif sub_ok == CheckStatus.WARNING and status == CheckStatus.OK:
113 status = CheckStatus.WARNING 113 status = CheckStatus.WARNING
114 114
115 # Report a summary of our findings to the user 115 # 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 1de84de7a..7ddad745d 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -1,62 +1,14 @@
1"""Used by the make system to generate info_config.h from info.json. 1"""Used by the make system to generate info_config.h from info.json.
2""" 2"""
3from pathlib import Path
4
5from dotty_dict import dotty
3from milc import cli 6from milc import cli
4 7
5from qmk.constants import LED_INDICATORS
6from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
7from qmk.info import info_json, rgblight_animations, rgblight_properties, rgblight_toggles 9from qmk.info import _json_load, info_json
8from qmk.path import is_keyboard, normpath 10from qmk.path import is_keyboard, normpath
9 11
10usb_prop_map = {
11 'vid': 'VENDOR_ID',
12 'pid': 'PRODUCT_ID',
13 'device_ver': 'DEVICE_VER',
14}
15
16
17def debounce(debounce):
18 """Return the config.h lines that set debounce
19 """
20 return """
21#ifndef DEBOUNCE
22# define DEBOUNCE %s
23#endif // DEBOUNCE
24""" % debounce
25
26
27def diode_direction(diode_direction):
28 """Return the config.h lines that set diode direction
29 """
30 return """
31#ifndef DIODE_DIRECTION
32# define DIODE_DIRECTION %s
33#endif // DIODE_DIRECTION
34""" % diode_direction
35
36
37def keyboard_name(keyboard_name):
38 """Return the config.h lines that set the keyboard's name.
39 """
40 return """
41#ifndef DESCRIPTION
42# define DESCRIPTION %s
43#endif // DESCRIPTION
44
45#ifndef PRODUCT
46# define PRODUCT %s
47#endif // PRODUCT
48""" % (keyboard_name.replace("'", ""), keyboard_name.replace("'", ""))
49
50
51def manufacturer(manufacturer):
52 """Return the config.h lines that set the manufacturer.
53 """
54 return """
55#ifndef MANUFACTURER
56# define MANUFACTURER %s
57#endif // MANUFACTURER
58""" % (manufacturer.replace("'", ""))
59
60 12
61def direct_pins(direct_pins): 13def direct_pins(direct_pins):
62 """Return the config.h lines that set the direct pins. 14 """Return the config.h lines that set the direct pins.
@@ -72,80 +24,34 @@ def direct_pins(direct_pins):
72 24
73 return """ 25 return """
74#ifndef MATRIX_COLS 26#ifndef MATRIX_COLS
75# define MATRIX_COLS %s 27# define MATRIX_COLS %s
76#endif // MATRIX_COLS 28#endif // MATRIX_COLS
77 29
78#ifndef MATRIX_ROWS 30#ifndef MATRIX_ROWS
79# define MATRIX_ROWS %s 31# define MATRIX_ROWS %s
80#endif // MATRIX_ROWS 32#endif // MATRIX_ROWS
81 33
82#ifndef DIRECT_PINS 34#ifndef DIRECT_PINS
83# define DIRECT_PINS {%s} 35# define DIRECT_PINS {%s}
84#endif // DIRECT_PINS 36#endif // DIRECT_PINS
85""" % (col_count, row_count, ','.join(rows)) 37""" % (col_count, row_count, ','.join(rows))
86 38
87 39
88def col_pins(col_pins): 40def pin_array(define, pins):
89 """Return the config.h lines that set the column pins. 41 """Return the config.h lines that set a pin array.
90 """
91 cols = ','.join(map(str, [pin or 'NO_PIN' for pin in col_pins]))
92 col_num = len(col_pins)
93
94 return """
95#ifndef MATRIX_COLS
96# define MATRIX_COLS %s
97#endif // MATRIX_COLS
98
99#ifndef MATRIX_COL_PINS
100# define MATRIX_COL_PINS {%s}
101#endif // MATRIX_COL_PINS
102""" % (col_num, cols)
103
104
105def row_pins(row_pins):
106 """Return the config.h lines that set the row pins.
107 """
108 rows = ','.join(map(str, [pin or 'NO_PIN' for pin in row_pins]))
109 row_num = len(row_pins)
110
111 return """
112#ifndef MATRIX_ROWS
113# define MATRIX_ROWS %s
114#endif // MATRIX_ROWS
115
116#ifndef MATRIX_ROW_PINS
117# define MATRIX_ROW_PINS {%s}
118#endif // MATRIX_ROW_PINS
119""" % (row_num, rows)
120
121
122def indicators(config):
123 """Return the config.h lines that setup LED indicators.
124 """ 42 """
125 defines = [] 43 pin_num = len(pins)
44 pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))
126 45
127 for led, define in LED_INDICATORS.items(): 46 return f"""
128 if led in config: 47#ifndef {define}S
129 defines.append('') 48# define {define}S {pin_num}
130 defines.append('#ifndef %s' % (define,)) 49#endif // {define}S
131 defines.append('# define %s %s' % (define, config[led]))
132 defines.append('#endif // %s' % (define,))
133 50
134 return '\n'.join(defines) 51#ifndef {define}_PINS
135 52# define {define}_PINS {{ {pin_array} }}
136 53#endif // {define}_PINS
137def layout_aliases(layout_aliases): 54"""
138 """Return the config.h lines that setup layout aliases.
139 """
140 defines = []
141
142 for alias, layout in layout_aliases.items():
143 defines.append('')
144 defines.append('#ifndef %s' % (alias,))
145 defines.append('# define %s %s' % (alias, layout))
146 defines.append('#endif // %s' % (alias,))
147
148 return '\n'.join(defines)
149 55
150 56
151def matrix_pins(matrix_pins): 57def matrix_pins(matrix_pins):
@@ -157,58 +63,14 @@ def matrix_pins(matrix_pins):
157 pins.append(direct_pins(matrix_pins['direct'])) 63 pins.append(direct_pins(matrix_pins['direct']))
158 64
159 if 'cols' in matrix_pins: 65 if 'cols' in matrix_pins:
160 pins.append(col_pins(matrix_pins['cols'])) 66 pins.append(pin_array('MATRIX_COL', matrix_pins['cols']))
161 67
162 if 'rows' in matrix_pins: 68 if 'rows' in matrix_pins:
163 pins.append(row_pins(matrix_pins['rows'])) 69 pins.append(pin_array('MATRIX_ROW', matrix_pins['rows']))
164 70
165 return '\n'.join(pins) 71 return '\n'.join(pins)
166 72
167 73
168def rgblight(config):
169 """Return the config.h lines that setup rgblight.
170 """
171 rgblight_config = []
172
173 for json_key, config_key in rgblight_properties.items():
174 if json_key in config:
175 rgblight_config.append('')
176 rgblight_config.append('#ifndef %s' % (config_key[0],))
177 rgblight_config.append('# define %s %s' % (config_key[0], config[json_key]))
178 rgblight_config.append('#endif // %s' % (config_key[0],))
179
180 for json_key, config_key in rgblight_toggles.items():
181 if config.get(json_key):
182 rgblight_config.append('')
183 rgblight_config.append('#ifndef %s' % (config_key,))
184 rgblight_config.append('# define %s' % (config_key,))
185 rgblight_config.append('#endif // %s' % (config_key,))
186
187 for json_key, config_key in rgblight_animations.items():
188 if 'animations' in config and config['animations'].get(json_key):
189 rgblight_config.append('')
190 rgblight_config.append('#ifndef %s' % (config_key,))
191 rgblight_config.append('# define %s' % (config_key,))
192 rgblight_config.append('#endif // %s' % (config_key,))
193
194 return '\n'.join(rgblight_config)
195
196
197def usb_properties(usb_props):
198 """Return the config.h lines that setup USB params.
199 """
200 usb_lines = []
201
202 for info_name, config_name in usb_prop_map.items():
203 if info_name in usb_props:
204 usb_lines.append('')
205 usb_lines.append('#ifndef ' + config_name)
206 usb_lines.append('# define %s %s' % (config_name, usb_props[info_name]))
207 usb_lines.append('#endif // ' + config_name)
208
209 return '\n'.join(usb_lines)
210
211
212@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 74@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
213@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 75@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
214@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') 76@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.')
@@ -228,39 +90,52 @@ def generate_config_h(cli):
228 cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) 90 cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard)
229 return False 91 return False
230 92
231 # Build the info.json file
232 kb_info_json = info_json(cli.config.generate_config_h.keyboard)
233
234 # Build the info_config.h file. 93 # Build the info_config.h file.
235 config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] 94 kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
95 info_config_map = _json_load(Path('data/mappings/info_config.json'))
236 96
237 if 'debounce' in kb_info_json: 97 config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
238 config_h_lines.append(debounce(kb_info_json['debounce']))
239
240 if 'diode_direction' in kb_info_json:
241 config_h_lines.append(diode_direction(kb_info_json['diode_direction']))
242
243 if 'indicators' in kb_info_json:
244 config_h_lines.append(indicators(kb_info_json['indicators']))
245
246 if 'keyboard_name' in kb_info_json:
247 config_h_lines.append(keyboard_name(kb_info_json['keyboard_name']))
248
249 if 'layout_aliases' in kb_info_json:
250 config_h_lines.append(layout_aliases(kb_info_json['layout_aliases']))
251
252 if 'manufacturer' in kb_info_json:
253 config_h_lines.append(manufacturer(kb_info_json['manufacturer']))
254 98
255 if 'rgblight' in kb_info_json: 99 # Iterate through the info_config map to generate basic things
256 config_h_lines.append(rgblight(kb_info_json['rgblight'])) 100 for config_key, info_dict in info_config_map.items():
101 info_key = info_dict['info_key']
102 key_type = info_dict.get('value_type', 'str')
103 to_config = info_dict.get('to_config', True)
104
105 if not to_config:
106 continue
107
108 try:
109 config_value = kb_info_json[info_key]
110 except KeyError:
111 continue
112
113 if key_type.startswith('array'):
114 config_h_lines.append('')
115 config_h_lines.append(f'#ifndef {config_key}')
116 config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}')
117 config_h_lines.append(f'#endif // {config_key}')
118 elif key_type == 'bool':
119 if config_value:
120 config_h_lines.append('')
121 config_h_lines.append(f'#ifndef {config_key}')
122 config_h_lines.append(f'# define {config_key}')
123 config_h_lines.append(f'#endif // {config_key}')
124 elif key_type == 'mapping':
125 for key, value in config_value.items():
126 config_h_lines.append('')
127 config_h_lines.append(f'#ifndef {key}')
128 config_h_lines.append(f'# define {key} {value}')
129 config_h_lines.append(f'#endif // {key}')
130 else:
131 config_h_lines.append('')
132 config_h_lines.append(f'#ifndef {config_key}')
133 config_h_lines.append(f'# define {config_key} {config_value}')
134 config_h_lines.append(f'#endif // {config_key}')
257 135
258 if 'matrix_pins' in kb_info_json: 136 if 'matrix_pins' in kb_info_json:
259 config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) 137 config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
260 138
261 if 'usb' in kb_info_json:
262 config_h_lines.append(usb_properties(kb_info_json['usb']))
263
264 # Show the results 139 # Show the results
265 config_h = '\n'.join(config_h_lines) 140 config_h = '\n'.join(config_h_lines)
266 141
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index b262e3c66..af740f341 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -1,16 +1,37 @@
1"""Used by the make system to generate a rules.mk 1"""Used by the make system to generate a rules.mk
2""" 2"""
3from pathlib import Path
4
5from dotty_dict import dotty
3from milc import cli 6from milc import cli
4 7
5from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
6from qmk.info import info_json 9from qmk.info import _json_load, info_json
7from qmk.path import is_keyboard, normpath 10from qmk.path import is_keyboard, normpath
8 11
9info_to_rules = { 12
10 'board': 'BOARD', 13def process_mapping_rule(kb_info_json, rules_key, info_dict):
11 'bootloader': 'BOOTLOADER', 14 """Return the rules.mk line(s) for a mapping rule.
12 'processor': 'MCU', 15 """
13} 16 if not info_dict.get('to_c', True):
17 return None
18
19 info_key = info_dict['info_key']
20 key_type = info_dict.get('value_type', 'str')
21
22 try:
23 rules_value = kb_info_json[info_key]
24 except KeyError:
25 return None
26
27 if key_type == 'array':
28 return f'{rules_key} ?= {" ".join(rules_value)}'
29 elif key_type == 'bool':
30 return f'{rules_key} ?= {"on" if rules_value else "off"}'
31 elif key_type == 'mapping':
32 return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()])
33
34 return f'{rules_key} ?= {rules_value}'
14 35
15 36
16@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 37@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@@ -22,7 +43,6 @@ info_to_rules = {
22def generate_rules_mk(cli): 43def generate_rules_mk(cli):
23 """Generates a rules.mk file from info.json. 44 """Generates a rules.mk file from info.json.
24 """ 45 """
25 # Determine our keyboard(s)
26 if not cli.config.generate_rules_mk.keyboard: 46 if not cli.config.generate_rules_mk.keyboard:
27 cli.log.error('Missing paramater: --keyboard') 47 cli.log.error('Missing paramater: --keyboard')
28 cli.subcommands['info'].print_help() 48 cli.subcommands['info'].print_help()
@@ -32,16 +52,18 @@ def generate_rules_mk(cli):
32 cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard) 52 cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard)
33 return False 53 return False
34 54
35 # Build the info.json file 55 kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard))
36 kb_info_json = info_json(cli.config.generate_rules_mk.keyboard) 56 info_rules_map = _json_load(Path('data/mappings/info_rules.json'))
37 rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] 57 rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '']
38 58
39 # Bring in settings 59 # Iterate through the info_rules map to generate basic rules
40 for info_key, rule_key in info_to_rules.items(): 60 for rules_key, info_dict in info_rules_map.items():
41 if info_key in kb_info_json: 61 new_entry = process_mapping_rule(kb_info_json, rules_key, info_dict)
42 rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}') 62
63 if new_entry:
64 rules_mk_lines.append(new_entry)
43 65
44 # Find features that should be enabled 66 # Iterate through features to enable/disable them
45 if 'features' in kb_info_json: 67 if 'features' in kb_info_json:
46 for feature, enabled in kb_info_json['features'].items(): 68 for feature, enabled in kb_info_json['features'].items():
47 if feature == 'bootmagic_lite' and enabled: 69 if feature == 'bootmagic_lite' and enabled:
@@ -51,15 +73,6 @@ def generate_rules_mk(cli):
51 enabled = 'yes' if enabled else 'no' 73 enabled = 'yes' if enabled else 'no'
52 rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') 74 rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
53 75
54 # Set the LED driver
55 if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:
56 driver = kb_info_json['led_matrix']['driver']
57 rules_mk_lines.append(f'LED_MATRIX_DRIVER ?= {driver}')
58
59 # Add community layouts
60 if 'community_layouts' in kb_info_json:
61 rules_mk_lines.append(f'LAYOUTS ?= {" ".join(kb_info_json["community_layouts"])}')
62
63 # Show the results 76 # Show the results
64 rules_mk = '\n'.join(rules_mk_lines) + '\n' 77 rules_mk = '\n'.join(rules_mk_lines) + '\n'
65 78
@@ -72,7 +85,7 @@ def generate_rules_mk(cli):
72 if cli.args.quiet: 85 if cli.args.quiet:
73 print(cli.args.output) 86 print(cli.args.output)
74 else: 87 else:
75 cli.log.info('Wrote info_config.h to %s.', cli.args.output) 88 cli.log.info('Wrote rules.mk to %s.', cli.args.output)
76 89
77 else: 90 else:
78 print(rules_mk) 91 print(rules_mk)
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py
index cb9461356..8a177eb6b 100644
--- a/lib/python/qmk/constants.py
+++ b/lib/python/qmk/constants.py
@@ -27,7 +27,7 @@ ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop'
27LED_INDICATORS = { 27LED_INDICATORS = {
28 'caps_lock': 'LED_CAPS_LOCK_PIN', 28 'caps_lock': 'LED_CAPS_LOCK_PIN',
29 'num_lock': 'LED_NUM_LOCK_PIN', 29 'num_lock': 'LED_NUM_LOCK_PIN',
30 'scrol_lock': 'LED_SCROLL_LOCK_PIN', 30 'scroll_lock': 'LED_SCROLL_LOCK_PIN',
31} 31}
32 32
33# Constants that should match their counterparts in make 33# Constants that should match their counterparts in make
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index cc81f7a08..2accaba9e 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -5,57 +5,18 @@ from collections.abc import Mapping
5from glob import glob 5from glob import glob
6from pathlib import Path 6from pathlib import Path
7 7
8import hjson
8import jsonschema 9import jsonschema
10from dotty_dict import dotty
9from milc import cli 11from milc import cli
10 12
11from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, LED_INDICATORS 13from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
12from qmk.c_parse import find_layouts 14from qmk.c_parse import find_layouts
13from qmk.keyboard import config_h, rules_mk 15from qmk.keyboard import config_h, rules_mk
14from qmk.keymap import list_keymaps 16from qmk.keymap import list_keymaps
15from qmk.makefile import parse_rules_mk_file 17from qmk.makefile import parse_rules_mk_file
16from qmk.math import compute 18from qmk.math import compute
17 19
18led_matrix_properties = {
19 'driver_count': 'LED_DRIVER_COUNT',
20 'driver_addr1': 'LED_DRIVER_ADDR_1',
21 'driver_addr2': 'LED_DRIVER_ADDR_2',
22 'driver_addr3': 'LED_DRIVER_ADDR_3',
23 'driver_addr4': 'LED_DRIVER_ADDR_4',
24 'led_count': 'LED_DRIVER_LED_COUNT',
25 'timeout': 'ISSI_TIMEOUT',
26 'persistence': 'ISSI_PERSISTENCE'
27}
28
29rgblight_properties = {
30 'led_count': ('RGBLED_NUM', int),
31 'pin': ('RGB_DI_PIN', str),
32 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int),
33 'hue_steps': ('RGBLIGHT_HUE_STEP', int),
34 'saturation_steps': ('RGBLIGHT_SAT_STEP', int),
35 'brightness_steps': ('RGBLIGHT_VAL_STEP', int)
36}
37
38rgblight_toggles = {
39 'sleep': 'RGBLIGHT_SLEEP',
40 'split': 'RGBLIGHT_SPLIT',
41}
42
43rgblight_animations = {
44 'all': 'RGBLIGHT_ANIMATIONS',
45 'alternating': 'RGBLIGHT_EFFECT_ALTERNATING',
46 'breathing': 'RGBLIGHT_EFFECT_BREATHING',
47 'christmas': 'RGBLIGHT_EFFECT_CHRISTMAS',
48 'knight': 'RGBLIGHT_EFFECT_KNIGHT',
49 'rainbow_mood': 'RGBLIGHT_EFFECT_RAINBOW_MOOD',
50 'rainbow_swirl': 'RGBLIGHT_EFFECT_RAINBOW_SWIRL',
51 'rgb_test': 'RGBLIGHT_EFFECT_RGB_TEST',
52 'snake': 'RGBLIGHT_EFFECT_SNAKE',
53 'static_gradient': 'RGBLIGHT_EFFECT_STATIC_GRADIENT',
54 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE'
55}
56
57usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
58
59true_values = ['1', 'on', 'yes'] 20true_values = ['1', 'on', 'yes']
60false_values = ['0', 'off', 'no'] 21false_values = ['0', 'off', 'no']
61 22
@@ -101,7 +62,6 @@ def info_json(keyboard):
101 except jsonschema.ValidationError as e: 62 except jsonschema.ValidationError as e:
102 json_path = '.'.join([str(p) for p in e.absolute_path]) 63 json_path = '.'.join([str(p) for p in e.absolute_path])
103 cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) 64 cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message)
104 print(dir(e))
105 exit() 65 exit()
106 66
107 # Make sure we have at least one layout 67 # Make sure we have at least one layout
@@ -132,7 +92,7 @@ def _json_load(json_file):
132 Note: file must be a Path object. 92 Note: file must be a Path object.
133 """ 93 """
134 try: 94 try:
135 return json.load(json_file.open()) 95 return hjson.load(json_file.open())
136 96
137 except json.decoder.JSONDecodeError as e: 97 except json.decoder.JSONDecodeError as e:
138 cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) 98 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):
172 return validator(data) 132 return validator(data)
173 133
174 134
175def _extract_debounce(info_data, config_c):
176 """Handle debounce.
177 """
178 if 'debounce' in info_data and 'DEBOUNCE' in config_c:
179 _log_warning(info_data, 'Debounce is specified in both info.json and config.h, the config.h value wins.')
180
181 if 'DEBOUNCE' in config_c:
182 info_data['debounce'] = int(config_c['DEBOUNCE'])
183
184 return info_data
185
186
187def _extract_diode_direction(info_data, config_c):
188 """Handle the diode direction.
189 """
190 if 'diode_direction' in info_data and 'DIODE_DIRECTION' in config_c:
191 _log_warning(info_data, 'Diode direction is specified in both info.json and config.h, the config.h value wins.')
192
193 if 'DIODE_DIRECTION' in config_c:
194 info_data['diode_direction'] = config_c.get('DIODE_DIRECTION')
195
196 return info_data
197
198
199def _extract_indicators(info_data, config_c):
200 """Find the LED indicator information.
201 """
202 for json_key, config_key in LED_INDICATORS.items():
203 if json_key in info_data.get('indicators', []) and config_key in config_c:
204 _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.')
205
206 if 'indicators' not in info_data:
207 info_data['indicators'] = {}
208
209 if config_key in config_c:
210 if 'indicators' not in info_data:
211 info_data['indicators'] = {}
212
213 info_data['indicators'][json_key] = config_c.get(config_key)
214
215 return info_data
216
217
218def _extract_community_layouts(info_data, rules):
219 """Find the community layouts in rules.mk.
220 """
221 community_layouts = rules['LAYOUTS'].split() if 'LAYOUTS' in rules else []
222
223 if 'community_layouts' in info_data:
224 for layout in community_layouts:
225 if layout not in info_data['community_layouts']:
226 community_layouts.append(layout)
227
228 else:
229 info_data['community_layouts'] = community_layouts
230
231 return info_data
232
233
234def _extract_features(info_data, rules): 135def _extract_features(info_data, rules):
235 """Find all the features enabled in rules.mk. 136 """Find all the features enabled in rules.mk.
236 """ 137 """
@@ -267,78 +168,6 @@ def _extract_features(info_data, rules):
267 return info_data 168 return info_data
268 169
269 170
270def _extract_led_drivers(info_data, rules):
271 """Find all the LED drivers set in rules.mk.
272 """
273 if 'LED_MATRIX_DRIVER' in rules:
274 if 'led_matrix' not in info_data:
275 info_data['led_matrix'] = {}
276
277 if info_data['led_matrix'].get('driver'):
278 _log_warning(info_data, 'LED Matrix driver is specified in both info.json and rules.mk, the rules.mk value wins.')
279
280 info_data['led_matrix']['driver'] = rules['LED_MATRIX_DRIVER']
281
282 return info_data
283
284
285def _extract_led_matrix(info_data, config_c):
286 """Handle the led_matrix configuration.
287 """
288 led_matrix = info_data.get('led_matrix', {})
289
290 for json_key, config_key in led_matrix_properties.items():
291 if config_key in config_c:
292 if json_key in led_matrix:
293 _log_warning(info_data, 'LED Matrix: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
294
295 led_matrix[json_key] = config_c[config_key]
296
297
298def _extract_rgblight(info_data, config_c):
299 """Handle the rgblight configuration.
300 """
301 rgblight = info_data.get('rgblight', {})
302 animations = rgblight.get('animations', {})
303
304 if 'RGBLED_SPLIT' in config_c:
305 raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip()
306 rgblight['split_count'] = [int(i) for i in raw_split.split(',')]
307
308 for json_key, config_key_type in rgblight_properties.items():
309 config_key, config_type = config_key_type
310
311 if config_key in config_c:
312 if json_key in rgblight:
313 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
314
315 try:
316 rgblight[json_key] = config_type(config_c[config_key])
317 except ValueError as e:
318 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)
319
320 for json_key, config_key in rgblight_toggles.items():
321 if config_key in config_c and json_key in rgblight:
322 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.', json_key)
323
324 rgblight[json_key] = config_key in config_c
325
326 for json_key, config_key in rgblight_animations.items():
327 if config_key in config_c:
328 if json_key in animations:
329 _log_warning(info_data, 'RGB Light: animations: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
330
331 animations[json_key] = config_c[config_key]
332
333 if animations:
334 rgblight['animations'] = animations
335
336 if rgblight:
337 info_data['rgblight'] = rgblight
338
339 return info_data
340
341
342def _pin_name(pin): 171def _pin_name(pin):
343 """Returns the proper representation for a pin. 172 """Returns the proper representation for a pin.
344 """ 173 """
@@ -426,34 +255,59 @@ def _extract_matrix_info(info_data, config_c):
426 return info_data 255 return info_data
427 256
428 257
429def _extract_usb_info(info_data, config_c): 258def _extract_config_h(info_data):
430 """Populate the USB information. 259 """Pull some keyboard information from existing config.h files
431 """ 260 """
432 if 'usb' not in info_data: 261 config_c = config_h(info_data['keyboard_folder'])
433 info_data['usb'] = {}
434 262
435 for info_name, config_name in usb_properties.items(): 263 # Pull in data from the json map
436 if config_name in config_c: 264 dotty_info = dotty(info_data)
437 if info_name in info_data['usb']: 265 info_config_map = _json_load(Path('data/mappings/info_config.json'))
438 _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name))
439 266
440 info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper() 267 for config_key, info_dict in info_config_map.items():
268 info_key = info_dict['info_key']
269 key_type = info_dict.get('value_type', 'str')
441 270
442 return info_data 271 try:
272 if config_key in config_c and info_dict.get('to_json', True):
273 if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
274 _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key))
443 275
276 if key_type.startswith('array'):
277 if '.' in key_type:
278 key_type, array_type = key_type.split('.', 1)
279 else:
280 array_type = None
444 281
445def _extract_config_h(info_data): 282 config_value = config_c[config_key].replace('{', '').replace('}', '').strip()
446 """Pull some keyboard information from existing config.h files 283
447 """ 284 if array_type == 'int':
448 config_c = config_h(info_data['keyboard_folder']) 285 dotty_info[info_key] = list(map(int, config_value.split(',')))
286 else:
287 dotty_info[info_key] = config_value.split(',')
288
289 elif key_type == 'bool':
290 dotty_info[info_key] = config_c[config_key] in true_values
291
292 elif key_type == 'hex':
293 dotty_info[info_key] = '0x' + config_c[config_key][2:].upper()
449 294
450 _extract_debounce(info_data, config_c) 295 elif key_type == 'list':
451 _extract_diode_direction(info_data, config_c) 296 dotty_info[info_key] = config_c[config_key].split()
452 _extract_indicators(info_data, config_c) 297
298 elif key_type == 'int':
299 dotty_info[info_key] = int(config_c[config_key])
300
301 else:
302 dotty_info[info_key] = config_c[config_key]
303
304 except Exception as e:
305 _log_warning(info_data, f'{config_key}->{info_key}: {e}')
306
307 info_data.update(dotty_info)
308
309 # Pull data that easily can't be mapped in json
453 _extract_matrix_info(info_data, config_c) 310 _extract_matrix_info(info_data, config_c)
454 _extract_usb_info(info_data, config_c)
455 _extract_led_matrix(info_data, config_c)
456 _extract_rgblight(info_data, config_c)
457 311
458 return info_data 312 return info_data
459 313
@@ -462,21 +316,66 @@ def _extract_rules_mk(info_data):
462 """Pull some keyboard information from existing rules.mk files 316 """Pull some keyboard information from existing rules.mk files
463 """ 317 """
464 rules = rules_mk(info_data['keyboard_folder']) 318 rules = rules_mk(info_data['keyboard_folder'])
465 mcu = rules.get('MCU', info_data.get('processor')) 319 info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4'))
466 320
467 if mcu in CHIBIOS_PROCESSORS: 321 if info_data['processor'] in CHIBIOS_PROCESSORS:
468 arm_processor_rules(info_data, rules) 322 arm_processor_rules(info_data, rules)
469 323
470 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: 324 elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS:
471 avr_processor_rules(info_data, rules) 325 avr_processor_rules(info_data, rules)
472 326
473 else: 327 else:
474 cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) 328 cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor']))
475 unknown_processor_rules(info_data, rules) 329 unknown_processor_rules(info_data, rules)
476 330
477 _extract_community_layouts(info_data, rules) 331 # Pull in data from the json map
332 dotty_info = dotty(info_data)
333 info_rules_map = _json_load(Path('data/mappings/info_rules.json'))
334
335 for rules_key, info_dict in info_rules_map.items():
336 info_key = info_dict['info_key']
337 key_type = info_dict.get('value_type', 'str')
338
339 try:
340 if rules_key in rules and info_dict.get('to_json', True):
341 if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
342 _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key))
343
344 if key_type.startswith('array'):
345 if '.' in key_type:
346 key_type, array_type = key_type.split('.', 1)
347 else:
348 array_type = None
349
350 rules_value = rules[rules_key].replace('{', '').replace('}', '').strip()
351
352 if array_type == 'int':
353 dotty_info[info_key] = list(map(int, rules_value.split(',')))
354 else:
355 dotty_info[info_key] = rules_value.split(',')
356
357 elif key_type == 'list':
358 dotty_info[info_key] = rules[rules_key].split()
359
360 elif key_type == 'bool':
361 dotty_info[info_key] = rules[rules_key] in true_values
362
363 elif key_type == 'hex':
364 dotty_info[info_key] = '0x' + rules[rules_key][2:].upper()
365
366 elif key_type == 'int':
367 dotty_info[info_key] = int(rules[rules_key])
368
369 else:
370 dotty_info[info_key] = rules[rules_key]
371
372 except Exception as e:
373 _log_warning(info_data, f'{rules_key}->{info_key}: {e}')
374
375 info_data.update(dotty_info)
376
377 # Merge in config values that can't be easily mapped
478 _extract_features(info_data, rules) 378 _extract_features(info_data, rules)
479 _extract_led_drivers(info_data, rules)
480 379
481 return info_data 380 return info_data
482 381
@@ -565,23 +464,7 @@ def arm_processor_rules(info_data, rules):
565 info_data['processor_type'] = 'arm' 464 info_data['processor_type'] = 'arm'
566 info_data['protocol'] = 'ChibiOS' 465 info_data['protocol'] = 'ChibiOS'
567 466
568 if 'MCU' in rules: 467 if 'bootloader' not in info_data:
569 if 'processor' in info_data:
570 _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.')
571
572 info_data['processor'] = rules['MCU']
573
574 elif 'processor' not in info_data:
575 info_data['processor'] = 'unknown'
576
577 if 'BOOTLOADER' in rules:
578 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
579 # if 'bootloader' in info_data:
580 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
581
582 info_data['bootloader'] = rules['BOOTLOADER']
583
584 else:
585 if 'STM32' in info_data['processor']: 468 if 'STM32' in info_data['processor']:
586 info_data['bootloader'] = 'stm32-dfu' 469 info_data['bootloader'] = 'stm32-dfu'
587 else: 470 else:
@@ -594,12 +477,6 @@ def arm_processor_rules(info_data, rules):
594 elif 'ARM_ATSAM' in rules: 477 elif 'ARM_ATSAM' in rules:
595 info_data['platform'] = 'ARM_ATSAM' 478 info_data['platform'] = 'ARM_ATSAM'
596 479
597 if 'BOARD' in rules:
598 if 'board' in info_data:
599 _log_warning(info_data, 'Board is specified in both info.json and rules.mk, the rules.mk value wins.')
600
601 info_data['board'] = rules['BOARD']
602
603 return info_data 480 return info_data
604 481
605 482
@@ -607,26 +484,10 @@ def avr_processor_rules(info_data, rules):
607 """Setup the default info for an AVR board. 484 """Setup the default info for an AVR board.
608 """ 485 """
609 info_data['processor_type'] = 'avr' 486 info_data['processor_type'] = 'avr'
610 info_data['bootloader'] = rules['BOOTLOADER'] if 'BOOTLOADER' in rules else 'atmel-dfu'
611 info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown' 487 info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown'
612 info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA' 488 info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA'
613 489
614 if 'MCU' in rules: 490 if 'bootloader' not in info_data:
615 if 'processor' in info_data:
616 _log_warning(info_data, 'Processor/MCU is specified in both info.json and rules.mk, the rules.mk value wins.')
617
618 info_data['processor'] = rules['MCU']
619
620 elif 'processor' not in info_data:
621 info_data['processor'] = 'unknown'
622
623 if 'BOOTLOADER' in rules:
624 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
625 # if 'bootloader' in info_data:
626 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
627
628 info_data['bootloader'] = rules['BOOTLOADER']
629 else:
630 info_data['bootloader'] = 'atmel-dfu' 491 info_data['bootloader'] = 'atmel-dfu'
631 492
632 # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk: 493 # FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk:
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index f889833d0..3efeddb85 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():
230 check_returncode(result) 230 check_returncode(result)
231 assert 'Breathing center: 1.2' in result.stdout 231 assert 'Breathing center: 1.2' in result.stdout
232 assert 'Breathing max: 127' in result.stdout 232 assert 'Breathing max: 127' in result.stdout
233
234
235def test_generate_config_h():
236 result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic')
237 check_returncode(result)
238 assert '# define DEVICE_VER 0x0001' in result.stdout
239 assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout
240 assert '# define DIODE_DIRECTION COL2ROW' in result.stdout
241 assert '# define MANUFACTURER none' in result.stdout
242 assert '# define PRODUCT handwired/pytest/basic' in result.stdout
243 assert '# define PRODUCT_ID 0x6465' in result.stdout
244 assert '# define VENDOR_ID 0xFEED' in result.stdout
245 assert '# define MATRIX_COLS 1' in result.stdout
246 assert '# define MATRIX_COL_PINS { F4 }' in result.stdout
247 assert '# define MATRIX_ROWS 1' in result.stdout
248 assert '# define MATRIX_ROW_PINS { F5 }' in result.stdout
249
250
251def test_generate_rules_mk():
252 result = check_subcommand('generate-rules-mk', '-kb', 'handwired/pytest/basic')
253 check_returncode(result)
254 assert 'BOOTLOADER ?= atmel-dfu' in result.stdout
255 assert 'MCU ?= atmega32u4' in result.stdout
256
257
258def test_generate_layouts():
259 result = check_subcommand('generate-layouts', '-kb', 'handwired/pytest/basic')
260 check_returncode(result)
261 assert '#define LAYOUT_custom(k0A) {' in result.stdout
diff --git a/requirements.txt b/requirements.txt
index f4d43da8d..27a6baed9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@
2appdirs 2appdirs
3argcomplete 3argcomplete
4colorama 4colorama
5dotty-dict
5hjson 6hjson
6jsonschema 7jsonschema
7milc 8milc