fbt: fixes for ufbt compat (#1940)

* fbt: split sdk management code
* scripts: fixed import handling
* fbt: sdk: reformatted paths
* scrips: dist: bundling libs as a build artifact
* fbt: sdk: better path management
* typo fix
* fbt: sdk: minor path handling fixes
* toolchain: fixed windows toolchain download

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
hedger 2022-10-28 19:32:06 +04:00 committed by GitHub
parent 9cd0592aaf
commit 4b921803cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 383 additions and 341 deletions

View File

@ -178,23 +178,6 @@ sources.extend(
)
)
fwenv.AppendUnique(
LINKFLAGS=[
"-specs=nano.specs",
"-specs=nosys.specs",
"-Wl,--gc-sections",
"-Wl,--undefined=uxTopUsedPriority",
"-Wl,--wrap,_malloc_r",
"-Wl,--wrap,_free_r",
"-Wl,--wrap,_calloc_r",
"-Wl,--wrap,_realloc_r",
"-n",
"-Xlinker",
"-Map=${TARGET}.map",
],
)
# Debug
# print(fwenv.Dump())

View File

@ -0,0 +1,44 @@
from typing import Set, ClassVar
from dataclasses import dataclass, field
@dataclass(frozen=True)
class ApiEntryFunction:
name: str
returns: str
params: str
csv_type: ClassVar[str] = "Function"
def dictify(self):
return dict(name=self.name, type=self.returns, params=self.params)
@dataclass(frozen=True)
class ApiEntryVariable:
name: str
var_type: str
csv_type: ClassVar[str] = "Variable"
def dictify(self):
return dict(name=self.name, type=self.var_type, params=None)
@dataclass(frozen=True)
class ApiHeader:
name: str
csv_type: ClassVar[str] = "Header"
def dictify(self):
return dict(name=self.name, type=None, params=None)
@dataclass
class ApiEntries:
# These are sets, to avoid creating duplicates when we have multiple
# declarations with same signature
functions: Set[ApiEntryFunction] = field(default_factory=set)
variables: Set[ApiEntryVariable] = field(default_factory=set)
headers: Set[ApiHeader] = field(default_factory=set)

View File

