Kaynağa Gözat

CLI: Udev related fixes and improvements (#10736)

Erovia 4 yıl önce
ebeveyn
işleme
b337ba798e

+ 106 - 81
lib/python/qmk/cli/doctor.py

@@ -7,6 +7,7 @@ import re
 import shutil
 import shutil
 import subprocess
 import subprocess
 from pathlib import Path
 from pathlib import Path
+from enum import Enum
 
 
 from milc import cli
 from milc import cli
 from qmk import submodules
 from qmk import submodules
@@ -14,6 +15,13 @@ from qmk.constants import QMK_FIRMWARE
 from qmk.questions import yesno
 from qmk.questions import yesno
 from qmk.commands import run
 from qmk.commands import run
 
 
+
+class CheckStatus(Enum):
+    OK = 1
+    WARNING = 2
+    ERROR = 3
+
+
 ESSENTIAL_BINARIES = {
 ESSENTIAL_BINARIES = {
     'dfu-programmer': {},
     'dfu-programmer': {},
     'avrdude': {},
     'avrdude': {},
@@ -33,9 +41,12 @@ def _udev_rule(vid, pid=None, *args):
     """
     """
     rule = ""
     rule = ""
     if pid:
     if pid:
-        rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % (vid, pid)
+        rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % (
+            vid,
+            pid,
+        )
     else:
     else:
-        rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % vid
+        rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid
     if args:
     if args:
         rule = ', '.join([rule, *args])
         rule = ', '.join([rule, *args])
     return rule
     return rule
@@ -69,24 +80,25 @@ def check_arm_gcc_version():
         version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip()
         version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip()
         cli.log.info('Found arm-none-eabi-gcc version %s', version_number)
         cli.log.info('Found arm-none-eabi-gcc version %s', version_number)
 
 
-    return True  # Right now all known arm versions are ok
+    return CheckStatus.OK  # Right now all known arm versions are ok
 
 
 
 
 def check_avr_gcc_version():
 def check_avr_gcc_version():
     """Returns True if the avr-gcc version is not known to cause problems.
     """Returns True if the avr-gcc version is not known to cause problems.
     """
     """
+    rc = CheckStatus.ERROR
     if 'output' in ESSENTIAL_BINARIES['avr-gcc']:
     if 'output' in ESSENTIAL_BINARIES['avr-gcc']:
         version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip()
         version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip()
 
 
+        cli.log.info('Found avr-gcc version %s', version_number)
+        rc = CheckStatus.OK
+
         parsed_version = parse_gcc_version(version_number)
         parsed_version = parse_gcc_version(version_number)
         if parsed_version['major'] > 8:
         if parsed_version['major'] > 8:
-            cli.log.error('We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.')
-            return False
+            cli.log.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.')
+            rc = CheckStatus.WARNING
 
 
-        cli.log.info('Found avr-gcc version %s', version_number)
-        return True
-
-    return False
+    return rc
 
 
 
 
 def check_avrdude_version():
 def check_avrdude_version():
@@ -95,7 +107,7 @@ def check_avrdude_version():
         version_number = last_line.split()[2][:-1]
         version_number = last_line.split()[2][:-1]
         cli.log.info('Found avrdude version %s', version_number)
         cli.log.info('Found avrdude version %s', version_number)
 
 
-    return True
+    return CheckStatus.OK
 
 
 
 
 def check_dfu_util_version():
 def check_dfu_util_version():
@@ -104,7 +116,7 @@ def check_dfu_util_version():
         version_number = first_line.split()[1]
         version_number = first_line.split()[1]
         cli.log.info('Found dfu-util version %s', version_number)
         cli.log.info('Found dfu-util version %s', version_number)
 
 
-    return True
+    return CheckStatus.OK
 
 
 
 
 def check_dfu_programmer_version():
 def check_dfu_programmer_version():
@@ -113,7 +125,7 @@ def check_dfu_programmer_version():
         version_number = first_line.split()[1]
         version_number = first_line.split()[1]
         cli.log.info('Found dfu-programmer version %s', version_number)
         cli.log.info('Found dfu-programmer version %s', version_number)
 
 
-    return True
+    return CheckStatus.OK
 
 
 
 
 def check_binaries():
 def check_binaries():
@@ -131,58 +143,56 @@ def check_binaries():
 def check_submodules():
 def check_submodules():
     """Iterates through all submodules to make sure they're cloned and up to date.
     """Iterates through all submodules to make sure they're cloned and up to date.
     """
     """
-    ok = True
-
     for submodule in submodules.status().values():
     for submodule in submodules.status().values():
         if submodule['status'] is None:
         if submodule['status'] is None:
             cli.log.error('Submodule %s has not yet been cloned!', submodule['name'])
             cli.log.error('Submodule %s has not yet been cloned!', submodule['name'])
-            ok = False
+            return CheckStatus.ERROR
         elif not submodule['status']:
         elif not submodule['status']:
-            cli.log.error('Submodule %s is not up to date!', submodule['name'])
-            ok = False
+            cli.log.warning('Submodule %s is not up to date!', submodule['name'])
+            return CheckStatus.WARNING
 
 
-    return ok
+    return CheckStatus.OK
 
 
 
 
 def check_udev_rules():
 def check_udev_rules():
     """Make sure the udev rules look good.
     """Make sure the udev rules look good.
     """
     """
-    ok = True
+    rc = CheckStatus.OK
     udev_dir = Path("/etc/udev/rules.d/")
     udev_dir = Path("/etc/udev/rules.d/")
     desired_rules = {
     desired_rules = {
         'atmel-dfu': {
         '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", "2FFB")  # AT90USB128
+            _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", "2ffb")  # AT90USB128
         },
         },
-        'kiibohd': {_udev_rule("1C11", "B007")},
+        'kiibohd': {_udev_rule("1c11", "b007")},
         'stm32': {
         'stm32': {
-            _udev_rule("1EAF", "0003"),  # STM32duino
-            _udev_rule("0483", "DF11")  # STM32 DFU
+            _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"')},
+        'bootloadhid': {_udev_rule("16c0", "05df")},
+        'usbasploader': {_udev_rule("16c0", "05dc")},
+        'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')},
         'caterina': {
         'caterina': {
             # Spark Fun Electronics
             # 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
+            _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
             # Arduino SA
             _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'),  # Leonardo
             _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'),  # Leonardo
             _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'),  # Micro
             _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
+            # 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
         }
         }
     }
     }
 
 
@@ -209,31 +219,43 @@ def check_udev_rules():
 
 
         # Check if the desired rules are among the currently present rules
         # Check if the desired rules are among the currently present rules
         for bootloader, rules in desired_rules.items():
         for bootloader, rules in desired_rules.items():
-            # For caterina, check if ModemManager is running
-            if bootloader == "caterina":
-                if check_modem_manager():
-                    ok = False
-                    cli.log.warn("{bg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.")
             if not rules.issubset(current_rules):
             if not rules.issubset(current_rules):
                 deprecated_rule = deprecated_rules.get(bootloader)
                 deprecated_rule = deprecated_rules.get(bootloader)
                 if deprecated_rule and deprecated_rule.issubset(current_rules):
                 if deprecated_rule and deprecated_rule.issubset(current_rules):
-                    cli.log.warn("{bg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader)
+                    cli.log.warning("{fg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader)
                 else:
                 else:
-                    cli.log.warn("{bg_yellow}Missing udev rules for '%s' boards. See https://docs.qmk.fm/#/faq_build?id=linux-udev-rules for more details.", bootloader)
+                    # For caterina, check if ModemManager is running
+                    if bootloader == "caterina":
+                        if check_modem_manager():
+                            rc = CheckStatus.WARNING
+                            cli.log.warning("{fg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.")
+                    rc = CheckStatus.WARNING
+                    cli.log.warning("{fg_yellow}Missing or outdated udev rules for '%s' boards. Run 'sudo cp %s/util/udev/50-qmk.rules /etc/udev/rules.d/'.", bootloader, QMK_FIRMWARE)
 
 
-    return ok
+    else:
+        cli.log.warning("{fg_yellow}'%s' does not exist. Skipping udev rule checking...", udev_dir)
+
+    return rc
+
+
+def check_systemd():
+    """Check if it's a systemd system
+    """
+    return bool(shutil.which("systemctl"))
 
 
 
 
 def check_modem_manager():
 def check_modem_manager():
     """Returns True if ModemManager is running.
     """Returns True if ModemManager is running.
+
     """
     """
-    if shutil.which("systemctl"):
+    if check_systemd():
         mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
         mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
         if mm_check.returncode == 0:
         if mm_check.returncode == 0:
             return True
             return True
-
     else:
     else:
-        cli.log.warn("Can't find systemctl to check for ModemManager.")
+        """(TODO): Add check for non-systemd systems
+        """
+    return False
 
 
 
 
 def is_executable(command):
 def is_executable(command):
@@ -263,12 +285,8 @@ def os_test_linux():
     """Run the Linux specific tests.
     """Run the Linux specific tests.
     """
     """
     cli.log.info("Detected {fg_cyan}Linux.")
     cli.log.info("Detected {fg_cyan}Linux.")
-    ok = True
 
 
-    if not check_udev_rules():
-        ok = False
-
-    return ok
+    return check_udev_rules()
 
 
 
 
 def os_test_macos():
 def os_test_macos():
@@ -276,7 +294,7 @@ def os_test_macos():
     """
     """
     cli.log.info("Detected {fg_cyan}macOS.")
     cli.log.info("Detected {fg_cyan}macOS.")
 
 
-    return True
+    return CheckStatus.OK
 
 
 
 
 def os_test_windows():
 def os_test_windows():
@@ -284,7 +302,7 @@ def os_test_windows():
     """
     """
     cli.log.info("Detected {fg_cyan}Windows.")
     cli.log.info("Detected {fg_cyan}Windows.")
 
 
-    return True
+    return CheckStatus.OK
 
 
 
 
 @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
 @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@@ -299,23 +317,20 @@ def doctor(cli):
         * [ ] Compile a trivial program with each compiler
         * [ ] Compile a trivial program with each compiler
     """
     """
     cli.log.info('QMK Doctor is checking your environment.')
     cli.log.info('QMK Doctor is checking your environment.')
-    ok = True
+    status = CheckStatus.OK
 
 
     # Determine our OS and run platform specific tests
     # Determine our OS and run platform specific tests
     platform_id = platform.platform().lower()
     platform_id = platform.platform().lower()
 
 
     if 'darwin' in platform_id or 'macos' in platform_id:
     if 'darwin' in platform_id or 'macos' in platform_id:
-        if not os_test_macos():
-            ok = False
+        status = os_test_macos()
     elif 'linux' in platform_id:
     elif 'linux' in platform_id:
-        if not os_test_linux():
-            ok = False
+        status = os_test_linux()
     elif 'windows' in platform_id:
     elif 'windows' in platform_id:
-        if not os_test_windows():
-            ok = False
+        status = os_test_windows()
     else:
     else:
-        cli.log.error('Unsupported OS detected: %s', platform_id)
-        ok = False
+        cli.log.warning('Unsupported OS detected: %s', platform_id)
+        status = CheckStatus.WARNING
 
 
     cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE)
     cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE)
 
 
@@ -330,31 +345,41 @@ def doctor(cli):
     if bin_ok:
     if bin_ok:
         cli.log.info('All dependencies are installed.')
         cli.log.info('All dependencies are installed.')
     else:
     else:
-        ok = False
+        status = CheckStatus.ERROR
 
 
     # Make sure the tools are at the correct version
     # Make sure the tools are at the correct version
+    ver_ok = []
     for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version):
     for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version):
