2020-01-24 11:31:16 -08:00
""" QMK Doctor
2019-07-15 12:14:27 -07:00
2020-01-24 11:31:16 -08:00
Check out the user ' s QMK environment and make sure it ' s ready to compile .
2019-07-15 12:14:27 -07:00
"""
2019-08-21 23:40:24 -07:00
import platform
2019-07-15 12:14:27 -07:00
from milc import cli
2020-11-28 12:02:18 -08:00
from milc . questions import yesno
2021-06-22 11:50:53 -07:00
2020-01-24 11:31:16 -08:00
from qmk import submodules
2023-11-28 07:53:43 +11:00
from qmk . constants import QMK_FIRMWARE , QMK_FIRMWARE_UPSTREAM , QMK_USERSPACE , HAS_QMK_USERSPACE
2021-07-10 16:04:50 +01:00
from . check import CheckStatus , check_binaries , check_binary_versions , check_submodules
2022-07-27 02:37:28 +10:00
from qmk . git import git_check_repo , git_get_branch , git_get_tag , git_get_last_log_entry , git_get_common_ancestor , git_is_dirty , git_get_remotes , git_check_deviation
2022-03-18 16:02:24 +00:00
from qmk . commands import in_virtualenv
2023-11-28 07:53:43 +11:00
from qmk . userspace import qmk_userspace_paths , qmk_userspace_validate , UserspaceValidationError
2020-01-24 11:31:16 -08:00
2026-01-19 17:04:25 +07:00
def distrib_tests ( ) :
def _load_kvp_file ( file ) :
""" Load a simple key=value file into a dictionary
"""
vars = { }
with open ( file , ' r ' ) as f :
for line in f :
if ' = ' in line :
key , value = line . split ( ' = ' , 1 )
vars [ key . strip ( ) ] = value . strip ( )
return vars
def _parse_toolchain_release_file ( file ) :
""" Parse the QMK toolchain release info file
"""
try :
vars = _load_kvp_file ( file )
return f ' { vars . get ( " TOOLCHAIN_HOST " , " unknown " ) } : { vars . get ( " TOOLCHAIN_TARGET " , " unknown " ) } : { vars . get ( " COMMIT_HASH " , " unknown " ) } '
except Exception as e :
cli . log . warning ( ' Error reading QMK toolchain release info file: %s ' , e )
return f ' Unknown toolchain release info file: { file } '
def _parse_flashutils_release_file ( file ) :
""" Parse the QMK flashutils release info file
"""
try :
vars = _load_kvp_file ( file )
return f ' { vars . get ( " FLASHUTILS_HOST " , " unknown " ) } : { vars . get ( " COMMIT_HASH " , " unknown " ) } '
except Exception as e :
cli . log . warning ( ' Error reading QMK flashutils release info file: %s ' , e )
return f ' Unknown flashutils release info file: { file } '
try :
from qmk . cli import QMK_DISTRIB_DIR
if ( QMK_DISTRIB_DIR / ' etc ' ) . exists ( ) :
cli . log . info ( ' Found QMK tools distribution directory: {fg_cyan} %s ' , QMK_DISTRIB_DIR )
toolchains = [ _parse_toolchain_release_file ( file ) for file in ( QMK_DISTRIB_DIR / ' etc ' ) . glob ( ' toolchain_release_* ' ) ]
if len ( toolchains ) > 0 :
cli . log . info ( ' Found QMK toolchains: {fg_cyan} %s ' , ' , ' . join ( toolchains ) )
else :
cli . log . warning ( ' No QMK toolchains manifest found. ' )
flashutils = [ _parse_flashutils_release_file ( file ) for file in ( QMK_DISTRIB_DIR / ' etc ' ) . glob ( ' flashutils_release_* ' ) ]
if len ( flashutils ) > 0 :
cli . log . info ( ' Found QMK flashutils: {fg_cyan} %s ' , ' , ' . join ( flashutils ) )
else :
cli . log . warning ( ' No QMK flashutils manifest found. ' )
except ImportError :
cli . log . info ( ' QMK tools distribution not found. ' )
return CheckStatus . OK
2020-12-18 16:42:30 -08:00
def os_tests ( ) :
""" Determine our OS and run platform specific tests
"""
platform_id = platform . platform ( ) . lower ( )
if ' darwin ' in platform_id or ' macos ' in platform_id :
2021-06-22 11:50:53 -07:00
from . macos import os_test_macos
2020-12-18 16:42:30 -08:00
return os_test_macos ( )
elif ' linux ' in platform_id :
2021-06-22 11:50:53 -07:00
from . linux import os_test_linux
2020-12-18 16:42:30 -08:00
return os_test_linux ( )
elif ' windows ' in platform_id :
2021-06-22 11:50:53 -07:00
from . windows import os_test_windows
2020-12-18 16:42:30 -08:00
return os_test_windows ( )
else :
cli . log . warning ( ' Unsupported OS detected: %s ' , platform_id )
return CheckStatus . WARNING
2021-07-10 16:04:50 +01:00
def git_tests ( ) :
""" Run Git-related checks
"""
status = CheckStatus . OK
# Make sure our QMK home is a Git repo
git_ok = git_check_repo ( )
if not git_ok :
cli . log . warning ( " {fg_yellow} QMK home does not appear to be a Git repository! (no .git folder) " )
status = CheckStatus . WARNING
else :
git_branch = git_get_branch ( )
if git_branch :
cli . log . info ( ' Git branch: %s ' , git_branch )
2022-02-02 15:31:42 +11:00
repo_version = git_get_tag ( )
if repo_version :
cli . log . info ( ' Repo version: %s ' , repo_version )
2021-07-10 16:04:50 +01:00
git_dirty = git_is_dirty ( )
if git_dirty :
cli . log . warning ( ' {fg_yellow} Git has unstashed/uncommitted changes. ' )
status = CheckStatus . WARNING
git_remotes = git_get_remotes ( )
if ' upstream ' not in git_remotes . keys ( ) or QMK_FIRMWARE_UPSTREAM not in git_remotes [ ' upstream ' ] . get ( ' url ' , ' ' ) :
cli . log . warning ( ' {fg_yellow} The official repository does not seem to be configured as git remote " upstream " . ' )
status = CheckStatus . WARNING
else :
git_deviation = git_check_deviation ( git_branch )
if git_branch in [ ' master ' , ' develop ' ] and git_deviation :
cli . log . warning ( ' {fg_yellow} The local " %s " branch contains commits not found in the upstream branch. ' , git_branch )
status = CheckStatus . WARNING
2022-07-27 02:37:28 +10:00
for branch in [ git_branch , ' upstream/master ' , ' upstream/develop ' ] :
cli . log . info ( ' - Latest %s : %s ' , branch , git_get_last_log_entry ( branch ) )
for branch in [ ' upstream/master ' , ' upstream/develop ' ] :
cli . log . info ( ' - Common ancestor with %s : %s ' , branch , git_get_common_ancestor ( branch , ' HEAD ' ) )
2021-07-10 16:04:50 +01:00
return status
2022-07-27 02:37:28 +10:00
def output_submodule_status ( ) :
""" Prints out information related to the submodule status.
"""
cli . log . info ( ' Submodule status: ' )
sub_status = submodules . status ( )
for s in sub_status . keys ( ) :
sub_info = sub_status [ s ]
if ' name ' in sub_info :
sub_name = sub_info [ ' name ' ]
sub_shorthash = sub_info [ ' shorthash ' ] if ' shorthash ' in sub_info else ' '
sub_describe = sub_info [ ' describe ' ] if ' describe ' in sub_info else ' '
sub_last_log_timestamp = sub_info [ ' last_log_timestamp ' ] if ' last_log_timestamp ' in sub_info else ' '
if sub_last_log_timestamp != ' ' :
cli . log . info ( f ' - { sub_name } : { sub_last_log_timestamp } -- { sub_describe } ( { sub_shorthash } ) ' )
else :
cli . log . error ( f ' - { sub_name } : <<< missing or unknown >>> ' )
2023-11-28 07:53:43 +11:00
def userspace_tests ( qmk_firmware ) :
if qmk_firmware :
cli . log . info ( f ' QMK home: {{ fg_cyan }} { qmk_firmware } ' )
for path in qmk_userspace_paths ( ) :
try :
qmk_userspace_validate ( path )
cli . log . info ( f ' Testing userspace candidate: {{ fg_cyan }} { path } {{ fg_reset }} -- {{ fg_green }} Valid `qmk.json` ' )
except FileNotFoundError :
2024-12-15 04:00:18 +00:00
cli . log . warning ( f ' Testing userspace candidate: {{ fg_cyan }} { path } {{ fg_reset }} -- {{ fg_red }} Missing `qmk.json` ' )
2023-11-28 07:53:43 +11:00
except UserspaceValidationError as err :
2024-12-15 04:00:18 +00:00
cli . log . warning ( f ' Testing userspace candidate: {{ fg_cyan }} { path } {{ fg_reset }} -- {{ fg_red }} Invalid `qmk.json` ' )
cli . log . warning ( f ' -- {{ fg_cyan }} { path } /qmk.json {{ fg_reset }} validation error: { err } ' )
2023-11-28 07:53:43 +11:00
if QMK_USERSPACE is not None :
cli . log . info ( f ' QMK userspace: {{ fg_cyan }} { QMK_USERSPACE } ' )
cli . log . info ( f ' Userspace enabled: {{ fg_cyan }} { HAS_QMK_USERSPACE } ' )
2020-01-24 11:31:16 -08:00
@cli.argument ( ' -y ' , ' --yes ' , action = ' store_true ' , arg_only = True , help = ' Answer yes to all questions. ' )
@cli.argument ( ' -n ' , ' --no ' , action = ' store_true ' , arg_only = True , help = ' Answer no to all questions. ' )
2019-09-22 13:25:33 -07:00
@cli.subcommand ( ' Basic QMK environment checks ' )
def doctor ( cli ) :
2019-07-15 12:14:27 -07:00
""" Basic QMK environment checks.
This is currently very simple , it just checks that all the expected binaries are on your system .
TODO ( unclaimed ) :
* [ ] Compile a trivial program with each compiler
"""
2019-08-22 09:38:10 -07:00
cli . log . info ( ' QMK Doctor is checking your environment. ' )
2026-01-19 17:04:25 +07:00
cli . log . info ( ' Python version: %s ' , platform . python_version ( ) )
2021-06-27 13:21:53 +10:00
cli . log . info ( ' CLI version: %s ' , cli . version )
2021-04-02 21:44:27 +11:00
cli . log . info ( ' QMK home: {fg_cyan} %s ' , QMK_FIRMWARE )
2019-08-22 09:38:10 -07:00
2021-09-27 10:02:54 -07:00
status = os_status = os_tests ( )
2026-01-19 17:04:25 +07:00
distrib_tests ( )
2023-11-28 07:53:43 +11:00
userspace_tests ( None )
2021-09-27 10:02:54 -07:00
git_status = git_tests ( )
2019-08-22 09:38:10 -07:00
2021-09-27 10:02:54 -07:00
if git_status == CheckStatus . ERROR or ( os_status == CheckStatus . OK and git_status == CheckStatus . WARNING ) :
status = git_status
2020-12-21 01:46:01 +11:00
2021-09-27 10:02:54 -07:00
if in_virtualenv ( ) :
2021-07-10 16:04:50 +01:00
cli . log . info ( ' CLI installed in virtualenv. ' )
2020-12-21 01:46:01 +11:00
2020-01-24 11:31:16 -08:00
# Make sure the basic CLI tools we need are available and can be executed.
bin_ok = check_binaries ( )
2023-01-09 09:27:41 +00:00
if bin_ok == CheckStatus . OK :
2020-01-24 11:31:16 -08:00
cli . log . info ( ' All dependencies are installed. ' )
2023-01-09 09:27:41 +00:00
elif bin_ok == CheckStatus . WARNING :
cli . log . warning ( ' Issues encountered while checking dependencies. ' )
2019-07-15 12:14:27 -07:00
else :
2020-11-16 21:09:32 +00:00
status = CheckStatus . ERROR
2020-01-24 11:31:16 -08:00
2020-03-08 09:21:45 -07:00
# Make sure the tools are at the correct version
2020-12-21 13:29:36 +01:00
ver_ok = check_binary_versions ( )
2020-11-16 21:09:32 +00:00
if CheckStatus . ERROR in ver_ok :
status = CheckStatus . ERROR
elif CheckStatus . WARNING in ver_ok and status == CheckStatus . OK :
status = CheckStatus . WARNING
2020-03-08 09:21:45 -07:00
2020-01-24 11:31:16 -08:00
# Check out the QMK submodules
sub_ok = check_submodules ( )
2020-11-16 21:09:32 +00:00
if sub_ok == CheckStatus . OK :
2020-01-24 11:31:16 -08:00
cli . log . info ( ' Submodules are up to date. ' )
else :
2023-01-02 22:00:29 +00:00
if git_check_repo ( ) and yesno ( ' Would you like to clone the submodules? ' , default = True ) :
2020-01-24 11:31:16 -08:00
submodules . update ( )
sub_ok = check_submodules ( )
2021-02-16 18:45:05 +01:00
if sub_ok == CheckStatus . ERROR :
2020-11-16 21:09:32 +00:00
status = CheckStatus . ERROR
2021-02-16 18:45:05 +01:00
elif sub_ok == CheckStatus . WARNING and status == CheckStatus . OK :
2020-11-16 21:09:32 +00:00
status = CheckStatus . WARNING
2019-07-15 12:14:27 -07:00
2022-07-27 02:37:28 +10:00
output_submodule_status ( )
2019-08-22 09:38:10 -07:00
# Report a summary of our findings to the user
2020-11-16 21:09:32 +00:00
if status == CheckStatus . OK :
2019-07-15 12:14:27 -07:00
cli . log . info ( ' {fg_green} QMK is ready to go ' )
2020-11-16 21:09:32 +00:00
return 0
elif status == CheckStatus . WARNING :
cli . log . info ( ' {fg_yellow} QMK is ready to go, but minor problems were found ' )
return 1
2019-08-21 23:40:24 -07:00
else :
2026-01-19 17:04:25 +07:00
cli . log . info ( ' {fg_red} Major problems detected, please fix these problems before proceeding. {fg_reset} ' )
cli . log . info ( ' {fg_blue} If you \' re missing dependencies, try following the instructions on: https://docs.qmk.fm/newbs_getting_started {fg_reset} ' )
cli . log . info ( ' {fg_blue} Additionally, check out the FAQ (https://docs.qmk.fm/#/faq_build) or join the QMK Discord (https://discord.gg/qmk) for help. {fg_reset} ' )
2020-11-16 21:09:32 +00:00
return 2