fbt_extapps.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. from SCons.Builder import Builder
  2. from SCons.Action import Action
  3. from SCons.Errors import UserError
  4. import SCons.Warnings
  5. import os
  6. import pathlib
  7. from fbt.elfmanifest import assemble_manifest_data
  8. from fbt.appmanifest import FlipperManifestException
  9. from fbt.sdk import SdkCache
  10. import itertools
  11. from site_scons.fbt.appmanifest import FlipperApplication
  12. def BuildAppElf(env, app):
  13. ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR")
  14. app_work_dir = os.path.join(ext_apps_work_dir, app.appid)
  15. env.VariantDir(app_work_dir, app._appdir, duplicate=False)
  16. app_env = env.Clone(FAP_SRC_DIR=app._appdir, FAP_WORK_DIR=app_work_dir)
  17. app_alias = f"fap_{app.appid}"
  18. # Deprecation stub
  19. legacy_app_taget_name = f"{app_env['FIRMWARE_BUILD_CFG']}_{app.appid}"
  20. def legacy_app_build_stub(**kw):
  21. raise UserError(
  22. f"Target name '{legacy_app_taget_name}' is deprecated, use '{app_alias}' instead"
  23. )
  24. app_env.PhonyTarget(legacy_app_taget_name, Action(legacy_app_build_stub, None))
  25. externally_built_files = []
  26. if app.fap_extbuild:
  27. for external_file_def in app.fap_extbuild:
  28. externally_built_files.append(external_file_def.path)
  29. app_env.Alias(app_alias, external_file_def.path)
  30. app_env.AlwaysBuild(
  31. app_env.Command(
  32. external_file_def.path,
  33. None,
  34. Action(
  35. external_file_def.command,
  36. "" if app_env["VERBOSE"] else "\tEXTCMD\t${TARGET}",
  37. ),
  38. )
  39. )
  40. if app.fap_icon_assets:
  41. app_env.CompileIcons(
  42. app_env.Dir(app_work_dir),
  43. app._appdir.Dir(app.fap_icon_assets),
  44. icon_bundle_name=f"{app.appid}_icons",
  45. )
  46. private_libs = []
  47. for lib_def in app.fap_private_libs:
  48. lib_src_root_path = os.path.join(app_work_dir, "lib", lib_def.name)
  49. app_env.AppendUnique(
  50. CPPPATH=list(
  51. app_env.Dir(lib_src_root_path).Dir(incpath).srcnode()
  52. for incpath in lib_def.fap_include_paths
  53. ),
  54. )
  55. lib_sources = list(
  56. itertools.chain.from_iterable(
  57. app_env.GlobRecursive(source_type, lib_src_root_path)
  58. for source_type in lib_def.sources
  59. )
  60. )
  61. if len(lib_sources) == 0:
  62. raise UserError(f"No sources gathered for private library {lib_def}")
  63. private_lib_env = app_env.Clone()
  64. private_lib_env.AppendUnique(
  65. CCFLAGS=[
  66. *lib_def.cflags,
  67. ],
  68. CPPDEFINES=lib_def.cdefines,
  69. CPPPATH=list(
  70. os.path.join(app._appdir.path, cinclude)
  71. for cinclude in lib_def.cincludes
  72. ),
  73. )
  74. lib = private_lib_env.StaticLibrary(
  75. os.path.join(app_work_dir, lib_def.name),
  76. lib_sources,
  77. )
  78. private_libs.append(lib)
  79. app_sources = list(
  80. itertools.chain.from_iterable(
  81. app_env.GlobRecursive(
  82. source_type,
  83. app_work_dir,
  84. exclude="lib",
  85. )
  86. for source_type in app.sources
  87. )
  88. )
  89. app_env.Append(
  90. LIBS=[*app.fap_libs, *private_libs],
  91. CPPPATH=env.Dir(app_work_dir),
  92. )
  93. app_elf_raw = app_env.Program(
  94. os.path.join(app_work_dir, f"{app.appid}_d"),
  95. app_sources,
  96. APP_ENTRY=app.entry_point,
  97. )
  98. app_env.Clean(app_elf_raw, [*externally_built_files, app_env.Dir(app_work_dir)])
  99. app_elf_dump = app_env.ObjDump(app_elf_raw)
  100. app_env.Alias(f"{app_alias}_list", app_elf_dump)
  101. app_elf_augmented = app_env.EmbedAppMetadata(
  102. os.path.join(ext_apps_work_dir, app.appid),
  103. app_elf_raw,
  104. APP=app,
  105. )
  106. manifest_vals = {
  107. k: v
  108. for k, v in vars(app).items()
  109. if not k.startswith(FlipperApplication.PRIVATE_FIELD_PREFIX)
  110. }
  111. app_env.Depends(
  112. app_elf_augmented,
  113. [app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)],
  114. )
  115. if app.fap_icon:
  116. app_env.Depends(
  117. app_elf_augmented,
  118. app_env.File(f"{app._apppath}/{app.fap_icon}"),
  119. )
  120. app_elf_import_validator = app_env.ValidateAppImports(app_elf_augmented)
  121. app_env.AlwaysBuild(app_elf_import_validator)
  122. app_env.Alias(app_alias, app_elf_import_validator)
  123. return (app_elf_augmented, app_elf_raw, app_elf_import_validator)
  124. def prepare_app_metadata(target, source, env):
  125. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True)
  126. if not sdk_cache.is_buildable():
  127. raise UserError(
  128. "SDK version is not finalized, please review changes and re-run operation"
  129. )
  130. app = env["APP"]
  131. meta_file_name = source[0].path + ".meta"
  132. with open(meta_file_name, "wb") as f:
  133. # f.write(f"hello this is {app}")
  134. f.write(
  135. assemble_manifest_data(
  136. app_manifest=app,
  137. hardware_target=int(env.subst("$TARGET_HW")),
  138. sdk_version=sdk_cache.version.as_int(),
  139. )
  140. )
  141. def validate_app_imports(target, source, env):
  142. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False)
  143. app_syms = set()
  144. with open(target[0].path, "rt") as f:
  145. for line in f:
  146. app_syms.add(line.split()[0])
  147. unresolved_syms = app_syms - sdk_cache.get_valid_names()
  148. if unresolved_syms:
  149. SCons.Warnings.warn(
  150. SCons.Warnings.LinkWarning,
  151. f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}",
  152. )
  153. def GetExtAppFromPath(env, app_dir):
  154. if not app_dir:
  155. raise UserError("APPSRC= not set")
  156. appmgr = env["APPMGR"]
  157. app = None
  158. try:
  159. # Maybe used passed an appid?
  160. app = appmgr.get(app_dir)
  161. except FlipperManifestException as _:
  162. # Look up path components in known app dits
  163. for dir_part in reversed(pathlib.Path(app_dir).parts):
  164. if app := appmgr.find_by_appdir(dir_part):
  165. break
  166. if not app:
  167. raise UserError(f"Failed to resolve application for given APPSRC={app_dir}")
  168. app_elf = env["_extapps"]["compact"].get(app.appid, None)
  169. if not app_elf:
  170. raise UserError(
  171. f"Application {app.appid} is not configured for building as external"
  172. )
  173. app_validator = env["_extapps"]["validators"].get(app.appid, None)
  174. return (app, app_elf[0], app_validator[0])
  175. def generate(env, **kw):
  176. env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"))
  177. # env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False)
  178. env.AddMethod(BuildAppElf)
  179. env.AddMethod(GetExtAppFromPath)
  180. env.Append(
  181. BUILDERS={
  182. "EmbedAppMetadata": Builder(
  183. action=[
  184. Action(prepare_app_metadata, "$APPMETA_COMSTR"),
  185. Action(
  186. "${OBJCOPY} "
  187. "--remove-section .ARM.attributes "
  188. "--add-section .fapmeta=${SOURCE}.meta "
  189. "--set-section-flags .fapmeta=contents,noload,readonly,data "
  190. "--strip-debug --strip-unneeded "
  191. "--add-gnu-debuglink=${SOURCE} "
  192. "${SOURCES} ${TARGET}",
  193. "$APPMETAEMBED_COMSTR",
  194. ),
  195. ],
  196. suffix=".fap",
  197. src_suffix=".elf",
  198. ),
  199. "ValidateAppImports": Builder(
  200. action=[
  201. Action(
  202. "@${NM} -P -u ${SOURCE} > ${TARGET}",
  203. None, # "$APPDUMP_COMSTR",
  204. ),
  205. Action(
  206. validate_app_imports,
  207. "$APPCHECK_COMSTR",
  208. ),
  209. ],
  210. suffix=".impsyms",
  211. src_suffix=".fap",
  212. ),
  213. }
  214. )
  215. def exists(env):
  216. return True