Skip to content

Commit a25dd58

Browse files
authored
QMK CLI and JSON keymap support (qmk#6176)
* Script to generate keymap.c from JSON file. * Support for keymap.json * Add a warning about the keymap.c getting overwritten. * Fix keymap generating * Install the python deps * Flesh out more of the python environment * Remove defunct json2keymap * Style everything with yapf * Polish up python support * Hide json keymap.c into the .build dir * Polish up qmk-compile-json * Make milc work with positional arguments * Fix a couple small things * Fix some errors and make the CLI more understandable * Make the qmk wrapper more robust * Add basic QMK Doctor * Clean up docstrings and flesh them out as needed * remove unused compile_firmware() function
1 parent 7ba82cb commit a25dd58

34 files changed

+1988
-83
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ insert_final_newline = true
1616
trim_trailing_whitespace = false
1717
indent_size = 4
1818

19+
[{qmk,*.py}]
20+
charset = utf-8
21+
max_line_length = 200
22+
1923
# Make these match what we have in .gitattributes
2024
[*.mk]
2125
end_of_line = lf

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ util/Win_Check_Output.txt
7070
secrets.tar
7171
id_rsa_*
7272
/.vs
73+
74+
# python things
75+
__pycache__

bin/qmk

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
"""CLI wrapper for running QMK commands.
3+
"""
4+
import os
5+
import subprocess
6+
import sys
7+
from glob import glob
8+
from time import strftime
9+
from importlib import import_module
10+
from importlib.util import find_spec
11+
12+
# Add the QMK python libs to our path
13+
script_dir = os.path.dirname(os.path.realpath(__file__))
14+
qmk_dir = os.path.abspath(os.path.join(script_dir, '..'))
15+
python_lib_dir = os.path.abspath(os.path.join(qmk_dir, 'lib', 'python'))
16+
sys.path.append(python_lib_dir)
17+
18+
# Change to the root of our checkout
19+
os.environ['ORIG_CWD'] = os.getcwd()
20+
os.chdir(qmk_dir)
21+
22+
# Make sure our modules have been setup
23+
with open('requirements.txt', 'r') as fd:
24+
for line in fd.readlines():
25+
line = line.strip().replace('<', '=').replace('>', '=')
26+
27+
if line[0] == '#':
28+
continue
29+
30+
if '#' in line:
31+
line = line.split('#')[0]
32+
33+
module = line.split('=')[0] if '=' in line else line
34+
if not find_spec(module):
35+
print('Your QMK build environment is not fully setup!\n')
36+
print('Please run `./util/qmk_install.sh` to setup QMK.')
37+
exit(255)
38+
39+
# Figure out our version
40+
command = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags']
41+
result = subprocess.run(command, text=True, capture_output=True)
42+
43+
if result.returncode == 0:
44+
os.environ['QMK_VERSION'] = 'QMK ' + result.stdout.strip()
45+
else:
46+
os.environ['QMK_VERSION'] = 'QMK ' + strftime('%Y-%m-%d-%H:%M:%S')
47+
48+
# Setup the CLI
49+
import milc
50+
milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}ψ{style_reset_all}'
51+
52+
# If we were invoked as `qmk <cmd>` massage sys.argv into `qmk-<cmd>`.
53+
# This means we can't accept arguments to the qmk script itself.
54+
script_name = os.path.basename(sys.argv[0])
55+
if script_name == 'qmk':
56+
if len(sys.argv) == 1:
57+
milc.cli.log.error('No subcommand specified!\n')
58+
59+
if len(sys.argv) == 1 or sys.argv[1] in ['-h', '--help']:
60+
milc.cli.echo('usage: qmk <subcommand> [...]')
61+
milc.cli.echo('\nsubcommands:')
62+
subcommands = glob(os.path.join(qmk_dir, 'bin', 'qmk-*'))
63+
for subcommand in sorted(subcommands):
64+
subcommand = os.path.basename(subcommand).split('-', 1)[1]
65+
milc.cli.echo('\t%s', subcommand)
66+
milc.cli.echo('\nqmk <subcommand> --help for more information')
67+
exit(1)
68+
69+
if sys.argv[1] in ['-V', '--version']:
70+
milc.cli.echo(os.environ['QMK_VERSION'])
71+
exit(0)
72+
73+
sys.argv[0] = script_name = '-'.join((script_name, sys.argv[1]))
74+
del sys.argv[1]
75+
76+
# Look for which module to import
77+
if script_name == 'qmk':
78+
milc.cli.print_help()
79+
exit(0)
80+
elif not script_name.startswith('qmk-'):
81+
milc.cli.log.error('Invalid symlink, must start with "qmk-": %s', script_name)
82+
else:
83+
subcommand = script_name.replace('-', '.').replace('_', '.').split('.')
84+
subcommand.insert(1, 'cli')
85+
subcommand = '.'.join(subcommand)
86+
87+
try:
88+
import_module(subcommand)
89+
except ModuleNotFoundError as e:
90+
if e.__class__.__name__ != subcommand:
91+
raise
92+
93+
milc.cli.log.error('Invalid subcommand! Could not import %s.', subcommand)
94+
exit(1)
95+
96+
if __name__ == '__main__':
97+
milc.cli()

