diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index be1f2cd74468..ccf9afdfa109 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -3,13 +3,23 @@ import re import sys import os +from typing import TextIO, TypedDict from qmk.constants import QMK_FIRMWARE from qmk.path import normpath -from milc import cli +from milc import MILC, cli -def eprint(*args, **kwargs): +class Defines(TypedDict): + keys: list[str] + dict: dict[str, str] + + +Override = tuple[str, str] +Diff = tuple[list[Override], list[str], list[str]] + + +def eprint(*args: object, **kwargs: object) -> None: print(*args, file=sys.stderr, **kwargs) @@ -39,25 +49,27 @@ def eprint(*args, **kwargs): """ -def collect_defines(filepath): +def collect_defines(filepath: str) -> Defines: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() define_search = re.compile(r'(?m)^#\s*define\s+(?:.*\\\r?\n)*.*$', re.MULTILINE) value_search = re.compile(r'^#\s*define\s+(?P[a-zA-Z0-9_]+(\([^\)]*\))?)\s*(?P.*)', re.DOTALL) define_matches = define_search.findall(content) - defines = {"keys": [], "dict": {}} + defines: Defines = {"keys": [], "dict": {}} for define_match in define_matches: value_match = value_search.search(define_match) + if value_match is None: + raise RuntimeError defines["keys"].append(value_match.group("name")) defines["dict"][value_match.group("name")] = value_match.group("value") return defines -def check_diffs(input_defs, reference_defs): - not_present_in_input = [] - not_present_in_reference = [] - to_override = [] +def check_diffs(input_defs: Defines, reference_defs: Defines) -> Diff: + not_present_in_input: list[str] = [] + not_present_in_reference: list[str] = [] + to_override: list[Override] = [] for key in reference_defs["keys"]: if key not in input_defs["dict"]: @@ -76,7 +88,7 @@ def check_diffs(input_defs, reference_defs): return (to_override, not_present_in_input, not_present_in_reference) -def migrate_chconf_h(to_override, outfile): +def migrate_chconf_h(to_override: list[Override], outfile: TextIO) -> None: print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) for override in to_override: @@ -86,7 +98,7 @@ def migrate_chconf_h(to_override, outfile): print("#include_next \n", file=outfile) -def migrate_halconf_h(to_override, outfile): +def migrate_halconf_h(to_override: list[Override], outfile: TextIO) -> None: print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) for override in to_override: @@ -96,7 +108,7 @@ def migrate_halconf_h(to_override, outfile): print("#include_next \n", file=outfile) -def migrate_mcuconf_h(to_override, outfile): +def migrate_mcuconf_h(to_override: list[Override], outfile: TextIO) -> None: print(file_header.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile) print("#include_next \n", file=outfile) @@ -113,7 +125,7 @@ def migrate_mcuconf_h(to_override, outfile): @cli.argument('-d', '--delete', arg_only=True, action='store_true', help='If the file has no overrides, migration will delete the input file.') @cli.argument('-f', '--force', arg_only=True, action='store_true', help='Re-migrates an already migrated file, even if it doesn\'t detect a full ChibiOS config.') @cli.subcommand('Generates a migrated ChibiOS configuration file, as a result of comparing the input against a reference') -def chibios_confmigrate(cli): +def chibios_confmigrate(cli: MILC) -> None: """Generates a usable ChibiOS replacement configuration file, based on a fully-defined conf and a reference config. """ diff --git a/lib/python/qmk/cli/ci/validate_aliases.py b/lib/python/qmk/cli/ci/validate_aliases.py index 7f781d43970b..49379f479de7 100644 --- a/lib/python/qmk/cli/ci/validate_aliases.py +++ b/lib/python/qmk/cli/ci/validate_aliases.py @@ -1,18 +1,19 @@ """Validates the list of keyboard aliases. """ -from milc import cli +from typing import Optional +from milc import MILC, cli from qmk.keyboard import resolve_keyboard, keyboard_folder, keyboard_alias_definitions -def _safe_keyboard_folder(target): +def _safe_keyboard_folder(target: str) -> Optional[str]: try: return keyboard_folder(target) # throws ValueError if it's invalid except Exception: return None -def _target_keyboard_exists(target): +def _target_keyboard_exists(target: Optional[str]) -> bool: # If there's no target, then we can't build it. if not target: return False @@ -30,12 +31,12 @@ def _target_keyboard_exists(target): @cli.subcommand('Validates the list of keyboard aliases.', hidden=True) -def ci_validate_aliases(cli): +def ci_validate_aliases(cli: MILC) -> bool: aliases = keyboard_alias_definitions() success = True for alias in aliases.keys(): - target = aliases[alias].get('target', None) + target: Optional[str] = aliases[alias].get('target', None) if not _target_keyboard_exists(target): cli.log.error(f'Keyboard alias {alias} has a target that doesn\'t exist: {target}') success = False diff --git a/lib/python/qmk/cli/doctor/check.py b/lib/python/qmk/cli/doctor/check.py index 2804a1d7df16..dbdad4fa059d 100644 --- a/lib/python/qmk/cli/doctor/check.py +++ b/lib/python/qmk/cli/doctor/check.py @@ -6,11 +6,18 @@ from subprocess import DEVNULL, TimeoutExpired from tempfile import TemporaryDirectory from pathlib import Path +from typing import TypedDict from milc import cli from qmk import submodules +class Version(TypedDict): + major: int + minor: int + patch: int + + class CheckStatus(Enum): OK = 1 WARNING = 2 @@ -30,8 +37,10 @@ class CheckStatus(Enum): } -def _parse_gcc_version(version): +def _parse_gcc_version(version: str) -> Version: m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version) + if m is None: + raise RuntimeError return { 'major': int(m.group(1)), @@ -40,7 +49,7 @@ def _parse_gcc_version(version): } -def _check_arm_gcc_version(): +def _check_arm_gcc_version() -> CheckStatus: """Returns True if the arm-none-eabi-gcc version is not known to cause problems. """ version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip() @@ -50,7 +59,7 @@ def _check_arm_gcc_version(): return _check_arm_gcc_installation() -def _check_arm_gcc_installation(): +def _check_arm_gcc_installation() -> CheckStatus: """Returns OK if the arm-none-eabi-gcc is fully installed and can produce binaries. """ with TemporaryDirectory() as temp_dir: @@ -77,7 +86,7 @@ def _check_arm_gcc_installation(): return CheckStatus.OK -def _check_avr_gcc_version(): +def _check_avr_gcc_version() -> CheckStatus: """Returns True if the avr-gcc version is not known to cause problems. """ version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip() @@ -87,7 +96,7 @@ def _check_avr_gcc_version(): return _check_avr_gcc_installation() -def _check_avr_gcc_installation(): +def _check_avr_gcc_installation() -> CheckStatus: """Returns OK if the avr-gcc is fully installed and can produce binaries. """ with TemporaryDirectory() as temp_dir: @@ -114,7 +123,7 @@ def _check_avr_gcc_installation(): return CheckStatus.OK -def _check_avrdude_version(): +def _check_avrdude_version() -> CheckStatus: last_line = ESSENTIAL_BINARIES['avrdude']['output'].split('\n')[-2] version_number = last_line.split()[2][:-1] cli.log.info('Found avrdude version %s', version_number) @@ -122,7 +131,7 @@ def _check_avrdude_version(): return CheckStatus.OK -def _check_dfu_util_version(): +def _check_dfu_util_version() -> CheckStatus: first_line = ESSENTIAL_BINARIES['dfu-util']['output'].split('\n')[0] version_number = first_line.split()[1] cli.log.info('Found dfu-util version %s', version_number) @@ -130,7 +139,7 @@ def _check_dfu_util_version(): return CheckStatus.OK -def _check_dfu_programmer_version(): +def _check_dfu_programmer_version() -> CheckStatus: first_line = ESSENTIAL_BINARIES['dfu-programmer']['output'].split('\n')[0] version_number = first_line.split()[1] cli.log.info('Found dfu-programmer version %s', version_number) @@ -138,7 +147,7 @@ def _check_dfu_programmer_version(): return CheckStatus.OK -def check_binaries(): +def check_binaries() -> CheckStatus: """Iterates through ESSENTIAL_BINARIES and tests them. """ ok = CheckStatus.OK @@ -155,7 +164,7 @@ def check_binaries(): return ok -def check_binary_versions(): +def check_binary_versions() -> list[CheckStatus]: """Check the versions of ESSENTIAL_BINARIES """ checks = { @@ -178,7 +187,7 @@ def check_binary_versions(): return versions -def check_submodules(): +def check_submodules() -> CheckStatus: """Iterates through all submodules to make sure they're cloned and up to date. """ for submodule in submodules.status().values(): @@ -190,7 +199,7 @@ def check_submodules(): return CheckStatus.OK -def is_executable(command): +def is_executable(command: str) -> bool: """Returns True if command exists and can be executed. """ # Make sure the command is in the path. @@ -213,7 +222,7 @@ def is_executable(command): return False -def release_info(file='/etc/os-release'): +def release_info(file: str = '/etc/os-release') -> dict[str, str]: """Parse release info to dict """ ret = {} diff --git a/lib/python/qmk/cli/doctor/linux.py b/lib/python/qmk/cli/doctor/linux.py index f0850d4e6488..36cba6c275fa 100644 --- a/lib/python/qmk/cli/doctor/linux.py +++ b/lib/python/qmk/cli/doctor/linux.py @@ -3,6 +3,7 @@ import platform import shutil from pathlib import Path +from typing import Optional from milc import cli @@ -10,11 +11,11 @@ from .check import CheckStatus, release_info -def _is_wsl(): +def _is_wsl() -> bool: return 'microsoft' in platform.uname().release.lower() -def _udev_rule(vid, pid=None, *args): +def _udev_rule(vid: str, pid: Optional[str] = None, *args: str) -> str: """ Helper function that return udev rules """ rule = "" @@ -30,8 +31,8 @@ def _udev_rule(vid, pid=None, *args): return rule -def _generate_desired_rules(bootloader_vids_pids): - rules = dict() +def _generate_desired_rules(bootloader_vids_pids: dict[str, set[tuple[str, str]]]) -> dict[str, set[str]]: + rules: dict[str, set[str]] = {} for bl in bootloader_vids_pids.keys(): rules[bl] = set() for vid_pid in bootloader_vids_pids[bl]: @@ -42,7 +43,7 @@ def _generate_desired_rules(bootloader_vids_pids): return rules -def _deprecated_udev_rule(vid, pid=None): +def _deprecated_udev_rule(vid: str, pid: Optional[str] = None) -> str: """ Helper function that return udev rules Note: these are no longer the recommended rules, this is just used to check for them @@ -53,7 +54,7 @@ def _deprecated_udev_rule(vid, pid=None): return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", MODE:="0666"' % vid -def check_udev_rules(): +def check_udev_rules() -> CheckStatus: """Make sure the udev rules look good. """ rc = CheckStatus.OK @@ -111,13 +112,13 @@ def check_udev_rules(): return rc -def check_systemd(): +def check_systemd() -> bool: """Check if it's a systemd system """ return bool(shutil.which("systemctl")) -def check_modem_manager(): +def check_modem_manager() -> bool: """Returns True if ModemManager is running. """ @@ -131,7 +132,7 @@ def check_modem_manager(): return False -def os_test_linux(): +def os_test_linux() -> CheckStatus: """Run the Linux specific tests. """ info = release_info() diff --git a/lib/python/qmk/cli/doctor/macos.py b/lib/python/qmk/cli/doctor/macos.py index 5d088c9492c6..4d9670bf6903 100644 --- a/lib/python/qmk/cli/doctor/macos.py +++ b/lib/python/qmk/cli/doctor/macos.py @@ -5,7 +5,7 @@ from .check import CheckStatus -def os_test_macos(): +def os_test_macos() -> CheckStatus: """Run the Mac specific tests. """ cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel') diff --git a/lib/python/qmk/cli/doctor/main.py b/lib/python/qmk/cli/doctor/main.py index 391353ebbfb1..1a81b8b4162c 100755 --- a/lib/python/qmk/cli/doctor/main.py +++ b/lib/python/qmk/cli/doctor/main.py @@ -5,7 +5,7 @@ import platform from subprocess import DEVNULL -from milc import cli +from milc import MILC, cli from milc.questions import yesno from qmk import submodules @@ -16,7 +16,7 @@ from qmk.userspace import qmk_userspace_paths, qmk_userspace_validate, UserspaceValidationError -def os_tests(): +def os_tests() -> CheckStatus: """Determine our OS and run platform specific tests """ platform_id = platform.platform().lower() @@ -35,7 +35,7 @@ def os_tests(): return CheckStatus.WARNING -def git_tests(): +def git_tests() -> CheckStatus: """Run Git-related checks """ status = CheckStatus.OK @@ -75,7 +75,7 @@ def git_tests(): return status -def output_submodule_status(): +def output_submodule_status() -> None: """Prints out information related to the submodule status. """ cli.log.info('Submodule status:') @@ -93,7 +93,7 @@ def output_submodule_status(): cli.log.error(f'- {sub_name}: <<< missing or unknown >>>') -def userspace_tests(qmk_firmware): +def userspace_tests(qmk_firmware) -> None: if qmk_firmware: cli.log.info(f'QMK home: {{fg_cyan}}{qmk_firmware}') @@ -115,7 +115,7 @@ def userspace_tests(qmk_firmware): @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') -def doctor(cli): +def doctor(cli: MILC) -> int: """Basic QMK environment checks. This is currently very simple, it just checks that all the expected binaries are on your system. diff --git a/lib/python/qmk/cli/doctor/windows.py b/lib/python/qmk/cli/doctor/windows.py index 26bb65374b84..00aae38e28f5 100644 --- a/lib/python/qmk/cli/doctor/windows.py +++ b/lib/python/qmk/cli/doctor/windows.py @@ -5,7 +5,7 @@ from .check import CheckStatus, release_info -def os_test_windows(): +def os_test_windows() -> CheckStatus: """Run the Windows specific tests. """ win32_ver = platform.win32_ver() diff --git a/lib/python/qmk/makefile.py b/lib/python/qmk/makefile.py index ae95abbf2368..b1dc1a4c79f9 100644 --- a/lib/python/qmk/makefile.py +++ b/lib/python/qmk/makefile.py @@ -1,9 +1,10 @@ """ Functions for working with Makefiles """ from pathlib import Path +from typing import Optional, Union -def parse_rules_mk_file(file, rules_mk=None): +def parse_rules_mk_file(filepath: Union[str, Path], rules_mk: Optional[dict[str, str]] = None) -> dict[str, str]: """Turn a rules.mk file into a dictionary. Args: @@ -16,7 +17,7 @@ def parse_rules_mk_file(file, rules_mk=None): if not rules_mk: rules_mk = {} - file = Path(file) + file = Path(filepath) if file.exists(): rules_mk_lines = file.read_text(encoding='utf-8').split("\n") diff --git a/lib/python/qmk/util.py b/lib/python/qmk/util.py index 8f99410e1de8..163b3dc4e83e 100644 --- a/lib/python/qmk/util.py +++ b/lib/python/qmk/util.py @@ -3,15 +3,16 @@ import contextlib import multiprocessing import sys +from typing import Union from milc import cli -maybe_exit_should_exit = True -maybe_exit_reraise = False +maybe_exit_should_exit: bool = True +maybe_exit_reraise: bool = False # Controls whether or not early `exit()` calls should be made -def maybe_exit(rc): +def maybe_exit(rc: Union[None, int, str]) -> None: if maybe_exit_should_exit: sys.exit(rc) if maybe_exit_reraise: @@ -20,14 +21,14 @@ def maybe_exit(rc): raise e -def maybe_exit_config(should_exit: bool = True, should_reraise: bool = False): +def maybe_exit_config(should_exit: bool = True, should_reraise: bool = False) -> None: global maybe_exit_should_exit global maybe_exit_reraise maybe_exit_should_exit = should_exit maybe_exit_reraise = should_reraise -def truthy(value, value_if_unknown=False): +def truthy(value: object, value_if_unknown: bool = False) -> bool: """Returns True if the value is truthy, False otherwise. Deals with: @@ -35,7 +36,7 @@ def truthy(value, value_if_unknown=False): False: 0, false, f, no, n, off """ if value in {False, True}: - return bool(value) + return value is True test_value = str(value).strip().lower()