@ -4,284 +4,18 @@ import csv
import operator
from enum import Enum, auto
from typing import List, Set, ClassVar, Any
from dataclasses import dataclass, field
from typing import Set, ClassVar, Any
from dataclasses import dataclass
from ansi.color import fg
from cxxheaderparser.parser import CxxParser
# 'Fixing' complaints about typedefs
CxxParser._fundamentals.discard("wchar_t")
from cxxheaderparser.types import (
EnumDecl,
Field,
ForwardDecl,
FriendDecl,
Function,
Method,
Typedef,
UsingAlias,
UsingDecl,
Variable,
Pointer,
Type,
PQName,
NameSpecifier,
FundamentalSpecifier,
Parameter,
Array,
Value,
Token,
FunctionType,
from . import (
ApiEntries,
ApiEntryFunction,
ApiEntryVariable,
ApiHeader,
)
from cxxheaderparser.parserstate import (
State,
EmptyBlockState,
ClassBlockState,
ExternBlockState,
NamespaceBlockState,
)
@dataclass(frozen=True)
class ApiEntryFunction:
name: str
returns: str
params: str
csv_type: ClassVar[str] = "Function"
def dictify(self):
return dict(name=self.name, type=self.returns, params=self.params)
@dataclass(frozen=True)
class ApiEntryVariable:
name: str
var_type: str
csv_type: ClassVar[str] = "Variable"
def dictify(self):
return dict(name=self.name, type=self.var_type, params=None)
@dataclass(frozen=True)
class ApiHeader:
name: str
csv_type: ClassVar[str] = "Header"
def dictify(self):
return dict(name=self.name, type=None, params=None)
@dataclass
class ApiEntries:
# These are sets, to avoid creating duplicates when we have multiple
# declarations with same signature
functions: Set[ApiEntryFunction] = field(default_factory=set)
variables: Set[ApiEntryVariable] = field(default_factory=set)
headers: Set[ApiHeader] = field(default_factory=set)
class SymbolManager:
def __init__(self):
self.api = ApiEntries()
self.name_hashes = set()
# Calculate hash of name and raise exception if it already is in the set
def _name_check(self, name: str):
name_hash = gnu_sym_hash(name)
if name_hash in self.name_hashes:
raise Exception(f"Hash collision on {name}")
self.name_hashes.add(name_hash)
def add_function(self, function_def: ApiEntryFunction):
if function_def in self.api.functions:
return
self._name_check(function_def.name)
self.api.functions.add(function_def)
def add_variable(self, variable_def: ApiEntryVariable):
if variable_def in self.api.variables:
return
self._name_check(variable_def.name)
self.api.variables.add(variable_def)
def add_header(self, header: str):
self.api.headers.add(ApiHeader(header))
def gnu_sym_hash(name: str):
h = 0x1505
for c in name:
h = (h << 5) + h + ord(c)
return str(hex(h))[-8:]
class SdkCollector:
def __init__(self):
self.symbol_manager = SymbolManager()
def add_header_to_sdk(self, header: str):
self.symbol_manager.add_header(header)
def process_source_file_for_sdk(self, file_path: str):
visitor = SdkCxxVisitor(self.symbol_manager)
with open(file_path, "rt") as f:
content = f.read()
parser = CxxParser(file_path, content, visitor, None)
parser.parse()
def get_api(self):
return self.symbol_manager.api
def stringify_array_dimension(size_descr):
if not size_descr:
return ""
return stringify_descr(size_descr)
def stringify_array_descr(type_descr):
assert isinstance(type_descr, Array)
return (
stringify_descr(type_descr.array_of),
stringify_array_dimension(type_descr.size),
)
def stringify_descr(type_descr):
if isinstance(type_descr, (NameSpecifier, FundamentalSpecifier)):
return type_descr.name
elif isinstance(type_descr, PQName):
return "::".join(map(stringify_descr, type_descr.segments))
elif isinstance(type_descr, Pointer):
# Hack
if isinstance(type_descr.ptr_to, FunctionType):
return stringify_descr(type_descr.ptr_to)
return f"{stringify_descr(type_descr.ptr_to)}*"
elif isinstance(type_descr, Type):
return (
f"{'const ' if type_descr.const else ''}"
f"{'volatile ' if type_descr.volatile else ''}"
f"{stringify_descr(type_descr.typename)}"
)
elif isinstance(type_descr, Parameter):
return stringify_descr(type_descr.type)
elif isinstance(type_descr, Array):
# Hack for 2d arrays
if isinstance(type_descr.array_of, Array):
argtype, dimension = stringify_array_descr(type_descr.array_of)
return (
f"{argtype}[{stringify_array_dimension(type_descr.size)}][{dimension}]"
)
return f"{stringify_descr(type_descr.array_of)}[{stringify_array_dimension(type_descr.size)}]"
elif isinstance(type_descr, Value):
return " ".join(map(stringify_descr, type_descr.tokens))
elif isinstance(type_descr, FunctionType):
return f"{stringify_descr(type_descr.return_type)} (*)({', '.join(map(stringify_descr, type_descr.parameters))})"
elif isinstance(type_descr, Token):
return type_descr.value
elif type_descr is None:
return ""
else:
raise Exception("unsupported type_descr: %s" % type_descr)
class SdkCxxVisitor:
def __init__(self, symbol_manager: SymbolManager):
self.api = symbol_manager
def on_variable(self, state: State, v: Variable) -> None:
if not v.extern:
return
self.api.add_variable(
ApiEntryVariable(
stringify_descr(v.name),
stringify_descr(v.type),
)
)
def on_function(self, state: State, fn: Function) -> None:
if fn.inline or fn.has_body:
return
self.api.add_function(
ApiEntryFunction(
stringify_descr(fn.name),
stringify_descr(fn.return_type),
", ".join(map(stringify_descr, fn.parameters))
+ (", ..." if fn.vararg else ""),
)
)
def on_define(self, state: State, content: str) -> None:
pass
def on_pragma(self, state: State, content: str) -> None:
pass
def on_include(self, state: State, filename: str) -> None:
pass
def on_empty_block_start(self, state: EmptyBlockState) -> None:
pass
def on_empty_block_end(self, state: EmptyBlockState) -> None:
pass
def on_extern_block_start(self, state: ExternBlockState) -> None:
pass
def on_extern_block_end(self, state: ExternBlockState) -> None:
pass
def on_namespace_start(self, state: NamespaceBlockState) -> None:
pass
def on_namespace_end(self, state: NamespaceBlockState) -> None:
pass
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
pass
def on_typedef(self, state: State, typedef: Typedef) -> None:
pass
def on_using_namespace(self, state: State, namespace: List[str]) -> None:
pass
def on_using_alias(self, state: State, using: UsingAlias) -> None:
pass
def on_using_declaration(self, state: State, using: UsingDecl) -> None:
pass
def on_enum(self, state: State, enum: EnumDecl) -> None:
pass
def on_class_start(self, state: ClassBlockState) -> None:
pass
def on_class_field(self, state: State, f: Field) -> None:
pass
def on_class_method(self, state: ClassBlockState, method: Method) -> None:
pass
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None:
pass
def on_class_end(self, state: ClassBlockState) -> None:
pass
@dataclass(frozen=True)
class SdkVersion:

View File

@ -0,0 +1,238 @@
from typing import List
from cxxheaderparser.parser import CxxParser
from . import (
ApiEntries,
ApiEntryFunction,
ApiEntryVariable,
ApiHeader,
)
# 'Fixing' complaints about typedefs
CxxParser._fundamentals.discard("wchar_t")
from cxxheaderparser.types import (
EnumDecl,
Field,
ForwardDecl,
FriendDecl,
Function,
Method,
Typedef,
UsingAlias,
UsingDecl,
Variable,
Pointer,
Type,
PQName,
NameSpecifier,
FundamentalSpecifier,
Parameter,
Array,
Value,
Token,
FunctionType,
)
from cxxheaderparser.parserstate import (
State,
EmptyBlockState,
ClassBlockState,
ExternBlockState,
NamespaceBlockState,
)
class SymbolManager:
def __init__(self):
self.api = ApiEntries()
self.name_hashes = set()
# Calculate hash of name and raise exception if it already is in the set
def _name_check(self, name: str):
name_hash = gnu_sym_hash(name)
if name_hash in self.name_hashes:
raise Exception(f"Hash collision on {name}")
self.name_hashes.add(name_hash)
def add_function(self, function_def: ApiEntryFunction):
if function_def in self.api.functions:
return
self._name_check(function_def.name)
self.api.functions.add(function_def)
def add_variable(self, variable_def: ApiEntryVariable):
if variable_def in self.api.variables:
return
self._name_check(variable_def.name)
self.api.variables.add(variable_def)
def add_header(self, header: str):
self.api.headers.add(ApiHeader(header))
def gnu_sym_hash(name: str):
h = 0x1505
for c in name:
h = (h << 5) + h + ord(c)
return str(hex(h))[-8:]
class SdkCollector:
def __init__(self):
self.symbol_manager = SymbolManager()
def add_header_to_sdk(self, header: str):
self.symbol_manager.add_header(header)
def process_source_file_for_sdk(self, file_path: str):
visitor = SdkCxxVisitor(self.symbol_manager)
with open(file_path, "rt") as f:
content = f.read()
parser = CxxParser(file_path, content, visitor, None)
parser.parse()
def get_api(self):
return self.symbol_manager.api
def stringify_array_dimension(size_descr):
if not size_descr:
return ""
return stringify_descr(size_descr)
def stringify_array_descr(type_descr):
assert isinstance(type_descr, Array)
return (
stringify_descr(type_descr.array_of),
stringify_array_dimension(type_descr.size),
)
def stringify_descr(type_descr):
if isinstance(type_descr, (NameSpecifier, FundamentalSpecifier)):
return type_descr.name
elif isinstance(type_descr, PQName):
return "::".join(map(stringify_descr, type_descr.segments))
elif isinstance(type_descr, Pointer):
# Hack
if isinstance(type_descr.ptr_to, FunctionType):
return stringify_descr(type_descr.ptr_to)
return f"{stringify_descr(type_descr.ptr_to)}*"
elif isinstance(type_descr, Type):
return (
f"{'const ' if type_descr.const else ''}"
f"{'volatile ' if type_descr.volatile else ''}"
f"{stringify_descr(type_descr.typename)}"
)
elif isinstance(type_descr, Parameter):
return stringify_descr(type_descr.type)
elif isinstance(type_descr, Array):
# Hack for 2d arrays
if isinstance(type_descr.array_of, Array):
argtype, dimension = stringify_array_descr(type_descr.array_of)
return (
f"{argtype}[{stringify_array_dimension(type_descr.size)}][{dimension}]"
)
return f"{stringify_descr(type_descr.array_of)}[{stringify_array_dimension(type_descr.size)}]"
elif isinstance(type_descr, Value):
return " ".join(map(stringify_descr, type_descr.tokens))
elif isinstance(type_descr, FunctionType):
return f"{stringify_descr(type_descr.return_type)} (*)({', '.join(map(stringify_descr, type_descr.parameters))})"
elif isinstance(type_descr, Token):
return type_descr.value
elif type_descr is None:
return ""
else:
raise Exception("unsupported type_descr: %s" % type_descr)
class SdkCxxVisitor:
def __init__(self, symbol_manager: SymbolManager):
self.api = symbol_manager
def on_variable(self, state: State, v: Variable) -> None:
if not v.extern:
return
self.api.add_variable(
ApiEntryVariable(
stringify_descr(v.name),
stringify_descr(v.type),
)
)
def on_function(self, state: State, fn: Function) -> None:
if fn.inline or fn.has_body:
return
self.api.add_function(
ApiEntryFunction(
stringify_descr(fn.name),
stringify_descr(fn.return_type),
", ".join(map(stringify_descr, fn.parameters))
+ (", ..." if fn.vararg else ""),
)
)
def on_define(self, state: State, content: str) -> None:
pass
def on_pragma(self, state: State, content: str) -> None:
pass
def on_include(self, state: State, filename: str) -> None:
pass
def on_empty_block_start(self, state: EmptyBlockState) -> None:
pass
def on_empty_block_end(self, state: EmptyBlockState) -> None:
pass
def on_extern_block_start(self, state: ExternBlockState) -> None:
pass
def on_extern_block_end(self, state: ExternBlockState) -> None:
pass
def on_namespace_start(self, state: NamespaceBlockState) -> None:
pass
def on_namespace_end(self, state: NamespaceBlockState) -> None:
pass
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
pass
def on_typedef(self, state: State, typedef: Typedef) -> None:
pass
def on_using_namespace(self, state: State, namespace: List[str]) -> None:
pass
def on_using_alias(self, state: State, using: UsingAlias) -> None:
pass
def on_using_declaration(self, state: State, using: UsingDecl) -> None:
pass
def on_enum(self, state: State, enum: EnumDecl) -> None:
pass
def on_class_start(self, state: ClassBlockState) -> None:
pass
def on_class_field(self, state: State, f: Field) -> None:
pass
def on_class_method(self, state: ClassBlockState, method: Method) -> None:
pass
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None:
pass
def on_class_end(self, state: ClassBlockState) -> None:
pass

View File

@ -8,7 +8,7 @@ import os
import pathlib
from fbt.elfmanifest import assemble_manifest_data
from fbt.appmanifest import FlipperApplication, FlipperManifestException
from fbt.sdk import SdkCache
from fbt.sdk.cache import SdkCache
import itertools
from ansi.color import fg

View File

@ -4,7 +4,7 @@ from SCons.Action import Action
from SCons.Errors import UserError
# from SCons.Scanner import C
from SCons.Script import Mkdir, Copy, Delete, Entry
from SCons.Script import Entry
from SCons.Util import LogicalLines
import os.path
@ -12,7 +12,8 @@ import posixpath
import pathlib
import json
from fbt.sdk import SdkCollector, SdkCache
from fbt.sdk.collector import SdkCollector
from fbt.sdk.cache import SdkCache
def ProcessSdkDepends(env, filename):
@ -49,15 +50,19 @@ def prebuild_sdk_create_origin_file(target, source, env):
class SdkMeta:
def __init__(self, env):
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.env["SDK_DEFINITION"].name,
"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_script": self.env.subst("${LINKER_SCRIPT_PATH}"),
}
with open(json_manifest_path, "wt") as f:
json.dump(meta_contents, f, indent=4)
@ -68,6 +73,8 @@ class SdkMeta:
class SdkTreeBuilder:
SDK_DIR_SUBST = "SDK_ROOT_DIR"
def __init__(self, env, target, source) -> None:
self.env = env
self.target = target
@ -88,6 +95,8 @@ class SdkTreeBuilder:
self.header_depends = list(
filter(lambda fname: fname.endswith(".h"), depends.split()),
)
self.header_depends.append(self.env.subst("${LINKER_SCRIPT_PATH}"))
self.header_depends.append(self.env.subst("${SDK_DEFINITION}"))
self.header_dirs = sorted(
set(map(os.path.normpath, map(os.path.dirname, self.header_depends)))
)
@ -102,17 +111,33 @@ class SdkTreeBuilder:
)
sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs)
for dir in full_fw_paths:
if dir in sdk_dirs:
filtered_paths.append(
posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir))
)
filtered_paths.extend(
map(
self.build_sdk_file_path,
filter(lambda path: path in sdk_dirs, full_fw_paths),
)
)
sdk_env = self.env.Clone()
sdk_env.Replace(CPPPATH=filtered_paths)
meta = SdkMeta(sdk_env)
sdk_env.Replace(
CPPPATH=filtered_paths,
LINKER_SCRIPT=self.env.subst("${APP_LINKER_SCRIPT}"),
ORIG_LINKER_SCRIPT_PATH=self.env["LINKER_SCRIPT_PATH"],
LINKER_SCRIPT_PATH=self.build_sdk_file_path("${ORIG_LINKER_SCRIPT_PATH}"),
)
meta = SdkMeta(sdk_env, self)
meta.save_to(self.target[0].path)
def build_sdk_file_path(self, orig_path: str) -> str:
return posixpath.normpath(
posixpath.join(
self.SDK_DIR_SUBST,
self.target_sdk_dir_name,
orig_path,
)
).replace("\\", "/")
def emitter(self, target, source, env):
target_folder = target[0]
target = [target_folder.File("sdk.opts")]
@ -128,8 +153,6 @@ class SdkTreeBuilder:
for sdkdir in dirs_to_create:
os.makedirs(sdkdir, exist_ok=True)
shutil.copy2(self.env["SDK_DEFINITION"].path, self.sdk_root_dir.path)
for header in self.header_depends:
shutil.copy2(header, self.sdk_deploy_dir.File(header).path)

