This commit is contained in:
Willy-JL
2023-09-30 19:25:50 +01:00
154 changed files with 3223 additions and 2939 deletions
+18
View File
@@ -79,11 +79,19 @@ class FlipperApplication:
fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list)
fap_private_libs: List[Library] = field(default_factory=list)
fap_file_assets: Optional[str] = None
fal_embedded: bool = False
# Internally used by fbt
_appmanager: Optional["AppManager"] = None
_appdir: Optional[object] = None
_apppath: Optional[str] = None
_plugins: List["FlipperApplication"] = field(default_factory=list)
_assets_dirs: List[object] = field(default_factory=list)
_section_fapmeta: Optional[object] = None
_section_fapfileassets: Optional[object] = None
@property
def embeds_plugins(self):
return any(plugin.fal_embedded for plugin in self._plugins)
def supports_hardware_target(self, target: str):
return target in self.targets or "all" in self.targets
@@ -137,6 +145,16 @@ class AppManager:
raise FlipperManifestException(
f"Plugin {kw.get('appid')} must have 'requires' in manifest"
)
else:
if kw.get("fal_embedded"):
raise FlipperManifestException(
f"App {kw.get('appid')} cannot have fal_embedded set"
)
# Harmless - cdefines for external apps are meaningless
# if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"):
# raise FlipperManifestException(
# f"External app {kw.get('appid')} must not have 'cdefines' in manifest"
# )
def load_manifest(self, app_manifest_path: str, app_dir_node: object):
if not os.path.exists(app_manifest_path):
+16 -12
View File
@@ -1,7 +1,7 @@
import hashlib
import os
import struct
from typing import TypedDict
from typing import TypedDict, List
class File(TypedDict):
@@ -32,20 +32,19 @@ class FileBundler:
u8[] file_content
"""
def __init__(self, directory_path: str):
self.directory_path = directory_path
self.file_list: list[File] = []
self.directory_list: list[Dir] = []
self._gather()
def __init__(self, assets_dirs: List[object]):
self.src_dirs = list(assets_dirs)
def _gather(self):
for root, dirs, files in os.walk(self.directory_path):
def _gather(self, directory_path: str):
if not os.path.isdir(directory_path):
raise Exception(f"Assets directory {directory_path} does not exist")
for root, dirs, files in os.walk(directory_path):
for file_info in files:
file_path = os.path.join(root, file_info)
file_size = os.path.getsize(file_path)
self.file_list.append(
{
"path": os.path.relpath(file_path, self.directory_path),
"path": os.path.relpath(file_path, directory_path),
"size": file_size,
"content_path": file_path,
}
@@ -57,15 +56,20 @@ class FileBundler:
# os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path)
# )
self.directory_list.append(
{
"path": os.path.relpath(dir_path, self.directory_path),
}
{"path": os.path.relpath(dir_path, directory_path)}
)
self.file_list.sort(key=lambda f: f["path"])
self.directory_list.sort(key=lambda d: d["path"])
def _process_src_dirs(self):
self.file_list: list[File] = []
self.directory_list: list[Dir] = []
for directory_path in self.src_dirs:
self._gather(directory_path)
def export(self, target_path: str):
self._process_src_dirs()
self._md5_hash = hashlib.md5()
with open(target_path, "wb") as f:
# Write header magic and version
+23 -11
View File
@@ -29,22 +29,26 @@ which is the name that most clang tools search for by default.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
import json
import itertools
import fnmatch
import itertools
import json
from shlex import quote
import SCons
from SCons.Tool.cxx import CXXSuffixes
from SCons.Tool.asm import ASPPSuffixes, ASSuffixes
from SCons.Tool.cc import CSuffixes
from SCons.Tool.asm import ASSuffixes, ASPPSuffixes
from SCons.Tool.cxx import CXXSuffixes
# TODO FL-3542: Is there a better way to do this than this global? Right now this exists so that the
# TODO: (-nofl) Is there a better way to do this than this global? Right now this exists so that the
# emitter we add can record all of the things it emits, so that the scanner for the top level
# compilation database can access the complete list, and also so that the writer has easy
# access to write all of the files. But it seems clunky. How can the emitter and the scanner
# communicate more gracefully?
__COMPILATION_DB_ENTRIES = []
# We cache the tool path lookups to avoid doing them over and over again.
_TOOL_PATH_CACHE = {}
# We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
# integrate with the cache, but there doesn't seem to be much call for it.
@@ -91,7 +95,7 @@ def make_emit_compilation_DB_entry(comstr):
__COMPILATIONDB_ENV=env,
)
# TODO FL-3541: Technically, these next two lines should not be required: it should be fine to
# TODO: (-nofl) Technically, these next two lines should not be required: it should be fine to
# cache the entries. However, they don't seem to update properly. Since they are quick
# to re-generate disable caching and sidestep this problem.
env.AlwaysBuild(entry)
@@ -122,6 +126,17 @@ def compilation_db_entry_action(target, source, env, **kw):
env=env["__COMPILATIONDB_ENV"],
)
# We assume first non-space character is the executable
executable = command.split(" ", 1)[0]
if not (tool_path := _TOOL_PATH_CACHE.get(executable, None)):
tool_path = env.WhereIs(executable) or executable
_TOOL_PATH_CACHE[executable] = tool_path
# If there are spaces in the executable path, we need to quote it
if " " in tool_path:
tool_path = quote(tool_path)
# Replacing the executable with the full path
command = tool_path + command[len(executable) :]
entry = {
"directory": env.Dir("#").abspath,
"command": command,
@@ -242,10 +257,7 @@ def generate(env, **kwargs):
for entry in components_by_suffix:
suffix = entry[0]
builder, base_emitter, command = entry[1]
# Assumes a dictionary emitter
emitter = builder.emitter.get(suffix, False)
if emitter:
if emitter := builder.emitter.get(suffix, False):
# We may not have tools installed which initialize all or any of
# cxx, cc, or assembly. If not skip resetting the respective emitter.
builder.emitter[suffix] = SCons.Builder.ListEmitter(
+8 -5
View File
@@ -2,8 +2,6 @@ import subprocess
import gdb
import objdump
import shutil
import strip
from SCons.Action import _subproc
from SCons.Errors import StopError
@@ -13,20 +11,25 @@ from SCons.Tool import ar, asm, gcc, gnulink, gxx
def prefix_commands(env, command_prefix, cmd_list):
for command in cmd_list:
if command in env:
env[command] = shutil.which(command_prefix + env[command])
prefixed_binary = command_prefix + env[command]
if not env.WhereIs(prefixed_binary):
raise StopError(
f"Toolchain binary {prefixed_binary} not found in PATH."
)
env.Replace(**{command: prefixed_binary})
def _get_tool_version(env, tool):
verstr = "version unknown"
proc = _subproc(
env,
env.subst("${%s} --version" % tool),
[env.subst("${%s}" % tool), "--version"],
stdout=subprocess.PIPE,
stderr="devnull",
stdin="devnull",
universal_newlines=True,
error="raise",
shell=True,
shell=False,
)
if proc:
verstr = proc.stdout.readline()
+62 -43
View File
@@ -3,7 +3,7 @@ import os
import pathlib
import shutil
from dataclasses import dataclass, field
from typing import Optional, Dict, List
from typing import Dict, List, Optional
import SCons.Warnings
from ansi.color import fg
@@ -32,11 +32,15 @@ class FlipperExternalAppInfo:
class AppBuilder:
@staticmethod
def get_app_work_dir(env, app):
return env["EXT_APPS_WORK_DIR"].Dir(app.appid)
def __init__(self, env, app):
self.fw_env = env
self.app = app
self.ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR")
self.app_work_dir = os.path.join(self.ext_apps_work_dir, self.app.appid)
self.ext_apps_work_dir = env["EXT_APPS_WORK_DIR"]
self.app_work_dir = self.get_app_work_dir(env, app)
self.app_alias = f"fap_{self.app.appid}"
self.externally_built_files = []
self.private_libs = []
@@ -83,9 +87,9 @@ class AppBuilder:
return
fap_icons = self.app_env.CompileIcons(
self.app_env.Dir(self.app_work_dir),
self.app_work_dir,
self.app._appdir.Dir(self.app.fap_icon_assets),
icon_bundle_name=f"{self.app.fap_icon_assets_symbol if self.app.fap_icon_assets_symbol else self.app.appid }_icons",
icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid }_icons",
)
self.app_env.Alias("_fap_icons", fap_icons)
self.fw_env.Append(_APP_ICONS=[fap_icons])
@@ -95,7 +99,7 @@ class AppBuilder:
self.private_libs.append(self._build_private_lib(lib_def))
def _build_private_lib(self, lib_def):
lib_src_root_path = os.path.join(self.app_work_dir, "lib", lib_def.name)
lib_src_root_path = self.app_work_dir.Dir("lib").Dir(lib_def.name)
self.app_env.AppendUnique(
CPPPATH=list(
self.app_env.Dir(lib_src_root_path)
@@ -119,9 +123,7 @@ class AppBuilder:
private_lib_env = self.app_env.Clone()
private_lib_env.AppendUnique(
CCFLAGS=[
*lib_def.cflags,
],
CCFLAGS=lib_def.cflags,
CPPDEFINES=lib_def.cdefines,
CPPPATH=list(
map(
@@ -132,14 +134,17 @@ class AppBuilder:
)
return private_lib_env.StaticLibrary(
os.path.join(self.app_work_dir, lib_def.name),
self.app_work_dir.File(lib_def.name),
lib_sources,
)
def _build_app(self):
if self.app.fap_file_assets:
self.app._assets_dirs = [self.app._appdir.Dir(self.app.fap_file_assets)]
self.app_env.Append(
LIBS=[*self.app.fap_libs, *self.private_libs],
CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir],
CPPPATH=[self.app_work_dir, self.app._appdir],
)
app_sources = list(
@@ -155,32 +160,46 @@ class AppBuilder:
app_artifacts = FlipperExternalAppInfo(self.app)
app_artifacts.debug = self.app_env.Program(
os.path.join(self.ext_apps_work_dir, f"{self.app.appid}_d"),
self.ext_apps_work_dir.File(f"{self.app.appid}_d.elf"),
app_sources,
APP_ENTRY=self.app.entry_point,
)[0]
app_artifacts.compact = self.app_env.EmbedAppMetadata(
os.path.join(self.ext_apps_work_dir, self.app.appid),
self.ext_apps_work_dir.File(f"{self.app.appid}.fap"),
app_artifacts.debug,
APP=self.app,
)[0]
if self.app.embeds_plugins:
self.app._assets_dirs.append(self.app_work_dir.Dir("assets"))
app_artifacts.validator = self.app_env.ValidateAppImports(
app_artifacts.compact
)[0]
if self.app.apptype == FlipperAppType.PLUGIN:
for parent_app_id in self.app.requires:
fal_path = (
f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}"
)
deployable = True
# If it's a plugin for a non-deployable app, don't include it in the resources
if parent_app := self.app._appmanager.get(parent_app_id):
if not parent_app.is_default_deployable:
deployable = False
app_artifacts.dist_entries.append((deployable, fal_path))
if self.app.fal_embedded:
parent_app = self.app._appmanager.get(parent_app_id)
if not parent_app:
raise UserError(
f"Embedded plugin {self.app.appid} requires unknown app {parent_app_id}"
)
self.app_env.Install(
target=self.get_app_work_dir(self.app_env, parent_app)
.Dir("assets")
.Dir("plugins"),
source=app_artifacts.compact,
)
else:
fal_path = f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}"
deployable = True
# If it's a plugin for a non-deployable app, don't include it in the resources
if parent_app := self.app._appmanager.get(parent_app_id):
if not parent_app.is_default_deployable:
deployable = False
app_artifacts.dist_entries.append((deployable, fal_path))
else:
fap_path = f"apps/{self.app.fap_category}/{app_artifacts.compact.name}"
app_artifacts.dist_entries.append(
@@ -194,7 +213,7 @@ class AppBuilder:
# Extra things to clean up along with the app
self.app_env.Clean(
app_artifacts.debug,
[*self.externally_built_files, self.app_env.Dir(self.app_work_dir)],
[*self.externally_built_files, self.app_work_dir],
)
# Create listing of the app
@@ -219,13 +238,10 @@ class AppBuilder:
)
# Add dependencies on file assets
if self.app.fap_file_assets:
for assets_dir in self.app._assets_dirs:
self.app_env.Depends(
app_artifacts.compact,
self.app_env.GlobRecursive(
"*",
self.app._appdir.Dir(self.app.fap_file_assets),
),
(assets_dir, self.app_env.GlobRecursive("*", assets_dir)),
)
# Always run the validator for the app's binary when building the app
@@ -344,25 +360,26 @@ def embed_app_metadata_emitter(target, source, env):
if app.apptype == FlipperAppType.PLUGIN:
target[0].name = target[0].name.replace(".fap", ".fal")
target.append(env.File(source[0].abspath + _FAP_META_SECTION))
app_work_dir = AppBuilder.get_app_work_dir(env, app)
app._section_fapmeta = app_work_dir.File(_FAP_META_SECTION)
target.append(app._section_fapmeta)
if app.fap_file_assets:
target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION))
# At this point, we haven't added dir with embedded plugins to _assets_dirs yet
if app._assets_dirs or app.embeds_plugins:
app._section_fapfileassets = app_work_dir.File(_FAP_FILEASSETS_SECTION)
target.append(app._section_fapfileassets)
return (target, source)
def prepare_app_files(target, source, env):
def prepare_app_file_assets(target, source, env):
files_section_node = next(
filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target)
)
app = env["APP"]
directory = env.Dir(app._apppath).Dir(app.fap_file_assets)
if not directory.exists():
raise UserError(f"File asset directory {directory} does not exist")
bundler = FileBundler(directory.abspath)
bundler = FileBundler(
list(env.Dir(asset_dir).abspath for asset_dir in env["APP"]._assets_dirs)
)
bundler.export(files_section_node.abspath)
@@ -376,12 +393,14 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature):
objcopy_str = (
"${OBJCOPY} "
"--remove-section .ARM.attributes "
"--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} "
"--add-section ${_FAP_META_SECTION}=${APP._section_fapmeta} "
)
if app.fap_file_assets:
actions.append(Action(prepare_app_files, "$APPFILE_COMSTR"))
objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} "
if app._section_fapfileassets:
actions.append(Action(prepare_app_file_assets, "$APPFILE_COMSTR"))
objcopy_str += (
"--add-section ${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets} "
)
objcopy_str += (
"--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data "
@@ -470,7 +489,7 @@ def AddAppBuildTarget(env, appname, build_target_name):
def generate(env, **kw):
env.SetDefault(
EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}",
EXT_APPS_WORK_DIR=env.Dir(env["FBT_FAP_DEBUG_ELF_ROOT"]),
APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py",
)
if not env["VERBOSE"]:
+1
View File
@@ -48,6 +48,7 @@ def generate(env):
"@.pvsoptions",
"-j${PVSNCORES}",
# "--incremental", # kinda broken on PVS side
"--disableLicenseExpirationCheck",
],
PVSCONVOPTIONS=[
"-a",
+3 -3
View File
@@ -14,15 +14,15 @@ IWDGSTOP:0x1:rw
IWDGSW:0x1:rw
IPCCDBA:0x0:rw
ESE:0x1:r
SFSA:0xD5:r
SFSA:0xD7:r
FSD:0x0:r
DDS:0x1:r
C2OPT:0x1:r
NBRSD:0x0:r
SNBRSA:0xD:r
SNBRSA:0xB:r
BRSD:0x0:r
SBRSA:0x12:r
SBRV:0x35400:r
SBRV:0x35C00:r
PCROP1A_STRT:0x1FF:r
PCROP1A_END:0x0:r
PCROP_RDP:0x1:rw
+10
View File
@@ -74,6 +74,9 @@ class Main(App):
self.parser_generate.add_argument(
"--I-understand-what-I-am-doing", dest="disclaimer", required=False
)
self.parser_generate.add_argument(
"--stackversion", dest="stack_version", required=False, default=""
)
self.parser_generate.set_defaults(func=self.generate)
@@ -94,6 +97,13 @@ class Main(App):
if not self.args.radiotype:
raise ValueError("Missing --radiotype")
radio_meta = CoproBinary(self.args.radiobin)
if self.args.stack_version:
actual_stack_version_str = f"{radio_meta.img_sig.version_major}.{radio_meta.img_sig.version_minor}.{radio_meta.img_sig.version_sub}"
if actual_stack_version_str != self.args.stack_version:
self.logger.error(
f"Stack version mismatch: expected {self.args.stack_version}, actual {actual_stack_version_str}"
)
return 1
radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype)
if (
get_stack_type(self.args.radiotype) not in self.WHITELISTED_STACK_TYPES