fbt_extapps.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. env.Depends(app_elf_augmented, [env["SDK_DEFINITION"], env.Value(app)])
  34. if app.fap_icon:
  35. env.Depends(
  36. app_elf_augmented,
  37. env.File(f"{app._apppath}/{app.fap_icon}"),
  38. )
  39. env.Alias(app_alias, app_elf_augmented)
  40. app_elf_import_validator = env.ValidateAppImports(app_elf_augmented)
  41. env.AlwaysBuild(app_elf_import_validator)
  42. return (app_elf_augmented, app_elf_raw, app_elf_import_validator)
  43. def prepare_app_metadata(target, source, env):
  44. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True)
  45. if not sdk_cache.is_buildable():
  46. raise UserError(
  47. "SDK version is not finalized, please review changes and re-run operation"
  48. )
  49. app = env["APP"]
  50. meta_file_name = source[0].path + ".meta"
  51. with open(meta_file_name, "wb") as f:
  52. # f.write(f"hello this is {app}")
  53. f.write(
  54. assemble_manifest_data(
  55. app_manifest=app,
  56. hardware_target=int(env.subst("$TARGET_HW")),
  57. sdk_version=sdk_cache.version.as_int(),
  58. )
  59. )
  60. def validate_app_imports(target, source, env):
  61. sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False)
  62. app_syms = set()
  63. with open(target[0].path, "rt") as f:
  64. for line in f:
  65. app_syms.add(line.split()[0])
  66. unresolved_syms = app_syms - sdk_cache.get_valid_names()
  67. if unresolved_syms:
  68. SCons.Warnings.warn(
  69. SCons.Warnings.LinkWarning,
  70. f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}",
  71. )
  72. def GetExtAppFromPath(env, app_dir):
  73. if not app_dir:
  74. raise UserError("APPSRC= not set")
  75. appmgr = env["APPMGR"]
  76. app = None
  77. for dir_part in reversed(pathlib.Path(app_dir).parts):
  78. if app := appmgr.find_by_appdir(dir_part):
  79. break
  80. if not app:
  81. raise UserError(f"Failed to resolve application for given APPSRC={app_dir}")
  82. app_elf = env["_extapps"]["compact"].get(app.appid, None)
  83. if not app_elf:
  84. raise UserError(f"No external app found for {app.appid}")
  85. return (app, app_elf[0])
  86. def generate(env, **kw):
  87. env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"))
  88. env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False)
  89. env.AddMethod(BuildAppElf)
  90. env.AddMethod(GetExtAppFromPath)
  91. env.Append(
  92. BUILDERS={
  93. "EmbedAppMetadata": Builder(
  94. action=[
  95. Action(prepare_app_metadata, "$APPMETA_COMSTR"),
  96. Action(
  97. "${OBJCOPY} "
  98. "--remove-section .ARM.attributes "
  99. "--add-section .fapmeta=${SOURCE}.meta "
  100. "--set-section-flags .fapmeta=contents,noload,readonly,data "
  101. "--strip-debug --strip-unneeded "
  102. "--add-gnu-debuglink=${SOURCE} "
  103. "${SOURCES} ${TARGET}",
  104. "$APPMETAEMBED_COMSTR",
  105. ),
  106. ],
  107. suffix=".fap",
  108. src_suffix=".elf",
  109. ),
  110. "ValidateAppImports": Builder(
  111. action=[
  112. Action(
  113. "@${NM} -P -u ${SOURCE} > ${TARGET}",
  114. None, # "$APPDUMP_COMSTR",
  115. ),
  116. Action(
  117. validate_app_imports,
  118. None, # "$APPCHECK_COMSTR",
  119. ),
  120. ],
  121. suffix=".impsyms",
  122. src_suffix=".fap",
  123. ),
  124. }
  125. )
  126. def exists(env):
  127. return True