[FL-2832] fbt: more fixes & improvements (#1854)
* github: bundling debug folder with scripts; docs: fixes & updates; fbt: added FAP_EXAMPLES variable to enable building example apps. Disabled by default. fbt: added TERM to list of proxied environment variables * fbt: better help output; disabled implicit_deps_unchanged; added color to import validator reports * fbt: moved debug configuration to separate tool * fbt: proper dependency tracker for SDK source file; renamed linker script for external apps * fbt: fixed debug elf path * fbt: packaging sdk archive * scripts: fixed sconsdist.py * fbt: reworked sdk packing; docs: updates * docs: info on cli target; linter fixes * fbt: moved main code to scripts folder * scripts: packing update into .tgz * fbt, scripts: reworked copro_dist to build .tgz * scripts: fixed naming for archived updater package * Scripts: fix ぐるぐる回る Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
259
scripts/fbt_tools/fbt_extapps.py
Normal file
259
scripts/fbt_tools/fbt_extapps.py
Normal file
@@ -0,0 +1,259 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Errors import UserError
|
||||
import SCons.Warnings
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from fbt.elfmanifest import assemble_manifest_data
|
||||
from fbt.appmanifest import FlipperApplication, FlipperManifestException
|
||||
from fbt.sdk import SdkCache
|
||||
import itertools
|
||||
|
||||
|
||||
def BuildAppElf(env, app):
|
||||
ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR")
|
||||
app_work_dir = os.path.join(ext_apps_work_dir, app.appid)
|
||||
|
||||
env.VariantDir(app_work_dir, app._appdir, duplicate=False)
|
||||
|
||||
app_env = env.Clone(FAP_SRC_DIR=app._appdir, FAP_WORK_DIR=app_work_dir)
|
||||
|
||||
app_alias = f"fap_{app.appid}"
|
||||
|
||||
# Deprecation stub
|
||||
legacy_app_taget_name = f"{app_env['FIRMWARE_BUILD_CFG']}_{app.appid}"
|
||||
|
||||
def legacy_app_build_stub(**kw):
|
||||
raise UserError(
|
||||
f"Target name '{legacy_app_taget_name}' is deprecated, use '{app_alias}' instead"
|
||||
)
|
||||
|
||||
app_env.PhonyTarget(legacy_app_taget_name, Action(legacy_app_build_stub, None))
|
||||
|
||||
externally_built_files = []
|
||||
if app.fap_extbuild:
|
||||
for external_file_def in app.fap_extbuild:
|
||||
externally_built_files.append(external_file_def.path)
|
||||
app_env.Alias(app_alias, external_file_def.path)
|
||||
app_env.AlwaysBuild(
|
||||
app_env.Command(
|
||||
external_file_def.path,
|
||||
None,
|
||||
Action(
|
||||
external_file_def.command,
|
||||
"" if app_env["VERBOSE"] else "\tEXTCMD\t${TARGET}",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if app.fap_icon_assets:
|
||||
app_env.CompileIcons(
|
||||
app_env.Dir(app_work_dir),
|
||||
app._appdir.Dir(app.fap_icon_assets),
|
||||
icon_bundle_name=f"{app.appid}_icons",
|
||||
)
|
||||
|
||||
private_libs = []
|
||||
|
||||
for lib_def in app.fap_private_libs:
|
||||
lib_src_root_path = os.path.join(app_work_dir, "lib", lib_def.name)
|
||||
app_env.AppendUnique(
|
||||
CPPPATH=list(
|
||||
app_env.Dir(lib_src_root_path).Dir(incpath).srcnode()
|
||||
for incpath in lib_def.fap_include_paths
|
||||
),
|
||||
)
|
||||
|
||||
lib_sources = list(
|
||||
itertools.chain.from_iterable(
|
||||
app_env.GlobRecursive(source_type, lib_src_root_path)
|
||||
for source_type in lib_def.sources
|
||||
)
|
||||
)
|
||||
if len(lib_sources) == 0:
|
||||
raise UserError(f"No sources gathered for private library {lib_def}")
|
||||
|
||||
private_lib_env = app_env.Clone()
|
||||
private_lib_env.AppendUnique(
|
||||
CCFLAGS=[
|
||||
*lib_def.cflags,
|
||||
],
|
||||
CPPDEFINES=lib_def.cdefines,
|
||||
CPPPATH=list(
|
||||
os.path.join(app._appdir.path, cinclude)
|
||||
for cinclude in lib_def.cincludes
|
||||
),
|
||||
)
|
||||
|
||||
lib = private_lib_env.StaticLibrary(
|
||||
os.path.join(app_work_dir, lib_def.name),
|
||||
lib_sources,
|
||||
)
|
||||
private_libs.append(lib)
|
||||
|
||||
app_sources = list(
|
||||
itertools.chain.from_iterable(
|
||||
app_env.GlobRecursive(
|
||||
source_type,
|
||||
app_work_dir,
|
||||
exclude="lib",
|
||||
)
|
||||
for source_type in app.sources
|
||||
)
|
||||
)
|
||||
|
||||
app_env.Append(
|
||||
LIBS=[*app.fap_libs, *private_libs],
|
||||
CPPPATH=env.Dir(app_work_dir),
|
||||
)
|
||||
|
||||
app_elf_raw = app_env.Program(
|
||||
os.path.join(ext_apps_work_dir, f"{app.appid}_d"),
|
||||
app_sources,
|
||||
APP_ENTRY=app.entry_point,
|
||||
)
|
||||
|
||||
app_env.Clean(app_elf_raw, [*externally_built_files, app_env.Dir(app_work_dir)])
|
||||
|
||||
app_elf_dump = app_env.ObjDump(app_elf_raw)
|
||||
app_env.Alias(f"{app_alias}_list", app_elf_dump)
|
||||
|
||||
app_elf_augmented = app_env.EmbedAppMetadata(
|
||||
os.path.join(ext_apps_work_dir, app.appid),
|
||||
app_elf_raw,
|
||||
APP=app,
|
||||
)
|
||||
|
||||
manifest_vals = {
|
||||
k: v
|
||||
for k, v in vars(app).items()
|
||||
if not k.startswith(FlipperApplication.PRIVATE_FIELD_PREFIX)
|
||||
}
|
||||
|
||||
app_env.Depends(
|
||||
app_elf_augmented,
|
||||
[app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)],
|
||||
)
|
||||
if app.fap_icon:
|
||||
app_env.Depends(
|
||||
app_elf_augmented,
|
||||
app_env.File(f"{app._apppath}/{app.fap_icon}"),
|
||||
)
|
||||
|
||||
app_elf_import_validator = app_env.ValidateAppImports(app_elf_augmented)
|
||||
app_env.AlwaysBuild(app_elf_import_validator)
|
||||
app_env.Alias(app_alias, app_elf_import_validator)
|
||||
return (app_elf_augmented, app_elf_raw, app_elf_import_validator)
|
||||
|
||||
|
||||
def prepare_app_metadata(target, source, env):
|
||||
sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True)
|
||||
|
||||
if not sdk_cache.is_buildable():
|
||||
raise UserError(
|
||||
"SDK version is not finalized, please review changes and re-run operation"
|
||||
)
|
||||
|
||||
app = env["APP"]
|
||||
meta_file_name = source[0].path + ".meta"
|
||||
with open(meta_file_name, "wb") as f:
|
||||
# f.write(f"hello this is {app}")
|
||||
f.write(
|
||||
assemble_manifest_data(
|
||||
app_manifest=app,
|
||||
hardware_target=int(env.subst("$TARGET_HW")),
|
||||
sdk_version=sdk_cache.version.as_int(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def validate_app_imports(target, source, env):
|
||||
sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False)
|
||||
app_syms = set()
|
||||
with open(target[0].path, "rt") as f:
|
||||
for line in f:
|
||||
app_syms.add(line.split()[0])
|
||||
unresolved_syms = app_syms - sdk_cache.get_valid_names()
|
||||
if unresolved_syms:
|
||||
SCons.Warnings.warn(
|
||||
SCons.Warnings.LinkWarning,
|
||||
f"\033[93m{source[0].path}: app won't run. Unresolved symbols: \033[95m{unresolved_syms}\033[0m",
|
||||
)
|
||||
|
||||
|
||||
def GetExtAppFromPath(env, app_dir):
|
||||
if not app_dir:
|
||||
raise UserError("APPSRC= not set")
|
||||
|
||||
appmgr = env["APPMGR"]
|
||||
|
||||
app = None
|
||||
try:
|
||||
# Maybe used passed an appid?
|
||||
app = appmgr.get(app_dir)
|
||||
except FlipperManifestException as _:
|
||||
# Look up path components in known app dits
|
||||
for dir_part in reversed(pathlib.Path(app_dir).parts):
|
||||
if app := appmgr.find_by_appdir(dir_part):
|
||||
break
|
||||
|
||||
if not app:
|
||||
raise UserError(f"Failed to resolve application for given APPSRC={app_dir}")
|
||||
|
||||
app_elf = env["_extapps"]["compact"].get(app.appid, None)
|
||||
if not app_elf:
|
||||
raise UserError(
|
||||
f"Application {app.appid} is not configured for building as external"
|
||||
)
|
||||
|
||||
app_validator = env["_extapps"]["validators"].get(app.appid, None)
|
||||
|
||||
return (app, app_elf[0], app_validator[0])
|
||||
|
||||
|
||||
def generate(env, **kw):
|
||||
env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"))
|
||||
# env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False)
|
||||
|
||||
env.AddMethod(BuildAppElf)
|
||||
env.AddMethod(GetExtAppFromPath)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"EmbedAppMetadata": Builder(
|
||||
action=[
|
||||
Action(prepare_app_metadata, "$APPMETA_COMSTR"),
|
||||
Action(
|
||||
"${OBJCOPY} "
|
||||
"--remove-section .ARM.attributes "
|
||||
"--add-section .fapmeta=${SOURCE}.meta "
|
||||
"--set-section-flags .fapmeta=contents,noload,readonly,data "
|
||||
"--strip-debug --strip-unneeded "
|
||||
"--add-gnu-debuglink=${SOURCE} "
|
||||
"${SOURCES} ${TARGET}",
|
||||
"$APPMETAEMBED_COMSTR",
|
||||
),
|
||||
],
|
||||
suffix=".fap",
|
||||
src_suffix=".elf",
|
||||
),
|
||||
"ValidateAppImports": Builder(
|
||||
action=[
|
||||
Action(
|
||||
"@${NM} -P -u ${SOURCE} > ${TARGET}",
|
||||
None, # "$APPDUMP_COMSTR",
|
||||
),
|
||||
Action(
|
||||
validate_app_imports,
|
||||
"$APPCHECK_COMSTR",
|
||||
),
|
||||
],
|
||||
suffix=".impsyms",
|
||||
src_suffix=".fap",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
Reference in New Issue
Block a user