diff options
-rwxr-xr-x | bin/qmk | 44 | ||||
-rw-r--r-- | lib/python/qmk/cli/__init__.py | 145 |
2 files changed, 121 insertions, 68 deletions
@@ -3,7 +3,6 @@ | |||
3 | """ | 3 | """ |
4 | import os | 4 | import os |
5 | import sys | 5 | import sys |
6 | from importlib.util import find_spec | ||
7 | from pathlib import Path | 6 | from pathlib import Path |
8 | 7 | ||
9 | # Add the QMK python libs to our path | 8 | # Add the QMK python libs to our path |
@@ -12,52 +11,9 @@ qmk_dir = script_dir.parent | |||
12 | python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve() | 11 | python_lib_dir = Path(qmk_dir / 'lib' / 'python').resolve() |
13 | sys.path.append(str(python_lib_dir)) | 12 | sys.path.append(str(python_lib_dir)) |
14 | 13 | ||
15 | |||
16 | def _check_modules(requirements): | ||
17 | """ Check if the modules in the given requirements.txt are available. | ||
18 | """ | ||
19 | with Path(qmk_dir / requirements).open() as fd: | ||
20 | for line in fd.readlines(): | ||
21 | line = line.strip().replace('<', '=').replace('>', '=') | ||
22 | |||
23 | if len(line) == 0 or line[0] == '#' or line.startswith('-r'): | ||
24 | continue | ||
25 | |||
26 | if '#' in line: | ||
27 | line = line.split('#')[0] | ||
28 | |||
29 | module = dict() | ||
30 | module['name'] = line.split('=')[0] if '=' in line else line | ||
31 | module['import'] = module['name'].replace('-', '_') | ||
32 | |||
33 | # Not every module is importable by its own name. | ||
34 | if module['name'] == "pep8-naming": | ||
35 | module['import'] = "pep8ext_naming" | ||
36 | |||
37 | if not find_spec(module['import']): | ||
38 | print('Could not find module %s!' % module['name']) | ||
39 | print('Please run `python3 -m pip install -r %s` to install required python dependencies.' % (qmk_dir / requirements,)) | ||
40 | if developer: | ||
41 | print('You can also turn off developer mode: qmk config user.developer=None') | ||
42 | print() | ||
43 | exit(255) | ||
44 | |||
45 | |||
46 | developer = False | ||
47 | # Make sure our modules have been setup | ||
48 | _check_modules('requirements.txt') | ||
49 | |||
50 | # Setup the CLI | 14 | # Setup the CLI |
51 | import milc # noqa | 15 | import milc # noqa |
52 | 16 | ||
53 | # For developers additional modules are needed | ||
54 | if milc.cli.config.user.developer: | ||
55 | # Do not run the check for 'config', | ||
56 | # so users can turn off developer mode | ||
57 | if len(sys.argv) == 1 or (len(sys.argv) > 1 and 'config' != sys.argv[1]): | ||
58 | developer = True | ||
59 | _check_modules('requirements-dev.txt') | ||
60 | |||
61 | milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' | 17 | milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' |
62 | 18 | ||
63 | 19 | ||
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 008e57f76..6fe769fe7 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py | |||
@@ -2,33 +2,79 @@ | |||
2 | 2 | ||
3 | We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. | 3 | We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. |
4 | """ | 4 | """ |
5 | import os | ||
6 | import shlex | ||
5 | import sys | 7 | import sys |
8 | from importlib.util import find_spec | ||
9 | from pathlib import Path | ||
10 | from subprocess import run | ||
6 | 11 | ||
7 | from milc import cli, __VERSION__ | 12 | from milc import cli, __VERSION__ |
13 | from milc.questions import yesno | ||
8 | 14 | ||
9 | from . import c2json | ||
10 | from . import cformat | ||
11 | from . import chibios | ||
12 | from . import clean | ||
13 | from . import compile | ||
14 | from . import config | ||
15 | from . import docs | ||
16 | from . import doctor | ||
17 | from . import fileformat | ||
18 | from . import flash | ||
19 | from . import format | ||
20 | from . import generate | ||
21 | from . import hello | ||
22 | from . import info | ||
23 | from . import json2c | ||
24 | from . import lint | ||
25 | from . import list | ||
26 | from . import kle2json | ||
27 | from . import multibuild | ||
28 | from . import new | ||
29 | from . import pyformat | ||
30 | from . import pytest | ||
31 | 15 | ||
16 | def _run_cmd(*command): | ||
17 | """Run a command in a subshell. | ||
18 | """ | ||
19 | if 'windows' in cli.platform.lower(): | ||
20 | safecmd = map(shlex.quote, command) | ||
21 | safecmd = ' '.join(safecmd) | ||
22 | command = [os.environ['SHELL'], '-c', safecmd] | ||
23 | |||
24 | return run(command) | ||
25 | |||
26 | |||
27 | def _find_broken_requirements(requirements): | ||
28 | """ Check if the modules in the given requirements.txt are available. | ||
29 | |||
30 | Args: | ||
31 | |||
32 | requirements | ||
33 | The path to a requirements.txt file | ||
34 | |||
35 | Returns a list of modules that couldn't be imported | ||
36 | """ | ||
37 | with Path(requirements).open() as fd: | ||
38 | broken_modules = [] | ||
39 | |||
40 | for line in fd.readlines(): | ||
41 | line = line.strip().replace('<', '=').replace('>', '=') | ||
42 | |||
43 | if len(line) == 0 or line[0] == '#' or line.startswith('-r'): | ||
44 | continue | ||
45 | |||
46 | if '#' in line: | ||
47 | line = line.split('#')[0] | ||
48 | |||
49 | module_name = line.split('=')[0] if '=' in line else line | ||
50 | module_import = module_name.replace('-', '_') | ||
51 | |||
52 | # Not every module is importable by its own name. | ||
53 | if module_name == "pep8-naming": | ||
54 | module_import = "pep8ext_naming" | ||
55 | |||
56 | if not find_spec(module_import): | ||
57 | broken_modules.append(module_name) | ||
58 | |||
59 | return broken_modules | ||
60 | |||
61 | |||
62 | def _broken_module_imports(requirements): | ||
63 | """Make sure we can import all the python modules. | ||
64 | """ | ||
65 | broken_modules = _find_broken_requirements(requirements) | ||
66 | |||
67 | for module in broken_modules: | ||
68 | print('Could not find module %s!' % module) | ||
69 | |||
70 | if broken_modules: | ||
71 | return True | ||
72 | |||
73 | return False | ||
74 | |||
75 | |||
76 | # Make sure our python is new enough | ||
77 | # | ||
32 | # Supported version information | 78 | # Supported version information |
33 | # | 79 | # |
34 | # Based on the OSes we support these are the minimum python version available by default. | 80 | # 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: | |||
54 | milc_version = __VERSION__.split('.') | 100 | milc_version = __VERSION__.split('.') |
55 | 101 | ||
56 | if int(milc_version[0]) < 2 and int(milc_version[1]) < 3: | 102 | if int(milc_version[0]) < 2 and int(milc_version[1]) < 3: |
57 | from pathlib import Path | ||
58 | |||
59 | requirements = Path('requirements.txt').resolve() | 103 | requirements = Path('requirements.txt').resolve() |
60 | 104 | ||
61 | print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') | 105 | print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') |
62 | exit(127) | 106 | exit(127) |
107 | |||
108 | # Check to make sure we have all our dependencies | ||
109 | msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.' | ||
110 | |||
111 | if _broken_module_imports('requirements.txt'): | ||
112 | if yesno('Would you like to install the required Python modules?'): | ||
113 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt') | ||
114 | else: | ||
115 | print() | ||
116 | print(msg_install % (str(Path('requirements.txt').resolve()),)) | ||
117 | print() | ||
118 | exit(1) | ||
119 | |||
120 | if cli.config.user.developer: | ||
121 | args = sys.argv[1:] | ||
122 | while args and args[0][0] == '-': | ||
123 | del args[0] | ||
124 | if not args or args[0] != 'config': | ||
125 | if _broken_module_imports('requirements-dev.txt'): | ||
126 | if yesno('Would you like to install the required developer Python modules?'): | ||
127 | _run_cmd(sys.executable, '-m', 'pip', 'install', '-r', 'requirements-dev.txt') | ||
128 | elif yesno('Would you like to disable developer mode?'): | ||
129 | _run_cmd(sys.argv[0], 'config', 'user.developer=None') | ||
130 | else: | ||
131 | print() | ||
132 | print(msg_install % (str(Path('requirements-dev.txt').resolve()),)) | ||
133 | print('You can also turn off developer mode: qmk config user.developer=None') | ||
134 | print() | ||
135 | exit(1) | ||
136 | |||
137 | # Import our subcommands | ||
138 | from . import c2json # noqa | ||
139 | from . import cformat # noqa | ||
140 | from . import chibios # noqa | ||
141 | from . import clean # noqa | ||
142 | from . import compile # noqa | ||
143 | from . import config # noqa | ||
144 | from . import docs # noqa | ||
145 | from . import doctor # noqa | ||
146 | from . import fileformat # noqa | ||
147 | from . import flash # noqa | ||
148 | from . import format # noqa | ||
149 | from . import generate # noqa | ||
150 | from . import hello # noqa | ||
151 | from . import info # noqa | ||
152 | from . import json2c # noqa | ||
153 | from . import lint # noqa | ||
154 | from . import list # noqa | ||
155 | from . import kle2json # noqa | ||
156 | from . import multibuild # noqa | ||
157 | from . import new # noqa | ||
158 | from . import pyformat # noqa | ||
159 | from . import pytest # noqa | ||