Move everything to Python 3.6 (#8835)
parent
5a8f59503e
commit
66d94dc22a
2
bin/qmk
2
bin/qmk
|
@ -35,7 +35,7 @@ def _check_modules(requirements):
|
||||||
|
|
||||||
if not find_spec(module['import']):
|
if not find_spec(module['import']):
|
||||||
print('Could not find module %s!' % module['name'])
|
print('Could not find module %s!' % module['name'])
|
||||||
print('Please run `python3 -m pip install -r %s` to install required python dependencies.' % str(qmk_dir / requirements))
|
print('Please run `python3 -m pip install -r %s` to install required python dependencies.' % (qmk_dir / requirements,))
|
||||||
if developer:
|
if developer:
|
||||||
print('You can also turn off developer mode: qmk config user.developer=None')
|
print('You can also turn off developer mode: qmk config user.developer=None')
|
||||||
print()
|
print()
|
||||||
|
|
|
@ -6,7 +6,7 @@ The QMK CLI makes building and working with QMK keyboards easier. We have provid
|
||||||
|
|
||||||
### Requirements :id=requirements
|
### Requirements :id=requirements
|
||||||
|
|
||||||
The CLI requires Python 3.5 or greater. We try to keep the number of requirements small but you will also need to install the packages listed in [`requirements.txt`](https://github.com/qmk/qmk_firmware/blob/master/requirements.txt). These are installed automatically when you install the QMK CLI.
|
QMK requires Python 3.6 or greater. We try to keep the number of requirements small but you will also need to install the packages listed in [`requirements.txt`](https://github.com/qmk/qmk_firmware/blob/master/requirements.txt). These are installed automatically when you install the QMK CLI.
|
||||||
|
|
||||||
### Install Using Homebrew (macOS, some Linux) :id=install-using-homebrew
|
### Install Using Homebrew (macOS, some Linux) :id=install-using-homebrew
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build
|
||||||
|
|
||||||
### Install Using easy_install or pip :id=install-using-easy_install-or-pip
|
### Install Using easy_install or pip :id=install-using-easy_install-or-pip
|
||||||
|
|
||||||
If your system is not listed above you can install QMK manually. First ensure that you have python 3.5 (or later) installed and have installed pip. Then install QMK with this command:
|
If your system is not listed above you can install QMK manually. First ensure that you have python 3.6 (or later) installed and have installed pip. Then install QMK with this command:
|
||||||
|
|
||||||
```
|
```
|
||||||
pip3 install qmk
|
pip3 install qmk
|
||||||
|
|
|
@ -44,7 +44,7 @@ def hello(cli):
|
||||||
|
|
||||||
First we import the `cli` object from `milc`. This is how we interact with the user and control the script's behavior. We use `@cli.argument()` to define a command line flag, `--name`. This also creates a configuration variable named `hello.name` (and the corresponding `user.name`) which the user can set so they don't have to specify the argument. The `cli.subcommand()` decorator designates this function as a subcommand. The name of the subcommand will be taken from the name of the function.
|
First we import the `cli` object from `milc`. This is how we interact with the user and control the script's behavior. We use `@cli.argument()` to define a command line flag, `--name`. This also creates a configuration variable named `hello.name` (and the corresponding `user.name`) which the user can set so they don't have to specify the argument. The `cli.subcommand()` decorator designates this function as a subcommand. The name of the subcommand will be taken from the name of the function.
|
||||||
|
|
||||||
Once inside our function we find a typical "Hello, World!" program. We use `cli.log` to access the underlying [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects), whose behavior is user controllable. We also access the value for name supplied by the user as `cli.config.hello.name`. The value for `cli.config.hello.name` will be determined by looking at the `--name` argument supplied by the user, if not provided it will use the value in the `qmk.ini` config file, and if neither of those is provided it will fall back to the default supplied in the `cli.argument()` decorator.
|
Once inside our function we find a typical "Hello, World!" program. We use `cli.log` to access the underlying [Logger Object](https://docs.python.org/3.6/library/logging.html#logger-objects), whose behavior is user controllable. We also access the value for name supplied by the user as `cli.config.hello.name`. The value for `cli.config.hello.name` will be determined by looking at the `--name` argument supplied by the user, if not provided it will use the value in the `qmk.ini` config file, and if neither of those is provided it will fall back to the default supplied in the `cli.argument()` decorator.
|
||||||
|
|
||||||
# User Interaction
|
# User Interaction
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ There are two main methods for outputting text in a subcommand- `cli.log` and `c
|
||||||
|
|
||||||
You can use special tokens to colorize your text, to make it easier to understand the output of your program. See [Colorizing Text](#colorizing-text) below.
|
You can use special tokens to colorize your text, to make it easier to understand the output of your program. See [Colorizing Text](#colorizing-text) below.
|
||||||
|
|
||||||
Both of these methods support built-in string formatting using python's [printf style string format operations](https://docs.python.org/3.5/library/stdtypes.html#old-string-formatting). You can use tokens such as `%s` and `%d` within your text strings then pass the values as arguments. See our Hello, World program above for an example.
|
Both of these methods support built-in string formatting using python's [printf style string format operations](https://docs.python.org/3.6/library/stdtypes.html#old-string-formatting). You can use tokens such as `%s` and `%d` within your text strings then pass the values as arguments. See our Hello, World program above for an example.
|
||||||
|
|
||||||
You should never use the format operator (`%`) directly, always pass values as arguments.
|
You should never use the format operator (`%`) directly, always pass values as arguments.
|
||||||
|
|
||||||
### Logging (`cli.log`)
|
### Logging (`cli.log`)
|
||||||
|
|
||||||
The `cli.log` object gives you access to a [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects). We have configured our log output to show the user a nice emoji for each log level (or the log level name if their terminal does not support unicode.) This way the user can tell at a glance which messages are most important when something goes wrong.
|
The `cli.log` object gives you access to a [Logger Object](https://docs.python.org/3.6/library/logging.html#logger-objects). We have configured our log output to show the user a nice emoji for each log level (or the log level name if their terminal does not support unicode.) This way the user can tell at a glance which messages are most important when something goes wrong.
|
||||||
|
|
||||||
The default log level is `INFO`. If the user runs `qmk -v <subcommand>` the default log level will be set to `DEBUG`.
|
The default log level is `INFO`. If the user runs `qmk -v <subcommand>` the default log level will be set to `DEBUG`.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Most of our style follows PEP8 with some local modifications to make things less nit-picky.
|
Most of our style follows PEP8 with some local modifications to make things less nit-picky.
|
||||||
|
|
||||||
* We target Python 3.5 for compatability with all supported platforms.
|
* We target Python 3.6 for compatability with all supported platforms.
|
||||||
* We indent using four (4) spaces (soft tabs)
|
* We indent using four (4) spaces (soft tabs)
|
||||||
* We encourage liberal use of comments
|
* We encourage liberal use of comments
|
||||||
* Think of them as a story describing the feature
|
* Think of them as a story describing the feature
|
||||||
|
@ -317,7 +317,7 @@ At the time of this writing our tests are not very comprehensive. Looking at the
|
||||||
|
|
||||||
## Integration Tests
|
## Integration Tests
|
||||||
|
|
||||||
Integration tests can be found in `lib/python/qmk/tests/test_cli_commands.py`. This is where CLI commands are actually run and their overall behavior is verified. We use [`subprocess`](https://docs.python.org/3.5/library/subprocess.html#module-subprocess) to launch each CLI command and a combination of checking output and returncode to determine if the right thing happened.
|
Integration tests can be found in `lib/python/qmk/tests/test_cli_commands.py`. This is where CLI commands are actually run and their overall behavior is verified. We use [`subprocess`](https://docs.python.org/3.6/library/subprocess.html#module-subprocess) to launch each CLI command and a combination of checking output and returncode to determine if the right thing happened.
|
||||||
|
|
||||||
## Unit Tests
|
## Unit Tests
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
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 sys
|
||||||
|
|
||||||
from milc import cli
|
from milc import cli
|
||||||
|
|
||||||
from . import cformat
|
from . import cformat
|
||||||
|
@ -19,5 +21,6 @@ from . import new
|
||||||
from . import pyformat
|
from . import pyformat
|
||||||
from . import pytest
|
from . import pytest
|
||||||
|
|
||||||
if not hasattr(cli, 'config_source'):
|
if sys.version_info[0] != 3 or sys.version_info[1] < 6:
|
||||||
cli.log.warning("Your QMK CLI is out of date. Please upgrade with `pip3 install --upgrade qmk` or by using your package manager.")
|
cli.log.error('Your Python is too old! Please upgrade to Python 3.6 or later.')
|
||||||
|
exit(127)
|
||||||
|
|
|
@ -22,9 +22,8 @@ def cformat_run(files, all_files):
|
||||||
cli.log.warn('No changes detected. Use "qmk cformat -a" to format all files')
|
cli.log.warn('No changes detected. Use "qmk cformat -a" to format all files')
|
||||||
return False
|
return False
|
||||||
if files and all_files:
|
if files and all_files:
|
||||||
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(cli.args.files))
|
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(files))
|
||||||
# 3.6+: Can remove the str casting, python will cast implicitly
|
subprocess.run(clang_format + [file for file in files], check=True)
|
||||||
subprocess.run(clang_format + [str(file) for file in files], check=True)
|
|
||||||
cli.log.info('Successfully formatted the C code.')
|
cli.log.info('Successfully formatted the C code.')
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
|
|
|
@ -135,16 +135,15 @@ def check_udev_rules():
|
||||||
}
|
}
|
||||||
|
|
||||||
if udev_dir.exists():
|
if udev_dir.exists():
|
||||||
udev_rules = [str(rule_file) for rule_file in udev_dir.glob('*.rules')]
|
udev_rules = [rule_file for rule_file in udev_dir.glob('*.rules')]
|
||||||
current_rules = set()
|
current_rules = set()
|
||||||
|
|
||||||
# Collect all rules from the config files
|
# Collect all rules from the config files
|
||||||
for rule_file in udev_rules:
|
for rule_file in udev_rules:
|
||||||
with open(rule_file, "r") as fd:
|
for line in rule_file.read_text().split('\n'):
|
||||||
for line in fd.readlines():
|
line = line.strip()
|
||||||
line = line.strip()
|
if not line.startswith("#") and len(line):
|
||||||
if not line.startswith("#") and len(line):
|
current_rules.add(line)
|
||||||
current_rules.add(line)
|
|
||||||
|
|
||||||
# Check if the desired rules are among the currently present rules
|
# Check if the desired rules are among the currently present rules
|
||||||
for bootloader, rules in desired_rules.items():
|
for bootloader, rules in desired_rules.items():
|
||||||
|
|
|
@ -10,29 +10,27 @@ import qmk.path
|
||||||
|
|
||||||
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
|
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.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('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||||
@cli.argument('filename', arg_only=True, help='Configurator JSON file')
|
@cli.argument('filename', type=qmk.path.normpath, arg_only=True, help='Configurator JSON file')
|
||||||
@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
|
@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
|
||||||
def json2c(cli):
|
def json2c(cli):
|
||||||
"""Generate a keymap.c from a configurator export.
|
"""Generate a keymap.c from a configurator export.
|
||||||
|
|
||||||
This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
|
This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
|
||||||
"""
|
"""
|
||||||
cli.args.filename = qmk.path.normpath(cli.args.filename)
|
|
||||||
|
|
||||||
# Error checking
|
# Error checking
|
||||||
if not cli.args.filename.exists():
|
if not cli.args.filename.exists():
|
||||||
cli.log.error('JSON file does not exist!')
|
cli.log.error('JSON file does not exist!')
|
||||||
cli.print_usage()
|
cli.print_usage()
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
if str(cli.args.filename) == '-':
|
if cli.args.filename.name == '-':
|
||||||
# TODO(skullydazed/anyone): Read file contents from STDIN
|
# TODO(skullydazed/anyone): Read file contents from STDIN
|
||||||
cli.log.error('Reading from STDIN is not (yet) supported.')
|
cli.log.error('Reading from STDIN is not (yet) supported.')
|
||||||
cli.print_usage()
|
cli.print_usage()
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Environment processing
|
# Environment processing
|
||||||
if cli.args.output == ('-'):
|
if cli.args.output.name == ('-'):
|
||||||
cli.args.output = None
|
cli.args.output = None
|
||||||
|
|
||||||
# Parse the configurator json
|
# Parse the configurator json
|
||||||
|
|
|
@ -37,12 +37,12 @@ def kle2json(cli):
|
||||||
file_path = Path(os.environ['ORIG_CWD'], cli.args.filename)
|
file_path = Path(os.environ['ORIG_CWD'], cli.args.filename)
|
||||||
# Check for valid file_path for more graceful failure
|
# Check for valid file_path for more graceful failure
|
||||||
if not file_path.exists():
|
if not file_path.exists():
|
||||||
return cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', str(file_path))
|
return cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path)
|
||||||
out_path = file_path.parent
|
out_path = file_path.parent
|
||||||
raw_code = file_path.open().read()
|
raw_code = file_path.open().read()
|
||||||
# Check if info.json exists, allow overwrite with force
|
# Check if info.json exists, allow overwrite with force
|
||||||
if Path(out_path, "info.json").exists() and not cli.args.force:
|
if Path(out_path, "info.json").exists() and not cli.args.force:
|
||||||
cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', str(out_path))
|
cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path)
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
# Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
|
# Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
|
||||||
|
@ -69,7 +69,7 @@ def kle2json(cli):
|
||||||
# Replace layout in keyboard json
|
# Replace layout in keyboard json
|
||||||
keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout)
|
keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout)
|
||||||
# Write our info.json
|
# Write our info.json
|
||||||
file = open(str(out_path) + "/info.json", "w")
|
file = open(out_path + "/info.json", "w")
|
||||||
file.write(keyboard)
|
file.write(keyboard)
|
||||||
file.close()
|
file.close()
|
||||||
cli.log.info('Wrote out {fg_cyan}%s/info.json', str(out_path))
|
cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path)
|
||||||
|
|
|
@ -40,7 +40,7 @@ def new_keymap(cli):
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# create user directory with default keymap files
|
# create user directory with default keymap files
|
||||||
shutil.copytree(str(keymap_path_default), str(keymap_path_new), symlinks=True)
|
shutil.copytree(keymap_path_default, keymap_path_new, symlinks=True)
|
||||||
|
|
||||||
# end message to user
|
# end message to user
|
||||||
cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new)
|
cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"""Functions that help you work with QMK keymaps.
|
"""Functions that help you work with QMK keymaps.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import qmk.path
|
import qmk.path
|
||||||
|
@ -127,7 +126,7 @@ def list_keymaps(keyboard_name):
|
||||||
while kb_path != keyboards_dir:
|
while kb_path != keyboards_dir:
|
||||||
keymaps_dir = kb_path / "keymaps"
|
keymaps_dir = kb_path / "keymaps"
|
||||||
if keymaps_dir.exists():
|
if keymaps_dir.exists():
|
||||||
names = names.union([keymap for keymap in os.listdir(str(keymaps_dir)) if (keymaps_dir / keymap / "keymap.c").is_file()])
|
names = names.union([keymap for keymap in keymaps_dir.iterdir() if (keymaps_dir / keymap / "keymap.c").is_file()])
|
||||||
kb_path = kb_path.parent
|
kb_path = kb_path.parent
|
||||||
|
|
||||||
# if community layouts are supported, get them
|
# if community layouts are supported, get them
|
||||||
|
@ -135,6 +134,6 @@ def list_keymaps(keyboard_name):
|
||||||
for layout in rules_mk["LAYOUTS"].split():
|
for layout in rules_mk["LAYOUTS"].split():
|
||||||
cl_path = Path.cwd() / "layouts" / "community" / layout
|
cl_path = Path.cwd() / "layouts" / "community" / layout
|
||||||
if cl_path.exists():
|
if cl_path.exists():
|
||||||
names = names.union([keymap for keymap in os.listdir(str(cl_path)) if (cl_path / keymap / "keymap.c").is_file()])
|
names = names.union([keymap for keymap in cl_path.iterdir() if (cl_path / keymap / "keymap.c").is_file()])
|
||||||
|
|
||||||
return sorted(names)
|
return sorted(names)
|
||||||
|
|
Loading…
Reference in New Issue