diff options
| author | Nick Brassel <nick@tzarc.org> | 2025-11-28 00:36:49 +1100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-28 00:36:49 +1100 |
| commit | 9c2ca00074784dbee27b459d71cfc8e75f47b976 (patch) | |
| tree | a59576289fd024bf35b0573db70eb8862ed44568 /lib/python/qmk/cli/doctor/check.py | |
| parent | 594558ec7b9ac1963870447778426682065e0d20 (diff) | |
QMK CLI Environment bootstrapper (#25038)
Co-authored-by: Joel Challis <git@zvecr.com>
Co-authored-by: Pascal Getreuer <getreuer@google.com>
Diffstat (limited to 'lib/python/qmk/cli/doctor/check.py')
| -rw-r--r-- | lib/python/qmk/cli/doctor/check.py | 74 |
1 files changed, 60 insertions, 14 deletions
diff --git a/lib/python/qmk/cli/doctor/check.py b/lib/python/qmk/cli/doctor/check.py index 51b0f0c80a..8a13cb0832 100644 --- a/lib/python/qmk/cli/doctor/check.py +++ b/lib/python/qmk/cli/doctor/check.py | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | """Check for specific programs. | 1 | """Check for specific programs. |
| 2 | """ | 2 | """ |
| 3 | from enum import Enum | 3 | from enum import Enum |
| 4 | import re | ||
| 5 | import shutil | 4 | import shutil |
| 6 | from subprocess import DEVNULL, TimeoutExpired | 5 | from subprocess import DEVNULL, TimeoutExpired |
| 7 | from tempfile import TemporaryDirectory | 6 | from tempfile import TemporaryDirectory |
| @@ -9,6 +8,7 @@ from pathlib import Path | |||
| 9 | 8 | ||
| 10 | from milc import cli | 9 | from milc import cli |
| 11 | from qmk import submodules | 10 | from qmk import submodules |
| 11 | from qmk.commands import find_make | ||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | class CheckStatus(Enum): | 14 | class CheckStatus(Enum): |
| @@ -17,7 +17,13 @@ class CheckStatus(Enum): | |||
| 17 | ERROR = 3 | 17 | ERROR = 3 |
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | WHICH_MAKE = Path(find_make()).name | ||
| 21 | |||
| 20 | ESSENTIAL_BINARIES = { | 22 | ESSENTIAL_BINARIES = { |
| 23 | WHICH_MAKE: {}, | ||
| 24 | 'git': {}, | ||
| 25 | 'dos2unix': {}, | ||
| 26 | 'diff': {}, | ||
| 21 | 'dfu-programmer': {}, | 27 | 'dfu-programmer': {}, |
| 22 | 'avrdude': {}, | 28 | 'avrdude': {}, |
| 23 | 'dfu-util': {}, | 29 | 'dfu-util': {}, |
| @@ -30,14 +36,39 @@ ESSENTIAL_BINARIES = { | |||
| 30 | } | 36 | } |
| 31 | 37 | ||
| 32 | 38 | ||
| 33 | def _parse_gcc_version(version): | 39 | def _check_make_version(): |
| 34 | m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version) | 40 | last_line = ESSENTIAL_BINARIES[WHICH_MAKE]['output'].split('\n')[0] |
| 41 | version_number = last_line.split()[2] | ||
| 42 | cli.log.info('Found %s version %s', WHICH_MAKE, version_number) | ||
| 35 | 43 | ||
| 36 | return { | 44 | return CheckStatus.OK |
| 37 | 'major': int(m.group(1)), | 45 | |
| 38 | 'minor': int(m.group(2)) if m.group(2) else 0, | 46 | |
| 39 | 'patch': int(m.group(3)) if m.group(3) else 0, | 47 | def _check_git_version(): |
| 40 | } | 48 | last_line = ESSENTIAL_BINARIES['git']['output'].split('\n')[0] |
| 49 | version_number = last_line.split()[2] | ||
| 50 | cli.log.info('Found git version %s', version_number) | ||
| 51 | |||
| 52 | return CheckStatus.OK | ||
| 53 | |||
| 54 | |||
| 55 | def _check_dos2unix_version(): | ||
| 56 | last_line = ESSENTIAL_BINARIES['dos2unix']['output'].split('\n')[0] | ||
| 57 | version_number = last_line.split()[1] | ||
| 58 | cli.log.info('Found dos2unix version %s', version_number) | ||
| 59 | |||
| 60 | return CheckStatus.OK | ||
| 61 | |||
| 62 | |||
| 63 | def _check_diff_version(): | ||
| 64 | last_line = ESSENTIAL_BINARIES['diff']['output'].split('\n')[0] | ||
| 65 | if 'Apple diff' in last_line: | ||
| 66 | version_number = last_line | ||
| 67 | else: | ||
| 68 | version_number = last_line.split()[3] | ||
| 69 | cli.log.info('Found diff version %s', version_number) | ||
| 70 | |||
| 71 | return CheckStatus.OK | ||
| 41 | 72 | ||
| 42 | 73 | ||
| 43 | def _check_arm_gcc_version(): | 74 | def _check_arm_gcc_version(): |
| @@ -148,16 +179,24 @@ def check_binaries(): | |||
| 148 | """Iterates through ESSENTIAL_BINARIES and tests them. | 179 | """Iterates through ESSENTIAL_BINARIES and tests them. |
| 149 | """ | 180 | """ |
| 150 | ok = CheckStatus.OK | 181 | ok = CheckStatus.OK |
| 182 | missing_from_path = [] | ||
| 151 | 183 | ||
| 152 | for binary in sorted(ESSENTIAL_BINARIES): | 184 | for binary in sorted(ESSENTIAL_BINARIES): |
| 153 | try: | 185 | try: |
| 154 | if not is_executable(binary): | 186 | if not is_in_path(binary): |
| 187 | ok = CheckStatus.ERROR | ||
| 188 | missing_from_path.append(binary) | ||
| 189 | elif not is_executable(binary): | ||
| 155 | ok = CheckStatus.ERROR | 190 | ok = CheckStatus.ERROR |
| 156 | except TimeoutExpired: | 191 | except TimeoutExpired: |
| 157 | cli.log.debug('Timeout checking %s', binary) | 192 | cli.log.debug('Timeout checking %s', binary) |
| 158 | if ok != CheckStatus.ERROR: | 193 | if ok != CheckStatus.ERROR: |
| 159 | ok = CheckStatus.WARNING | 194 | ok = CheckStatus.WARNING |
| 160 | 195 | ||
| 196 | if missing_from_path: | ||
| 197 | location_noun = 'its location' if len(missing_from_path) == 1 else 'their locations' | ||
| 198 | cli.log.error('{fg_red}' + ', '.join(missing_from_path) + f' may need to be installed, or {location_noun} added to your path.') | ||
| 199 | |||
| 161 | return ok | 200 | return ok |
| 162 | 201 | ||
| 163 | 202 | ||
| @@ -165,6 +204,10 @@ def check_binary_versions(): | |||
| 165 | """Check the versions of ESSENTIAL_BINARIES | 204 | """Check the versions of ESSENTIAL_BINARIES |
| 166 | """ | 205 | """ |
| 167 | checks = { | 206 | checks = { |
| 207 | WHICH_MAKE: _check_make_version, | ||
| 208 | 'git': _check_git_version, | ||
| 209 | 'dos2unix': _check_dos2unix_version, | ||
| 210 | 'diff': _check_diff_version, | ||
| 168 | 'arm-none-eabi-gcc': _check_arm_gcc_version, | 211 | 'arm-none-eabi-gcc': _check_arm_gcc_version, |
| 169 | 'avr-gcc': _check_avr_gcc_version, | 212 | 'avr-gcc': _check_avr_gcc_version, |
| 170 | 'avrdude': _check_avrdude_version, | 213 | 'avrdude': _check_avrdude_version, |
| @@ -196,15 +239,18 @@ def check_submodules(): | |||
| 196 | return CheckStatus.OK | 239 | return CheckStatus.OK |
| 197 | 240 | ||
| 198 | 241 | ||
| 199 | def is_executable(command): | 242 | def is_in_path(command): |
| 200 | """Returns True if command exists and can be executed. | 243 | """Returns True if command is found in the path. |
| 201 | """ | 244 | """ |
| 202 | # Make sure the command is in the path. | 245 | if shutil.which(command) is None: |
| 203 | res = shutil.which(command) | ||
| 204 | if res is None: | ||
| 205 | cli.log.error("{fg_red}Can't find %s in your path.", command) | 246 | cli.log.error("{fg_red}Can't find %s in your path.", command) |
| 206 | return False | 247 | return False |
| 248 | return True | ||
| 249 | |||
| 207 | 250 | ||
| 251 | def is_executable(command): | ||
| 252 | """Returns True if command can be executed. | ||
| 253 | """ | ||
| 208 | # Make sure the command can be executed | 254 | # Make sure the command can be executed |
| 209 | version_arg = ESSENTIAL_BINARIES[command].get('version_arg', '--version') | 255 | version_arg = ESSENTIAL_BINARIES[command].get('version_arg', '--version') |
| 210 | check = cli.run([command, version_arg], combined_output=True, stdin=DEVNULL, timeout=5) | 256 | check = cli.run([command, version_arg], combined_output=True, stdin=DEVNULL, timeout=5) |
