mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-17 00:28:11 -07:00
Merge branch 'dev' of https://github.com/DarkFlippers/unleashed-firmware into dev --nobuild
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]:
|
||||
|
||||
@@ -48,6 +48,7 @@ def generate(env):
|
||||
"@.pvsoptions",
|
||||
"-j${PVSNCORES}",
|
||||
# "--incremental", # kinda broken on PVS side
|
||||
"--disableLicenseExpirationCheck",
|
||||
],
|
||||
PVSCONVOPTIONS=[
|
||||
"-a",
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user