Merge commit 'd9e077468ab3446cbd7306a453a73dad2c1403e8' into firmware_21
This commit is contained in:
@@ -16,12 +16,14 @@ import_names = {
|
||||
# A mapping of package name to importable name
|
||||
'pep8-naming': 'pep8ext_naming',
|
||||
'pyusb': 'usb.core',
|
||||
'qmk-dotty-dict': 'dotty_dict'
|
||||
}
|
||||
|
||||
safe_commands = [
|
||||
# A list of subcommands we always run, even when the module imports fail
|
||||
'clone',
|
||||
'config',
|
||||
'doctor',
|
||||
'env',
|
||||
'setup',
|
||||
]
|
||||
@@ -38,7 +40,10 @@ subcommands = [
|
||||
'qmk.cli.doctor',
|
||||
'qmk.cli.fileformat',
|
||||
'qmk.cli.flash',
|
||||
'qmk.cli.format.c',
|
||||
'qmk.cli.format.json',
|
||||
'qmk.cli.format.python',
|
||||
'qmk.cli.format.text',
|
||||
'qmk.cli.generate.api',
|
||||
'qmk.cli.generate.config_h',
|
||||
'qmk.cli.generate.dfu_header',
|
||||
@@ -48,12 +53,14 @@ subcommands = [
|
||||
'qmk.cli.generate.layouts',
|
||||
'qmk.cli.generate.rgb_breathe_table',
|
||||
'qmk.cli.generate.rules_mk',
|
||||
'qmk.cli.generate.version_h',
|
||||
'qmk.cli.hello',
|
||||
'qmk.cli.info',
|
||||
'qmk.cli.json2c',
|
||||
'qmk.cli.lint',
|
||||
'qmk.cli.list.keyboards',
|
||||
'qmk.cli.list.keymaps',
|
||||
'qmk.cli.list.layouts',
|
||||
'qmk.cli.kle2json',
|
||||
'qmk.cli.multibuild',
|
||||
'qmk.cli.new.keyboard',
|
||||
@@ -63,6 +70,26 @@ subcommands = [
|
||||
]
|
||||
|
||||
|
||||
def _install_deps(requirements):
|
||||
"""Perform the installation of missing requirements.
|
||||
|
||||
If we detect that we are running in a virtualenv we can't write into we'll use sudo to perform the pip install.
|
||||
"""
|
||||
command = [sys.executable, '-m', 'pip', 'install']
|
||||
|
||||
if sys.prefix != sys.base_prefix:
|
||||
# We are in a virtualenv, check to see if we need to use sudo to write to it
|
||||
if not os.access(sys.prefix, os.W_OK):
|
||||
print('Notice: Using sudo to install modules to location owned by root:', sys.prefix)
|
||||
command.insert(0, 'sudo')
|
||||
|
||||
elif not os.access(sys.prefix, os.W_OK):
|
||||
# We can't write to sys.prefix, attempt to install locally
|
||||
command.append('--local')
|
||||
|
||||
return _run_cmd(*command, '-r', requirements)
|
||||
|
||||
|
||||
def _run_cmd(*command):
|
||||
"""Run a command in a subshell.
|
||||
"""
|
||||
@@ -155,8 +182,14 @@ if int(milc_version[0]) < 2 and int(milc_version[1]) < 4:
|
||||
print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
|
||||
exit(127)
|
||||
|
||||
# Make sure we can run binaries in the same directory as our Python interpreter
|
||||
python_dir = os.path.dirname(sys.executable)
|
||||
|
||||
if python_dir not in os.environ['PATH'].split(':'):
|
||||
os.environ['PATH'] = ":".join((python_dir, os.environ['PATH']))
|
||||
|
||||
# Check to make sure we have all our dependencies
|
||||
msg_install = 'Please run `python3 -m pip install -r %s` to install required python dependencies.'
|
||||
msg_install = f'Please run `{sys.executable} -m pip install -r %s` to install required python dependencies.'
|
||||
args = sys.argv[1:]
|
||||
while args and args[0][0] == '-':
|
||||
del args[0]
|
||||
@@ -166,7 +199,7 @@ safe_command = args and args[0] in safe_commands
|
||||
if not safe_command:
|
||||
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')
|
||||
_install_deps('requirements.txt')
|
||||
else:
|
||||
print()
|
||||
print(msg_install % (str(Path('requirements.txt').resolve()),))
|
||||
@@ -175,7 +208,7 @@ if not safe_command:
|
||||
|
||||
if cli.config.user.developer and _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')
|
||||
_install_deps('requirements-dev.txt')
|
||||
elif yesno('Would you like to disable developer mode?'):
|
||||
_run_cmd(sys.argv[0], 'config', 'user.developer=None')
|
||||
else:
|
||||
@@ -190,7 +223,7 @@ for subcommand in subcommands:
|
||||
try:
|
||||
__import__(subcommand)
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
except (ImportError, ModuleNotFoundError) as e:
|
||||
if safe_command:
|
||||
print(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}')
|
||||
else:
|
||||
|
||||
139
lib/python/qmk/cli/cformat.py
Normal file → Executable file
139
lib/python/qmk/cli/cformat.py
Normal file → Executable file
@@ -1,137 +1,28 @@
|
||||
"""Format C code according to QMK's style.
|
||||
"""Point people to the new command name.
|
||||
"""
|
||||
from os import path
|
||||
from shutil import which
|
||||
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from milc import cli
|
||||
|
||||
from qmk.path import normpath
|
||||
from qmk.c_parse import c_source_files
|
||||
|
||||
c_file_suffixes = ('c', 'h', 'cpp')
|
||||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
|
||||
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
|
||||
|
||||
|
||||
def find_clang_format():
|
||||
"""Returns the path to clang-format.
|
||||
"""
|
||||
for clang_version in range(20, 6, -1):
|
||||
binary = f'clang-format-{clang_version}'
|
||||
|
||||
if which(binary):
|
||||
return binary
|
||||
|
||||
return 'clang-format'
|
||||
|
||||
|
||||
def find_diffs(files):
|
||||
"""Run clang-format and diff it against a file.
|
||||
"""
|
||||
found_diffs = False
|
||||
|
||||
for file in files:
|
||||
cli.log.debug('Checking for changes in %s', file)
|
||||
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
||||
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
|
||||
|
||||
if diff.returncode != 0:
|
||||
print(diff.stdout)
|
||||
found_diffs = True
|
||||
|
||||
return found_diffs
|
||||
|
||||
|
||||
def cformat_run(files):
|
||||
"""Spawn clang-format subprocess with proper arguments
|
||||
"""
|
||||
# Determine which version of clang-format to use
|
||||
clang_format = [find_clang_format(), '-i']
|
||||
|
||||
try:
|
||||
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
|
||||
cli.log.info('Successfully formatted the C code.')
|
||||
return True
|
||||
|
||||
except CalledProcessError as e:
|
||||
cli.log.error('Error formatting C code!')
|
||||
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
|
||||
cli.log.debug('STDOUT:')
|
||||
cli.log.debug(e.stdout)
|
||||
cli.log.debug('STDERR:')
|
||||
cli.log.debug(e.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def filter_files(files, core_only=False):
|
||||
"""Yield only files to be formatted and skip the rest
|
||||
"""
|
||||
if core_only:
|
||||
# Filter non-core files
|
||||
for index, file in enumerate(files):
|
||||
# The following statement checks each file to see if the file path is
|
||||
# - in the core directories
|
||||
# - not in the ignored directories
|
||||
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
|
||||
files[index] = None
|
||||
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
|
||||
|
||||
for file in files:
|
||||
if file and file.name.split('.')[-1] in c_file_suffixes:
|
||||
yield file
|
||||
else:
|
||||
cli.log.debug('Skipping file %s', file)
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
|
||||
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
|
||||
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
|
||||
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
|
||||
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
|
||||
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
|
||||
@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
|
||||
@cli.subcommand('Pointer to the new command name: qmk format-c.', hidden=True)
|
||||
def cformat(cli):
|
||||
"""Format C code according to QMK's style.
|
||||
"""Pointer to the new command name: qmk format-c.
|
||||
"""
|
||||
# Find the list of files to format
|
||||
if cli.args.files:
|
||||
files = list(filter_files(cli.args.files, cli.args.core_only))
|
||||
cli.log.warning('"qmk cformat" has been renamed to "qmk format-c". Please use the new command in the future.')
|
||||
argv = [sys.executable, *sys.argv]
|
||||
argv[argv.index('cformat')] = 'format-c'
|
||||
script_path = Path(argv[1])
|
||||
script_path_exe = Path(f'{argv[1]}.exe')
|
||||
|
||||
if not files:
|
||||
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
|
||||
exit(0)
|
||||
if not script_path.exists() and script_path_exe.exists():
|
||||
# For reasons I don't understand ".exe" is stripped from the script name on windows.
|
||||
argv[1] = str(script_path_exe)
|
||||
|
||||
if cli.args.all_files:
|
||||
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
|
||||
|
||||
elif cli.args.all_files:
|
||||
all_files = c_source_files(core_dirs)
|
||||
files = list(filter_files(all_files, True))
|
||||
|
||||
else:
|
||||
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
|
||||
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
|
||||
|
||||
if git_diff.returncode != 0:
|
||||
cli.log.error("Error running %s", git_diff_cmd)
|
||||
print(git_diff.stderr)
|
||||
return git_diff.returncode
|
||||
|
||||
files = []
|
||||
|
||||
for file in git_diff.stdout.strip().split('\n'):
|
||||
if not any([file.startswith(ignore) for ignore in ignored]):
|
||||
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
|
||||
files.append(file)
|
||||
|
||||
# Sanity check
|
||||
if not files:
|
||||
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
|
||||
return False
|
||||
|
||||
# Run clang-format on the files we've found
|
||||
if cli.args.dry_run:
|
||||
return not find_diffs(files)
|
||||
else:
|
||||
return cformat_run(files)
|
||||
return cli.run(argv, capture_output=False).returncode
|
||||
|
||||
@@ -18,7 +18,7 @@ from qmk.keymap import keymap_completer
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
|
||||
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
||||
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
||||
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.subcommand('Compile a QMK Firmware.')
|
||||
|
||||
@@ -34,8 +34,8 @@ KNOWN_BOOTLOADERS = {
|
||||
('03EB', '2FFB'): 'atmel-dfu: AT90USB128',
|
||||
('03EB', '6124'): 'Microchip SAM-BA',
|
||||
('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER',
|
||||
('16C0', '05DC'): 'USBasp: USBaspLoader',
|
||||
('16C0', '05DF'): 'bootloadHID: HIDBoot',
|
||||
('16C0', '05DC'): 'usbasploader: USBaspLoader',
|
||||
('16C0', '05DF'): 'bootloadhid: HIDBoot',
|
||||
('16C0', '0478'): 'halfkay: Teensy Halfkay',
|
||||
('1B4F', '9203'): 'caterina: Pro Micro 3.3V',
|
||||
('1B4F', '9205'): 'caterina: Pro Micro 5V',
|
||||
@@ -48,10 +48,11 @@ KNOWN_BOOTLOADERS = {
|
||||
('239A', '000C'): 'caterina: Adafruit Feather 32U4',
|
||||
('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v',
|
||||
('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v',
|
||||
('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v',
|
||||
('2A03', '0036'): 'caterina: Arduino Leonardo',
|
||||
('2A03', '0037'): 'caterina: Arduino Micro',
|
||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode'
|
||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode',
|
||||
('03EB', '2067'): 'qmk-hid: HID Bootloader',
|
||||
('03EB', '2045'): 'lufa-ms: LUFA Mass Storage Bootloader'
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
"""
|
||||
import http.server
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
@cli.argument('-p', '--port', default=8936, type=int, help='Port number to use.')
|
||||
@cli.argument('-b', '--browser', action='store_true', help='Open the docs in the default browser.')
|
||||
@cli.subcommand('Run a local webserver for QMK documentation.', hidden=False if cli.config.user.developer else True)
|
||||
def docs(cli):
|
||||
"""Spin up a local HTTPServer instance for the QMK docs.
|
||||
@@ -14,9 +16,12 @@ def docs(cli):
|
||||
os.chdir('docs')
|
||||
|
||||
with http.server.HTTPServer(('', cli.config.docs.port), http.server.SimpleHTTPRequestHandler) as httpd:
|
||||
cli.log.info("Serving QMK docs at http://localhost:%d/", cli.config.docs.port)
|
||||
cli.log.info(f"Serving QMK docs at http://localhost:{cli.config.docs.port}/")
|
||||
cli.log.info("Press Control+C to exit.")
|
||||
|
||||
if cli.config.docs.browser:
|
||||
webbrowser.open(f'http://localhost:{cli.config.docs.port}')
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
|
||||
5
lib/python/qmk/cli/doctor/__init__.py
Executable file
5
lib/python/qmk/cli/doctor/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
"""QMK Doctor
|
||||
|
||||
Check out the user's QMK environment and make sure it's ready to compile.
|
||||
"""
|
||||
from .main import doctor
|
||||
164
lib/python/qmk/cli/doctor/check.py
Normal file
164
lib/python/qmk/cli/doctor/check.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""Check for specific programs.
|
||||
"""
|
||||
from enum import Enum
|
||||
import re
|
||||
import shutil
|
||||
from subprocess import DEVNULL
|
||||
|
||||
from milc import cli
|
||||
from qmk import submodules
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
|
||||
|
||||
class CheckStatus(Enum):
|
||||
OK = 1
|
||||
WARNING = 2
|
||||
ERROR = 3
|
||||
|
||||
|
||||
ESSENTIAL_BINARIES = {
|
||||
'dfu-programmer': {},
|
||||
'avrdude': {},
|
||||
'dfu-util': {},
|
||||
'avr-gcc': {
|
||||
'version_arg': '-dumpversion'
|
||||
},
|
||||
'arm-none-eabi-gcc': {
|
||||
'version_arg': '-dumpversion'
|
||||
},
|
||||
'bin/qmk': {},
|
||||
}
|
||||
|
||||
|
||||
def _parse_gcc_version(version):
|
||||
m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
|
||||
|
||||
return {
|
||||
'major': int(m.group(1)),
|
||||
'minor': int(m.group(2)) if m.group(2) else 0,
|
||||
'patch': int(m.group(3)) if m.group(3) else 0,
|
||||
}
|
||||
|
||||
|
||||
def _check_arm_gcc_version():
|
||||
"""Returns True if the arm-none-eabi-gcc version is not known to cause problems.
|
||||
"""
|
||||
if 'output' in ESSENTIAL_BINARIES['arm-none-eabi-gcc']:
|
||||
version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip()
|
||||
cli.log.info('Found arm-none-eabi-gcc version %s', version_number)
|
||||
|
||||
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.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.')
|
||||
rc = CheckStatus.WARNING
|
||||
|
||||
return rc
|
||||
|
||||
|
||||
def _check_avrdude_version():
|
||||
if 'output' in ESSENTIAL_BINARIES['avrdude']:
|
||||
last_line = ESSENTIAL_BINARIES['avrdude']['output'].split('\n')[-2]
|
||||
version_number = last_line.split()[2][:-1]
|
||||
cli.log.info('Found avrdude version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_dfu_util_version():
|
||||
if 'output' in ESSENTIAL_BINARIES['dfu-util']:
|
||||
first_line = ESSENTIAL_BINARIES['dfu-util']['output'].split('\n')[0]
|
||||
version_number = first_line.split()[1]
|
||||
cli.log.info('Found dfu-util version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def _check_dfu_programmer_version():
|
||||
if 'output' in ESSENTIAL_BINARIES['dfu-programmer']:
|
||||
first_line = ESSENTIAL_BINARIES['dfu-programmer']['output'].split('\n')[0]
|
||||
version_number = first_line.split()[1]
|
||||
cli.log.info('Found dfu-programmer version %s', version_number)
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def check_binaries():
|
||||
"""Iterates through ESSENTIAL_BINARIES and tests them.
|
||||
"""
|
||||
ok = True
|
||||
|
||||
for binary in sorted(ESSENTIAL_BINARIES):
|
||||
if not is_executable(binary):
|
||||
ok = False
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def check_binary_versions():
|
||||
"""Check the versions of ESSENTIAL_BINARIES
|
||||
"""
|
||||
versions = []
|
||||
for check in (_check_arm_gcc_version, _check_avr_gcc_version, _check_avrdude_version, _check_dfu_util_version, _check_dfu_programmer_version):
|
||||
versions.append(check())
|
||||
return versions
|
||||
|
||||
|
||||
def check_submodules():
|
||||
"""Iterates through all submodules to make sure they're cloned and up to date.
|
||||
"""
|
||||
for submodule in submodules.status().values():
|
||||
if submodule['status'] is None:
|
||||
cli.log.error('Submodule %s has not yet been cloned!', submodule['name'])
|
||||
return CheckStatus.ERROR
|
||||
elif not submodule['status']:
|
||||
cli.log.warning('Submodule %s is not up to date!', submodule['name'])
|
||||
return CheckStatus.WARNING
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def is_executable(command):
|
||||
"""Returns True if command exists and can be executed.
|
||||
"""
|
||||
# Make sure the command is in the path.
|
||||
res = shutil.which(command)
|
||||
if res is None:
|
||||
cli.log.error("{fg_red}Can't find %s in your path.", command)
|
||||
return False
|
||||
|
||||
# Make sure the command can be executed
|
||||
version_arg = ESSENTIAL_BINARIES[command].get('version_arg', '--version')
|
||||
check = cli.run([command, version_arg], combined_output=True, stdin=DEVNULL, timeout=5)
|
||||
|
||||
ESSENTIAL_BINARIES[command]['output'] = check.stdout
|
||||
|
||||
if check.returncode in [0, 1]: # Older versions of dfu-programmer exit 1
|
||||
cli.log.debug('Found {fg_cyan}%s', command)
|
||||
return True
|
||||
|
||||
cli.log.error("{fg_red}Can't run `%s %s`", command, version_arg)
|
||||
return False
|
||||
|
||||
|
||||
def check_git_repo():
|
||||
"""Checks that the .git directory exists inside QMK_HOME.
|
||||
|
||||
This is a decent enough indicator that the qmk_firmware directory is a
|
||||
proper Git repository, rather than a .zip download from GitHub.
|
||||
"""
|
||||
dot_git = QMK_FIRMWARE / '.git'
|
||||
|
||||
return CheckStatus.OK if dot_git.exists() else CheckStatus.WARNING
|
||||
181
lib/python/qmk/cli/doctor/linux.py
Normal file
181
lib/python/qmk/cli/doctor/linux.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""OS-specific functions for: Linux
|
||||
"""
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from .check import CheckStatus
|
||||
|
||||
|
||||
def _udev_rule(vid, pid=None, *args):
|
||||
""" Helper function that return udev rules
|
||||
"""
|
||||
rule = ""
|
||||
if pid:
|
||||
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % (
|
||||
vid,
|
||||
pid,
|
||||
)
|
||||
else:
|
||||
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid
|
||||
if args:
|
||||
rule = ', '.join([rule, *args])
|
||||
return rule
|
||||
|
||||
|
||||
def _deprecated_udev_rule(vid, pid=None):
|
||||
""" Helper function that return udev rules
|
||||
|
||||
Note: these are no longer the recommended rules, this is just used to check for them
|
||||
"""
|
||||
if pid:
|
||||
return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", MODE:="0666"' % (vid, pid)
|
||||
else:
|
||||
return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", MODE:="0666"' % vid
|
||||
|
||||
|
||||
def check_udev_rules():
|
||||
"""Make sure the udev rules look good.
|
||||
"""
|
||||
rc = CheckStatus.OK
|
||||
udev_dirs = [
|
||||
Path("/usr/lib/udev/rules.d/"),
|
||||
Path("/usr/local/lib/udev/rules.d/"),
|
||||
Path("/run/udev/rules.d/"),
|
||||
Path("/etc/udev/rules.d/"),
|
||||
]
|
||||
desired_rules = {
|
||||
'atmel-dfu': {
|
||||
_udev_rule("03eb", "2fef"), # ATmega16U2
|
||||
_udev_rule("03eb", "2ff0"), # ATmega32U2
|
||||
_udev_rule("03eb", "2ff3"), # ATmega16U4
|
||||
_udev_rule("03eb", "2ff4"), # ATmega32U4
|
||||
_udev_rule("03eb", "2ff9"), # AT90USB64
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
_udev_rule("03eb", "2ffa"), # AT90USB162
|
||||
>>>>>>> 0.12.52~1
|
||||
_udev_rule("03eb", "2ffb") # AT90USB128
|
||||
},
|
||||
'kiibohd': {_udev_rule("1c11", "b007")},
|
||||
'stm32': {
|
||||
_udev_rule("1eaf", "0003"), # STM32duino
|
||||
_udev_rule("0483", "df11") # STM32 DFU
|
||||
},
|
||||
'bootloadhid': {_udev_rule("16c0", "05df")},
|
||||
'usbasploader': {_udev_rule("16c0", "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
|
||||
# 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 LLC
|
||||
_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("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
|
||||
_udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro
|
||||
},
|
||||
'hid-bootloader': {
|
||||
_udev_rule("03eb", "2067"), # QMK HID
|
||||
_udev_rule("16c0", "0478") # PJRC halfkay
|
||||
}
|
||||
}
|
||||
|
||||
# These rules are no longer recommended, only use them to check for their presence.
|
||||
deprecated_rules = {
|
||||
'atmel-dfu': {_deprecated_udev_rule("03eb", "2ff4"), _deprecated_udev_rule("03eb", "2ffb"), _deprecated_udev_rule("03eb", "2ff0")},
|
||||
'kiibohd': {_deprecated_udev_rule("1c11")},
|
||||
'stm32': {_deprecated_udev_rule("1eaf", "0003"), _deprecated_udev_rule("0483", "df11")},
|
||||
'bootloadhid': {_deprecated_udev_rule("16c0", "05df")},
|
||||
'caterina': {'ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1"', 'ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1"'},
|
||||
'tmk': {_deprecated_udev_rule("feed")}
|
||||
}
|
||||
|
||||
if any(udev_dir.exists() for udev_dir in udev_dirs):
|
||||
udev_rules = [rule_file for udev_dir in udev_dirs for rule_file in udev_dir.glob('*.rules')]
|
||||
current_rules = set()
|
||||
|
||||
# Collect all rules from the config files
|
||||
for rule_file in udev_rules:
|
||||
<<<<<<< HEAD
|
||||
for line in rule_file.read_text().split('\n'):
|
||||
=======
|
||||
for line in rule_file.read_text(encoding='utf-8').split('\n'):
|
||||
>>>>>>> 0.12.52~1
|
||||
line = line.strip()
|
||||
if not line.startswith("#") and len(line):
|
||||
current_rules.add(line)
|
||||
|
||||
# Check if the desired rules are among the currently present rules
|
||||
for bootloader, rules in desired_rules.items():
|
||||
if not rules.issubset(current_rules):
|
||||
deprecated_rule = deprecated_rules.get(bootloader)
|
||||
if deprecated_rule and deprecated_rule.issubset(current_rules):
|
||||
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:
|
||||
# For caterina, check if ModemManager is running
|
||||
if bootloader == "caterina" and check_modem_manager():
|
||||
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)
|
||||
|
||||
else:
|
||||
cli.log.warning("{fg_yellow}Can't find udev rules, skipping udev rule checking...")
|
||||
cli.log.debug("Checked directories: %s", ', '.join(str(udev_dir) for udev_dir in udev_dirs))
|
||||
|
||||
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 check_systemd():
|
||||
<<<<<<< HEAD
|
||||
mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
|
||||
=======
|
||||
mm_check = cli.run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
|
||||
>>>>>>> 0.12.52~1
|
||||
if mm_check.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
"""(TODO): Add check for non-systemd systems
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def os_test_linux():
|
||||
"""Run the Linux specific tests.
|
||||
"""
|
||||
# Don't bother with udev on WSL, for now
|
||||
if 'microsoft' in platform.uname().release.lower():
|
||||
cli.log.info("Detected {fg_cyan}Linux (WSL){fg_reset}.")
|
||||
|
||||
# https://github.com/microsoft/WSL/issues/4197
|
||||
if QMK_FIRMWARE.as_posix().startswith("/mnt"):
|
||||
cli.log.warning("I/O performance on /mnt may be extremely slow.")
|
||||
return CheckStatus.WARNING
|
||||
|
||||
return CheckStatus.OK
|
||||
else:
|
||||
cli.log.info("Detected {fg_cyan}Linux{fg_reset}.")
|
||||
|
||||
return check_udev_rules()
|
||||
13
lib/python/qmk/cli/doctor/macos.py
Normal file
13
lib/python/qmk/cli/doctor/macos.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import platform
|
||||
|
||||
from milc import cli
|
||||
|
||||
from .check import CheckStatus
|
||||
|
||||
|
||||
def os_test_macos():
|
||||
"""Run the Mac specific tests.
|
||||
"""
|
||||
cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
|
||||
|
||||
return CheckStatus.OK
|
||||
@@ -7,9 +7,11 @@ from subprocess import DEVNULL
|
||||
|
||||
from milc import cli
|
||||
from milc.questions import yesno
|
||||
|
||||
from qmk import submodules
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.os_helpers import CheckStatus, check_binaries, check_binary_versions, check_submodules, check_git_repo
|
||||
from qmk.constants import QMK_FIRMWARE, QMK_FIRMWARE_UPSTREAM
|
||||
from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules
|
||||
from qmk.commands import git_check_repo, git_get_branch, git_is_dirty, git_get_remotes, git_check_deviation, in_virtualenv
|
||||
|
||||
|
||||
def os_tests():
|
||||
@@ -18,51 +20,48 @@ def os_tests():
|
||||
platform_id = platform.platform().lower()
|
||||
|
||||
if 'darwin' in platform_id or 'macos' in platform_id:
|
||||
from .macos import os_test_macos
|
||||
return os_test_macos()
|
||||
elif 'linux' in platform_id:
|
||||
from .linux import os_test_linux
|
||||
return os_test_linux()
|
||||
elif 'windows' in platform_id:
|
||||
from .windows import os_test_windows
|
||||
return os_test_windows()
|
||||
else:
|
||||
cli.log.warning('Unsupported OS detected: %s', platform_id)
|
||||
return CheckStatus.WARNING
|
||||
|
||||
|
||||
def os_test_linux():
|
||||
"""Run the Linux specific tests.
|
||||
def git_tests():
|
||||
"""Run Git-related checks
|
||||
"""
|
||||
# Don't bother with udev on WSL, for now
|
||||
if 'microsoft' in platform.uname().release.lower():
|
||||
cli.log.info("Detected {fg_cyan}Linux (WSL){fg_reset}.")
|
||||
status = CheckStatus.OK
|
||||
|
||||
# https://github.com/microsoft/WSL/issues/4197
|
||||
if QMK_FIRMWARE.as_posix().startswith("/mnt"):
|
||||
cli.log.warning("I/O performance on /mnt may be extremely slow.")
|
||||
return CheckStatus.WARNING
|
||||
|
||||
return 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:
|
||||
cli.log.info("Detected {fg_cyan}Linux{fg_reset}.")
|
||||
from qmk.os_helpers.linux import check_udev_rules
|
||||
git_branch = git_get_branch()
|
||||
if git_branch:
|
||||
cli.log.info('Git branch: %s', git_branch)
|
||||
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
|
||||
|
||||
return check_udev_rules()
|
||||
|
||||
|
||||
def os_test_macos():
|
||||
"""Run the Mac specific tests.
|
||||
"""
|
||||
cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
|
||||
|
||||
return CheckStatus.OK
|
||||
|
||||
|
||||
def os_test_windows():
|
||||
"""Run the Windows specific tests.
|
||||
"""
|
||||
win32_ver = platform.win32_ver()
|
||||
cli.log.info("Detected {fg_cyan}Windows %s (%s){fg_reset}.", win32_ver[0], win32_ver[1])
|
||||
|
||||
return CheckStatus.OK
|
||||
return status
|
||||
|
||||
|
||||
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
|
||||
@@ -77,16 +76,17 @@ def doctor(cli):
|
||||
* [ ] Compile a trivial program with each compiler
|
||||
"""
|
||||
cli.log.info('QMK Doctor is checking your environment.')
|
||||
cli.log.info('CLI version: %s', cli.version)
|
||||
cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE)
|
||||
|
||||
status = os_tests()
|
||||
status = os_status = os_tests()
|
||||
git_status = git_tests()
|
||||
|
||||
# Make sure our QMK home is a Git repo
|
||||
git_ok = check_git_repo()
|
||||
if git_status == CheckStatus.ERROR or (os_status == CheckStatus.OK and git_status == CheckStatus.WARNING):
|
||||
status = git_status
|
||||
|
||||
if git_ok == CheckStatus.WARNING:
|
||||
cli.log.warning("QMK home does not appear to be a Git repository! (no .git folder)")
|
||||
status = CheckStatus.WARNING
|
||||
if in_virtualenv():
|
||||
cli.log.info('CLI installed in virtualenv.')
|
||||
|
||||
# Make sure the basic CLI tools we need are available and can be executed.
|
||||
bin_ok = check_binaries()
|
||||
14
lib/python/qmk/cli/doctor/windows.py
Normal file
14
lib/python/qmk/cli/doctor/windows.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import platform
|
||||
|
||||
from milc import cli
|
||||
|
||||
from .check import CheckStatus
|
||||
|
||||
|
||||
def os_test_windows():
|
||||
"""Run the Windows specific tests.
|
||||
"""
|
||||
win32_ver = platform.win32_ver()
|
||||
cli.log.info("Detected {fg_cyan}Windows %s (%s){fg_reset}.", win32_ver[0], win32_ver[1])
|
||||
|
||||
return CheckStatus.OK
|
||||
24
lib/python/qmk/cli/fileformat.py
Normal file → Executable file
24
lib/python/qmk/cli/fileformat.py
Normal file → Executable file
@@ -1,13 +1,23 @@
|
||||
"""Format files according to QMK's style.
|
||||
"""Point people to the new command name.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
@cli.subcommand("Format files according to QMK's style.", hidden=True)
|
||||
@cli.subcommand('Pointer to the new command name: qmk format-text.', hidden=True)
|
||||
def fileformat(cli):
|
||||
"""Run several general formatting commands.
|
||||
"""Pointer to the new command name: qmk format-text.
|
||||
"""
|
||||
dos2unix = subprocess.run(['bash', '-c', 'git ls-files -z | xargs -0 dos2unix'], stdout=subprocess.DEVNULL)
|
||||
return dos2unix.returncode
|
||||
cli.log.warning('"qmk fileformat" has been renamed to "qmk format-text". Please use the new command in the future.')
|
||||
argv = [sys.executable, *sys.argv]
|
||||
argv[argv.index('fileformat')] = 'format-text'
|
||||
script_path = Path(argv[1])
|
||||
script_path_exe = Path(f'{argv[1]}.exe')
|
||||
|
||||
if not script_path.exists() and script_path_exe.exists():
|
||||
# For reasons I don't understand ".exe" is stripped from the script name on windows.
|
||||
argv[1] = str(script_path_exe)
|
||||
|
||||
return cli.run(argv, capture_output=False).returncode
|
||||
|
||||
@@ -38,7 +38,7 @@ def print_bootloader_help():
|
||||
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
||||
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
|
||||
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.subcommand('QMK Flash.')
|
||||
|
||||
137
lib/python/qmk/cli/format/c.py
Normal file
137
lib/python/qmk/cli/format/c.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""Format C code according to QMK's style.
|
||||
"""
|
||||
from os import path
|
||||
from shutil import which
|
||||
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
|
||||
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from milc import cli
|
||||
|
||||
from qmk.path import normpath
|
||||
from qmk.c_parse import c_source_files
|
||||
|
||||
c_file_suffixes = ('c', 'h', 'cpp')
|
||||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
|
||||
ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards')
|
||||
|
||||
|
||||
def find_clang_format():
|
||||
"""Returns the path to clang-format.
|
||||
"""
|
||||
for clang_version in range(20, 6, -1):
|
||||
binary = f'clang-format-{clang_version}'
|
||||
|
||||
if which(binary):
|
||||
return binary
|
||||
|
||||
return 'clang-format'
|
||||
|
||||
|
||||
def find_diffs(files):
|
||||
"""Run clang-format and diff it against a file.
|
||||
"""
|
||||
found_diffs = False
|
||||
|
||||
for file in files:
|
||||
cli.log.debug('Checking for changes in %s', file)
|
||||
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
||||
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
|
||||
|
||||
if diff.returncode != 0:
|
||||
print(diff.stdout)
|
||||
found_diffs = True
|
||||
|
||||
return found_diffs
|
||||
|
||||
|
||||
def cformat_run(files):
|
||||
"""Spawn clang-format subprocess with proper arguments
|
||||
"""
|
||||
# Determine which version of clang-format to use
|
||||
clang_format = [find_clang_format(), '-i']
|
||||
|
||||
try:
|
||||
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
|
||||
cli.log.info('Successfully formatted the C code.')
|
||||
return True
|
||||
|
||||
except CalledProcessError as e:
|
||||
cli.log.error('Error formatting C code!')
|
||||
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
|
||||
cli.log.debug('STDOUT:')
|
||||
cli.log.debug(e.stdout)
|
||||
cli.log.debug('STDERR:')
|
||||
cli.log.debug(e.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def filter_files(files, core_only=False):
|
||||
"""Yield only files to be formatted and skip the rest
|
||||
"""
|
||||
if core_only:
|
||||
# Filter non-core files
|
||||
for index, file in enumerate(files):
|
||||
# The following statement checks each file to see if the file path is
|
||||
# - in the core directories
|
||||
# - not in the ignored directories
|
||||
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
|
||||
files[index] = None
|
||||
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
|
||||
|
||||
for file in files:
|
||||
if file and file.name.split('.')[-1] in c_file_suffixes:
|
||||
yield file
|
||||
else:
|
||||
cli.log.debug('Skipping file %s', file)
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
|
||||
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
|
||||
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
|
||||
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
|
||||
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
|
||||
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
|
||||
def format_c(cli):
|
||||
"""Format C code according to QMK's style.
|
||||
"""
|
||||
# Find the list of files to format
|
||||
if cli.args.files:
|
||||
files = list(filter_files(cli.args.files, cli.args.core_only))
|
||||
|
||||
if not files:
|
||||
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
|
||||
exit(0)
|
||||
|
||||
if cli.args.all_files:
|
||||
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
|
||||
|
||||
elif cli.args.all_files:
|
||||
all_files = c_source_files(core_dirs)
|
||||
files = list(filter_files(all_files, True))
|
||||
|
||||
else:
|
||||
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
|
||||
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
|
||||
|
||||
if git_diff.returncode != 0:
|
||||
cli.log.error("Error running %s", git_diff_cmd)
|
||||
print(git_diff.stderr)
|
||||
return git_diff.returncode
|
||||
|
||||
files = []
|
||||
|
||||
for file in git_diff.stdout.strip().split('\n'):
|
||||
if not any([file.startswith(ignore) for ignore in ignored]):
|
||||
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
|
||||
files.append(file)
|
||||
|
||||
# Sanity check
|
||||
if not files:
|
||||
cli.log.error('No changed files detected. Use "qmk format-c -a" to format all core files')
|
||||
return False
|
||||
|
||||
# Run clang-format on the files we've found
|
||||
if cli.args.dry_run:
|
||||
return not find_diffs(files)
|
||||
else:
|
||||
return cformat_run(files)
|
||||
@@ -8,7 +8,7 @@ from jsonschema import ValidationError
|
||||
from milc import cli
|
||||
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load, keyboard_validate
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder
|
||||
from qmk.path import normpath
|
||||
|
||||
@@ -23,14 +23,13 @@ def format_json(cli):
|
||||
|
||||
if cli.args.format == 'auto':
|
||||
try:
|
||||
keyboard_validate(json_file)
|
||||
validate(json_file, 'qmk.keyboard.v1')
|
||||
json_encoder = InfoJSONEncoder
|
||||
|
||||
except ValidationError as e:
|
||||
cli.log.warning('File %s did not validate as a keyboard:\n\t%s', cli.args.json_file, e)
|
||||
cli.log.info('Treating %s as a keymap file.', cli.args.json_file)
|
||||
json_encoder = KeymapJSONEncoder
|
||||
|
||||
elif cli.args.format == 'keyboard':
|
||||
json_encoder = InfoJSONEncoder
|
||||
elif cli.args.format == 'keymap':
|
||||
|
||||
26
lib/python/qmk/cli/format/python.py
Executable file
26
lib/python/qmk/cli/format/python.py
Executable file
@@ -0,0 +1,26 @@
|
||||
"""Format python code according to QMK's style.
|
||||
"""
|
||||
from subprocess import CalledProcessError, DEVNULL
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.")
|
||||
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True)
|
||||
def format_python(cli):
|
||||
"""Format python code according to QMK's style.
|
||||
"""
|
||||
edit = '--diff' if cli.args.dry_run else '--in-place'
|
||||
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python']
|
||||
try:
|
||||
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
|
||||
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.')
|
||||
return True
|
||||
|
||||
except CalledProcessError:
|
||||
if cli.args.dry_run:
|
||||
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!')
|
||||
else:
|
||||
cli.log.error('Error formatting python code!')
|
||||
|
||||
return False
|
||||
27
lib/python/qmk/cli/format/text.py
Normal file
27
lib/python/qmk/cli/format/text.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Ensure text files have the proper line endings.
|
||||
"""
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
@cli.subcommand("Ensure text files have the proper line endings.", hidden=True)
|
||||
def format_text(cli):
|
||||
"""Ensure text files have the proper line endings.
|
||||
"""
|
||||
try:
|
||||
file_list_cmd = cli.run(['git', 'ls-files', '-z'], check=True)
|
||||
except CalledProcessError as e:
|
||||
cli.log.error('Could not get file list: %s', e)
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
cli.log.error('Unhandled exception: %s: %s', e.__class__.__name__, e)
|
||||
cli.log.exception(e)
|
||||
exit(1)
|
||||
|
||||
dos2unix = cli.run(['xargs', '-0', 'dos2unix'], stdin=None, input=file_list_cmd.stdout)
|
||||
|
||||
if dos2unix.returncode != 0:
|
||||
print(dos2unix.stderr)
|
||||
|
||||
return dos2unix.returncode
|
||||
@@ -5,14 +5,14 @@ from pathlib import Path
|
||||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard, normpath
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
def direct_pins(direct_pins):
|
||||
def direct_pins(direct_pins, postfix):
|
||||
"""Return the config.h lines that set the direct pins.
|
||||
"""
|
||||
rows = []
|
||||
@@ -24,81 +24,60 @@ def direct_pins(direct_pins):
|
||||
col_count = len(direct_pins[0])
|
||||
row_count = len(direct_pins)
|
||||
|
||||
return """
|
||||
#ifndef MATRIX_COLS
|
||||
# define MATRIX_COLS %s
|
||||
#endif // MATRIX_COLS
|
||||
return f"""
|
||||
#ifndef MATRIX_COLS{postfix}
|
||||
# define MATRIX_COLS{postfix} {col_count}
|
||||
#endif // MATRIX_COLS{postfix}
|
||||
|
||||
#ifndef MATRIX_ROWS
|
||||
# define MATRIX_ROWS %s
|
||||
#endif // MATRIX_ROWS
|
||||
#ifndef MATRIX_ROWS{postfix}
|
||||
# define MATRIX_ROWS{postfix} {row_count}
|
||||
#endif // MATRIX_ROWS{postfix}
|
||||
|
||||
#ifndef DIRECT_PINS
|
||||
# define DIRECT_PINS {%s}
|
||||
#endif // DIRECT_PINS
|
||||
""" % (col_count, row_count, ','.join(rows))
|
||||
#ifndef DIRECT_PINS{postfix}
|
||||
# define DIRECT_PINS{postfix} {{ {", ".join(rows)} }}
|
||||
#endif // DIRECT_PINS{postfix}
|
||||
"""
|
||||
|
||||
|
||||
def pin_array(define, pins):
|
||||
def pin_array(define, pins, postfix):
|
||||
"""Return the config.h lines that set a pin array.
|
||||
"""
|
||||
pin_num = len(pins)
|
||||
pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))
|
||||
|
||||
return f"""
|
||||
#ifndef {define}S
|
||||
# define {define}S {pin_num}
|
||||
#endif // {define}S
|
||||
#ifndef {define}S{postfix}
|
||||
# define {define}S{postfix} {pin_num}
|
||||
#endif // {define}S{postfix}
|
||||
|
||||
#ifndef {define}_PINS
|
||||
# define {define}_PINS {{ {pin_array} }}
|
||||
#endif // {define}_PINS
|
||||
#ifndef {define}_PINS{postfix}
|
||||
# define {define}_PINS{postfix} {{ {pin_array} }}
|
||||
#endif // {define}_PINS{postfix}
|
||||
"""
|
||||
|
||||
|
||||
def matrix_pins(matrix_pins):
|
||||
def matrix_pins(matrix_pins, postfix=''):
|
||||
"""Add the matrix config to the config.h.
|
||||
"""
|
||||
pins = []
|
||||
|
||||
if 'direct' in matrix_pins:
|
||||
pins.append(direct_pins(matrix_pins['direct']))
|
||||
pins.append(direct_pins(matrix_pins['direct'], postfix))
|
||||
|
||||
if 'cols' in matrix_pins:
|
||||
pins.append(pin_array('MATRIX_COL', matrix_pins['cols']))
|
||||
pins.append(pin_array('MATRIX_COL', matrix_pins['cols'], postfix))
|
||||
|
||||
if 'rows' in matrix_pins:
|
||||
pins.append(pin_array('MATRIX_ROW', matrix_pins['rows']))
|
||||
pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'], postfix))
|
||||
|
||||
return '\n'.join(pins)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
def generate_config_h(cli):
|
||||
"""Generates the info_config.h file.
|
||||
def generate_config_items(kb_info_json, config_h_lines):
|
||||
"""Iterate through the info_config map to generate basic config values.
|
||||
"""
|
||||
# Determine our keyboard(s)
|
||||
if not cli.config.generate_config_h.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['info'].print_help()
|
||||
return False
|
||||
|
||||
if not is_keyboard(cli.config.generate_config_h.keyboard):
|
||||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard)
|
||||
return False
|
||||
|
||||
# Build the info_config.h file.
|
||||
kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
|
||||
info_config_map = json_load(Path('data/mappings/info_config.json'))
|
||||
|
||||
config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
|
||||
|
||||
# Iterate through the info_config map to generate basic things
|
||||
for config_key, info_dict in info_config_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
@@ -135,9 +114,75 @@ def generate_config_h(cli):
|
||||
config_h_lines.append(f'# define {config_key} {config_value}')
|
||||
config_h_lines.append(f'#endif // {config_key}')
|
||||
|
||||
|
||||
def generate_split_config(kb_info_json, config_h_lines):
|
||||
"""Generate the config.h lines for split boards."""
|
||||
if 'primary' in kb_info_json['split']:
|
||||
if kb_info_json['split']['primary'] in ('left', 'right'):
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef MASTER_LEFT')
|
||||
config_h_lines.append('# ifndef MASTER_RIGHT')
|
||||
if kb_info_json['split']['primary'] == 'left':
|
||||
config_h_lines.append('# define MASTER_LEFT')
|
||||
elif kb_info_json['split']['primary'] == 'right':
|
||||
config_h_lines.append('# define MASTER_RIGHT')
|
||||
config_h_lines.append('# endif // MASTER_RIGHT')
|
||||
config_h_lines.append('#endif // MASTER_LEFT')
|
||||
elif kb_info_json['split']['primary'] == 'pin':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef SPLIT_HAND_PIN')
|
||||
config_h_lines.append('# define SPLIT_HAND_PIN')
|
||||
config_h_lines.append('#endif // SPLIT_HAND_PIN')
|
||||
elif kb_info_json['split']['primary'] == 'matrix_grid':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef SPLIT_HAND_MATRIX_GRID')
|
||||
config_h_lines.append('# define SPLIT_HAND_MATRIX_GRID {%s}' % (','.join(kb_info_json["split"]["matrix_grid"],)))
|
||||
config_h_lines.append('#endif // SPLIT_HAND_MATRIX_GRID')
|
||||
elif kb_info_json['split']['primary'] == 'eeprom':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef EE_HANDS')
|
||||
config_h_lines.append('# define EE_HANDS')
|
||||
config_h_lines.append('#endif // EE_HANDS')
|
||||
|
||||
if 'protocol' in kb_info_json['split'].get('transport', {}):
|
||||
if kb_info_json['split']['transport']['protocol'] == 'i2c':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append('#ifndef USE_I2C')
|
||||
config_h_lines.append('# define USE_I2C')
|
||||
config_h_lines.append('#endif // USE_I2C')
|
||||
|
||||
if 'right' in kb_info_json['split'].get('matrix_pins', {}):
|
||||
config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT'))
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate config.h for.')
|
||||
@cli.argument('-km', '--keymap', arg_only=True, help='Keymap to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
def generate_config_h(cli):
|
||||
"""Generates the info_config.h file.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
# Build the info_config.h file.
|
||||
config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.', ' */', '', '#pragma once']
|
||||
|
||||
generate_config_items(kb_info_json, config_h_lines)
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
||||
|
||||
if 'split' in kb_info_json:
|
||||
generate_split_config(kb_info_json, config_h_lines)
|
||||
|
||||
# Show the results
|
||||
config_h = '\n'.join(config_h_lines)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def generate_dfu_header(cli):
|
||||
# Build the Keyboard.h file.
|
||||
kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard))
|
||||
|
||||
keyboard_h_lines = ['/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.' ' */', '', '#pragma once']
|
||||
keyboard_h_lines = ['/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.', ' */', '', '#pragma once']
|
||||
keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}')
|
||||
keyboard_h_lines.append(f'#define PRODUCT {cli.config.generate_dfu_header.keyboard} Bootloader')
|
||||
|
||||
|
||||
@@ -4,15 +4,17 @@ Compile an info.json for a particular keyboard and pretty-print it.
|
||||
"""
|
||||
import json
|
||||
|
||||
from jsonschema import Draft7Validator, validators
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from jsonschema import Draft7Validator, RefResolver, validators
|
||||
from milc import cli
|
||||
from pathlib import Path
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import load_jsonschema
|
||||
from qmk.json_schema import compile_schema_store
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard
|
||||
from qmk.path import is_keyboard, normpath
|
||||
|
||||
|
||||
def pruning_validator(validator_class):
|
||||
@@ -34,15 +36,19 @@ def pruning_validator(validator_class):
|
||||
def strip_info_json(kb_info_json):
|
||||
"""Remove the API-only properties from the info.json.
|
||||
"""
|
||||
schema_store = compile_schema_store()
|
||||
pruning_draft_7_validator = pruning_validator(Draft7Validator)
|
||||
schema = load_jsonschema('keyboard')
|
||||
validator = pruning_draft_7_validator(schema).validate
|
||||
schema = schema_store['qmk.keyboard.v1']
|
||||
resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
|
||||
validator = pruning_draft_7_validator(schema, resolver=resolver).validate
|
||||
|
||||
return validator(kb_info_json)
|
||||
|
||||
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
|
||||
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
|
||||
@cli.argument('-o', '--output', arg_only=True, completer=FilesCompleter, help='Write the output the specified file, overwriting if necessary.')
|
||||
@cli.argument('-ow', '--overwrite', arg_only=True, action='store_true', help='Overwrite the existing info.json. (Overrides the location of --output)')
|
||||
@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
@@ -59,9 +65,29 @@ def generate_info_json(cli):
|
||||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard)
|
||||
return False
|
||||
|
||||
if cli.args.overwrite:
|
||||
output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve()
|
||||
|
||||
if cli.args.output:
|
||||
cli.log.warning('Overwriting user supplied --output with %s', output_path)
|
||||
|
||||
cli.args.output = output_path
|
||||
|
||||
# Build the info.json file
|
||||
kb_info_json = info_json(cli.config.generate_info_json.keyboard)
|
||||
strip_info_json(kb_info_json)
|
||||
info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder)
|
||||
|
||||
# Display the results
|
||||
print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder))
|
||||
if cli.args.output:
|
||||
# Write to a file
|
||||
output_path = normpath(cli.args.output)
|
||||
|
||||
if output_path.exists():
|
||||
cli.log.warning('Overwriting output file %s', output_path)
|
||||
|
||||
output_path.write_text(info_json_text + '\n')
|
||||
cli.log.info('Wrote info.json to %s.', output_path)
|
||||
|
||||
else:
|
||||
# Display the results
|
||||
print(info_json_text)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import normpath
|
||||
@@ -29,17 +28,15 @@ def would_populate_layout_h(keyboard):
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.h for.')
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.h for.')
|
||||
@cli.subcommand('Used by the make system to generate keyboard.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
def generate_keyboard_h(cli):
|
||||
"""Generates the keyboard.h file.
|
||||
"""
|
||||
has_layout_h = would_populate_layout_h(cli.config.generate_keyboard_h.keyboard)
|
||||
has_layout_h = would_populate_layout_h(cli.args.keyboard)
|
||||
|
||||
# Build the layouts.h file.
|
||||
keyboard_h_lines = ['/* This file was generated by `qmk generate-keyboard-h`. Do not edit or copy.' ' */', '', '#pragma once', '#include "quantum.h"']
|
||||
keyboard_h_lines = ['/* This file was generated by `qmk generate-keyboard-h`. Do not edit or copy.', ' */', '', '#pragma once', '#include "quantum.h"']
|
||||
|
||||
if not has_layout_h:
|
||||
keyboard_h_lines.append('#pragma error("<keyboard>.h is only optional for data driven keyboards - kb.h == bad times")')
|
||||
|
||||
@@ -38,7 +38,7 @@ def generate_layouts(cli):
|
||||
kb_info_json = info_json(cli.config.generate_layouts.keyboard)
|
||||
|
||||
# Build the layouts.h file.
|
||||
layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.' ' */', '', '#pragma once']
|
||||
layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.', ' */', '', '#pragma once']
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
if 'direct' in kb_info_json['matrix_pins']:
|
||||
|
||||
@@ -5,11 +5,11 @@ from pathlib import Path
|
||||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import is_keyboard, normpath
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
@@ -26,7 +26,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
if key_type == 'array':
|
||||
if key_type in ['array', 'list']:
|
||||
return f'{rules_key} ?= {" ".join(rules_value)}'
|
||||
elif key_type == 'bool':
|
||||
return f'{rules_key} ?= {"on" if rules_value else "off"}'
|
||||
@@ -39,23 +39,21 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode")
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
|
||||
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
|
||||
@automagic_keyboard
|
||||
@automagic_keymap
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate rules.mk for.')
|
||||
@cli.argument('-km', '--keymap', arg_only=True, help='Keymap to generate rules.mk for.')
|
||||
@cli.subcommand('Used by the make system to generate rules.mk from info.json', hidden=True)
|
||||
def generate_rules_mk(cli):
|
||||
"""Generates a rules.mk file from info.json.
|
||||
"""
|
||||
if not cli.config.generate_rules_mk.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['info'].print_help()
|
||||
return False
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
if not is_keyboard(cli.config.generate_rules_mk.keyboard):
|
||||
cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard)
|
||||
return False
|
||||
|
||||
kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard))
|
||||
info_rules_map = json_load(Path('data/mappings/info_rules.json'))
|
||||
rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '']
|
||||
|
||||
@@ -76,6 +74,17 @@ def generate_rules_mk(cli):
|
||||
enabled = 'yes' if enabled else 'no'
|
||||
rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
|
||||
|
||||
# Set SPLIT_TRANSPORT, if needed
|
||||
if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom':
|
||||
rules_mk_lines.append('SPLIT_TRANSPORT ?= custom')
|
||||
|
||||
# Set CUSTOM_MATRIX, if needed
|
||||
if kb_info_json.get('matrix_pins', {}).get('custom'):
|
||||
if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
|
||||
rules_mk_lines.append('CUSTOM_MATRIX ?= lite')
|
||||
else:
|
||||
rules_mk_lines.append('CUSTOM_MATRIX ?= yes')
|
||||
|
||||
# Show the results
|
||||
rules_mk = '\n'.join(rules_mk_lines) + '\n'
|
||||
|
||||
|
||||
28
lib/python/qmk/cli/generate/version_h.py
Normal file
28
lib/python/qmk/cli/generate/version_h.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Used by the make system to generate version.h for use in code.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.commands import create_version_h
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('--skip-git', arg_only=True, action='store_true', help='Skip Git operations')
|
||||
@cli.argument('--skip-all', arg_only=True, action='store_true', help='Use placeholder values for all defines (implies --skip-git)')
|
||||
@cli.subcommand('Used by the make system to generate version.h for use in code', hidden=True)
|
||||
def generate_version_h(cli):
|
||||
"""Generates the version.h file.
|
||||
"""
|
||||
if cli.args.skip_all:
|
||||
cli.args.skip_git = True
|
||||
|
||||
version_h = create_version_h(cli.args.skip_git, cli.args.skip_all)
|
||||
|
||||
if cli.args.output:
|
||||
cli.args.output.write_text(version_h)
|
||||
|
||||
if not cli.args.quiet:
|
||||
cli.log.info('Wrote version.h to %s.', cli.args.output)
|
||||
else:
|
||||
print(version_h)
|
||||
@@ -24,19 +24,15 @@ def show_keymap(kb_info_json, title_caps=True):
|
||||
keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)
|
||||
|
||||
if keymap_path and keymap_path.suffix == '.json':
|
||||
if title_caps:
|
||||
cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap)
|
||||
else:
|
||||
cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap)
|
||||
|
||||
keymap_data = json.load(keymap_path.open(encoding='utf-8'))
|
||||
layout_name = keymap_data['layout']
|
||||
layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names
|
||||
|
||||
for layer_num, layer in enumerate(keymap_data['layers']):
|
||||
if title_caps:
|
||||
cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num)
|
||||
cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)
|
||||
else:
|
||||
cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num)
|
||||
cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)
|
||||
|
||||
print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer))
|
||||
|
||||
@@ -45,7 +41,7 @@ def show_layouts(kb_info_json, title_caps=True):
|
||||
"""Render the layouts with info.json labels.
|
||||
"""
|
||||
for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items():
|
||||
title = layout_name.title() if title_caps else layout_name
|
||||
title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}'
|
||||
cli.echo('{fg_cyan}%s{fg_reset}:', title)
|
||||
print(layout_art) # Avoid passing dirty data to cli.echo()
|
||||
|
||||
@@ -87,23 +83,12 @@ def print_friendly_output(kb_info_json):
|
||||
cli.echo('{fg_blue}Maintainer{fg_reset}: %s', kb_info_json['maintainer'])
|
||||
cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s', kb_info_json.get('keyboard_folder', 'Unknown'))
|
||||
cli.echo('{fg_blue}Layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
|
||||
if 'width' in kb_info_json and 'height' in kb_info_json:
|
||||
cli.echo('{fg_blue}Size{fg_reset}: %s x %s' % (kb_info_json['width'], kb_info_json['height']))
|
||||
cli.echo('{fg_blue}Processor{fg_reset}: %s', kb_info_json.get('processor', 'Unknown'))
|
||||
cli.echo('{fg_blue}Bootloader{fg_reset}: %s', kb_info_json.get('bootloader', 'Unknown'))
|
||||
if 'layout_aliases' in kb_info_json:
|
||||
aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()]
|
||||
cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),))
|
||||
|
||||
if cli.config.info.layouts:
|
||||
show_layouts(kb_info_json, True)
|
||||
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, True)
|
||||
|
||||
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
|
||||
show_keymap(kb_info_json, True)
|
||||
|
||||
|
||||
def print_text_output(kb_info_json):
|
||||
"""Print the info.json in a plain text format.
|
||||
@@ -124,6 +109,24 @@ def print_text_output(kb_info_json):
|
||||
show_keymap(kb_info_json, False)
|
||||
|
||||
|
||||
def print_dotted_output(kb_info_json, prefix=''):
|
||||
"""Print the info.json in a plain text format with dot-joined keys.
|
||||
"""
|
||||
for key in sorted(kb_info_json):
|
||||
new_prefix = f'{prefix}.{key}' if prefix else key
|
||||
|
||||
if key in ['parse_errors', 'parse_warnings']:
|
||||
continue
|
||||
elif key == 'layouts' and prefix == '':
|
||||
cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
|
||||
elif isinstance(kb_info_json[key], dict):
|
||||
print_dotted_output(kb_info_json[key], new_prefix)
|
||||
elif isinstance(kb_info_json[key], list):
|
||||
cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key]))))
|
||||
else:
|
||||
cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
|
||||
|
||||
|
||||
def print_parsed_rules_mk(keyboard_name):
|
||||
rules = rules_mk(keyboard_name)
|
||||
for k in sorted(rules.keys()):
|
||||
@@ -164,10 +167,22 @@ def info(cli):
|
||||
# Output in the requested format
|
||||
if cli.args.format == 'json':
|
||||
print(json.dumps(kb_info_json, cls=InfoJSONEncoder))
|
||||
return True
|
||||
elif cli.args.format == 'text':
|
||||
print_text_output(kb_info_json)
|
||||
print_dotted_output(kb_info_json)
|
||||
title_caps = False
|
||||
elif cli.args.format == 'friendly':
|
||||
print_friendly_output(kb_info_json)
|
||||
title_caps = True
|
||||
else:
|
||||
cli.log.error('Unknown format: %s', cli.args.format)
|
||||
return False
|
||||
|
||||
if cli.config.info.layouts:
|
||||
show_layouts(kb_info_json, title_caps)
|
||||
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, title_caps)
|
||||
|
||||
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
|
||||
show_keymap(kb_info_json, title_caps)
|
||||
|
||||
@@ -44,8 +44,6 @@ def kle2json(cli):
|
||||
'keyboard_name': kle.name,
|
||||
'url': '',
|
||||
'maintainer': 'qmk',
|
||||
'width': kle.columns,
|
||||
'height': kle.rows,
|
||||
'layouts': {
|
||||
'LAYOUT': {
|
||||
'layout': kle2qmk(kle)
|
||||
|
||||
@@ -4,7 +4,7 @@ from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.keyboard import keyboard_completer
|
||||
from qmk.keyboard import find_readme, keyboard_completer
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import is_keyboard, keyboard
|
||||
|
||||
@@ -31,7 +31,8 @@ def lint(cli):
|
||||
ok = True
|
||||
keyboard_path = keyboard(cli.config.lint.keyboard)
|
||||
keyboard_info = info_json(cli.config.lint.keyboard)
|
||||
readme_path = keyboard_path / 'readme.md'
|
||||
readme_path = find_readme(cli.config.lint.keyboard)
|
||||
missing_readme_path = keyboard_path / 'readme.md'
|
||||
|
||||
# Check for errors in the info.json
|
||||
if keyboard_info['parse_errors']:
|
||||
@@ -43,9 +44,9 @@ def lint(cli):
|
||||
cli.log.error('Warnings found when generating info.json (Strict mode enabled.)')
|
||||
|
||||
# Check for a readme.md and warn if it doesn't exist
|
||||
if not readme_path.exists():
|
||||
if not readme_path:
|
||||
ok = False
|
||||
cli.log.error('Missing %s', readme_path)
|
||||
cli.log.error('Missing %s', missing_readme_path)
|
||||
|
||||
# Keymap specific checks
|
||||
if cli.config.lint.keymap:
|
||||
|
||||
18
lib/python/qmk/cli/list/layouts.py
Normal file
18
lib/python/qmk/cli/list/layouts.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""List the keymaps for a specific keyboard
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.decorators import automagic_keyboard
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.info import info_json
|
||||
|
||||
|
||||
@cli.argument("-kb", "--keyboard", type=keyboard_folder, completer=keyboard_completer, help="Specify keyboard name. Example: monarch")
|
||||
@cli.subcommand("List the layouts for a specific keyboard")
|
||||
@automagic_keyboard
|
||||
def list_layouts(cli):
|
||||
"""List the layouts for a specific keyboard
|
||||
"""
|
||||
info_data = info_json(cli.config.list_layouts.keyboard)
|
||||
for name in sorted(info_data.get('community_layouts', [])):
|
||||
print(name)
|
||||
@@ -10,7 +10,7 @@ from subprocess import DEVNULL
|
||||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.commands import _find_make
|
||||
from qmk.commands import _find_make, get_make_parallel_args
|
||||
import qmk.keyboard
|
||||
import qmk.keymap
|
||||
|
||||
@@ -28,7 +28,7 @@ def _is_split(keyboard_name):
|
||||
return True if 'SPLIT_KEYBOARD' in rules_mk and rules_mk['SPLIT_KEYBOARD'].lower() == 'yes' else False
|
||||
|
||||
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
|
||||
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
|
||||
@cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
|
||||
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on the supplied value in rules.mk. Supported format is 'SPLIT_KEYBOARD=yes'. May be passed multiple times.")
|
||||
@cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.")
|
||||
@@ -80,7 +80,7 @@ all: {keyboard_safe}_binary
|
||||
)
|
||||
# yapf: enable
|
||||
|
||||
cli.run([make_cmd, '-j', str(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
|
||||
cli.run([make_cmd, *get_make_parallel_args(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
|
||||
|
||||
# Check for failures
|
||||
failures = [f for f in builddir.glob(f'failed.log.{os.getpid()}.*')]
|
||||
|
||||
@@ -1,11 +1,142 @@
|
||||
"""This script automates the creation of keyboards.
|
||||
"""This script automates the creation of new keyboard directories using a starter template.
|
||||
"""
|
||||
from datetime import date
|
||||
import fileinput
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from qmk.commands import git_get_username
|
||||
import qmk.path
|
||||
from milc import cli
|
||||
from milc.questions import choice, question
|
||||
|
||||
KEYBOARD_TYPES = ['avr', 'ps2avrgb']
|
||||
|
||||
|
||||
@cli.subcommand('Creates a new keyboard')
|
||||
def new_keyboard(cli):
|
||||
"""Creates a new keyboard
|
||||
def keyboard_name(name):
|
||||
"""Callable for argparse validation.
|
||||
"""
|
||||
# TODO: replace this bodge to the existing script
|
||||
cli.run(['util/new_keyboard.sh'], stdin=None, capture_output=False)
|
||||
if not validate_keyboard_name(name):
|
||||
raise ValueError
|
||||
return name
|
||||
|
||||
|
||||
def validate_keyboard_name(name):
|
||||
"""Returns True if the given keyboard name contains only lowercase a-z, 0-9 and underscore characters.
|
||||
"""
|
||||
regex = re.compile(r'^[a-z0-9][a-z0-9/_]+$')
|
||||
return bool(regex.match(name))
|
||||
|
||||
|
||||
@cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name)
|
||||
@cli.argument('-t', '--type', help='Specify the keyboard type', arg_only=True, choices=KEYBOARD_TYPES)
|
||||
@cli.argument('-u', '--username', help='Specify your username (default from Git config)', arg_only=True)
|
||||
@cli.subcommand('Creates a new keyboard directory')
|
||||
def new_keyboard(cli):
|
||||
"""Creates a new keyboard.
|
||||
"""
|
||||
cli.log.info('{style_bright}Generating a new QMK keyboard directory{style_normal}')
|
||||
cli.echo('')
|
||||
|
||||
# Get keyboard name
|
||||
new_keyboard_name = None
|
||||
while not new_keyboard_name:
|
||||
new_keyboard_name = cli.args.keyboard if cli.args.keyboard else question('Keyboard Name:')
|
||||
if not validate_keyboard_name(new_keyboard_name):
|
||||
cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.')
|
||||
|
||||
# Exit if passed by arg
|
||||
if cli.args.keyboard:
|
||||
return False
|
||||
|
||||
new_keyboard_name = None
|
||||
continue
|
||||
|
||||
keyboard_path = qmk.path.keyboard(new_keyboard_name)
|
||||
if keyboard_path.exists():
|
||||
cli.log.error(f'Keyboard {{fg_cyan}}{new_keyboard_name}{{fg_reset}} already exists! Please choose a different name.')
|
||||
|
||||
# Exit if passed by arg
|
||||
if cli.args.keyboard:
|
||||
return False
|
||||
|
||||
new_keyboard_name = None
|
||||
|
||||
# Get keyboard type
|
||||
keyboard_type = cli.args.type if cli.args.type else choice('Keyboard Type:', KEYBOARD_TYPES, default=0)
|
||||
|
||||
# Get username
|
||||
user_name = None
|
||||
while not user_name:
|
||||
user_name = question('Your Name:', default=find_user_name())
|
||||
|
||||
if not user_name:
|
||||
cli.log.error('You didn\'t provide a username, and we couldn\'t find one set in your QMK or Git configs. Please try again.')
|
||||
|
||||
# Exit if passed by arg
|
||||
if cli.args.username:
|
||||
return False
|
||||
|
||||
# Copy all the files
|
||||
copy_templates(keyboard_type, keyboard_path)
|
||||
|
||||
# Replace all the placeholders
|
||||
keyboard_basename = keyboard_path.name
|
||||
replacements = [
|
||||
('%YEAR%', str(date.today().year)),
|
||||
('%KEYBOARD%', keyboard_basename),
|
||||
('%YOUR_NAME%', user_name),
|
||||
]
|
||||
filenames = [
|
||||
keyboard_path / 'config.h',
|
||||
keyboard_path / 'info.json',
|
||||
keyboard_path / 'readme.md',
|
||||
keyboard_path / f'{keyboard_basename}.c',
|
||||
keyboard_path / f'{keyboard_basename}.h',
|
||||
keyboard_path / 'keymaps/default/readme.md',
|
||||
keyboard_path / 'keymaps/default/keymap.c',
|
||||
]
|
||||
replace_placeholders(replacements, filenames)
|
||||
|
||||
cli.echo('')
|
||||
cli.log.info(f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{new_keyboard_name}{{fg_green}}.{{fg_reset}}')
|
||||
cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}{keyboard_path}{{fg_reset}},')
|
||||
cli.log.info('or open the directory in your preferred text editor.')
|
||||
|
||||
|
||||
def find_user_name():
|
||||
if cli.args.username:
|
||||
return cli.args.username
|
||||
elif cli.config.user.name:
|
||||
return cli.config.user.name
|
||||
else:
|
||||
return git_get_username()
|
||||
|
||||
|
||||
def copy_templates(keyboard_type, keyboard_path):
|
||||
"""Copies the template files from data/templates to the new keyboard directory.
|
||||
"""
|
||||
template_base_path = Path('data/templates')
|
||||
keyboard_basename = keyboard_path.name
|
||||
|
||||
cli.log.info('Copying base template files...')
|
||||
shutil.copytree(template_base_path / 'base', keyboard_path)
|
||||
|
||||
cli.log.info(f'Copying {{fg_cyan}}{keyboard_type}{{fg_reset}} template files...')
|
||||
shutil.copytree(template_base_path / keyboard_type, keyboard_path, dirs_exist_ok=True)
|
||||
|
||||
cli.log.info(f'Renaming {{fg_cyan}}keyboard.[ch]{{fg_reset}} to {{fg_cyan}}{keyboard_basename}.[ch]{{fg_reset}}...')
|
||||
shutil.move(keyboard_path / 'keyboard.c', keyboard_path / f'{keyboard_basename}.c')
|
||||
shutil.move(keyboard_path / 'keyboard.h', keyboard_path / f'{keyboard_basename}.h')
|
||||
|
||||
|
||||
def replace_placeholders(replacements, filenames):
|
||||
"""Replaces the given placeholders in each template file.
|
||||
"""
|
||||
for replacement in replacements:
|
||||
cli.log.info(f'Replacing {{fg_cyan}}{replacement[0]}{{fg_reset}} with {{fg_cyan}}{replacement[1]}{{fg_reset}}...')
|
||||
|
||||
with fileinput.input(files=filenames, inplace=True) as file:
|
||||
for line in file:
|
||||
print(line.replace(replacement[0], replacement[1]), end='')
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
"""Format python code according to QMK's style.
|
||||
"""Point people to the new command name.
|
||||
"""
|
||||
from subprocess import CalledProcessError, DEVNULL
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
|
||||
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True)
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.")
|
||||
@cli.subcommand('Pointer to the new command name: qmk format-python.', hidden=False if cli.config.user.developer else True)
|
||||
def pyformat(cli):
|
||||
"""Format python code according to QMK's style.
|
||||
"""Pointer to the new command name: qmk format-python.
|
||||
"""
|
||||
edit = '--diff' if cli.args.dry_run else '--in-place'
|
||||
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python']
|
||||
try:
|
||||
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
|
||||
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.')
|
||||
return True
|
||||
cli.log.warning('"qmk pyformat" has been renamed to "qmk format-python". Please use the new command in the future.')
|
||||
argv = [sys.executable, *sys.argv]
|
||||
argv[argv.index('pyformat')] = 'format-python'
|
||||
script_path = Path(argv[1])
|
||||
script_path_exe = Path(f'{argv[1]}.exe')
|
||||
|
||||
except CalledProcessError:
|
||||
if cli.args.dry_run:
|
||||
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!')
|
||||
else:
|
||||
cli.log.error('Error formatting python code!')
|
||||
if not script_path.exists() and script_path_exe.exists():
|
||||
# For reasons I don't understand ".exe" is stripped from the script name on windows.
|
||||
argv[1] = str(script_path_exe)
|
||||
|
||||
return False
|
||||
return cli.run(argv, capture_output=False).returncode
|
||||
|
||||
Reference in New Issue
Block a user