idf_ext.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import copy
  2. import glob
  3. import os
  4. import os.path
  5. import re
  6. import shutil
  7. import tempfile
  8. def action_extensions(base_actions, project_path=os.getcwd()):
  9. """ Describes extensions for unit tests. This function expects that actions "all" and "reconfigure" """
  10. PROJECT_NAME = "unit-test-app"
  11. # List of unit-test-app configurations.
  12. # Each file in configs/ directory defines a configuration. The format is the
  13. # same as sdkconfig file. Configuration is applied on top of sdkconfig.defaults
  14. # file from the project directory
  15. CONFIG_NAMES = os.listdir(os.path.join(project_path, "configs"))
  16. # Build (intermediate) and output (artifact) directories
  17. BUILDS_DIR = os.path.join(project_path, "builds")
  18. BINARIES_DIR = os.path.join(project_path, "output")
  19. def parse_file_to_dict(path, regex):
  20. """
  21. Parse the config file at 'path'
  22. Returns a dict of name:value.
  23. """
  24. compiled_regex = re.compile(regex)
  25. result = {}
  26. with open(path) as f:
  27. for line in f:
  28. m = compiled_regex.match(line)
  29. if m:
  30. result[m.group(1)] = m.group(2)
  31. return result
  32. def parse_config(path):
  33. """
  34. Expected format with default regex is "key=value"
  35. """
  36. return parse_file_to_dict(path, r"^([^=]+)=(.+)$")
  37. def ut_apply_config(ut_apply_config_name, ctx, args):
  38. config_name = re.match(r"ut-apply-config-(.*)", ut_apply_config_name).group(1)
  39. # Make sure that define_cache_entry is list
  40. args.define_cache_entry = list(args.define_cache_entry)
  41. new_cache_values = {}
  42. sdkconfig_set = list(filter(lambda s: "SDKCONFIG=" in s, args.define_cache_entry))
  43. sdkconfig_path = os.path.join(args.project_dir, "sdkconfig")
  44. if sdkconfig_set:
  45. sdkconfig_path = sdkconfig_set[-1].split("=")[1]
  46. sdkconfig_path = os.path.abspath(sdkconfig_path)
  47. try:
  48. os.remove(sdkconfig_path)
  49. except OSError:
  50. pass
  51. if config_name in CONFIG_NAMES:
  52. # Parse the sdkconfig for components to be included/excluded and tests to be run
  53. config_path = os.path.join(project_path, "configs", config_name)
  54. config = parse_config(config_path)
  55. new_cache_values["EXCLUDE_COMPONENTS"] = config.get("EXCLUDE_COMPONENTS", "''")
  56. new_cache_values["TEST_EXCLUDE_COMPONENTS"] = config.get("TEST_EXCLUDE_COMPONENTS", "''")
  57. new_cache_values["TEST_COMPONENTS"] = config.get("TEST_COMPONENTS", "''")
  58. new_cache_values["TESTS_ALL"] = int(new_cache_values["TEST_COMPONENTS"] == "''")
  59. with tempfile.NamedTemporaryFile(delete=False) as sdkconfig_temp:
  60. # Use values from the combined defaults and the values from
  61. # config folder to build config
  62. sdkconfig_default = os.path.join(project_path, "sdkconfig.defaults")
  63. with open(sdkconfig_default, "rb") as sdkconfig_default_file:
  64. sdkconfig_temp.write(sdkconfig_default_file.read())
  65. sdkconfig_config = os.path.join(project_path, "configs", config_name)
  66. with open(sdkconfig_config, "rb") as sdkconfig_config_file:
  67. sdkconfig_temp.write(b"\n")
  68. sdkconfig_temp.write(sdkconfig_config_file.read())
  69. new_cache_values["SDKCONFIG_DEFAULTS"] = sdkconfig_temp.name
  70. try:
  71. args.define_cache_entry.extend(["%s=%s" % (k, v) for k, v in new_cache_values.items()])
  72. reconfigure = base_actions["actions"]["reconfigure"]["callback"]
  73. reconfigure(None, ctx, args)
  74. finally:
  75. try:
  76. os.unlink(sdkconfig_temp.name)
  77. except OSError:
  78. pass
  79. # This target builds the configuration. It does not currently track dependencies,
  80. # but is good enough for CI builds if used together with clean-all-configs.
  81. # For local builds, use 'apply-config-NAME' target and then use normal 'all'
  82. # and 'flash' targets.
  83. def ut_build(ut_build_name, ctx, args):
  84. # Create a copy of the passed arguments to prevent arg modifications to accrue if
  85. # all configs are being built
  86. build_args = copy.copy(args)
  87. config_name = re.match(r"ut-build-(.*)", ut_build_name).group(1)
  88. if config_name in CONFIG_NAMES:
  89. build_args.build_dir = os.path.join(BUILDS_DIR, config_name)
  90. src = os.path.join(BUILDS_DIR, config_name)
  91. dest = os.path.join(BINARIES_DIR, config_name)
  92. try:
  93. os.makedirs(dest)
  94. except OSError:
  95. pass
  96. # Build, tweaking paths to sdkconfig and sdkconfig.defaults
  97. ut_apply_config("ut-apply-config-" + config_name, ctx, build_args)
  98. build_target = base_actions["actions"]["all"]["callback"]
  99. build_target("all", ctx, build_args)
  100. # Copy artifacts to the output directory
  101. shutil.copyfile(
  102. os.path.join(build_args.project_dir, "sdkconfig"),
  103. os.path.join(dest, "sdkconfig"),
  104. )
  105. binaries = [PROJECT_NAME + x for x in [".elf", ".bin", ".map"]]
  106. for binary in binaries:
  107. shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary))
  108. try:
  109. os.mkdir(os.path.join(dest, "bootloader"))
  110. except OSError:
  111. pass
  112. shutil.copyfile(
  113. os.path.join(src, "bootloader", "bootloader.bin"),
  114. os.path.join(dest, "bootloader", "bootloader.bin"),
  115. )
  116. for partition_table in glob.glob(os.path.join(src, "partition_table", "partition-table*.bin")):
  117. try:
  118. os.mkdir(os.path.join(dest, "partition_table"))
  119. except OSError:
  120. pass
  121. shutil.copyfile(
  122. partition_table,
  123. os.path.join(dest, "partition_table", os.path.basename(partition_table)),
  124. )
  125. shutil.copyfile(
  126. os.path.join(src, "flasher_args.json"),
  127. os.path.join(dest, "flasher_args.json"),
  128. )
  129. binaries = glob.glob(os.path.join(src, "*.bin"))
  130. binaries = [os.path.basename(s) for s in binaries]
  131. for binary in binaries:
  132. shutil.copyfile(os.path.join(src, binary), os.path.join(dest, binary))
  133. def ut_clean(ut_clean_name, ctx, args):
  134. config_name = re.match(r"ut-clean-(.*)", ut_clean_name).group(1)
  135. if config_name in CONFIG_NAMES:
  136. shutil.rmtree(os.path.join(BUILDS_DIR, config_name), ignore_errors=True)
  137. shutil.rmtree(os.path.join(BINARIES_DIR, config_name), ignore_errors=True)
  138. def test_component_callback(ctx, global_args, tasks):
  139. """ Convert the values passed to the -T and -E parameter to corresponding cache entry definitions TESTS_ALL and TEST_COMPONENTS """
  140. test_components = global_args.test_components
  141. test_exclude_components = global_args.test_exclude_components
  142. cache_entries = {}
  143. if test_components:
  144. if "all" in test_components:
  145. cache_entries["TESTS_ALL"] = 1
  146. cache_entries["TEST_COMPONENTS"] = "''"
  147. else:
  148. cache_entries["TESTS_ALL"] = 0
  149. cache_entries["TEST_COMPONENTS"] = " ".join(test_components)
  150. if test_exclude_components:
  151. cache_entries["TEST_EXCLUDE_COMPONENTS"] = " ".join(test_exclude_components)
  152. if cache_entries:
  153. global_args.define_cache_entry = list(global_args.define_cache_entry)
  154. global_args.define_cache_entry.extend(["%s=%s" % (k, v) for k, v in cache_entries.items()])
  155. # Add global options
  156. extensions = {
  157. "global_options": [{
  158. "names": ["-T", "--test-components"],
  159. "help": "Specify the components to test.",
  160. "scope": "shared",
  161. "multiple": True,
  162. }, {
  163. "names": ["-E", "--test-exclude-components"],
  164. "help": "Specify the components to exclude from testing.",
  165. "scope": "shared",
  166. "multiple": True,
  167. }],
  168. "global_action_callbacks": [test_component_callback],
  169. "actions": {},
  170. }
  171. # This generates per-config targets (clean, build, apply-config).
  172. build_all_config_deps = []
  173. clean_all_config_deps = []
  174. for config in CONFIG_NAMES:
  175. config_build_action_name = "ut-build-" + config
  176. config_clean_action_name = "ut-clean-" + config
  177. config_apply_config_action_name = "ut-apply-config-" + config
  178. extensions["actions"][config_build_action_name] = {
  179. "callback":
  180. ut_build,
  181. "help":
  182. "Build unit-test-app with configuration provided in configs/NAME. " +
  183. "Build directory will be builds/%s/, " % config_build_action_name +
  184. "output binaries will be under output/%s/" % config_build_action_name,
  185. }
  186. extensions["actions"][config_clean_action_name] = {
  187. "callback": ut_clean,
  188. "help": "Remove build and output directories for configuration %s." % config_clean_action_name,
  189. }
  190. extensions["actions"][config_apply_config_action_name] = {
  191. "callback":
  192. ut_apply_config,
  193. "help":
  194. "Generates configuration based on configs/%s in sdkconfig file." % config_apply_config_action_name +
  195. "After this, normal all/flash targets can be used. Useful for development/debugging.",
  196. }
  197. build_all_config_deps.append(config_build_action_name)
  198. clean_all_config_deps.append(config_clean_action_name)
  199. extensions["actions"]["ut-build-all-configs"] = {
  200. "callback": ut_build,
  201. "help": "Build all configurations defined in configs/ directory.",
  202. "dependencies": build_all_config_deps,
  203. }
  204. extensions["actions"]["ut-clean-all-configs"] = {
  205. "callback": ut_clean,
  206. "help": "Remove build and output directories for all configurations defined in configs/ directory.",
  207. "dependencies": clean_all_config_deps,
  208. }
  209. return extensions