You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
4.5 KiB

  1. """Command to look over a keyboard/keymap and check for common mistakes.
  2. """
  3. from pathlib import Path
  4. from milc import cli
  5. from qmk.decorators import automagic_keyboard, automagic_keymap
  6. from qmk.info import info_json
  7. from qmk.keyboard import keyboard_completer, list_keyboards
  8. from qmk.keymap import locate_keymap
  9. from qmk.path import is_keyboard, keyboard
  10. def keymap_check(kb, km):
  11. """Perform the keymap level checks.
  12. """
  13. ok = True
  14. keymap_path = locate_keymap(kb, km)
  15. if not keymap_path:
  16. ok = False
  17. cli.log.error("%s: Can't find %s keymap.", kb, km)
  18. return ok
  19. def rules_mk_assignment_only(keyboard_path):
  20. """Check the keyboard-level rules.mk to ensure it only has assignments.
  21. """
  22. current_path = Path()
  23. errors = []
  24. for path_part in keyboard_path.parts:
  25. current_path = current_path / path_part
  26. rules_mk = current_path / 'rules.mk'
  27. if rules_mk.exists():
  28. continuation = None
  29. for i, line in enumerate(rules_mk.open()):
  30. line = line.strip()
  31. if '#' in line:
  32. line = line[:line.index('#')]
  33. if continuation:
  34. line = continuation + line
  35. continuation = None
  36. if line:
  37. if line[-1] == '\\':
  38. continuation = line[:-1]
  39. continue
  40. if line and '=' not in line:
  41. errors.append(f'Non-assignment code on line +{i} {rules_mk}: {line}')
  42. return errors
  43. @cli.argument('--strict', action='store_true', help='Treat warnings as errors')
  44. @cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Comma separated list of keyboards to check')
  45. @cli.argument('-km', '--keymap', help='The keymap to check')
  46. @cli.argument('--all-kb', action='store_true', arg_only=True, help='Check all keyboards')
  47. @cli.subcommand('Check keyboard and keymap for common mistakes.')
  48. @automagic_keyboard
  49. @automagic_keymap
  50. def lint(cli):
  51. """Check keyboard and keymap for common mistakes.
  52. """
  53. failed = []
  54. # Determine our keyboard list
  55. if cli.args.all_kb:
  56. if cli.args.keyboard:
  57. cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes presidence.')
  58. keyboard_list = list_keyboards()
  59. elif not cli.config.lint.keyboard:
  60. cli.log.error('Missing required arguments: --keyboard or --all-kb')
  61. cli.print_help()
  62. return False
  63. else:
  64. keyboard_list = cli.config.lint.keyboard.split(',')
  65. # Lint each keyboard
  66. for kb in keyboard_list:
  67. if not is_keyboard(kb):
  68. cli.log.error('No such keyboard: %s', kb)
  69. continue
  70. # Gather data about the keyboard.
  71. ok = True
  72. keyboard_path = keyboard(kb)
  73. keyboard_info = info_json(kb)
  74. # Check for errors in the info.json
  75. if keyboard_info['parse_errors']:
  76. ok = False
  77. cli.log.error('%s: Errors found when generating info.json.', kb)
  78. if cli.config.lint.strict and keyboard_info['parse_warnings']:
  79. ok = False
  80. cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb)
  81. # Check the rules.mk file(s)
  82. rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path)
  83. if rules_mk_assignment_errors:
  84. ok = False
  85. cli.log.error('%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb)
  86. for assignment_error in rules_mk_assignment_errors:
  87. cli.log.error(assignment_error)
  88. # Keymap specific checks
  89. if cli.config.lint.keymap:
  90. if not keymap_check(kb, cli.config.lint.keymap):
  91. ok = False
  92. # Check if all non-data driven macros exist in <keyboard.h>
  93. for layout, data in keyboard_info['layouts'].items():
  94. # Matrix data should be a list with exactly two integers: [0, 1]
  95. if not data['c_macro'] and not all('matrix' in key_data.keys() or len(key_data) == 2 or all(isinstance(n, int) for n in key_data) for key_data in data['layout']):
  96. cli.log.error(f'{kb}: "{layout}" has no "matrix" definition in either "info.json" or "<keyboard>.h"!')
  97. ok = False
  98. # Report status
  99. if not ok:
  100. failed.append(kb)
  101. # Check and report the overall status
  102. if failed:
  103. cli.log.error('Lint check failed for: %s', ', '.join(failed))
  104. return False
  105. cli.log.info('Lint check passed!')
  106. return True