Jelajahi Sumber

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>
hedger 3 tahun lalu
induk
melakukan
4b921803cb

+ 0 - 17
firmware.scons

@@ -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())
 

+ 44 - 0
scripts/fbt/sdk/__init__.py

@@ -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)

+ 7 - 273
scripts/fbt/sdk.py → scripts/fbt/sdk/cache.py

@@ -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:

+ 238 - 0
scripts/fbt/sdk/collector.py

@@ -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

+ 1 - 1
scripts/fbt_tools/fbt_extapps.py

@@ -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
 

+ 36 - 13
scripts/fbt_tools/fbt_sdk.py

@@ -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)
 

+ 36 - 32
scripts/sconsdist.py

@@ -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,
-                                ),
-                            )
-
-    def copy(self):
+        for foldertype in ("sdk", "lib"):
+            if exists(sdk_folder := join(obj_directory, foldertype)):
+                self.package_zip(foldertype, sdk_folder)
+
+    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:

+ 3 - 3
scripts/toolchain/windows-toolchain-download.ps1

@@ -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!"

+ 1 - 1
site_scons/extapps.scons

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

+ 18 - 2
site_scons/firmwareopts.scons

@@ -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",
     )