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.

169 lines
7.3 KiB

  1. """Compile all keyboards.
  2. This will compile everything in parallel, for testing purposes.
  3. """
  4. import fnmatch
  5. import logging
  6. import multiprocessing
  7. import os
  8. import re
  9. from pathlib import Path
  10. from subprocess import DEVNULL
  11. from dotty_dict import dotty
  12. from milc import cli
  13. from qmk.constants import QMK_FIRMWARE
  14. from qmk.commands import _find_make, get_make_parallel_args
  15. from qmk.info import keymap_json
  16. import qmk.keyboard
  17. import qmk.keymap
  18. def _set_log_level(level):
  19. cli.acquire_lock()
  20. old = cli.log_level
  21. cli.log_level = level
  22. cli.log.setLevel(level)
  23. logging.root.setLevel(level)
  24. cli.release_lock()
  25. return old
  26. def _all_keymaps(keyboard):
  27. old = _set_log_level(logging.CRITICAL)
  28. keymaps = qmk.keymap.list_keymaps(keyboard)
  29. _set_log_level(old)
  30. return (keyboard, keymaps)
  31. def _keymap_exists(keyboard, keymap):
  32. old = _set_log_level(logging.CRITICAL)
  33. ret = keyboard if qmk.keymap.locate_keymap(keyboard, keymap) is not None else None
  34. _set_log_level(old)
  35. return ret
  36. def _load_keymap_info(keyboard, keymap):
  37. old = _set_log_level(logging.CRITICAL)
  38. ret = (keyboard, keymap, keymap_json(keyboard, keymap))
  39. _set_log_level(old)
  40. return ret
  41. @cli.argument('-t', '--no-temp', arg_only=True, action='store_true', help="Remove temporary files during build.")
  42. @cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
  43. @cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.")
  44. @cli.argument(
  45. '-f',
  46. '--filter',
  47. arg_only=True,
  48. action='append',
  49. default=[],
  50. help= # noqa: `format-python` and `pytest` don't agree here.
  51. "Filter the list of keyboards based on the supplied value in rules.mk. Matches info.json structure, and accepts the format 'features.rgblight=true'. May be passed multiple times, all filters need to match. Value may include wildcards such as '*' and '?'." # noqa: `format-python` and `pytest` don't agree here.
  52. )
  53. @cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.")
  54. @cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
  55. @cli.subcommand('Compile QMK Firmware for all keyboards.', hidden=False if cli.config.user.developer else True)
  56. def mass_compile(cli):
  57. """Compile QMK Firmware against all keyboards.
  58. """
  59. make_cmd = _find_make()
  60. if cli.args.clean:
  61. cli.run([make_cmd, 'clean'], capture_output=False, stdin=DEVNULL)
  62. builddir = Path(QMK_FIRMWARE) / '.build'
  63. makefile = builddir / 'parallel_kb_builds.mk'
  64. targets = []
  65. with multiprocessing.Pool() as pool:
  66. cli.log.info(f'Retrieving list of keyboards with keymap "{cli.args.keymap}"...')
  67. target_list = []
  68. if cli.args.keymap == 'all':
  69. kb_to_kms = pool.map(_all_keymaps, qmk.keyboard.list_keyboards())
  70. for targets in kb_to_kms:
  71. keyboard = targets[0]
  72. keymaps = targets[1]
  73. target_list.extend([(keyboard, keymap) for keymap in keymaps])
  74. else:
  75. target_list = [(kb, cli.args.keymap) for kb in filter(lambda kb: kb is not None, pool.starmap(_keymap_exists, [(kb, cli.args.keymap) for kb in qmk.keyboard.list_keyboards()]))]
  76. if len(cli.args.filter) == 0:
  77. targets = target_list
  78. else:
  79. cli.log.info('Parsing data for all matching keyboard/keymap combinations...')
  80. valid_keymaps = [(e[0], e[1], dotty(e[2])) for e in pool.starmap(_load_keymap_info, target_list)]
  81. filter_re = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)\s*=\s*(?P<value>[^#]+)$')
  82. for filter_txt in cli.args.filter:
  83. f = filter_re.match(filter_txt)
  84. if f is not None:
  85. key = f.group('key')
  86. value = f.group('value')
  87. cli.log.info(f'Filtering on condition ("{key}" == "{value}")...')
  88. def _make_filter(k, v):
  89. expr = fnmatch.translate(v)
  90. rule = re.compile(expr, re.IGNORECASE)
  91. def f(e):
  92. lhs = e[2].get(k)
  93. lhs = str(False if lhs is None else lhs)
  94. return rule.search(lhs) is not None
  95. return f
  96. valid_keymaps = filter(_make_filter(key, value), valid_keymaps)
  97. targets = [(e[0], e[1]) for e in valid_keymaps]
  98. if len(targets) == 0:
  99. return
  100. builddir.mkdir(parents=True, exist_ok=True)
  101. with open(makefile, "w") as f:
  102. for target in sorted(targets):
  103. keyboard_name = target[0]
  104. keymap_name = target[1]
  105. keyboard_safe = keyboard_name.replace('/', '_')
  106. # yapf: disable
  107. f.write(
  108. f"""\
  109. all: {keyboard_safe}_{keymap_name}_binary
  110. {keyboard_safe}_{keymap_name}_binary:
  111. @rm -f "{QMK_FIRMWARE}/.build/failed.log.{keyboard_safe}.{keymap_name}" || true
  112. @echo "Compiling QMK Firmware for target: '{keyboard_name}:{keymap_name}'..." >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}"
  113. +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/builddefs/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{keymap_name}" COLOR=true SILENT=false {' '.join(cli.args.env)} \\
  114. >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}.{keymap_name}" 2>&1 \\
  115. || cp "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}.{keymap_name}" "{QMK_FIRMWARE}/.build/failed.log.{os.getpid()}.{keyboard_safe}.{keymap_name}"
  116. @{{ grep '\[ERRORS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}.{keymap_name}" >/dev/null 2>&1 && printf "Build %-64s \e[1;31m[ERRORS]\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\
  117. || {{ grep '\[WARNINGS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}.{keymap_name}" >/dev/null 2>&1 && printf "Build %-64s \e[1;33m[WARNINGS]\e[0m\\n" "{keyboard_name}:{keymap_name}" ; }} \\
  118. || printf "Build %-64s \e[1;32m[OK]\e[0m\\n" "{keyboard_name}:{keymap_name}"
  119. @rm -f "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}.{keymap_name}" || true
  120. """# noqa
  121. )
  122. # yapf: enable
  123. if cli.args.no_temp:
  124. # yapf: disable
  125. f.write(
  126. f"""\
  127. @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{keymap_name}.elf" 2>/dev/null || true
  128. @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{keymap_name}.map" 2>/dev/null || true
  129. @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{keymap_name}.hex" 2>/dev/null || true
  130. @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{keymap_name}.bin" 2>/dev/null || true
  131. @rm -rf "{QMK_FIRMWARE}/.build/{keyboard_safe}_{keymap_name}.uf2" 2>/dev/null || true
  132. @rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}" || true
  133. @rm -rf "{QMK_FIRMWARE}/.build/obj_{keyboard_safe}_{keymap_name}" || true
  134. """# noqa
  135. )
  136. # yapf: enable
  137. f.write('\n')
  138. cli.run([make_cmd, *get_make_parallel_args(cli.args.parallel), '-f', makefile.as_posix(), 'all'], capture_output=False, stdin=DEVNULL)
  139. # Check for failures
  140. failures = [f for f in builddir.glob(f'failed.log.{os.getpid()}.*')]
  141. if len(failures) > 0:
  142. return False