-        if not check():
-            ok = False
+        ver_ok.append(check())
+
+    if CheckStatus.ERROR in ver_ok:
+        status = CheckStatus.ERROR
+    elif CheckStatus.WARNING in ver_ok and status == CheckStatus.OK:
+        status = CheckStatus.WARNING
 
 
     # Check out the QMK submodules
     # Check out the QMK submodules
     sub_ok = check_submodules()
     sub_ok = check_submodules()
 
 
-    if sub_ok:
+    if sub_ok == CheckStatus.OK:
         cli.log.info('Submodules are up to date.')
         cli.log.info('Submodules are up to date.')
     else:
     else:
         if yesno('Would you like to clone the submodules?', default=True):
         if yesno('Would you like to clone the submodules?', default=True):
             submodules.update()
             submodules.update()
             sub_ok = check_submodules()
             sub_ok = check_submodules()
 
 
-        if not sub_ok:
-            ok = False
+        if CheckStatus.ERROR in sub_ok:
+            status = CheckStatus.ERROR
+        elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK:
+            status = CheckStatus.WARNING
 
 
     # Report a summary of our findings to the user
     # Report a summary of our findings to the user
-    if ok:
+    if status == CheckStatus.OK:
         cli.log.info('{fg_green}QMK is ready to go')
         cli.log.info('{fg_green}QMK is ready to go')
