mirror of
https://github.com/carlosedp/qmk_firmware.git
synced 2026-08-05 00:36:43 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -45,7 +45,7 @@ def strip_multiline_comment(string):
|
||||
|
||||
|
||||
def c_source_files(dir_names):
|
||||
"""Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
|
||||
"""Returns a list of all *.c, *.h, *.cpp, and *.hpp files for a given list of directories
|
||||
|
||||
Args:
|
||||
|
||||
@@ -54,7 +54,7 @@ def c_source_files(dir_names):
|
||||
"""
|
||||
files = []
|
||||
for dir in dir_names:
|
||||
files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp'])
|
||||
files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp', '.hpp'])
|
||||
return files
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ safe_commands = [
|
||||
|
||||
subcommands = [
|
||||
'qmk.cli.ci.validate_aliases',
|
||||
'qmk.cli.ci.validate_keyboard_targets',
|
||||
'qmk.cli.bux',
|
||||
'qmk.cli.c2json',
|
||||
'qmk.cli.cd',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Validates the list of keyboard targets.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@cli.subcommand('Validates the list of keyboard targets.', hidden=True)
|
||||
def ci_validate_keyboard_targets(cli):
|
||||
errors = set()
|
||||
|
||||
for rules_mk in Path('keyboards').glob('**/rules.mk'):
|
||||
if any({'keymaps', 'common', 'lib'} & set(rules_mk.parts)):
|
||||
continue
|
||||
|
||||
folder = rules_mk.parent
|
||||
if not any(folder.glob('**/keyboard.json')):
|
||||
errors.add(folder)
|
||||
|
||||
for keymap in Path('keyboards').glob('**/keymaps/'):
|
||||
folder = keymap.parent
|
||||
if not any(folder.glob('**/keyboard.json')):
|
||||
errors.add(folder)
|
||||
|
||||
for error in errors:
|
||||
print(f"{error}::Legacy target detected")
|
||||
|
||||
exit(min(len(errors), 255))
|
||||
@@ -41,6 +41,7 @@ def compile(cli):
|
||||
cli.args.filter = []
|
||||
cli.config.mass_compile.keymap = cli.config.compile.keymap
|
||||
cli.config.mass_compile.parallel = cli.config.compile.parallel
|
||||
cli.args.print_failures = False
|
||||
cli.args.no_temp = False
|
||||
return mass_compile(cli)
|
||||
|
||||
@@ -51,6 +52,7 @@ def compile(cli):
|
||||
cli.args.filter = []
|
||||
cli.config.mass_compile.keymap = None
|
||||
cli.config.mass_compile.parallel = cli.config.compile.parallel
|
||||
cli.args.print_failures = False
|
||||
cli.args.no_temp = False
|
||||
return mass_compile(cli)
|
||||
|
||||
|
||||
@@ -1,62 +1,23 @@
|
||||
"""OS-specific functions for: Linux
|
||||
"""
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from .check import CheckStatus, release_info
|
||||
|
||||
QMK_UDEV_INSTALL_SCRIPT = 'util/install_udev.sh'
|
||||
|
||||
|
||||
def _is_wsl():
|
||||
return 'microsoft' in platform.uname().release.lower()
|
||||
|
||||
|
||||
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 _generate_desired_rules(bootloader_vids_pids):
|
||||
rules = dict()
|
||||
for bl in bootloader_vids_pids.keys():
|
||||
rules[bl] = set()
|
||||
for vid_pid in bootloader_vids_pids[bl]:
|
||||
if bl == 'caterina' or bl == 'md-boot':
|
||||
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"'))
|
||||
else:
|
||||
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1]))
|
||||
return rules
|
||||
|
||||
|
||||
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/"),
|
||||
@@ -64,24 +25,15 @@ def check_udev_rules():
|
||||
Path("/etc/udev/rules.d/"),
|
||||
]
|
||||
|
||||
desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS)
|
||||
if not any(udev_dir.exists() for udev_dir in udev_dirs):
|
||||
cli.log.warning("{fg_yellow}Can't find udev rules directories, skipping udev rule checking...")
|
||||
cli.log.debug("Checked directories: %s", ', '.join(str(udev_dir) for udev_dir in udev_dirs))
|
||||
return CheckStatus.WARNING
|
||||
|
||||
# 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:
|
||||
# Collect all non-comment lines from QMK-related rules files
|
||||
current_rules = set()
|
||||
for udev_dir in udev_dirs:
|
||||
for rule_file in udev_dir.glob('*qmk*'):
|
||||
try:
|
||||
for line in rule_file.read_text(encoding='utf-8').split('\n'):
|
||||
line = line.strip()
|
||||
@@ -90,45 +42,17 @@ def check_udev_rules():
|
||||
except (PermissionError, FileNotFoundError):
|
||||
cli.log.debug("Failed to read: %s", rule_file)
|
||||
|
||||
# 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.")
|
||||
if not current_rules:
|
||||
cli.log.warning("{fg_yellow}Missing udev rules for QMK boards. Please run '%s' to install the rules", QMK_UDEV_INSTALL_SCRIPT)
|
||||
return CheckStatus.WARNING
|
||||
|
||||
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)
|
||||
# Check for the qmk_udev ID_QMK marker
|
||||
if any('ID_QMK' in rule for rule in current_rules):
|
||||
return CheckStatus.OK
|
||||
|
||||
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():
|
||||
mm_check = cli.run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
|
||||
if mm_check.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
"""(TODO): Add check for non-systemd systems
|
||||
"""
|
||||
return False
|
||||
# Legacy rules found (TAG+="uaccess" without ID_QMK)
|
||||
cli.log.warning("{fg_yellow}Found legacy udev rules. Please run '%s' to install the latest rules", QMK_UDEV_INSTALL_SCRIPT)
|
||||
return CheckStatus.WARNING
|
||||
|
||||
|
||||
def os_test_linux():
|
||||
|
||||
@@ -95,7 +95,7 @@ def generate_config_items(kb_info_json, config_h_lines):
|
||||
|
||||
try:
|
||||
config_value = kb_info_json[info_key]
|
||||
except KeyError:
|
||||
except (KeyError, IndexError):
|
||||
continue
|
||||
|
||||
if key_type.startswith('array.array'):
|
||||
|
||||
@@ -31,7 +31,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
|
||||
try:
|
||||
rules_value = kb_info_json[info_key]
|
||||
except KeyError:
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
|
||||
if key_type in ['array', 'list']:
|
||||
|
||||
+10
-21
@@ -22,15 +22,10 @@ INVALID_KM_NAMES = ['via', 'vial']
|
||||
def _list_defaultish_keymaps(kb):
|
||||
"""Return default like keymaps for a given keyboard
|
||||
"""
|
||||
defaultish = ['ansi', 'iso']
|
||||
keymaps = set(list_keymaps(kb, include_userspace=False, include_community=False))
|
||||
|
||||
# This is only here to flag it as "testable", so it doesn't fly under the radar during PR
|
||||
defaultish.extend(INVALID_KM_NAMES)
|
||||
|
||||
keymaps = set()
|
||||
for x in list_keymaps(kb, include_userspace=False):
|
||||
if x in defaultish or x.startswith('default'):
|
||||
keymaps.add(x)
|
||||
# Ensure that at least a 'default' keymap always exists
|
||||
keymaps.add('default')
|
||||
|
||||
return keymaps
|
||||
|
||||
@@ -173,14 +168,6 @@ def _handle_invalid_features(kb, info):
|
||||
return ok
|
||||
|
||||
|
||||
def _handle_invalid_config(kb, info):
|
||||
"""Check for invalid keyboard level config
|
||||
"""
|
||||
if info.get('url') == "":
|
||||
cli.log.warning(f'{kb}: Invalid keyboard level config detected - Optional field "url" should not be empty.')
|
||||
return True
|
||||
|
||||
|
||||
def _chibios_conf_includenext_check(target):
|
||||
"""Check the ChibiOS conf.h for the correct inclusion of the next conf.h
|
||||
"""
|
||||
@@ -297,9 +284,6 @@ def keyboard_check(kb): # noqa C901
|
||||
if not _handle_invalid_features(kb, kb_info):
|
||||
ok = False
|
||||
|
||||
if not _handle_invalid_config(kb, kb_info):
|
||||
ok = False
|
||||
|
||||
if not _handle_duplicating_code_defaults(kb, kb_info):
|
||||
ok = False
|
||||
|
||||
@@ -366,6 +350,11 @@ def lint(cli):
|
||||
cli.print_help()
|
||||
return False
|
||||
|
||||
# milc config handling of user.keymap breaks running lint without keymap argument
|
||||
# so we have to disable that while still allowing a default to be set with lint.keymap
|
||||
if 'keymap' not in cli.config_source.lint.keys() and cli.config.lint.keymap:
|
||||
cli.config.lint.keymap = None
|
||||
|
||||
if isinstance(cli.config.lint.keyboard, str):
|
||||
# if provided via config - string not array
|
||||
keyboard_list = [cli.config.lint.keyboard]
|
||||
@@ -381,12 +370,12 @@ def lint(cli):
|
||||
# Determine keymaps to also check
|
||||
if cli.args.keymap == 'all':
|
||||
keymaps = list_keymaps(kb)
|
||||
elif cli.args.keymap:
|
||||
keymaps = {cli.args.keymap}
|
||||
elif cli.config.lint.keymap:
|
||||
keymaps = {cli.config.lint.keymap}
|
||||
else:
|
||||
keymaps = _list_defaultish_keymaps(kb)
|
||||
# Ensure that at least a 'default' keymap always exists
|
||||
keymaps.add('default')
|
||||
|
||||
ok = True
|
||||
|
||||
|
||||
+18
-19
@@ -18,8 +18,8 @@ from qmk.makefile import parse_rules_mk_file
|
||||
from qmk.math_ops import compute
|
||||
from qmk.util import maybe_exit, truthy
|
||||
|
||||
true_values = ['1', 'on', 'yes']
|
||||
false_values = ['0', 'off', 'no']
|
||||
TRUE_VALUES = ['true', '1', 'on', 'yes']
|
||||
FALSE_VALUES = ['false', '0', 'off', 'no']
|
||||
|
||||
|
||||
class LedFlags(IntFlag):
|
||||
@@ -319,7 +319,7 @@ def _extract_features(info_data, rules):
|
||||
for key, value in rules.items():
|
||||
if key.endswith('_ENABLE'):
|
||||
key = '_'.join(key.split('_')[:-1]).lower()
|
||||
value = True if value.lower() in true_values else False if value.lower() in false_values else value
|
||||
value = True if value.lower() in TRUE_VALUES else False if value.lower() in FALSE_VALUES else value
|
||||
|
||||
if key in ['lto']:
|
||||
continue
|
||||
@@ -420,19 +420,6 @@ def _extract_direct_matrix(direct_pins):
|
||||
return direct_pin_array
|
||||
|
||||
|
||||
def _extract_audio(info_data, config_c):
|
||||
"""Populate data about the audio configuration
|
||||
"""
|
||||
audio_pins = []
|
||||
|
||||
for pin in 'B5', 'B6', 'B7', 'C4', 'C5', 'C6':
|
||||
if config_c.get(f'{pin}_AUDIO'):
|
||||
audio_pins.append(pin)
|
||||
|
||||
if audio_pins:
|
||||
info_data['audio'] = {'pins': audio_pins}
|
||||
|
||||
|
||||
def _extract_encoders_values(config_c, postfix=''):
|
||||
"""Common encoder extraction logic
|
||||
"""
|
||||
@@ -657,7 +644,7 @@ def _config_to_json(key_type, config_value):
|
||||
elif key_type in ['bool', 'flag']:
|
||||
if isinstance(config_value, bool):
|
||||
return config_value
|
||||
return config_value in true_values
|
||||
return config_value in TRUE_VALUES
|
||||
|
||||
elif key_type == 'hex':
|
||||
return '0x' + config_value[2:].upper()
|
||||
@@ -718,7 +705,6 @@ def _extract_config_h(info_data, config_c):
|
||||
|
||||
# Pull data that easily can't be mapped in json
|
||||
_extract_matrix_info(info_data, config_c)
|
||||
_extract_audio(info_data, config_c)
|
||||
_extract_secure_unlock(info_data, config_c)
|
||||
_extract_split_handedness(info_data, config_c)
|
||||
_extract_split_serial(info_data, config_c)
|
||||
@@ -1151,4 +1137,17 @@ def get_modules(keyboard, keymap_filename):
|
||||
if keymap_json:
|
||||
modules.extend(keymap_json.get('modules', []))
|
||||
|
||||
return list(dict.fromkeys(modules)) # remove dupes
|
||||
# remove duplicates while maintaining the current order
|
||||
ret = list(dict.fromkeys(modules))
|
||||
|
||||
# We currently do not support duplicate module names
|
||||
# e.g.: ['foo/hello_world', 'bar/hello_world'] will fail
|
||||
seen = set()
|
||||
for module in ret:
|
||||
module_slug = Path(module).name.lower()
|
||||
if module_slug in seen:
|
||||
duplicates = list(filter(lambda m: module_slug == Path(m).name.lower(), ret))
|
||||
raise Exception(f'Duplicate module name detected: "{module_slug}" - {duplicates}')
|
||||
seen.add(module_slug)
|
||||
|
||||
return ret
|
||||
|
||||
+18
-14
@@ -389,7 +389,7 @@ def is_keymap_target(keyboard, keymap):
|
||||
return False
|
||||
|
||||
|
||||
def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False, include_userspace=True):
|
||||
def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False, include_userspace=True, include_community=True):
|
||||
"""List the available keymaps for a keyboard.
|
||||
|
||||
Args:
|
||||
@@ -411,6 +411,9 @@ def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=Fa
|
||||
include_userspace
|
||||
When set to True, also search userspace for available keymaps
|
||||
|
||||
include_community
|
||||
When set to True, also search community layouts folder for available keymaps
|
||||
|
||||
Returns:
|
||||
a sorted list of valid keymap names.
|
||||
"""
|
||||
@@ -434,21 +437,22 @@ def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=Fa
|
||||
|
||||
kb_path = kb_path.parent
|
||||
|
||||
# Check community layouts as a fallback
|
||||
info = info_json(keyboard)
|
||||
if include_community:
|
||||
# Check community layouts as a fallback
|
||||
info = info_json(keyboard)
|
||||
|
||||
community_parents = list(Path('layouts').glob('*/'))
|
||||
if has_userspace and (Path(QMK_USERSPACE) / "layouts").exists():
|
||||
community_parents.append(Path(QMK_USERSPACE) / "layouts")
|
||||
community_parents = list(Path('layouts').glob('*/'))
|
||||
if has_userspace and (Path(QMK_USERSPACE) / "layouts").exists():
|
||||
community_parents.append(Path(QMK_USERSPACE) / "layouts")
|
||||
|
||||
for community_parent in community_parents:
|
||||
for layout in info.get("community_layouts", []):
|
||||
cl_path = community_parent / layout
|
||||
if cl_path.is_dir():
|
||||
for keymap in cl_path.iterdir():
|
||||
if is_keymap_dir(keymap, c, json, additional_files):
|
||||
keymap = keymap if fullpath else keymap.name
|
||||
names.add(keymap)
|
||||
for community_parent in community_parents:
|
||||
for layout in info.get("community_layouts", []):
|
||||
cl_path = community_parent / layout
|
||||
if cl_path.is_dir():
|
||||
for keymap in cl_path.iterdir():
|
||||
if is_keymap_dir(keymap, c, json, additional_files):
|
||||
keymap = keymap if fullpath else keymap.name
|
||||
names.add(keymap)
|
||||
|
||||
return sorted(names)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user