fbt_sdk.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import shutil
  2. from SCons.Builder import Builder
  3. from SCons.Action import Action
  4. from SCons.Errors import UserError
  5. # from SCons.Scanner import C
  6. from SCons.Script import Entry
  7. from SCons.Util import LogicalLines
  8. import os.path
  9. import posixpath
  10. import pathlib
  11. import json
  12. from fbt.sdk.collector import SdkCollector
  13. from fbt.sdk.cache import SdkCache
  14. from fbt.util import path_as_posix
  15. def ProcessSdkDepends(env, filename):
  16. try:
  17. with open(filename, "r") as fin:
  18. lines = LogicalLines(fin).readlines()
  19. except IOError:
  20. return []
  21. _, depends = lines[0].split(":", 1)
  22. depends = depends.split()
  23. depends.pop(0) # remove the .c file
  24. depends = list(
  25. # Don't create dependency on non-existing files
  26. # (e.g. when they were renamed since last build)
  27. filter(
  28. lambda file: file.exists(),
  29. (env.File(f"#{path}") for path in depends),
  30. )
  31. )
  32. return depends
  33. def api_amalgam_emitter(target, source, env):
  34. target.append(env.ChangeFileExtension(target[0], ".d"))
  35. target.append(env.ChangeFileExtension(target[0], ".i.c"))
  36. return target, source
  37. def api_amalgam_gen_origin_header(target, source, env):
  38. mega_file = env.subst("${TARGET}.c", target=target[0])
  39. with open(mega_file, "wt") as sdk_c:
  40. sdk_c.write(
  41. "\n".join(f"#include <{h.srcnode().path}>" for h in env["SDK_HEADERS"])
  42. )
  43. class SdkMeta:
  44. MAP_FILE_SUBST = "SDK_MAP_FILE_SUBST"
  45. def __init__(self, env, tree_builder: "SdkTreeBuilder"):
  46. self.env = env
  47. self.treebuilder = tree_builder
  48. def save_to(self, json_manifest_path: str):
  49. meta_contents = {
  50. "sdk_symbols": self.treebuilder.build_sdk_file_path(
  51. self.env["SDK_DEFINITION"].path
  52. ),
  53. "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"),
  54. "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"),
  55. "linker_args": self._wrap_scons_vars("$LINKFLAGS"),
  56. "linker_libs": self.env.subst("${LIBS}"),
  57. "app_ep_subst": self.env.subst("${APP_ENTRY}"),
  58. "sdk_path_subst": self.env.subst("${SDK_DIR_SUBST}"),
  59. "map_file_subst": self.MAP_FILE_SUBST,
  60. "hardware": self.env.subst("${TARGET_HW}"),
  61. }
  62. with open(json_manifest_path, "wt") as f:
  63. json.dump(meta_contents, f, indent=4)
  64. def _wrap_scons_vars(self, vars: str):
  65. expanded_vars = self.env.subst(
  66. vars,
  67. target=Entry(self.MAP_FILE_SUBST),
  68. )
  69. return path_as_posix(expanded_vars)
  70. class SdkTreeBuilder:
  71. SDK_DIR_SUBST = "SDK_ROOT_DIR"
  72. SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST"
  73. HEADER_EXTENSIONS = [".h", ".hpp"]
  74. def __init__(self, env, target, source) -> None:
  75. self.env = env
  76. self.target = target
  77. self.source = source
  78. self.header_depends = []
  79. self.header_dirs = []
  80. self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk")
  81. self.sdk_root_dir = target[0].Dir(".")
  82. self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name)
  83. self.sdk_env = self.env.Clone(
  84. APP_ENTRY=self.SDK_APP_EP_SUBST,
  85. SDK_DIR_SUBST=self.SDK_DIR_SUBST,
  86. )
  87. def _parse_sdk_depends(self):
  88. deps_file = self.source[0]
  89. with open(deps_file.path, "rt") as deps_f:
  90. lines = LogicalLines(deps_f).readlines()
  91. _, depends = lines[0].split(":", 1)
  92. self.header_depends = list(
  93. filter(
  94. lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)),
  95. depends.split(),
  96. ),
  97. )
  98. self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}"))
  99. self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}"))
  100. self.header_dirs = sorted(
  101. set(map(os.path.normpath, map(os.path.dirname, self.header_depends)))
  102. )
  103. def _generate_sdk_meta(self):
  104. filtered_paths = ["."]
  105. full_fw_paths = list(
  106. map(
  107. os.path.normpath,
  108. (
  109. self.sdk_env.Dir(inc_dir).relpath
  110. for inc_dir in self.sdk_env["CPPPATH"]
  111. ),
  112. )
  113. )
  114. sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs)
  115. filtered_paths.extend(
  116. filter(lambda path: path in sdk_dirs, full_fw_paths),
  117. )
  118. filtered_paths = list(map(self.build_sdk_file_path, filtered_paths))
  119. self.sdk_env.Replace(
  120. CPPPATH=filtered_paths,
  121. ORIG_LINKER_SCRIPT_PATH=self.env["LINKER_SCRIPT_PATH"],
  122. LINKER_SCRIPT_PATH=self.build_sdk_file_path("${ORIG_LINKER_SCRIPT_PATH}"),
  123. )
  124. meta = SdkMeta(self.sdk_env, self)
  125. meta.save_to(self.target[0].path)
  126. def build_sdk_file_path(self, orig_path: str) -> str:
  127. return path_as_posix(
  128. posixpath.normpath(
  129. posixpath.join(
  130. self.SDK_DIR_SUBST,
  131. self.target_sdk_dir_name,
  132. orig_path,
  133. )
  134. )
  135. )
  136. def emitter(self, target, source, env):
  137. target_folder = target[0]
  138. target = [target_folder.File("sdk.opts")]
  139. return target, source
  140. def _run_deploy_commands(self):
  141. dirs_to_create = set(
  142. self.sdk_deploy_dir.Dir(dirpath).path for dirpath in self.header_dirs
  143. )
  144. shutil.rmtree(self.sdk_root_dir.path, ignore_errors=False)
  145. for sdkdir in dirs_to_create:
  146. os.makedirs(sdkdir, exist_ok=True)
  147. for header in self.header_depends:
  148. shutil.copy2(header, self.sdk_deploy_dir.File(header).path)
  149. def deploy_action(self):
  150. self._parse_sdk_depends()
  151. self._run_deploy_commands()
  152. self._generate_sdk_meta()
  153. def deploy_sdk_header_tree_action(target, source, env):
  154. sdk_tree = SdkTreeBuilder(env, target, source)
  155. return sdk_tree.deploy_action()
  156. def deploy_sdk_header_tree_emitter(target, source, env):
  157. sdk_tree = SdkTreeBuilder(env, target, source)
  158. return sdk_tree.emitter(target, source, env)
  159. def gen_sdk_data(sdk_cache: SdkCache):
  160. api_def = []
  161. api_def.extend(
  162. (f"#include <{h.name}>" for h in sdk_cache.get_headers()),
  163. )
  164. api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};")
  165. api_def.append(
  166. "static constexpr auto elf_api_table = sort(create_array_t<sym_entry>("
  167. )
  168. api_lines = []
  169. for fun_def in sdk_cache.get_functions():
  170. api_lines.append(
  171. f"API_METHOD({fun_def.name}, {fun_def.returns}, ({fun_def.params}))"
  172. )
  173. for var_def in sdk_cache.get_variables():
  174. api_lines.append(f"API_VARIABLE({var_def.name}, {var_def.var_type })")
  175. api_def.append(",\n".join(api_lines))
  176. api_def.append("));")
  177. return api_def
  178. def _check_sdk_is_up2date(sdk_cache: SdkCache):
  179. if not sdk_cache.is_buildable():
  180. raise UserError(
  181. "SDK version is not finalized, please review changes and re-run operation. See AppsOnSDCard.md for more details"
  182. )
  183. def validate_api_cache(source, target, env):
  184. # print(f"Generating SDK for {source[0]} to {target[0]}")
  185. current_sdk = SdkCollector()
  186. current_sdk.process_source_file_for_sdk(source[0].path)
  187. for h in env["SDK_HEADERS"]:
  188. current_sdk.add_header_to_sdk(pathlib.Path(h.srcnode().path).as_posix())
  189. sdk_cache = SdkCache(target[0].path)
  190. sdk_cache.validate_api(current_sdk.get_api())
  191. sdk_cache.save()
  192. _check_sdk_is_up2date(sdk_cache)
  193. def generate_api_table(source, target, env):
  194. sdk_cache = SdkCache(source[0].path)
  195. _check_sdk_is_up2date(sdk_cache)
  196. api_def = gen_sdk_data(sdk_cache)
  197. with open(target[0].path, "wt") as f:
  198. f.write("\n".join(api_def))
  199. def generate(env, **kw):
  200. if not env["VERBOSE"]:
  201. env.SetDefault(
  202. SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}",
  203. SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}",
  204. SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}",
  205. APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}",
  206. SDKTREE_COMSTR="\tSDKTREE\t${TARGET}",
  207. )
  208. # Filtering out things cxxheaderparser cannot handle
  209. env.SetDefault(
  210. SDK_PP_FLAGS=[
  211. '-D"_Static_assert(x,y)="',
  212. '-D"__asm__(x)="',
  213. '-D"__attribute__(x)="',
  214. "-Drestrict=",
  215. "-D_Noreturn=",
  216. "-D__restrict=",
  217. "-D__extension__=",
  218. "-D__inline=inline",
  219. "-D__inline__=inline",
  220. ]
  221. )
  222. env.AddMethod(ProcessSdkDepends)
  223. env.Append(
  224. BUILDERS={
  225. "ApiAmalgamator": Builder(
  226. emitter=api_amalgam_emitter,
  227. action=[
  228. Action(
  229. api_amalgam_gen_origin_header,
  230. "$SDK_AMALGAMATE_HEADER_COMSTR",
  231. ),
  232. Action(
  233. "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c",
  234. "$SDK_AMALGAMATE_PP_COMSTR",
  235. ),
  236. ],
  237. suffix=".i",
  238. ),
  239. "SDKHeaderTreeExtractor": Builder(
  240. action=Action(
  241. deploy_sdk_header_tree_action,
  242. "$SDKTREE_COMSTR",
  243. ),
  244. emitter=deploy_sdk_header_tree_emitter,
  245. src_suffix=".d",
  246. ),
  247. "ApiTableValidator": Builder(
  248. action=Action(
  249. validate_api_cache,
  250. "$SDKSYM_UPDATER_COMSTR",
  251. ),
  252. suffix=".csv",
  253. src_suffix=".i",
  254. ),
  255. "ApiSymbolTable": Builder(
  256. action=Action(
  257. generate_api_table,
  258. "$APITABLE_GENERATOR_COMSTR",
  259. ),
  260. suffix=".h",
  261. src_suffix=".csv",
  262. ),
  263. }
  264. )
  265. def exists(env):
  266. return True