Browse Source

Move the module checking and updating to lib/python (#12416)

* move the module checking and updating to lib/python

* make flake8 happy

* Update lib/python/qmk/cli/__init__.py

Co-authored-by: Erovia <Erovia@users.noreply.github.com>

* prompt the user to disable developer mode

* pyformat

* flake8

Co-authored-by: Erovia <Erovia@users.noreply.github.com>
Zach White 4 years ago
parent
commit
bc38c38f8c
2 changed files with 121 additions and 68 deletions
  1. 0 44
      bin/qmk
  2. 121 24
      lib/python/qmk/cli/__init__.py

+ 0 - 44
bin/qmk

@@ -3,7 +3,6 @@
 """
 """
 import os
 import os
 import sys
 import sys
-from importlib.util import find_spec
 from pathlib import Path
 from pathlib import Path
 
 
 # Add the QMK python libs to our path
 # Add the QMK python libs to our path
@@ -12,52 +11,9 @@ qmk_dir = script_dir.parent
 python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve()
 python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve()
 sys.path.append(str(python_lib_dir))
 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"
-
-            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
 # Setup the CLI
 import milc  # noqa
 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}'
 milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}'
 
 
 
 

+ 121 - 24
lib/python/qmk/cli/__init__.py

@@ -2,33 +2,79 @@
 
 
 We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
 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
 import sys
+from importlib.util import find_spec
+from pathlib import Path
+from subprocess import run
 
 
 from milc import cli, __VERSION__
 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 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"
+
+            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
 # Supported version information
 #
 #
 # Based on the OSes we support these are the minimum python version available by default.
 # Based on the OSes we support these are the minimum python version available by default.
@@ -54,9 +100,60 @@ if sys.version_info[0] != 3 or sys.version_info[1] < 7:
 milc_version = __VERSION__.split('.')
 milc_version = __VERSION__.split('.')
 
 
 if int(milc_version[0]) < 2 and int(milc_version[1]) < 3:
 if int(milc_version[0]) < 2 and int(milc_version[1]) < 3:
-    from pathlib import Path
-
     requirements = Path('requirements.txt').resolve()
     requirements = Path('requirements.txt').resolve()
 
 
     print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
     print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
     exit(127)
     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 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