bin/qmk-compile-json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
qmk

bin/qmk-doctor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
qmk

bin/qmk-hello

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
qmk

bin/qmk-json-keymap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
qmk

build_json.mk

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Look for a json keymap file
2+
ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.json)","")
3+
KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c
4+
KEYMAP_JSON := $(MAIN_KEYMAP_PATH_5)/keymap.json
5+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5)
6+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.json)","")
7+
KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c
8+
KEYMAP_JSON := $(MAIN_KEYMAP_PATH_4)/keymap.json
9+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4)
10+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.json)","")
11+
KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c
12+
KEYMAP_JSON := $(MAIN_KEYMAP_PATH_3)/keymap.json
13+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3)
14+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.json)","")
15+
KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c
16+
KEYMAP_JSON := $(MAIN_KEYMAP_PATH_2)/keymap.json
17+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2)
18+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.json)","")
19+
KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c
20+
KEYMAP_JSON := $(MAIN_KEYMAP_PATH_1)/keymap.json
21+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1)
22+
endif
23+
24+
# Generate the keymap.c
25+
ifneq ("$(KEYMAP_JSON)","")
26+
_ = $(shell bin/qmk-json-keymap -f $(KEYMAP_JSON) -o $(KEYMAP_C))
27+
endif

build_keyboard.mk

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -98,31 +98,38 @@ MAIN_KEYMAP_PATH_3 := $(KEYBOARD_PATH_3)/keymaps/$(KEYMAP)
9898
MAIN_KEYMAP_PATH_4 := $(KEYBOARD_PATH_4)/keymaps/$(KEYMAP)
9999
MAIN_KEYMAP_PATH_5 := $(KEYBOARD_PATH_5)/keymaps/$(KEYMAP)
100100

