unleashed-firmware/scripts/fbt_tools/fbt_sdk.py
hedger 9e42e00ead
Icons: compression fixes & larger dimension support (#3564)
* toolbox, gui: fixes for compressed icon handling
* ufbt: fixes for generated vscode project
* scripts: increased max dimensions for image converter
* icon type changes
* linter fixes; api sync
* gui: docs fix
* toolbox: fixed potential decoder buffer overflow
* minor cleanup
* fbt: sdk: suppressed deprecation warnings in API table
* toolbox: compress: added unit tests
   vscode: now installs resources for unit_tests
   unit_tests: now loads subghz region data
* toolbox: compress: review fixes, pt 1
* compress: now passes decoder buffer size as constructor argument; auto-resize decoder buffer; crash on failed icon decompression
* PVS fixes
* pvs fixes, pt2
* doxygen fixes
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* UnitTests: move all tests into plugins, brakes testing
* UnitTests: add plugin API and update plugin entrypoints
* UniTests: Test runner that works with plugins
* fbt: extra filtering for extapps to include in build
* UnitTests: filter tests by name
* loader: restored API table for unit_test build config
* Add various missing symbols to API table
* UnitTest: fail on plugin load error
* UnitTests: cleanup plugin api and reporting
* unit_tests: composite resolver
* UnitTests: remove unused declaration
* unit_tests, nfc: moved mock nfc implementation to libnfc
* unit_tests: api: removed redundant #define
* toolbox: compress: removed size_hint for icons; triggering furi_check on oversized icons
* gui: icon, icon_animation: removed size hit APIs
* Format Sources. Cleanup code.
* loader: refuse to start .fal as app
* toolbox: compress: fixed memory corruption in operations with small destination buffer; added unit tests for that case
* unit_tests: proper test skipping; better selective test interface
* unit_tests: moved 'loading' logging to proper location

Co-authored-by: あく <alleteam@gmail.com>
2024-05-20 18:23:47 +01:00

338 lines
10 KiB
Python

import json
import os.path
import pathlib
import posixpath
import shutil
from fbt.sdk.cache import SdkCache
from fbt.sdk.collector import SdkCollector
from fbt.util import PosixPathWrapper
from SCons.Action import Action
from SCons.Builder import Builder
from SCons.Errors import UserError
# from SCons.Scanner import C
from SCons.Script import Entry
from SCons.Util import LogicalLines
def ProcessSdkDepends(env, filename):
try:
with open(filename, "r") as fin:
lines = LogicalLines(fin).readlines()
except IOError:
return []
_, depends = lines[0].split(":", 1)
depends = depends.split()
depends.pop(0) # remove the .c file
depends = list(
# Don't create dependency on non-existing files
# (e.g. when they were renamed since last build)
filter(
lambda file: file.exists(),
(env.File(f"#{path}") for path in depends),
)
)
return depends
def _api_amalgam_emitter(target, source, env):
target.append(env.ChangeFileExtension(target[0], ".d"))
target.append(env.ChangeFileExtension(target[0], ".i.c"))
return target, source
def _api_amalgam_gen_origin_header(target, source, env):
mega_file = env.subst("${TARGET}.c", target=target[0])
with open(mega_file, "wt") as sdk_c:
sdk_c.write(
"\n".join(f"#include <{h.srcnode().path}>" for h in env["SDK_HEADERS"])
)
class SdkMeta:
MAP_FILE_SUBST = "SDK_MAP_FILE_SUBST"
def __init__(self, env, tree_builder: "SdkTreeBuilder"):
self.env = env
self.treebuilder = tree_builder
def save_to(self, json_manifest_path: str):
meta_contents = {
"sdk_symbols": self.treebuilder.build_sdk_file_path(
self.env["SDK_DEFINITION"].path
),
"cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"),
"cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"),
"linker_args": self._wrap_scons_vars("$LINKFLAGS"),
"linker_libs": self.env.subst("${LIBS}"),
"app_ep_subst": self.env.subst("${APP_ENTRY}"),
"sdk_path_subst": self.env.subst("${SDK_DIR_SUBST}"),
"map_file_subst": self.MAP_FILE_SUBST,
"hardware": self.env.subst("${TARGET_HW}"),
}
with open(json_manifest_path, "wt") as f:
json.dump(meta_contents, f, indent=4)
def _wrap_scons_vars(self, vars: str):
expanded_vars = self.env.subst(
vars,
target=Entry(self.MAP_FILE_SUBST),
)
return PosixPathWrapper.fixup_separators(expanded_vars)
class SdkTreeBuilder:
SDK_DIR_SUBST = "SDK_ROOT_DIR"
SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST"
HEADER_EXTENSIONS = [".h", ".hpp"]
def __init__(self, env, target, source) -> None:
self.env = env
self.target = target
self.source = source
self.header_depends = []
self.header_dirs = []
self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk")
self.sdk_root_dir = target[0].Dir(".")
self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name)
self.sdk_env = self.env.Clone(
APP_ENTRY=self.SDK_APP_EP_SUBST,
SDK_DIR_SUBST=self.SDK_DIR_SUBST,
)
def _parse_sdk_depends(self):
deps_file = self.source[0]
with open(deps_file.path, "rt") as deps_f:
lines = LogicalLines(deps_f).readlines()
_, depends = lines[0].split(":", 1)
self.header_depends = list(
filter(
lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)),
depends.split(),
),
)
self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}"))
self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}"))
self.header_dirs = sorted(
set(map(os.path.normpath, map(os.path.dirname, self.header_depends)))
)
def _generate_sdk_meta(self):
filtered_paths = ["."]
full_fw_paths = list(
map(
os.path.normpath,
(
self.sdk_env.Dir(inc_dir).relpath
for inc_dir in self.sdk_env["CPPPATH"]
),
)
)
sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs)
filtered_paths.extend(
filter(lambda path: path in sdk_dirs, full_fw_paths),
)
filtered_paths = list(map(self.build_sdk_file_path, filtered_paths))
self.sdk_env.Replace(
CPPPATH=filtered_paths,
ORIG_LINKER_SCRIPT_PATH=self.env["LINKER_SCRIPT_PATH"],
LINKER_SCRIPT_PATH=self.build_sdk_file_path("${ORIG_LINKER_SCRIPT_PATH}"),
)
meta = SdkMeta(self.sdk_env, self)
meta.save_to(self.target[0].path)
def build_sdk_file_path(self, orig_path: str) -> str:
return PosixPathWrapper.fix_path(
posixpath.normpath(
posixpath.join(
self.SDK_DIR_SUBST,
self.target_sdk_dir_name,
orig_path,
)
)
)
def emitter(self, target, source, env):
target_folder = target[0]
target = [target_folder.File("sdk.opts")]
return target, source
def _run_deploy_commands(self):
dirs_to_create = set(
self.sdk_deploy_dir.Dir(dirpath).path for dirpath in self.header_dirs
)
shutil.rmtree(self.sdk_root_dir.path, ignore_errors=False)
for sdkdir in dirs_to_create:
os.makedirs(sdkdir, exist_ok=True)
for header in self.header_depends:
shutil.copy2(header, self.sdk_deploy_dir.File(header).path)
def deploy_action(self):
self._parse_sdk_depends()
self._run_deploy_commands()
self._generate_sdk_meta()
def _deploy_sdk_header_tree_action(target, source, env):
sdk_tree = SdkTreeBuilder(env, target, source)
return sdk_tree.deploy_action()
def _deploy_sdk_header_tree_emitter(target, source, env):
sdk_tree = SdkTreeBuilder(env, target, source)
return sdk_tree.emitter(target, source, env)
def gen_sdk_data(sdk_cache: SdkCache):
api_def = []
api_def.extend(
(f"#include <{h.name}>" for h in sdk_cache.get_headers()),
)
api_def.append('#pragma GCC diagnostic ignored "-Wdeprecated-declarations"')
api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};")
api_def.append(
"static constexpr auto elf_api_table = sort(create_array_t<sym_entry>("
)
api_lines = []
for fun_def in sdk_cache.get_functions():
api_lines.append(
f"API_METHOD({fun_def.name}, {fun_def.returns}, ({fun_def.params}))"
)
for var_def in sdk_cache.get_variables():
api_lines.append(f"API_VARIABLE({var_def.name}, {var_def.var_type })")
api_def.append(",\n".join(api_lines))
api_def.append("));")
return api_def
def _check_sdk_is_up2date(sdk_cache: SdkCache):
if not sdk_cache.is_buildable():
raise UserError(
"SDK version is not finalized, please review changes and re-run operation. See AppsOnSDCard.md for more details"
)
def _validate_api_cache(source, target, env):
# print(f"Generating SDK for {source[0]} to {target[0]}")
current_sdk = SdkCollector()
current_sdk.process_source_file_for_sdk(source[0].path)
for h in env["SDK_HEADERS"]:
current_sdk.add_header_to_sdk(pathlib.Path(h.srcnode().path).as_posix())
sdk_cache = SdkCache(target[0].path)
sdk_cache.validate_api(current_sdk.get_api())
sdk_cache.save()
_check_sdk_is_up2date(sdk_cache)
def _generate_api_table(source, target, env):
sdk_cache = SdkCache(source[0].path)
_check_sdk_is_up2date(sdk_cache)
api_def = gen_sdk_data(sdk_cache)
with open(target[0].path, "wt") as f:
f.write("\n".join(api_def))
def generate(env, **kw):
if not env["VERBOSE"]:
env.SetDefault(
SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}",
SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}",
SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}",
APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}",
SDKTREE_COMSTR="\tSDKTREE\t${TARGET}",
)
# Filtering out things cxxheaderparser cannot handle
env.SetDefault(
SDK_PP_FLAGS=[
'-D"_Static_assert(x,y)="',
'-D"__asm__(x)="',
'-D"__attribute__(x)="',
"-Drestrict=",
"-D_Noreturn=",
"-D__restrict=",
"-D__extension__=",
"-D__inline=inline",
"-D__inline__=inline",
]
)
env.AddMethod(ProcessSdkDepends)
env.Append(
BUILDERS={
"ApiAmalgamator": Builder(
emitter=_api_amalgam_emitter,
action=[
Action(
_api_amalgam_gen_origin_header,
"$SDK_AMALGAMATE_HEADER_COMSTR",
),
Action(
[
[
"$CC",
"-o",
"$TARGET",
"-E",
"-P",
"$CCFLAGS",
"$_CCCOMCOM",
"$SDK_PP_FLAGS",
"-MMD",
"${TARGET}.c",
]
],
"$SDK_AMALGAMATE_PP_COMSTR",
),
],
suffix=".i",
),
"SDKHeaderTreeExtractor": Builder(
action=Action(
_deploy_sdk_header_tree_action,
"$SDKTREE_COMSTR",
),
emitter=_deploy_sdk_header_tree_emitter,
src_suffix=".d",
),
"ApiTableValidator": Builder(
action=Action(
_validate_api_cache,
"$SDKSYM_UPDATER_COMSTR",
),
suffix=".csv",
src_suffix=".i",
),
"ApiSymbolTable": Builder(
action=Action(
_generate_api_table,
"$APITABLE_GENERATOR_COMSTR",
),
suffix=".h",
src_suffix=".csv",
),
}
)
def exists(env):
return True