chore(lib/utils) update from mainline

This commit is contained in:
Florian Didron
2026-01-19 17:04:25 +07:00
parent a07f8e6c7d
commit 7038782bc4
37 changed files with 1167 additions and 173 deletions

View File

@@ -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',

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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']))

View File

@@ -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):

View File

@@ -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))

View File

@@ -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}"

View File

@@ -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)

View File

@@ -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!')

View File

@@ -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)

View File

@@ -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))

View File

@@ -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 = []

View File

@@ -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

View File

@@ -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))

View File

@@ -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:

View File

@@ -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))

View File

@@ -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