commands.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. """Helper functions for commands.
  2. """
  3. import json
  4. import os
  5. import platform
  6. import subprocess
  7. import shlex
  8. import shutil
  9. from pathlib import Path
  10. from time import strftime
  11. from milc import cli
  12. import qmk.keymap
  13. from qmk.constants import KEYBOARD_OUTPUT_PREFIX
  14. from qmk.json_schema import json_load
  15. time_fmt = '%Y-%m-%d-%H:%M:%S'
  16. def _find_make():
  17. """Returns the correct make command for this environment.
  18. """
  19. make_cmd = os.environ.get('MAKE')
  20. if not make_cmd:
  21. make_cmd = 'gmake' if shutil.which('gmake') else 'make'
  22. return make_cmd
  23. def create_make_target(target, parallel=1, **env_vars):
  24. """Create a make command
  25. Args:
  26. target
  27. Usually a make rule, such as 'clean' or 'all'.
  28. parallel
  29. The number of make jobs to run in parallel
  30. **env_vars
  31. Environment variables to be passed to make.
  32. Returns:
  33. A command that can be run to make the specified keyboard and keymap
  34. """
  35. env = []
  36. make_cmd = _find_make()
  37. for key, value in env_vars.items():
  38. env.append(f'{key}={value}')
  39. return [make_cmd, '-j', str(parallel), *env, target]
  40. def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars):
  41. """Create a make compile command
  42. Args:
  43. keyboard
  44. The path of the keyboard, for example 'plank'
  45. keymap
  46. The name of the keymap, for example 'algernon'
  47. target
  48. Usually a bootloader.
  49. parallel
  50. The number of make jobs to run in parallel
  51. **env_vars
  52. Environment variables to be passed to make.
  53. Returns:
  54. A command that can be run to make the specified keyboard and keymap
  55. """
  56. make_args = [keyboard, keymap]
  57. if target:
  58. make_args.append(target)
  59. return create_make_target(':'.join(make_args), parallel, **env_vars)
  60. def get_git_version(repo_dir='.', check_dir='.'):
  61. """Returns the current git version for a repo, or the current time.
  62. """
  63. git_describe_cmd = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags']
  64. if Path(check_dir).exists():
  65. git_describe = cli.run(git_describe_cmd, cwd=repo_dir)
  66. if git_describe.returncode == 0:
  67. return git_describe.stdout.strip()
  68. else:
  69. cli.log.warn(f'"{" ".join(git_describe_cmd)}" returned error code {git_describe.returncode}')
  70. print(git_describe.stderr)
  71. return strftime(time_fmt)
  72. return strftime(time_fmt)
  73. def write_version_h(git_version, build_date, chibios_version, chibios_contrib_version):
  74. """Generate and write quantum/version.h
  75. """
  76. version_h = [
  77. f'#define QMK_VERSION "{git_version}"',
  78. f'#define QMK_BUILDDATE "{build_date}"',
  79. f'#define CHIBIOS_VERSION "{chibios_version}"',
  80. f'#define CHIBIOS_CONTRIB_VERSION "{chibios_contrib_version}"',
  81. ]
  82. version_h_file = Path('quantum/version.h')
  83. version_h_file.write_text('\n'.join(version_h))
  84. def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_vars):
  85. """Convert a configurator export JSON file into a C file and then compile it.
  86. Args:
  87. user_keymap
  88. A deserialized keymap export
  89. bootloader
  90. A bootloader to flash
  91. parallel
  92. The number of make jobs to run in parallel
  93. Returns:
  94. A command to run to compile and flash the C file.
  95. """
  96. # Write the keymap.c file
  97. keyboard_filesafe = user_keymap['keyboard'].replace('/', '_')
  98. target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
  99. keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
  100. keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}')
  101. c_text = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])
  102. keymap_dir = keymap_output / 'src'
  103. keymap_c = keymap_dir / 'keymap.c'
  104. keymap_dir.mkdir(exist_ok=True, parents=True)
  105. keymap_c.write_text(c_text)
  106. # Write the version.h file
  107. git_version = get_git_version()
  108. build_date = strftime('%Y-%m-%d-%H:%M:%S')
  109. chibios_version = get_git_version("lib/chibios", "lib/chibios/os")
  110. chibios_contrib_version = get_git_version("lib/chibios-contrib", "lib/chibios-contrib/os")
  111. write_version_h(git_version, build_date, chibios_version, chibios_contrib_version)
  112. # Return a command that can be run to make the keymap and flash if given
  113. verbose = 'true' if cli.config.general.verbose else 'false'
  114. color = 'true' if cli.config.general.color else 'false'
  115. make_command = [_find_make()]
  116. if not cli.config.general.verbose:
  117. make_command.append('-s')
  118. make_command.extend([
  119. '-j',
  120. str(parallel),
  121. '-r',
  122. '-R',
  123. '-f',
  124. 'build_keyboard.mk',
  125. ])
  126. if bootloader:
  127. make_command.append(bootloader)
  128. for key, value in env_vars.items():
  129. make_command.append(f'{key}={value}')
  130. make_command.extend([
  131. f'GIT_VERSION={git_version}',
  132. f'BUILD_DATE={build_date}',
  133. f'CHIBIOS_VERSION={chibios_version}',
  134. f'CHIBIOS_CONTRIB_VERSION={chibios_contrib_version}',
  135. f'KEYBOARD={user_keymap["keyboard"]}',
  136. f'KEYMAP={user_keymap["keymap"]}',
  137. f'KEYBOARD_FILESAFE={keyboard_filesafe}',
  138. f'TARGET={target}',
  139. f'KEYBOARD_OUTPUT={keyboard_output}',
  140. f'KEYMAP_OUTPUT={keymap_output}',
  141. f'MAIN_KEYMAP_PATH_1={keymap_output}',
  142. f'MAIN_KEYMAP_PATH_2={keymap_output}',
  143. f'MAIN_KEYMAP_PATH_3={keymap_output}',
  144. f'MAIN_KEYMAP_PATH_4={keymap_output}',
  145. f'MAIN_KEYMAP_PATH_5={keymap_output}',
  146. f'KEYMAP_C={keymap_c}',
  147. f'KEYMAP_PATH={keymap_dir}',
  148. f'VERBOSE={verbose}',
  149. f'COLOR={color}',
  150. 'SILENT=false',
  151. f'QMK_BIN={"bin/qmk" if "DEPRECATED_BIN_QMK" in os.environ else "qmk"}',
  152. ])
  153. return make_command
  154. def parse_configurator_json(configurator_file):
  155. """Open and parse a configurator json export
  156. """
  157. # FIXME(skullydazed/anyone): Add validation here
  158. user_keymap = json.load(configurator_file)
  159. orig_keyboard = user_keymap['keyboard']
  160. aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
  161. if orig_keyboard in aliases:
  162. if 'target' in aliases[orig_keyboard]:
  163. user_keymap['keyboard'] = aliases[orig_keyboard]['target']
  164. if 'layouts' in aliases[orig_keyboard] and user_keymap['layout'] in aliases[orig_keyboard]['layouts']:
  165. user_keymap['layout'] = aliases[orig_keyboard]['layouts'][user_keymap['layout']]
  166. return user_keymap
  167. def run(command, *args, **kwargs):
  168. """Run a command with subprocess.run
  169. """
  170. platform_id = platform.platform().lower()
  171. if isinstance(command, str):
  172. raise TypeError('`command` must be a non-text sequence such as list or tuple.')
  173. if 'windows' in platform_id:
  174. safecmd = map(str, command)
  175. safecmd = map(shlex.quote, safecmd)
  176. safecmd = ' '.join(safecmd)
  177. command = [os.environ.get('SHELL', '/usr/bin/bash'), '-c', safecmd]
  178. return subprocess.run(command, *args, **kwargs)