diff --git a/bin/qmk b/bin/qmk index 4b5fd5bbc..a6e8ce08d 100755 --- a/bin/qmk +++ b/bin/qmk @@ -3,7 +3,6 @@ """ import os import sys -from importlib.util import find_spec from pathlib import Path # Add the QMK python libs to our path @@ -12,54 +11,9 @@ qmk_dir = script_dir.parent python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve() sys.path.append(str(python_lib_dir)) - -def _check_modules(requirements): - """ Check if the modules in the given requirements.txt are available. - """ - with Path(qmk_dir / requirements).open() as fd: - for line in fd.readlines(): - line = line.strip().replace('<', '=').replace('>', '=') - - if len(line) == 0 or line[0] == '#' or line.startswith('-r'): - continue - - if '#' in line: - line = line.split('#')[0] - - module = dict() - module['name'] = line.split('=')[0] if '=' in line else line - module['import'] = module['name'].replace('-', '_') - - # Not every module is importable by its own name. - if module['name'] == "pep8-naming": - module['import'] = "pep8ext_naming" - elif module['name'] == 'pyusb': - module['import'] = 'usb.core' - - if not find_spec(module['import']): - print('Could not find module %s!' % module['name']) - print('Please run `python3 -m pip install -r %s` to install required python dependencies.' % (qmk_dir / requirements,)) - if developer: - print('You can also turn off developer mode: qmk config user.developer=None') - print() - exit(255) - - -developer = False -# Make sure our modules have been setup -_check_modules('requirements.txt') - # Setup the CLI import milc # noqa -# For developers additional modules are needed -if milc.cli.config.user.developer: - # Do not run the check for 'config', - # so users can turn off developer mode - if len(sys.argv) == 1 or (len(sys.argv) > 1 and 'config' != sys.argv[1]): - developer = True - _check_modules('requirements-dev.txt') - milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index e7a5d5cd8..1fe065720 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -2,34 +2,81 @@ We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. """ +import os +import shlex import sys +from importlib.util import find_spec +from pathlib import Path +from subprocess import run from milc import cli, __VERSION__ +from milc.questions import yesno -from . import c2json -from . import cformat -from . import chibios -from . import clean -from . import compile -from . import config -from . import console -from . import docs -from . import doctor -from . import fileformat -from . import flash -from . import format -from . import generate -from . import hello -from . import info -from . import json2c -from . import lint -from . import list -from . import kle2json -from . import multibuild -from . import new -from . import pyformat -from . import pytest +def _run_cmd(*command): + """Run a command in a subshell. + """ + if 'windows' in cli.platform.lower(): + safecmd = map(shlex.quote, command) + safecmd = ' '.join(safecmd) + command = [os.environ['SHELL'], '-c', safecmd] + + return run(command) + + +def _find_broken_requirements(requirements): + """ Check if the modules in the given requirements.txt are available. + + Args: + + requirements + The path to a requirements.txt file + + Returns a list of modules that couldn't be imported + """ + with Path(requirements).open() as fd: + broken_modules = [] + + for line in fd.readlines(): + line = line.strip().replace('<', '=').replace('>', '=') + + if len(line) == 0 or line[0] == '#' or line.startswith('-r'): + continue + + if '#' in line: + line = line.split('#')[0] + + module_name = line.split('=')[0] if '=' in line else line + module_import = module_name.replace('-', '_') + + # Not every module is importable by its own name. + if module_name == "pep8-naming": + module_import = "pep8ext_naming" + elif module_name == 'pyusb': + module_import = 'usb.core' + + if not find_spec(module_import): + broken_modules.append(module_name) + + return broken_modules + + +def _broken_module_imports(requirements): + """Make sure we can import all the python modules. + """ + broken_modules = _find_broken_requirements(requirements) + + for module in broken_modules: + print('Could not find module %s!' % module) + + if broken_modules: + return True + + return False + + +# Make sure our python is new enough +# # Supported version information # # Based on the OSes we support these are the minimum python version available by default. @@ -55,9 +102,61 @@ if sys.version_info[0] != 3 or sys.version_info[1] < 7: milc_version = __VERSION__.split('.') if int(milc_version[0]) < 2 and int(milc_version[1]) < 3: - from pathlib import Path - requirements = Path('requirements.txt').resolve() print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') exit(127) + +# Check to make sure we have all our dependencies +msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.' + +if _broken_module_imports('requirements.txt'): + if yesno('Would you like to install the required Python modules?'): + _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt') + else: + print() + print(msg_install % (str(Path('requirements.txt').resolve()),)) + print() + exit(1) + +if cli.config.user.developer: + args = sys.argv[1:] + while args and args[0][0] == '-': + del args[0] + if not args or args[0] != 'config': + if _broken_module_imports('requirements-dev.txt'): + if yesno('Would you like to install the required developer Python modules?'): + _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements-dev.txt') + elif yesno('Would you like to disable developer mode?'): + _run_cmd(sys.argv[0], 'config', 'user.developer=None') + else: + print() + print(msg_install % (str(Path('requirements-dev.txt').resolve()),)) + print('You can also turn off developer mode: qmk config user.developer=None') + print() + exit(1) + +# Import our subcommands +from . import c2json # noqa +from . import cformat # noqa +from . import chibios # noqa +from . import clean # noqa +from . import compile # noqa +from . import config # noqa +from . import console # noqa +from . import docs # noqa +from . import doctor # noqa +from . import fileformat # noqa +from . import flash # noqa +from . import format # noqa +from . import generate # noqa +from . import hello # noqa +from . import info # noqa +from . import json2c # noqa +from . import lint # noqa +from . import list # noqa +from . import kle2json # noqa +from . import multibuild # noqa +from . import new # noqa +from . import pyformat # noqa +from . import pytest # noqa