+        return 0
+    elif status == CheckStatus.WARNING:
+        cli.log.info('{fg_yellow}QMK is ready to go, but minor problems were found')
+        return 1
     else:
     else:
-        cli.log.info('{fg_yellow}Problems detected, please fix these problems before proceeding.')
-        # FIXME(skullydazed/unclaimed): Link to a document about troubleshooting, or discord or something
-
-    return ok
+        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/Uq7gcHh) for help.')
+        return 2

+ 12 - 12
lib/python/qmk/tests/test_cli_commands.py

@@ -13,14 +13,14 @@ def check_subcommand(command, *args):
     return result
     return result
 
 
 
 
-def check_returncode(result, expected=0):
+def check_returncode(result, expected=[0]):
     """Print stdout if `result.returncode` does not match `expected`.
     """Print stdout if `result.returncode` does not match `expected`.
     """
     """
-    if result.returncode != expected:
+    if result.returncode not in expected:
         print('`%s` stdout:' % ' '.join(result.args))
         print('`%s` stdout:' % ' '.join(result.args))
         print(result.stdout)
         print(result.stdout)
         print('returncode:', result.returncode)
         print('returncode:', result.returncode)
-    assert result.returncode == expected
+    assert result.returncode in expected
 
 
 
 
 def test_cformat():
 def test_cformat():
