fbt_extapps.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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.sdk import SdkCache
  9. import itertools
  10. def BuildAppElf(env, app):
  11. work_dir = env.subst("$EXT_APPS_WORK_DIR")
  12. app_alias = f"{env['FIRMWARE_BUILD_CFG']}_{app.appid}"
  13. app_original_elf = os.path.join(work_dir, f"{app.appid}_d")
  14. app_sources = list(
  15. itertools.chain.from_iterable(
  16. env.GlobRecursive(source_type, os.path.join(work_dir, app._appdir.relpath))
  17. for source_type in app.sources
  18. )
  19. )
  20. app_elf_raw = env.Program(
  21. app_original_elf,
  22. app_sources,
  23. APP_ENTRY=app.entry_point,
  24. LIBS=env["LIBS"] + app.fap_libs,
  25. )
  26. app_elf_dump = env.ObjDump(app_elf_raw)
  27. env.Alias(f"{app_alias}_list", app_elf_dump)
  28. app_elf_augmented = env.EmbedAppMetadata(
  29. os.path.join(env.subst("$PLUGIN_ELF_DIR"), app.appid),
  30. app_elf_raw,
  31. APP=app,
  32. )
  33. manifest_vals = vars(app)
  34. manifest_vals = {
  35. k: v for k, v in manifest_vals.items() if k not in ("_appdir", "_apppath")
  36. }
  37. env.Depends(
  38. app_elf_augmented,
  39. [env["SDK_DEFINITION"], env.Value(manifest_vals)],
  40. )
  41. if app.fap_icon:
  42. env.Depends(
  43. app_elf_augmented,
  44. env.File(f"{app._apppath}/{app.fap_icon}"),
  45. )
  46. env.Alias(app_alias, app_elf_augmented)
  47. app_elf_import_validator = env.ValidateAppImports(app_elf_augmented)
  48. env.AlwaysBuild(app_elf_import_validator)
  49. env.Alias(app_alias, app_elf_import_validator)
  50. return (app_elf_augmented, app_elf_raw, app_elf_import_validator)
  51. def prepare_app_metadata(target, source, env):
  52. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True)
  53. if not sdk_cache.is_buildable():
  54. raise UserError(
  55. "SDK version is not finalized, please review changes and re-run operation"
  56. )
  57. app = env["APP"]
  58. meta_file_name = source[0].path + ".meta"
  59. with open(meta_file_name, "wb") as f:
  60. # f.write(f"hello this is {app}")
  61. f.write(
  62. assemble_manifest_data(
  63. app_manifest=app,
  64. hardware_target=int(env.subst("$TARGET_HW")),
  65. sdk_version=sdk_cache.version.as_int(),
  66. )
  67. )
  68. def validate_app_imports(target, source, env):
  69. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False)
  70. app_syms = set()
  71. with open(target[0].path, "rt") as f:
  72. for line in f:
  73. app_syms.add(line.split()[0])
  74. unresolved_syms = app_syms - sdk_cache.get_valid_names()
  75. if unresolved_syms:
  76. SCons.Warnings.warn(
  77. SCons.Warnings.LinkWarning,
  78. f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}",
  79. )
  80. def GetExtAppFromPath(env, app_dir):
  81. if not app_dir:
  82. raise UserError("APPSRC= not set")
  83. appmgr = env["APPMGR"]
  84. app = None
  85. for dir_part in reversed(pathlib.Path(app_dir).parts):
  86. if app := appmgr.find_by_appdir(dir_part):
  87. break
  88. if not app:
  89. raise UserError(f"Failed to resolve application for given APPSRC={app_dir}")
  90. app_elf = env["_extapps"]["compact"].get(app.appid, None)
  91. if not app_elf:
  92. raise UserError(
  93. f"Application {app.appid} is not configured for building as external"
  94. )
  95. app_validator = env["_extapps"]["validators"].get(app.appid, None)
  96. return (app, app_elf[0], app_validator[0])
  97. def generate(env, **kw):
  98. env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"))
  99. env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False)
  100. env.AddMethod(BuildAppElf)
  101. env.AddMethod(GetExtAppFromPath)
  102. env.Append(
  103. BUILDERS={
  104. "EmbedAppMetadata": Builder(
  105. action=[
  106. Action(prepare_app_metadata, "$APPMETA_COMSTR"),
  107. Action(
  108. "${OBJCOPY} "
  109. "--remove-section .ARM.attributes "
  110. "--add-section .fapmeta=${SOURCE}.meta "
  111. "--set-section-flags .fapmeta=contents,noload,readonly,data "
  112. "--strip-debug --strip-unneeded "
  113. "--add-gnu-debuglink=${SOURCE} "
  114. "${SOURCES} ${TARGET}",
  115. "$APPMETAEMBED_COMSTR",
  116. ),
  117. ],
  118. suffix=".fap",
  119. src_suffix=".elf",
  120. ),
  121. "ValidateAppImports": Builder(
  122. action=[
  123. Action(
  124. "@${NM} -P -u ${SOURCE} > ${TARGET}",
  125. None, # "$APPDUMP_COMSTR",
  126. ),
  127. Action(
  128. validate_app_imports,
  129. "$APPCHECK_COMSTR",
  130. ),
  131. ],
  132. suffix=".impsyms",
  133. src_suffix=".fap",
  134. ),
  135. }
  136. )
  137. def exists(env):
  138. return True