mirror of
https://github.com/zsa/qmk_firmware.git
synced 2026-04-17 05:59:44 +00:00
chore(lib/utils) update from mainline
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import platformdirs
|
||||
import shlex
|
||||
import sys
|
||||
from importlib.util import find_spec
|
||||
@@ -12,6 +14,28 @@ from subprocess import run
|
||||
from milc import cli, __VERSION__
|
||||
from milc.questions import yesno
|
||||
|
||||
|
||||
def _get_default_distrib_path():
|
||||
if 'windows' in platform.platform().lower():
|
||||
try:
|
||||
result = cli.run(['cygpath', '-w', '/opt/qmk'])
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return platformdirs.user_data_dir('qmk')
|
||||
|
||||
|
||||
# Ensure the QMK distribution is on the `$PATH` if present. This must be kept in sync with qmk/qmk_cli.
|
||||
QMK_DISTRIB_DIR = Path(os.environ.get('QMK_DISTRIB_DIR', _get_default_distrib_path()))
|
||||
if QMK_DISTRIB_DIR.exists():
|
||||
os.environ['PATH'] = str(QMK_DISTRIB_DIR / 'bin') + os.pathsep + os.environ['PATH']
|
||||
|
||||
# Prepend any user-defined path prefix
|
||||
if 'QMK_PATH_PREFIX' in os.environ:
|
||||
os.environ['PATH'] = os.environ['QMK_PATH_PREFIX'] + os.pathsep + os.environ['PATH']
|
||||
|
||||
import_names = {
|
||||
# A mapping of package name to importable name
|
||||
'pep8-naming': 'pep8ext_naming',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.keyboard import resolve_keyboard, keyboard_folder, keyboard_alias_definitions
|
||||
from qmk.keyboard import keyboard_folder, keyboard_alias_definitions
|
||||
|
||||
|
||||
def _safe_keyboard_folder(target):
|
||||
@@ -17,10 +17,6 @@ def _target_keyboard_exists(target):
|
||||
if not target:
|
||||
return False
|
||||
|
||||
# If the target directory existed but there was no rules.mk or rules.mk was incorrectly parsed, then we can't build it.
|
||||
if not resolve_keyboard(target):
|
||||
return False
|
||||
|
||||
# If the target directory exists but it itself has an invalid alias or invalid rules.mk, then we can't build it either.
|
||||
if not _safe_keyboard_folder(target):
|
||||
return False
|
||||
@@ -29,6 +25,21 @@ def _target_keyboard_exists(target):
|
||||
return True
|
||||
|
||||
|
||||
def _alias_not_self(alias):
|
||||
"""Check if alias points to itself, either directly or within a circular reference
|
||||
"""
|
||||
aliases = keyboard_alias_definitions()
|
||||
|
||||
found = set()
|
||||
while alias in aliases:
|
||||
found.add(alias)
|
||||
alias = aliases[alias].get('target', alias)
|
||||
if alias in found:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.subcommand('Validates the list of keyboard aliases.', hidden=True)
|
||||
def ci_validate_aliases(cli):
|
||||
aliases = keyboard_alias_definitions()
|
||||
@@ -36,7 +47,11 @@ def ci_validate_aliases(cli):
|
||||
success = True
|
||||
for alias in aliases.keys():
|
||||
target = aliases[alias].get('target', None)
|
||||
if not _target_keyboard_exists(target):
|
||||
if not _alias_not_self(alias):
|
||||
cli.log.error(f'Keyboard alias {alias} should not point to itself')
|
||||
success = False
|
||||
|
||||
elif not _target_keyboard_exists(target):
|
||||
cli.log.error(f'Keyboard alias {alias} has a target that doesn\'t exist: {target}')
|
||||
success = False
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Check for specific programs.
|
||||
"""
|
||||
from enum import Enum
|
||||
import re
|
||||
import shutil
|
||||
from subprocess import DEVNULL, TimeoutExpired
|
||||
from tempfile import TemporaryDirectory
|
||||
@@ -9,6 +8,7 @@ from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
from qmk import submodules
|
||||
from qmk.commands import find_make
|
||||
|
||||
|
||||
class CheckStatus(Enum):
|
||||
@@ -17,7 +17,13 @@ class CheckStatus(Enum):
|
||||
ERROR = 3
|
||||
|
||||
|
||||
WHICH_MAKE = Path(find_make()).name
|
||||
|
||||
ESSENTIAL_BINARIES = {
|
||||
WHICH_MAKE: {},
|
||||
'git': {},
|
||||
'dos2unix': {},
|
||||
'diff': {},
|
||||
'dfu-programmer': {},
|
||||
'avrdude': {},
|
||||
'dfu-util': {},
|
||||
@@ -30,14 +36,39 @@ ESSENTIAL_BINARIES = {
|
||||
}
|
||||
|
||||
|
||||
def _parse_gcc_version(version):
|
||||
m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
|
||||
def _check_make_version():
|
||||
last_line = ESSENTIAL_BINARIES[WHICH_MAKE]['output'].split('\n')[0]
|
||||
version_number = last_line.split()[2]
|
||||
cli.log.info('Found %s version %s', WHICH_MAKE, version_number)
|
||||
|
||||
return {
|
||||
'major': int(m.group(1)),
|
||||
'minor': int(m.group(2)) if m.group(2) else 0,
|
||||
'patch': int(m.group(3)) if m.group(3) else 0,
|
||||
}
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_git_version():
|
||||
last_line = ESSENTIAL_BINARIES['git']['output'].split('\n')[0]
|
||||
version_number = last_line.split()[2]
|
||||
cli.log.info('Found git version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_dos2unix_version():
|
||||
last_line = ESSENTIAL_BINARIES['dos2unix']['output'].split('\n')[0]
|
||||
version_number = last_line.split()[1]
|
||||
cli.log.info('Found dos2unix version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_diff_version():
|
||||
last_line = ESSENTIAL_BINARIES['diff']['output'].split('\n')[0]
|
||||
if 'Apple diff' in last_line:
|
||||
version_number = last_line
|
||||
else:
|
||||
version_number = last_line.split()[3]
|
||||
cli.log.info('Found diff version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_arm_gcc_version():
|
||||
@@ -148,16 +179,24 @@ def check_binaries():
|
||||
"""Iterates through ESSENTIAL_BINARIES and tests them.
|
||||
"""
|
||||
ok = CheckStatus.OK
|
||||
missing_from_path = []
|
||||
|
||||
for binary in sorted(ESSENTIAL_BINARIES):
|
||||
try:
|
||||
if not is_executable(binary):
|
||||
if not is_in_path(binary):
|
||||
ok = CheckStatus.ERROR
|
||||
missing_from_path.append(binary)
|
||||
elif not is_executable(binary):
|
||||
ok = CheckStatus.ERROR
|
||||
except TimeoutExpired:
|
||||
cli.log.debug('Timeout checking %s', binary)
|
||||
if ok != CheckStatus.ERROR:
|
||||
ok = CheckStatus.WARNING
|
||||
|
||||
if missing_from_path:
|
||||
location_noun = 'its location' if len(missing_from_path) == 1 else 'their locations'
|
||||
cli.log.error('{fg_red}' + ', '.join(missing_from_path) + f' may need to be installed, or {location_noun} added to your path.')
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
@@ -165,6 +204,10 @@ def check_binary_versions():
|
||||
"""Check the versions of ESSENTIAL_BINARIES
|
||||
"""
|
||||
checks = {
|
||||
WHICH_MAKE: _check_make_version,
|
||||
'git': _check_git_version,
|
||||
'dos2unix': _check_dos2unix_version,
|
||||
'diff': _check_diff_version,
|
||||
'arm-none-eabi-gcc': _check_arm_gcc_version,
|
||||
'avr-gcc': _check_avr_gcc_version,
|
||||
'avrdude': _check_avrdude_version,
|
||||
@@ -196,15 +239,18 @@ def check_submodules():
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def is_executable(command):
|
||||
"""Returns True if command exists and can be executed.
|
||||
def is_in_path(command):
|
||||
"""Returns True if command is found in the path.
|
||||
"""
|
||||
# Make sure the command is in the path.
|
||||
res = shutil.which(command)
|
||||
if res is None:
|
||||
if shutil.which(command) is None:
|
||||
cli.log.error("{fg_red}Can't find %s in your path.", command)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_executable(command):
|
||||
"""Returns True if command can be executed.
|
||||
"""
|
||||
# Make sure the command can be executed
|
||||
version_arg = ESSENTIAL_BINARIES[command].get('version_arg', '--version')
|
||||
check = cli.run([command, version_arg], combined_output=True, stdin=DEVNULL, timeout=5)
|
||||
|
||||
@@ -87,7 +87,7 @@ def check_udev_rules():
|
||||
line = line.strip()
|
||||
if not line.startswith("#") and len(line):
|
||||
current_rules.add(line)
|
||||
except PermissionError:
|
||||
except (PermissionError, FileNotFoundError):
|
||||
cli.log.debug("Failed to read: %s", rule_file)
|
||||
|
||||
# Check if the desired rules are among the currently present rules
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
Check out the user's QMK environment and make sure it's ready to compile.
|
||||
"""
|
||||
import platform
|
||||
from subprocess import DEVNULL
|
||||
|
||||
from milc import cli
|
||||
from milc.questions import yesno
|
||||
@@ -16,6 +15,60 @@ from qmk.commands import in_virtualenv
|
||||
from qmk.userspace import qmk_userspace_paths, qmk_userspace_validate, UserspaceValidationError
|
||||
|
||||
|
||||
def distrib_tests():
|
||||
def _load_kvp_file(file):
|
||||
"""Load a simple key=value file into a dictionary
|
||||
"""
|
||||
vars = {}
|
||||
with open(file, 'r') as f:
|
||||
for line in f:
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
vars[key.strip()] = value.strip()
|
||||
return vars
|
||||
|
||||
def _parse_toolchain_release_file(file):
|
||||
"""Parse the QMK toolchain release info file
|
||||
"""
|
||||
try:
|
||||
vars = _load_kvp_file(file)
|
||||
return f'{vars.get("TOOLCHAIN_HOST", "unknown")}:{vars.get("TOOLCHAIN_TARGET", "unknown")}:{vars.get("COMMIT_HASH", "unknown")}'
|
||||
except Exception as e:
|
||||
cli.log.warning('Error reading QMK toolchain release info file: %s', e)
|
||||
return f'Unknown toolchain release info file: {file}'
|
||||
|
||||
def _parse_flashutils_release_file(file):
|
||||
"""Parse the QMK flashutils release info file
|
||||
"""
|
||||
try:
|
||||
vars = _load_kvp_file(file)
|
||||
return f'{vars.get("FLASHUTILS_HOST", "unknown")}:{vars.get("COMMIT_HASH", "unknown")}'
|
||||
except Exception as e:
|
||||
cli.log.warning('Error reading QMK flashutils release info file: %s', e)
|
||||
return f'Unknown flashutils release info file: {file}'
|
||||
|
||||
try:
|
||||
from qmk.cli import QMK_DISTRIB_DIR
|
||||
if (QMK_DISTRIB_DIR / 'etc').exists():
|
||||
cli.log.info('Found QMK tools distribution directory: {fg_cyan}%s', QMK_DISTRIB_DIR)
|
||||
|
||||
toolchains = [_parse_toolchain_release_file(file) for file in (QMK_DISTRIB_DIR / 'etc').glob('toolchain_release_*')]
|
||||
if len(toolchains) > 0:
|
||||
cli.log.info('Found QMK toolchains: {fg_cyan}%s', ', '.join(toolchains))
|
||||
else:
|
||||
cli.log.warning('No QMK toolchains manifest found.')
|
||||
|
||||
flashutils = [_parse_flashutils_release_file(file) for file in (QMK_DISTRIB_DIR / 'etc').glob('flashutils_release_*')]
|
||||
if len(flashutils) > 0:
|
||||
cli.log.info('Found QMK flashutils: {fg_cyan}%s', ', '.join(flashutils))
|
||||
else:
|
||||
cli.log.warning('No QMK flashutils manifest found.')
|
||||
except ImportError:
|
||||
cli.log.info('QMK tools distribution not found.')
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def os_tests():
|
||||
"""Determine our OS and run platform specific tests
|
||||
"""
|
||||
@@ -124,10 +177,12 @@ def doctor(cli):
|
||||
* [ ] Compile a trivial program with each compiler
|
||||
"""
|
||||
cli.log.info('QMK Doctor is checking your environment.')
|
||||
cli.log.info('Python version: %s', platform.python_version())
|
||||
cli.log.info('CLI version: %s', cli.version)
|
||||
cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE)
|
||||
|
||||
status = os_status = os_tests()
|
||||
distrib_tests()
|
||||
|
||||
userspace_tests(None)
|
||||
|
||||
@@ -141,12 +196,6 @@ def doctor(cli):
|
||||
|
||||
# Make sure the basic CLI tools we need are available and can be executed.
|
||||
bin_ok = check_binaries()
|
||||
|
||||
if bin_ok == CheckStatus.ERROR:
|
||||
if yesno('Would you like to install dependencies?', default=True):
|
||||
cli.run(['util/qmk_install.sh', '-y'], stdin=DEVNULL, capture_output=False)
|
||||
bin_ok = check_binaries()
|
||||
|
||||
if bin_ok == CheckStatus.OK:
|
||||
cli.log.info('All dependencies are installed.')
|
||||
elif bin_ok == CheckStatus.WARNING:
|
||||
@@ -163,7 +212,6 @@ def doctor(cli):
|
||||
|
||||
# Check out the QMK submodules
|
||||
sub_ok = check_submodules()
|
||||
|
||||
if sub_ok == CheckStatus.OK:
|
||||
cli.log.info('Submodules are up to date.')
|
||||
else:
|
||||
@@ -186,6 +234,7 @@ def doctor(cli):
|
||||
cli.log.info('{fg_yellow}QMK is ready to go, but minor problems were found')
|
||||
return 1
|
||||
else:
|
||||
cli.log.info('{fg_red}Major problems detected, please fix these problems before proceeding.')
|
||||
cli.log.info('{fg_blue}Check out the FAQ (https://docs.qmk.fm/#/faq_build) or join the QMK Discord (https://discord.gg/qmk) for help.')
|
||||
cli.log.info('{fg_red}Major problems detected, please fix these problems before proceeding.{fg_reset}')
|
||||
cli.log.info('{fg_blue}If you\'re missing dependencies, try following the instructions on: https://docs.qmk.fm/newbs_getting_started{fg_reset}')
|
||||
cli.log.info('{fg_blue}Additionally, check out the FAQ (https://docs.qmk.fm/#/faq_build) or join the QMK Discord (https://discord.gg/qmk) for help.{fg_reset}')
|
||||
return 2
|
||||
|
||||
@@ -250,8 +250,8 @@ def to_hex(b: int) -> str:
|
||||
|
||||
|
||||
@cli.argument('filename', type=normpath, help='The autocorrection database file')
|
||||
@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('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a output file is supplied.')
|
||||
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a output file is supplied.')
|
||||
@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.subcommand('Generate the autocorrection data file from a dictionary file.')
|
||||
@@ -263,7 +263,7 @@ def generate_autocorrect_data(cli):
|
||||
current_keyboard = cli.args.keyboard or cli.config.user.keyboard or cli.config.generate_autocorrect_data.keyboard
|
||||
current_keymap = cli.args.keymap or cli.config.user.keymap or cli.config.generate_autocorrect_data.keymap
|
||||
|
||||
if current_keyboard and current_keymap:
|
||||
if not cli.args.output and current_keyboard and current_keymap:
|
||||
cli.args.output = locate_keymap(current_keyboard, current_keymap).parent / 'autocorrect_data.h'
|
||||
|
||||
assert all(0 <= b <= 255 for b in data)
|
||||
|
||||
@@ -72,19 +72,6 @@ def generate_matrix_size(kb_info_json, config_h_lines):
|
||||
config_h_lines.append(generate_define('MATRIX_ROWS', kb_info_json['matrix_size']['rows']))
|
||||
|
||||
|
||||
def generate_matrix_masked(kb_info_json, config_h_lines):
|
||||
""""Enable matrix mask if required"""
|
||||
mask_required = False
|
||||
|
||||
if 'matrix_grid' in kb_info_json.get('dip_switch', {}):
|
||||
mask_required = True
|
||||
if 'matrix_grid' in kb_info_json.get('split', {}).get('handedness', {}):
|
||||
mask_required = True
|
||||
|
||||
if mask_required:
|
||||
config_h_lines.append(generate_define('MATRIX_MASKED'))
|
||||
|
||||
|
||||
def generate_config_items(kb_info_json, config_h_lines):
|
||||
"""Iterate through the info_config map to generate basic config values.
|
||||
"""
|
||||
@@ -138,11 +125,12 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''):
|
||||
config_h_lines.append(generate_define(f'ENCODER_A_PINS{postfix}', f'{{ {", ".join(a_pads)} }}'))
|
||||
config_h_lines.append(generate_define(f'ENCODER_B_PINS{postfix}', f'{{ {", ".join(b_pads)} }}'))
|
||||
|
||||
if None in resolutions:
|
||||
cli.log.debug(f"Unable to generate ENCODER_RESOLUTION{postfix} configuration")
|
||||
elif len(resolutions) == 0:
|
||||
if len(resolutions) == 0 or all(r is None for r in resolutions):
|
||||
cli.log.debug(f"Skipping ENCODER_RESOLUTION{postfix} configuration")
|
||||
elif len(set(resolutions)) == 1:
|
||||
return
|
||||
|
||||
resolutions = [4 if r is None else r for r in resolutions]
|
||||
if len(set(resolutions)) == 1:
|
||||
config_h_lines.append(generate_define(f'ENCODER_RESOLUTION{postfix}', resolutions[0]))
|
||||
else:
|
||||
config_h_lines.append(generate_define(f'ENCODER_RESOLUTIONS{postfix}', f'{{ {", ".join(map(str,resolutions))} }}'))
|
||||
@@ -202,8 +190,6 @@ def generate_config_h(cli):
|
||||
|
||||
generate_matrix_size(kb_info_json, config_h_lines)
|
||||
|
||||
generate_matrix_masked(kb_info_json, config_h_lines)
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
||||
|
||||
|
||||
@@ -92,15 +92,15 @@ def _generate_helpers(lines, keycodes):
|
||||
for group, codes in temp.items():
|
||||
lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
|
||||
hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
|
||||
lines.append(f'#define IS_{ _translate_group(group).upper() }_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})')
|
||||
lines.append(f'#define IS_{_translate_group(group).upper()}_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})')
|
||||
|
||||
lines.append('')
|
||||
lines.append('// Switch statement Helpers')
|
||||
for group, codes in temp.items():
|
||||
lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
|
||||
hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
|
||||
name = f'{ _translate_group(group).upper() }_KEYCODE_RANGE'
|
||||
lines.append(f'#define { name.ljust(35) } {lo} ... {hi}')
|
||||
name = f'{_translate_group(group).upper()}_KEYCODE_RANGE'
|
||||
lines.append(f'#define {name.ljust(35)} {lo} ... {hi}')
|
||||
|
||||
|
||||
def _generate_aliases(lines, keycodes):
|
||||
|
||||
@@ -96,11 +96,10 @@ def generate_rules_mk(cli):
|
||||
rules_mk_lines.append(generate_rule('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(generate_rule('CUSTOM_MATRIX', 'lite'))
|
||||
else:
|
||||
rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'yes'))
|
||||
if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
|
||||
rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'lite'))
|
||||
elif kb_info_json.get('matrix_pins', {}).get('custom'):
|
||||
rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'yes'))
|
||||
|
||||
if converter:
|
||||
rules_mk_lines.append(generate_rule('CONVERT_TO', converter))
|
||||
|
||||
@@ -8,6 +8,7 @@ from qmk.path import normpath
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.git import git_get_qmk_hash, git_get_version, git_is_dirty
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
from qmk.util import triplet_to_bcd
|
||||
|
||||
TIME_FMT = '%Y-%m-%d-%H:%M:%S'
|
||||
|
||||
@@ -32,12 +33,14 @@ def generate_version_h(cli):
|
||||
git_dirty = False
|
||||
git_version = "NA"
|
||||
git_qmk_hash = "NA"
|
||||
git_bcd_version = "0x00000000"
|
||||
chibios_version = "NA"
|
||||
chibios_contrib_version = "NA"
|
||||
else:
|
||||
git_dirty = git_is_dirty()
|
||||
git_version = git_get_version() or current_time
|
||||
git_qmk_hash = git_get_qmk_hash() or "Unknown"
|
||||
git_bcd_version = triplet_to_bcd(git_version)
|
||||
chibios_version = git_get_version("chibios", "os") or current_time
|
||||
chibios_contrib_version = git_get_version("chibios-contrib", "os") or current_time
|
||||
|
||||
@@ -48,6 +51,7 @@ def generate_version_h(cli):
|
||||
f"""
|
||||
#define QMK_VERSION "{git_version}"
|
||||
#define QMK_BUILDDATE "{current_time}"
|
||||
#define QMK_VERSION_BCD {git_bcd_version}
|
||||
#define QMK_GIT_HASH "{git_qmk_hash}{'*' if git_dirty else ''}"
|
||||
#define CHIBIOS_VERSION "{chibios_version}"
|
||||
#define CHIBIOS_CONTRIB_VERSION "{chibios_contrib_version}"
|
||||
|
||||
@@ -102,6 +102,48 @@ def show_matrix(kb_info_json, title_caps=True):
|
||||
print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, labels))
|
||||
|
||||
|
||||
def show_leds(kb_info_json, title_caps=True):
|
||||
"""Render LED indices per key, using the keyboard's key layout geometry.
|
||||
|
||||
We build a map from (row, col) -> LED index using rgb_matrix/led_matrix layout,
|
||||
then label each key with its LED index. Keys without an associated LED are left blank.
|
||||
"""
|
||||
# Prefer rgb_matrix, fall back to led_matrix
|
||||
led_feature = None
|
||||
for feature in ['rgb_matrix', 'led_matrix']:
|
||||
if 'layout' in kb_info_json.get(feature, {}):
|
||||
led_feature = feature
|
||||
break
|
||||
|
||||
if not led_feature:
|
||||
cli.echo('{fg_yellow}No rgb_matrix/led_matrix layout found to derive LED indices.{fg_reset}')
|
||||
return
|
||||
|
||||
# Build mapping from matrix position -> LED indices for faster lookup later
|
||||
by_matrix = {}
|
||||
for idx, led in enumerate(kb_info_json[led_feature]['layout']):
|
||||
if 'matrix' in led:
|
||||
led_key = tuple(led.get('matrix'))
|
||||
by_matrix[led_key] = idx
|
||||
|
||||
# For each keyboard layout (e.g., LAYOUT), render keys labeled with LED index (or blank)
|
||||
for layout_name, layout in kb_info_json['layouts'].items():
|
||||
labels = []
|
||||
for key in layout['layout']:
|
||||
led_key = tuple(key.get('matrix'))
|
||||
label = str(by_matrix[led_key]) if led_key in by_matrix else ''
|
||||
|
||||
labels.append(label)
|
||||
|
||||
# Header
|
||||
if title_caps:
|
||||
cli.echo('{fg_blue}LED indices for "%s"{fg_reset}:', layout_name)
|
||||
else:
|
||||
cli.echo('{fg_blue}leds_%s{fg_reset}:', layout_name)
|
||||
|
||||
print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, labels))
|
||||
|
||||
|
||||
def print_friendly_output(kb_info_json):
|
||||
"""Print the info.json in a friendly text format.
|
||||
"""
|
||||
@@ -169,6 +211,7 @@ def print_parsed_rules_mk(keyboard_name):
|
||||
@cli.argument('-km', '--keymap', help='Keymap to show info for (Optional).')
|
||||
@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
|
||||
@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')
|
||||
@cli.argument('-L', '--leds', action='store_true', help='Render the LED layout with LED indices (rgb_matrix/led_matrix).')
|
||||
@cli.argument('-f', '--format', default='friendly', arg_only=True, help='Format to display the data in (friendly, text, json) (Default: friendly).')
|
||||
@cli.argument('--ascii', action='store_true', default=not UNICODE_SUPPORT, help='Render layout box drawings in ASCII only.')
|
||||
@cli.argument('-r', '--rules-mk', action='store_true', help='Render the parsed values of the keyboard\'s rules.mk file.')
|
||||
@@ -227,5 +270,8 @@ def info(cli):
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, title_caps)
|
||||
|
||||
if cli.config.info.leds:
|
||||
show_leds(kb_info_json, title_caps)
|
||||
|
||||
if cli.config.info.keymap:
|
||||
show_keymap(kb_info_json, title_caps)
|
||||
|
||||
@@ -304,6 +304,10 @@ def keyboard_check(kb): # noqa C901
|
||||
cli.log.error(f'{kb}: The file "{file}" should not exist!')
|
||||
ok = False
|
||||
|
||||
if not _get_readme_files(kb):
|
||||
cli.log.error(f'{kb}: Is missing a readme.md file!')
|
||||
ok = False
|
||||
|
||||
for file in _get_readme_files(kb):
|
||||
if _is_invalid_readme(file):
|
||||
cli.log.error(f'{kb}: The file "{file}" still contains template tokens!')
|
||||
|
||||
@@ -5,10 +5,9 @@ from milc import cli
|
||||
import qmk.keyboard
|
||||
|
||||
|
||||
@cli.argument('--no-resolve-defaults', arg_only=True, action='store_false', help='Ignore any "DEFAULT_FOLDER" within keyboards rules.mk')
|
||||
@cli.subcommand("List the keyboards currently defined within QMK")
|
||||
def list_keyboards(cli):
|
||||
"""List the keyboards currently defined within QMK
|
||||
"""
|
||||
for keyboard_name in qmk.keyboard.list_keyboards(cli.args.no_resolve_defaults):
|
||||
for keyboard_name in qmk.keyboard.list_keyboards():
|
||||
print(keyboard_name)
|
||||
|
||||
@@ -16,7 +16,7 @@ from qmk.build_targets import BuildTarget, JsonKeymapBuildTarget
|
||||
from qmk.util import maybe_exit_config
|
||||
|
||||
|
||||
def mass_compile_targets(targets: List[BuildTarget], clean: bool, dry_run: bool, no_temp: bool, parallel: int, **env):
|
||||
def mass_compile_targets(targets: List[BuildTarget], clean: bool, dry_run: bool, no_temp: bool, parallel: int, print_failures: bool, **env):
|
||||
if len(targets) == 0:
|
||||
return
|
||||
|
||||
@@ -37,6 +37,30 @@ def mass_compile_targets(targets: List[BuildTarget], clean: bool, dry_run: bool,
|
||||
|
||||
builddir.mkdir(parents=True, exist_ok=True)
|
||||
with open(makefile, "w") as f:
|
||||
# yapf: disable
|
||||
f.write(
|
||||
f"""\
|
||||
# This file is auto-generated by qmk mass-compile
|
||||
# Do not edit this file directly.
|
||||
all: print_failures
|
||||
.PHONY: all_targets print_failures
|
||||
print_failures: all_targets
|
||||
"""# noqa
|
||||
)
|
||||
if print_failures:
|
||||
f.write(
|
||||
f"""\
|
||||
@for f in $$(ls .build/failed.log.{os.getpid()}.* 2>/dev/null | sort); do \\
|
||||
echo; \\
|
||||
echo "======================================================================================"; \\
|
||||
echo "Failed build log: $$f"; \\
|
||||
echo "------------------------------------------------------"; \\
|
||||
cat $$f; \\
|
||||
echo "------------------------------------------------------"; \\
|
||||
done
|
||||
"""# noqa
|
||||
)
|
||||
# yapf: enable
|
||||
for target in sorted(targets, key=lambda t: (t.keyboard, t.keymap)):
|
||||
keyboard_name = target.keyboard
|
||||
keymap_name = target.keymap
|
||||
@@ -58,7 +82,7 @@ def mass_compile_targets(targets: List[BuildTarget], clean: bool, dry_run: bool,
|
||||
f.write(
|
||||
f"""\
|
||||
.PHONY: {target_filename}{target_suffix}_binary
|
||||
all: {target_filename}{target_suffix}_binary
|
||||
all_targets: {target_filename}{target_suffix}_binary
|
||||
{target_filename}{target_suffix}_binary:
|
||||
@rm -f "{build_log}" || true
|
||||
@echo "Compiling QMK Firmware for target: '{keyboard_name}:{keymap_name}'..." >>"{build_log}"
|
||||
@@ -98,6 +122,7 @@ all: {target_filename}{target_suffix}_binary
|
||||
@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('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the commands to be run.")
|
||||
@cli.argument('-p', '--print-failures', arg_only=True, action='store_true', help="Print failed builds.")
|
||||
@cli.argument(
|
||||
'-f',
|
||||
'--filter',
|
||||
@@ -123,4 +148,4 @@ def mass_compile(cli):
|
||||
else:
|
||||
targets = search_keymap_targets([('all', cli.config.mass_compile.keymap)], cli.args.filter)
|
||||
|
||||
return mass_compile_targets(targets, cli.args.clean, cli.args.dry_run, cli.args.no_temp, cli.config.mass_compile.parallel, **build_environment(cli.args.env))
|
||||
return mass_compile_targets(targets, cli.args.clean, cli.args.dry_run, cli.args.no_temp, cli.config.mass_compile.parallel, cli.args.print_failures, **build_environment(cli.args.env))
|
||||
|
||||
@@ -6,14 +6,14 @@ from dotty_dict import dotty
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder, resolve_keyboard
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.info import info_json, find_info_json
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import json_load
|
||||
|
||||
|
||||
def _candidate_files(keyboard):
|
||||
kb_dir = Path(resolve_keyboard(keyboard))
|
||||
kb_dir = Path(keyboard)
|
||||
|
||||
cur_dir = Path('keyboards')
|
||||
files = []
|
||||
|
||||
@@ -9,6 +9,7 @@ from milc import cli
|
||||
from milc.questions import question, choice
|
||||
|
||||
from qmk.constants import HAS_QMK_USERSPACE, QMK_USERSPACE
|
||||
from qmk.errors import NoSuchKeyboardError
|
||||
from qmk.path import is_keyboard, keymaps, keymap
|
||||
from qmk.git import git_get_username
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
@@ -110,13 +111,18 @@ def new_keymap(cli):
|
||||
cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} does not exist! Please choose a valid name.')
|
||||
return False
|
||||
|
||||
# generate keymap paths
|
||||
keymaps_dirs = keymaps(kb_name)
|
||||
keymap_path_default = keymap(kb_name, 'default')
|
||||
keymap_path_new = keymaps_dirs[0] / user_name
|
||||
# validate before any keymap ops
|
||||
try:
|
||||
keymaps_dirs = keymaps(kb_name)
|
||||
keymap_path_new = keymaps_dirs[0] / user_name
|
||||
except NoSuchKeyboardError:
|
||||
cli.log.error(f'Keymap folder for {{fg_cyan}}{kb_name}{{fg_reset}} does not exist!')
|
||||
return False
|
||||
|
||||
if not keymap_path_default.exists():
|
||||
cli.log.error(f'Default keymap {{fg_cyan}}{keymap_path_default}{{fg_reset}} does not exist!')
|
||||
keymap_path_default = keymap(kb_name, 'default')
|
||||
|
||||
if not keymap_path_default:
|
||||
cli.log.error(f'Default keymap for {{fg_cyan}}{kb_name}{{fg_reset}} does not exist!')
|
||||
return False
|
||||
|
||||
if not validate_keymap_name(user_name):
|
||||
@@ -134,7 +140,7 @@ def new_keymap(cli):
|
||||
_set_converter(keymap_path_new / 'keymap.json', converter)
|
||||
|
||||
# end message to user
|
||||
cli.log.info(f'{{fg_green}}Created a new keymap called {{fg_cyan}}{user_name}{{fg_green}} in: {{fg_cyan}}{keymap_path_new}.{{fg_reset}}')
|
||||
cli.log.info(f'{{fg_green}}Created a new keymap called {{fg_cyan}}{user_name}{{fg_green}} in: {{fg_cyan}}{keymap_path_new}{{fg_reset}}.')
|
||||
cli.log.info(f"Compile a firmware with your new keymap by typing: {{fg_yellow}}qmk compile -kb {kb_name} -km {user_name}{{fg_reset}}.")
|
||||
|
||||
# Add to userspace compile if we have userspace available
|
||||
|
||||
@@ -5,7 +5,7 @@ from milc import cli
|
||||
|
||||
@cli.argument('--allow-unknown', arg_only=True, action='store_true', help="Return original if rule is not a valid keyboard.")
|
||||
@cli.argument('keyboard', arg_only=True, help='The keyboard\'s name')
|
||||
@cli.subcommand('Resolve DEFAULT_FOLDER and any keyboard_aliases for provided rule')
|
||||
@cli.subcommand('Resolve any keyboard_aliases for provided rule')
|
||||
def resolve_alias(cli):
|
||||
try:
|
||||
print(keyboard_folder(cli.args.keyboard))
|
||||
|
||||
@@ -47,6 +47,7 @@ def userspace_add(cli):
|
||||
from qmk.cli.new.keymap import new_keymap
|
||||
cli.config.new_keymap.keyboard = cli.args.keyboard
|
||||
cli.config.new_keymap.keymap = cli.args.keymap
|
||||
cli.args.skip_converter = True
|
||||
if new_keymap(cli) is not False:
|
||||
userspace.add_target(keyboard=cli.args.keyboard, keymap=cli.args.keymap, build_env=build_env)
|
||||
else:
|
||||
|
||||
@@ -20,6 +20,7 @@ def _extra_arg_setter(target, extra_args):
|
||||
@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('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the commands to be run.")
|
||||
@cli.argument('-p', '--print-failures', arg_only=True, action='store_true', help="Print failed builds.")
|
||||
@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.subcommand('Compiles the build targets specified in userspace `qmk.json`.')
|
||||
def userspace_compile(cli):
|
||||
@@ -42,4 +43,4 @@ def userspace_compile(cli):
|
||||
if len(keyboard_keymap_targets) > 0:
|
||||
build_targets.extend(search_keymap_targets(keyboard_keymap_targets))
|
||||
|
||||
return mass_compile_targets(list(set(build_targets)), cli.args.clean, cli.args.dry_run, cli.config.userspace_compile.no_temp, cli.config.userspace_compile.parallel, **build_environment(cli.args.env))
|
||||
return mass_compile_targets(list(set(build_targets)), cli.args.clean, cli.args.dry_run, cli.config.userspace_compile.no_temp, cli.config.userspace_compile.parallel, cli.args.print_failures, **build_environment(cli.args.env))
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.constants import QMK_FIRMWARE, HAS_QMK_USERSPACE
|
||||
from qmk.cli.doctor.main import userspace_tests
|
||||
|
||||
|
||||
@cli.subcommand('Checks userspace configuration.')
|
||||
def userspace_doctor(cli):
|
||||
userspace_tests(QMK_FIRMWARE)
|
||||
|
||||
return 0 if HAS_QMK_USERSPACE else 1
|
||||
|
||||
Reference in New Issue
Block a user