101-
ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","")
102-
-include $(MAIN_KEYMAP_PATH_5)/rules.mk
103-
KEYMAP_C := $(MAIN_KEYMAP_PATH_5)/keymap.c
104-
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5)
105-
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.c)","")
106-
-include $(MAIN_KEYMAP_PATH_4)/rules.mk
107-
KEYMAP_C := $(MAIN_KEYMAP_PATH_4)/keymap.c
108-
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4)
109-
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.c)","")
110-
-include $(MAIN_KEYMAP_PATH_3)/rules.mk
111-
KEYMAP_C := $(MAIN_KEYMAP_PATH_3)/keymap.c
112-
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3)
113-
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.c)","")
114-
-include $(MAIN_KEYMAP_PATH_2)/rules.mk
115-
KEYMAP_C := $(MAIN_KEYMAP_PATH_2)/keymap.c
116-
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2)
117-
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.c)","")
118-
-include $(MAIN_KEYMAP_PATH_1)/rules.mk
119-
KEYMAP_C := $(MAIN_KEYMAP_PATH_1)/keymap.c
120-
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1)
121-
else ifneq ($(LAYOUTS),)
122-
include build_layout.mk
123-
else
124-
$(error Could not find keymap)
125-
# this state should never be reached
101+
# Check for keymap.json first, so we can regenerate keymap.c
102+
include build_json.mk
103+
104+
ifeq ("$(wildcard $(KEYMAP_PATH))", "")
105+
# Look through the possible keymap folders until we find a matching keymap.c
106+
ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","")
107+
-include $(MAIN_KEYMAP_PATH_5)/rules.mk
108+
KEYMAP_C := $(MAIN_KEYMAP_PATH_5)/keymap.c
109+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5)
110+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.c)","")
111+
-include $(MAIN_KEYMAP_PATH_4)/rules.mk
112+
KEYMAP_C := $(MAIN_KEYMAP_PATH_4)/keymap.c
113+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4)
114+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.c)","")
115+
-include $(MAIN_KEYMAP_PATH_3)/rules.mk
116+
KEYMAP_C := $(MAIN_KEYMAP_PATH_3)/keymap.c
117+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3)
118+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.c)","")
119+
-include $(MAIN_KEYMAP_PATH_2)/rules.mk
120+
KEYMAP_C := $(MAIN_KEYMAP_PATH_2)/keymap.c
121+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2)
122+
else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.c)","")
123+
-include $(MAIN_KEYMAP_PATH_1)/rules.mk
124+
KEYMAP_C := $(MAIN_KEYMAP_PATH_1)/keymap.c
125+
KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1)
126+
else ifneq ($(LAYOUTS),)
127+
# If we haven't found a keymap yet fall back to community layouts
128+
include build_layout.mk
129+
else
130+
$(error Could not find keymap)
131+
# this state should never be reached
132+
endif
126133
endif
127134

128135
ifeq ($(strip $(CTPC)), yes)
@@ -313,7 +320,6 @@ ifneq ("$(wildcard $(USER_PATH)/config.h)","")
313320
CONFIG_H += $(USER_PATH)/config.h
314321
endif
315322

316-
317323
# Object files directory
318324
# To put object files in current directory, use a dot (.), do NOT make
319325
# this an empty or blank macro!
@@ -323,7 +329,7 @@ ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","")
323329
CONFIG_H += $(KEYMAP_PATH)/config.h
324330
endif
325331

326-
# # project specific files
332+
# project specific files
327333
SRC += $(KEYBOARD_SRC) \
328334
$(KEYMAP_C) \
329335
$(QUANTUM_SRC)

docs/_summary.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
* [QMK Basics](README.md)
1010
* [QMK Introduction](getting_started_introduction.md)
11+
* [QMK CLI](cli.md)
1112
* [Contributing to QMK](contributing.md)
1213
* [How to Use Github](getting_started_github.md)
1314
* [Getting Help](getting_started_getting_help.md)
@@ -34,13 +35,16 @@
3435
* [Keyboard Guidelines](hardware_keyboard_guidelines.md)
3536
* [Config Options](config_options.md)
3637
* [Keycodes](keycodes.md)
38+
* [Coding Conventions - C](coding_conventions_c.md)
39+
* [Coding Conventions - Python](coding_conventions_python.md)
3740
* [Documentation Best Practices](documentation_best_practices.md)
3841
* [Documentation Templates](documentation_templates.md)
3942
* [Glossary](reference_glossary.md)
4043
* [Unit Testing](unit_testing.md)
4144
* [Useful Functions](ref_functions.md)
4245
* [Configurator Support](reference_configurator_support.md)
4346
* [info.json Format](reference_info_json.md)
47+
* [Python Development](python_development.md)
4448

4549
* [Features](features.md)
4650
* [Basic Keycodes](keycodes_basic.md)

0 commit comments

Comments
 (0)