forked from mirrors/qmk_firmware
Compare commits
30 Commits
0.14.10
...
qmk_compil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81b17125eb | ||
|
|
6f4742bde6 | ||
|
|
4fadb98a02 | ||
|
|
335dd3c5c3 | ||
|
|
dcbfdb5cfc | ||
|
|
823a74ebae | ||
|
|
08b0ecb175 | ||
|
|
b4e18c9019 | ||
|
|
07b8035ba9 | ||
|
|
4f20c94b97 | ||
|
|
ea862e24f6 | ||
|
|
7fe506006e | ||
|
|
d3ed6fa8a4 | ||
|
|
50fdb2a52c | ||
|
|
596c4a1f87 | ||
|
|
b46064a891 | ||
|
|
92e606b927 | ||
|
|
5fbfab1f3b | ||
|
|
532bff7b6c | ||
|
|
d227c8692a | ||
|
|
55d6956553 | ||
|
|
8800adc533 | ||
|
|
7209d7cca9 | ||
|
|
77a93fec79 | ||
|
|
c729df09ca | ||
|
|
566d598516 | ||
|
|
f155865804 | ||
|
|
1ac3b3e9b6 | ||
|
|
7b8cdfc19d | ||
|
|
9fe7b5307a |
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@@ -22,7 +22,6 @@ keymap:
|
|||||||
via:
|
via:
|
||||||
- keyboards/**/keymaps/via/*
|
- keyboards/**/keymaps/via/*
|
||||||
cli:
|
cli:
|
||||||
- bin/qmk
|
|
||||||
- requirements.txt
|
- requirements.txt
|
||||||
- lib/python/**/*
|
- lib/python/**/*
|
||||||
python:
|
python:
|
||||||
|
|||||||
1
.github/workflows/cli.yml
vendored
1
.github/workflows/cli.yml
vendored
@@ -8,7 +8,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'lib/python/**'
|
- 'lib/python/**'
|
||||||
- 'bin/qmk'
|
|
||||||
- 'requirements.txt'
|
- 'requirements.txt'
|
||||||
- '.github/workflows/cli.yml'
|
- '.github/workflows/cli.yml'
|
||||||
|
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -30,11 +30,7 @@ endif
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# Determine which qmk cli to use
|
# Determine which qmk cli to use
|
||||||
ifeq (,$(shell which qmk))
|
QMK_BIN := qmk
|
||||||
QMK_BIN = bin/qmk
|
|
||||||
else
|
|
||||||
QMK_BIN = qmk
|
|
||||||
endif
|
|
||||||
|
|
||||||
# avoid 'Entering|Leaving directory' messages
|
# avoid 'Entering|Leaving directory' messages
|
||||||
MAKEFLAGS += --no-print-directory
|
MAKEFLAGS += --no-print-directory
|
||||||
|
|||||||
58
bin/qmk
58
bin/qmk
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""CLI wrapper for running QMK commands.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Add the QMK python libs to our path
|
|
||||||
script_dir = Path(os.path.realpath(__file__)).parent
|
|
||||||
qmk_dir = script_dir.parent
|
|
||||||
python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve()
|
|
||||||
sys.path.append(str(python_lib_dir))
|
|
||||||
|
|
||||||
# Setup the CLI
|
|
||||||
import milc # noqa
|
|
||||||
|
|
||||||
milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}'
|
|
||||||
|
|
||||||
|
|
||||||
@milc.cli.entrypoint('QMK Helper Script')
|
|
||||||
def qmk_main(cli):
|
|
||||||
"""The function that gets run when no subcommand is provided.
|
|
||||||
"""
|
|
||||||
cli.print_help()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Setup our environment and then call the CLI entrypoint.
|
|
||||||
"""
|
|
||||||
# Change to the root of our checkout
|
|
||||||
os.environ['ORIG_CWD'] = os.getcwd()
|
|
||||||
os.environ['DEPRECATED_BIN_QMK'] = '1'
|
|
||||||
os.chdir(qmk_dir)
|
|
||||||
|
|
||||||
print('Warning: The bin/qmk script is being deprecated. Please install the QMK CLI: python3 -m pip install qmk', file=sys.stderr)
|
|
||||||
|
|
||||||
# Import the subcommands
|
|
||||||
import milc.subcommand.config # noqa
|
|
||||||
import qmk.cli # noqa
|
|
||||||
|
|
||||||
# Execute
|
|
||||||
return_code = milc.cli()
|
|
||||||
|
|
||||||
if return_code is False:
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
elif return_code is not True and isinstance(return_code, int):
|
|
||||||
if return_code < 0 or return_code > 255:
|
|
||||||
milc.cli.log.error('Invalid return_code: %d', return_code)
|
|
||||||
exit(255)
|
|
||||||
|
|
||||||
exit(return_code)
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -115,6 +115,7 @@ include $(INFO_RULES_MK)
|
|||||||
# Check for keymap.json first, so we can regenerate keymap.c
|
# Check for keymap.json first, so we can regenerate keymap.c
|
||||||
include build_json.mk
|
include build_json.mk
|
||||||
|
|
||||||
|
# Pull in keymap level rules.mk
|
||||||
ifeq ("$(wildcard $(KEYMAP_PATH))", "")
|
ifeq ("$(wildcard $(KEYMAP_PATH))", "")
|
||||||
# Look through the possible keymap folders until we find a matching keymap.c
|
# Look through the possible keymap folders until we find a matching keymap.c
|
||||||
ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","")
|
ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","")
|
||||||
@@ -345,6 +346,7 @@ ifeq ("$(USER_NAME)","")
|
|||||||
endif
|
endif
|
||||||
USER_PATH := users/$(USER_NAME)
|
USER_PATH := users/$(USER_NAME)
|
||||||
|
|
||||||
|
# Pull in user level rules.mk
|
||||||
-include $(USER_PATH)/rules.mk
|
-include $(USER_PATH)/rules.mk
|
||||||
ifneq ("$(wildcard $(USER_PATH)/config.h)","")
|
ifneq ("$(wildcard $(USER_PATH)/config.h)","")
|
||||||
CONFIG_H += $(USER_PATH)/config.h
|
CONFIG_H += $(USER_PATH)/config.h
|
||||||
@@ -356,6 +358,23 @@ endif
|
|||||||
# Disable features that a keyboard doesn't support
|
# Disable features that a keyboard doesn't support
|
||||||
-include disable_features.mk
|
-include disable_features.mk
|
||||||
|
|
||||||
|
# Pull in post_rules.mk files from all our subfolders
|
||||||
|
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/post_rules.mk)","")
|
||||||
|
include $(KEYBOARD_PATH_1)/post_rules.mk
|
||||||
|
endif
|
||||||
|
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/post_rules.mk)","")
|
||||||
|
include $(KEYBOARD_PATH_2)/post_rules.mk
|
||||||
|
endif
|
||||||
|
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/post_rules.mk)","")
|
||||||
|
include $(KEYBOARD_PATH_3)/post_rules.mk
|
||||||
|
endif
|
||||||
|
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/post_rules.mk)","")
|
||||||
|
include $(KEYBOARD_PATH_4)/post_rules.mk
|
||||||
|
endif
|
||||||
|
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/post_rules.mk)","")
|
||||||
|
include $(KEYBOARD_PATH_5)/post_rules.mk
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","")
|
ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","")
|
||||||
CONFIG_H += $(KEYMAP_PATH)/config.h
|
CONFIG_H += $(KEYMAP_PATH)/config.h
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -118,54 +118,6 @@ This command lets you configure the behavior of QMK. For the full `qmk config` d
|
|||||||
qmk config [-ro] [config_token1] [config_token2] [...] [config_tokenN]
|
qmk config [-ro] [config_token1] [config_token2] [...] [config_tokenN]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `qmk console`
|
|
||||||
|
|
||||||
This command lets you connect to keyboard consoles to get debugging messages. It only works if your keyboard firmware has been compiled with `CONSOLE_ENABLE=yes`.
|
|
||||||
|
|
||||||
**Usage**:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console [-d <pid>:<vid>[:<index>]] [-l] [-n] [-t] [-w <seconds>]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Examples**:
|
|
||||||
|
|
||||||
Connect to all available keyboards and show their console messages:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console
|
|
||||||
```
|
|
||||||
|
|
||||||
List all devices:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console -l
|
|
||||||
```
|
|
||||||
|
|
||||||
Show only messages from clueboard/66/rev3 keyboards:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console -d C1ED:2370
|
|
||||||
```
|
|
||||||
|
|
||||||
Show only messages from the second clueboard/66/rev3:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console -d C1ED:2370:2
|
|
||||||
```
|
|
||||||
|
|
||||||
Show timestamps and VID:PID instead of names:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console -n -t
|
|
||||||
```
|
|
||||||
|
|
||||||
Disable bootloader messages:
|
|
||||||
|
|
||||||
```
|
|
||||||
qmk console --no-bootloaders
|
|
||||||
```
|
|
||||||
|
|
||||||
## `qmk doctor`
|
## `qmk doctor`
|
||||||
|
|
||||||
This command examines your environment and alerts you to potential build or flash problems. It can fix many of them if you want it to.
|
This command examines your environment and alerts you to potential build or flash problems. It can fix many of them if you want it to.
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ enum my_keycodes {
|
|||||||
|
|
||||||
Before opening a pull request, you can preview your changes if you have set up the development environment by running this command from the `qmk_firmware/` folder:
|
Before opening a pull request, you can preview your changes if you have set up the development environment by running this command from the `qmk_firmware/` folder:
|
||||||
|
|
||||||
./bin/qmk docs
|
qmk docs
|
||||||
|
|
||||||
or if you only have Python 3 installed:
|
or if you only have Python 3 installed:
|
||||||
|
|
||||||
|
|||||||
@@ -51,25 +51,6 @@ Wir suchen nach Freiwilligen, die ein `qmk`-Package für weitere Betriebssysteme
|
|||||||
* Installiere mit einem [virtualenv](https://virtualenv.pypa.io/en/latest/).
|
* Installiere mit einem [virtualenv](https://virtualenv.pypa.io/en/latest/).
|
||||||
* Weise den User an, die Umgebungs-Variable `QMK_HOME` zu setzen, um die Firmware-Quelle anders einzustellen als `~/qmk_firmware`.
|
* Weise den User an, die Umgebungs-Variable `QMK_HOME` zu setzen, um die Firmware-Quelle anders einzustellen als `~/qmk_firmware`.
|
||||||
|
|
||||||
# Lokale CLI
|
|
||||||
|
|
||||||
Wenn Du die globale CLI nicht verwenden möchtest, beinhaltet `qmk_firmware` auch eine lokale CLI. Du kannst sie hier finden: `qmk_firmware/bin/qmk`. Du kannst den `qmk`-Befehl aus irgendeinem Datei-Verzeichnis ausführen und es wird immer auf dieser Kopie von `qmk_firmware` arbeiten.
|
|
||||||
|
|
||||||
**Beispiel**:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ~/qmk_firmware/bin/qmk hello
|
|
||||||
Ψ Hello, World!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Einschränkungen der lokalen CLI
|
|
||||||
|
|
||||||
Hier ein Vergleich mit der globalen CLI:
|
|
||||||
|
|
||||||
* Die lokale CLI unterstützt kein `qmk setup` oder `qmk clone`.
|
|
||||||
* Die lokale CLI arbeitet immer innerhalb der selben `qmk_firmware`-Verzeichnisstruktur, auch wenn Du mehrere Repositories geklont hast.
|
|
||||||
* Die lokale CLI läuft nicht in einer virtualenv. Daher ist es möglich, dass Abhängigkeiten (dependencies) miteinander in Konflikt kommen/stehen.
|
|
||||||
|
|
||||||
# CLI-Befehle
|
# CLI-Befehle
|
||||||
|
|
||||||
## `qmk compile`
|
## `qmk compile`
|
||||||
|
|||||||
@@ -48,25 +48,6 @@ Nous recherchons des gens pour créer et maintenir un paquet `qmk` pour plus de
|
|||||||
* Installez en utilisant un virtualenv
|
* Installez en utilisant un virtualenv
|
||||||
* Expliquez à l'utilisateur de définir la variable d'environnement `QMK_Home` pour "check out" les sources du firmware à un autre endroit que `~/qmk_firmware`.
|
* Expliquez à l'utilisateur de définir la variable d'environnement `QMK_Home` pour "check out" les sources du firmware à un autre endroit que `~/qmk_firmware`.
|
||||||
|
|
||||||
# CLI locale
|
|
||||||
|
|
||||||
Si vous ne voulez pas utiliser la CLI globale, il y a une CLI locale empaquetée avec `qmk_firmware`. Vous pouvez le trouver dans `qmk_firmware/bin/qmk`. Vous pouvez lancer la commande `qmk` depuis n'importe quel répertoire et elle fonctionnera toujours sur cette copie de `qmk_firmware`.
|
|
||||||
|
|
||||||
**Exemple**:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ~/qmk_firmware/bin/qmk hello
|
|
||||||
Ψ Hello, World!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Limitations de la CLI locale
|
|
||||||
|
|
||||||
Il y a quelques limitations à la CLI locale comparé à la globale:
|
|
||||||
|
|
||||||
* La CLI locale ne supporte pas `qmk setup` ou `qmk clone`
|
|
||||||
* La CLI locale n'opère pas sur le même arbre `qmk_firmware`, même si vous avez plusieurs dépôts clonés.
|
|
||||||
* La CLI locale ne s'exécute pas dans un virtualenv, donc il y a des risques que des dépendances seront en conflit
|
|
||||||
|
|
||||||
# Les commandes CLI
|
# Les commandes CLI
|
||||||
|
|
||||||
## `qmk compile`
|
## `qmk compile`
|
||||||
|
|||||||
@@ -144,10 +144,38 @@ The `rules.mk` file can also be placed in a sub-folder, and its reading order is
|
|||||||
* `keyboards/top_folder/sub_1/sub_2/sub_3/sub_4/rules.mk`
|
* `keyboards/top_folder/sub_1/sub_2/sub_3/sub_4/rules.mk`
|
||||||
* `keyboards/top_folder/keymaps/a_keymap/rules.mk`
|
* `keyboards/top_folder/keymaps/a_keymap/rules.mk`
|
||||||
* `users/a_user_folder/rules.mk`
|
* `users/a_user_folder/rules.mk`
|
||||||
|
* `keyboards/top_folder/sub_1/sub_2/sub_3/sub_4/post_rules.mk`
|
||||||
|
* `keyboards/top_folder/sub_1/sub_2/sub_3/post_rules.mk`
|
||||||
|
* `keyboards/top_folder/sub_1/sub_2/post_rules.mk`
|
||||||
|
* `keyboards/top_folder/sub_1/post_rules.mk`
|
||||||
|
* `keyboards/top_folder/post_rules.mk`
|
||||||
* `common_features.mk`
|
* `common_features.mk`
|
||||||
|
|
||||||
Many of the settings written in the `rules.mk` file are interpreted by `common_features.mk`, which sets the necessary source files and compiler options.
|
Many of the settings written in the `rules.mk` file are interpreted by `common_features.mk`, which sets the necessary source files and compiler options.
|
||||||
|
|
||||||
|
The `post_rules.mk` file can interpret `features` of a keyboard-level before `common_features.mk`. For example, when your designed keyboard has the option to implement backlighting or underglow using rgblight.c, writing the following in the `post_rules.mk` makes it easier for the user to configure the `rules.mk`.
|
||||||
|
|
||||||
|
* `keyboards/top_folder/keymaps/a_keymap/rules.mk`
|
||||||
|
```makefile
|
||||||
|
# Please set the following according to the selection of the hardware implementation option.
|
||||||
|
RGBLED_OPTION_TYPE = backlight ## none, backlight or underglow
|
||||||
|
```
|
||||||
|
* `keyboards/top_folder/post_rules.mk`
|
||||||
|
```makefile
|
||||||
|
ifeq ($(filter $(strip $(RGBLED_OPTION_TYPE))x, nonex backlightx underglowx x),)
|
||||||
|
$(error unknown RGBLED_OPTION_TYPE value "$(RGBLED_OPTION_TYPE)")
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(RGBLED_OPTION_TYPE)),backlight)
|
||||||
|
RGBLIGHT_ENABLE = yes
|
||||||
|
OPT_DEFS += -DRGBLED_NUM=30
|
||||||
|
endif
|
||||||
|
ifeq ($(strip $(RGBLED_OPTION_TYPE)),underglow)
|
||||||
|
RGBLIGHT_ENABLE = yes
|
||||||
|
OPT_DEFS += -DRGBLED_NUM=6
|
||||||
|
endif
|
||||||
|
```
|
||||||
|
|
||||||
?> See `build_keyboard.mk` and `common_features.mk` for more details.
|
?> See `build_keyboard.mk` and `common_features.mk` for more details.
|
||||||
|
|
||||||
### `<keyboard_name.c>`
|
### `<keyboard_name.c>`
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
Run commands in the root directory of this repository.
|
Run commands in the root directory of this repository.
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/qmk compile && sudo dfu-programmer atmega32u4 erase && sudo dfu-programmer atmega32u4 flash ./dztech_dz65rgb_v2_jumper149.hex && sudo dfu-programmer atmega32u4 reset
|
qmk compile && sudo dfu-programmer atmega32u4 erase && sudo dfu-programmer atmega32u4 flash ./dztech_dz65rgb_v2_jumper149.hex && sudo dfu-programmer atmega32u4 reset
|
||||||
```
|
```
|
||||||
|
|
||||||
## build
|
## build
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/qmk compile
|
qmk compile
|
||||||
```
|
```
|
||||||
|
|
||||||
## flash
|
## flash
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"""Functions for working with config.h files.
|
"""Functions for working with config.h files.
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
|
||||||
import re
|
import re
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
@@ -12,18 +13,21 @@ single_comment_regex = re.compile(r'\s+/[/*].*$')
|
|||||||
multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
|
multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def strip_line_comment(string):
|
def strip_line_comment(string):
|
||||||
"""Removes comments from a single line string.
|
"""Removes comments from a single line string.
|
||||||
"""
|
"""
|
||||||
return single_comment_regex.sub('', string)
|
return single_comment_regex.sub('', string)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def strip_multiline_comment(string):
|
def strip_multiline_comment(string):
|
||||||
"""Removes comments from a single line string.
|
"""Removes comments from a single line string.
|
||||||
"""
|
"""
|
||||||
return multi_comment_regex.sub('', string)
|
return multi_comment_regex.sub('', string)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def c_source_files(dir_names):
|
def c_source_files(dir_names):
|
||||||
"""Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
|
"""Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
|
||||||
|
|
||||||
@@ -38,6 +42,7 @@ def c_source_files(dir_names):
|
|||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def find_layouts(file):
|
def find_layouts(file):
|
||||||
"""Returns list of parsed LAYOUT preprocessor macros found in the supplied include file.
|
"""Returns list of parsed LAYOUT preprocessor macros found in the supplied include file.
|
||||||
"""
|
"""
|
||||||
@@ -144,6 +149,7 @@ def _default_key(label=None):
|
|||||||
return new_key
|
return new_key
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _parse_layout_macro(layout_macro):
|
def _parse_layout_macro(layout_macro):
|
||||||
"""Split the LAYOUT macro into its constituent parts
|
"""Split the LAYOUT macro into its constituent parts
|
||||||
"""
|
"""
|
||||||
@@ -154,6 +160,7 @@ def _parse_layout_macro(layout_macro):
|
|||||||
return macro_name, layout, matrix
|
return macro_name, layout, matrix
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _parse_matrix_locations(matrix, file, macro_name):
|
def _parse_matrix_locations(matrix, file, macro_name):
|
||||||
"""Parse raw matrix data into a dictionary keyed by the LAYOUT identifier.
|
"""Parse raw matrix data into a dictionary keyed by the LAYOUT identifier.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ subcommands = [
|
|||||||
'qmk.cli.chibios.confmigrate',
|
'qmk.cli.chibios.confmigrate',
|
||||||
'qmk.cli.clean',
|
'qmk.cli.clean',
|
||||||
'qmk.cli.compile',
|
'qmk.cli.compile',
|
||||||
'qmk.cli.console',
|
|
||||||
'qmk.cli.docs',
|
'qmk.cli.docs',
|
||||||
'qmk.cli.doctor',
|
'qmk.cli.doctor',
|
||||||
'qmk.cli.fileformat',
|
'qmk.cli.fileformat',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
from subprocess import DEVNULL
|
from subprocess import DEVNULL
|
||||||
|
|
||||||
from qmk.commands import create_make_target
|
from qmk.commands import _find_make
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
|
|
||||||
@@ -11,4 +11,6 @@ from milc import cli
|
|||||||
def clean(cli):
|
def clean(cli):
|
||||||
"""Runs `make clean` (or `make distclean` if --all is passed)
|
"""Runs `make clean` (or `make distclean` if --all is passed)
|
||||||
"""
|
"""
|
||||||
cli.run(create_make_target('distclean' if cli.args.all else 'clean'), capture_output=False, stdin=DEVNULL)
|
make_cmd = [_find_make(), 'distclean' if cli.args.all else 'clean']
|
||||||
|
|
||||||
|
cli.run(make_cmd, capture_output=False, stdin=DEVNULL)
|
||||||
|
|||||||
@@ -2,23 +2,24 @@
|
|||||||
|
|
||||||
You can compile a keymap already in the repo or using a QMK Configurator export.
|
You can compile a keymap already in the repo or using a QMK Configurator export.
|
||||||
"""
|
"""
|
||||||
from subprocess import DEVNULL
|
|
||||||
|
|
||||||
from argcomplete.completers import FilesCompleter
|
from argcomplete.completers import FilesCompleter
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
import qmk.path
|
import qmk.path
|
||||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||||
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
|
from qmk.commands import do_compile
|
||||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
from qmk.keyboard import keyboard_completer, is_keyboard_target
|
||||||
from qmk.keymap import keymap_completer
|
from qmk.keymap import keymap_completer
|
||||||
|
from qmk.metadata import true_values, false_values
|
||||||
|
|
||||||
|
|
||||||
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export to compile')
|
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export to compile')
|
||||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
|
@cli.argument('-t', '--target', help="The make target to run. By default it compiles the keyboard only.")
|
||||||
|
@cli.argument('-kb', '--keyboard', type=is_keyboard_target, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
|
||||||
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
|
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
|
||||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
||||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||||
|
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter your list against info.json.")
|
||||||
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
||||||
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||||
@cli.subcommand('Compile a QMK Firmware.')
|
@cli.subcommand('Compile a QMK Firmware.')
|
||||||
@@ -31,47 +32,31 @@ def compile(cli):
|
|||||||
|
|
||||||
If a keyboard and keymap are provided this command will build a firmware based on that.
|
If a keyboard and keymap are provided this command will build a firmware based on that.
|
||||||
"""
|
"""
|
||||||
if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
|
# If -f has been specified without a keyboard target, assume -kb all
|
||||||
command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, 'clean')
|
keyboard = cli.config.compile.keyboard or ''
|
||||||
cli.run(command, capture_output=False, stdin=DEVNULL)
|
|
||||||
|
|
||||||
# Build the environment vars
|
if cli.args.filter and not cli.args.keyboard:
|
||||||
envs = {}
|
cli.log.debug('--filter supplied without --keyboard, assuming --keyboard all.')
|
||||||
for env in cli.args.env:
|
keyboard = 'all'
|
||||||
if '=' in env:
|
|
||||||
key, value = env.split('=', 1)
|
|
||||||
envs[key] = value
|
|
||||||
else:
|
|
||||||
cli.log.warning('Invalid environment variable: %s', env)
|
|
||||||
|
|
||||||
# Determine the compile command
|
if cli.args.filename and cli.args.filter:
|
||||||
command = None
|
cli.log.warning('Ignoring --filter because a keymap.json was provided.')
|
||||||
|
|
||||||
if cli.args.filename:
|
filters = {}
|
||||||
# If a configurator JSON was provided generate a keymap and compile it
|
|
||||||
user_keymap = parse_configurator_json(cli.args.filename)
|
|
||||||
command = compile_configurator_json(user_keymap, parallel=cli.config.compile.parallel, **envs)
|
|
||||||
|
|
||||||
else:
|
for filter in cli.args.filter:
|
||||||
if cli.config.compile.keyboard and cli.config.compile.keymap:
|
if '=' in filter:
|
||||||
# Generate the make command for a specific keyboard/keymap.
|
key, value = filter.split('=', 1)
|
||||||
command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, parallel=cli.config.compile.parallel, **envs)
|
|
||||||
|
|
||||||
elif not cli.config.compile.keyboard:
|
if value in true_values:
|
||||||
cli.log.error('Could not determine keyboard!')
|
value = True
|
||||||
elif not cli.config.compile.keymap:
|
elif value in false_values:
|
||||||
cli.log.error('Could not determine keymap!')
|
value = False
|
||||||
|
elif value.isdigit():
|
||||||
|
value = int(value)
|
||||||
|
elif '.' in value and value.replace('.').isdigit():
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
# Compile the firmware, if we're able to
|
filters[key] = value
|
||||||
if command:
|
|
||||||
cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
|
|
||||||
if not cli.args.dry_run:
|
|
||||||
cli.echo('\n')
|
|
||||||
# FIXME(skullydazed/anyone): Remove text=False once milc 1.0.11 has had enough time to be installed everywhere.
|
|
||||||
compile = cli.run(command, capture_output=False, text=False)
|
|
||||||
return compile.returncode
|
|
||||||
|
|
||||||
else:
|
return do_compile(keyboard, cli.config.compile.keymap, cli.config.compile.parallel, cli.config.compile.target, filters)
|
||||||
cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
|
|
||||||
cli.echo('usage: qmk compile [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [filename]')
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
"""Acquire debugging information from usb hid devices
|
|
||||||
|
|
||||||
cli implementation of https://www.pjrc.com/teensy/hid_listen.html
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from threading import Thread
|
|
||||||
from time import sleep, strftime
|
|
||||||
|
|
||||||
import hid
|
|
||||||
import usb.core
|
|
||||||
|
|
||||||
from milc import cli
|
|
||||||
|
|
||||||
LOG_COLOR = {
|
|
||||||
'next': 0,
|
|
||||||
'colors': [
|
|
||||||
'{fg_blue}',
|
|
||||||
'{fg_cyan}',
|
|
||||||
'{fg_green}',
|
|
||||||
'{fg_magenta}',
|
|
||||||
'{fg_red}',
|
|
||||||
'{fg_yellow}',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
KNOWN_BOOTLOADERS = {
|
|
||||||
# VID , PID
|
|
||||||
('03EB', '2FEF'): 'atmel-dfu: ATmega16U2',
|
|
||||||
('03EB', '2FF0'): 'atmel-dfu: ATmega32U2',
|
|
||||||
('03EB', '2FF3'): 'atmel-dfu: ATmega16U4',
|
|
||||||
('03EB', '2FF4'): 'atmel-dfu: ATmega32U4',
|
|
||||||
('03EB', '2FF9'): 'atmel-dfu: AT90USB64',
|
|
||||||
('03EB', '2FFA'): 'atmel-dfu: AT90USB162',
|
|
||||||
('03EB', '2FFB'): 'atmel-dfu: AT90USB128',
|
|
||||||
('03EB', '6124'): 'Microchip SAM-BA',
|
|
||||||
('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER',
|
|
||||||
('16C0', '05DC'): 'USBasp: USBaspLoader',
|
|
||||||
('16C0', '05DF'): 'bootloadHID: HIDBoot',
|
|
||||||
('16C0', '0478'): 'halfkay: Teensy Halfkay',
|
|
||||||
('1B4F', '9203'): 'caterina: Pro Micro 3.3V',
|
|
||||||
('1B4F', '9205'): 'caterina: Pro Micro 5V',
|
|
||||||
('1B4F', '9207'): 'caterina: LilyPadUSB',
|
|
||||||
('1C11', 'B007'): 'kiibohd: Kiibohd DFU Bootloader',
|
|
||||||
('1EAF', '0003'): 'stm32duino: Maple 003',
|
|
||||||
('1FFB', '0101'): 'caterina: Polou A-Star 32U4 Bootloader',
|
|
||||||
('2341', '0036'): 'caterina: Arduino Leonardo',
|
|
||||||
('2341', '0037'): 'caterina: Arduino Micro',
|
|
||||||
('239A', '000C'): 'caterina: Adafruit Feather 32U4',
|
|
||||||
('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v',
|
|
||||||
('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v',
|
|
||||||
('2A03', '0036'): 'caterina: Arduino Leonardo',
|
|
||||||
('2A03', '0037'): 'caterina: Arduino Micro',
|
|
||||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode',
|
|
||||||
('03EB', '2067'): 'qmk-hid: HID Bootloader',
|
|
||||||
('03EB', '2045'): 'lufa-ms: LUFA Mass Storage Bootloader'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MonitorDevice(object):
|
|
||||||
def __init__(self, hid_device, numeric):
|
|
||||||
self.hid_device = hid_device
|
|
||||||
self.numeric = numeric
|
|
||||||
self.device = hid.Device(path=hid_device['path'])
|
|
||||||
self.current_line = ''
|
|
||||||
|
|
||||||
cli.log.info('Console Connected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device)
|
|
||||||
|
|
||||||
def read(self, size, encoding='ascii', timeout=1):
|
|
||||||
"""Read size bytes from the device.
|
|
||||||
"""
|
|
||||||
return self.device.read(size, timeout).decode(encoding)
|
|
||||||
|
|
||||||
def read_line(self):
|
|
||||||
"""Read from the device's console until we get a \n.
|
|
||||||
"""
|
|
||||||
while '\n' not in self.current_line:
|
|
||||||
self.current_line += self.read(32).replace('\x00', '')
|
|
||||||
|
|
||||||
lines = self.current_line.split('\n', 1)
|
|
||||||
self.current_line = lines[1]
|
|
||||||
|
|
||||||
return lines[0]
|
|
||||||
|
|
||||||
def run_forever(self):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
message = {**self.hid_device, 'text': self.read_line()}
|
|
||||||
identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string'])
|
|
||||||
message['identifier'] = ':'.join(identifier)
|
|
||||||
message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else ''
|
|
||||||
|
|
||||||
cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message)
|
|
||||||
|
|
||||||
except hid.HIDException:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class FindDevices(object):
|
|
||||||
def __init__(self, vid, pid, index, numeric):
|
|
||||||
self.vid = vid
|
|
||||||
self.pid = pid
|
|
||||||
self.index = index
|
|
||||||
self.numeric = numeric
|
|
||||||
|
|
||||||
def run_forever(self):
|
|
||||||
"""Process messages from our queue in a loop.
|
|
||||||
"""
|
|
||||||
live_devices = {}
|
|
||||||
live_bootloaders = {}
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
for device in list(live_devices):
|
|
||||||
if not live_devices[device]['thread'].is_alive():
|
|
||||||
cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device])
|
|
||||||
del live_devices[device]
|
|
||||||
|
|
||||||
for device in self.find_devices():
|
|
||||||
if device['path'] not in live_devices:
|
|
||||||
device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']]
|
|
||||||
LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors'])
|
|
||||||
live_devices[device['path']] = device
|
|
||||||
|
|
||||||
try:
|
|
||||||
monitor = MonitorDevice(device, self.numeric)
|
|
||||||
device['thread'] = Thread(target=monitor.run_forever, daemon=True)
|
|
||||||
|
|
||||||
device['thread'].start()
|
|
||||||
except Exception as e:
|
|
||||||
device['e'] = e
|
|
||||||
device['e_name'] = e.__class__.__name__
|
|
||||||
cli.log.error("Could not connect to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s:%(vendor_id)04X:%(product_id)04X:%(index)d): %(e_name)s: %(e)s", device)
|
|
||||||
if cli.config.general.verbose:
|
|
||||||
cli.log.exception(e)
|
|
||||||
del live_devices[device['path']]
|
|
||||||
|
|
||||||
if cli.args.bootloaders:
|
|
||||||
for device in self.find_bootloaders():
|
|
||||||
if device.address in live_bootloaders:
|
|
||||||
live_bootloaders[device.address]._qmk_found = True
|
|
||||||
else:
|
|
||||||
name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))]
|
|
||||||
cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name)
|
|
||||||
device._qmk_found = True
|
|
||||||
live_bootloaders[device.address] = device
|
|
||||||
|
|
||||||
for device in list(live_bootloaders):
|
|
||||||
if live_bootloaders[device]._qmk_found:
|
|
||||||
live_bootloaders[device]._qmk_found = False
|
|
||||||
else:
|
|
||||||
name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))]
|
|
||||||
cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name)
|
|
||||||
del live_bootloaders[device]
|
|
||||||
|
|
||||||
sleep(.1)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
|
|
||||||
def is_bootloader(self, hid_device):
|
|
||||||
"""Returns true if the device in question matches a known bootloader vid/pid.
|
|
||||||
"""
|
|
||||||
return (int2hex(hid_device.idVendor), int2hex(hid_device.idProduct)) in KNOWN_BOOTLOADERS
|
|
||||||
|
|
||||||
def is_console_hid(self, hid_device):
|
|
||||||
"""Returns true when the usage page indicates it's a teensy-style console.
|
|
||||||
"""
|
|
||||||
return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074
|
|
||||||
|
|
||||||
def is_filtered_device(self, hid_device):
|
|
||||||
"""Returns True if the device should be included in the list of available consoles.
|
|
||||||
"""
|
|
||||||
return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid
|
|
||||||
|
|
||||||
def find_devices_by_report(self, hid_devices):
|
|
||||||
"""Returns a list of available teensy-style consoles by doing a brute-force search.
|
|
||||||
|
|
||||||
Some versions of linux don't report usage and usage_page. In that case we fallback to reading the report (possibly inaccurately) ourselves.
|
|
||||||
"""
|
|
||||||
devices = []
|
|
||||||
|
|
||||||
for device in hid_devices:
|
|
||||||
path = device['path'].decode('utf-8')
|
|
||||||
|
|
||||||
if path.startswith('/dev/hidraw'):
|
|
||||||
number = path[11:]
|
|
||||||
report = Path(f'/sys/class/hidraw/hidraw{number}/device/report_descriptor')
|
|
||||||
|
|
||||||
if report.exists():
|
|
||||||
rp = report.read_bytes()
|
|
||||||
|
|
||||||
if rp[1] == 0x31 and rp[3] == 0x09:
|
|
||||||
devices.append(device)
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def find_bootloaders(self):
|
|
||||||
"""Returns a list of available bootloader devices.
|
|
||||||
"""
|
|
||||||
return list(filter(self.is_bootloader, usb.core.find(find_all=True)))
|
|
||||||
|
|
||||||
def find_devices(self):
|
|
||||||
"""Returns a list of available teensy-style consoles.
|
|
||||||
"""
|
|
||||||
hid_devices = hid.enumerate()
|
|
||||||
devices = list(filter(self.is_console_hid, hid_devices))
|
|
||||||
|
|
||||||
if not devices:
|
|
||||||
devices = self.find_devices_by_report(hid_devices)
|
|
||||||
|
|
||||||
if self.vid and self.pid:
|
|
||||||
devices = list(filter(self.is_filtered_device, devices))
|
|
||||||
|
|
||||||
# Add index numbers
|
|
||||||
device_index = {}
|
|
||||||
for device in devices:
|
|
||||||
id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id'])))
|
|
||||||
|
|
||||||
if id not in device_index:
|
|
||||||
device_index[id] = 0
|
|
||||||
|
|
||||||
device_index[id] += 1
|
|
||||||
device['index'] = device_index[id]
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def int2hex(number):
|
|
||||||
"""Returns a string representation of the number as hex.
|
|
||||||
"""
|
|
||||||
return "%04X" % number
|
|
||||||
|
|
||||||
|
|
||||||
def list_devices(device_finder):
|
|
||||||
"""Show the user a nicely formatted list of devices.
|
|
||||||
"""
|
|
||||||
devices = device_finder.find_devices()
|
|
||||||
|
|
||||||
if devices:
|
|
||||||
cli.log.info('Available devices:')
|
|
||||||
for dev in devices:
|
|
||||||
color = LOG_COLOR['colors'][LOG_COLOR['next']]
|
|
||||||
LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors'])
|
|
||||||
cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string'])
|
|
||||||
|
|
||||||
if cli.args.bootloaders:
|
|
||||||
bootloaders = device_finder.find_bootloaders()
|
|
||||||
|
|
||||||
if bootloaders:
|
|
||||||
cli.log.info('Available Bootloaders:')
|
|
||||||
|
|
||||||
for dev in bootloaders:
|
|
||||||
cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))])
|
|
||||||
|
|
||||||
|
|
||||||
@cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.')
|
|
||||||
@cli.argument('-d', '--device', help='Device to select - uses format <pid>:<vid>[:<index>].')
|
|
||||||
@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.')
|
|
||||||
@cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.')
|
|
||||||
@cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.')
|
|
||||||
@cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)")
|
|
||||||
@cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True)
|
|
||||||
def console(cli):
|
|
||||||
"""Acquire debugging information from usb hid devices
|
|
||||||
"""
|
|
||||||
vid = None
|
|
||||||
pid = None
|
|
||||||
index = 1
|
|
||||||
|
|
||||||
if cli.config.console.device:
|
|
||||||
device = cli.config.console.device.split(':')
|
|
||||||
|
|
||||||
if len(device) == 2:
|
|
||||||
vid, pid = device
|
|
||||||
|
|
||||||
elif len(device) == 3:
|
|
||||||
vid, pid, index = device
|
|
||||||
|
|
||||||
if not index.isdigit():
|
|
||||||
cli.log.error('Device index must be a number! Got "%s" instead.', index)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
index = int(index)
|
|
||||||
|
|
||||||
if index < 1:
|
|
||||||
cli.log.error('Device index must be greater than 0! Got %s', index)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
else:
|
|
||||||
cli.log.error('Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "%s".', cli.config.console.device)
|
|
||||||
cli.print_help()
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
vid = vid.upper()
|
|
||||||
pid = pid.upper()
|
|
||||||
|
|
||||||
device_finder = FindDevices(vid, pid, index, cli.args.numeric)
|
|
||||||
|
|
||||||
if cli.args.list:
|
|
||||||
return list_devices(device_finder)
|
|
||||||
|
|
||||||
print('Looking for devices...', flush=True)
|
|
||||||
device_finder.run_forever()
|
|
||||||
@@ -26,7 +26,6 @@ ESSENTIAL_BINARIES = {
|
|||||||
'arm-none-eabi-gcc': {
|
'arm-none-eabi-gcc': {
|
||||||
'version_arg': '-dumpversion'
|
'version_arg': '-dumpversion'
|
||||||
},
|
},
|
||||||
'bin/qmk': {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
You can compile a keymap already in the repo or using a QMK Configurator export.
|
You can compile a keymap already in the repo or using a QMK Configurator export.
|
||||||
A bootloader must be specified.
|
A bootloader must be specified.
|
||||||
"""
|
"""
|
||||||
from subprocess import DEVNULL
|
|
||||||
|
|
||||||
from argcomplete.completers import FilesCompleter
|
from argcomplete.completers import FilesCompleter
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
import qmk.path
|
import qmk.path
|
||||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||||
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
|
from qmk.commands import do_compile
|
||||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
from qmk.keyboard import keyboard_completer, is_keyboard_target
|
||||||
|
|
||||||
|
|
||||||
def print_bootloader_help():
|
def print_bootloader_help():
|
||||||
@@ -36,7 +34,7 @@ def print_bootloader_help():
|
|||||||
@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
|
@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
|
||||||
@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
|
@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
|
||||||
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
||||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
@cli.argument('-kb', '--keyboard', type=is_keyboard_target, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
||||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
||||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
||||||
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
||||||
@@ -54,55 +52,10 @@ def flash(cli):
|
|||||||
|
|
||||||
If bootloader is omitted the make system will use the configured bootloader for that keyboard.
|
If bootloader is omitted the make system will use the configured bootloader for that keyboard.
|
||||||
"""
|
"""
|
||||||
if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
|
|
||||||
command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean')
|
|
||||||
cli.run(command, capture_output=False, stdin=DEVNULL)
|
|
||||||
|
|
||||||
# Build the environment vars
|
|
||||||
envs = {}
|
|
||||||
for env in cli.args.env:
|
|
||||||
if '=' in env:
|
|
||||||
key, value = env.split('=', 1)
|
|
||||||
envs[key] = value
|
|
||||||
else:
|
|
||||||
cli.log.warning('Invalid environment variable: %s', env)
|
|
||||||
|
|
||||||
# Determine the compile command
|
|
||||||
command = ''
|
|
||||||
|
|
||||||
if cli.args.bootloaders:
|
if cli.args.bootloaders:
|
||||||
# Provide usage and list bootloaders
|
# Provide usage and list bootloaders
|
||||||
cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
|
cli.print_usage()
|
||||||
print_bootloader_help()
|
print_bootloader_help()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if cli.args.filename:
|
return do_compile(cli.config.flash.keyboard, cli.config.flash.keymap, cli.config.flash.parallel, cli.config.flash.bootloader)
|
||||||
# Handle compiling a configurator JSON
|
|
||||||
user_keymap = parse_configurator_json(cli.args.filename)
|
|
||||||
keymap_path = qmk.path.keymap(user_keymap['keyboard'])
|
|
||||||
command = compile_configurator_json(user_keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
|
|
||||||
|
|
||||||
cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])
|
|
||||||
|
|
||||||
else:
|
|
||||||
if cli.config.flash.keyboard and cli.config.flash.keymap:
|
|
||||||
# Generate the make command for a specific keyboard/keymap.
|
|
||||||
command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
|
|
||||||
|
|
||||||
elif not cli.config.flash.keyboard:
|
|
||||||
cli.log.error('Could not determine keyboard!')
|
|
||||||
elif not cli.config.flash.keymap:
|
|
||||||
cli.log.error('Could not determine keymap!')
|
|
||||||
|
|
||||||
# Compile the firmware, if we're able to
|
|
||||||
if command:
|
|
||||||
cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
|
|
||||||
if not cli.args.dry_run:
|
|
||||||
cli.echo('\n')
|
|
||||||
compile = cli.run(command, capture_output=False, stdin=DEVNULL)
|
|
||||||
return compile.returncode
|
|
||||||
|
|
||||||
else:
|
|
||||||
cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
|
|
||||||
cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ def format_python(cli):
|
|||||||
"""Format python code according to QMK's style.
|
"""Format python code according to QMK's style.
|
||||||
"""
|
"""
|
||||||
edit = '--diff' if cli.args.dry_run else '--in-place'
|
edit = '--diff' if cli.args.dry_run else '--in-place'
|
||||||
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python']
|
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'lib/python']
|
||||||
try:
|
try:
|
||||||
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
|
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
|
||||||
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.')
|
cli.log.info('Python code in `lib/python` is correctly formatted.')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
if cli.args.dry_run:
|
if cli.args.dry_run:
|
||||||
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!')
|
cli.log.error('Python code in `lib/python` is incorrectly formatted!')
|
||||||
else:
|
else:
|
||||||
cli.log.error('Error formatting python code!')
|
cli.log.error('Error formatting python code!')
|
||||||
|
|
||||||
|
|||||||
@@ -1,72 +1,129 @@
|
|||||||
"""Command to look over a keyboard/keymap and check for common mistakes.
|
"""Command to look over a keyboard/keymap and check for common mistakes.
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||||
from qmk.info import info_json
|
from qmk.info import info_json
|
||||||
from qmk.keyboard import find_readme, keyboard_completer
|
from qmk.keyboard import keyboard_completer, list_keyboards
|
||||||
from qmk.keymap import locate_keymap
|
from qmk.keymap import locate_keymap
|
||||||
from qmk.path import is_keyboard, keyboard
|
from qmk.path import is_keyboard, keyboard
|
||||||
|
|
||||||
|
|
||||||
@cli.argument('--strict', action='store_true', help='Treat warnings as errors.')
|
def keymap_check(kb, km):
|
||||||
@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.')
|
"""Perform the keymap level checks.
|
||||||
@cli.argument('-km', '--keymap', help='The keymap to check.')
|
"""
|
||||||
|
ok = True
|
||||||
|
keymap_path = locate_keymap(kb, km)
|
||||||
|
|
||||||
|
if not keymap_path:
|
||||||
|
ok = False
|
||||||
|
cli.log.error("%s: Can't find %s keymap.", kb, km)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
def rules_mk_assignment_only(keyboard_path):
|
||||||
|
"""Check the keyboard-level rules.mk to ensure it only has assignments.
|
||||||
|
"""
|
||||||
|
current_path = Path()
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for path_part in keyboard_path.parts:
|
||||||
|
current_path = current_path / path_part
|
||||||
|
rules_mk = current_path / 'rules.mk'
|
||||||
|
|
||||||
|
if rules_mk.exists():
|
||||||
|
continuation = None
|
||||||
|
|
||||||
|
for i, line in enumerate(rules_mk.open()):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if '#' in line:
|
||||||
|
line = line[:line.index('#')]
|
||||||
|
|
||||||
|
if continuation:
|
||||||
|
line = continuation + line
|
||||||
|
continuation = None
|
||||||
|
|
||||||
|
if line:
|
||||||
|
if line[-1] == '\\':
|
||||||
|
continuation = line[:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line and '=' not in line:
|
||||||
|
errors.append(f'Non-assignment code on line +{i} {rules_mk}: {line}')
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@cli.argument('--strict', action='store_true', help='Treat warnings as errors')
|
||||||
|
@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Comma separated list of keyboards to check')
|
||||||
|
@cli.argument('-km', '--keymap', help='The keymap to check')
|
||||||
|
@cli.argument('--all-kb', action='store_true', arg_only=True, help='Check all keyboards')
|
||||||
@cli.subcommand('Check keyboard and keymap for common mistakes.')
|
@cli.subcommand('Check keyboard and keymap for common mistakes.')
|
||||||
@automagic_keyboard
|
@automagic_keyboard
|
||||||
@automagic_keymap
|
@automagic_keymap
|
||||||
def lint(cli):
|
def lint(cli):
|
||||||
"""Check keyboard and keymap for common mistakes.
|
"""Check keyboard and keymap for common mistakes.
|
||||||
"""
|
"""
|
||||||
if not cli.config.lint.keyboard:
|
failed = []
|
||||||
cli.log.error('Missing required argument: --keyboard')
|
|
||||||
|
# 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()
|
cli.print_help()
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
keyboard_list = cli.config.lint.keyboard.split(',')
|
||||||
|
|
||||||
if not is_keyboard(cli.config.lint.keyboard):
|
# Lint each keyboard
|
||||||
cli.log.error('No such keyboard: %s', cli.config.lint.keyboard)
|
for kb in keyboard_list:
|
||||||
return False
|
if not is_keyboard(kb):
|
||||||
|
cli.log.error('No such keyboard: %s', kb)
|
||||||
|
continue
|
||||||
|
|
||||||
# Gather data about the keyboard.
|
# Gather data about the keyboard.
|
||||||
ok = True
|
ok = True
|
||||||
keyboard_path = keyboard(cli.config.lint.keyboard)
|
keyboard_path = keyboard(kb)
|
||||||
keyboard_info = info_json(cli.config.lint.keyboard)
|
keyboard_info = info_json(kb)
|
||||||
readme_path = find_readme(cli.config.lint.keyboard)
|
|
||||||
missing_readme_path = keyboard_path / 'readme.md'
|
|
||||||
|
|
||||||
# Check for errors in the info.json
|
# Check for errors in the info.json
|
||||||
if keyboard_info['parse_errors']:
|
if keyboard_info['parse_errors']:
|
||||||
ok = False
|
ok = False
|
||||||
cli.log.error('Errors found when generating info.json.')
|
cli.log.error('%s: Errors found when generating info.json.', kb)
|
||||||
|
|
||||||
if cli.config.lint.strict and keyboard_info['parse_warnings']:
|
if cli.config.lint.strict and keyboard_info['parse_warnings']:
|
||||||
ok = False
|
ok = False
|
||||||
cli.log.error('Warnings found when generating info.json (Strict mode enabled.)')
|
cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb)
|
||||||
|
|
||||||
# Check for a readme.md and warn if it doesn't exist
|
# Check the rules.mk file(s)
|
||||||
if not readme_path:
|
rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path)
|
||||||
|
if rules_mk_assignment_errors:
|
||||||
ok = False
|
ok = False
|
||||||
cli.log.error('Missing %s', missing_readme_path)
|
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
|
# Keymap specific checks
|
||||||
if cli.config.lint.keymap:
|
if cli.config.lint.keymap:
|
||||||
keymap_path = locate_keymap(cli.config.lint.keyboard, cli.config.lint.keymap)
|
if not keymap_check(kb, cli.config.lint.keymap):
|
||||||
|
|
||||||
if not keymap_path:
|
|
||||||
ok = False
|
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:
|
# Report status
|
||||||
ok = False
|
if not ok:
|
||||||
|
failed.append(kb)
|
||||||
|
|
||||||
# Check and report the overall status
|
# Check and report the overall status
|
||||||
if ok:
|
if failed:
|
||||||
|
cli.log.error('Lint check failed for: %s', ', '.join(failed))
|
||||||
|
return False
|
||||||
|
|
||||||
cli.log.info('Lint check passed!')
|
cli.log.info('Lint check passed!')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
cli.log.error('Lint check failed!')
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ def pytest(cli):
|
|||||||
"""Run several linting/testing commands.
|
"""Run several linting/testing commands.
|
||||||
"""
|
"""
|
||||||
nose2 = cli.run(['nose2', '-v'], capture_output=False, stdin=DEVNULL)
|
nose2 = cli.run(['nose2', '-v'], capture_output=False, stdin=DEVNULL)
|
||||||
flake8 = cli.run(['flake8', 'lib/python', 'bin/qmk'], capture_output=False, stdin=DEVNULL)
|
flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL)
|
||||||
|
|
||||||
return flake8.returncode | nose2.returncode
|
return flake8.returncode | nose2.returncode
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
"""Helper functions for commands.
|
"""Helper functions for commands.
|
||||||
"""
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import DEVNULL
|
from subprocess import DEVNULL
|
||||||
from time import strftime
|
from time import sleep, strftime
|
||||||
|
|
||||||
|
from dotty_dict import dotty
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
import qmk.keymap
|
import qmk.keymap
|
||||||
from qmk.constants import QMK_FIRMWARE, KEYBOARD_OUTPUT_PREFIX
|
from qmk.constants import QMK_FIRMWARE, KEYBOARD_OUTPUT_PREFIX
|
||||||
|
from qmk.info import info_json
|
||||||
from qmk.json_schema import json_load
|
from qmk.json_schema import json_load
|
||||||
|
from qmk.keyboard import list_keyboards
|
||||||
|
|
||||||
time_fmt = '%Y-%m-%d-%H:%M:%S'
|
time_fmt = '%Y-%m-%d-%H:%M:%S'
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _find_make():
|
def _find_make():
|
||||||
"""Returns the correct make command for this environment.
|
"""Returns the correct make command for this environment.
|
||||||
"""
|
"""
|
||||||
@@ -55,7 +61,7 @@ def create_make_target(target, parallel=1, **env_vars):
|
|||||||
return [make_cmd, *get_make_parallel_args(parallel), *env, target]
|
return [make_cmd, *get_make_parallel_args(parallel), *env, target]
|
||||||
|
|
||||||
|
|
||||||
def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars):
|
def create_make_command(keyboard, keymap, target=None, parallel=1, silent=False, **env_vars):
|
||||||
"""Create a make compile command
|
"""Create a make compile command
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -79,14 +85,29 @@ def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars):
|
|||||||
|
|
||||||
A command that can be run to make the specified keyboard and keymap
|
A command that can be run to make the specified keyboard and keymap
|
||||||
"""
|
"""
|
||||||
make_args = [keyboard, keymap]
|
make_cmd = [_find_make(), '--no-print-directory', '-r', '-R', '-C', './', '-f', 'build_keyboard.mk']
|
||||||
|
|
||||||
|
env_vars['KEYBOARD'] = keyboard
|
||||||
|
env_vars['KEYMAP'] = keymap
|
||||||
|
env_vars['QMK_BIN'] = 'bin/qmk' if 'DEPRECATED_BIN_QMK' in os.environ else 'qmk'
|
||||||
|
env_vars['VERBOSE'] = 'true' if cli.config.general.verbose else ''
|
||||||
|
env_vars['SILENT'] = 'true' if silent else 'false'
|
||||||
|
env_vars['COLOR'] = 'true' if cli.config.general.color else ''
|
||||||
|
|
||||||
|
if parallel > 1:
|
||||||
|
make_cmd.append('-j')
|
||||||
|
make_cmd.append(str(parallel))
|
||||||
|
|
||||||
if target:
|
if target:
|
||||||
make_args.append(target)
|
make_cmd.append(target)
|
||||||
|
|
||||||
return create_make_target(':'.join(make_args), parallel, **env_vars)
|
for key, value in env_vars.items():
|
||||||
|
make_cmd.append(f'{key}={value}')
|
||||||
|
|
||||||
|
return make_cmd
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def get_git_version(current_time, repo_dir='.', check_dir='.'):
|
def get_git_version(current_time, repo_dir='.', check_dir='.'):
|
||||||
"""Returns the current git version for a repo, or the current time.
|
"""Returns the current git version for a repo, or the current time.
|
||||||
"""
|
"""
|
||||||
@@ -233,12 +254,13 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
|
|||||||
f'VERBOSE={verbose}',
|
f'VERBOSE={verbose}',
|
||||||
f'COLOR={color}',
|
f'COLOR={color}',
|
||||||
'SILENT=false',
|
'SILENT=false',
|
||||||
f'QMK_BIN={"bin/qmk" if "DEPRECATED_BIN_QMK" in os.environ else "qmk"}',
|
'QMK_BIN="qmk"',
|
||||||
])
|
])
|
||||||
|
|
||||||
return make_command
|
return user_keymap['keyboard'], user_keymap['keymap'], make_command
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def parse_configurator_json(configurator_file):
|
def parse_configurator_json(configurator_file):
|
||||||
"""Open and parse a configurator json export
|
"""Open and parse a configurator json export
|
||||||
"""
|
"""
|
||||||
@@ -332,3 +354,165 @@ def in_virtualenv():
|
|||||||
"""
|
"""
|
||||||
active_prefix = getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
|
active_prefix = getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
|
||||||
return active_prefix != sys.prefix
|
return active_prefix != sys.prefix
|
||||||
|
|
||||||
|
|
||||||
|
def do_compile(keyboard, keymap, parallel, target=None, filters=None, environment=None):
|
||||||
|
"""Shared code between `qmk compile` and `qmk flash`.
|
||||||
|
"""
|
||||||
|
if keyboard is None:
|
||||||
|
keyboard = ''
|
||||||
|
|
||||||
|
if environment is None:
|
||||||
|
environment = {}
|
||||||
|
|
||||||
|
all_keyboards = keyboard == 'all' or keyboard.startswith('all-')
|
||||||
|
all_keymaps = keymap == 'all'
|
||||||
|
multiple_compiles = all_keyboards or all_keymaps
|
||||||
|
|
||||||
|
# Setup the environment
|
||||||
|
envs = {'REQUIRE_PLATFORM_KEY': ''}
|
||||||
|
|
||||||
|
for env in environment:
|
||||||
|
if '=' in env:
|
||||||
|
key, value = env.split('=', 1)
|
||||||
|
if key in envs:
|
||||||
|
cli.log.warning('Overwriting existing environment variable %s=%s with %s=%s!', key, envs[key], key, value)
|
||||||
|
envs[key] = value
|
||||||
|
else:
|
||||||
|
cli.log.warning('Invalid environment variable: %s', env)
|
||||||
|
|
||||||
|
if keyboard.startswith('all-'):
|
||||||
|
envs['REQUIRE_PLATFORM_KEY'] = keyboard[4:]
|
||||||
|
|
||||||
|
# Run clean if necessary
|
||||||
|
if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
|
||||||
|
for kb, km in keyboard_keymap_iter(keyboard, keymap, {}):
|
||||||
|
cli.log.info('Cleaning previous build files for keyboard {fg_cyan}%s{fg_reset} keymap {fg_cyan}%s', kb, km)
|
||||||
|
make_cmd = create_make_command(kb, km, 'clean', 1, multiple_compiles, **envs)
|
||||||
|
cli.run(make_cmd, capture_output=False, stdin=DEVNULL)
|
||||||
|
|
||||||
|
# Determine the compile command(s)
|
||||||
|
command = None
|
||||||
|
|
||||||
|
if cli.args.filename:
|
||||||
|
if cli.args.keyboard:
|
||||||
|
cli.log.warning('Ignoring --keyboard because a keymap.json was provided.')
|
||||||
|
|
||||||
|
if cli.args.keymap:
|
||||||
|
cli.log.warning('Ignoring --keymap because a keymap.json was provided.')
|
||||||
|
|
||||||
|
# If a configurator JSON was provided generate a keymap and compile it
|
||||||
|
user_keymap = parse_configurator_json(cli.args.filename)
|
||||||
|
command = compile_configurator_json(user_keymap, parallel=parallel, **envs)
|
||||||
|
|
||||||
|
elif keyboard and keymap:
|
||||||
|
if multiple_compiles:
|
||||||
|
command = 'multiple'
|
||||||
|
else:
|
||||||
|
command = create_make_command(keyboard, keymap, target=target, parallel=parallel, silent=multiple_compiles, **envs)
|
||||||
|
|
||||||
|
elif not keyboard:
|
||||||
|
cli.log.error('Could not determine keyboard!')
|
||||||
|
|
||||||
|
elif not keymap:
|
||||||
|
cli.log.error('Could not determine keymap!')
|
||||||
|
|
||||||
|
# Compile the firmware, if we're able to
|
||||||
|
if command == 'multiple':
|
||||||
|
cli.log.info('Building {fg_cyan}%s{fg_reset} with keymap {fg_cyan}%s', keyboard, keymap)
|
||||||
|
|
||||||
|
returncodes = []
|
||||||
|
for keyboard, keymap in keyboard_keymap_iter(keyboard, keymap, filters):
|
||||||
|
command = create_make_command(keyboard, keymap, target=target, parallel=1, silent=multiple_compiles, **envs)
|
||||||
|
while threading.active_count() >= parallel + 1:
|
||||||
|
sleep(1)
|
||||||
|
threading.Thread(target=_execute_compile, args=(keyboard, keymap, command, target, returncodes)).start()
|
||||||
|
|
||||||
|
while threading.active_count() > 1:
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
if any(returncodes):
|
||||||
|
print()
|
||||||
|
cli.log.error('Could not compile all targets, look above this message for more details. Failing target(s):')
|
||||||
|
|
||||||
|
for i, returncode in enumerate(returncodes):
|
||||||
|
if returncode != 0:
|
||||||
|
keyboard, keymap, command = returncodes[i]
|
||||||
|
cli.echo('\tkeyboard: {fg_cyan}%s{fg_reset} keymap: {fg_cyan}%s', keyboard, keymap)
|
||||||
|
|
||||||
|
elif command:
|
||||||
|
if target:
|
||||||
|
cli.log.info('Building {fg_cyan}%s{fg_reset} with keymap {fg_cyan}%s{fg_reset} and target {fg_cyan}%s', keyboard, keymap, target)
|
||||||
|
else:
|
||||||
|
cli.log.info('Building {fg_cyan}%s{fg_reset} with keymap {fg_cyan}%s', keyboard, keymap)
|
||||||
|
|
||||||
|
if _execute_compile(keyboard, keymap, command, target) != 0:
|
||||||
|
print()
|
||||||
|
cli.log.error('Could not compile all targets, look above this message for more details. Failing target(s):')
|
||||||
|
cli.echo('\tkeyboard: {fg_cyan}%s{fg_reset} keymap: {fg_cyan}%s', keyboard, keymap)
|
||||||
|
|
||||||
|
elif filters:
|
||||||
|
cli.log.error('No keyboards found after applying filter(s)!')
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
|
||||||
|
cli.print_help()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _execute_compile(keyboard, keymap, command, target, returncodes=None):
|
||||||
|
if not returncodes:
|
||||||
|
returncodes = []
|
||||||
|
|
||||||
|
if keymap not in qmk.keymap.list_keymaps(keyboard):
|
||||||
|
cli.log.debug('Skipping keyboard %s, no %s keymap found.', keyboard, keymap)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
cli.log.debug('Running make command: {fg_blue}%s', ' '.join(command))
|
||||||
|
|
||||||
|
if not cli.args.dry_run:
|
||||||
|
compile = cli.run(command, combined_output=True)
|
||||||
|
|
||||||
|
cli.acquire_lock()
|
||||||
|
returncodes.append(compile.returncode)
|
||||||
|
cli.release_lock()
|
||||||
|
|
||||||
|
if compile.returncode != 0:
|
||||||
|
cli.log.info('Could not build firmware for {fg_cyan}%s{fg_reset} with keymap {fg_cyan}%s', keyboard, keymap)
|
||||||
|
print(compile.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def _keyboard_list(keyboard):
|
||||||
|
"""Returns a list of keyboards matching keyboard.
|
||||||
|
"""
|
||||||
|
if keyboard == 'all' or keyboard.startswith('all-'):
|
||||||
|
return list_keyboards()
|
||||||
|
|
||||||
|
return [keyboard]
|
||||||
|
|
||||||
|
|
||||||
|
def keyboard_keymap_iter(cli_keyboard, cli_keymap, filters):
|
||||||
|
"""Iterates over the keyboard/keymap for this command and yields a pairing of each.
|
||||||
|
"""
|
||||||
|
for keyboard in _keyboard_list(cli_keyboard):
|
||||||
|
continue_flag = False
|
||||||
|
|
||||||
|
if filters:
|
||||||
|
info_data = dotty(info_json(keyboard))
|
||||||
|
|
||||||
|
for key, value in filters.items():
|
||||||
|
if info_data.get(key) != value:
|
||||||
|
continue_flag = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if continue_flag:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cli_keymap == 'all':
|
||||||
|
for keymap in qmk.keymap.list_keymaps(keyboard):
|
||||||
|
yield keyboard, keymap
|
||||||
|
|
||||||
|
else:
|
||||||
|
yield keyboard, cli_keymap
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Gratefully adapted from https://stackoverflow.com/a/241506
|
Gratefully adapted from https://stackoverflow.com/a/241506
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
comment_pattern = re.compile(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE)
|
comment_pattern = re.compile(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE)
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ def _comment_stripper(match):
|
|||||||
return ' ' if s.startswith('/') else s
|
return ' ' if s.startswith('/') else s
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def comment_remover(text):
|
def comment_remover(text):
|
||||||
"""Remove C/C++ style comments from text.
|
"""Remove C/C++ style comments from text.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
"""Functions to convert to and from QMK formats
|
"""Functions to convert to and from QMK formats
|
||||||
"""
|
"""
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def kle2qmk(kle):
|
def kle2qmk(kle):
|
||||||
"""Convert a KLE layout to QMK's layout format.
|
"""Convert a KLE layout to QMK's layout format.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Functions that help us generate and use info.json files.
|
"""Functions that help us generate and use info.json files.
|
||||||
"""
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -12,37 +13,22 @@ from qmk.c_parse import find_layouts
|
|||||||
from qmk.json_schema import deep_update, json_load, validate
|
from qmk.json_schema import deep_update, json_load, validate
|
||||||
from qmk.keyboard import config_h, rules_mk
|
from qmk.keyboard import config_h, rules_mk
|
||||||
from qmk.keymap import list_keymaps
|
from qmk.keymap import list_keymaps
|
||||||
from qmk.makefile import parse_rules_mk_file
|
|
||||||
from qmk.math import compute
|
from qmk.math import compute
|
||||||
|
from qmk.metadata import basic_info_json, info_log_error, info_log_warning, true_values, false_values
|
||||||
true_values = ['1', 'on', 'yes']
|
|
||||||
false_values = ['0', 'off', 'no']
|
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
def _valid_community_layout(layout):
|
def _valid_community_layout(layout):
|
||||||
"""Validate that a declared community list exists
|
"""Validate that a declared community list exists
|
||||||
"""
|
"""
|
||||||
return (Path('layouts/default') / layout).exists()
|
return (Path('layouts/default') / layout).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
def info_json(keyboard):
|
def info_json(keyboard):
|
||||||
"""Generate the info.json data for a specific keyboard.
|
"""Generate the info.json data for a specific keyboard.
|
||||||
"""
|
"""
|
||||||
cur_dir = Path('keyboards')
|
info_data = basic_info_json(keyboard)
|
||||||
rules = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk')
|
|
||||||
if 'DEFAULT_FOLDER' in rules:
|
|
||||||
keyboard = rules['DEFAULT_FOLDER']
|
|
||||||
rules = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk', rules)
|
|
||||||
|
|
||||||
info_data = {
|
|
||||||
'keyboard_name': str(keyboard),
|
|
||||||
'keyboard_folder': str(keyboard),
|
|
||||||
'keymaps': {},
|
|
||||||
'layouts': {},
|
|
||||||
'parse_errors': [],
|
|
||||||
'parse_warnings': [],
|
|
||||||
'maintainer': 'qmk',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Populate the list of JSON keymaps
|
# Populate the list of JSON keymaps
|
||||||
for keymap in list_keymaps(keyboard, c=False, fullpath=True):
|
for keymap in list_keymaps(keyboard, c=False, fullpath=True):
|
||||||
@@ -81,20 +67,20 @@ def info_json(keyboard):
|
|||||||
_find_missing_layouts(info_data, keyboard)
|
_find_missing_layouts(info_data, keyboard)
|
||||||
|
|
||||||
if not info_data.get('layouts'):
|
if not info_data.get('layouts'):
|
||||||
_log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.')
|
info_log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.')
|
||||||
|
|
||||||
# Filter out any non-existing community layouts
|
# Filter out any non-existing community layouts
|
||||||
for layout in info_data.get('community_layouts', []):
|
for layout in info_data.get('community_layouts', []):
|
||||||
if not _valid_community_layout(layout):
|
if not _valid_community_layout(layout):
|
||||||
# Ignore layout from future checks
|
# Ignore layout from future checks
|
||||||
info_data['community_layouts'].remove(layout)
|
info_data['community_layouts'].remove(layout)
|
||||||
_log_error(info_data, 'Claims to support a community layout that does not exist: %s' % (layout))
|
info_log_error(info_data, 'Claims to support a community layout that does not exist: %s' % (layout))
|
||||||
|
|
||||||
# Make sure we supply layout macros for the community layouts we claim to support
|
# Make sure we supply layout macros for the community layouts we claim to support
|
||||||
for layout in info_data.get('community_layouts', []):
|
for layout in info_data.get('community_layouts', []):
|
||||||
layout_name = 'LAYOUT_' + layout
|
layout_name = 'LAYOUT_' + layout
|
||||||
if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}):
|
if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}):
|
||||||
_log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name))
|
info_log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name))
|
||||||
|
|
||||||
# Check that the reported matrix size is consistent with the actual matrix size
|
# Check that the reported matrix size is consistent with the actual matrix size
|
||||||
_check_matrix(info_data)
|
_check_matrix(info_data)
|
||||||
@@ -130,7 +116,7 @@ def _extract_features(info_data, rules):
|
|||||||
info_data['features'] = {}
|
info_data['features'] = {}
|
||||||
|
|
||||||
if key in info_data['features']:
|
if key in info_data['features']:
|
||||||
_log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,))
|
info_log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,))
|
||||||
|
|
||||||
info_data['features'][key] = value
|
info_data['features'][key] = value
|
||||||
info_data['config_h_features'][key] = value
|
info_data['config_h_features'][key] = value
|
||||||
@@ -209,7 +195,7 @@ def _extract_split_main(info_data, config_c):
|
|||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
|
|
||||||
if 'main' in info_data['split']:
|
if 'main' in info_data['split']:
|
||||||
_log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_PIN) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
info_log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_PIN) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
||||||
|
|
||||||
info_data['split']['main'] = 'pin'
|
info_data['split']['main'] = 'pin'
|
||||||
|
|
||||||
@@ -218,7 +204,7 @@ def _extract_split_main(info_data, config_c):
|
|||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
|
|
||||||
if 'main' in info_data['split']:
|
if 'main' in info_data['split']:
|
||||||
_log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_MATRIX_GRID) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
info_log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_MATRIX_GRID) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
||||||
|
|
||||||
info_data['split']['main'] = 'matrix_grid'
|
info_data['split']['main'] = 'matrix_grid'
|
||||||
info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID'])
|
info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID'])
|
||||||
@@ -228,7 +214,7 @@ def _extract_split_main(info_data, config_c):
|
|||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
|
|
||||||
if 'main' in info_data['split']:
|
if 'main' in info_data['split']:
|
||||||
_log_warning(info_data, 'Split main hand is specified in both config.h (EE_HANDS) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
info_log_warning(info_data, 'Split main hand is specified in both config.h (EE_HANDS) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
||||||
|
|
||||||
info_data['split']['main'] = 'eeprom'
|
info_data['split']['main'] = 'eeprom'
|
||||||
|
|
||||||
@@ -237,7 +223,7 @@ def _extract_split_main(info_data, config_c):
|
|||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
|
|
||||||
if 'main' in info_data['split']:
|
if 'main' in info_data['split']:
|
||||||
_log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_RIGHT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
info_log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_RIGHT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
||||||
|
|
||||||
info_data['split']['main'] = 'right'
|
info_data['split']['main'] = 'right'
|
||||||
|
|
||||||
@@ -246,7 +232,7 @@ def _extract_split_main(info_data, config_c):
|
|||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
|
|
||||||
if 'main' in info_data['split']:
|
if 'main' in info_data['split']:
|
||||||
_log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_LEFT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
info_log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_LEFT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
|
||||||
|
|
||||||
info_data['split']['main'] = 'left'
|
info_data['split']['main'] = 'left'
|
||||||
|
|
||||||
@@ -261,7 +247,7 @@ def _extract_split_transport(info_data, config_c):
|
|||||||
info_data['split']['transport'] = {}
|
info_data['split']['transport'] = {}
|
||||||
|
|
||||||
if 'protocol' in info_data['split']['transport']:
|
if 'protocol' in info_data['split']['transport']:
|
||||||
_log_warning(info_data, 'Split transport is specified in both config.h (USE_I2C) and info.json (split.transport.protocol) (Value: %s), the config.h value wins.' % info_data['split']['transport'])
|
info_log_warning(info_data, 'Split transport is specified in both config.h (USE_I2C) and info.json (split.transport.protocol) (Value: %s), the config.h value wins.' % info_data['split']['transport'])
|
||||||
|
|
||||||
info_data['split']['transport']['protocol'] = 'i2c'
|
info_data['split']['transport']['protocol'] = 'i2c'
|
||||||
|
|
||||||
@@ -285,7 +271,7 @@ def _extract_split_right_pins(info_data, config_c):
|
|||||||
|
|
||||||
if row_pins and col_pins:
|
if row_pins and col_pins:
|
||||||
if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data:
|
if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data:
|
||||||
_log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
if 'split' not in info_data:
|
if 'split' not in info_data:
|
||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
@@ -303,7 +289,7 @@ def _extract_split_right_pins(info_data, config_c):
|
|||||||
|
|
||||||
if direct_pins:
|
if direct_pins:
|
||||||
if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}):
|
if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}):
|
||||||
_log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
if 'split' not in info_data:
|
if 'split' not in info_data:
|
||||||
info_data['split'] = {}
|
info_data['split'] = {}
|
||||||
@@ -341,7 +327,7 @@ def _extract_matrix_info(info_data, config_c):
|
|||||||
|
|
||||||
if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
|
if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
|
||||||
if 'matrix_size' in info_data:
|
if 'matrix_size' in info_data:
|
||||||
_log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
info_data['matrix_size'] = {
|
info_data['matrix_size'] = {
|
||||||
'cols': compute(config_c.get('MATRIX_COLS', '0')),
|
'cols': compute(config_c.get('MATRIX_COLS', '0')),
|
||||||
@@ -350,14 +336,14 @@ def _extract_matrix_info(info_data, config_c):
|
|||||||
|
|
||||||
if row_pins and col_pins:
|
if row_pins and col_pins:
|
||||||
if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
|
if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
|
||||||
_log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
info_snippet['cols'] = _extract_pins(col_pins)
|
info_snippet['cols'] = _extract_pins(col_pins)
|
||||||
info_snippet['rows'] = _extract_pins(row_pins)
|
info_snippet['rows'] = _extract_pins(row_pins)
|
||||||
|
|
||||||
if direct_pins:
|
if direct_pins:
|
||||||
if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:
|
if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:
|
||||||
_log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
info_snippet['direct'] = _extract_direct_matrix(direct_pins)
|
info_snippet['direct'] = _extract_direct_matrix(direct_pins)
|
||||||
|
|
||||||
@@ -369,7 +355,7 @@ def _extract_matrix_info(info_data, config_c):
|
|||||||
|
|
||||||
if config_c.get('CUSTOM_MATRIX', 'no') != 'no':
|
if config_c.get('CUSTOM_MATRIX', 'no') != 'no':
|
||||||
if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
|
if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
|
||||||
_log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.')
|
info_log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
info_snippet['custom'] = True
|
info_snippet['custom'] = True
|
||||||
|
|
||||||
@@ -398,7 +384,7 @@ def _extract_config_h(info_data):
|
|||||||
try:
|
try:
|
||||||
if config_key in config_c and info_dict.get('to_json', True):
|
if config_key in config_c and info_dict.get('to_json', True):
|
||||||
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
||||||
_log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key))
|
info_log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key))
|
||||||
|
|
||||||
if key_type.startswith('array'):
|
if key_type.startswith('array'):
|
||||||
if '.' in key_type:
|
if '.' in key_type:
|
||||||
@@ -429,7 +415,7 @@ def _extract_config_h(info_data):
|
|||||||
dotty_info[info_key] = config_c[config_key]
|
dotty_info[info_key] = config_c[config_key]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log_warning(info_data, f'{config_key}->{info_key}: {e}')
|
info_log_warning(info_data, f'{config_key}->{info_key}: {e}')
|
||||||
|
|
||||||
info_data.update(dotty_info)
|
info_data.update(dotty_info)
|
||||||
|
|
||||||
@@ -470,7 +456,7 @@ def _extract_rules_mk(info_data):
|
|||||||
try:
|
try:
|
||||||
if rules_key in rules and info_dict.get('to_json', True):
|
if rules_key in rules and info_dict.get('to_json', True):
|
||||||
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
||||||
_log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key))
|
info_log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key))
|
||||||
|
|
||||||
if key_type.startswith('array'):
|
if key_type.startswith('array'):
|
||||||
if '.' in key_type:
|
if '.' in key_type:
|
||||||
@@ -501,7 +487,7 @@ def _extract_rules_mk(info_data):
|
|||||||
dotty_info[info_key] = rules[rules_key]
|
dotty_info[info_key] = rules[rules_key]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log_warning(info_data, f'{rules_key}->{info_key}: {e}')
|
info_log_warning(info_data, f'{rules_key}->{info_key}: {e}')
|
||||||
|
|
||||||
info_data.update(dotty_info)
|
info_data.update(dotty_info)
|
||||||
|
|
||||||
@@ -544,11 +530,11 @@ def _check_matrix(info_data):
|
|||||||
|
|
||||||
if col_count != actual_col_count and col_count != (actual_col_count / 2):
|
if col_count != actual_col_count and col_count != (actual_col_count / 2):
|
||||||
# FIXME: once we can we should detect if split is enabled to do the actual_col_count/2 check.
|
# FIXME: once we can we should detect if split is enabled to do the actual_col_count/2 check.
|
||||||
_log_error(info_data, f'MATRIX_COLS is inconsistent with the size of MATRIX_COL_PINS: {col_count} != {actual_col_count}')
|
info_log_error(info_data, f'MATRIX_COLS is inconsistent with the size of MATRIX_COL_PINS: {col_count} != {actual_col_count}')
|
||||||
|
|
||||||
if row_count != actual_row_count and row_count != (actual_row_count / 2):
|
if row_count != actual_row_count and row_count != (actual_row_count / 2):
|
||||||
# FIXME: once we can we should detect if split is enabled to do the actual_row_count/2 check.
|
# FIXME: once we can we should detect if split is enabled to do the actual_row_count/2 check.
|
||||||
_log_error(info_data, f'MATRIX_ROWS is inconsistent with the size of MATRIX_ROW_PINS: {row_count} != {actual_row_count}')
|
info_log_error(info_data, f'MATRIX_ROWS is inconsistent with the size of MATRIX_ROW_PINS: {row_count} != {actual_row_count}')
|
||||||
|
|
||||||
|
|
||||||
def _search_keyboard_h(keyboard):
|
def _search_keyboard_h(keyboard):
|
||||||
@@ -577,7 +563,7 @@ def _find_missing_layouts(info_data, keyboard):
|
|||||||
|
|
||||||
If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above.
|
If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above.
|
||||||
"""
|
"""
|
||||||
_log_warning(info_data, '%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
|
info_log_warning(info_data, '%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
|
||||||
|
|
||||||
for file in glob('keyboards/%s/*.h' % keyboard):
|
for file in glob('keyboards/%s/*.h' % keyboard):
|
||||||
these_layouts, these_aliases = find_layouts(file)
|
these_layouts, these_aliases = find_layouts(file)
|
||||||
@@ -596,20 +582,6 @@ def _find_missing_layouts(info_data, keyboard):
|
|||||||
info_data['layout_aliases'][alias] = alias_text
|
info_data['layout_aliases'][alias] = alias_text
|
||||||
|
|
||||||
|
|
||||||
def _log_error(info_data, message):
|
|
||||||
"""Send an error message to both JSON and the log.
|
|
||||||
"""
|
|
||||||
info_data['parse_errors'].append(message)
|
|
||||||
cli.log.error('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
|
|
||||||
|
|
||||||
|
|
||||||
def _log_warning(info_data, message):
|
|
||||||
"""Send a warning message to both JSON and the log.
|
|
||||||
"""
|
|
||||||
info_data['parse_warnings'].append(message)
|
|
||||||
cli.log.warning('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
|
|
||||||
|
|
||||||
|
|
||||||
def arm_processor_rules(info_data, rules):
|
def arm_processor_rules(info_data, rules):
|
||||||
"""Setup the default info for an ARM board.
|
"""Setup the default info for an ARM board.
|
||||||
"""
|
"""
|
||||||
@@ -668,7 +640,7 @@ def merge_info_jsons(keyboard, info_data):
|
|||||||
new_info_data = json_load(info_file)
|
new_info_data = json_load(info_file)
|
||||||
|
|
||||||
if not isinstance(new_info_data, dict):
|
if not isinstance(new_info_data, dict):
|
||||||
_log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
|
info_log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -686,13 +658,13 @@ def merge_info_jsons(keyboard, info_data):
|
|||||||
|
|
||||||
for layout_name, layout in new_info_data.get('layouts', {}).items():
|
for layout_name, layout in new_info_data.get('layouts', {}).items():
|
||||||
if layout_name in info_data.get('layout_aliases', {}):
|
if layout_name in info_data.get('layout_aliases', {}):
|
||||||
_log_warning(info_data, f"info.json uses alias name {layout_name} instead of {info_data['layout_aliases'][layout_name]}")
|
info_log_warning(info_data, f"info.json uses alias name {layout_name} instead of {info_data['layout_aliases'][layout_name]}")
|
||||||
layout_name = info_data['layout_aliases'][layout_name]
|
layout_name = info_data['layout_aliases'][layout_name]
|
||||||
|
|
||||||
if layout_name in info_data['layouts']:
|
if layout_name in info_data['layouts']:
|
||||||
if len(info_data['layouts'][layout_name]['layout']) != len(layout['layout']):
|
if len(info_data['layouts'][layout_name]['layout']) != len(layout['layout']):
|
||||||
msg = '%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s'
|
msg = '%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s'
|
||||||
_log_error(info_data, msg % (info_data['keyboard_folder'], layout_name, len(layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
|
info_log_error(info_data, msg % (info_data['keyboard_folder'], layout_name, len(layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
|
||||||
else:
|
else:
|
||||||
for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']):
|
for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']):
|
||||||
existing_key.update(new_key)
|
existing_key.update(new_key)
|
||||||
@@ -720,15 +692,18 @@ def find_info_json(keyboard):
|
|||||||
|
|
||||||
# Add DEFAULT_FOLDER before parents, if present
|
# Add DEFAULT_FOLDER before parents, if present
|
||||||
rules = rules_mk(keyboard)
|
rules = rules_mk(keyboard)
|
||||||
|
|
||||||
if 'DEFAULT_FOLDER' in rules:
|
if 'DEFAULT_FOLDER' in rules:
|
||||||
info_jsons.append(Path(rules['DEFAULT_FOLDER']) / 'info.json')
|
info_jsons.append(Path(rules['DEFAULT_FOLDER']) / 'info.json')
|
||||||
|
|
||||||
# Add in parent folders for least specific
|
# Add in parent folders for least specific
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
info_jsons.append(keyboard_parent / 'info.json')
|
this_info_json = keyboard_parent / 'info.json'
|
||||||
|
|
||||||
|
if this_info_json.exists():
|
||||||
|
yield this_info_json
|
||||||
|
|
||||||
if keyboard_parent.parent == base_path:
|
if keyboard_parent.parent == base_path:
|
||||||
break
|
break
|
||||||
keyboard_parent = keyboard_parent.parent
|
|
||||||
|
|
||||||
# Return a list of the info.json files that actually exist
|
keyboard_parent = keyboard_parent.parent
|
||||||
return [info_json for info_json in info_jsons if info_json.exists()]
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import jsonschema
|
|||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def json_load(json_file):
|
def json_load(json_file):
|
||||||
"""Load a json file from disk.
|
"""Load a json file from disk.
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ def json_load(json_file):
|
|||||||
exit(1)
|
exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
cli.log.error('Unknown error attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
|
cli.log.error('Unknown error attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
|
||||||
|
if cli.args.verbose:
|
||||||
|
cli.log.exception(e)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"""Functions that help us work with keyboards.
|
"""Functions that help us work with keyboards.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
from array import array
|
from array import array
|
||||||
|
from functools import lru_cache
|
||||||
|
from glob import glob
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
|
||||||
from glob import glob
|
|
||||||
|
|
||||||
import qmk.path
|
import qmk.path
|
||||||
from qmk.c_parse import parse_config_h_file
|
from qmk.c_parse import parse_config_h_file
|
||||||
@@ -64,6 +65,17 @@ def find_readme(keyboard):
|
|||||||
return cur_dir / 'readme.md'
|
return cur_dir / 'readme.md'
|
||||||
|
|
||||||
|
|
||||||
|
def is_keyboard_target(keyboard_target):
|
||||||
|
"""Checks to make sure the supplied keyboard_target is valid.
|
||||||
|
|
||||||
|
This is mainly used by commands that accept --keyboard.
|
||||||
|
"""
|
||||||
|
if keyboard_target in ['all', 'all-avr', 'all-chibios', 'all-arm_atsam']:
|
||||||
|
return keyboard_target
|
||||||
|
|
||||||
|
return keyboard_folder(keyboard_target)
|
||||||
|
|
||||||
|
|
||||||
def keyboard_folder(keyboard):
|
def keyboard_folder(keyboard):
|
||||||
"""Returns the actual keyboard folder.
|
"""Returns the actual keyboard folder.
|
||||||
|
|
||||||
@@ -206,6 +218,7 @@ def render_layout(layout_data, render_ascii, key_labels=None):
|
|||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def render_layouts(info_json, render_ascii):
|
def render_layouts(info_json, render_ascii):
|
||||||
"""Renders all the layouts from an `info_json` structure.
|
"""Renders all the layouts from an `info_json` structure.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import DEVNULL
|
from subprocess import DEVNULL
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ from pygments import lex
|
|||||||
import qmk.path
|
import qmk.path
|
||||||
from qmk.keyboard import find_keyboard_from_dir, rules_mk
|
from qmk.keyboard import find_keyboard_from_dir, rules_mk
|
||||||
from qmk.errors import CppError
|
from qmk.errors import CppError
|
||||||
|
from qmk.metadata import basic_info_json
|
||||||
|
|
||||||
# The `keymap.c` template to use when a keyboard doesn't have its own
|
# The `keymap.c` template to use when a keyboard doesn't have its own
|
||||||
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
|
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
|
||||||
@@ -30,6 +32,7 @@ __KEYMAP_GOES_HERE__
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def template_json(keyboard):
|
def template_json(keyboard):
|
||||||
"""Returns a `keymap.json` template for a keyboard.
|
"""Returns a `keymap.json` template for a keyboard.
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ def template_json(keyboard):
|
|||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def template_c(keyboard):
|
def template_c(keyboard):
|
||||||
"""Returns a `keymap.c` template for a keyboard.
|
"""Returns a `keymap.c` template for a keyboard.
|
||||||
|
|
||||||
@@ -122,6 +126,7 @@ def keymap_completer(prefix, action, parser, parsed_args):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def is_keymap_dir(keymap, c=True, json=True, additional_files=None):
|
def is_keymap_dir(keymap, c=True, json=True, additional_files=None):
|
||||||
"""Return True if Path object `keymap` has a keymap file inside.
|
"""Return True if Path object `keymap` has a keymap file inside.
|
||||||
|
|
||||||
@@ -180,6 +185,7 @@ def generate_json(keymap, keyboard, layout, layers):
|
|||||||
return new_keymap
|
return new_keymap
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def generate_c(keyboard, layout, layers):
|
def generate_c(keyboard, layout, layers):
|
||||||
"""Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers.
|
"""Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers.
|
||||||
|
|
||||||
@@ -266,6 +272,7 @@ def write(keyboard, keymap, layout, layers):
|
|||||||
return write_file(keymap_file, keymap_content)
|
return write_file(keymap_file, keymap_content)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def locate_keymap(keyboard, keymap):
|
def locate_keymap(keyboard, keymap):
|
||||||
"""Returns the path to a keymap for a specific keyboard.
|
"""Returns the path to a keymap for a specific keyboard.
|
||||||
"""
|
"""
|
||||||
@@ -305,6 +312,7 @@ def locate_keymap(keyboard, keymap):
|
|||||||
return community_layout / 'keymap.c'
|
return community_layout / 'keymap.c'
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False):
|
def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False):
|
||||||
"""List the available keymaps for a keyboard.
|
"""List the available keymaps for a keyboard.
|
||||||
|
|
||||||
@@ -327,13 +335,11 @@ def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=Fa
|
|||||||
Returns:
|
Returns:
|
||||||
a sorted list of valid keymap names.
|
a sorted list of valid keymap names.
|
||||||
"""
|
"""
|
||||||
# parse all the rules.mk files for the keyboard
|
info_data = basic_info_json(keyboard)
|
||||||
rules = rules_mk(keyboard)
|
|
||||||
names = set()
|
names = set()
|
||||||
|
|
||||||
if rules:
|
|
||||||
keyboards_dir = Path('keyboards')
|
keyboards_dir = Path('keyboards')
|
||||||
kb_path = keyboards_dir / keyboard
|
kb_path = keyboards_dir / info_data['keyboard_folder']
|
||||||
|
|
||||||
# walk up the directory tree until keyboards_dir
|
# walk up the directory tree until keyboards_dir
|
||||||
# and collect all directories' name with keymap.c file in it
|
# and collect all directories' name with keymap.c file in it
|
||||||
@@ -349,8 +355,7 @@ def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=Fa
|
|||||||
kb_path = kb_path.parent
|
kb_path = kb_path.parent
|
||||||
|
|
||||||
# if community layouts are supported, get them
|
# if community layouts are supported, get them
|
||||||
if "LAYOUTS" in rules:
|
for layout in info_data.get('community_layouts', []):
|
||||||
for layout in rules["LAYOUTS"].split():
|
|
||||||
cl_path = Path('layouts/community') / layout
|
cl_path = Path('layouts/community') / layout
|
||||||
if cl_path.is_dir():
|
if cl_path.is_dir():
|
||||||
for keymap in cl_path.iterdir():
|
for keymap in cl_path.iterdir():
|
||||||
@@ -361,6 +366,7 @@ def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=Fa
|
|||||||
return sorted(names)
|
return sorted(names)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _c_preprocess(path, stdin=DEVNULL):
|
def _c_preprocess(path, stdin=DEVNULL):
|
||||||
""" Run a file through the C pre-processor
|
""" Run a file through the C pre-processor
|
||||||
|
|
||||||
@@ -380,6 +386,7 @@ def _c_preprocess(path, stdin=DEVNULL):
|
|||||||
return pre_processed_keymap.stdout
|
return pre_processed_keymap.stdout
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _get_layers(keymap): # noqa C901 : until someone has a good idea how to simplify/split up this code
|
def _get_layers(keymap): # noqa C901 : until someone has a good idea how to simplify/split up this code
|
||||||
""" Find the layers in a keymap.c file.
|
""" Find the layers in a keymap.c file.
|
||||||
|
|
||||||
@@ -500,6 +507,7 @@ def _get_layers(keymap): # noqa C901 : until someone has a good idea how to sim
|
|||||||
return layers
|
return layers
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def parse_keymap_c(keymap_file, use_cpp=True):
|
def parse_keymap_c(keymap_file, use_cpp=True):
|
||||||
""" Parse a keymap.c file.
|
""" Parse a keymap.c file.
|
||||||
|
|
||||||
@@ -529,6 +537,7 @@ def parse_keymap_c(keymap_file, use_cpp=True):
|
|||||||
return keymap
|
return keymap
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def c2json(keyboard, keymap, keymap_file, use_cpp=True):
|
def c2json(keyboard, keymap, keymap_file, use_cpp=True):
|
||||||
""" Convert keymap.c to keymap.json
|
""" Convert keymap.c to keymap.json
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
""" Functions for working with Makefiles
|
""" Functions for working with Makefiles
|
||||||
"""
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def parse_rules_mk_file(file, rules_mk=None):
|
def parse_rules_mk_file(file, rules_mk=None):
|
||||||
"""Turn a rules.mk file into a dictionary.
|
"""Turn a rules.mk file into a dictionary.
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,22 @@
|
|||||||
Gratefully copied from https://stackoverflow.com/a/9558001
|
Gratefully copied from https://stackoverflow.com/a/9558001
|
||||||
"""
|
"""
|
||||||
import ast
|
import ast
|
||||||
import operator as op
|
import operator
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
# supported operators
|
# supported operators
|
||||||
operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor, ast.USub: op.neg}
|
operators = {
|
||||||
|
ast.Add: operator.add,
|
||||||
|
ast.Sub: operator.sub,
|
||||||
|
ast.Mult: operator.mul,
|
||||||
|
ast.Div: operator.truediv,
|
||||||
|
ast.Pow: operator.pow,
|
||||||
|
ast.BitXor: operator.xor,
|
||||||
|
ast.USub: operator.neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def compute(expr):
|
def compute(expr):
|
||||||
"""Parse a mathematical expression and return the answer.
|
"""Parse a mathematical expression and return the answer.
|
||||||
|
|
||||||
@@ -22,6 +32,7 @@ def compute(expr):
|
|||||||
return _eval(ast.parse(expr, mode='eval').body)
|
return _eval(ast.parse(expr, mode='eval').body)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def _eval(node):
|
def _eval(node):
|
||||||
if isinstance(node, ast.Num): # <number>
|
if isinstance(node, ast.Num): # <number>
|
||||||
return node.n
|
return node.n
|
||||||
|
|||||||
533
lib/python/qmk/metadata.py
Normal file
533
lib/python/qmk/metadata.py
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
"""Functions that help us generate and use info.json files.
|
||||||
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
|
from glob import glob
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
from dotty_dict import dotty
|
||||||
|
from milc import cli
|
||||||
|
|
||||||
|
from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
|
||||||
|
from qmk.c_parse import find_layouts
|
||||||
|
from qmk.json_schema import deep_update, json_load, validate
|
||||||
|
from qmk.keyboard import config_h, rules_mk
|
||||||
|
from qmk.makefile import parse_rules_mk_file
|
||||||
|
from qmk.math import compute
|
||||||
|
|
||||||
|
true_values = ['1', 'on', 'yes', 'true']
|
||||||
|
false_values = ['0', 'off', 'no', 'false']
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def basic_info_json(keyboard):
|
||||||
|
"""Generate a subset of info.json for a specific keyboard.
|
||||||
|
|
||||||
|
This does minimal validation, and should only be used as needed to avoid loops or when performance is critical.
|
||||||
|
"""
|
||||||
|
cur_dir = Path('keyboards')
|
||||||
|
rules = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk')
|
||||||
|
|
||||||
|
if 'DEFAULT_FOLDER' in rules:
|
||||||
|
keyboard = rules['DEFAULT_FOLDER']
|
||||||
|
rules = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk', rules)
|
||||||
|
|
||||||
|
info_data = {
|
||||||
|
'keyboard_name': str(keyboard),
|
||||||
|
'keyboard_folder': str(keyboard),
|
||||||
|
'keymaps': {},
|
||||||
|
'layouts': {},
|
||||||
|
'parse_errors': [],
|
||||||
|
'parse_warnings': [],
|
||||||
|
'maintainer': 'qmk',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Populate layout data
|
||||||
|
layouts, aliases = _find_all_layouts(info_data, keyboard)
|
||||||
|
|
||||||
|
if aliases:
|
||||||
|
info_data['layout_aliases'] = aliases
|
||||||
|
|
||||||
|
for layout_name, layout_json in layouts.items():
|
||||||
|
if not layout_name.startswith('LAYOUT_kc'):
|
||||||
|
layout_json['c_macro'] = True
|
||||||
|
info_data['layouts'][layout_name] = layout_json
|
||||||
|
|
||||||
|
# Merge in the data from info.json, config.h, and rules.mk
|
||||||
|
info_data = merge_info_jsons(keyboard, info_data)
|
||||||
|
info_data = _extract_config_h(info_data)
|
||||||
|
info_data = _extract_rules_mk(info_data)
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_features(info_data, rules):
|
||||||
|
"""Find all the features enabled in rules.mk.
|
||||||
|
"""
|
||||||
|
# Special handling for bootmagic which also supports a "lite" mode.
|
||||||
|
if rules.get('BOOTMAGIC_ENABLE') == 'lite':
|
||||||
|
rules['BOOTMAGIC_LITE_ENABLE'] = 'on'
|
||||||
|
del rules['BOOTMAGIC_ENABLE']
|
||||||
|
if rules.get('BOOTMAGIC_ENABLE') == 'full':
|
||||||
|
rules['BOOTMAGIC_ENABLE'] = 'on'
|
||||||
|
|
||||||
|
# Skip non-boolean features we haven't implemented special handling for
|
||||||
|
for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE':
|
||||||
|
if rules.get(feature):
|
||||||
|
del rules[feature]
|
||||||
|
|
||||||
|
# Process the rest of the rules as booleans
|
||||||
|
for key, value in rules.items():
|
||||||
|
if key.endswith('_ENABLE'):
|
||||||
|
key = '_'.join(key.split('_')[:-1]).lower()
|
||||||
|
value = True if value.lower() in true_values else False if value.lower() in false_values else value
|
||||||
|
|
||||||
|
if 'config_h_features' not in info_data:
|
||||||
|
info_data['config_h_features'] = {}
|
||||||
|
|
||||||
|
if 'features' not in info_data:
|
||||||
|
info_data['features'] = {}
|
||||||
|
|
||||||
|
if key in info_data['features']:
|
||||||
|
info_log_warning(info_data, 'Feature %s is specified in both info.json and rules.mk, the rules.mk value wins.' % (key,))
|
||||||
|
|
||||||
|
info_data['features'][key] = value
|
||||||
|
info_data['config_h_features'][key] = value
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def _pin_name(pin):
|
||||||
|
"""Returns the proper representation for a pin.
|
||||||
|
"""
|
||||||
|
pin = pin.strip()
|
||||||
|
|
||||||
|
if not pin:
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif pin.isdigit():
|
||||||
|
return int(pin)
|
||||||
|
|
||||||
|
elif pin == 'NO_PIN':
|
||||||
|
return None
|
||||||
|
|
||||||
|
return pin
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def _extract_pins(pins):
|
||||||
|
"""Returns a list of pins from a comma separated string of pins.
|
||||||
|
"""
|
||||||
|
return [_pin_name(pin) for pin in pins.split(',')]
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_direct_matrix(info_data, direct_pins):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
info_data['matrix_pins'] = {}
|
||||||
|
direct_pin_array = []
|
||||||
|
|
||||||
|
while direct_pins[-1] != '}':
|
||||||
|
direct_pins = direct_pins[:-1]
|
||||||
|
|
||||||
|
for row in direct_pins.split('},{'):
|
||||||
|
if row.startswith('{'):
|
||||||
|
row = row[1:]
|
||||||
|
|
||||||
|
if row.endswith('}'):
|
||||||
|
row = row[:-1]
|
||||||
|
|
||||||
|
direct_pin_array.append([])
|
||||||
|
|
||||||
|
for pin in row.split(','):
|
||||||
|
if pin == 'NO_PIN':
|
||||||
|
pin = None
|
||||||
|
|
||||||
|
direct_pin_array[-1].append(pin)
|
||||||
|
|
||||||
|
return direct_pin_array
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_matrix_info(info_data, config_c):
|
||||||
|
"""Populate the matrix information.
|
||||||
|
"""
|
||||||
|
row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
|
||||||
|
col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
|
||||||
|
direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
|
||||||
|
|
||||||
|
if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
|
||||||
|
if 'matrix_size' in info_data:
|
||||||
|
info_log_warning(info_data, 'Matrix size is specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
|
info_data['matrix_size'] = {
|
||||||
|
'cols': compute(config_c.get('MATRIX_COLS', '0')),
|
||||||
|
'rows': compute(config_c.get('MATRIX_ROWS', '0')),
|
||||||
|
}
|
||||||
|
|
||||||
|
if row_pins and col_pins:
|
||||||
|
if 'matrix_pins' in info_data:
|
||||||
|
info_log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
|
info_data['matrix_pins'] = {
|
||||||
|
'cols': _extract_pins(col_pins),
|
||||||
|
'rows': _extract_pins(row_pins),
|
||||||
|
}
|
||||||
|
|
||||||
|
if direct_pins:
|
||||||
|
if 'matrix_pins' in info_data:
|
||||||
|
info_log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
|
||||||
|
|
||||||
|
info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins)
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_config_h(info_data):
|
||||||
|
"""Pull some keyboard information from existing config.h files
|
||||||
|
"""
|
||||||
|
config_c = config_h(info_data['keyboard_folder'])
|
||||||
|
|
||||||
|
# Pull in data from the json map
|
||||||
|
dotty_info = dotty(info_data)
|
||||||
|
info_config_map = json_load(Path('data/mappings/info_config.json'))
|
||||||
|
|
||||||
|
for config_key, info_dict in info_config_map.items():
|
||||||
|
info_key = info_dict['info_key']
|
||||||
|
key_type = info_dict.get('value_type', 'str')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if config_key in config_c and info_dict.get('to_json', True):
|
||||||
|
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
||||||
|
info_log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key))
|
||||||
|
|
||||||
|
if key_type.startswith('array'):
|
||||||
|
if '.' in key_type:
|
||||||
|
key_type, array_type = key_type.split('.', 1)
|
||||||
|
else:
|
||||||
|
array_type = None
|
||||||
|
|
||||||
|
config_value = config_c[config_key].replace('{', '').replace('}', '').strip()
|
||||||
|
|
||||||
|
if array_type == 'int':
|
||||||
|
dotty_info[info_key] = list(map(int, config_value.split(',')))
|
||||||
|
else:
|
||||||
|
dotty_info[info_key] = config_value.split(',')
|
||||||
|
|
||||||
|
elif key_type == 'bool':
|
||||||
|
dotty_info[info_key] = config_c[config_key] in true_values
|
||||||
|
|
||||||
|
elif key_type == 'hex':
|
||||||
|
dotty_info[info_key] = '0x' + config_c[config_key][2:].upper()
|
||||||
|
|
||||||
|
elif key_type == 'list':
|
||||||
|
dotty_info[info_key] = config_c[config_key].split()
|
||||||
|
|
||||||
|
elif key_type == 'int':
|
||||||
|
dotty_info[info_key] = int(config_c[config_key])
|
||||||
|
|
||||||
|
else:
|
||||||
|
dotty_info[info_key] = config_c[config_key]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
info_log_warning(info_data, f'{config_key}->{info_key}: {e}')
|
||||||
|
|
||||||
|
info_data.update(dotty_info)
|
||||||
|
|
||||||
|
# Pull data that easily can't be mapped in json
|
||||||
|
_extract_matrix_info(info_data, config_c)
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_rules_mk(info_data):
|
||||||
|
"""Pull some keyboard information from existing rules.mk files
|
||||||
|
"""
|
||||||
|
rules = rules_mk(info_data['keyboard_folder'])
|
||||||
|
info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4'))
|
||||||
|
|
||||||
|
if info_data['processor'] in CHIBIOS_PROCESSORS:
|
||||||
|
arm_processor_rules(info_data, rules)
|
||||||
|
|
||||||
|
elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS:
|
||||||
|
avr_processor_rules(info_data, rules)
|
||||||
|
|
||||||
|
else:
|
||||||
|
cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor']))
|
||||||
|
unknown_processor_rules(info_data, rules)
|
||||||
|
|
||||||
|
# Pull in data from the json map
|
||||||
|
dotty_info = dotty(info_data)
|
||||||
|
info_rules_map = json_load(Path('data/mappings/info_rules.json'))
|
||||||
|
|
||||||
|
for rules_key, info_dict in info_rules_map.items():
|
||||||
|
info_key = info_dict['info_key']
|
||||||
|
key_type = info_dict.get('value_type', 'str')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if rules_key in rules and info_dict.get('to_json', True):
|
||||||
|
if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True):
|
||||||
|
info_log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key))
|
||||||
|
|
||||||
|
if key_type.startswith('array'):
|
||||||
|
if '.' in key_type:
|
||||||
|
key_type, array_type = key_type.split('.', 1)
|
||||||
|
else:
|
||||||
|
array_type = None
|
||||||
|
|
||||||
|
rules_value = rules[rules_key].replace('{', '').replace('}', '').strip()
|
||||||
|
|
||||||
|
if array_type == 'int':
|
||||||
|
dotty_info[info_key] = list(map(int, rules_value.split(',')))
|
||||||
|
else:
|
||||||
|
dotty_info[info_key] = rules_value.split(',')
|
||||||
|
|
||||||
|
elif key_type == 'list':
|
||||||
|
dotty_info[info_key] = rules[rules_key].split()
|
||||||
|
|
||||||
|
elif key_type == 'bool':
|
||||||
|
dotty_info[info_key] = rules[rules_key] in true_values
|
||||||
|
|
||||||
|
elif key_type == 'hex':
|
||||||
|
dotty_info[info_key] = '0x' + rules[rules_key][2:].upper()
|
||||||
|
|
||||||
|
elif key_type == 'int':
|
||||||
|
dotty_info[info_key] = int(rules[rules_key])
|
||||||
|
|
||||||
|
else:
|
||||||
|
dotty_info[info_key] = rules[rules_key]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
info_log_warning(info_data, f'{rules_key}->{info_key}: {e}')
|
||||||
|
|
||||||
|
info_data.update(dotty_info)
|
||||||
|
|
||||||
|
# Merge in config values that can't be easily mapped
|
||||||
|
_extract_features(info_data, rules)
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def _search_keyboard_h(path):
|
||||||
|
current_path = Path('keyboards/')
|
||||||
|
aliases = {}
|
||||||
|
layouts = {}
|
||||||
|
|
||||||
|
for directory in path.parts:
|
||||||
|
current_path = current_path / directory
|
||||||
|
keyboard_h = '%s.h' % (directory,)
|
||||||
|
keyboard_h_path = current_path / keyboard_h
|
||||||
|
if keyboard_h_path.exists():
|
||||||
|
new_layouts, new_aliases = find_layouts(keyboard_h_path)
|
||||||
|
layouts.update(new_layouts)
|
||||||
|
|
||||||
|
for alias, alias_text in new_aliases.items():
|
||||||
|
if alias_text in layouts:
|
||||||
|
aliases[alias] = alias_text
|
||||||
|
|
||||||
|
return layouts, aliases
|
||||||
|
|
||||||
|
|
||||||
|
def _find_all_layouts(info_data, keyboard):
|
||||||
|
"""Looks for layout macros associated with this keyboard.
|
||||||
|
"""
|
||||||
|
layouts, aliases = _search_keyboard_h(Path(keyboard))
|
||||||
|
|
||||||
|
if not layouts:
|
||||||
|
# If we don't find any layouts from info.json or keyboard.h we widen our search. This is error prone which is why we want to encourage people to follow the standard above.
|
||||||
|
info_data['parse_warnings'].append('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard))
|
||||||
|
layouts, new_aliases = _deep_search_layouts(keyboard)
|
||||||
|
aliases.update(new_aliases)
|
||||||
|
|
||||||
|
return layouts, aliases
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def _deep_search_layouts(keyboard):
|
||||||
|
"""Do a wider (error-prone) search for layout macros.
|
||||||
|
"""
|
||||||
|
layouts = {}
|
||||||
|
aliases = {}
|
||||||
|
|
||||||
|
for file in glob('keyboards/%s/*.h' % keyboard):
|
||||||
|
if file.endswith('.h'):
|
||||||
|
these_layouts, these_aliases = find_layouts(file)
|
||||||
|
|
||||||
|
if these_layouts:
|
||||||
|
layouts.update(these_layouts)
|
||||||
|
|
||||||
|
for alias, alias_text in these_aliases.items():
|
||||||
|
if alias_text in layouts:
|
||||||
|
aliases[alias] = alias_text
|
||||||
|
|
||||||
|
return layouts, aliases
|
||||||
|
|
||||||
|
|
||||||
|
def info_log_error(info_data, message):
|
||||||
|
"""Send an error message to both JSON and the log.
|
||||||
|
"""
|
||||||
|
info_data['parse_errors'].append(message)
|
||||||
|
cli.log.error('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
|
||||||
|
|
||||||
|
|
||||||
|
def info_log_warning(info_data, message):
|
||||||
|
"""Send a warning message to both JSON and the log.
|
||||||
|
"""
|
||||||
|
info_data['parse_warnings'].append(message)
|
||||||
|
cli.log.warning('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
|
||||||
|
|
||||||
|
|
||||||
|
def arm_processor_rules(info_data, rules):
|
||||||
|
"""Setup the default info for an ARM board.
|
||||||
|
"""
|
||||||
|
info_data['processor_type'] = 'arm'
|
||||||
|
info_data['protocol'] = 'ChibiOS'
|
||||||
|
|
||||||
|
if 'bootloader' not in info_data:
|
||||||
|
if 'STM32' in info_data['processor']:
|
||||||
|
info_data['bootloader'] = 'stm32-dfu'
|
||||||
|
else:
|
||||||
|
info_data['bootloader'] = 'unknown'
|
||||||
|
|
||||||
|
if 'STM32' in info_data['processor']:
|
||||||
|
info_data['platform'] = 'STM32'
|
||||||
|
elif 'MCU_SERIES' in rules:
|
||||||
|
info_data['platform'] = rules['MCU_SERIES']
|
||||||
|
elif 'ARM_ATSAM' in rules:
|
||||||
|
info_data['platform'] = 'ARM_ATSAM'
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def avr_processor_rules(info_data, rules):
|
||||||
|
"""Setup the default info for an AVR board.
|
||||||
|
"""
|
||||||
|
info_data['processor_type'] = 'avr'
|
||||||
|
info_data['platform'] = rules['ARCH'] if 'ARCH' in rules else 'unknown'
|
||||||
|
info_data['protocol'] = 'V-USB' if rules.get('MCU') in VUSB_PROCESSORS else 'LUFA'
|
||||||
|
|
||||||
|
if 'bootloader' not in info_data:
|
||||||
|
info_data['bootloader'] = 'atmel-dfu'
|
||||||
|
|
||||||
|
# FIXME(fauxpark/anyone): Eventually we should detect the protocol by looking at PROTOCOL inherited from mcu_selection.mk:
|
||||||
|
# info_data['protocol'] = 'V-USB' if rules.get('PROTOCOL') == 'VUSB' else 'LUFA'
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def unknown_processor_rules(info_data, rules):
|
||||||
|
"""Setup the default keyboard info for unknown boards.
|
||||||
|
"""
|
||||||
|
info_data['bootloader'] = 'unknown'
|
||||||
|
info_data['platform'] = 'unknown'
|
||||||
|
info_data['processor'] = 'unknown'
|
||||||
|
info_data['processor_type'] = 'unknown'
|
||||||
|
info_data['protocol'] = 'unknown'
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
def store_mtime(file):
|
||||||
|
"""Stores the mtime for a json file.
|
||||||
|
"""
|
||||||
|
cli.log.debug('store_mtime(%s)', file)
|
||||||
|
mtime = str(os.stat(file).st_mtime)
|
||||||
|
cache_file = f'.build/json_times/{file}'
|
||||||
|
cache_dir = os.path.dirname(cache_file)
|
||||||
|
os.makedirs(cache_dir, exist_ok=True)
|
||||||
|
with open(cache_file, 'w') as fd:
|
||||||
|
fd.write(mtime)
|
||||||
|
|
||||||
|
|
||||||
|
def has_been_validated(file):
|
||||||
|
"""Returns True if file is in the json cache.
|
||||||
|
"""
|
||||||
|
cli.log.debug('has_been_validated(%s)', file)
|
||||||
|
mtime_file = f'.build/json_times/{file}'
|
||||||
|
if os.path.exists(mtime_file):
|
||||||
|
with open(mtime_file) as fd:
|
||||||
|
cache_mtime = fd.read()
|
||||||
|
cache_mtime = cache_mtime.strip()
|
||||||
|
file_mtime = str(os.stat(file).st_mtime)
|
||||||
|
if cache_mtime == file_mtime:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
os.remove(mtime_file)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def merge_info_jsons(keyboard, info_data):
|
||||||
|
"""Return a merged copy of all the info.json files for a keyboard.
|
||||||
|
"""
|
||||||
|
for info_file in find_info_json(keyboard):
|
||||||
|
# Load and validate the JSON data
|
||||||
|
new_info_data = json_load(info_file)
|
||||||
|
|
||||||
|
if not isinstance(new_info_data, dict):
|
||||||
|
info_log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not has_been_validated(info_file):
|
||||||
|
try:
|
||||||
|
validate(new_info_data, 'qmk.keyboard.v1')
|
||||||
|
except jsonschema.ValidationError as e:
|
||||||
|
json_path = '.'.join([str(p) for p in e.absolute_path])
|
||||||
|
cli.log.error('Not including data from file: %s', info_file)
|
||||||
|
cli.log.error('\t%s: %s', json_path, e.message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
store_mtime(info_file)
|
||||||
|
|
||||||
|
# Merge layout data in
|
||||||
|
if 'layout_aliases' in new_info_data:
|
||||||
|
info_data['layout_aliases'] = {**info_data.get('layout_aliases', {}), **new_info_data['layout_aliases']}
|
||||||
|
del new_info_data['layout_aliases']
|
||||||
|
|
||||||
|
for layout_name, layout in new_info_data.get('layouts', {}).items():
|
||||||
|
if layout_name in info_data.get('layout_aliases', {}):
|
||||||
|
info_log_warning(info_data, f"info.json uses alias name {layout_name} instead of {info_data['layout_aliases'][layout_name]}")
|
||||||
|
layout_name = info_data['layout_aliases'][layout_name]
|
||||||
|
|
||||||
|
if layout_name in info_data['layouts']:
|
||||||
|
for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']):
|
||||||
|
existing_key.update(new_key)
|
||||||
|
else:
|
||||||
|
layout['c_macro'] = False
|
||||||
|
info_data['layouts'][layout_name] = layout
|
||||||
|
|
||||||
|
# Update info_data with the new data
|
||||||
|
if 'layouts' in new_info_data:
|
||||||
|
del new_info_data['layouts']
|
||||||
|
|
||||||
|
deep_update(info_data, new_info_data)
|
||||||
|
|
||||||
|
return info_data
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def find_info_json(keyboard):
|
||||||
|
"""Finds all the info.json files associated with a keyboard.
|
||||||
|
"""
|
||||||
|
# Find the most specific first
|
||||||
|
base_path = Path('keyboards')
|
||||||
|
keyboard_path = base_path / keyboard
|
||||||
|
keyboard_parent = keyboard_path.parent
|
||||||
|
info_jsons = [keyboard_path / 'info.json']
|
||||||
|
|
||||||
|
# Add DEFAULT_FOLDER before parents, if present
|
||||||
|
rules = rules_mk(keyboard)
|
||||||
|
if 'DEFAULT_FOLDER' in rules:
|
||||||
|
info_jsons.append(Path(rules['DEFAULT_FOLDER']) / 'info.json')
|
||||||
|
|
||||||
|
# Add in parent folders for least specific
|
||||||
|
for _ in range(5):
|
||||||
|
info_jsons.append(keyboard_parent / 'info.json')
|
||||||
|
if keyboard_parent.parent == base_path:
|
||||||
|
break
|
||||||
|
keyboard_parent = keyboard_parent.parent
|
||||||
|
|
||||||
|
# Return a list of the info.json files that actually exist
|
||||||
|
return [info_json for info_json in info_jsons if info_json.exists()]
|
||||||
@@ -3,12 +3,14 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from qmk.constants import MAX_KEYBOARD_SUBFOLDERS, QMK_FIRMWARE
|
from qmk.constants import MAX_KEYBOARD_SUBFOLDERS, QMK_FIRMWARE
|
||||||
from qmk.errors import NoSuchKeyboardError
|
from qmk.errors import NoSuchKeyboardError
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def is_keyboard(keyboard_name):
|
def is_keyboard(keyboard_name):
|
||||||
"""Returns True if `keyboard_name` is a keyboard we can compile.
|
"""Returns True if `keyboard_name` is a keyboard we can compile.
|
||||||
"""
|
"""
|
||||||
@@ -19,6 +21,7 @@ def is_keyboard(keyboard_name):
|
|||||||
return rules_mk.exists()
|
return rules_mk.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def under_qmk_firmware():
|
def under_qmk_firmware():
|
||||||
"""Returns a Path object representing the relative path under qmk_firmware, or None.
|
"""Returns a Path object representing the relative path under qmk_firmware, or None.
|
||||||
"""
|
"""
|
||||||
@@ -30,12 +33,14 @@ def under_qmk_firmware():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def keyboard(keyboard_name):
|
def keyboard(keyboard_name):
|
||||||
"""Returns the path to a keyboard's directory relative to the qmk root.
|
"""Returns the path to a keyboard's directory relative to the qmk root.
|
||||||
"""
|
"""
|
||||||
return Path('keyboards') / keyboard_name
|
return Path('keyboards') / keyboard_name
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def keymap(keyboard_name):
|
def keymap(keyboard_name):
|
||||||
"""Locate the correct directory for storing a keymap.
|
"""Locate the correct directory for storing a keymap.
|
||||||
|
|
||||||
@@ -56,6 +61,7 @@ def keymap(keyboard_name):
|
|||||||
raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard_name)
|
raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard_name)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def normpath(path):
|
def normpath(path):
|
||||||
"""Returns a `pathlib.Path()` object for a given path.
|
"""Returns a `pathlib.Path()` object for a given path.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
"""Functions for working with QMK's submodules.
|
"""Functions for working with QMK's submodules.
|
||||||
"""
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=0)
|
||||||
def status():
|
def status():
|
||||||
"""Returns a dictionary of submodules.
|
"""Returns a dictionary of submodules.
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def test_hello():
|
|||||||
def test_format_python():
|
def test_format_python():
|
||||||
result = check_subcommand('format-python', '--dry-run')
|
result = check_subcommand('format-python', '--dry-run')
|
||||||
check_returncode(result)
|
check_returncode(result)
|
||||||
assert 'Python code in `bin/qmk` and `lib/python` is correctly formatted.' in result.stdout
|
assert 'Python code in `lib/python` is correctly formatted.' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_list_keyboards():
|
def test_list_keyboards():
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ let
|
|||||||
projectDir = ./util/nix;
|
projectDir = ./util/nix;
|
||||||
overrides = poetry2nix.overrides.withDefaults (self: super: {
|
overrides = poetry2nix.overrides.withDefaults (self: super: {
|
||||||
qmk = super.qmk.overridePythonAttrs(old: {
|
qmk = super.qmk.overridePythonAttrs(old: {
|
||||||
# Allow QMK CLI to run "bin/qmk" as a subprocess (the wrapper changes
|
# Allow QMK CLI to run "qmk" as a subprocess (the wrapper changes
|
||||||
# $PATH and breaks these invocations).
|
# $PATH and breaks these invocations).
|
||||||
dontWrapPythonPrograms = true;
|
dontWrapPythonPrograms = true;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user