fbt_sdk.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. from SCons.Builder import Builder
  2. from SCons.Action import Action
  3. from SCons.Errors import UserError
  4. # from SCons.Scanner import C
  5. from SCons.Script import Mkdir, Copy, Delete, Entry
  6. from SCons.Util import LogicalLines
  7. import os.path
  8. import posixpath
  9. import pathlib
  10. import json
  11. from fbt.sdk import SdkCollector, SdkCache
  12. def ProcessSdkDepends(env, filename):
  13. try:
  14. with open(filename, "r") as fin:
  15. lines = LogicalLines(fin).readlines()
  16. except IOError:
  17. return []
  18. _, depends = lines[0].split(":", 1)
  19. depends = depends.split()
  20. depends.pop(0) # remove the .c file
  21. depends = list(
  22. # Don't create dependency on non-existing files
  23. # (e.g. when they were renamed since last build)
  24. filter(
  25. lambda file: file.exists(),
  26. (env.File(f"#{path}") for path in depends),
  27. )
  28. )
  29. return depends
  30. def prebuild_sdk_emitter(target, source, env):
  31. target.append(env.ChangeFileExtension(target[0], ".d"))
  32. target.append(env.ChangeFileExtension(target[0], ".i.c"))
  33. return target, source
  34. def prebuild_sdk_create_origin_file(target, source, env):
  35. mega_file = env.subst("${TARGET}.c", target=target[0])
  36. with open(mega_file, "wt") as sdk_c:
  37. sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"]))
  38. class SdkMeta:
  39. def __init__(self, env):
  40. self.env = env
  41. def save_to(self, json_manifest_path: str):
  42. meta_contents = {
  43. "sdk_symbols": self.env["SDK_DEFINITION"].name,
  44. "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"),
  45. "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"),
  46. "linker_args": self._wrap_scons_vars("$LINKFLAGS"),
  47. }
  48. with open(json_manifest_path, "wt") as f:
  49. json.dump(meta_contents, f, indent=4)
  50. def _wrap_scons_vars(self, vars: str):
  51. expanded_vars = self.env.subst(vars, target=Entry("dummy"))
  52. return expanded_vars.replace("\\", "/")
  53. class SdkTreeBuilder:
  54. def __init__(self, env, target, source) -> None:
  55. self.env = env
  56. self.target = target
  57. self.source = source
  58. self.header_depends = []
  59. self.header_dirs = []
  60. self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk")
  61. self.sdk_root_dir = target[0].Dir(".")
  62. self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name)
  63. def _parse_sdk_depends(self):
  64. deps_file = self.source[0]
  65. with open(deps_file.path, "rt") as deps_f:
  66. lines = LogicalLines(deps_f).readlines()
  67. _, depends = lines[0].split(":", 1)
  68. self.header_depends = list(
  69. filter(lambda fname: fname.endswith(".h"), depends.split()),
  70. )
  71. self.header_dirs = sorted(
  72. set(map(os.path.normpath, map(os.path.dirname, self.header_depends)))
  73. )
  74. def _generate_sdk_meta(self):
  75. filtered_paths = [self.target_sdk_dir_name]
  76. full_fw_paths = list(
  77. map(
  78. os.path.normpath,
  79. (self.env.Dir(inc_dir).relpath for inc_dir in self.env["CPPPATH"]),
  80. )
  81. )
  82. sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs)
  83. for dir in full_fw_paths:
  84. if dir in sdk_dirs:
  85. filtered_paths.append(
  86. posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir))
  87. )
  88. sdk_env = self.env.Clone()
  89. sdk_env.Replace(CPPPATH=filtered_paths)
  90. meta = SdkMeta(sdk_env)
  91. meta.save_to(self.target[0].path)
  92. def emitter(self, target, source, env):
  93. target_folder = target[0]
  94. target = [target_folder.File("sdk.opts")]
  95. return target, source
  96. def _create_deploy_commands(self):
  97. dirs_to_create = set(
  98. self.sdk_deploy_dir.Dir(dirpath) for dirpath in self.header_dirs
  99. )
  100. actions = [
  101. Delete(self.sdk_deploy_dir),
  102. Mkdir(self.sdk_deploy_dir),
  103. Copy(
  104. self.sdk_root_dir,
  105. self.env["SDK_DEFINITION"],
  106. ),
  107. ]
  108. actions += [Mkdir(d) for d in dirs_to_create]
  109. actions += [
  110. Action(
  111. Copy(self.sdk_deploy_dir.File(h).path, h),
  112. # f"Copy {h} to {self.sdk_deploy_dir}",
  113. )
  114. for h in self.header_depends
  115. ]
  116. return actions
  117. def generate_actions(self):
  118. self._parse_sdk_depends()
  119. self._generate_sdk_meta()
  120. return self._create_deploy_commands()
  121. def deploy_sdk_tree(target, source, env, for_signature):
  122. if for_signature:
  123. return []
  124. sdk_tree = SdkTreeBuilder(env, target, source)
  125. return sdk_tree.generate_actions()
  126. def deploy_sdk_tree_emitter(target, source, env):
  127. sdk_tree = SdkTreeBuilder(env, target, source)
  128. return sdk_tree.emitter(target, source, env)
  129. def gen_sdk_data(sdk_cache: SdkCache):
  130. api_def = []
  131. api_def.extend(
  132. (f"#include <{h.name}>" for h in sdk_cache.get_headers()),
  133. )
  134. api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};")
  135. api_def.append(
  136. "static constexpr auto elf_api_table = sort(create_array_t<sym_entry>("
  137. )
  138. api_lines = []
  139. for fun_def in sdk_cache.get_functions():
  140. api_lines.append(
  141. f"API_METHOD({fun_def.name}, {fun_def.returns}, ({fun_def.params}))"
  142. )
  143. for var_def in sdk_cache.get_variables():
  144. api_lines.append(f"API_VARIABLE({var_def.name}, {var_def.var_type })")
  145. api_def.append(",\n".join(api_lines))
  146. api_def.append("));")
  147. return api_def
  148. def _check_sdk_is_up2date(sdk_cache: SdkCache):
  149. if not sdk_cache.is_buildable():
  150. raise UserError(
  151. "SDK version is not finalized, please review changes and re-run operation"
  152. )
  153. def validate_sdk_cache(source, target, env):
  154. # print(f"Generating SDK for {source[0]} to {target[0]}")
  155. current_sdk = SdkCollector()
  156. current_sdk.process_source_file_for_sdk(source[0].path)
  157. for h in env["SDK_HEADERS"]:
  158. current_sdk.add_header_to_sdk(pathlib.Path(h.path).as_posix())
  159. sdk_cache = SdkCache(target[0].path)
  160. sdk_cache.validate_api(current_sdk.get_api())
  161. sdk_cache.save()
  162. _check_sdk_is_up2date(sdk_cache)
  163. def generate_sdk_symbols(source, target, env):
  164. sdk_cache = SdkCache(source[0].path)
  165. _check_sdk_is_up2date(sdk_cache)
  166. api_def = gen_sdk_data(sdk_cache)
  167. with open(target[0].path, "wt") as f:
  168. f.write("\n".join(api_def))
  169. def generate(env, **kw):
  170. env.AddMethod(ProcessSdkDepends)
  171. env.Append(
  172. BUILDERS={
  173. "SDKPrebuilder": Builder(
  174. emitter=prebuild_sdk_emitter,
  175. action=[
  176. Action(
  177. prebuild_sdk_create_origin_file,
  178. "$SDK_PREGEN_COMSTR",
  179. ),
  180. Action(
  181. "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c",
  182. "$SDK_COMSTR",
  183. ),
  184. ],
  185. suffix=".i",
  186. ),
  187. "SDKTree": Builder(
  188. generator=deploy_sdk_tree,
  189. emitter=deploy_sdk_tree_emitter,
  190. src_suffix=".d",
  191. ),
  192. "SDKSymUpdater": Builder(
  193. action=Action(
  194. validate_sdk_cache,
  195. "$SDKSYM_UPDATER_COMSTR",
  196. ),
  197. suffix=".csv",
  198. src_suffix=".i",
  199. ),
  200. "SDKSymGenerator": Builder(
  201. action=Action(
  202. generate_sdk_symbols,
  203. "$SDKSYM_GENERATOR_COMSTR",
  204. ),
  205. suffix=".h",
  206. src_suffix=".csv",
  207. ),
  208. }
  209. )
  210. def exists(env):
  211. return True