doctor.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """QMK Python Doctor
  2. Check up for QMK environment.
  3. """
  4. import platform
  5. import shutil
  6. import subprocess
  7. import glob
  8. from milc import cli
  9. @cli.subcommand('Basic QMK environment checks')
  10. def doctor(cli):
  11. """Basic QMK environment checks.
  12. This is currently very simple, it just checks that all the expected binaries are on your system.
  13. TODO(unclaimed):
  14. * [ ] Compile a trivial program with each compiler
  15. * [ ] Check for udev entries on linux
  16. """
  17. cli.log.info('QMK Doctor is checking your environment.')
  18. # Make sure the basic CLI tools we need are available and can be executed.
  19. binaries = ['dfu-programmer', 'avrdude', 'dfu-util', 'avr-gcc', 'arm-none-eabi-gcc', 'bin/qmk']
  20. ok = True
  21. for binary in binaries:
  22. res = shutil.which(binary)
  23. if res is None:
  24. cli.log.error("{fg_red}QMK can't find %s in your path.", binary)
  25. ok = False
  26. else:
  27. check = subprocess.run([binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5)
  28. if check.returncode in [0, 1]:
  29. cli.log.info('Found {fg_cyan}%s', binary)
  30. else:
  31. cli.log.error("{fg_red}Can't run `%s --version`", binary)
  32. ok = False
  33. # Determine our OS and run platform specific tests
  34. OS = platform.system() # noqa (N806), uppercase name is ok in this instance
  35. if OS == "Darwin":
  36. cli.log.info("Detected {fg_cyan}macOS.")
  37. elif OS == "Linux":
  38. cli.log.info("Detected {fg_cyan}Linux.")
  39. # Checking for udev rules
  40. udev_dir = "/etc/udev/rules.d/"
  41. # These are the recommended udev rules
  42. desired_rules = {"dfu": {'SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff4", MODE:="0666"',
  43. 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ffb", MODE:="0666"',
  44. 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff0", MODE:="0666"'},
  45. "tmk": {'SUBSYSTEMS=="usb", ATTRS{idVendor}=="feed", MODE:="0666"'},
  46. "input-club": {'SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c11", MODE:="0666"'},
  47. "stm32": {'SUBSYSTEMS=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0003", MODE:="0666"',
  48. 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE:="0666"'},
  49. "catalina": {'ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1"',
  50. 'ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1"'}}
  51. if os.path.exists(udev_dir):
  52. udev_rules = [rule for rule in glob.iglob(os.path.join(udev_dir, "*.rules")) if os.path.isfile(rule)]
  53. # Collect all rules from the config files
  54. current_rules = set()
  55. for rule in udev_rules:
  56. with open(rule, "r") as fd:
  57. for line in fd.readlines():
  58. line = line.strip()
  59. if not line.startswith("#") and len(line):
  60. current_rules.add(line)
  61. # Check if the desired rules are among the currently present rules
  62. for bootloader, rules in desired_rules.items():
  63. if not rules.issubset(current_rules):
  64. # If the rules for catalina are not present, check if ModemManager is running
  65. if bootloader == "catalina":
  66. if shutil.which("systemctl"):
  67. mm_check = subprocess.run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
  68. if mm_check.returncode == 0:
  69. ok = False
  70. cli.log.warn("{bg_yellow}Detected ModemManager without udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro-Micro.")
  71. else:
  72. cli.log.warn("Can't find systemctl to check for ModemManager.")
  73. else:
  74. cli.log.warn("{bg_yellow}Missing udev rules for '%s' boards. You'll need to use `sudo` in order to flash them.", bootloader)
  75. else:
  76. cli.log.info("Assuming {fg_cyan}Windows.")
  77. # Report a summary of our findings to the user
  78. if ok:
  79. cli.log.info('{fg_green}QMK is ready to go')
  80. else:
  81. cli.log.info('{fg_yellow}Problems detected, please fix these problems before proceeding.')
  82. # FIXME(skullydazed): Link to a document about troubleshooting, or discord or something