Merge remote-tracking branch 'upstream/develop' into xap
This commit is contained in:
commit
3c66b9b0ec
7256 changed files with 77233 additions and 46094 deletions
|
@ -35,7 +35,6 @@ subcommands = [
|
|||
'qmk.cli.chibios.confmigrate',
|
||||
'qmk.cli.clean',
|
||||
'qmk.cli.compile',
|
||||
'qmk.cli.console',
|
||||
'qmk.cli.docs',
|
||||
'qmk.cli.doctor',
|
||||
'qmk.cli.fileformat',
|
||||
|
@ -72,6 +71,26 @@ subcommands = [
|
|||
]
|
||||
|
||||
|
||||
def _install_deps(requirements):
|
||||
"""Perform the installation of missing requirements.
|
||||
|
||||
If we detect that we are running in a virtualenv we can't write into we'll use sudo to perform the pip install.
|
||||
"""
|
||||
command = [sys.executable, '-m', 'pip', 'install']
|
||||
|
||||
if sys.prefix != sys.base_prefix:
|
||||
# We are in a virtualenv, check to see if we need to use sudo to write to it
|
||||
if not os.access(sys.prefix, os.W_OK):
|
||||
print('Notice: Using sudo to install modules to location owned by root:', sys.prefix)
|
||||
command.insert(0, 'sudo')
|
||||
|
||||
elif not os.access(sys.prefix, os.W_OK):
|
||||
# We can't write to sys.prefix, attempt to install locally
|
||||
command.append('--local')
|
||||
|
||||
return _run_cmd(*command, '-r', requirements)
|
||||
|
||||
|
||||
def _run_cmd(*command):
|
||||
"""Run a command in a subshell.
|
||||
"""
|
||||
|
@ -164,8 +183,14 @@ if int(milc_version[0]) < 2 and int(milc_version[1]) < 4:
|
|||
print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
|
||||
exit(127)
|
||||
|
||||
# Make sure we can run binaries in the same directory as our Python interpreter
|
||||
python_dir = os.path.dirname(sys.executable)
|
||||
|
||||
if python_dir not in os.environ['PATH'].split(':'):
|
||||
os.environ['PATH'] = ":".join((python_dir, os.environ['PATH']))
|
||||
|
||||
# Check to make sure we have all our dependencies
|
||||
msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.'
|
||||
msg_install = f'Please run `{sys.executable} -m pip install -r %s` to install required python dependencies.'
|
||||
args = sys.argv[1:]
|
||||
while args and args[0][0] == '-':
|
||||
del args[0]
|
||||
|
@ -175,7 +200,7 @@ safe_command = args and args[0] in safe_commands
|
|||
if not safe_command:
|
||||
if _broken_module_imports('requirements.txt'):
|
||||
if yesno('Would you like to install the required Python modules?'):
|
||||
_run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt')
|
||||
_install_deps('requirements.txt')
|
||||
else:
|
||||
print()
|
||||
print(msg_install % (str(Path('requirements.txt').resolve()),))
|
||||
|
@ -184,7 +209,7 @@ if not safe_command:
|
|||
|
||||
if cli.config.user.developer and _broken_module_imports('requirements-dev.txt'):
|
||||
if yesno('Would you like to install the required developer Python modules?'):
|
||||
_run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements-dev.txt')
|
||||
_install_deps('requirements-dev.txt')
|
||||
elif yesno('Would you like to disable developer mode?'):
|
||||
_run_cmd(sys.argv[0], 'config', 'user.developer=None')
|
||||
else:
|
||||
|
|
|
@ -18,7 +18,7 @@ from qmk.keymap import keymap_completer
|
|||
@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('-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('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||
@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('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.subcommand('Compile a QMK Firmware.')
|
||||
|
|
|
@ -1,302 +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',
|
||||
('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'
|
||||
}
|
||||
|
||||
|
||||
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': {
|
||||
'version_arg': '-dumpversion'
|
||||
},
|
||||
'bin/qmk': {},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -82,6 +82,10 @@ def check_udev_rules():
|
|||
# dog hunter AG
|
||||
_udev_rule("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
|
||||
_udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro
|
||||
},
|
||||
'hid-bootloader': {
|
||||
_udev_rule("03eb", "2067"), # QMK HID
|
||||
_udev_rule("16c0", "0478") # PJRC halfkay
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ def print_bootloader_help():
|
|||
@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('-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 to run.")
|
||||
@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('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.subcommand('QMK Flash.')
|
||||
|
|
|
@ -12,7 +12,7 @@ from qmk.c_parse import c_source_files
|
|||
|
||||
c_file_suffixes = ('c', 'h', 'cpp')
|
||||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
|
||||
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
|
||||
ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards')
|
||||
|
||||
|
||||
def find_clang_format():
|
||||
|
|
|
@ -11,15 +11,15 @@ def format_python(cli):
|
|||
"""Format python code according to QMK's style.
|
||||
"""
|
||||
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:
|
||||
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
|
||||
|
||||
except CalledProcessError:
|
||||
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:
|
||||
cli.log.error('Error formatting python code!')
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@ from pathlib import Path
|
|||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard, normpath
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
def direct_pins(direct_pins):
|
||||
def direct_pins(direct_pins, postfix):
|
||||
"""Return the config.h lines that set the direct pins.
|
||||
"""
|
||||
rows = []
|
||||
|
@ -24,81 +24,60 @@ def direct_pins(direct_pins):
|
|||
col_count = len(direct_pins[0])
|
||||
row_count = len(direct_pins)
|
||||
|
||||
return """
|
||||
#ifndef MATRIX_COLS
|
||||
# define MATRIX_COLS %s
|
||||
#endif // MATRIX_COLS
|
||||
return f"""
|
||||
#ifndef MATRIX_COLS{postfix}
|
||||
# define MATRIX_COLS{postfix} {col_count}
|
||||
#endif // MATRIX_COLS{postfix}
|
||||
|
||||
#ifndef MATRIX_ROWS
|
||||
# define MATRIX_ROWS %s
|
||||
#endif // MATRIX_ROWS
|
||||
#ifndef MATRIX_ROWS{postfix}
|
||||
# define MATRIX_ROWS{postfix} {row_count}
|
||||
#endif // MATRIX_ROWS{postfix}
|
||||
|
||||
#ifndef DIRECT_PINS
|
||||
# define DIRECT_PINS {%s}
|
||||
#endif // DIRECT_PINS
|
||||
""" % (col_count, row_count, ','.join(rows))
|
||||
#ifndef DIRECT_PINS{postfix}
|
||||
# define DIRECT_PINS{postfix} {{ {", ".join(rows)} }}
|
||||
#endif // DIRECT_PINS{postfix}
|
||||
"""
|
||||
|
||||
|
||||
def pin_array(define, pins):
|
||||
def pin_array(define, pins, postfix):
|
||||
"""Return the config.h lines that set a pin array.
|
||||
"""
|
||||
pin_num = len(pins)
|
||||
pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))
|
||||
|
||||
return f"""
|
||||
#ifndef {define}S
|
||||
# define {define}S {pin_num}
|
||||
#endif // {define}S
|
||||
#ifndef {define}S{postfix}
|
||||
# define {define}S{postfix} {pin_num}
|
||||
#endif // {define}S{postfix}
|
||||
|
||||
#ifndef {define}_PINS
|
||||
# define {define}_PINS {{ {pin_array} }}
|
||||
#endif // {define}_PINS
|
||||
#ifndef {define}_PINS{postfix}
|
||||
# define {define}_PINS{postfix} {{ {pin_array} }}
|
||||
#endif // {define}_PINS{postfix}
|
||||
"""
|
||||
|
||||
|
||||
def matrix_pins(matrix_pins):
|
||||
def matrix_pins(matrix_pins, postfix=''):
|
||||
"""Add the matrix config to the config.h.
|
||||
"""
|
||||
pins = []
|
||||
|
||||
if 'direct' in matrix_pins:
|
||||
pins.append(direct_pins(matrix_pins['direct']))
|
||||
pins.append(direct_pins(matrix_pins['direct'], postfix))
|
||||
|
||||
if 'cols' in matrix_pins:
|
||||
pins.append(pin_array('MATRIX_COL', matrix_pins['cols']))
|
||||
pins.append(pin_array('MATRIX_COL', matrix_pins['cols'], postfix))
|
||||
|
||||
if 'rows' in matrix_pins:
|
||||
pins.append(pin_array('MATRIX_ROW', matrix_pins['rows']))
|
||||
pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'], postfix))
|
||||
|
||||
return '\n'.join(pins)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
def generate_config_h(cli):
|
||||
"""Generates the info_config.h file.
|
||||
def generate_config_items(kb_info_json, config_h_lines):
|
||||
"""Iterate through the info_config map to generate basic config values.
|
||||
"""
|
||||
# Determine our keyboard(s)
|
||||
if not cli.config.generate_config_h.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['info'].print_help()
|
||||
return False
|
||||
|
||||
if not is_keyboard(cli.config.generate_config_h.keyboard):
|
||||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard)
|
||||
return False
|
||||
|
||||
# Build the info_config.h file.
|
||||
kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
|
||||
info_config_map = json_load(Path('data/mappings/info_config.json'))
|
||||
|
||||
config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
|
||||
|
||||
# Iterate through the info_config map to generate basic things
|
||||
for config_key, info_dict in info_config_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
|
@ -135,9 +114,75 @@ def generate_config_h(cli):
|
|||
config_h_lines.append(f'# define {config_key} {config_value}')
|
||||
config_h_lines.append(f'#endif // {config_key}')
|
||||
|
||||
|
||||
def generate_split_config(kb_info_json, config_h_lines):
|
||||
"""Generate the config.h lines for split boards."""
|
||||
if 'primary' in kb_info_json['split']:
|
||||
if kb_info_json['split']['primary'] in ('left', 'right'):
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef MASTER_LEFT')
|
||||
config_h_lines.append('# ifndef MASTER_RIGHT')
|
||||
if kb_info_json['split']['primary'] == 'left':
|
||||
config_h_lines.append('# define MASTER_LEFT')
|
||||
elif kb_info_json['split']['primary'] == 'right':
|
||||
config_h_lines.append('# define MASTER_RIGHT')
|
||||
config_h_lines.append('# endif // MASTER_RIGHT')
|
||||
config_h_lines.append('#endif // MASTER_LEFT')
|
||||
elif kb_info_json['split']['primary'] == 'pin':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef SPLIT_HAND_PIN')
|
||||
config_h_lines.append('# define SPLIT_HAND_PIN')
|
||||
config_h_lines.append('#endif // SPLIT_HAND_PIN')
|
||||
elif kb_info_json['split']['primary'] == 'matrix_grid':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef SPLIT_HAND_MATRIX_GRID')
|
||||
config_h_lines.append('# define SPLIT_HAND_MATRIX_GRID {%s}' % (','.join(kb_info_json["split"]["matrix_grid"],)))
|
||||
config_h_lines.append('#endif // SPLIT_HAND_MATRIX_GRID')
|
||||
elif kb_info_json['split']['primary'] == 'eeprom':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef EE_HANDS')
|
||||
config_h_lines.append('# define EE_HANDS')
|
||||
config_h_lines.append('#endif // EE_HANDS')
|
||||
|
||||
if 'protocol' in kb_info_json['split'].get('transport', {}):
|
||||
if kb_info_json['split']['transport']['protocol'] == 'i2c':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef USE_I2C')
|
||||
config_h_lines.append('# define USE_I2C')
|
||||
config_h_lines.append('#endif // USE_I2C')
|
||||
|
||||
if 'right' in kb_info_json['split'].get('matrix_pins', {}):
|
||||
config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT'))
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate config.h for.')
|
||||
@cli.argument('-km', '--keymap', arg_only=True, help='Keymap to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
def generate_config_h(cli):
|
||||
"""Generates the info_config.h file.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
# Build the info_config.h file.
|
||||
config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
|
||||
|
||||
generate_config_items(kb_info_json, config_h_lines)
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
||||
|
||||
if 'split' in kb_info_json:
|
||||
generate_split_config(kb_info_json, config_h_lines)
|
||||
|
||||
# Show the results
|
||||
config_h = '\n'.join(config_h_lines)
|
||||
|
||||
|
|
|
@ -4,15 +4,17 @@ Compile an info.json for a particular keyboard and pretty-print it.
|
|||
"""
|
||||
import json
|
||||
|
||||
from jsonschema import Draft7Validator, validators
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from jsonschema import Draft7Validator, RefResolver, validators
|
||||
from milc import cli
|
||||
from pathlib import Path
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import load_jsonschema
|
||||
from qmk.json_schema import compile_schema_store
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard
|
||||
from qmk.path import is_keyboard, normpath
|
||||
|
||||
|
||||
def pruning_validator(validator_class):
|
||||
|
@ -34,15 +36,19 @@ def pruning_validator(validator_class):
|
|||
def strip_info_json(kb_info_json):
|
||||
"""Remove the API-only properties from the info.json.
|
||||
"""
|
||||
schema_store = compile_schema_store()
|
||||
pruning_draft_7_validator = pruning_validator(Draft7Validator)
|
||||
schema = load_jsonschema('keyboard')
|
||||
validator = pruning_draft_7_validator(schema).validate
|
||||
schema = schema_store['qmk.keyboard.v1']
|
||||
resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
|
||||
validator = pruning_draft_7_validator(schema, resolver=resolver).validate
|
||||
|
||||
return validator(kb_info_json)
|
||||
|
||||
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
|
||||
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
|
||||
@cli.argument('-o', '--output', arg_only=True, completer=FilesCompleter, help='Write the output the specified file, overwriting if necessary.')
|
||||
@cli.argument('-ow', '--overwrite', arg_only=True, action='store_true', help='Overwrite the existing info.json. (Overrides the location of --output)')
|
||||
@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
|
@ -59,9 +65,29 @@ def generate_info_json(cli):
|
|||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard)
|
||||
return False
|
||||
|
||||
if cli.args.overwrite:
|
||||
output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve()
|
||||
|
||||
if cli.args.output:
|
||||
cli.log.warning('Overwriting user supplied --output with %s', output_path)
|
||||
|
||||
cli.args.output = output_path
|
||||
|
||||
# Build the info.json file
|
||||
kb_info_json = info_json(cli.config.generate_info_json.keyboard)
|
||||
strip_info_json(kb_info_json)
|
||||
info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder)
|
||||
|
||||
# Display the results
|
||||
print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder))
|
||||
if cli.args.output:
|
||||
# Write to a file
|
||||
output_path = normpath(cli.args.output)
|
||||
|
||||
if output_path.exists():
|
||||
cli.log.warning('Overwriting output file %s', output_path)
|
||||
|
||||
output_path.write_text(info_json_text + '\n')
|
||||
cli.log.info('Wrote info.json to %s.', output_path)
|
||||
|
||||
else:
|
||||
# Display the results
|
||||
print(info_json_text)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import normpath
|
||||
|
@ -29,14 +28,12 @@ def would_populate_layout_h(keyboard):
|
|||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.h for.')
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.h for.')
|
||||
@cli.subcommand('Used by the make system to generate keyboard.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
def generate_keyboard_h(cli):
|
||||
"""Generates the keyboard.h file.
|
||||
"""
|
||||
has_layout_h = would_populate_layout_h(cli.config.generate_keyboard_h.keyboard)
|
||||
has_layout_h = would_populate_layout_h(cli.args.keyboard)
|
||||
|
||||
# Build the layouts.h file.
|
||||
keyboard_h_lines = ['/* This file was generated by `qmk generate-keyboard-h`. Do not edit or copy.' ' */', '', '#pragma once', '#include "quantum.h"']
|
||||
|
|
|
@ -5,11 +5,11 @@ from pathlib import Path
|
|||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard, normpath
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
|
@ -39,23 +39,21 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
|||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate rules.mk for.')
|
||||
@cli.argument('-km', '--keymap', arg_only=True, help='Keymap to generate rules.mk for.')
|
||||
@cli.subcommand('Used by the make system to generate rules.mk from info.json', hidden=True)
|
||||
def generate_rules_mk(cli):
|
||||
"""Generates a rules.mk file from info.json.
|
||||
"""
|
||||
if not cli.config.generate_rules_mk.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['info'].print_help()
|
||||
return False
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
if not is_keyboard(cli.config.generate_rules_mk.keyboard):
|
||||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard)
|
||||
return False
|
||||
|
||||
kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard))
|
||||
info_rules_map = json_load(Path('data/mappings/info_rules.json'))
|
||||
rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '']
|
||||
|
||||
|
@ -76,6 +74,17 @@ def generate_rules_mk(cli):
|
|||
enabled = 'yes' if enabled else 'no'
|
||||
rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
|
||||
|
||||
# Set SPLIT_TRANSPORT, if needed
|
||||
if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom':
|
||||
rules_mk_lines.append('SPLIT_TRANSPORT ?= custom')
|
||||
|
||||
# Set CUSTOM_MATRIX, if needed
|
||||
if kb_info_json.get('matrix_pins', {}).get('custom'):
|
||||
if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
|
||||
rules_mk_lines.append('CUSTOM_MATRIX ?= lite')
|
||||
else:
|
||||
rules_mk_lines.append('CUSTOM_MATRIX ?= yes')
|
||||
|
||||
# Show the results
|
||||
rules_mk = '\n'.join(rules_mk_lines) + '\n'
|
||||
|
||||
|
|
|
@ -24,19 +24,15 @@ def show_keymap(kb_info_json, title_caps=True):
|
|||
keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)
|
||||
|
||||
if keymap_path and keymap_path.suffix == '.json':
|
||||
if title_caps:
|
||||
cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap)
|
||||
else:
|
||||
cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap)
|
||||
|
||||
keymap_data = json.load(keymap_path.open(encoding='utf-8'))
|
||||
layout_name = keymap_data['layout']
|
||||
layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names
|
||||
|
||||
for layer_num, layer in enumerate(keymap_data['layers']):
|
||||
if title_caps:
|
||||
cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num)
|
||||
cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)
|
||||
else:
|
||||
cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num)
|
||||
cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)
|
||||
|
||||
print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer))
|
||||
|
||||
|
@ -45,7 +41,7 @@ def show_layouts(kb_info_json, title_caps=True):
|
|||
"""Render the layouts with info.json labels.
|
||||
"""
|
||||
for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items():
|
||||
title = layout_name.title() if title_caps else layout_name
|
||||
title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}'
|
||||
cli.echo('{fg_cyan}%s{fg_reset}:', title)
|
||||
print(layout_art) # Avoid passing dirty data to cli.echo()
|
||||
|
||||
|
@ -93,15 +89,6 @@ def print_friendly_output(kb_info_json):
|
|||
aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()]
|
||||
cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),))
|
||||
|
||||
if cli.config.info.layouts:
|
||||
show_layouts(kb_info_json, True)
|
||||
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, True)
|
||||
|
||||
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
|
||||
show_keymap(kb_info_json, True)
|
||||
|
||||
|
||||
def print_text_output(kb_info_json):
|
||||
"""Print the info.json in a plain text format.
|
||||
|
@ -122,6 +109,24 @@ def print_text_output(kb_info_json):
|
|||
show_keymap(kb_info_json, False)
|
||||
|
||||
|
||||
def print_dotted_output(kb_info_json, prefix=''):
|
||||
"""Print the info.json in a plain text format with dot-joined keys.
|
||||
"""
|
||||
for key in sorted(kb_info_json):
|
||||
new_prefix = f'{prefix}.{key}' if prefix else key
|
||||
|
||||
if key in ['parse_errors', 'parse_warnings']:
|
||||
continue
|
||||
elif key == 'layouts' and prefix == '':
|
||||
cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
|
||||
elif isinstance(kb_info_json[key], dict):
|
||||
print_dotted_output(kb_info_json[key], new_prefix)
|
||||
elif isinstance(kb_info_json[key], list):
|
||||
cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key]))))
|
||||
else:
|
||||
cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
|
||||
|
||||
|
||||
def print_parsed_rules_mk(keyboard_name):
|
||||
rules = rules_mk(keyboard_name)
|
||||
for k in sorted(rules.keys()):
|
||||
|
@ -162,10 +167,22 @@ def info(cli):
|
|||
# Output in the requested format
|
||||
if cli.args.format == 'json':
|
||||
print(json.dumps(kb_info_json, cls=InfoJSONEncoder))
|
||||
return True
|
||||
elif cli.args.format == 'text':
|
||||
print_text_output(kb_info_json)
|
||||
print_dotted_output(kb_info_json)
|
||||
title_caps = False
|
||||
elif cli.args.format == 'friendly':
|
||||
print_friendly_output(kb_info_json)
|
||||
title_caps = True
|
||||
else:
|
||||
cli.log.error('Unknown format: %s', cli.args.format)
|
||||
return False
|
||||
|
||||
if cli.config.info.layouts:
|
||||
show_layouts(kb_info_json, title_caps)
|
||||
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, title_caps)
|
||||
|
||||
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
|
||||
show_keymap(kb_info_json, title_caps)
|
||||
|
|
|
@ -1,72 +1,129 @@
|
|||
"""Command to look over a keyboard/keymap and check for common mistakes.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
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.path import is_keyboard, keyboard
|
||||
|
||||
|
||||
@cli.argument('--strict', action='store_true', help='Treat warnings as errors.')
|
||||
@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.')
|
||||
@cli.argument('-km', '--keymap', help='The keymap to check.')
|
||||
def keymap_check(kb, km):
|
||||
"""Perform the keymap level checks.
|
||||
"""
|
||||
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.')
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
def lint(cli):
|
||||
"""Check keyboard and keymap for common mistakes.
|
||||
"""
|
||||
if not cli.config.lint.keyboard:
|
||||
cli.log.error('Missing required argument: --keyboard')
|
||||
failed = []
|
||||
|
||||
# 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()
|
||||
return False
|
||||
else:
|
||||
keyboard_list = cli.config.lint.keyboard.split(',')
|
||||
|
||||
if not is_keyboard(cli.config.lint.keyboard):
|
||||
cli.log.error('No such keyboard: %s', cli.config.lint.keyboard)
|
||||
return False
|
||||
# Lint each keyboard
|
||||
for kb in keyboard_list:
|
||||
if not is_keyboard(kb):
|
||||
cli.log.error('No such keyboard: %s', kb)
|
||||
continue
|
||||
|
||||
# Gather data about the keyboard.
|
||||
ok = True
|
||||
keyboard_path = keyboard(cli.config.lint.keyboard)
|
||||
keyboard_info = info_json(cli.config.lint.keyboard)
|
||||
readme_path = find_readme(cli.config.lint.keyboard)
|
||||
missing_readme_path = keyboard_path / 'readme.md'
|
||||
# Gather data about the keyboard.
|
||||
ok = True
|
||||
keyboard_path = keyboard(kb)
|
||||
keyboard_info = info_json(kb)
|
||||
|
||||
# Check for errors in the info.json
|
||||
if keyboard_info['parse_errors']:
|
||||
ok = False
|
||||
cli.log.error('Errors found when generating info.json.')
|
||||
|
||||
if cli.config.lint.strict and keyboard_info['parse_warnings']:
|
||||
ok = False
|
||||
cli.log.error('Warnings found when generating info.json (Strict mode enabled.)')
|
||||
|
||||
# Check for a readme.md and warn if it doesn't exist
|
||||
if not readme_path:
|
||||
ok = False
|
||||
cli.log.error('Missing %s', missing_readme_path)
|
||||
|
||||
# Keymap specific checks
|
||||
if cli.config.lint.keymap:
|
||||
keymap_path = locate_keymap(cli.config.lint.keyboard, cli.config.lint.keymap)
|
||||
|
||||
if not keymap_path:
|
||||
# Check for errors in the info.json
|
||||
if keyboard_info['parse_errors']:
|
||||
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)
|
||||
cli.log.error('%s: Errors found when generating info.json.', kb)
|
||||
|
||||
if cli.config.lint.strict:
|
||||
ok = False
|
||||
if cli.config.lint.strict and keyboard_info['parse_warnings']:
|
||||
ok = False
|
||||
cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb)
|
||||
|
||||
# Check the rules.mk file(s)
|
||||
rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path)
|
||||
if rules_mk_assignment_errors:
|
||||
ok = False
|
||||
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
|
||||
if cli.config.lint.keymap:
|
||||
if not keymap_check(kb, cli.config.lint.keymap):
|
||||
ok = False
|
||||
|
||||
# Report status
|
||||
if not ok:
|
||||
failed.append(kb)
|
||||
|
||||
# Check and report the overall status
|
||||
if ok:
|
||||
cli.log.info('Lint check passed!')
|
||||
return True
|
||||
if failed:
|
||||
cli.log.error('Lint check failed for: %s', ', '.join(failed))
|
||||
return False
|
||||
|
||||
cli.log.error('Lint check failed!')
|
||||
return False
|
||||
cli.log.info('Lint check passed!')
|
||||
return True
|
||||
|
|
|
@ -10,7 +10,7 @@ from subprocess import DEVNULL
|
|||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.commands import _find_make
|
||||
from qmk.commands import _find_make, get_make_parallel_args
|
||||
import qmk.keyboard
|
||||
import qmk.keymap
|
||||
|
||||
|
@ -28,7 +28,7 @@ def _is_split(keyboard_name):
|
|||
return True if 'SPLIT_KEYBOARD' in rules_mk and rules_mk['SPLIT_KEYBOARD'].lower() == 'yes' else False
|
||||
|
||||
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
||||
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on the supplied value in rules.mk. Supported format is 'SPLIT_KEYBOARD=yes'. May be passed multiple times.")
|
||||
@cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.")
|
||||
|
@ -80,7 +80,7 @@ all: {keyboard_safe}_binary
|
|||
)
|
||||
# yapf: enable
|
||||
|
||||
cli.run([make_cmd, '-j', str(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
|
||||
cli.run([make_cmd, *get_make_parallel_args(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
|
||||
|
||||
# Check for failures
|
||||
failures = [f for f in builddir.glob(f'failed.log.{os.getpid()}.*')]
|
||||
|
|
|
@ -115,9 +115,9 @@ def find_user_name():
|
|||
|
||||
|
||||
def copy_templates(keyboard_type, keyboard_path):
|
||||
"""Copies the template files from quantum/template to the new keyboard directory.
|
||||
"""Copies the template files from data/templates to the new keyboard directory.
|
||||
"""
|
||||
template_base_path = Path('quantum/template')
|
||||
template_base_path = Path('data/templates')
|
||||
keyboard_basename = keyboard_path.name
|
||||
|
||||
cli.log.info('Copying base template files...')
|
||||
|
|
|
@ -12,6 +12,6 @@ def pytest(cli):
|
|||
"""Run several linting/testing commands.
|
||||
"""
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue