2020-02-17 19:42:11 +00:00
|
|
|
"""Helper functions for commands.
|
2019-10-05 06:38:34 +00:00
|
|
|
"""
|
2020-03-29 12:29:44 +00:00
|
|
|
import os
|
2021-07-10 15:04:50 +00:00
|
|
|
import sys
|
2022-10-19 10:29:44 +00:00
|
|
|
import json
|
2020-04-13 16:44:27 +00:00
|
|
|
import shutil
|
2022-03-18 17:03:54 +00:00
|
|
|
from itertools import islice
|
2021-01-16 23:13:04 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from milc import cli
|
2022-02-28 20:02:39 +00:00
|
|
|
import jsonschema
|
2020-02-17 19:42:11 +00:00
|
|
|
|
2023-06-20 22:41:49 +00:00
|
|
|
from qmk.constants import INTERMEDIATE_OUTPUT_PREFIX
|
2022-02-28 20:02:39 +00:00
|
|
|
from qmk.json_schema import json_load, validate
|
2021-01-16 23:13:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _find_make():
|
|
|
|
"""Returns the correct make command for this environment.
|
|
|
|
"""
|
|
|
|
make_cmd = os.environ.get('MAKE')
|
|
|
|
|
|
|
|
if not make_cmd:
|
|
|
|
make_cmd = 'gmake' if shutil.which('gmake') else 'make'
|
2019-10-05 06:38:34 +00:00
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
return make_cmd
|
2019-11-16 07:10:19 +00:00
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
def create_make_target(target, dry_run=False, parallel=1, **env_vars):
|
2021-05-09 10:57:49 +00:00
|
|
|
"""Create a make command
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
|
|
target
|
|
|
|
Usually a make rule, such as 'clean' or 'all'.
|
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
dry_run
|
|
|
|
make -n -- don't actually build
|
|
|
|
|
2021-05-09 10:57:49 +00:00
|
|
|
parallel
|
|
|
|
The number of make jobs to run in parallel
|
|
|
|
|
|
|
|
**env_vars
|
|
|
|
Environment variables to be passed to make.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A command that can be run to make the specified keyboard and keymap
|
|
|
|
"""
|
|
|
|
env = []
|
|
|
|
make_cmd = _find_make()
|
|
|
|
|
|
|
|
for key, value in env_vars.items():
|
|
|
|
env.append(f'{key}={value}')
|
|
|
|
|
2023-03-21 08:25:08 +00:00
|
|
|
if cli.config.general.verbose:
|
|
|
|
env.append('VERBOSE=true')
|
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
return [make_cmd, *(['-n'] if dry_run else []), *get_make_parallel_args(parallel), *env, target]
|
2021-05-09 10:57:49 +00:00
|
|
|
|
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
def create_make_command(keyboard, keymap, target=None, dry_run=False, parallel=1, **env_vars):
|
2019-10-05 06:38:34 +00:00
|
|
|
"""Create a make compile command
|
|
|
|
|
|
|
|
Args:
|
2020-02-17 19:42:11 +00:00
|
|
|
|
2019-10-05 06:38:34 +00:00
|
|
|
keyboard
|
|
|
|
The path of the keyboard, for example 'plank'
|
|
|
|
|
|
|
|
keymap
|
|
|
|
The name of the keymap, for example 'algernon'
|
|
|
|
|
|
|
|
target
|
|
|
|
Usually a bootloader.
|
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
dry_run
|
|
|
|
make -n -- don't actually build
|
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
parallel
|
|
|
|
The number of make jobs to run in parallel
|
|
|
|
|
|
|
|
**env_vars
|
|
|
|
Environment variables to be passed to make.
|
|
|
|
|
2019-10-05 06:38:34 +00:00
|
|
|
Returns:
|
2020-02-17 19:42:11 +00:00
|
|
|
|
2019-10-05 06:38:34 +00:00
|
|
|
A command that can be run to make the specified keyboard and keymap
|
|
|
|
"""
|
2020-02-17 19:42:11 +00:00
|
|
|
make_args = [keyboard, keymap]
|
2019-10-05 06:38:34 +00:00
|
|
|
|
2020-02-17 19:42:11 +00:00
|
|
|
if target:
|
|
|
|
make_args.append(target)
|
2019-11-16 07:10:19 +00:00
|
|
|
|
2021-09-16 04:59:57 +00:00
|
|
|
return create_make_target(':'.join(make_args), dry_run=dry_run, parallel=parallel, **env_vars)
|
2019-11-16 07:10:19 +00:00
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
|
2021-08-17 22:46:59 +00:00
|
|
|
def get_make_parallel_args(parallel=1):
|
|
|
|
"""Returns the arguments for running the specified number of parallel jobs.
|
|
|
|
"""
|
|
|
|
parallel_args = []
|
|
|
|
|
|
|
|
if int(parallel) <= 0:
|
|
|
|
# 0 or -1 means -j without argument (unlimited jobs)
|
|
|
|
parallel_args.append('--jobs')
|
|
|
|
else:
|
|
|
|
parallel_args.append('--jobs=' + str(parallel))
|
|
|
|
|
|
|
|
if int(parallel) != 1:
|
|
|
|
# If more than 1 job is used, synchronize parallel output by target
|
|
|
|
parallel_args.append('--output-sync=target')
|
|
|
|
|
|
|
|
return parallel_args
|
|
|
|
|
|
|
|
|
2022-10-20 13:35:27 +00:00
|
|
|
def compile_configurator_json(user_keymap, bootloader=None, parallel=1, clean=False, **env_vars):
|
2021-01-16 23:13:04 +00:00
|
|
|
"""Convert a configurator export JSON file into a C file and then compile it.
|
2019-10-05 06:38:34 +00:00
|
|
|
|
|
|
|
Args:
|
2020-02-17 19:42:11 +00:00
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
user_keymap
|
|
|
|
A deserialized keymap export
|
2019-10-05 06:38:34 +00:00
|
|
|
|
|
|
|
bootloader
|
|
|
|
A bootloader to flash
|
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
parallel
|
|
|
|
The number of make jobs to run in parallel
|
|
|
|
|
2019-10-05 06:38:34 +00:00
|
|
|
Returns:
|
2020-02-17 19:42:11 +00:00
|
|
|
|
2019-10-05 06:38:34 +00:00
|
|
|
A command to run to compile and flash the C file.
|
|
|
|
"""
|
2022-02-28 20:02:39 +00:00
|
|
|
# In case the user passes a keymap.json from a keymap directory directly to the CLI.
|
|
|
|
# e.g.: qmk compile - < keyboards/clueboard/california/keymaps/default/keymap.json
|
|
|
|
user_keymap["keymap"] = user_keymap.get("keymap", "default_json")
|
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
keyboard_filesafe = user_keymap['keyboard'].replace('/', '_')
|
|
|
|
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
|
2023-06-20 22:41:49 +00:00
|
|
|
intermediate_output = Path(f'{INTERMEDIATE_OUTPUT_PREFIX}{keyboard_filesafe}_{user_keymap["keymap"]}')
|
|
|
|
keymap_dir = intermediate_output / 'src'
|
2022-10-19 10:29:44 +00:00
|
|
|
keymap_json = keymap_dir / 'keymap.json'
|
2021-01-16 23:13:04 +00:00
|
|
|
|
2022-10-20 13:35:27 +00:00
|
|
|
if clean:
|
2023-06-20 22:41:49 +00:00
|
|
|
if intermediate_output.exists():
|
|
|
|
shutil.rmtree(intermediate_output)
|
2022-10-20 13:35:27 +00:00
|
|
|
|
|
|
|
# begin with making the deepest folder in the tree
|
2021-01-16 23:13:04 +00:00
|
|
|
keymap_dir.mkdir(exist_ok=True, parents=True)
|
2022-10-20 13:35:27 +00:00
|
|
|
|
|
|
|
# Compare minified to ensure consistent comparison
|
|
|
|
new_content = json.dumps(user_keymap, separators=(',', ':'))
|
|
|
|
if keymap_json.exists():
|
|
|
|
old_content = json.dumps(json.loads(keymap_json.read_text(encoding='utf-8')), separators=(',', ':'))
|
|
|
|
if old_content == new_content:
|
|
|
|
new_content = None
|
|
|
|
|
|
|
|
# Write the keymap.json file if different
|
|
|
|
if new_content:
|
|
|
|
keymap_json.write_text(new_content, encoding='utf-8')
|
2019-10-05 06:38:34 +00:00
|
|
|
|
|
|
|
# Return a command that can be run to make the keymap and flash if given
|
2021-01-16 23:13:04 +00:00
|
|
|
verbose = 'true' if cli.config.general.verbose else 'false'
|
|
|
|
color = 'true' if cli.config.general.color else 'false'
|
2021-01-17 17:33:29 +00:00
|
|
|
make_command = [_find_make()]
|
|
|
|
|
|
|
|
if not cli.config.general.verbose:
|
|
|
|
make_command.append('-s')
|
|
|
|
|
|
|
|
make_command.extend([
|
2021-08-17 22:46:59 +00:00
|
|
|
*get_make_parallel_args(parallel),
|
2021-01-16 23:13:04 +00:00
|
|
|
'-r',
|
|
|
|
'-R',
|
|
|
|
'-f',
|
2022-02-10 17:45:51 +00:00
|
|
|
'builddefs/build_keyboard.mk',
|
2021-01-17 17:33:29 +00:00
|
|
|
])
|
2021-01-16 23:13:04 +00:00
|
|
|
|
2021-02-01 19:55:35 +00:00
|
|
|
if bootloader:
|
|
|
|
make_command.append(bootloader)
|
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
make_command.extend([
|
|
|
|
f'KEYBOARD={user_keymap["keyboard"]}',
|
|
|
|
f'KEYMAP={user_keymap["keymap"]}',
|
|
|
|
f'KEYBOARD_FILESAFE={keyboard_filesafe}',
|
|
|
|
f'TARGET={target}',
|
2023-06-20 22:41:49 +00:00
|
|
|
f'INTERMEDIATE_OUTPUT={intermediate_output}',
|
|
|
|
f'MAIN_KEYMAP_PATH_1={intermediate_output}',
|
|
|
|
f'MAIN_KEYMAP_PATH_2={intermediate_output}',
|
|
|
|
f'MAIN_KEYMAP_PATH_3={intermediate_output}',
|
|
|
|
f'MAIN_KEYMAP_PATH_4={intermediate_output}',
|
|
|
|
f'MAIN_KEYMAP_PATH_5={intermediate_output}',
|
2022-10-19 10:29:44 +00:00
|
|
|
f'KEYMAP_JSON={keymap_json}',
|
2021-01-16 23:13:04 +00:00
|
|
|
f'KEYMAP_PATH={keymap_dir}',
|
|
|
|
f'VERBOSE={verbose}',
|
|
|
|
f'COLOR={color}',
|
|
|
|
'SILENT=false',
|
2021-08-29 23:50:22 +00:00
|
|
|
'QMK_BIN="qmk"',
|
2021-01-16 23:13:04 +00:00
|
|
|
])
|
|
|
|
|
2023-04-03 05:20:39 +00:00
|
|
|
for key, value in env_vars.items():
|
|
|
|
make_command.append(f'{key}={value}')
|
|
|
|
|
2021-01-16 23:13:04 +00:00
|
|
|
return make_command
|
2020-02-17 19:42:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
def parse_configurator_json(configurator_file):
|
|
|
|
"""Open and parse a configurator json export
|
|
|
|
"""
|
2022-02-28 20:02:39 +00:00
|
|
|
user_keymap = json_load(configurator_file)
|
|
|
|
# Validate against the jsonschema
|
|
|
|
try:
|
|
|
|
validate(user_keymap, 'qmk.keymap.v1')
|
|
|
|
|
|
|
|
except jsonschema.ValidationError as e:
|
|
|
|
cli.log.error(f'Invalid JSON keymap: {configurator_file} : {e.message}')
|
|
|
|
exit(1)
|
|
|
|
|
2021-03-24 16:26:38 +00:00
|
|
|
orig_keyboard = user_keymap['keyboard']
|
2022-11-08 01:05:08 +00:00
|
|
|
aliases = json_load(Path('data/mappings/keyboard_aliases.hjson'))
|
2021-03-24 16:26:38 +00:00
|
|
|
|
|
|
|
if orig_keyboard in aliases:
|
|
|
|
if 'target' in aliases[orig_keyboard]:
|
|
|
|
user_keymap['keyboard'] = aliases[orig_keyboard]['target']
|
|
|
|
|
|
|
|
if 'layouts' in aliases[orig_keyboard] and user_keymap['layout'] in aliases[orig_keyboard]['layouts']:
|
|
|
|
user_keymap['layout'] = aliases[orig_keyboard]['layouts'][user_keymap['layout']]
|
2020-02-17 19:42:11 +00:00
|
|
|
|
|
|
|
return user_keymap
|
2021-07-10 15:04:50 +00:00
|
|
|
|
|
|
|
|
2022-10-20 13:35:27 +00:00
|
|
|
def build_environment(args):
|
|
|
|
"""Common processing for cli.args.env
|
|
|
|
"""
|
|
|
|
envs = {}
|
|
|
|
for env in args:
|
|
|
|
if '=' in env:
|
|
|
|
key, value = env.split('=', 1)
|
|
|
|
envs[key] = value
|
|
|
|
else:
|
|
|
|
cli.log.warning('Invalid environment variable: %s', env)
|
|
|
|
return envs
|
|
|
|
|
|
|
|
|
2021-07-10 15:04:50 +00:00
|
|
|
def in_virtualenv():
|
|
|
|
"""Check if running inside a virtualenv.
|
|
|
|
Based on https://stackoverflow.com/a/1883251
|
|
|
|
"""
|
|
|
|
active_prefix = getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
|
|
|
|
return active_prefix != sys.prefix
|
2022-03-15 17:59:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_chunks(it, size):
|
|
|
|
"""Break down a collection into smaller parts
|
|
|
|
"""
|
|
|
|
it = iter(it)
|
|
|
|
return iter(lambda: tuple(islice(it, size)), ())
|
|
|
|
|
|
|
|
|
2022-03-18 01:09:29 +00:00
|
|
|
def dump_lines(output_file, lines, quiet=True):
|
2022-03-15 17:59:12 +00:00
|
|
|
"""Handle dumping to stdout or file
|
|
|
|
Creates parent folders if required
|
|
|
|
"""
|
2022-03-18 01:09:29 +00:00
|
|
|
generated = '\n'.join(lines) + '\n'
|
|
|
|
if output_file and output_file.name != '-':
|
|
|
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
if output_file.exists():
|
|
|
|
output_file.replace(output_file.parent / (output_file.name + '.bak'))
|
2022-03-18 01:15:23 +00:00
|
|
|
output_file.write_text(generated, encoding='utf-8')
|
2022-03-18 01:09:29 +00:00
|
|
|
|
|
|
|
if not quiet:
|
|
|
|
cli.log.info(f'Wrote {output_file.name} to {output_file}.')
|
|
|
|
else:
|
|
|
|
print(generated)
|