View File

@ -48,48 +48,52 @@ class Main(App):
)
self.parser_copy.set_defaults(func=self.copy)
def get_project_filename(self, project, filetype):
def get_project_file_name(self, project: ProjectDir, filetype: str) -> str:
# Temporary fix
project_name = project.project
if project_name == "firmware":
if filetype == "zip":
project_name = "sdk"
elif filetype != "elf":
project_name = "full"
if project_name == "firmware" and filetype != "elf":
project_name = "full"
return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}"
return self.get_dist_file_name(project_name, filetype)
def get_dist_filepath(self, filename):
def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str:
return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}"
def get_dist_file_path(self, filename: str) -> str:
return join(self.output_dir_path, filename)
def copy_single_project(self, project):
def copy_single_project(self, project: ProjectDir) -> None:
obj_directory = join("build", project.dir)
for filetype in ("elf", "bin", "dfu", "json"):
if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")):
shutil.copyfile(
src_file,
self.get_dist_filepath(
self.get_project_filename(project, filetype)
self.get_dist_file_path(
self.get_project_file_name(project, filetype)
),
)
if exists(sdk_folder := join(obj_directory, "sdk")):
with zipfile.ZipFile(
self.get_dist_filepath(self.get_project_filename(project, "zip")),
"w",
zipfile.ZIP_DEFLATED,
) as zf:
for root, dirs, files in walk(sdk_folder):
for file in files:
zf.write(
join(root, file),
relpath(
join(root, file),
sdk_folder,
),
)
for foldertype in ("sdk", "lib"):
if exists(sdk_folder := join(obj_directory, foldertype)):
self.package_zip(foldertype, sdk_folder)
def copy(self):
def package_zip(self, foldertype, sdk_folder):
with zipfile.ZipFile(
self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")),
"w",
zipfile.ZIP_DEFLATED,
) as zf:
for root, _, files in walk(sdk_folder):
for file in files:
zf.write(
join(root, file),
relpath(
join(root, file),
sdk_folder,
),
)
def copy(self) -> int:
self.projects = dict(
map(
lambda pd: (pd.project, pd),
@ -144,12 +148,12 @@ class Main(App):
"-t",
self.target,
"--dfu",
self.get_dist_filepath(
self.get_project_filename(self.projects["firmware"], "dfu")
self.get_dist_file_path(
self.get_project_file_name(self.projects["firmware"], "dfu")
),
"--stage",
self.get_dist_filepath(
self.get_project_filename(self.projects["updater"], "bin")
self.get_dist_file_path(
self.get_project_file_name(self.projects["updater"], "bin")
),
]
if self.args.resources:

View File

@ -23,12 +23,12 @@ if (!(Test-Path -LiteralPath "$repo_root\toolchain")) {
New-Item "$repo_root\toolchain" -ItemType Directory
}
Write-Host -NoNewline "Unziping Windows toolchain.."
Write-Host -NoNewline "Extracting Windows toolchain.."
Add-Type -Assembly "System.IO.Compression.Filesystem"
[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip", "$repo_root\")
[System.IO.Compression.ZipFile]::ExtractToDirectory("$repo_root\$toolchain_zip", "$repo_root\")
Move-Item -Path "$repo_root\$toolchain_dir" -Destination "$repo_root\toolchain\x86_64-windows"
Write-Host "done!"
Write-Host -NoNewline "Clearing temporary files.."
Write-Host -NoNewline "Cleaning up temporary files.."
Remove-Item -LiteralPath "$repo_root\$toolchain_zip" -Force
Write-Host "done!"

View File

@ -21,7 +21,7 @@ appenv = ENV.Clone(
)
appenv.Replace(
LINKER_SCRIPT="application_ext",
LINKER_SCRIPT=appenv.subst("$APP_LINKER_SCRIPT"),
)
appenv.AppendUnique(

View File

@ -32,12 +32,27 @@ else:
],
)
ENV.Append(
ENV.AppendUnique(
LINKFLAGS=[
"-Tfirmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld",
"-specs=nano.specs",
"-specs=nosys.specs",
"-Wl,--gc-sections",
"-Wl,--undefined=uxTopUsedPriority",
"-Wl,--wrap,_malloc_r",
"-Wl,--wrap,_free_r",
"-Wl,--wrap,_calloc_r",
"-Wl,--wrap,_realloc_r",
"-n",
"-Xlinker",
"-Map=${TARGET}.map",
"-T${LINKER_SCRIPT_PATH}",
],
)
ENV.SetDefault(
LINKER_SCRIPT_PATH="firmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld",
)
if ENV["FIRMWARE_BUILD_CFG"] == "updater":
ENV.Append(
IMAGE_BASE_ADDRESS="0x20000000",
@ -47,4 +62,5 @@ else:
ENV.Append(
IMAGE_BASE_ADDRESS="0x8000000",
LINKER_SCRIPT="stm32wb55xx_flash",
APP_LINKER_SCRIPT="application_ext",
)