Merge tag '0.18.16' into firmware22

This commit is contained in:
Drashna Jael're
2022-11-08 17:14:27 -08:00
431 changed files with 21351 additions and 6419 deletions

View File

@@ -15,6 +15,7 @@ from milc.questions import yesno
import_names = {
# A mapping of package name to importable name
'pep8-naming': 'pep8ext_naming',
'pyserial': 'serial',
'pyusb': 'usb.core',
'qmk-dotty-dict': 'dotty_dict',
'pillow': 'PIL'
@@ -59,6 +60,9 @@ subcommands = [
'qmk.cli.generate.rules_mk',
'qmk.cli.generate.version_h',
'qmk.cli.hello',
'qmk.cli.import.kbfirmware',
'qmk.cli.import.keyboard',
'qmk.cli.import.keymap',
'qmk.cli.info',
'qmk.cli.json2c',
'qmk.cli.lint',
@@ -91,7 +95,7 @@ def _install_deps(requirements):
elif not os.access(sys.prefix, os.W_OK):
# We can't write to sys.prefix, attempt to install locally
command.append('--local')
command.append('--user')
return _run_cmd(*command, '-r', requirements)
@@ -156,6 +160,18 @@ def _broken_module_imports(requirements):
return False
def _yesno(*args):
"""Wrapper to only prompt if interactive
"""
return sys.stdout.isatty() and yesno(*args)
def _eprint(errmsg):
"""Wrapper to print to stderr
"""
print(errmsg, file=sys.stderr)
# Make sure our python is new enough
#
# Supported version information
@@ -177,7 +193,7 @@ def _broken_module_imports(requirements):
# void: 3.9
if sys.version_info[0] != 3 or sys.version_info[1] < 7:
print('Error: Your Python is too old! Please upgrade to Python 3.7 or later.')
_eprint('Error: Your Python is too old! Please upgrade to Python 3.7 or later.')
exit(127)
milc_version = __VERSION__.split('.')
@@ -185,7 +201,7 @@ milc_version = __VERSION__.split('.')
if int(milc_version[0]) < 2 and int(milc_version[1]) < 4:
requirements = Path('requirements.txt').resolve()
print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
_eprint(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
@@ -195,7 +211,7 @@ 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 = f'Please run `{sys.executable} -m pip install -r %s` to install required python dependencies.'
msg_install = f'\nPlease 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]
@@ -204,24 +220,20 @@ 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?'):
if _yesno('Would you like to install the required Python modules?'):
_install_deps('requirements.txt')
else:
print()
print(msg_install % (str(Path('requirements.txt').resolve()),))
print()
_eprint(msg_install % (str(Path('requirements.txt').resolve()),))
exit(1)
if cli.config.user.developer and _broken_module_imports('requirements-dev.txt'):
if yesno('Would you like to install the required developer Python modules?'):
if _yesno('Would you like to install the required developer Python modules?'):
_install_deps('requirements-dev.txt')
elif yesno('Would you like to disable developer mode?'):
elif _yesno('Would you like to disable developer mode?'):
_run_cmd(sys.argv[0], 'config', 'user.developer=None')
else:
print()
print(msg_install % (str(Path('requirements-dev.txt').resolve()),))
print('You can also turn off developer mode: qmk config user.developer=None')
print()
_eprint(msg_install % (str(Path('requirements-dev.txt').resolve()),))
_eprint('You can also turn off developer mode: qmk config user.developer=None')
exit(1)
# Import our subcommands
@@ -231,6 +243,6 @@ for subcommand in subcommands:
except (ImportError, ModuleNotFoundError) as e:
if safe_command:
print(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}')
_eprint(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}')
else:
raise

View File

@@ -32,8 +32,9 @@ def compile(cli):
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:
command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, 'clean')
cli.run(command, capture_output=False, stdin=DEVNULL)
if cli.config.compile.keyboard and cli.config.compile.keymap:
command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap, 'clean')
cli.run(command, capture_output=False, stdin=DEVNULL)
# Build the environment vars
envs = {}

View File

@@ -6,7 +6,7 @@ from pathlib import Path
from milc import cli
from qmk.constants import QMK_FIRMWARE
from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS
from .check import CheckStatus
@@ -26,6 +26,18 @@ def _udev_rule(vid, pid=None, *args):
return rule
def _generate_desired_rules(bootloader_vids_pids):
rules = dict()
for bl in bootloader_vids_pids.keys():
rules[bl] = set()
for vid_pid in bootloader_vids_pids[bl]:
if bl == 'caterina' or bl == 'md-boot':
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"'))
else:
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1]))
return rules
def _deprecated_udev_rule(vid, pid=None):
""" Helper function that return udev rules
@@ -47,47 +59,8 @@ def check_udev_rules():
Path("/run/udev/rules.d/"),
Path("/etc/udev/rules.d/"),
]
desired_rules = {
'atmel-dfu': {
_udev_rule("03eb", "2fef"), # ATmega16U2
_udev_rule("03eb", "2ff0"), # ATmega32U2
_udev_rule("03eb", "2ff3"), # ATmega16U4
_udev_rule("03eb", "2ff4"), # ATmega32U4
_udev_rule("03eb", "2ff9"), # AT90USB64
_udev_rule("03eb", "2ffa"), # AT90USB162
_udev_rule("03eb", "2ffb") # AT90USB128
},
'kiibohd': {_udev_rule("1c11", "b007")},
'stm32': {
_udev_rule("1eaf", "0003"), # STM32duino
_udev_rule("0483", "df11") # STM32 DFU
},
'bootloadhid': {_udev_rule("16c0", "05df")},
'usbasploader': {_udev_rule("16c0", "05dc")},
'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')},
'caterina': {
# Spark Fun Electronics
_udev_rule("1b4f", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz
_udev_rule("1b4f", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz
_udev_rule("1b4f", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones)
# Pololu Electronics
_udev_rule("1ffb", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4
# Arduino SA
_udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
_udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro
# Adafruit Industries LLC
_udev_rule("239a", "000c", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4
_udev_rule("239a", "000d", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz
_udev_rule("239a", "000e", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz
# 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
}
}
desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS)
# These rules are no longer recommended, only use them to check for their presence.
deprecated_rules = {

View File

@@ -8,6 +8,6 @@ from .check import CheckStatus
def os_test_macos():
"""Run the Mac specific tests.
"""
cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel')
return CheckStatus.OK

View File

@@ -11,7 +11,7 @@ from milc.questions import yesno
from qmk import submodules
from qmk.constants import QMK_FIRMWARE, QMK_FIRMWARE_UPSTREAM
from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules
from qmk.git import git_check_repo, git_get_branch, git_get_tag, git_is_dirty, git_get_remotes, git_check_deviation
from qmk.git import git_check_repo, git_get_branch, git_get_tag, git_get_last_log_entry, git_get_common_ancestor, git_is_dirty, git_get_remotes, git_check_deviation
from qmk.commands import in_virtualenv
@@ -66,10 +66,32 @@ def git_tests():
if git_branch in ['master', 'develop'] and git_deviation:
cli.log.warning('{fg_yellow}The local "%s" branch contains commits not found in the upstream branch.', git_branch)
status = CheckStatus.WARNING
for branch in [git_branch, 'upstream/master', 'upstream/develop']:
cli.log.info('- Latest %s: %s', branch, git_get_last_log_entry(branch))
for branch in ['upstream/master', 'upstream/develop']:
cli.log.info('- Common ancestor with %s: %s', branch, git_get_common_ancestor(branch, 'HEAD'))
return status
def output_submodule_status():
"""Prints out information related to the submodule status.
"""
cli.log.info('Submodule status:')
sub_status = submodules.status()
for s in sub_status.keys():
sub_info = sub_status[s]
if 'name' in sub_info:
sub_name = sub_info['name']
sub_shorthash = sub_info['shorthash'] if 'shorthash' in sub_info else ''
sub_describe = sub_info['describe'] if 'describe' in sub_info else ''
sub_last_log_timestamp = sub_info['last_log_timestamp'] if 'last_log_timestamp' in sub_info else ''
if sub_last_log_timestamp != '':
cli.log.info(f'- {sub_name}: {sub_last_log_timestamp} -- {sub_describe} ({sub_shorthash})')
else:
cli.log.error(f'- {sub_name}: <<< missing or unknown >>>')
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
@cli.subcommand('Basic QMK environment checks')
@@ -129,6 +151,8 @@ def doctor(cli):
elif sub_ok == CheckStatus.WARNING and status == CheckStatus.OK:
status = CheckStatus.WARNING
output_submodule_status()
# Report a summary of our findings to the user
if status == CheckStatus.OK:
cli.log.info('{fg_green}QMK is ready to go')

View File

@@ -4,6 +4,7 @@ You can compile a keymap already in the repo or using a QMK Configurator export.
A bootloader must be specified.
"""
from subprocess import DEVNULL
import sys
from argcomplete.completers import FilesCompleter
from milc import cli
@@ -12,6 +13,7 @@ import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.flashers import flasher
def print_bootloader_help():
@@ -33,12 +35,15 @@ def print_bootloader_help():
cli.echo('\tdfu-split-right')
cli.echo('\tdfu-util-split-left')
cli.echo('\tdfu-util-split-right')
cli.echo('\tuf2-split-left')
cli.echo('\tuf2-split-right')
cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.')
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.')
@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('-m', '--mcu', help='The MCU name. Required for HalfKay, HID, USBAspLoader and ISP flashing.')
@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.")
@@ -51,6 +56,8 @@ def print_bootloader_help():
def flash(cli):
"""Compile and or flash QMK Firmware or keyboard/layout
If a binary firmware is supplied, try to flash that.
If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
will be ignored.
@@ -58,55 +65,69 @@ def flash(cli):
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)
if cli.args.filename and cli.args.filename.suffix in ['.bin', '.hex']:
# Try to flash binary firmware
cli.echo('Flashing binary firmware...\nPlease reset your keyboard into bootloader mode now!\nPress Ctrl-C to exit.\n')
try:
err, msg = flasher(cli.args.mcu, cli.args.filename)
if err:
cli.log.error(msg)
return False
except KeyboardInterrupt:
cli.log.info('Ctrl-C was pressed, exiting...')
sys.exit(0)
else:
if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
if cli.config.flash.keyboard and cli.config.flash.keymap:
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:
# Provide usage and list bootloaders
cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
print_bootloader_help()
return False
if cli.args.filename:
# 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'])
# 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)
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)
# Determine the compile command
command = ''
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!')
if cli.args.bootloaders:
# Provide usage and list bootloaders
cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
print_bootloader_help()
return False
# 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
if cli.args.filename:
# 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
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

View File

@@ -12,29 +12,14 @@ from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import json_load
from qmk.keyboard import find_readme, list_keyboards
TEMPLATE_PATH = Path('data/templates/api/')
DATA_PATH = Path('data')
TEMPLATE_PATH = DATA_PATH / 'templates/api/'
BUILD_API_PATH = Path('.build/api_data/')
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
def generate_api(cli):
"""Generates the QMK API data.
def _filtered_keyboard_list():
"""Perform basic filtering of list_keyboards
"""
if BUILD_API_PATH.exists():
shutil.rmtree(BUILD_API_PATH)
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
v1_dir = BUILD_API_PATH / 'v1'
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
# Filter down when required
keyboard_list = list_keyboards()
if cli.args.filter:
kb_list = []
@@ -42,6 +27,30 @@ def generate_api(cli):
if any(i in keyboard_name for i in cli.args.filter):
kb_list.append(keyboard_name)
keyboard_list = kb_list
return keyboard_list
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
@cli.subcommand('Generate QMK API data', hidden=False if cli.config.user.developer else True)
def generate_api(cli):
"""Generates the QMK API data.
"""
v1_dir = BUILD_API_PATH / 'v1'
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
if BUILD_API_PATH.exists():
shutil.rmtree(BUILD_API_PATH)
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
shutil.copytree(DATA_PATH, v1_dir)
# Filter down when required
keyboard_list = _filtered_keyboard_list()
kb_all = {}
usb_list = {}