@@ -45,7 +45,7 @@ def test_flash():
 
 
 def test_flash_bootloaders():
 def test_flash_bootloaders():
     result = check_subcommand('flash', '-b')
     result = check_subcommand('flash', '-b')
-    check_returncode(result, 1)
+    check_returncode(result, [1])
 
 
 
 
 def test_config():
 def test_config():
@@ -62,7 +62,7 @@ def test_kle2json():
 
 
 def test_doctor():
 def test_doctor():
     result = check_subcommand('doctor', '-n')
     result = check_subcommand('doctor', '-n')
-    check_returncode(result)
+    check_returncode(result, [0, 1])
     assert 'QMK Doctor is checking your environment.' in result.stdout
     assert 'QMK Doctor is checking your environment.' in result.stdout
     assert 'QMK is ready to go' in result.stdout
     assert 'QMK is ready to go' in result.stdout
 
 
@@ -89,43 +89,43 @@ def test_list_keyboards():
 
 
 def test_list_keymaps():
 def test_list_keymaps():
     result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest')
     result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert 'default' and 'test' in result.stdout
     assert 'default' and 'test' in result.stdout
 
 
 
 
 def test_list_keymaps_long():
 def test_list_keymaps_long():
     result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest')
     result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert 'default' and 'test' in result.stdout
     assert 'default' and 'test' in result.stdout
 
 
 
 
 def test_list_keymaps_kb_only():
 def test_list_keymaps_kb_only():
     result = check_subcommand('list-keymaps', '-kb', 'niu_mini')
     result = check_subcommand('list-keymaps', '-kb', 'niu_mini')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert 'default' and 'via' in result.stdout
     assert 'default' and 'via' in result.stdout
 
 
 
 
 def test_list_keymaps_vendor_kb():
 def test_list_keymaps_vendor_kb():
     result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar')
     result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert 'default' and 'via' in result.stdout
     assert 'default' and 'via' in result.stdout
 
 
 
 
 def test_list_keymaps_vendor_kb_rev():
 def test_list_keymaps_vendor_kb_rev():
     result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2')
     result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert 'default' and 'via' in result.stdout
     assert 'default' and 'via' in result.stdout
 
 
 
 
 def test_list_keymaps_no_keyboard_found():
 def test_list_keymaps_no_keyboard_found():
     result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl')
     result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl')
