diff options
| author | Erovia <Erovia@users.noreply.github.com> | 2020-11-16 21:09:32 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-11-16 21:09:32 +0000 |
| commit | b337ba798e23876870f8daf415bc929c0b5382fa (patch) | |
| tree | 60ad63e1a8b8a33583f67a3617ad10c7c117c3c9 /lib/python | |
| parent | 94e94ffb5bbe61b5da4aad205016923746010b23 (diff) | |
| download | qmk_firmware-b337ba798e23876870f8daf415bc929c0b5382fa.tar.gz qmk_firmware-b337ba798e23876870f8daf415bc929c0b5382fa.zip | |
CLI: Udev related fixes and improvements (#10736)
Diffstat (limited to 'lib/python')
| -rwxr-xr-x | lib/python/qmk/cli/doctor.py | 187 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 24 |
2 files changed, 118 insertions, 93 deletions
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py index caa98a71c..a5eda555f 100755 --- a/lib/python/qmk/cli/doctor.py +++ b/lib/python/qmk/cli/doctor.py | |||
| @@ -7,6 +7,7 @@ import re | |||
| 7 | import shutil | 7 | import shutil |
| 8 | import subprocess | 8 | import subprocess |
| 9 | from pathlib import Path | 9 | from pathlib import Path |
| 10 | from enum import Enum | ||
| 10 | 11 | ||
| 11 | from milc import cli | 12 | from milc import cli |
| 12 | from qmk import submodules | 13 | from qmk import submodules |
| @@ -14,6 +15,13 @@ from qmk.constants import QMK_FIRMWARE | |||
| 14 | from qmk.questions import yesno | 15 | from qmk.questions import yesno |
| 15 | from qmk.commands import run | 16 | from qmk.commands import run |
| 16 | 17 | ||
| 18 | |||
| 19 | class CheckStatus(Enum): | ||
| 20 | OK = 1 | ||
| 21 | WARNING = 2 | ||
| 22 | ERROR = 3 | ||
| 23 | |||
| 24 | |||
| 17 | ESSENTIAL_BINARIES = { | 25 | ESSENTIAL_BINARIES = { |
| 18 | 'dfu-programmer': {}, | 26 | 'dfu-programmer': {}, |
| 19 | 'avrdude': {}, | 27 | 'avrdude': {}, |
| @@ -33,9 +41,12 @@ def _udev_rule(vid, pid=None, *args): | |||
| 33 | """ | 41 | """ |
| 34 | rule = "" | 42 | rule = "" |
| 35 | if pid: | 43 | if pid: |
| 36 | rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % (vid, pid) | 44 | rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % ( |
| 45 | vid, | ||
| 46 | pid, | ||
| 47 | ) | ||
| 37 | else: | 48 | else: |
| 38 | rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % vid | 49 | rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid |
| 39 | if args: | 50 | if args: |
| 40 | rule = ', '.join([rule, *args]) | 51 | rule = ', '.join([rule, *args]) |
| 41 | return rule | 52 | return rule |
| @@ -69,24 +80,25 @@ def check_arm_gcc_version(): | |||
| 69 | version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip() | 80 | version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip() |
| 70 | cli.log.info('Found arm-none-eabi-gcc version %s', version_number) | 81 | cli.log.info('Found arm-none-eabi-gcc version %s', version_number) |
| 71 | 82 | ||
| 72 | return True # Right now all known arm versions are ok | 83 | return CheckStatus.OK # Right now all known arm versions are ok |
| 73 | 84 | ||
| 74 | 85 | ||
| 75 | def check_avr_gcc_version(): | 86 | def check_avr_gcc_version(): |
| 76 | """Returns True if the avr-gcc version is not known to cause problems. | 87 | """Returns True if the avr-gcc version is not known to cause problems. |
| 77 | """ | 88 | """ |
| 89 | rc = CheckStatus.ERROR | ||
| 78 | if 'output' in ESSENTIAL_BINARIES['avr-gcc']: | 90 | if 'output' in ESSENTIAL_BINARIES['avr-gcc']: |
| 79 | version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip() | 91 | version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip() |
| 80 | 92 | ||
| 93 | cli.log.info('Found avr-gcc version %s', version_number) | ||
| 94 | rc = CheckStatus.OK | ||
| 95 | |||
| 81 | parsed_version = parse_gcc_version(version_number) | 96 | parsed_version = parse_gcc_version(version_number) |
| 82 | if parsed_version['major'] > 8: | 97 | if parsed_version['major'] > 8: |
| 83 | cli.log.error('We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.') | 98 | cli.log.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.') |
| 84 | return False | 99 | rc = CheckStatus.WARNING |
| 85 | 100 | ||
| 86 | cli.log.info('Found avr-gcc version %s', version_number) | 101 | return rc |
| 87 | return True | ||
| 88 | |||
| 89 | return False | ||
| 90 | 102 | ||
| 91 | 103 | ||
| 92 | def check_avrdude_version(): | 104 | def check_avrdude_version(): |
| @@ -95,7 +107,7 @@ def check_avrdude_version(): | |||
| 95 | version_number = last_line.split()[2][:-1] | 107 | version_number = last_line.split()[2][:-1] |
| 96 | cli.log.info('Found avrdude version %s', version_number) | 108 | cli.log.info('Found avrdude version %s', version_number) |
| 97 | 109 | ||
| 98 | return True | 110 | return CheckStatus.OK |
| 99 | 111 | ||
| 100 | 112 | ||
| 101 | def check_dfu_util_version(): | 113 | def check_dfu_util_version(): |
| @@ -104,7 +116,7 @@ def check_dfu_util_version(): | |||
| 104 | version_number = first_line.split()[1] | 116 | version_number = first_line.split()[1] |
| 105 | cli.log.info('Found dfu-util version %s', version_number) | 117 | cli.log.info('Found dfu-util version %s', version_number) |
| 106 | 118 | ||
| 107 | return True | 119 | return CheckStatus.OK |
| 108 | 120 | ||
| 109 | 121 | ||
| 110 | def check_dfu_programmer_version(): | 122 | def check_dfu_programmer_version(): |
| @@ -113,7 +125,7 @@ def check_dfu_programmer_version(): | |||
| 113 | version_number = first_line.split()[1] | 125 | version_number = first_line.split()[1] |
| 114 | cli.log.info('Found dfu-programmer version %s', version_number) | 126 | cli.log.info('Found dfu-programmer version %s', version_number) |
| 115 | 127 | ||
| 116 | return True | 128 | return CheckStatus.OK |
| 117 | 129 | ||
| 118 | 130 | ||
| 119 | def check_binaries(): | 131 | def check_binaries(): |
| @@ -131,58 +143,56 @@ def check_binaries(): | |||
| 131 | def check_submodules(): | 143 | def check_submodules(): |
| 132 | """Iterates through all submodules to make sure they're cloned and up to date. | 144 | """Iterates through all submodules to make sure they're cloned and up to date. |
| 133 | """ | 145 | """ |
| 134 | ok = True | ||
| 135 | |||
| 136 | for submodule in submodules.status().values(): | 146 | for submodule in submodules.status().values(): |
| 137 | if submodule['status'] is None: | 147 | if submodule['status'] is None: |
| 138 | cli.log.error('Submodule %s has not yet been cloned!', submodule['name']) | 148 | cli.log.error('Submodule %s has not yet been cloned!', submodule['name']) |
| 139 | ok = False | 149 | return CheckStatus.ERROR |
| 140 | elif not submodule['status']: | 150 | elif not submodule['status']: |
| 141 | cli.log.error('Submodule %s is not up to date!', submodule['name']) | 151 | cli.log.warning('Submodule %s is not up to date!', submodule['name']) |
| 142 | ok = False | 152 | return CheckStatus.WARNING |
| 143 | 153 | ||
| 144 | return ok | 154 | return CheckStatus.OK |
| 145 | 155 | ||
| 146 | 156 | ||
| 147 | def check_udev_rules(): | 157 | def check_udev_rules(): |
| 148 | """Make sure the udev rules look good. | 158 | """Make sure the udev rules look good. |
| 149 | """ | 159 | """ |
| 150 | ok = True | 160 | rc = CheckStatus.OK |
| 151 | udev_dir = Path("/etc/udev/rules.d/") | 161 | udev_dir = Path("/etc/udev/rules.d/") |
| 152 | desired_rules = { | 162 | desired_rules = { |
| 153 | 'atmel-dfu': { | 163 | 'atmel-dfu': { |
| 154 | _udev_rule("03EB", "2FEF"), # ATmega16U2 | 164 | _udev_rule("03eb", "2fef"), # ATmega16U2 |
| 155 | _udev_rule("03EB", "2FF0"), # ATmega32U2 | 165 | _udev_rule("03eb", "2ff0"), # ATmega32U2 |
| 156 | _udev_rule("03EB", "2FF3"), # ATmega16U4 | 166 | _udev_rule("03eb", "2ff3"), # ATmega16U4 |
| 157 | _udev_rule("03EB", "2FF4"), # ATmega32U4 | 167 | _udev_rule("03eb", "2ff4"), # ATmega32U4 |
| 158 | _udev_rule("03EB", "2FF9"), # AT90USB64 | 168 | _udev_rule("03eb", "2ff9"), # AT90USB64 |
| 159 | _udev_rule("03EB", "2FFB") # AT90USB128 | 169 | _udev_rule("03eb", "2ffb") # AT90USB128 |
| 160 | }, | 170 | }, |
| 161 | 'kiibohd': {_udev_rule("1C11", "B007")}, | 171 | 'kiibohd': {_udev_rule("1c11", "b007")}, |
| 162 | 'stm32': { | 172 | 'stm32': { |
| 163 | _udev_rule("1EAF", "0003"), # STM32duino | 173 | _udev_rule("1eaf", "0003"), # STM32duino |
| 164 | _udev_rule("0483", "DF11") # STM32 DFU | 174 | _udev_rule("0483", "df11") # STM32 DFU |
| 165 | }, | 175 | }, |
| 166 | 'bootloadhid': {_udev_rule("16C0", "05DF")}, | 176 | 'bootloadhid': {_udev_rule("16c0", "05df")}, |
| 167 | 'usbasploader': {_udev_rule("16C0", "05DC")}, | 177 | 'usbasploader': {_udev_rule("16c0", "05dc")}, |
| 168 | 'massdrop': {_udev_rule("03EB", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')}, | 178 | 'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')}, |
| 169 | 'caterina': { | 179 | 'caterina': { |
| 170 | # Spark Fun Electronics | 180 | # Spark Fun Electronics |
| 171 | _udev_rule("1B4F", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz | 181 | _udev_rule("1b4f", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz |
| 172 | _udev_rule("1B4F", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz | 182 | _udev_rule("1b4f", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz |
| 173 | _udev_rule("1B4F", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones) | 183 | _udev_rule("1b4f", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones) |
| 174 | # Pololu Electronics | 184 | # Pololu EleCTRONICS |
| 175 | _udev_rule("1FFB", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4 | 185 | _udev_rule("1ffb", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4 |
| 176 | # Arduino SA | 186 | # Arduino SA |
| 177 | _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo | 187 | _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo |
| 178 | _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro | 188 | _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro |
| 179 | # Adafruit Industries LLC | 189 | # Adafruit INDUSTRIES llC |
| 180 | _udev_rule("239A", "000C", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4 | 190 | _udev_rule("239a", "000c", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4 |
| 181 | _udev_rule("239A", "000D", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz | 191 | _udev_rule("239a", "000d", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz |
| 182 | _udev_rule("239A", "000E", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz | 192 | _udev_rule("239a", "000e", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz |
| 183 | # dog hunter AG | 193 | # dog hunter ag |
| 184 | _udev_rule("2A03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo | 194 | _udev_rule("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo |
| 185 | _udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro | 195 | _udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro |
| 186 | } | 196 | } |
| 187 | } | 197 | } |
| 188 | 198 | ||
| @@ -209,31 +219,43 @@ def check_udev_rules(): | |||
| 209 | 219 | ||
| 210 | # Check if the desired rules are among the currently present rules | 220 | # Check if the desired rules are among the currently present rules |
| 211 | for bootloader, rules in desired_rules.items(): | 221 | for bootloader, rules in desired_rules.items(): |
| 212 | # For caterina, check if ModemManager is running | ||
| 213 | if bootloader == "caterina": | ||
| 214 | if check_modem_manager(): | ||
| 215 | ok = False | ||
| 216 | 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.") | ||
| 217 | if not rules.issubset(current_rules): | 222 | if not rules.issubset(current_rules): |
| 218 | deprecated_rule = deprecated_rules.get(bootloader) | 223 | deprecated_rule = deprecated_rules.get(bootloader) |
| 219 | if deprecated_rule and deprecated_rule.issubset(current_rules): | 224 | if deprecated_rule and deprecated_rule.issubset(current_rules): |
| 220 | 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) | 225 | 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) |
| 221 | else: | 226 | else: |
| 222 | 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) | 227 | # For caterina, check if ModemManager is running |
| 228 | if bootloader == "caterina": | ||
| 229 | if check_modem_manager(): | ||
| 230 | rc = CheckStatus.WARNING | ||
| 231 | 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.") | ||
| 232 | rc = CheckStatus.WARNING | ||
| 233 | 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) | ||
| 223 | 234 | ||
| 224 | return ok | 235 | else: |
| 236 | cli.log.warning("{fg_yellow}'%s' does not exist. Skipping udev rule checking...", udev_dir) | ||
| 237 | |||
| 238 | return rc | ||
| 239 | |||
| 240 | |||
| 241 | def check_systemd(): | ||
| 242 | """Check if it's a systemd system | ||
| 243 | """ | ||
| 244 | return bool(shutil.which("systemctl")) | ||
| 225 | 245 | ||
| 226 | 246 | ||
| 227 | def check_modem_manager(): | 247 | def check_modem_manager(): |
| 228 | """Returns True if ModemManager is running. | 248 | """Returns True if ModemManager is running. |
| 249 | |||
| 229 | """ | 250 | """ |
| 230 | if shutil.which("systemctl"): | 251 | if check_systemd(): |
| 231 | mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10) | 252 | mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10) |
| 232 | if mm_check.returncode == 0: | 253 | if mm_check.returncode == 0: |
| 233 | return True | 254 | return True |
| 234 | |||
| 235 | else: | 255 | else: |
| 236 | cli.log.warn("Can't find systemctl to check for ModemManager.") | 256 | """(TODO): Add check for non-systemd systems |
| 257 | """ | ||
| 258 | return False | ||
| 237 | 259 | ||
| 238 | 260 | ||
| 239 | def is_executable(command): | 261 | def is_executable(command): |
| @@ -263,12 +285,8 @@ def os_test_linux(): | |||
| 263 | """Run the Linux specific tests. | 285 | """Run the Linux specific tests. |
| 264 | """ | 286 | """ |
| 265 | cli.log.info("Detected {fg_cyan}Linux.") | 287 | cli.log.info("Detected {fg_cyan}Linux.") |
| 266 | ok = True | ||
| 267 | 288 | ||
| 268 | if not check_udev_rules(): | 289 | return check_udev_rules() |
| 269 | ok = False | ||
| 270 | |||
| 271 | return ok | ||
| 272 | 290 | ||
| 273 | 291 | ||
| 274 | def os_test_macos(): | 292 | def os_test_macos(): |
| @@ -276,7 +294,7 @@ def os_test_macos(): | |||
| 276 | """ | 294 | """ |
| 277 | cli.log.info("Detected {fg_cyan}macOS.") | 295 | cli.log.info("Detected {fg_cyan}macOS.") |
| 278 | 296 | ||
| 279 | return True | 297 | return CheckStatus.OK |
| 280 | 298 | ||
| 281 | 299 | ||
| 282 | def os_test_windows(): | 300 | def os_test_windows(): |
| @@ -284,7 +302,7 @@ def os_test_windows(): | |||
| 284 | """ | 302 | """ |
| 285 | cli.log.info("Detected {fg_cyan}Windows.") | 303 | cli.log.info("Detected {fg_cyan}Windows.") |
| 286 | 304 | ||
| 287 | return True | 305 | return CheckStatus.OK |
| 288 | 306 | ||
| 289 | 307 | ||
| 290 | @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.') | 308 | @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.') |
| @@ -299,23 +317,20 @@ def doctor(cli): | |||
| 299 | * [ ] Compile a trivial program with each compiler | 317 | * [ ] Compile a trivial program with each compiler |
| 300 | """ | 318 | """ |
| 301 | cli.log.info('QMK Doctor is checking your environment.') | 319 | cli.log.info('QMK Doctor is checking your environment.') |
| 302 | ok = True | 320 | status = CheckStatus.OK |
| 303 | 321 | ||
| 304 | # Determine our OS and run platform specific tests | 322 | # Determine our OS and run platform specific tests |
| 305 | platform_id = platform.platform().lower() | 323 | platform_id = platform.platform().lower() |
| 306 | 324 | ||
| 307 | if 'darwin' in platform_id or 'macos' in platform_id: | 325 | if 'darwin' in platform_id or 'macos' in platform_id: |
| 308 | if not os_test_macos(): | 326 | status = os_test_macos() |
| 309 | ok = False | ||
| 310 | elif 'linux' in platform_id: | 327 | elif 'linux' in platform_id: |
| 311 | if not os_test_linux(): | 328 | status = os_test_linux() |
| 312 | ok = False | ||
| 313 | elif 'windows' in platform_id: | 329 | elif 'windows' in platform_id: |
| 314 | if not os_test_windows(): | 330 | status = os_test_windows() |
| 315 | ok = False | ||
| 316 | else: | 331 | else: |
| 317 | cli.log.error('Unsupported OS detected: %s', platform_id) | 332 | cli.log.warning('Unsupported OS detected: %s', platform_id) |
| 318 | ok = False | 333 | status = CheckStatus.WARNING |
| 319 | 334 | ||
| 320 | cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE) | 335 | cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE) |
| 321 | 336 | ||
| @@ -330,31 +345,41 @@ def doctor(cli): | |||
| 330 | if bin_ok: | 345 | if bin_ok: |
| 331 | cli.log.info('All dependencies are installed.') | 346 | cli.log.info('All dependencies are installed.') |
| 332 | else: | 347 | else: |
| 333 | ok = False | 348 | status = CheckStatus.ERROR |
| 334 | 349 | ||
| 335 | # Make sure the tools are at the correct version | 350 | # Make sure the tools are at the correct version |
| 351 | ver_ok = [] | ||
| 336 | for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version): | 352 | for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version): |
| 337 | if not check(): | 353 | ver_ok.append(check()) |
| 338 | ok = False | 354 | |
| 355 | if CheckStatus.ERROR in ver_ok: | ||
| 356 | status = CheckStatus.ERROR | ||
| 357 | elif CheckStatus.WARNING in ver_ok and status == CheckStatus.OK: | ||
| 358 | status = CheckStatus.WARNING | ||
| 339 | 359 | ||
| 340 | # Check out the QMK submodules | 360 | # Check out the QMK submodules |
| 341 | sub_ok = check_submodules() | 361 | sub_ok = check_submodules() |
| 342 | 362 | ||
| 343 | if sub_ok: | 363 | if sub_ok == CheckStatus.OK: |
| 344 | cli.log.info('Submodules are up to date.') | 364 | cli.log.info('Submodules are up to date.') |
| 345 | else: | 365 | else: |
| 346 | if yesno('Would you like to clone the submodules?', default=True): | 366 | if yesno('Would you like to clone the submodules?', default=True): |
| 347 | submodules.update() | 367 | submodules.update() |
| 348 | sub_ok = check_submodules() | 368 | sub_ok = check_submodules() |
| 349 | 369 | ||
| 350 | if not sub_ok: | 370 | if CheckStatus.ERROR in sub_ok: |
| 351 | ok = False | 371 | status = CheckStatus.ERROR |
| 372 | elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK: | ||
| 373 | status = CheckStatus.WARNING | ||
| 352 | 374 | ||
| 353 | # Report a summary of our findings to the user | 375 | # Report a summary of our findings to the user |
| 354 | if ok: | 376 | if status == CheckStatus.OK: |
| 355 | cli.log.info('{fg_green}QMK is ready to go') | 377 | cli.log.info('{fg_green}QMK is ready to go') |
| 378 | return 0 | ||
| 379 | elif status == CheckStatus.WARNING: | ||
| 380 | cli.log.info('{fg_yellow}QMK is ready to go, but minor problems were found') | ||
| 381 | return 1 | ||
| 356 | else: | 382 | else: |
| 357 | cli.log.info('{fg_yellow}Problems detected, please fix these problems before proceeding.') | 383 | cli.log.info('{fg_red}Major problems detected, please fix these problems before proceeding.') |
| 358 | # FIXME(skullydazed/unclaimed): Link to a document about troubleshooting, or discord or something | 384 | 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.') |
| 359 | 385 | return 2 | |
| 360 | return ok | ||
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index df5f047da..dd0c572a7 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py | |||
| @@ -13,14 +13,14 @@ def check_subcommand(command, *args): | |||
| 13 | return result | 13 | return result |
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | def check_returncode(result, expected=0): | 16 | def check_returncode(result, expected=[0]): |
| 17 | """Print stdout if `result.returncode` does not match `expected`. | 17 | """Print stdout if `result.returncode` does not match `expected`. |
| 18 | """ | 18 | """ |
| 19 | if result.returncode != expected: | 19 | if result.returncode not in expected: |
| 20 | print('`%s` stdout:' % ' '.join(result.args)) | 20 | print('`%s` stdout:' % ' '.join(result.args)) |
| 21 | print(result.stdout) | 21 | print(result.stdout) |
| 22 | print('returncode:', result.returncode) | 22 | print('returncode:', result.returncode) |
| 23 | assert result.returncode == expected | 23 | assert result.returncode in expected |
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | def test_cformat(): | 26 | def test_cformat(): |
| @@ -45,7 +45,7 @@ def test_flash(): | |||
| 45 | 45 | ||
| 46 | def test_flash_bootloaders(): | 46 | def test_flash_bootloaders(): |
| 47 | result = check_subcommand('flash', '-b') | 47 | result = check_subcommand('flash', '-b') |
| 48 | check_returncode(result, 1) | 48 | check_returncode(result, [1]) |
| 49 | 49 | ||
| 50 | 50 | ||
| 51 | def test_config(): | 51 | def test_config(): |
| @@ -62,7 +62,7 @@ def test_kle2json(): | |||
| 62 | 62 | ||
| 63 | def test_doctor(): | 63 | def test_doctor(): |
| 64 | result = check_subcommand('doctor', '-n') | 64 | result = check_subcommand('doctor', '-n') |
| 65 | check_returncode(result) | 65 | check_returncode(result, [0, 1]) |
| 66 | assert 'QMK Doctor is checking your environment.' in result.stdout | 66 | assert 'QMK Doctor is checking your environment.' in result.stdout |
| 67 | assert 'QMK is ready to go' in result.stdout | 67 | assert 'QMK is ready to go' in result.stdout |
| 68 | 68 | ||
| @@ -89,43 +89,43 @@ def test_list_keyboards(): | |||
| 89 | 89 | ||
| 90 | def test_list_keymaps(): | 90 | def test_list_keymaps(): |
| 91 | result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest') | 91 | result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest') |
| 92 | check_returncode(result, 0) | 92 | check_returncode(result) |
| 93 | assert 'default' and 'test' in result.stdout | 93 | assert 'default' and 'test' in result.stdout |
| 94 | 94 | ||
| 95 | 95 | ||
| 96 | def test_list_keymaps_long(): | 96 | def test_list_keymaps_long(): |
| 97 | result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest') | 97 | result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest') |
| 98 | check_returncode(result, 0) | 98 | check_returncode(result) |
| 99 | assert 'default' and 'test' in result.stdout | 99 | assert 'default' and 'test' in result.stdout |
| 100 | 100 | ||
| 101 | 101 | ||
| 102 | def test_list_keymaps_kb_only(): | 102 | def test_list_keymaps_kb_only(): |
| 103 | result = check_subcommand('list-keymaps', '-kb', 'niu_mini') | 103 | result = check_subcommand('list-keymaps', '-kb', 'niu_mini') |
| 104 | check_returncode(result, 0) | 104 | check_returncode(result) |
| 105 | assert 'default' and 'via' in result.stdout | 105 | assert 'default' and 'via' in result.stdout |
| 106 | 106 | ||
| 107 | 107 | ||
| 108 | def test_list_keymaps_vendor_kb(): | 108 | def test_list_keymaps_vendor_kb(): |
| 109 | result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar') | 109 | result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar') |
| 110 | check_returncode(result, 0) | 110 | check_returncode(result) |
| 111 | assert 'default' and 'via' in result.stdout | 111 | assert 'default' and 'via' in result.stdout |
| 112 | 112 | ||
| 113 | 113 | ||
| 114 | def test_list_keymaps_vendor_kb_rev(): | 114 | def test_list_keymaps_vendor_kb_rev(): |
| 115 | result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2') | 115 | result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2') |
| 116 | check_returncode(result, 0) | 116 | check_returncode(result) |
| 117 | assert 'default' and 'via' in result.stdout | 117 | assert 'default' and 'via' in result.stdout |
| 118 | 118 | ||
| 119 | 119 | ||
| 120 | def test_list_keymaps_no_keyboard_found(): | 120 | def test_list_keymaps_no_keyboard_found(): |
| 121 | result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl') | 121 | result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl') |
| 122 | check_returncode(result, 1) | 122 | check_returncode(result, [1]) |
| 123 | assert 'does not exist' in result.stdout | 123 | assert 'does not exist' in result.stdout |
| 124 | 124 | ||
| 125 | 125 | ||
| 126 | def test_json2c(): | 126 | def test_json2c(): |
| 127 | result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json') | 127 | result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json') |
| 128 | check_returncode(result, 0) | 128 | check_returncode(result) |
| 129 | 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' | 129 | 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' |
| 130 | 130 | ||
| 131 | 131 | ||
