@ -7,6 +7,7 @@ import re
import shutil
import subprocess
from pathlib import Path
from enum import Enum
from milc import cli
from qmk import submodules
@ -14,6 +15,13 @@ from qmk.constants import QMK_FIRMWARE
from qmk.questions import yesno
from qmk.commands import run
class CheckStatus ( Enum ) :
OK = 1
WARNING = 2
ERROR = 3
ESSENTIAL_BINARIES = {
' dfu-programmer ' : { } ,
' avrdude ' : { } ,
@ -33,9 +41,12 @@ def _udev_rule(vid, pid=None, *args):
"""
rule = " "
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 :
rule = ' SUBSYSTEMS== " usb " , ATTRS{idVendor}== " %s " , TAG+= " uaccess " , RUN{builtin}+= " uaccess " ' % vid
rule = ' SUBSYSTEMS== " usb " , ATTRS{idVendor}== " %s " , TAG+= " uaccess " ' % vid
if args :
rule = ' , ' . join ( [ rule , * args ] )
return rule
@ -69,24 +80,25 @@ def check_arm_gcc_version():
version_number = ESSENTIAL_BINARIES [ ' arm-none-eabi-gcc ' ] [ ' output ' ] . strip ( )
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 ( ) :
""" Returns True if the avr-gcc version is not known to cause problems.
"""
rc = CheckStatus . ERROR
if ' output ' in ESSENTIAL_BINARIES [ ' avr-gcc ' ] :
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 )
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 ( ) :
@ -95,7 +107,7 @@ def check_avrdude_version():
version_number = last_line . split ( ) [ 2 ] [ : - 1 ]
cli . log . info ( ' Found avrdude version %s ' , version_number )
return True
return CheckStatus . OK
def check_dfu_util_version ( ) :
@ -104,7 +116,7 @@ def check_dfu_util_version():
version_number = first_line . split ( ) [ 1 ]
cli . log . info ( ' Found dfu-util version %s ' , version_number )
return True
return CheckStatus . OK
def check_dfu_programmer_version ( ) :
@ -113,7 +125,7 @@ def check_dfu_programmer_version():
version_number = first_line . split ( ) [ 1 ]
cli . log . info ( ' Found dfu-programmer version %s ' , version_number )
return True
return CheckStatus . OK
def check_binaries ( ) :
@ -131,58 +143,56 @@ def check_binaries():
def check_submodules ( ) :
""" Iterates through all submodules to make sure they ' re cloned and up to date.
"""
ok = True
for submodule in submodules . status ( ) . values ( ) :
if submodule [ ' status ' ] is None :
cli . log . error ( ' Submodule %s has not yet been cloned! ' , submodule [ ' name ' ] )
ok = False
return CheckStatus . ERROR
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 ( ) :
""" Make sure the udev rules look good.
"""
ok = True
rc = CheckStatus . OK
udev_dir = Path ( " /etc/udev/rules.d/ " )
desired_rules = {
' atmel-dfu ' : {
_udev_rule ( " 03EB " , " 2FEF " ) , # ATmega16U2
_udev_rule ( " 03EB " , " 2FF 0 " ) , # ATmega32U2
_udev_rule ( " 03EB " , " 2FF 3 " ) , # ATmega16U4
_udev_rule ( " 03EB " , " 2FF 4 " ) , # ATmega32U4
_udev_rule ( " 03EB " , " 2FF 9 " ) , # AT90USB64
_udev_rule ( " 03EB " , " 2FFB " ) # AT90USB128
_udev_rule ( " 03eb " , " 2fef " ) , # ATmega16U2
_udev_rule ( " 03eb " , " 2ff 0 " ) , # ATmega32U2
_udev_rule ( " 03eb " , " 2ff 3 " ) , # ATmega16U4
_udev_rule ( " 03eb " , " 2ff 4 " ) , # ATmega32U4
_udev_rule ( " 03eb " , " 2ff 9 " ) , # AT90USB64
_udev_rule ( " 03eb " , " 2ffb " ) # AT90USB128
} ,
' kiibohd ' : { _udev_rule ( " 1C 11 " , " B 007" ) } ,
' kiibohd ' : { _udev_rule ( " 1c 11 " , " b 007" ) } ,
' stm32 ' : {
_udev_rule ( " 1EAF " , " 0003 " ) , # STM32duino
_udev_rule ( " 0483 " , " DF 11" ) # STM32 DFU
_udev_rule ( " 1eaf " , " 0003 " ) , # STM32duino
_udev_rule ( " 0483 " , " df 11" ) # STM32 DFU
} ,
' bootloadhid ' : { _udev_rule ( " 16C 0 " , " 05DF " ) } ,
' usbasploader ' : { _udev_rule ( " 16C 0 " , " 05DC " ) } ,
' massdrop ' : { _udev_rule ( " 03EB " , " 6124 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) } ,
' bootloadhid ' : { _udev_rule ( " 16c 0 " , " 05df " ) } ,
' usbasploader ' : { _udev_rule ( " 16c 0 " , " 05dc " ) } ,
' massdrop ' : { _udev_rule ( " 03eb " , " 6124 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) } ,
' caterina ' : {
# 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
_udev_rule ( " 2341 " , " 0036 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) , # Leonardo
_udev_rule ( " 2341 " , " 0037 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) , # Micro
# Adafruit Industries LL C
_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 ( " 2A 03 " , " 0036 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) , # Leonardo
_udev_rule ( " 2A 03 " , " 0037 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) # Micro
# Adafruit INDUSTRIES ll C
_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 ( " 2a 03 " , " 0036 " , ' ENV{ID_MM_DEVICE_IGNORE}= " 1 " ' ) , # Leonardo
_udev_rule ( " 2a 03 " , " 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
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 ) :
deprecated_rule = deprecated_rules . get ( bootloader )
if deprecated_rule and deprecated_rule . issubset ( current_rules ) :
cli . log . warn ( " {b g_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 :
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 ( ) :
""" Returns True if ModemManager is running.
"""
if shutil . which ( " systemctl " ) :
if check_systemd ( ) :
mm_check = run ( [ " systemctl " , " --quiet " , " is-active " , " ModemManager.service " ] , timeout = 10 )
if mm_check . returncode == 0 :
return True
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 ) :
@ -263,12 +285,8 @@ def os_test_linux():
""" Run the Linux specific tests.
"""
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 ( ) :
@ -276,7 +294,7 @@ def os_test_macos():
"""
cli . log . info ( " Detected {fg_cyan}macOS. " )
return True
return CheckStatus . OK
def os_test_windows ( ) :
@ -284,7 +302,7 @@ def os_test_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. ' )
@ -299,23 +317,20 @@ def doctor(cli):
* [ ] Compile a trivial program with each compiler
"""
cli . log . info ( ' QMK Doctor is checking your environment. ' )
ok = True
status = CheckStatus . OK
# Determine our OS and run platform specific tests
platform_id = platform . platform ( ) . lower ( )
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 :
if not os_test_linux ( ) :
ok = False
status = os_test_linux ( )
elif ' windows ' in platform_id :
if not os_test_windows ( ) :
ok = False
status = os_test_windows ( )
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 )
@ -330,31 +345,41 @@ def doctor(cli):
if bin_ok :
cli . log . info ( ' All dependencies are installed. ' )
else :
ok = False
status = CheckStatus . ERROR
# 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 ) :
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
sub_ok = check_submodules ( )
if sub_ok :
if sub_ok == CheckStatus . OK :
cli . log . info ( ' Submodules are up to date. ' )
else :
if yesno ( ' Would you like to clone the submodules? ' , default = True ) :
submodules . update ( )
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
if ok :
if status == Chec kStatus . OK :
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 :
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