-    check_returncode(result, 1)
+    check_returncode(result, [1])
     assert 'does not exist' in result.stdout
     assert 'does not exist' in result.stdout
 
 
 
 
 def test_json2c():
 def test_json2c():
     result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json')
     result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json')
-    check_returncode(result, 0)
+    check_returncode(result)
     assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'
     assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'
 
 
 
 

+ 23 - 23
util/udev/50-qmk.rules

@@ -1,60 +1,60 @@
 # Atmel DFU
 # Atmel DFU
 ### ATmega16U2
 ### ATmega16U2
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FEF", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2fef", TAG+="uaccess"
 ### ATmega32U2
 ### ATmega32U2
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF0", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff0", TAG+="uaccess"
 ### ATmega16U4
 ### ATmega16U4
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF3", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff3", TAG+="uaccess"
 ### ATmega32U4
 ### ATmega32U4
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF4", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff4", TAG+="uaccess"
 ### AT90USB64
 ### AT90USB64
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF9", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff9", TAG+="uaccess"
 ### AT90USB128
 ### AT90USB128
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FFB", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ffb", TAG+="uaccess"
 
 
 # Input Club
 # Input Club
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1C11", ATTRS{idProduct}=="B007", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c11", ATTRS{idProduct}=="b007", TAG+="uaccess"
 
 
 # STM32duino
 # STM32duino
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1EAF", ATTRS{idProduct}=="0003", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0003", TAG+="uaccess"
 # STM32 DFU
 # STM32 DFU
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="DF11", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", TAG+="uaccess"
 
 
 # BootloadHID
 # BootloadHID
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="16C0", ATTRS{idProduct}=="05DF", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", TAG+="uaccess"
 
 
 # USBAspLoader
 # USBAspLoader
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="16C0", ATTRS{idProduct}=="05DC", TAG+="uaccess", RUN{builtin}+="uaccess"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", TAG+="uaccess"
 
 
 # ModemManager should ignore the following devices
 # ModemManager should ignore the following devices
 # Atmel SAM-BA (Massdrop)
 # Atmel SAM-BA (Massdrop)
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="6124", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 
 
 # Caterina (Pro Micro)
 # Caterina (Pro Micro)
 ## Spark Fun Electronics
 ## Spark Fun Electronics
 ### Pro Micro 3V3/8MHz
 ### Pro Micro 3V3/8MHz
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9203", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9203", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### Pro Micro 5V/16MHz
 ### Pro Micro 5V/16MHz
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9205", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9205", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### LilyPad 3V3/8MHz (and some Pro Micro clones)
 ### LilyPad 3V3/8MHz (and some Pro Micro clones)
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9207", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9207", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ## Pololu Electronics
 ## Pololu Electronics
 ### A-Star 32U4
 ### A-Star 32U4
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1FFB", ATTRS{idProduct}=="0101", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="0101", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ## Arduino SA
 ## Arduino SA
 ### Leonardo
 ### Leonardo
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0036", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### Micro
 ### Micro
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0037", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ## Adafruit Industries LLC
 ## Adafruit Industries LLC
 ### Feather 32U4
 ### Feather 32U4
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000C", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000c", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### ItsyBitsy 32U4 3V3/8MHz
 ### ItsyBitsy 32U4 3V3/8MHz
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000D", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000d", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### ItsyBitsy 32U4 5V/16MHz
 ### ItsyBitsy 32U4 5V/16MHz
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000E", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000e", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ## dog hunter AG
 ## dog hunter AG
 ### Leonardo
 ### Leonardo
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="2A03", ATTRS{idProduct}=="0036", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
 ### Micro
 ### Micro
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="2A03", ATTRS{idProduct}=="0037", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"