View File

@@ -134,6 +134,36 @@ def generate_config_items(kb_info_json, config_h_lines):
config_h_lines.append(f'#endif // {config_key}')
def generate_encoder_config(encoder_json, config_h_lines, postfix=''):
"""Generate the config.h lines for encoders."""
a_pads = []
b_pads = []
resolutions = []
for encoder in encoder_json.get("rotary", []):
a_pads.append(encoder["pin_a"])
b_pads.append(encoder["pin_b"])
resolutions.append(encoder.get("resolution", None))
config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}')
config_h_lines.append(f'# define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}')
config_h_lines.append(f'#endif // ENCODERS_PAD_A{postfix}')
config_h_lines.append(f'#ifndef ENCODERS_PAD_B{postfix}')
config_h_lines.append(f'# define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}')
config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}')
if None in resolutions:
cli.log.debug("Unable to generate ENCODER_RESOLUTION configuration")
elif len(set(resolutions)) == 1:
config_h_lines.append(f'#ifndef ENCODER_RESOLUTION{postfix}')
config_h_lines.append(f'# define ENCODER_RESOLUTION{postfix} { resolutions[0] }')
config_h_lines.append(f'#endif // ENCODER_RESOLUTION{postfix}')
else:
config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}')
config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(map(str,resolutions)) } }}')
config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}')
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']:
@@ -173,6 +203,9 @@ def generate_split_config(kb_info_json, config_h_lines):
if 'right' in kb_info_json['split'].get('matrix_pins', {}):
config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT'))
if 'right' in kb_info_json['split'].get('encoder', {}):
generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_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")
@@ -198,6 +231,9 @@ def generate_config_h(cli):
if 'matrix_pins' in kb_info_json:
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
if 'encoder' in kb_info_json:
generate_encoder_config(kb_info_json['encoder'], config_h_lines)
if 'split' in kb_info_json:
generate_split_config(kb_info_json, config_h_lines)

View File

@@ -33,8 +33,8 @@ def generate_dfu_header(cli):
kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard))
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once']
keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}')
keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader')
keyboard_h_lines.append(f'#define MANUFACTURER "{kb_info_json["manufacturer"]}"')
keyboard_h_lines.append(f'#define PRODUCT "{kb_info_json["keyboard_name"]} Bootloader"')
# Optional
if 'qmk_lufa_bootloader.esc_output' in kb_info_json:

View File

@@ -5,7 +5,7 @@ Compile an info.json for a particular keyboard and pretty-print it.
import json
from argcomplete.completers import FilesCompleter
from jsonschema import Draft7Validator, RefResolver, validators
from jsonschema import Draft202012Validator, RefResolver, validators
from milc import cli
from pathlib import Path
@@ -18,7 +18,7 @@ from qmk.path import is_keyboard, normpath
def pruning_validator(validator_class):
"""Extends Draft7Validator to remove properties that aren't specified in the schema.
"""Extends Draft202012Validator to remove properties that aren't specified in the schema.
"""
validate_properties = validator_class.VALIDATORS["properties"]
@@ -37,10 +37,10 @@ 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)
pruning_draft_validator = pruning_validator(Draft202012Validator)
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
validator = pruning_draft_validator(schema, resolver=resolver).validate
return validator(kb_info_json)

View File

@@ -41,7 +41,7 @@ def generate_keyboard_h(cli):
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "quantum.h"']
if not has_layout_h:
keyboard_h_lines.append('#pragma error("<keyboard>.h is only optional for data driven keyboards - kb.h == bad times")')
keyboard_h_lines.append('#error("<keyboard>.h is only optional for data driven keyboards - kb.h == bad times")')
# Show the results
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)

View File

View File

@@ -0,0 +1,25 @@
from milc import cli
from qmk.importers import import_kbfirmware as _import_kbfirmware
from qmk.path import FileType
from qmk.json_schema import json_load
@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('Import kbfirmware json export')
def import_kbfirmware(cli):
filename = cli.args.filename[0]
data = json_load(filename)
cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
cli.echo('')
cli.log.warn("Support here is basic - Consider using 'qmk new-keyboard' instead")
kb_name = _import_kbfirmware(data)
cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}')
cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},')
cli.log.info('or open the directory in your preferred text editor.')
cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.")

View File

@@ -0,0 +1,23 @@
from milc import cli
from qmk.importers import import_keyboard as _import_keyboard
from qmk.path import FileType
from qmk.json_schema import json_load
@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('Import data-driven keyboard')
def import_keyboard(cli):
filename = cli.args.filename[0]
data = json_load(filename)
cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
cli.echo('')
kb_name = _import_keyboard(data)
cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}')
cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},')
cli.log.info('or open the directory in your preferred text editor.')
cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.")

View File

@@ -0,0 +1,23 @@
from milc import cli
from qmk.importers import import_keymap as _import_keymap
from qmk.path import FileType
from qmk.json_schema import json_load
@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('Import data-driven keymap')
def import_keymap(cli):
filename = cli.args.filename[0]
data = json_load(filename)
cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
cli.echo('')
kb_name, km_name = _import_keymap(data)
cli.log.info(f'{{fg_green}}Imported a new keymap named {{fg_cyan}}{km_name}{{fg_green}}.{{fg_reset}}')
cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}/keymaps/{km_name}{{fg_reset}},')
cli.log.info('or open the directory in your preferred text editor.')
cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km {km_name}{{fg_reset}}.")

View File

@@ -7,26 +7,43 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.keyboard import keyboard_completer, list_keyboards
from qmk.keymap import locate_keymap
from qmk.keymap import locate_keymap, list_keymaps
from qmk.path import is_keyboard, keyboard
from qmk.git import git_get_ignored_files
def keymap_check(kb, km):
"""Perform the keymap level checks.
def _list_defaultish_keymaps(kb):
"""Return default like keymaps for a given keyboard
"""
defaultish = ['ansi', 'iso', 'via']
keymaps = set()
for x in list_keymaps(kb):
if x in defaultish or x.startswith('default'):
keymaps.add(x)
return keymaps
def _handle_json_errors(kb, info):
"""Convert any json errors into lint errors
"""
ok = True
keymap_path = locate_keymap(kb, km)
if not keymap_path:
# Check for errors in the json
if info['parse_errors']:
ok = False
cli.log.error("%s: Can't find %s keymap.", kb, km)
cli.log.error(f'{kb}: Errors found when generating info.json.')
if cli.config.lint.strict and info['parse_warnings']:
ok = False
cli.log.error(f'{kb}: Warnings found when generating info.json (Strict mode enabled.)')
return ok
def rules_mk_assignment_only(keyboard_path):
def _rules_mk_assignment_only(kb):
"""Check the keyboard-level rules.mk to ensure it only has assignments.
"""
keyboard_path = keyboard(kb)
current_path = Path()
errors = []
@@ -58,10 +75,58 @@ def rules_mk_assignment_only(keyboard_path):
return errors
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
# Additional checks
invalid_files = git_get_ignored_files(keymap_path.parent.as_posix())
for file in invalid_files:
cli.log.error(f'{kb}/{km}: The file "{file}" should not exist!')
ok = False
return ok
def keyboard_check(kb):
"""Perform the keyboard level checks.
"""
ok = True
kb_info = info_json(kb)
if not _handle_json_errors(kb, kb_info):
ok = False
# Additional checks
rules_mk_assignment_errors = _rules_mk_assignment_only(kb)
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)
invalid_files = git_get_ignored_files(f'keyboards/{kb}/')
for file in invalid_files:
if 'keymap' in file:
continue
cli.log.error(f'{kb}: The file "{file}" should not exist!')
ok = False
return ok
@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.argument('--all-km', action='store_true', arg_only=True, help='Check all keymaps')
@cli.subcommand('Check keyboard and keymap for common mistakes.')
@automagic_keyboard
@automagic_keymap
@@ -73,7 +138,7 @@ def lint(cli):
# 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.')
cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes precedence.')
keyboard_list = list_keyboards()
elif not cli.config.lint.keyboard:
@@ -89,38 +154,25 @@ def lint(cli):
cli.log.error('No such keyboard: %s', kb)
continue
# Gather data about the keyboard.
# Determine keymaps to also check
if cli.args.all_km:
keymaps = list_keymaps(kb)
elif cli.config.lint.keymap:
keymaps = {cli.config.lint.keymap}
else:
keymaps = _list_defaultish_keymaps(kb)
# Ensure that at least a 'default' keymap always exists
keymaps.add('default')
ok = True
keyboard_path = keyboard(kb)
keyboard_info = info_json(kb)
# Check for errors in the info.json
if keyboard_info['parse_errors']:
# keyboard level checks
if not keyboard_check(kb):
ok = False
cli.log.error('%s: Errors found when generating info.json.', kb)
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
# Check if all non-data driven macros exist in <keyboard.h>
for layout, data in keyboard_info['layouts'].items():
# Matrix data should be a list with exactly two integers: [0, 1]
if not data['c_macro'] and not all('matrix' in key_data.keys() or len(key_data) == 2 or all(isinstance(n, int) for n in key_data) for key_data in data['layout']):
cli.log.error(f'{kb}: "{layout}" has no "matrix" definition in either "info.json" or "<keyboard>.h"!')
for keymap in keymaps:
if not keymap_check(kb, keymap):
ok = False
# Report status

View File

@@ -28,6 +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('-t', '--no-temp', arg_only=True, action='store_true', help="Remove temporary files during build.")
@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.")
@@ -69,6 +70,7 @@ def multibuild(cli):
all: {keyboard_safe}_binary
{keyboard_safe}_binary:
@rm -f "{QMK_FIRMWARE}/.build/failed.log.{keyboard_safe}" || true
@echo "Compiling QMK Firmware for target: '{keyboard_name}:{cli.args.keymap}'..." >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}"
+@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/builddefs/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false {' '.join(cli.args.env)} \\
>>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" 2>&1 \\
|| cp "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" "{QMK_FIRMWARE}/.build/failed.log.{os.getpid()}.{keyboard_safe}"
@@ -76,11 +78,26 @@ all: {keyboard_safe}_binary
|| {{ grep '\[WARNINGS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" >/dev/null 2>&1 && printf "Build %-64s \e[1;33m[WARNINGS]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}" ; }} \\
|| printf "Build %-64s \e[1;32m[OK]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}"
@rm -f "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" || true
"""# noqa
)
# yapf: enable
if cli.args.no_temp:
# yapf: disable
f.write(
f"""\
@rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.elf" 2>/dev/null || true
@rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.map" 2>/dev/null || true
@rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.hex" 2>/dev/null || true
@rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.bin" 2>/dev/null || true
@rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{cli.args.keymap}.uf2" 2>/dev/null || true
@rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}" || true
@rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}_{cli.args.keymap}" || true
"""# noqa
)
# yapf: enable
f.write('\n')
cli.run([make_cmd, *get_make_parallel_args(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
# Check for failures